Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unpacking my submission and small fix in README #40

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions FabioPagani/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,7 @@ I really believe that memory forensics on Linux will be "the next big thing" in

### References
[1] https://www.usenix.org/system/files/sec19-pagani.pdf

[2] https://github.com/emdel/ksfinder

[3] https://github.com/psviderski/volatility-android/blob/master/volatility/plugins/linux/auto_ksymbol.py
116 changes: 116 additions & 0 deletions FabioPagani/kallsyms/kallsyms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/usr/bin/python3
from unicorn_magic import extract_symbols
import tempfile
import struct
import mmap
import sys
import re
import os

DUMP = None
RESULTS_DIR = None
THRESHOLD_KALLSYMS = 2000
THRESHOLD_KSYMTAB = 2000

# Since the ksymtab contains an entry for the function
# kallsyms_on_each_symbol, first of all we find the ksymtab and the
# physical address of "kallsyms_on_each_symbol".

# KASLR randomizes at the page granularity, so page offsets are
# not changed. For this reason, we can search in the symtab all those entries that
# have a name value with the same page offset of the string. At this point
# we know 3 elements of the equation: value_va - name_va =
# value_pa - name_pa and thus we can find value_pa (the physical
# address of the function).

def read_str(address):
s = ""
while "\x00" not in s:
s += chr(DUMP[address])
address+=1
return s[:-1]

def dump_kallsyms(ksyms, va, pa):
ksyms.sort()
filename = os.path.join(RESULTS_DIR, hex(pa))
print("[+] Saving %d kallsyms found with kallsyms_on_each_symbol @ 0x%x in %s" % (len(ksyms), va, filename))
with open(filename, "w") as f:
for value, name in ksyms:
f.write("%016x %s\n" % (value, name))

def extract_kallsyms():
for ksymtab, va, pa in find_kallsyms_on_each_symbol_function():
ksyms = extract_symbols(DUMP, va, pa)
if len(ksyms) > THRESHOLD_KALLSYMS:
# Adding the symbols contained in the ksymtab
for value, name in ksymtab:
name_str = read_str(name - va + pa)
if (value, name_str) not in ksyms:
ksyms.append((value, name_str))

dump_kallsyms(ksyms, va, pa)

# Value can also be a per_cpu pointer, thus the check if is less than 0x100000
def is_valid_entry(value, name):
return name >= 0xffffffff80000000 and (0xffffffff80000000 <= value < 0xffffffffffffffff or value <= 0x100000)

def find_candidate_ksymtab():
ksymtab = []
size = DUMP.size()
ksymtab_len = 0
for i in range(0, size, 16):
if i % 1000000 == 0:
sys.stderr.write('\rDone %.2f%%' % ((i)/size*100))

value, name = struct.unpack("<QQ", DUMP[i:i+16])
if is_valid_entry(value, name):
ksymtab.append((value, name))
ksymtab_len += 1
continue

if ksymtab_len > THRESHOLD_KSYMTAB:
yield ksymtab

ksymtab_len = 0
ksymtab = []

def find_string(s):
for match in re.finditer(s, DUMP):
yield match.start()

# Finds those entries in ksymtab that have page_offset(name) in offsets.
def get_entries_with_name_offset(ksymtab, offsets):
return [(v, n) for (v, n) in ksymtab if n & 0xfff in offsets]

def find_kallsyms_on_each_symbol_function():
name_pas = list(find_string(b"kallsyms_on_each_symbol\x00"))
if len(name_pas) == 0:
print("[-] kallsyms_on_each_symbol string not found, aborting!")
sys.exit(-1)

for name_pa in name_pas:
print("[+] Candidate kallsyms_on_each_symbol string found @ 0x%x" % name_pa)

name_pas_offsets = [n & 0xfff for n in name_pas]

for ksymtab in find_candidate_ksymtab():
print("\n[+] Found a potential ksymtab with: %d elements" % len(ksymtab))
for (value_va, name_va) in get_entries_with_name_offset(ksymtab, name_pas_offsets):
value_pa = (value_va - name_va) + name_pa
print("[+] Candidate kallsyms_on_each_symbol function va: 0x%x pa: 0x%x name: 0x%x" %
(value_va, value_pa, name_va))
yield ksymtab, value_va, value_pa

if __name__ == "__main__":
if len(sys.argv[0]) < 2:
print("Usage: %s dump.raw [DUMP MUST BE IN RAW FORMAT]" % sys.argv[0])
sys.exit(0)

with open(sys.argv[1]) as f:
DUMP = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)

RESULTS_DIR = tempfile.mkdtemp(prefix="kallsyms_")

extract_kallsyms()

print("\n[+] Extracted kallsyms saved in %s" % RESULTS_DIR)
66 changes: 66 additions & 0 deletions FabioPagani/kallsyms/unicorn_magic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from unicorn import *
from unicorn.x86_const import *

def hook_mem_invalid(uc, access, address, size, value, user_data):
# print("Mem_invalid @ 0x%x" % address)
return True

def align_page(a):
return a & ~0xfff

def read_str(uc, address):
s = b""
while b"\x00" not in s:
s += uc.mem_read(address, 1)
address+=1
return s[:-1]

def hook_code64(uc, address, size, user_data):
ksyms, callback_addr = user_data
# print(">>> Tracing instruction at 0x%x, callback at 0x%x " % (address, callback_addr))
if address == callback_addr:
sym_name = read_str(uc, uc.reg_read(UC_X86_REG_RSI)).decode("utf-8")
sym_address = int(uc.reg_read(UC_X86_REG_RCX))

# print("FOUND: 0x%x %s" % (sym_address, sym_name))
ksyms.append((sym_address, sym_name))
uc.reg_write(UC_X86_REG_RAX, 0)

def extract_symbols(dump, kallsyms_on_each_va, kallsyms_on_each_pa):
ksyms = []
mu = Uc(UC_ARCH_X86, UC_MODE_64)

# We read 16mb before and 16mb after, is should be enough to cover all the kernel .text and data.
load_va = align_page(kallsyms_on_each_va - 2**24)
load_pa = align_page(kallsyms_on_each_pa - 2**24)
mem = dump[load_pa:load_pa+2**25]

mu.mem_map(load_va, len(mem))
mu.mem_write(load_va, mem)

# Map the zero page for gs:0x28 accesses
mu.mem_map(0, 4096)
mu.mem_write(0, b"\x00"*4096)

# Setup the stack...
STACK = 0x200000
STACK_SIZE = 0x100000
mu.mem_map(STACK - STACK_SIZE, STACK)
mu.reg_write(UC_X86_REG_RSP, STACK)

mu.reg_write(UC_X86_REG_GS, 0x1000)
# Inject our fake callback function, which consists only of a ret
callback_addr = load_va
mu.mem_write(callback_addr, b"\xc3")
mu.reg_write(UC_X86_REG_RDI, callback_addr)

mu.hook_add(UC_HOOK_CODE, hook_code64, (ksyms, callback_addr))
mu.hook_add(UC_HOOK_MEM_READ_UNMAPPED, hook_mem_invalid)

try:
mu.emu_start(kallsyms_on_each_va, kallsyms_on_each_va+0x20000)
except unicorn.UcError:
# print("unicorn throw an exception, we should be done here..")
pass

return ksyms
Loading