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 da0f3f5df02d660fe08f2789eabfcc41d6efe5a5
parent bf50144b57dbddac57a47ade8a2da68f149e465e
Author: deurzen <m.deurzen@tum.de>
Date:   Sun,  7 Feb 2021 13:38:43 +0100

implements critical value handling, debug refactoring

Diffstat:
Mproject/extract_sizeret.py | 113++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
1 file changed, 72 insertions(+), 41 deletions(-)

diff --git a/project/extract_sizeret.py b/project/extract_sizeret.py @@ -3,6 +3,7 @@ import gdb import re import json +from enum import IntEnum # allocator |-> register containing size argument break_arg = { @@ -23,16 +24,20 @@ break_arg_access = { "kmem_cache_alloc_node": ("rdi", "struct kmem_cache *", "object_size"), } -# { type -> [field chain] } -# Make sure each entry in a field chain is a pointer, -# as it is dereferenced to obtain the next field -watch_write_field_chain = { +# { type |-> [(access chain, critical value)] } +# +# Make sure each entry in an access chain (apart from the last entry) +# is a pointer, as it is dereferenced to obtain the next field +# +# If `critical_value` is set to None, any changes to the field are reported +watch_write_access_chain = { "struct task_struct *": [ # (((struct task_struct *)<address>)->real_cred)->uid - ["real_cred", "uid"], + (["real_cred", "uid"], 0), ] } +avail_hw_breakpoints = 4 watchpoints = {} n_watchpoints = 0 @@ -52,7 +57,13 @@ mem_map = {} size_at_entry = None -debug = True +class DebugLevel(IntEnum): + __order__ = 'WARN INFO TRACE' + WARN = 0 + INFO = 1 + TRACE = 2 + +debug_level = DebugLevel.INFO class RkPrintMem(gdb.Command): def __init__(self): @@ -71,12 +82,12 @@ RkPrintMem() class RkDebug(gdb.Command): def __init__(self): - super(RkDebug, self).__init__("rk-debug", gdb.COMMAND_USER) + super(RkDebug, self).__init__("rk-debug_level", gdb.COMMAND_USER) def invoke(self, arg, from_tty): - global debug - debug = not debug - print(f"Debug messages set to {debug}") + global debug_level + debug_level = DebugLevel((int(debug_level) + 1) % len(list(map(int, DebugLevel)))) + print(f"debug_level messages set to {debug_level.name}") RkDebug() @@ -110,9 +121,10 @@ class EntryExitBreakpoint(gdb.Breakpoint): gdb.Breakpoint.__init__(self, b) def stop(self): + global avail_hw_breakpoints global watchpoints global n_watchpoints - global watch_write_field_chain + global watch_write_access_chain global mem_map frame = gdb.newest_frame() @@ -139,20 +151,22 @@ class EntryExitBreakpoint(gdb.Breakpoint): mem_map[address] = (type, size, caller) - if type[7:] in watch_write_field_chain: - field_chains = watch_write_field_chain[type[7:]] - for field_chain in field_chains: - if n_watchpoints + len(field_chain) <= 4: + if type[7:] in watch_write_access_chain: + access_chains = watch_write_access_chain[type[7:]] + for access_chain, critical_value in access_chains: + if n_watchpoints + len(access_chain) <= avail_hw_breakpoints: + watchpoint = WriteWatchpoint(address, type[7:], access_chain, critical_value) + if address in watchpoints: - watchpoints[address].append(WriteWatchpoint(address, type[7:], field_chain)) + watchpoints[address].append(watchpoint) else: - watchpoints[address] = [WriteWatchpoint(address, type[7:], field_chain)] + watchpoints[address] = [watchpoint] - n_watchpoints += len(field_chain) - if n_watchpoints >= 4: + n_watchpoints += len(access_chain) + if n_watchpoints >= avail_hw_breakpoints: break - if debug: + if debug_level >= DebugLevel.TRACE: print("Allocating", (type, size, caller), "at", hex(address)) return False @@ -173,7 +187,9 @@ class EntryExitBreakpoint(gdb.Breakpoint): 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]) + size = int(gdb.execute(f"p (({type})${reg})->{field}", + to_string=True).strip().split(" ")[2]) + if size > 0: size_at_entry = size return None @@ -216,7 +232,7 @@ class FreeBreakpoint(gdb.Breakpoint): global watchpoints global n_watchpoints global free_funcs - global debug + global debug_level frame = gdb.newest_frame() @@ -230,14 +246,17 @@ class FreeBreakpoint(gdb.Breakpoint): if address in watchpoints: for watchpoint in watchpoints[address]: - print("Deleting watchpoint on", watchpoint.current_chain, "which is at", hex(address)) + if debug_level >= DebugLevel.INFO: + print("Deleting watchpoint on", watchpoint.current_chain, + "which is at", hex(address)) + watchpoint.delete() - n_watchpoints -= len(watchpoint.field_chain) + n_watchpoints -= len(watchpoint.access_chain) del(watchpoints[address]) if address in mem_map: - if debug: + if debug_level >= DebugLevel.TRACE: print("Freeing", mem_map[address], "at", hex(address)) mem_map.pop(address) @@ -246,48 +265,60 @@ class FreeBreakpoint(gdb.Breakpoint): class WriteWatchpoint(gdb.Breakpoint): address = None type = None - field_chain = None + access_chain = None + critical_value = None previous_value = None previous_value_print = None - def __init__(self, address, type, field_chain): + def __init__(self, address, type, access_chain, critical_value): global watchpoints self.address = address self.type = type - self.field_chain = field_chain + self.access_chain = access_chain + self.critical_value = critical_value current_chain = f"(({type}){hex(address)})" - for field in field_chain: + for field in access_chain: current_chain = "(" + current_chain + "->" + field + ")" self.previous_value = self.get_value(current_chain) self.previous_value_print = self.get_value_print(current_chain) - print("Setting watchpoint on", current_chain, "which is at", hex(address)) + if debug_level >= DebugLevel.INFO: + print("Setting watchpoint on", current_chain, "which is at", hex(address)) + self.current_chain = current_chain gdb.Breakpoint.__init__(self, current_chain, internal=True, type=gdb.BP_WATCHPOINT) def stop(self): current_chain = f"(({self.type}){hex(self.address)})" - for field in self.field_chain: + for field in self.access_chain: current_chain = "(" + current_chain + "->" + field + ")" current_value = self.get_value(current_chain) - if self.previous_value is not None and self.previous_value != current_value: - current_value_print = self.get_value_print(current_chain) + current_value_print = self.get_value_print(current_chain) - print(current_chain, "changed from", self.previous_value_print, - "to", current_value_print) + if self.previous_value is not None and current_value is not None: + if self.previous_value != current_value: + if debug_level >= DebugLevel.INFO: + print(current_chain, "changed from", self.previous_value_print, + "to", current_value_print) - self.previous_value = current_value - self.previous_value_print = current_value_print + if debug_level >= DebugLevel.WARN: + current_value = int.from_bytes(bytes(current_value), "little") + if current_value == self.critical_value: + print(f"WARNING: critical value {self.critical_value} set to {current_chain}") + + self.previous_value = current_value + self.previous_value_print = current_value_print return False def get_value_print(self, name): try: - return "\n".join([line.strip() for line in gdb.execute(f"p {name}", to_string=True).strip().split("\n")[1:-1]]) + return "\n".join([line.strip() for line in + gdb.execute(f"p {name}", to_string=True).strip().split("\n")[1:-1]]) except: return None @@ -295,17 +326,17 @@ class WriteWatchpoint(gdb.Breakpoint): try: size = int(gdb.parse_and_eval(f"sizeof({name})")) except: - return 0 + return None try: address = int(gdb.execute(f"p &({name})", to_string = True).strip().split(" ")[-1], 16) except: - return 0 + return None try: return gdb.selected_inferior().read_memory(address, size) except: - return 0 + return None class Stage3(): breakpoints = []