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 fe54bdd511b34c48d67eb1382c4c2a6c80f909ab
parent b303c8d5268297ee642f012fa81b4c5172ad21b4
Author: deurzen <m.deurzen@tum.de>
Date:   Wed,  3 Feb 2021 01:05:31 +0100

adds project scripts

Diffstat:
Aproject/extract_sizeret.py | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aproject/occ.sh | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aproject/type_dict.py | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 223 insertions(+), 0 deletions(-)

diff --git a/project/extract_sizeret.py b/project/extract_sizeret.py @@ -0,0 +1,78 @@ +#!/usr/bin/python3 + +import gdb + +# allocator mapped to register containing size argument +break_arg = { + "kmem_cache_alloc_trace": "$rdx", + "kmalloc_order": "$rdi", + "__kmalloc": "$rdi", +} + +# allocator mapped to offset to retq +break_retq = { + "kmem_cache_alloc_trace": 232, + "kmalloc_order": 47, + "__kmalloc": 265, +} + +entries = set() +exits = set() + +prev_entry = None + +class EntryExitBreakpoint(gdb.Breakpoint): + def __init__(self, b): + gdb.Breakpoint.__init__(self, b) + + def stop(self): + global break_arg + global args + global entries + global exits + global prev_entry + + f = gdb.newest_frame() + + if not f.is_valid(): + return False + + if f.unwind_stop_reason() != gdb.FRAME_UNWIND_NO_REASON: + return False + + 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()])}" + + 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) + prev_entry = None + + # TODO: extract filename + + return False + +class Stage3(): + breakpoints = [] + + def __init__(self): + global break_retq + global entries + global exits + + for b, retq in break_retq.items(): + # 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) + +Stage3() diff --git a/project/occ.sh b/project/occ.sh @@ -0,0 +1,59 @@ +#!/bin/bash +#Extract all occurences of function calls and the assigned variables from kernel sources +funcs=("kmalloc" "kzalloc" "kfree") +out=".funcs" + +rm -f cscope.out cscope.files $out + +for f in ${funcs[@]}; do + rm -f $f +done + +if [ $# -eq 0 ]; then + echo "Usage: $0 <kernel src dir>" + exit 0 +fi + +if ! [ -x "$(command -v cscope)" ]; then + echo 'Dependency cscope is missing.' >&2 + exit 1 +fi + +echo "Generating file cscope.files.." +find $1 \ + -path "$1arch/alpha*" -prune -o \ + -path "$1arch/arc*" -prune -o \ + -path "$1arch/arm*" -prune -o \ + -path "$1arch/arm64*" -prune -o \ + -path "$1arch/c6x*" -prune -o \ + -path "$1arch/h8300*" -prune -o \ + -path "$1arch/hexagon*" -prune -o \ + -path "$1arch/ia64*" -prune -o \ + -path "$1arch/m68k*" -prune -o \ + -path "$1arch/microblaze*" -prune -o \ + -path "$1arch/mips*" -prune -o \ + -path "$1arch/nds32*" -prune -o \ + -path "$1arch/nios2*" -prune -o \ + -path "$1arch/openrisc*" -prune -o \ + -path "$1arch/parisc*" -prune -o \ + -path "$1arch/powerpc*" -prune -o \ + -path "$1arch/riscv*" -prune -o \ + -path "$1arch/s390*" -prune -o \ + -path "$1arch/sh*" -prune -o \ + -path "$1arch/sparc*" -prune -o \ + -path "$1arch/um*" -prune -o \ + -path "$1arch/unicore32*" -prune -o \ + -path "$1arch/xtensa*" -prune -o \ + -path "$1drivers*" -prune -o \ + -path "$1Documentation*" -prune -o \ + -path "$1scripts*" -prune -o \ + -path "$1tools*" -prune -o \ + -name "*.[chxsS]" -print > ./cscope.files +echo "Done!" + +echo "Generating occurence database.." +echo "$1" >> $out +for f in ${funcs[@]}; do + cscope -L -0 $f >> $out +done +echo "Done!" +\ No newline at end of file diff --git a/project/type_dict.py b/project/type_dict.py @@ -0,0 +1,85 @@ +################################################################### +# Format of input file: +# First line: +# directory prefix to prune +# +# Rest of lines: +# <filename> <func or global> <line> <var or call to free> +################################################################### + +################################################################### +# Format of dictionary: +# ('filename:line') |-> ('type') +################################################################### + +import json +import os, errno +import gdb + +def delfile(name): + try: + os.remove(name) + except OSError as err: + if err.errno != errno.ENOENT: + raise + +class CodeDict(): + in_n = ".funcs" + out_n = ".dict" + + inf = None + outf = None + + dict = {} + + def __init__(self): + self.setup() + self.parse() + + self.outf.write(json.dumps(self.dict)) + + def setup(self): + try: + self.inf = open(self.in_n, "r") + except: + print(f"No file {in_n} found! Run occ.sh first") + raise + + delfile(self.out_n) + self.outf = open(self.out_n, "w+") + + def parse(self): + ignore = ["*", "->", "(", ")", "[", "]"] + + dir = len(self.inf.readline()) - 1 + + for line in self.inf.readlines(): + # Remove directory prefix, insert ./ to reflect the frame representation of source file in gdb + l = ("./" + (line[dir:])).split(" ") + + src = l[0] + fn = l[1] + lnr = l[2] + var = l[3] + + if any(s in var for s in ignore): + continue + + if fn == "<global>": + try: + type_info = gdb.execute(f"whatis '{src}'::{var}", to_string = True) + except: + continue + else: + try: + type_info = gdb.execute(f"whatis '{fn}'::{var}", to_string = True) + except: + continue + + if type_info is not None: + key = f"{src}:{lnr}" + self.dict[key] = type_info + + print(self.dict) + +CodeDict()