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 9f3ba4948a2d0219cbddfd5654ab40727682f20f
parent 029f0e11d30363a1f6617bb4236a91681e98789f
Author: Tizian Leonhardt <tizianleonhardt@web.de>
Date:   Fri,  5 Feb 2021 13:07:42 +0100

Merging

Diffstat:
Mproject/extract_sizeret.py | 12+++++++++++-
Mproject/type_dict.py | 55++++++++++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 59 insertions(+), 8 deletions(-)

diff --git a/project/extract_sizeret.py b/project/extract_sizeret.py @@ -9,11 +9,20 @@ break_arg = { "kmem_cache_alloc_trace": "rdx", "kmalloc_order": "rdi", "__kmalloc": "rdi", + "vmalloc": "rdi", + "vzalloc": "rdi", + "vmalloc_user": "rdi", + "vmalloc_node": "rdi", + "vzalloc_node": "rdi", + "vmalloc_exec": "rdi", + "vmalloc_32": "rdi", + "vmalloc_32_user": "rdi", } free_funcs = { + # *v*free variants all call kfree "kfree": "rdi", - "kmem_cache_free" : "rsi" + "kmem_cache_free" : "rsi", } entries = set() @@ -69,6 +78,7 @@ class EntryExitBreakpoint(gdb.Breakpoint): (size, address) = extret mem_map[address] = (type, size, caller) + print("Allocating ", (type, size, caller)) return False def extract(self, frame): diff --git a/project/type_dict.py b/project/type_dict.py @@ -15,6 +15,7 @@ import json import os, errno import gdb +import re def delfile(name): try: @@ -42,26 +43,27 @@ class CodeDict(): try: self.inf = open(self.in_n, "r") except: - print(f"No file {in_n} found! Run occ.sh first") + print(f"{in_n} file not found, run `occ.sh` first") raise delfile(self.out_n) self.outf = open(self.out_n, "w+") def parse(self): - ignore = ["*", "->", "(", ")", "[", "]"] - for line in self.inf.readlines(): # Insert ./ to reflect the frame representation of source file in gdb - l = "./" + line + l = ("./" + line).split(" ") + + if len(l) < 5 or l[4] != "=": + continue src = l[0] fn = l[1] lnr = l[2] var = l[3] - if any(s in var for s in ignore): - continue + var = re.split('\-\>|\.', var) + var[0] = re.sub('[.*?]', '', var[0]) if fn == "<global>": try: @@ -70,11 +72,50 @@ class CodeDict(): continue else: try: - type_info = gdb.execute(f"whatis '{fn}'::{var}", to_string = True) + type_info = gdb.execute(f"whatis '{fn}'::{var[0]}", to_string = True) except: continue + if len(var) > 1: + print("looking in", type_info[7:].strip(), "for", var[1:]) + type_info = self.parse_chain(type_info[7:], var, 1) + print("FOUND:", type_info) + if type_info is not None: key = f"{src}:{lnr}" self.dict[key] = type_info.replace('\n','') + + def parse_chain(self, next_type, chain, index): + # we're at the final field access, return its type + if index >= len(chain): + return next_type + + # we need to look for the type of the next field in the field access chain + field = chain[index] + field = re.sub('\[.*?\]', '', field) + + # obtain the fields of the compound type to search through + ptype = gdb.execute(f"ptype {next_type}", to_string = True).split("\n")[1:-2] + + # loop over the compound type's fields, attempt to match field we're looking for + for f in ptype: + # account for possible bit field + bitfield = f.rfind(':') + if bitfield > 0: + f = f[:bitfield] + + # account for possible array + f = re.sub('\[.*?\]', '', f) + + # match on field name, everything preceding it is its type + name = re.search(f"[^_A-Za-z]({field})[^_A-Za-z0-9]", f) + + # field name was found, extract type and recurse if necessary + if bool(name): + return self.parse_chain(f[:name.start(1)], chain, index + 1) + + # field not found + return None + + CodeDict()