Replaced php scripts for alternatives

Add kpatch C code for patch DSM kernel
Fix ramdisk-common-init-script (wrong merge)
This commit is contained in:
Fabio Belavenuto 2022-07-18 13:15:06 -03:00
parent ea1cf413be
commit c7aab7f297
12 changed files with 358 additions and 489 deletions

1
.gitignore vendored
View File

@ -8,3 +8,4 @@ test.sh
docker/Dockerfile docker/Dockerfile
docker/cache docker/cache
*.bak *.bak
*.o

View File

@ -20,3 +20,9 @@ tasks:
dir: addons dir: addons
cmds: cmds:
- ./compile-addons.sh {{.CLI_ARGS}} - ./compile-addons.sh {{.CLI_ARGS}}
compile-kpatch:
dir: kpatch
cmds:
- make clean all
- mv kpatch ../files/board/arpl/overlayfs/opt/arpl/

View File

@ -1,245 +0,0 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
/**
* This file contains usual common functions used by patchers
*
* Most of the functions here are written to be C-like without utilizing any of the PHP's magic. This functionality is
* ultimately intended to be rewritten into a dynamic patcher in C. Making this code compatible wtih simple C (e.g.
* by not using fancy regexes) will make it slower in PHP but MUCH easier to rewrite into C later on.
*/
function perr(string $txt, $die = false)
{
fwrite(STDERR, $txt);
if ($die) {
die();
}
}
/**
* @return int
*/
function getELFSectionAddr(string $elf, string $section, int $pos)
{
$secAddr = exec(
sprintf('readelf -S \'%1$s\' | grep -E \'\s%2$s\s\' | awk -F\'%2$s\' \'{ print $2 }\' | awk \'{ print $%3$d }\'', $elf, str_replace('.', '\.', $section), $pos)
);
if (!$secAddr) {
perr("$section section not found in $elf file\n", true);
}
$secAddr = hexdec(substr($secAddr, -8));
perr("Found $section at " . decTo32bUFhex($secAddr) . " in $elf\n");
return $secAddr;
}
function getELFStringLoc(string $elf, string $text)
{
$strAddr = exec(
sprintf(
'readelf -p \'.rodata\' \'%s\' | grep \'%s\' | grep -oE \'\[(\s+)?.+\]\' | grep -oE \'[a-f0-9]+\'',
$elf, $text
)
);
if (!$strAddr) {
perr("$text string not found in $elf file's .rodata section\n", true);
}
$secAddr = hexdec(substr($strAddr, -8));
perr("Found \"$text\" at " . decTo32bUFhex($secAddr) . " in $elf\n");
return $secAddr;
}
function getArgFilePath(int $argn)
{
global $argv;
$file = realpath($argv[$argn]);
if (!is_file($file) || !$file) {
perr("Expected a readable file in argument $argn - found none\n", true);
}
return $file;
}
/**
* Converts decimal value to 32-bit little-endian hex value
*/
function decTo32bLEhex(int $dec)
{
$hex = str_pad(dechex($dec), 32 / 8 * 2, 'f', STR_PAD_LEFT); //32-bit hex
return implode('', array_reverse(str_split($hex, 2))); //make it little-endian
}
/**
* Converts decimal value to 32-bit user-friendly (and big-endian) hex value
*
* This function should really be used for printing
*/
function decTo32bUFhex(int $dec)
{
return implode(' ', str_split(str_pad(dechex($dec), 32 / 8 * 2, 'f', STR_PAD_LEFT), 2));
}
function rawToUFhex(string $raw)
{
$out = '';
for($i=0, $iMax = strlen($raw); $i < $iMax; $i++) {
$out .= sprintf('%02x', ord($raw[$i]));
if ($i+1 !== $iMax) {
$out .= ' ';
}
}
return $out;
}
/**
* Convert hex values to their binary/raw counterparts as-is
*/
function hex2raw(string $hex)
{
$bin = '';
for ($i = 0, $iMax = strlen($hex); $i < $iMax; $i += 2) {
$bin .= chr(hexdec($hex[$i] . $hex[$i + 1]));
}
return $bin;
}
const DIR_FWD = 1;
const DIR_RWD = -1;
function findSequence($fp, string $bin, int $pos, int $dir, int $maxToCheck)
{
if ($maxToCheck === -1) {
$maxToCheck = PHP_INT_MAX;
}
$len = strlen($bin);
do {
fseek($fp, $pos);
if (strcmp(fread($fp, $len), $bin) === 0) {
return $pos;
}
$pos = $pos + $dir;
$maxToCheck--;
} while (!feof($fp) && $pos != -1 && $maxToCheck != 0);
return -1;
}
/**
* Locates a pattern of bytes $searchSeqNum in a $fp stream starting from $pos seeking up to $maxToCheck
*
* @param array $searchSeqNum An array containing n elements (where n=length of the searched sequence). Each element can
* be a null (denoting "any byte"), singular hex/int value (e.g. 0xF5), or a range in a form
* of a two-element array (e.g. [0xF0, 0xF7])
*/
function findSequenceWithWildcard($fp, array $searchSeqNum, int $pos, int $maxToCheck)
{
if ($maxToCheck === -1) {
$maxToCheck = PHP_INT_MAX;
}
$bufLen = count($searchSeqNum);
if ($maxToCheck < $bufLen) {
perr("maxToCheck cannot be smaller than search sequence!", true);
}
//Convert all singular value to raw bytes while leaving arrays as numeric (performance reasons). As this loop is
//executed once per pattern it can be sub-optimal but more careful with data validation
$searchSeq = [];
foreach ($searchSeqNum as $idx => $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);
}

