diff --git a/.gitignore b/.gitignore index 6c3ac2f5..cc7824ef 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ test.sh docker/Dockerfile docker/cache *.bak +*.o diff --git a/Taskfile.yaml b/Taskfile.yaml index bcb6559c..33afc022 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -20,3 +20,9 @@ tasks: dir: addons cmds: - ./compile-addons.sh {{.CLI_ARGS}} + + compile-kpatch: + dir: kpatch + cmds: + - make clean all + - mv kpatch ../files/board/arpl/overlayfs/opt/arpl/ diff --git a/files/board/arpl/overlayfs/opt/arpl/boot.sh b/files/board/arpl/overlayfs/opt/arpl/boot.sh index 224136aa..84583403 100755 --- a/files/board/arpl/overlayfs/opt/arpl/boot.sh +++ b/files/board/arpl/overlayfs/opt/arpl/boot.sh @@ -9,12 +9,13 @@ loaderIsConfigured || die "Loader is not configured!" # Print text centralized, if variable ${COLUMNS} is defined clear +TITLE="Welcome to Automated Redpill Loader v${ARPL_VERSION}" +printf "\033[1;44m%*s\n" $COLUMNS "" +printf "\033[1;44m%*s\033[A\n" $COLUMNS "" +printf "\033[1;32m%*s\033[0m\n" $(((${#TITLE}+$COLUMNS)/2)) "${TITLE}" +printf "\033[1;44m%*s\033[0m\n" $COLUMNS "" TITLE="BOOTING..." -if [ -z "${COLUMNS}" ]; then - echo -e "\033[1;33m${TITLE}\033[0m" -else - printf "\033[1;33m%*s\033[0m\n" $(((${#TITLE}+${COLUMNS})/2)) "${TITLE}" -fi +printf "\033[1;33m%*s\033[0m\n" $(((${#TITLE}+${COLUMNS})/2)) "${TITLE}" # Check if DSM zImage changed, patch it if necessary ZIMAGE_HASH="`readConfigKey "zimage-hash" "${USER_CONFIG_FILE}"`" diff --git a/files/board/arpl/overlayfs/opt/arpl/common.php b/files/board/arpl/overlayfs/opt/arpl/common.php deleted file mode 100755 index 8fcae197..00000000 --- a/files/board/arpl/overlayfs/opt/arpl/common.php +++ /dev/null @@ -1,245 +0,0 @@ -#!/usr/bin/env php - $num) { - if ($num === null) { - $searchSeq[] = null; - } elseif (is_array($num) && count($num) == 2 && is_int($num[0]) && is_int($num[1]) && $num[0] >= 0 && - $num[0] <= 255 && $num[1] >= 0 && $num[1] <= 255 && $num[0] < $num[1]) { - $searchSeq[] = $num; //Leave them as numeric - } elseif (is_int($num) && $num >= 0 && $num <= 255) { - $searchSeq[] = chr($num); - } else { - perr("Found invalid search sequence at index $idx", true); - } - } - - //$pos denotes start position but it's also used to mark where start of a potential pattern match was found - fseek($fp, $pos); - do { //This loop is optimized for speed - $buf = fread($fp, $bufLen); - if (!isset($buf[$bufLen-1])) { - break; //Not enough data = no match - } - - $successfulLoops = 0; - foreach ($searchSeq as $byteIdx => $seekByte) { - if ($seekByte === null) { //any character - ++$successfulLoops; - continue; - } - - //element in the array can be a range [(int)from,(int)to] or a literal SINGLE byte - //if isset finds a second element it will mean for us that it's an array of 2 elements (as we don't expect - //a string longer than a single byte) - if (isset($seekByte[1])) { - $curByteNum = ord($buf[$byteIdx]); - if ($curByteNum < $seekByte[0] || $curByteNum > $seekByte[1]) { - break; - } - } elseif($buf[$byteIdx] !== $seekByte) { //If the byte doesn't match literally we know it's not a match - break; - } - - ++$successfulLoops; - } - if ($successfulLoops === $bufLen) { - return $pos; - } - - fseek($fp, ++$pos); - $maxToCheck--; - } while (!feof($fp) && $maxToCheck != 0); - - return -1; -} - -/** - * @return resource - */ -function getFileMemMapped(string $path) -{ - $fp = fopen('php://memory', 'r+'); - fwrite($fp, file_get_contents($path)); //poor man's mmap :D - - return $fp; -} - -function saveStreamToFile($fp, string $path) -{ - perr("Saving stream to $path ...\n"); - - $fp2 = fopen($path, 'w'); - fseek($fp, 0); - while (!feof($fp)) { - fwrite($fp2, fread($fp, 8192)); - } - fclose($fp2); - - perr("DONE!\n"); -} - -/** - * Do not call this in time-sensitive code... - */ -function readAt($fp, int $pos, int $len) -{ - fseek($fp, $pos); - return fread($fp, $len); -} diff --git a/files/board/arpl/overlayfs/opt/arpl/crc32.php b/files/board/arpl/overlayfs/opt/arpl/crc32.php deleted file mode 100755 index 1f2799c0..00000000 --- a/files/board/arpl/overlayfs/opt/arpl/crc32.php +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env php - 2) { - fwrite(STDERR, "Usage: " . $argv[0] . " \n"); - die(); -} -echo hash_file('crc32b', $argv[1]); -?> diff --git a/files/board/arpl/overlayfs/opt/arpl/kpatch b/files/board/arpl/overlayfs/opt/arpl/kpatch new file mode 100755 index 00000000..cf716ad7 Binary files /dev/null and b/files/board/arpl/overlayfs/opt/arpl/kpatch differ diff --git a/files/board/arpl/overlayfs/opt/arpl/menu.sh b/files/board/arpl/overlayfs/opt/arpl/menu.sh index f72fef66..2377b6f7 100755 --- a/files/board/arpl/overlayfs/opt/arpl/menu.sh +++ b/files/board/arpl/overlayfs/opt/arpl/menu.sh @@ -180,6 +180,7 @@ function addonMenu() { done < <(availableAddons "${PLATFORM}" "${KVER}") if [ ! -f "${TMP_PATH}/menu" ] ; then dialog --backtitle "`backtitle`" --msgbox "No available addons to add" 0 0 + NEXT="e" continue fi dialog --backtitle "`backtitle`" --menu "Select an addon" 0 0 0 \ @@ -249,12 +250,13 @@ function cmdlineMenu() { while IFS="=" read KEY VALUE; do [ -n "${KEY}" ] && CMDLINE["${KEY}"]="${VALUE}" done < <(readConfigMap "cmdline" "${USER_CONFIG_FILE}") - echo "a \"Add/edit an cmdline item\"" > "${TMP_PATH}/menu" - echo "d \"Delete cmdline item(s)\"" >> "${TMP_PATH}/menu" - echo "s \"Show user cmdline\"" >> "${TMP_PATH}/menu" - echo "m \"Show model/build cmdline\"" >> "${TMP_PATH}/menu" - echo "u \"Show SATA(s) # ports and drives\"" >> "${TMP_PATH}/menu" - echo "e \"Exit\"" >> "${TMP_PATH}/menu" + echo "a \"Add/edit an cmdline item\"" > "${TMP_PATH}/menu" + echo "d \"Delete cmdline item(s)\"" >> "${TMP_PATH}/menu" + echo "c \"Define a custom MAC\"" >> "${TMP_PATH}/menu" + echo "s \"Show user cmdline\"" >> "${TMP_PATH}/menu" + echo "m \"Show model/build cmdline\"" >> "${TMP_PATH}/menu" + echo "u \"Show SATA(s) # ports and drives\"" >> "${TMP_PATH}/menu" + echo "e \"Exit\"" >> "${TMP_PATH}/menu" # Loop menu while true; do dialog --backtitle "`backtitle`" --menu "Choose a option" 0 0 0 \ @@ -296,6 +298,24 @@ function cmdlineMenu() { deleteConfigKey "cmdline.${I}" "${USER_CONFIG_FILE}" done ;; + c) + dialog --backtitle "`backtitle`" --title "User cmdline" \ + --inputbox "Type a custom MAC address" 0 0 "${CMDLINE['mac1']}"\ + 2>${TMP_PATH}/resp + [ $? -ne 0 ] && continue + MAC1="`sed 's/://g' <"${TMP_PATH}/resp"`" + if [ -z "${MAC1}" ]; then + unset CMDLINE["mac1"] + unset CMDLINE["netif_num"] + deleteConfigKey "cmdline.mac1" "${USER_CONFIG_FILE}" + deleteConfigKey "cmdline.netif_num" "${USER_CONFIG_FILE}" + else + CMDLINE["mac1"]="${MAC1}" + CMDLINE["netif_num"]=1 + writeConfigKey "cmdline.mac1" "${MAC1}" "${USER_CONFIG_FILE}" + writeConfigKey "cmdline.netif_num" "1" "${USER_CONFIG_FILE}" + fi + ;; s) ITEMS="" for KEY in ${!CMDLINE[@]}; do diff --git a/files/board/arpl/overlayfs/opt/arpl/patch-boot_params-check.php b/files/board/arpl/overlayfs/opt/arpl/patch-boot_params-check.php deleted file mode 100755 index 4e88d88e..00000000 --- a/files/board/arpl/overlayfs/opt/arpl/patch-boot_params-check.php +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/bin/env php - 3) { - perr("Usage: " . $argv[0] . " []\n", true); -} - -$file = getArgFilePath(1); -perr("\nGenerating patch for $file\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 -$codeAddr = getELFSectionAddr($file, '.init.text', 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. -const PUSH_R12_R15_SEQ = [0x41, [0x54, 0x57]]; -const PUSH_R12_R15_SEQ_LEN = 2; -const LOCK_OR_LOOK_AHEAD = 1024; -const LOCK_OR_PTR_SEQs = [ - [0xF0, 0x80, null, null, null, null, null, 0x01], - [0xF0, 0x80, null, null, null, null, null, 0x02], - [0xF0, 0x80, null, null, null, null, null, 0x04], - [0xF0, 0x80, null, null, null, null, null, 0x08], -]; -const LOCK_OR_PTR_SEQs_NUM = 4; //how many sequences we are expecting -const LOCK_OR_PTR_SEQ_LEN = 8; //length of a single sequence - -$fp = getFileMemMapped($file); //Read the whole file to memory to make fseet/fread much faster -$pos = $codeAddr; //Start from where code starts -$orsPos = null; //When matched it will contain our resulting file offsets to LOCK(); OR BYTE PTR [rip+...],0x calls -perr("Looking for f() candidates...\n"); -do { - $find = findSequenceWithWildcard($fp, PUSH_R12_R15_SEQ, $pos, -1); - if ($find === -1) { - break; //no more "functions" left - } - - perr("\rAnalyzing f() candidate @ " . decTo32bUFhex($pos)); - - //we found something looking like PUSH R12-R15, now find the ORs - $orsPos = []; //file offsets where LOCK() calls should start - $orsPosNum = 0; //Number of LOCK(); OR ptr sequences found - $seqPos = $pos; - foreach (LOCK_OR_PTR_SEQs as $idx => $seq) { - $find = findSequenceWithWildcard($fp, $seq, $seqPos, LOCK_OR_LOOK_AHEAD); - if ($find === -1) { - break; //Seq not found - there's no point to look further - } - - $orsPos[] = $find; - ++$orsPosNum; - $seqPos = $find + LOCK_OR_PTR_SEQ_LEN; //Next search will start after the current sequence code - } - - //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 - if ($orsPosNum !== LOCK_OR_PTR_SEQs_NUM) { - $pos += PUSH_R12_R15_SEQ_LEN; - if ($orsPosNum === 0) { - $pos += LOCK_OR_LOOK_AHEAD; - } - 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) - $seqPtrsDist = []; - perr("\n[?] Found possible f() @ " . decTo32bUFhex($pos) . "\n"); - $ptrOffset = null; - $equalJumps = 0; - foreach (LOCK_OR_PTR_SEQs as $idx => $seq) { - //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] - $seqData = readAt($fp, $orsPos[$idx], LOCK_OR_PTR_SEQ_LEN); - $newPtrOffset = //how far it "jumps" - $orsPos[$idx] + - (unpack('V', $seqData[3] . $seqData[4] . $seqData[5] . $seqData[6])[1]); //u32 bit LE - - if($ptrOffset === null) { - $ptrOffset = $newPtrOffset; //Save the first one to compare in the next loop - ++$equalJumps; - } elseif ($ptrOffset === $newPtrOffset) { - ++$equalJumps; - } - - perr( - "\t[+] Found LOCK-OR#$idx sequence @ " . decTo32bUFhex($orsPos[$idx]) . " => " . - rawToUFhex($seqData) . " [RIP+(dec)$newPtrOffset]\n" - ); - } - if ($equalJumps !== 4) { - perr("\t[-] LOCK-OR PTR offset mismatch - $equalJumps/" . LOCK_OR_PTR_SEQs_NUM . " matched\n"); - //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 - $pos = $orsPos[3]; - continue; - } - - perr("\t[+] All $equalJumps LOCK-OR PTR offsets equal - match found!\n"); - break; -} while(!feof($fp)); - -if ($orsPos === null) { //There's a small chance no candidates with LOCK ORs were found - perr("Failed to find matching sequences", true); -} - -//Patch offsets -foreach ($orsPos as $seqFileOffset) { - //The offset will point at LOCK(), we need to change the OR (0x80 0x0d) to AND (0x80 0x25) so the two bytes after - $seqFileOffset = $seqFileOffset+2; - - perr("Patching OR to AND @ file offset (dec)$seqFileOffset\n"); - fseek($fp, $seqFileOffset); - fwrite($fp, "\x25"); //0x0d is OR, 0x25 is AND -} - -if (!isset($argv[2])) { - perr("No output file specified - discarding data\n"); - exit; -} - -saveStreamToFile($fp, $argv[2]); -fclose($fp); diff --git a/files/board/arpl/overlayfs/opt/arpl/patch-ramdisk-check.php b/files/board/arpl/overlayfs/opt/arpl/patch-ramdisk-check.php deleted file mode 100755 index 0f464051..00000000 --- a/files/board/arpl/overlayfs/opt/arpl/patch-ramdisk-check.php +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env php - 3) { - perr("Usage: " . $argv[0] . " []\n", true); -} - -$file = getArgFilePath(1); -perr("\nGenerating patch for $file\n"); - -//Strings (e.g. error for printk()) reside in .rodata - start searching there to save time -$rodataAddr = getELFSectionAddr($file, '.rodata', 2); - -//Locate the precise location of "ramdisk error" string -$rdErrAddr = getELFStringLoc($file, '3ramdisk corrupt'); - - -//offsets will be 32 bit in ASM and in LE -$errPrintAddr = $rodataAddr + $rdErrAddr; -$errPrintCAddrLEH = decTo32bLEhex($errPrintAddr - 1); //Somehow rodata contains off-by-one sometimes... -$errPrintAddrLEH = decTo32bLEhex($errPrintAddr); -perr("LE arg addr: " . $errPrintCAddrLEH . "\n"); - -$fp = getFileMemMapped($file); //Read the whole file to memory to make fseet/fread much faster - -//Find the printk() call argument -$printkPos = findSequence($fp, hex2raw($errPrintCAddrLEH), 0, DIR_FWD, -1); -if ($printkPos === -1) { - perr("printk pos not found!\n", true); -} -perr("Found printk arg @ " . decTo32bUFhex($printkPos) . "\n"); - -//double check if it's a MOV reg,VAL (where reg is EAX/ECX/EDX/EBX/ESP/EBP/ESI/EDI) -fseek($fp, $printkPos - 3); -$instr = fread($fp, 3); -if (strncmp($instr, "\x48\xc7", 2) !== 0) { - perr("Expected MOV=>reg before printk error, got " . bin2hex($instr) . "\n", true); -} -$dstReg = ord($instr[2]); -if ($dstReg < 192 || $dstReg > 199) { - perr("Expected MOV w/reg operand [C0-C7], got " . bin2hex($instr[2]) . "\n", true); -} -$movPos = $printkPos - 3; -perr("Found printk MOV @ " . decTo32bUFhex($movPos) . "\n"); - -//now we should seek a reasonable amount (say, up to 32 bytes) for a sequence of CALL x => TEST EAX,EAX => JZ -$testPos = findSequence($fp, "\x85\xc0", $movPos, DIR_RWD, 32); -if ($testPos === -1) { - perr("Failed to find TEST eax,eax\n", true); -} - -$jzPos = $testPos + 2; -fseek($fp, $jzPos); -$jz = fread($fp, 2); -if ($jz[0] !== "\x74") { - perr("Failed to find JZ\n", true); -} - -$jzp = "\xEB" . $jz[1]; -perr('OK - patching ' . bin2hex($jz) . " (JZ) to " . bin2hex($jzp) . " (JMP) @ $jzPos\n"); -fseek($fp, $jzPos); //we should be here already -perr("Patched " . fwrite($fp, $jzp) . " bytes in memory\n"); - -if (!isset($argv[2])) { - perr("No output file specified - discarding data\n"); - exit; -} - -if (!isset($argv[2])) { - perr("No output file specified - discarding data\n"); - exit; -} - -saveStreamToFile($fp, $argv[2]); -fclose($fp); diff --git a/files/board/arpl/overlayfs/opt/arpl/patch/ramdisk-common-init-script.patch b/files/board/arpl/overlayfs/opt/arpl/patch/ramdisk-common-init-script.patch index b34f6a18..f550b024 100644 --- a/files/board/arpl/overlayfs/opt/arpl/patch/ramdisk-common-init-script.patch +++ b/files/board/arpl/overlayfs/opt/arpl/patch/ramdisk-common-init-script.patch @@ -13,7 +13,7 @@ echo "Insert basic USB modules..." SYNOLoadModules $USB_MODULES +SYNOLoadModules "usb-storage" -+/addons/addons.sh early; /addons/addons.sh patches ++/addons/addons.sh early # insert Etron USB3.0 drivers diff --git a/files/board/arpl/overlayfs/opt/arpl/vmlinux-to-bzImage.sh b/files/board/arpl/overlayfs/opt/arpl/vmlinux-to-bzImage.sh index 94978c45..d1b463b7 100755 --- a/files/board/arpl/overlayfs/opt/arpl/vmlinux-to-bzImage.sh +++ b/files/board/arpl/overlayfs/opt/arpl/vmlinux-to-bzImage.sh @@ -56,8 +56,7 @@ gzip -cd "${SCRIPT_DIR}/zImage_template.gz" > "${ZIMAGE_MOD}" dd if="${VMLINUX_MOD}" of="${ZIMAGE_MOD}" bs=16494 seek=1 conv=notrunc >"${LOG_FILE}" 2>&1 || dieLog file_size_le "${VMLINUX_MOD}" | dd of="${ZIMAGE_MOD}" bs=15745134 seek=1 conv=notrunc >"${LOG_FILE}" 2>&1 || dieLog -RUN_SIZE=$(objdump -h ${VMLINUX_MOD} | sh "${SCRIPT_DIR}/calc_run_size.sh") +RUN_SIZE=`objdump -h ${VMLINUX_MOD} | sh "${SCRIPT_DIR}/calc_run_size.sh"` size_le $RUN_SIZE | dd of=$ZIMAGE_MOD bs=15745210 seek=1 conv=notrunc >"${LOG_FILE}" 2>&1 || dieLog file_size_le "${VMLINUX_MOD}" | dd of="${ZIMAGE_MOD}" bs=15745244 seek=1 conv=notrunc >"${LOG_FILE}" 2>&1 || dieLog -# cksum $ZIMAGE_MOD # https://blog.box.com/crc32-checksums-the-good-the-bad-and-the-ugly -size_le $(($((16#$(php "${SCRIPT_DIR}/crc32.php" "${ZIMAGE_MOD}"))) ^ 0xFFFFFFFF)) | dd of="${ZIMAGE_MOD}" conv=notrunc oflag=append >"${LOG_FILE}" 2>&1 || dieLog +size_le $(($((16#`crc32 "${ZIMAGE_MOD}" | awk '{print$1}'`)) ^ 0xFFFFFFFF)) | dd of="${ZIMAGE_MOD}" conv=notrunc oflag=append >"${LOG_FILE}" 2>&1 || dieLog diff --git a/files/board/arpl/overlayfs/opt/arpl/zimage-patch.sh b/files/board/arpl/overlayfs/opt/arpl/zimage-patch.sh index 34e1c1b7..f3886277 100755 --- a/files/board/arpl/overlayfs/opt/arpl/zimage-patch.sh +++ b/files/board/arpl/overlayfs/opt/arpl/zimage-patch.sh @@ -12,14 +12,11 @@ echo -n "." # Extract vmlinux /opt/arpl/bzImage-to-vmlinux.sh "${ORI_ZIMAGE_FILE}" "${TMP_PATH}/vmlinux" >"${LOG_FILE}" 2>&1 || dieLog echo -n "." -# Patch boot params -/opt/arpl/patch-boot_params-check.php "${TMP_PATH}/vmlinux" "${TMP_PATH}/vmlinux-mod1" >"${LOG_FILE}" 2>&1 || dieLog -echo -n "." -# Patch ramdisk check -/opt/arpl/patch-ramdisk-check.php "${TMP_PATH}/vmlinux-mod1" "${TMP_PATH}/vmlinux-mod2" >"${LOG_FILE}" 2>&1 || dieLog +# Patch boot params and ramdisk check +/opt/arpl/kpatch "${TMP_PATH}/vmlinux" "${TMP_PATH}/vmlinux-mod" >"${LOG_FILE}" 2>&1 || dieLog echo -n "." # rebuild zImage -/opt/arpl/vmlinux-to-bzImage.sh "${TMP_PATH}/vmlinux-mod2" "${MOD_ZIMAGE_FILE}" >"${LOG_FILE}" 2>&1 || dieLog +/opt/arpl/vmlinux-to-bzImage.sh "${TMP_PATH}/vmlinux-mod" "${MOD_ZIMAGE_FILE}" >"${LOG_FILE}" 2>&1 || dieLog echo -n "." # Update HASH of new DSM zImage diff --git a/kpatch/Makefile b/kpatch/Makefile new file mode 100644 index 00000000..f16a524e --- /dev/null +++ b/kpatch/Makefile @@ -0,0 +1,12 @@ + +CFLAGS = -Wall -pedantic +LDFLAGS = +LIBS = /lib/x86_64-linux-gnu/libelf.a /lib/x86_64-linux-gnu/libz.a + +all: kpatch + +kpatch: main.o + cc $(LDFLAGS) -o $@ $^ $(LIBS) + +clean: + rm -f kpatch *.o diff --git a/kpatch/main.c b/kpatch/main.c new file mode 100644 index 00000000..f601ad53 --- /dev/null +++ b/kpatch/main.c @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2020 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 + * + */ +/** + * A quick tool for patching the ramdisk check in the DSM kernel image + * This lets you tinker with the initial ramdisk contents without disabling mount() features and modules loading + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const int DIR_FWD = 1; +const int DIR_RWD = -1; + +/* Variables */ +int fd; +int verbose = 1, read_only = 0; +Elf *elfHandle; +GElf_Ehdr elfExecHeader; +uint64_t orPos[4], fileSize, rodataAddr, rodataOffs, initTextOffs; +unsigned char *fileData; + +/*****************************************************************************/ +void errorMsg(char *message) { + fprintf(stderr, "%s\n", message); + 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; + + //The function will reside in init code part. We don't care we may potentially search beyond as we expect it to be found + printf("Found .init.text at %lX\n", initTextOffs); + 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#$idx sequence @ %lX => %02X %02X %02X %02X %02X %02X %02X %02X [RIP+%lX]\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"); + } 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; + + i = pos; + do { + if (strncmp((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 (strncmp(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!"); + } + //double check if it's a MOV reg,VAL (where reg is EAX/ECX/EDX/EBX/ESP/EBP/ESI/EDI) + printkPos -= 3; + if (strncmp((const char*)fileData+printkPos, "\x48\xc7", 2) != 0) { + printf("Expected MOV=>reg before printk error, got %02X %02X\n", fileData[printkPos], fileData[printkPos+1]); + errorMsg(""); + } + if (fileData[printkPos+2] < 0xC0 || fileData[printkPos+2] > 0xC7) { + printf("Expected MOV w/reg operand [C0-C7], got %02X\n", fileData[printkPos+2]); + errorMsg(""); + } + 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; +} + +/*****************************************************************************/ +int main(int argc, char *argv[]) { + struct stat fileInf; + Elf_Scn *section; + GElf_Shdr sectionHeader; + char *sectionName; + + if (argc != 3) { + errorMsg("Use: kpatch "); + } + + if (elf_version(EV_CURRENT) == EV_NONE) + elfErrno(); + + if ((fd = open(argv[1], 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"); + break; + case ELF_K_COFF: + errorMsg("COFF binaries not supported"); + break; + case ELF_K_AR: + errorMsg("AR archives not supported"); + 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); + + patchBootParams(); + patchRamdiskCheck(); + if ((fd = open(argv[2], O_WRONLY | O_CREAT, 0644)) == -1) { + errorNum(); + } + if (fileSize != write(fd, fileData, fileSize)) { + errorNum(); + } + close(fd); + printf("\n"); + return 0; +}