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 54966ee319deae1c4a11f57c2e56e0db7b93ade8
parent 42cf3ffe4a78d79c2fc49a166971671e24cb6024
Author: deurzen <m.deurzen@tum.de>
Date:   Sun, 24 Jan 2021 09:25:02 +0100

adds initial relatext data retrieval

Diffstat:
Mmem_forensics/memcheck-gdb.py | 100++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Aruntime/md5sum | 1+
2 files changed, 83 insertions(+), 18 deletions(-)

diff --git a/mem_forensics/memcheck-gdb.py b/mem_forensics/memcheck-gdb.py @@ -588,6 +588,12 @@ class RkCheckFunctions(gdb.Command): symbols = None headers = None + use_memoization = False + + same_count = 0 + diff_count = 0 + skip_count = 0 + #Key: symbol, value: tuple (size, code bytes from ELF file) code_dict = {} @@ -595,6 +601,8 @@ class RkCheckFunctions(gdb.Command): altinstr_dict = {} paravirt_dict = {} + relatext_dict = {} + def __init__(self): super(RkCheckFunctions, self).__init__("rk-check-functions", gdb.COMMAND_USER, gdb.COMMAND_DATA) @@ -606,15 +614,19 @@ class RkCheckFunctions(gdb.Command): print("no object file has been read in to calculate offsets, please run `rk-load-symbols` first") return None - found = False - for root, _, files in os.walk("."): - if "xbfunc.gdb" in files: - found = True - gdb.execute(f'source {os.path.join(root, "xbfunc.gdb")}') + md5sum = subprocess.check_output(f"md5sum {file_g}", shell=True).split()[0] - if not found: - print("could not locate the `xbfunc.gdb` file that is required to perform the function check") - return None + path = None + for root, dirs, files in os.walk("."): + if "runtime" in dirs: + path = os.path.join(root, f"runtime") + break + + if path: + with open(f"{path}/md5sum") as f: + if md5sum.decode(sys.stdout.encoding) == f.readline().strip(): + print("using memoized ELF data stored in `runtime/{func,altinstr,paravirt}`") + self.use_memoization = True self.f = elffile.ELFFile(open(file_g, "rb")) self.s = self.f.get_section_by_name(".symtab") @@ -624,33 +636,40 @@ class RkCheckFunctions(gdb.Command): self.fill_code_dict() self.fill_altinstr_dict() self.fill_paravirt_dict() + self.fill_relatext_dict() print(" done!") print("comparing functions...", end='', flush=True) self.compare_functions() print(" done!") + print(f"{self.diff_count} functions differ, {self.same_count} are equal, {self.skip_count} skipped") + def fill_code_dict(self): for i, symbol in enumerate(self.s.iter_symbols()): - # if i < 30195: - # continue - if i > 2000: + if i < 800: + continue + if i > 1000: break if symbol.entry["st_info"]["type"] == "STT_FUNC": name = symbol.name size = symbol.entry["st_size"] else: + self.skip_count += 1 continue if name is None or ".cold." in name or ".part." in name or ".constprop." in name: + self.skip_count += 1 continue if size is None or size == 0: + self.skip_count += 1 continue addr = self.get_v_addr(name) if addr is None: + self.skip_count += 1 continue objdump = subprocess.check_output(f"objdump -z --disassemble={name} {file_g}", shell=True) @@ -720,19 +739,26 @@ class RkCheckFunctions(gdb.Command): else: self.altinstr_dict[key] = [value] - i = i + alt_instr_sz + i += alt_instr_sz def fill_paravirt_dict(self): global file_g global v_off_g # paravirt_patch_site layout (read from elf section .parainstructions, size with padding: 16 bytes): - # .quad instr <-- Adress to instruction = instr + v_off_G + # .quad instr <-- Adress to instruction = instr + v_off_g # .byte instrtype # .byte len # .short clobbers # 4 byte padding + # struct paravirt_patch_site { + # u8 *instr; /* original instructions */ + # u8 instrtype; /* type of this instruction */ + # u8 len; /* length of original instruction */ + # u16 clobbers; /* what registers you may clobber */ + #}; + sec = self.f.get_section_by_name(".parainstructions") data = sec.data() @@ -759,7 +785,33 @@ class RkCheckFunctions(gdb.Command): else: self.paravirt_dict[key] = [value] - i = i + paravirt_patch_site_sz + i += paravirt_patch_site_sz + + def fill_relatext_dict(self): + global file_g + global v_off_g + + # typedef __u64 Elf64_Addr; + # typedef __u64 Elf64_Xword; + # typedef __s64 Elf64_Sxword; + # + # typedef struct elf64_rela { + # Elf64_Addr r_offset; /* Location at which to apply the action */ + # Elf64_Xword r_info; /* index and type of relocation */ + # Elf64_Sxword r_addend; /* Constant addend used to compute value */ + # } Elf64_Rela; + + sec = self.f.get_section_by_name(".rela.text") + data = sec.data() + + for reloc in sec.iter_relocations(): + addr = reloc['r_offset'] + v_off_g + info = reloc['r_info'] + addend = reloc['r_addend'] + + print('offset = %s' % hex(addr)) + print('info = %s' % hex(info)) + print('addend = %s' % hex(addend)) def compare_functions(self): for name, (size, elf_bytes) in self.code_dict.items(): @@ -785,24 +837,36 @@ class RkCheckFunctions(gdb.Command): if len(live_bytes) > 1 and live_bytes[0:2] == "cc": int3_chain = ''.join('c' * len(live_bytes)) if live_bytes == int3_chain: + self.skip_count += 1 return if len(live_bytes) > 1 and live_bytes[0:2] == "00": null_chain = ''.join('0' * len(live_bytes)) if live_bytes == null_chain: + self.skip_count += 1 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_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 + if 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]) + 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: + self.diff_count += 1 print(f"function `{name}` compromised, live bytes not equal to ELF bytes") print(f"excluded: {to_exclude}, expected: {elf_bytes}, live: {live_bytes}") + else: + self.same_count += 1 def get_v_addr(self, symbol): try: diff --git a/runtime/md5sum b/runtime/md5sum @@ -0,0 +1 @@ +15f3d058efb6b51899fb6a6175b565f9