Skip to content

Commit edc4ec6

Browse files
authored
Merge pull request #64 from mrexodia/win32k-syscalls
Add support for win32k syscalls
2 parents 0d13ad0 + 318efc5 commit edc4ec6

File tree

3 files changed

+71
-22
lines changed

3 files changed

+71
-22
lines changed

src/dumpulator/details.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ def __getitem__(self, index):
395395
regs = self._regs
396396

397397
if not self._x64:
398-
arg_addr = regs.esp + (index + 2) * 4
398+
arg_addr = regs.esp + (index + 1) * 4
399399
data = self._memory.read(arg_addr, 4)
400400
return struct.unpack("<I", data)[0]
401401

src/dumpulator/dumpulator.py

+62-18
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ def __init__(self, minidump_file, *, trace=False, quiet=False, thread_id=None, d
296296
self.debug(f"total commit: {hex(self._pages.total_commit)}, pages: {self._pages.total_commit // PAGE_SIZE}")
297297
self._setup_modules()
298298
self.syscalls = []
299+
self.win32k_syscalls = []
299300
self._setup_syscalls()
300301
self._setup_emulator(thread)
301302
self.handles = HandleManager()
@@ -779,10 +780,10 @@ def _setup_modules(self):
779780
def _setup_syscalls(self):
780781
# Load the ntdll module from memory
781782
ntdll = self.modules["ntdll.dll"]
782-
syscalls = []
783+
nt_syscalls = []
783784
for export in ntdll.exports:
784785
if export.name and export.name.startswith("Zw"):
785-
syscalls.append((export.address, export.name))
786+
nt_syscalls.append((export.address, export.name))
786787
elif export.name == "Wow64Transition":
787788
patch_addr = self.read_ptr(export.address)
788789
self.info(f"Patching Wow64Transition: {hex(export.address)} -> {hex(patch_addr)}")
@@ -795,20 +796,46 @@ def _setup_syscalls(self):
795796
elif export.name == "LdrLoadDll":
796797
self.LdrLoadDll = export.address
797798

798-
syscalls.sort()
799-
for index, (rva, name) in enumerate(syscalls):
800-
cb = syscall_functions.get(name, None)
801-
argcount = 0
802-
if cb:
803-
argspec = inspect.getfullargspec(cb)
804-
argcount = len(argspec.args) - 1
805-
self.syscalls.append((name, cb, argcount))
799+
def add_syscalls(syscalls, table):
800+
# The index when sorting by RVA is the syscall index
801+
syscalls.sort()
802+
for index, (rva, name) in enumerate(syscalls):
803+
cb = syscall_functions.get(name, None)
804+
argcount = 0
805+
if cb:
806+
argspec = inspect.getfullargspec(cb)
807+
argcount = len(argspec.args) - 1
808+
table.append((name, cb, argcount))
809+
810+
add_syscalls(nt_syscalls, self.syscalls)
811+
812+
# Get the syscalls for win32u
813+
win32u = self.modules.find("win32u.dll")
814+
if win32u is not None:
815+
win32k_syscalls = []
816+
for export in win32u.exports:
817+
if export.name and export.name.startswith("Nt"):
818+
win32k_syscalls.append((export.address, export.name))
819+
820+
add_syscalls(win32k_syscalls, self.win32k_syscalls)
821+
806822

807823
def push(self, value):
808824
csp = self.regs.csp - self.ptr_size()
809825
self.write_ptr(csp, value)
810826
self.regs.csp = csp
811827

828+
def pop(self):
829+
csp = self.regs.csp
830+
value = self.read_ptr(csp)
831+
self.regs.csp = csp + self.ptr_size()
832+
return value
833+
834+
def ret(self, imm=0):
835+
return_address = self.pop()
836+
self.regs.csp -= imm
837+
return return_address
838+
812839
def read(self, addr, size):
813840
if not isinstance(addr, int):
814841
addr = int(addr)
@@ -1464,19 +1491,36 @@ def _hook_syscall(uc: Uc, dp: Dumpulator):
14641491
# Flush the trace for easier debugging
14651492
if dp.trace is not None:
14661493
dp.trace.flush()
1467-
index = dp.regs.cax & 0xffff
1468-
if index < len(dp.syscalls):
1469-
name, syscall_impl, argcount = dp.syscalls[index]
1494+
1495+
# Extract the table and function number from eax
1496+
service_number = dp.regs.cax & 0xffff
1497+
table_number = (service_number >> 12) & 0xf # 0: ntoskrnl, 1: win32k
1498+
function_index = service_number & 0xfff
1499+
if table_number == 0:
1500+
table = dp.syscalls
1501+
table_prefix = ""
1502+
elif table_number == 1:
1503+
table = dp.win32k_syscalls
1504+
table_prefix = "win32k "
1505+
else:
1506+
table = []
1507+
table_prefix = f"unknown:{table_number} "
1508+
1509+
if function_index < len(table):
1510+
name, syscall_impl, argcount = table[function_index]
14701511
if syscall_impl:
14711512
argspec = inspect.getfullargspec(syscall_impl)
14721513
args = []
14731514

14741515
def syscall_arg(index):
1516+
# There is an extra call that adds a return address to the stack
1517+
if dp.wow64:
1518+
index += 1
14751519
if index == 0 and dp.ptr_size() == 8:
14761520
return dp.regs.r10
14771521
return dp.args[index]
14781522

1479-
dp.info(f"[{dp.sequence_id}] syscall (index: {hex(index)}): {name}(")
1523+
dp.info(f"[{dp.sequence_id}] {table_prefix}syscall: {name}( /* index: {hex(service_number)} */")
14801524
for i in range(0, argcount):
14811525
argname = argspec.args[1 + i]
14821526
argtype = argspec.annotations[argname]
@@ -1499,9 +1543,9 @@ def syscall_arg(index):
14991543
argvalue = argtype(dp, argvalue)
15001544
elif issubclass(argtype, Enum):
15011545
try:
1502-
argvalue = argtype(dp.args[i] & 0xFFFFFFFF)
1546+
argvalue = argtype(argvalue & 0xFFFFFFFF)
15031547
except KeyError as x:
1504-
raise Exception(f"Unknown enum value {dp.args[i]} for {type(argtype)}") from None
1548+
raise Exception(f"Unknown enum value {argvalue} for {type(argtype)}") from None
15051549
else:
15061550
argvalue = argtype(argvalue)
15071551
args.append(argvalue)
@@ -1535,9 +1579,9 @@ def syscall_arg(index):
15351579
finally:
15361580
dp.sequence_id += 1
15371581
else:
1538-
raise dp.raise_kill(NotImplementedError(f"syscall index: {index:x} -> {name} not implemented!")) from None
1582+
raise dp.raise_kill(NotImplementedError(f"{table_prefix}syscall {hex(service_number)} -> {name} not implemented!")) from None
15391583
else:
1540-
raise dp.raise_kill(IndexError(f"syscall index {index:x} out of range")) from None
1584+
raise dp.raise_kill(IndexError(f"{table_prefix}syscall {hex(service_number)} (index: {hex(function_index)}) out of range")) from None
15411585

15421586
def _emulate_unsupported_instruction(dp: Dumpulator, instr: CsInsn):
15431587
if instr.id == X86_INS_RDRAND:

src/dumpulator/ntsyscalls.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010

1111
def syscall(func):
1212
name: str = func.__name__
13-
if name.startswith("Nt"):
14-
name = "Zw" + name[2:]
15-
syscall_functions[name] = func
13+
if name[:2] not in ["Zw", "Nt"]:
14+
raise Exception(f"All syscalls have to be prefixed with 'Zw' or 'Nt'")
15+
# Add the function with both prefixes to avoid name bugs
16+
syscall_functions["Zw" + name[2:]] = func
17+
syscall_functions["Nt" + name[2:]] = func
1618
return func
1719

1820
@syscall
@@ -4138,6 +4140,9 @@ def ZwSetInformationProcess(dp: Dumpulator,
41384140
return STATUS_SUCCESS
41394141
elif ProcessInformationClass == PROCESSINFOCLASS.ProcessFaultInformation:
41404142
assert ProcessInformationLength == 8
4143+
# https://github.com/blackhatethicalhacking/sandbox-attacksurface-analysis-tools/blob/946912f55770522ed4a2c957d5f57a6a2e2845df/NtApiDotNet/NtProcessNative.cs#L403
4144+
fault_flags = dp.read_ulong(ProcessInformation.ptr)
4145+
additional_info = dp.read_ulong(ProcessInformation.ptr + 4)
41414146
return STATUS_SUCCESS
41424147
elif ProcessInformationClass == PROCESSINFOCLASS.ProcessLoaderDetour:
41434148
assert ProcessInformationLength == 4

0 commit comments

Comments
 (0)