linux-rootkit

Feature-rich interactive rootkit that targets Linux kernel 4.19, accompanied by a dynamic kernel memory analysis GDB plugin for in vivo introspection (e.g. using QEMU)
git clone git://git.deurzen.net/linux-rootkit
Log | Files | Refs

commit 87a7cb3dcfb8c1a04a8360c9dd8053a06d1cf956
parent 7ef784e0a87749c183a9c9c2ba0fd28c6bf82ec7
Author: deurzen <m.deurzen@tum.de>
Date:   Wed,  3 Feb 2021 09:31:16 +0100

updates project script

Diffstat:
Mmem_forensics/memcheck-gdb.py | 5+++--
Mproject/extract_sizeret.py | 32+++++++++++++++++---------------
Dup.sh | 77-----------------------------------------------------------------------------
3 files changed, 20 insertions(+), 94 deletions(-)

diff --git a/mem_forensics/memcheck-gdb.py b/mem_forensics/memcheck-gdb.py @@ -634,9 +634,10 @@ class RkCheckFunctions(gdb.Command): self.fill_paravirt_dict() print(" done!") - print("comparing functions...", end='', flush=True) + print("comparing functions...") + print("listing functions that differ", flush=True) self.compare_functions() - print(" done!") + print("...done!") print(f"{self.diff_count} functions differ, {self.same_count} are equal, {self.skip_count} symbols skipped") diff --git a/project/extract_sizeret.py b/project/extract_sizeret.py @@ -1,6 +1,7 @@ #!/usr/bin/python3 import gdb +import re # allocator mapped to register containing size argument break_arg = { @@ -9,13 +10,6 @@ break_arg = { "__kmalloc": "$rdi", } -# allocator mapped to offset to retq -break_retq = { - "kmem_cache_alloc_trace": 232, - "kmalloc_order": 47, - "__kmalloc": 265, -} - entries = set() exits = set() @@ -42,14 +36,15 @@ class EntryExitBreakpoint(gdb.Breakpoint): if self.number in entries: # extract size from correct register, print for now - prev_entry = f"size={gdb.parse_and_eval(break_arg[f.name()])}" + if int(gdb.parse_and_eval(break_arg[f.name()])) > 0: + prev_entry = f"size={gdb.parse_and_eval(break_arg[f.name()])}" elif self.number in exits: if prev_entry is None: return False # extract return value, print for now - print(f"f{prev_entry}, ret={hex(int(str(gdb.parse_and_eval('$rax')), 10) & (2 ** 64 - 1))}", flush=True) + print(f"{prev_entry}, ret={hex(int(str(gdb.parse_and_eval('$rax')), 10) & (2 ** 64 - 1))}", flush=True) prev_entry = None # TODO: extract filename @@ -60,19 +55,26 @@ class Stage3(): breakpoints = [] def __init__(self): - global break_retq + global break_arg global entries global exits - for b, retq in break_retq.items(): + for b in break_arg.keys(): # set breakpoint at function entry, to extract size b_entry = EntryExitBreakpoint(b) self.breakpoints.append(b_entry) entries.add(b_entry.number) - # set breakpoint at function exit (retq), to extract return value - b_exit = EntryExitBreakpoint(f"*{hex(int(str(gdb.parse_and_eval(b).address).split(' ')[0], 16) + retq)}") - self.breakpoints.append(b_exit) - exits.add(b_exit.number) + # lookup offset from function entry to retq, account for possibility of >1 retq occurrence + disass = gdb.execute(f"disass {b}", to_string=True).strip().split("\n") + disass = [instr.split("\t") for instr in disass] + instrs = [(instr[0].strip(), instr[1].split(" ")[0].strip()) for instr in disass if len(instr) > 1] + retqs = [int(loc.split("<")[1].split(">")[0]) for (loc, instr) in instrs if instr == "retq"] + + # set breakpoints at function exits (retq), to extract return value + for retq in retqs: + b_exit = EntryExitBreakpoint(f"*{hex(int(str(gdb.parse_and_eval(b).address).split(' ')[0], 16) + retq)}") + self.breakpoints.append(b_exit) + exits.add(b_exit.number) Stage3() diff --git a/up.sh b/up.sh @@ -1,77 +0,0 @@ -#!/usr/bin/env bash - -while (( "$#" )); do - case "$1" in - -h|--help) - echo "$(basename $0): start RKP QEMU instance" - echo "options:" - echo " --debug: have QEMU listen to an incoming gdb connection on :1234" - echo " --block: block before QEMU start" - echo " --ssh: directly connect to instance over ssh" - exit - ;; - --init) - INITREPO=1 - shift - ;; - --debug) - PARAMS="$PARAMS -s" - GDBSET=1 - shift - ;; - --block) - PARAMS="$PARAMS -S" - BLOCKSET=1 - shift - ;; - --ssh) - EXPECTSSH=1 - shift - ;; - --) - shift - break - ;; - -*|--*=) - >&2 echo "invalid option: $1" - exit 1 - ;; - *) - PARAMS="$PARAMS $1" - shift - ;; - esac -done - -if ! test -z $INITREPO; then - if ! test -e ./debian-10.6.0-amd64-netinst.iso; then - wget https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-10.6.0-amd64-netinst.iso - fi - - if ! test -e ./linux-image-4.19.0-12-amd64-dbg_4.19.152-1_amd64.deb; then - wget http://security.debian.org/debian-security/pool/updates/main/l/linux/linux-image-4.19.0-12-amd64-dbg_4.19.152-1_amd64.deb - fi - - if ! test -e ./debian.img; then - qemu-img create -f qcow2 debian.img 20G - qemu-system-x86_64 -hda debian.img -cdrom debian-10.6.0-amd64-netinst.iso -boot d -m 4096 - fi - - echo "!!! add nokaslr to GRUB_CMDLINE_LINUX_DEFAULT and" - echo "!!! set GRUB_HIDDEN_TIMEOUT=0 and GRUB_HIDDEN_TIMEOUT_QUIET=true" - echo "!!! in /etc/default/grub, then run grub-mkconfig" -fi - -if ! test -z $BLOCKSET && test -z $GDBSET; then - >&2 echo "--debug flag must be set with --block" - exit 1 -fi - -qemu-system-x86_64 -gdb tcp::1234 -hda debian.img -m 4096 -enable-kvm -cpu host -device e1000,netdev=net0 -netdev user,id=net0,hostfwd=tcp::2222-:22 & - -if ! test -z $GDBSET; then - test -z $EXPECTSSH || st -e ./ssh.expect & - exec sh -c 'gdb -q -ex "target remote :1234"' -else - test -z $EXPECTSSH || exec ./ssh.expect -fi