/* * Copyright (c) 2022 Fabio Belavenuto * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ /** * Converted from php code by Fabio Belavenuto * * A quick tool for patching the boot_params check in the DSM kernel image * This lets you tinker with the initial ramdisk contents without disabling mount() features and modules loading * * The overall pattern we need to find is: * - an CDECL function * - does "LOCK OR [const-ptr],n" 4x * - values of ORs are 1/2/4/8 respectively * - [const-ptr] is always the same * * Added patch for CMOS_WRITE by Fabio Belavenuto * */ #include #include #include #include #include #include #include #include #include #include #include const int DIR_FWD = 1; const int DIR_RWD = -1; /* Variables */ int fd, verbose = 1, read_only = 0; Elf *elfHandle; GElf_Ehdr elfExecHeader; uint64_t orPos[4], fileSize, rodataAddr, rodataOffs, initTextOffs; unsigned char *fileData; /*****************************************************************************/ void errorMsg(char *fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); fprintf(stderr, "\n"); exit(1); } /*****************************************************************************/ void errorNum() { char str[100] = {0}; perror(str); exit(2); } /*****************************************************************************/ void elfErrno() { int err; if ((err = elf_errno()) != 0) { fprintf(stderr, "%s\n", elf_errmsg(err)); exit(3); } } /*****************************************************************************/ // Finding a function boundary is non-trivial really as patters can vary, we can have multiple exit points, and in CISC // there are many things which may match e.g. "PUSH EBP". Implementing even a rough disassembler is pointless. // However, we can certainly cheat here as we know with CDECL a non-empty function will always contain one or more // PUSH (0x41) R12-R15 (0x54-57) sequences. Then we can search like a 1K forward for these characteristic LOCK OR. uint64_t findPUSH_R12_R15_SEQ(uint64_t start) { uint64_t i; for (i = start; i < fileSize; i++) { if (fileData[i] == 0x41 && (fileData[i + 1] >= 0x54 && fileData[i + 1] <= 0x57)) { return i; } } return -1; } /*****************************************************************************/ //[0xF0, 0x80, null, null, null, null, null, 0xXX], uint64_t findORs(uint64_t start, uint32_t maxCheck) { uint64_t i; int c = 0; uint8_t lb = 0x01; for (i = start; i < fileSize; i++) { if (fileData[i] == 0xF0 && fileData[i + 1] == 0x80 && fileData[i + 7] == lb) { orPos[c++] = i; i += 7; lb <<= 1; } if (c == 4) { break; } if (--maxCheck == 0) { break; } } return c; } /*****************************************************************************/ void patchBootParams() { uint64_t addr, pos; uint64_t newPtrOffset, ptrOffset; int n; printf("Patching boot params.\n"); // The function will reside in init code part. We don't care we may potentially search beyond as we expect it to be found while (initTextOffs < fileSize) { addr = findPUSH_R12_R15_SEQ(initTextOffs); if (addr == -1) break; // no more "functions" left printf("\rAnalyzing f() candidate @ %lX, PUSH @ %lX", initTextOffs, addr); // we found something looking like PUSH R12-R15, now find the ORs n = findORs(initTextOffs, 1024); if (n != 4) { // We can always move forward by the function token length (obvious) but if we couldn't find any LOCK-OR tokens // we can skip the whole look ahead distance. We CANNOT do that if we found even a single token because the next one // might have been just after the look ahead distance initTextOffs += 2; if (n == 0) { initTextOffs += 1024; } continue; // Continue the main search loop to find next function candidate } // We found LOCK(); OR ptr sequences so we can print some logs and collect ptrs (as this is quite expensive) printf("\n[?] Found possible f() @ %lX\n", initTextOffs); ptrOffset = 0; int ec = 0; for (n = 0; n < 4; n++) { // data will have the following bytes: // [0-LOCK()] [1-OR()] [2-BYTE-PTR] [3-OFFS-b3] [4-OFFS-b2] [5-OFFS-b1] [6-OFFS-b1] [7-NUMBER] pos = orPos[n]; // how far it "jumps" newPtrOffset = pos + (fileData[pos + 6] << 24 | fileData[pos + 5] << 16 | fileData[pos + 4] << 8 | fileData[pos + 3]); if (ptrOffset == 0) { ptrOffset = newPtrOffset; ++ec; } else if (ptrOffset == newPtrOffset) { ++ec; } printf("\t[+] Found LOCK-OR#%d sequence @ %lX => %02X %02X %02X %02X %02X %02X %02X %02X [RIP+%lX]\n", n, pos, fileData[pos], fileData[pos + 1], fileData[pos + 2], fileData[pos + 3], fileData[pos + 4], fileData[pos + 5], fileData[pos + 6], fileData[pos + 7], newPtrOffset); } if (ec != 4) { printf("\t[-] LOCK-OR PTR offset mismatch - %d/4 matched\n", ec); // If the pointer checking failed we can at least move beyond the last LOCK-OR found as we know there's no valid // sequence of LOCK-ORs there initTextOffs = orPos[3]; continue; } printf("\t[+] All %d LOCK-OR PTR offsets equal - match found!\n", ec); break; } if (addr == -1) { errorMsg("\nFailed to find matching sequences\n"); } else { // Patch offsets for (n = 0; n < 4; n++) { // The offset will point at LOCK(), we need to change the OR (0x80 0x0d) to AND (0x80 0x25) so the two bytes after pos = orPos[n] + 2; printf("Patching OR to AND @ %lX\n", pos); fileData[pos] = 0x25; } } } /*****************************************************************************/ uint32_t changeEndian(uint32_t num) { return ((num >> 24) & 0xff) | // move byte 3 to byte 0 ((num << 8) & 0xff0000) | // move byte 1 to byte 2 ((num >> 8) & 0xff00) | // move byte 2 to byte 1 ((num << 24) & 0xff000000); // move byte 0 to byte 3 } /*****************************************************************************/ uint64_t findSeq(const char *seq, int len, uint32_t pos, int dir, uint64_t max) { uint64_t i = pos; do { if (memcmp((const char *)fileData + i, seq, len) == 0) { return i; } i += dir; --max; } while (i > 0 && i < fileSize && max > 0); return -1; } /*****************************************************************************/ void patchRamdiskCheck() { uint64_t pos, errPrintAddr; uint64_t printkPos, testPos, jzPos; const char str[] = "3ramdisk corrupt"; printf("Patching ramdisk check.\n"); for (pos = rodataOffs; pos < fileSize; pos++) { if (memcmp(str, (const char *)(fileData + pos), 16) == 0) { pos -= rodataOffs; break; } } errPrintAddr = rodataAddr + pos - 1; printf("LE arg addr: %08lX\n", errPrintAddr); printkPos = findSeq((const char *)&errPrintAddr, 4, 0, DIR_FWD, -1); if (printkPos == -1) { errorMsg("printk pos not found!\n"); } // double check if it's a MOV reg,VAL (where reg is EAX/ECX/EDX/EBX/ESP/EBP/ESI/EDI) printkPos -= 3; if (memcmp((const char *)fileData + printkPos, "\x48\xc7", 2) != 0) { errorMsg("Expected MOV=>reg before printk error, got %02X %02X\n", fileData[printkPos], fileData[printkPos + 1]); } if (fileData[printkPos + 2] < 0xC0 || fileData[printkPos + 2] > 0xC7) { errorMsg("Expected MOV w/reg operand [C0-C7], got %02X\n", fileData[printkPos + 2]); } printf("Found printk MOV @ %08lX\n", printkPos); // now we should seek a reasonable amount (say, up to 32 bytes) for a sequence of CALL x => TEST EAX,EAX => JZ testPos = findSeq("\x85\xc0", 2, printkPos, DIR_RWD, 32); if (testPos == -1) { errorMsg("Failed to find TEST eax,eax\n"); } printf("Found TEST eax,eax @ %08lX\n", testPos); jzPos = testPos + 2; if (fileData[jzPos] != 0x74) { errorMsg("Failed to find JZ\n"); } printf("OK - patching %02X%02X (JZ) to %02X%02X (JMP) @ %08lX\n", fileData[jzPos], fileData[jzPos + 1], 0xEB, fileData[jzPos + 1], jzPos); fileData[jzPos] = 0xEB; } /*****************************************************************************/ void patchCmosWrite() { uint64_t pos, errPrintAddr; uint64_t pr_errPos, testPos, callPos; const char str[] = "3smpboot: %s: this boot have memory training"; printf("Patching call to rtc_cmos_write.\n"); for (pos = rodataOffs; pos < fileSize; pos++) { if (memcmp(str, (const char *)(fileData + pos), 16) == 0) { pos -= rodataOffs; break; } } errPrintAddr = rodataAddr + pos - 1; printf("LE arg addr: %08lX\n", errPrintAddr); pr_errPos = findSeq((const char *)&errPrintAddr, 4, 0, DIR_FWD, -1); if (pr_errPos == -1) { printf("pr_err pos not found - ignoring.\n"); // Some kernels do not have the call, exit without error return; } // double check if it's a MOV reg,VAL (where reg is EAX/ECX/EDX/EBX/ESP/EBP/ESI/EDI) pr_errPos -= 3; if (memcmp((const char *)fileData + pr_errPos, "\x48\xc7", 2) != 0) { errorMsg("Expected MOV=>reg before pr_err error, got %02X %02X\n", fileData[pr_errPos], fileData[pr_errPos + 1]); } if (fileData[pr_errPos + 2] < 0xC0 || fileData[pr_errPos + 2] > 0xC7) { errorMsg("Expected MOV w/reg operand [C0-C7], got %02X\n", fileData[pr_errPos + 2]); } printf("Found pr_err MOV @ %08lX\n", pr_errPos); // now we should seek a reasonable amount (say, up to 64 bytes) for a sequence of // MOV ESI, 0x48 => MOV EDI, 0xFF => MOV EBX, EAX testPos = findSeq("\xBE\x48\x00\x00\x00\xBF\xFF\x00\x00\x00\x89\xC3", 12, pr_errPos, DIR_RWD, 64); if (testPos == -1) { printf("Failed to find MOV ESI, 0x48 => MOV EDI, 0xFF => MOV EBX, EAX\n"); return; } printf("Found MOV ESI, 0x48 => MOV EDI, 0xFF => MOV EBX, EAX @ %08lX\n", testPos); callPos = testPos + 12; if (fileData[callPos] != 0xE8) { errorMsg("Failed to find CALL\n"); } printf("OK - patching %02X (CALL) to 0x90.. (NOPs) @ %08lX\n", fileData[callPos], callPos); for (uint64_t i = 0; i < 5; i++) fileData[callPos + i] = 0x90; } /*****************************************************************************/ int main(int argc, char *argv[]) { struct stat fileInf; Elf_Scn *section; GElf_Shdr sectionHeader; char *sectionName; char *fileIn = NULL, *fileOut = NULL; int onlyBoot = 0, onlyRD = 0, onlyCMOS = 0, c; if (argc < 3) { errorMsg("Use: kpatch (option) \nOptions:\n -b Only bootparams\n -r Only ramdisk\n -c Only CMOS"); } c = 1; while (c < argc) { if (strcmp(argv[c], "-b") == 0) { onlyBoot = 1; } else if (strcmp(argv[c], "-r") == 0) { onlyRD = 1; } else if (strcmp(argv[c], "-c") == 0) { onlyCMOS = 1; } else if (fileIn == NULL) { fileIn = argv[c]; } else { fileOut = argv[c]; break; } ++c; } if (NULL == fileIn) { errorMsg("Please give a input filename"); } if (NULL == fileOut) { errorMsg("Please give a output filename"); } if (elf_version(EV_CURRENT) == EV_NONE) elfErrno(); if ((fd = open(fileIn, O_RDONLY)) == -1) errorNum(); if ((elfHandle = elf_begin(fd, ELF_C_READ, NULL)) == NULL) elfErrno(); if (gelf_getehdr(elfHandle, &elfExecHeader) == NULL) elfErrno(); switch (elf_kind(elfHandle)) { case ELF_K_NUM: case ELF_K_NONE: errorMsg("file type unknown\n"); break; case ELF_K_COFF: errorMsg("COFF binaries not supported\n"); break; case ELF_K_AR: errorMsg("AR archives not supported\n"); break; case ELF_K_ELF: break; } section = NULL; while ((section = elf_nextscn(elfHandle, section)) != NULL) { if (gelf_getshdr(section, §ionHeader) != §ionHeader) elfErrno(); if ((sectionName = elf_strptr(elfHandle, elfExecHeader.e_shstrndx, sectionHeader.sh_name)) == NULL) elfErrno(); if (strcmp(sectionName, ".init.text") == 0) { initTextOffs = sectionHeader.sh_offset; } else if (strcmp(sectionName, ".rodata") == 0) { rodataAddr = sectionHeader.sh_addr & 0xFFFFFFFF; rodataOffs = sectionHeader.sh_offset; } } elfErrno(); /* If there isn't elf_errno set, nothing will happend. */ elf_end(elfHandle); if (fstat(fd, &fileInf) == -1) errorNum(); fileSize = fileInf.st_size; fileData = malloc(fileSize); if (fileSize != read(fd, fileData, fileSize)) { errorNum(); } close(fd); printf("Found .init.text offset @ %lX\n", initTextOffs); printf("Found .rodata address @ %lX\n", rodataAddr); printf("Found .rodata offset @ %lX\n", rodataOffs); if (onlyBoot == 0 && onlyCMOS == 0 && onlyRD == 0) { patchBootParams(); patchRamdiskCheck(); patchCmosWrite(); } else { if (onlyBoot == 1) { patchBootParams(); } if (onlyRD == 1) { patchRamdiskCheck(); } if (onlyCMOS == 1) { patchCmosWrite(); } } if ((fd = open(fileOut, O_WRONLY | O_CREAT, 0644)) == -1) { errorNum(); } if (fileSize != write(fd, fileData, fileSize)) { errorNum(); } close(fd); printf("Finish!\n"); return 0; }