View File

@ -1,8 +0,0 @@
#!/usr/bin/env php
<?php
if ($argc < 2 || $argc > 2) {
fwrite(STDERR, "Usage: " . $argv[0] . " <file>\n");
die();
}
echo hash_file('crc32b', $argv[1]);
?>

Binary file not shown.

View File

@ -1,141 +0,0 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
/**
* 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
*
* Usage: php patch-boot_params-check.php vmlinux vmlinux-mod
*/
require __DIR__ . '/common.php';
if ($argc < 2 || $argc > 3) {
perr("Usage: " . $argv[0] . " <inFile> [<outFile>]\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);

View File

@ -1,85 +0,0 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
/**
* 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
*
* Usage: php patch-ramdisk-check.php vmlinux vmlinux-mod
*/
require __DIR__ . '/common.php';
if ($argc < 2 || $argc > 3) {
perr("Usage: " . $argv[0] . " <inFile> [<outFile>]\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);

View File

@ -13,7 +13,7 @@
echo "Insert basic USB modules..." echo "Insert basic USB modules..."
SYNOLoadModules $USB_MODULES SYNOLoadModules $USB_MODULES
+SYNOLoadModules "usb-storage" +SYNOLoadModules "usb-storage"
+/addons/addons.sh early; /addons/addons.sh patches +/addons/addons.sh early
# insert Etron USB3.0 drivers # insert Etron USB3.0 drivers

View File

@ -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 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 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 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 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#`crc32 "${ZIMAGE_MOD}" | awk '{print$1}'`)) ^ 0xFFFFFFFF)) | dd of="${ZIMAGE_MOD}" conv=notrunc oflag=append >"${LOG_FILE}" 2>&1 || dieLog
size_le $(($((16#$(php "${SCRIPT_DIR}/crc32.php" "${ZIMAGE_MOD}"))) ^ 0xFFFFFFFF)) | dd of="${ZIMAGE_MOD}" conv=notrunc oflag=append >"${LOG_FILE}" 2>&1 || dieLog

View File

@ -12,14 +12,11 @@ echo -n "."
# Extract vmlinux # Extract vmlinux
/opt/arpl/bzImage-to-vmlinux.sh "${ORI_ZIMAGE_FILE}" "${TMP_PATH}/vmlinux" >"${LOG_FILE}" 2>&1 || dieLog /opt/arpl/bzImage-to-vmlinux.sh "${ORI_ZIMAGE_FILE}" "${TMP_PATH}/vmlinux" >"${LOG_FILE}" 2>&1 || dieLog
echo -n "." echo -n "."
# Patch boot params # Patch boot params and ramdisk check
/opt/arpl/patch-boot_params-check.php "${TMP_PATH}/vmlinux" "${TMP_PATH}/vmlinux-mod1" >"${LOG_FILE}" 2>&1 || dieLog /opt/arpl/kpatch "${TMP_PATH}/vmlinux" "${TMP_PATH}/vmlinux-mod" >"${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
echo -n "." echo -n "."
# rebuild zImage # 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 "." echo -n "."
# Update HASH of new DSM zImage # Update HASH of new DSM zImage

12
kpatch/Makefile Normal file
View File

@ -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

333
kpatch/main.c Normal file
View File

@ -0,0 +1,333 @@
/*
* Copyright (c) 2020 Fabio Belavenuto <belavenuto@gmail.com>
*
* 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 <belavenuto@gmail.com>
*
* 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <getopt.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <gelf.h>
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 <vmlinux> <output>");
}
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, &sectionHeader) != &sectionHeader)
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;
}