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 d6bc952cf7b0e69f08b9051629d56f77761c5518
parent 0dc9b0c2ebdd4c2c30506d22064cf014e032b957
Author: deurzen <m.deurzen@tum.de>
Date:   Sun, 24 Jan 2021 06:18:11 +0100

adds nop exclusion

Diffstat:
Mmem_forensics/memcheck-gdb.py | 51++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 38 insertions(+), 13 deletions(-)

diff --git a/mem_forensics/memcheck-gdb.py b/mem_forensics/memcheck-gdb.py @@ -621,18 +621,20 @@ class RkCheckFunctions(gdb.Command): print("this might take a while") print("populating dictionaries...", end='', flush=True) - self.fill_code_dict() self.fill_altinstr_dict() self.fill_paravirt_dict() - self.compare_functions() + print(" done!") + print("comparing functions...", end='', flush=True) + self.compare_functions() print(" done!") def fill_code_dict(self): - sym_i = 0 - for symbol in self.s.iter_symbols(): - if sym_i == 100: + for i, symbol in enumerate(self.s.iter_symbols()): + if i < 30195: + continue + if i > 30200: break if symbol.entry["st_info"]["type"] == "STT_FUNC": @@ -644,6 +646,9 @@ class RkCheckFunctions(gdb.Command): if name is None or ".cold." in name or ".part." in name or ".constprop." in name: continue + if size is None or size == 0: + continue + addr = self.get_v_addr(name) if addr is None: continue @@ -676,7 +681,6 @@ class RkCheckFunctions(gdb.Command): elf_bytes = "".join(elf_bytes) self.code_dict[name] = (size, elf_bytes) - sym_i += 1 def fill_altinstr_dict(self): global file_g @@ -759,21 +763,42 @@ class RkCheckFunctions(gdb.Command): def compare_functions(self): for name, (size, elf_bytes) in self.code_dict.items(): - v_addr = self.get_v_addr(name) - live_bytes = gdb.execute(f"xbfunc {v_addr} {size}", to_string=True).split() + disassembly = gdb.execute(f"disass/r {name}", to_string=True).split("\n")[1:-2] + + to_exclude_live = [] + for i, s in enumerate(disassembly): + if "nop" in s: + to_exclude_live.append(i) + + disassembly = [line.split("\t") for line in disassembly] + live_bytes = [line[1].strip().replace(' ', '') for line in disassembly] + + to_exclude = [] + for i in to_exclude_live: + for j in range(len(live_bytes[i])): + to_exclude.append(i + j) + live_bytes = "".join(live_bytes) + int3_chain = ''.join('c' * len(live_bytes)) + if live_bytes == int3_chain: + return + + null_chain = ''.join('0' * len(live_bytes)) + if live_bytes == null_chain: + return + to_exclude_paravirt = [l for r in self.paravirt_dict[name] for l in list(r)] if name in self.paravirt_dict else [] to_exclude_altinstr = [l for r in self.altinstr_dict[name] for l in list(r)] if name in self.altinstr_dict else [] - to_exclude = to_exclude_paravirt + to_exclude_altinstr + to_exclude += to_exclude_paravirt + to_exclude_altinstr if to_exclude: - elf_bytes = [elf_byte for i, elf_byte in enumerate(elf_bytes) if i not in to_exclude] - live_bytes = [elf_byte for i, elf_byte in enumerate(live_bytes) if i not in to_exclude] + elf_bytes = "".join([elf_byte for i, elf_byte in enumerate(elf_bytes) if i not in to_exclude]) + live_bytes = "".join([elf_byte for i, elf_byte in enumerate(live_bytes) if i not in to_exclude]) if live_bytes != elf_bytes: - print(f"function `{name} compromised, live bytes not equal to ELF bytes") - print(f"expected: {elf_bytes}, live: {live_bytes}") + print(f"function `{name}` compromised, live bytes not equal to ELF bytes") + print(f"excluded: {to_exclude}, expected: {elf_bytes}, live: {live_bytes}") def get_v_addr(self, symbol): try: