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 75800ef0584d31c6ccdd5135579bef715a146c70
parent d8ab0d2c79f3419e9b7ba30082d0ab01dacbb9f1
Author: deurzen <m.deurzen@tum.de>
Date:   Sat,  6 Feb 2021 01:03:33 +0100

implements task_struct allocation tracking

Diffstat:
Mproject/extract_sizeret.py | 31++++++++++++++++++++++++-------
1 file changed, 24 insertions(+), 7 deletions(-)

diff --git a/project/extract_sizeret.py b/project/extract_sizeret.py @@ -19,10 +19,14 @@ break_arg = { "vmalloc_32_user": "rdi", } +break_arg_access = { + "kmem_cache_alloc_node": ("rdi", "struct kmem_cache *", "object_size"), +} + free_funcs = { "kfree": "rdi", "vfree": "rdi", - "kmem_cache_free": "rsi" + "kmem_cache_free": "rsi", } entries = set() @@ -129,9 +133,18 @@ class EntryExitBreakpoint(gdb.Breakpoint): if self.number in entries: # extract size from correct register - if int(frame.read_register(break_arg[frame.name()])) > 0: - size_at_entry = int(frame.read_register(break_arg[frame.name()])) - return None + if frame.name() in break_arg: + size = int(frame.read_register(break_arg[frame.name()])) + if size > 0: + size_at_entry = size + return None + + elif frame.name() in break_arg_access: + (reg, type, field) = break_arg_access[frame.name()] + size = int(gdb.execute(f"p (({type})${reg})->{field}", to_string=True).strip().split(" ")[2]) + if size > 0: + size_at_entry = size + return None elif self.number in exits and size_at_entry is not None: # extract return value, return tuple (size, address) @@ -139,6 +152,8 @@ class EntryExitBreakpoint(gdb.Breakpoint): size_at_entry = None return ret + return None + def type_lookup(self, frame): global types @@ -206,19 +221,21 @@ class Stage3(): with open(self.dictfile, 'r') as dct: types = json.load(dct) - for b in break_arg.keys(): + types["./kernel/fork.c:812"] = "type = struct task_struct *" + + for b in (break_arg.keys() | break_arg_access.keys()): # set breakpoint at function entry, to extract size b_entry = EntryExitBreakpoint(b) self.breakpoints.append(b_entry) entries.add(b_entry.number) - # lookup offset from function entry to retq, account for possibility of >1 retq occurrence + # lookup offset from function entry to ret{,q}, account for possibility of >1 ret{,q} 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 == "ret" or instr == "retq"] - # set breakpoints at function exits (retq), to extract return value + # set breakpoints at function exits (ret{,q}), 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)