mirror of
https://github.com/naksyn/PythonMemoryModule
synced 2026-06-06 16:24:25 +00:00
command line support (partial) via PEB stomping
This update include support to passing command line parameters to unmanaged exe via PEB stomping. This technique is not working with every executable since it depends on which functions are used to pass arguments. Generally, to get a universally working technique would be required to hook GetCommandlineA GetCommandlineW __getmainargs and __wgetmainargs since PEB stomping won't cover all cases, more details here: https://blog-30cm-tw.translate.goog/2020/08/windows-c-mainargc-argv.html?_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=it&_x_tr_pto=wapp However, during my testing I found that mimikatz and several go binaries are working just by doing PEB stomping. On the other hand, cmdline passing via PEB stomping alone to mingw and VS compiled binaries won't likely work.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
from .debugger import Debugger, HXBreakpoint
|
||||
from .symboldbg import SymbolDebugger
|
||||
from .localdbg import LocalDebugger
|
||||
from .breakpoints import *
|
||||
from .breakpoints import *
|
||||
@@ -0,0 +1,258 @@
|
||||
from collections import OrderedDict
|
||||
|
||||
import windows
|
||||
from windows.generated_def.winstructs import *
|
||||
from windows.generated_def import windef
|
||||
from windows.winobject.process import WinProcess, WinThread
|
||||
from windows.pycompat import basestring
|
||||
|
||||
|
||||
STANDARD_BP = "BP"
|
||||
HARDWARE_EXEC_BP = "HXBP"
|
||||
MEMORY_BREAKPOINT = "MEMBP"
|
||||
|
||||
class Breakpoint(object):
|
||||
"""An standard (Int3) breakpoint (type == ``STANDARD_BP``)"""
|
||||
type = STANDARD_BP # REAL BP
|
||||
def __init__(self, addr):
|
||||
self.addr = addr
|
||||
|
||||
def apply_to_target(self, target):
|
||||
return isinstance(target, WinProcess)
|
||||
|
||||
def trigger(self, dbg, exception):
|
||||
"""Called when breakpoint is hit"""
|
||||
pass
|
||||
|
||||
|
||||
class ProxyBreakpoint(Breakpoint):
|
||||
def __init__(self, target, addr, type):
|
||||
self.target = target
|
||||
self.addr = addr
|
||||
self.type = type
|
||||
|
||||
def trigger(self, dbg, exception):
|
||||
return self.target(dbg, exception)
|
||||
|
||||
|
||||
class HXBreakpoint(Breakpoint):
|
||||
"""An hardware-execution breakpoint (type == ``HARDWARE_EXEC_BP``)"""
|
||||
type = HARDWARE_EXEC_BP
|
||||
|
||||
def apply_to_target(self, target):
|
||||
return isinstance(target, WinThread)
|
||||
|
||||
class MemoryBreakpoint(Breakpoint):
|
||||
"""A memory breakpoint (type == ``MEMORY_BREAKPOINT``)"""
|
||||
type = MEMORY_BREAKPOINT
|
||||
DEFAULT_EVENTS = "RWX"
|
||||
DEFAULT_SIZE = 0x1000
|
||||
def __init__(self, addr, size=None, events=None):
|
||||
"""``size``: the size of the memory breakpoint.
|
||||
|
||||
``events``: a string representing the events that interest the BP (any of "RWX")"""
|
||||
super(MemoryBreakpoint, self).__init__(addr)
|
||||
self.size = size if size is not None else self.DEFAULT_SIZE
|
||||
events = events if events is not None else self.DEFAULT_EVENTS
|
||||
self.events = set(events)
|
||||
|
||||
def trigger(self, dbg, exception):
|
||||
"""Called when breakpoint is hit"""
|
||||
pass
|
||||
|
||||
|
||||
## Arguments Helper (need to move this elsewhere)
|
||||
class X86ArgumentRetriever(object):
|
||||
def get_arg(self, nb, proc, thread):
|
||||
return proc.read_dword(thread.context.sp + 4 + (4 * nb))
|
||||
|
||||
def set_arg(self, nb, value, proc, thread):
|
||||
return proc.write_dword(thread.context.sp + 4 + (4 * nb), value)
|
||||
|
||||
class X64ArgumentRetriever(object):
|
||||
REG_ARGS = ["Rcx", "Rdx", "R8", "R9"]
|
||||
def get_arg(self, nb, proc, thread):
|
||||
if nb < len(self.REG_ARGS):
|
||||
return getattr(thread.context, self.REG_ARGS[nb])
|
||||
return proc.read_qword(thread.context.sp + 8 + (8 * nb))
|
||||
|
||||
def set_arg(self, nb, value, proc, thread):
|
||||
if nb < len(self.REG_ARGS):
|
||||
ctx = thread.context
|
||||
setattr(ctx, self.REG_ARGS[nb], value)
|
||||
return thread.set_context(ctx)
|
||||
return proc.write_qword(thread.context.sp + 8 + (8 * nb), value)
|
||||
|
||||
## Behaviour breakpoint !
|
||||
# class FunctionParamDumpBP(Breakpoint):
|
||||
class FunctionParamDumpBPAbstract(object):
|
||||
def __init__(self, addr=None, target=None):
|
||||
if target is None:
|
||||
try:
|
||||
target = self.TARGET
|
||||
except AttributeError as e:
|
||||
raise ValueError("{0} bp without a <target> must have a <TARGET> class attribute")
|
||||
if addr is None:
|
||||
addr = "{0}!{1}".format(target.target_dll, target.target_func)
|
||||
super(FunctionParamDumpBPAbstract, self).__init__(addr)
|
||||
self.target = target
|
||||
self.target_args = target.prototype._argtypes_
|
||||
self.target_params = target.params
|
||||
|
||||
def extract_arguments_32bits(self, cproc, cthread):
|
||||
x = windows.debug.X86ArgumentRetriever()
|
||||
res = OrderedDict()
|
||||
for i, (name, type) in enumerate(zip(self.target_params, self.target_args)):
|
||||
value = x.get_arg(i, cproc, cthread)
|
||||
rt = windows.remotectypes.transform_type_to_remote32bits(type)
|
||||
if issubclass(rt, windows.remotectypes.RemoteValue):
|
||||
t = rt(value, cproc)
|
||||
else:
|
||||
t = rt(value)
|
||||
# Will fail in py3..
|
||||
content = None
|
||||
try:
|
||||
content = t.contents
|
||||
except Exception as e:
|
||||
# contents will fail on basic type
|
||||
# Not really an expected behavior
|
||||
# But it works for now.. (and since a while)
|
||||
pass
|
||||
if content is None:
|
||||
t = t.value
|
||||
res[name[1]] = t
|
||||
return res
|
||||
|
||||
def extract_arguments_64bits(self, cproc, cthread):
|
||||
x = windows.debug.X64ArgumentRetriever()
|
||||
res = OrderedDict()
|
||||
for i, (name, type) in enumerate(zip(self.target_params, self.target_args)):
|
||||
value = x.get_arg(i, cproc, cthread)
|
||||
rt = windows.remotectypes.transform_type_to_remote64bits(type)
|
||||
if issubclass(rt, windows.remotectypes.RemoteValue):
|
||||
t = rt(value, cproc)
|
||||
else:
|
||||
t = rt(value)
|
||||
if not hasattr(t, "contents"):
|
||||
try:
|
||||
t = t.value
|
||||
except AttributeError:
|
||||
pass
|
||||
res[name[1]] = t
|
||||
return res
|
||||
|
||||
def extract_arguments(self, cproc, cthread):
|
||||
"""Extracts the functions parameters in an :class:`OrderedDict`"""
|
||||
if windows.current_process.bitness == 32:
|
||||
return self.extract_arguments_32bits(cproc, cthread)
|
||||
if cproc.bitness == 64:
|
||||
return self.extract_arguments_64bits(cproc, cthread)
|
||||
# SysWow process from a 64bits debugger, handle bitness with CS
|
||||
if cthread.context.SegCs == windows.syswow64.CS_32bits:
|
||||
return self.extract_arguments_32bits(cproc, cthread)
|
||||
return self.extract_arguments_64bits(cproc, cthread)
|
||||
|
||||
def arguments(self, dbg):
|
||||
"TEST PARAM DICT"
|
||||
if windows.current_process.bitness == 32:
|
||||
extractor = windows.debug.X86ArgumentRetriever()
|
||||
elif dbg.current_process.bitness == 64:
|
||||
extractor = windows.debug.X64ArgumentRetriever()
|
||||
elif dbg.current_thread.context.SegCs == windows.syswow64.CS_32bits:
|
||||
extractor = windows.debug.X86ArgumentRetriever()
|
||||
else:
|
||||
extractor = windows.debug.X64ArgumentRetriever()
|
||||
name_map = {name:i for i, name in enumerate(t[1] for t in self.target_params)}
|
||||
return FunctionParameterProxy(extractor, name_map, self.target_args, dbg)
|
||||
|
||||
class FunctionParameterProxy(object):
|
||||
# TODO: clean this + put more of the logic in the X64ArgumentRetriever
|
||||
def __init__(self, extractor, name_map, parameters_type, x):
|
||||
self.extractor = extractor
|
||||
self.name_map = name_map
|
||||
self.parameters_type = parameters_type
|
||||
self.x = x
|
||||
|
||||
def __getitem__(self, x):
|
||||
if isinstance(x, basestring):
|
||||
x = self.name_map[x]
|
||||
# import pdb;pdb.set_trace()
|
||||
argtype = self.parameters_type[x]
|
||||
value = self.extractor.get_arg(x, self.x.current_process, self.x.current_thread)
|
||||
rt = windows.remotectypes.transform_type_to_remote32bits(argtype)
|
||||
if issubclass(rt, windows.remotectypes.RemoteValue):
|
||||
t = rt(value, self.x.current_process)
|
||||
else:
|
||||
t = rt(value)
|
||||
if not hasattr(t, "contents"):
|
||||
try:
|
||||
t = t.value
|
||||
except AttributeError:
|
||||
pass
|
||||
return t
|
||||
|
||||
def __setitem__(self, x, value):
|
||||
if isinstance(x, basestring):
|
||||
x = self.name_map[x]
|
||||
try:
|
||||
ctypes.cast(value, PVOID)
|
||||
except ctypes.ArgumentError:
|
||||
pass
|
||||
value = getattr(value, "value", value)
|
||||
return self.extractor.set_arg(x, value, self.x.current_process, self.x.current_thread)
|
||||
|
||||
|
||||
|
||||
class FunctionParamDumpBP(FunctionParamDumpBPAbstract, Breakpoint):
|
||||
pass
|
||||
|
||||
class FunctionParamDumpHXBP(FunctionParamDumpBPAbstract, HXBreakpoint):
|
||||
pass
|
||||
|
||||
class FunctionRetBP(Breakpoint):
|
||||
def __init__(self, addr, initial_breakpoint):
|
||||
super(FunctionRetBP, self).__init__(addr)
|
||||
self.initial_breakpoint = initial_breakpoint
|
||||
|
||||
def trigger(self, dbg, exc):
|
||||
dbg.del_bp(self, targets=[dbg.current_process])
|
||||
return self.initial_breakpoint.ret_trigger(dbg, exc)
|
||||
|
||||
|
||||
class FunctionCallBP(Breakpoint):
|
||||
"""A Breakpoint that allow to trigger at the return of a function"""
|
||||
def break_on_ret(self, dbg, exception):
|
||||
"""Setup a breakpoint at the return address of the function, this breakpoint will call :func:`ret_trigger`"""
|
||||
return_addr = self.get_ret_addr(dbg, exception)
|
||||
dbg.add_bp(FunctionRetBP(return_addr, self), target=dbg.current_process)
|
||||
|
||||
def get_ret_addr(self, dbg, exception):
|
||||
"""Get the return address of the current target, only valid in the trigger() function."""
|
||||
cproc = dbg.current_process
|
||||
return dbg.current_process.read_ptr(dbg.current_thread.context.sp)
|
||||
|
||||
|
||||
def ret_trigger(self, dbg, exception):
|
||||
"""Called at the return of the function if :func:`break_on_ret` was called"""
|
||||
raise NotImplementedError("ret_trigger")
|
||||
|
||||
|
||||
class FunctionBP(FunctionCallBP, FunctionParamDumpBP):
|
||||
"""A breakpoint that accepts a function from :mod:`windows.winproxy` and able to:
|
||||
|
||||
- Extract the arguments of the functions
|
||||
- Break at the return of the function
|
||||
"""
|
||||
|
||||
class PrintBP(Breakpoint):
|
||||
def __init__(self, addr, format, func=None):
|
||||
super(PrintBP, self).__init__(addr)
|
||||
self.format = format
|
||||
self.func = func
|
||||
|
||||
def trigger(self, dbg, exc):
|
||||
thread = dbg.current_thread
|
||||
format_dict = {"dbg": dbg, "exc": exc, "proc": dbg.current_process, "thread": thread, "ctx": thread.context}
|
||||
if self.func:
|
||||
format_dict.update(self.func(**format_dict))
|
||||
print(self.format.format(**format_dict))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,305 @@
|
||||
from collections import defaultdict
|
||||
from contextlib import contextmanager
|
||||
|
||||
import windows
|
||||
import windows.winobject.exception as winexception
|
||||
|
||||
from windows import winproxy
|
||||
from windows.generated_def import windef
|
||||
from windows.generated_def.winstructs import *
|
||||
from .breakpoints import *
|
||||
|
||||
class FakeDebuggerCurrentThread(object):
|
||||
"""A pseudo thread representing the current thread at exception time"""
|
||||
def __init__(self, dbg):
|
||||
self.dbg = dbg
|
||||
|
||||
@property
|
||||
def tid(self):
|
||||
return windows.current_thread.tid
|
||||
|
||||
@property
|
||||
def context(self):
|
||||
"""!!! This context in-place modification will be effective without set_context"""
|
||||
return self.dbg.get_exception_context()
|
||||
|
||||
def set_context(self, context):
|
||||
# The context returned by 'self.context' already modify the return context in place..
|
||||
pass
|
||||
|
||||
class LocalDebugger(object):
|
||||
"""A debugger interface around :func:`AddVectoredExceptionHandler`.
|
||||
|
||||
Handle:
|
||||
|
||||
* Standard BP (int3)
|
||||
* Hardware-Exec BP (DrX)
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.breakpoints = {}
|
||||
self._memory_save = {}
|
||||
self._reput_breakpoint = {}
|
||||
self._hxbp_breakpoint = defaultdict(dict)
|
||||
|
||||
self.callback_vectored = winexception.VectoredException(self.callback)
|
||||
winproxy.AddVectoredExceptionHandler(0, self.callback_vectored)
|
||||
self.setup_hxbp_callback_vectored = winexception.VectoredException(self.setup_hxbp_callback)
|
||||
self.hxbp_info = None
|
||||
self.code = windows.native_exec.create_function(b"\xcc\xc3", [PVOID])
|
||||
self.veh_depth = 0
|
||||
self.current_exception = None
|
||||
self.exceptions_stack = [None]
|
||||
self.current_process = windows.current_process
|
||||
self.current_thread = FakeDebuggerCurrentThread(self)
|
||||
|
||||
@contextmanager
|
||||
def NewCurrentException(self, exc):
|
||||
try:
|
||||
self.exceptions_stack.append(exc)
|
||||
self.current_exception = exc
|
||||
self.veh_depth += 1
|
||||
yield exc
|
||||
finally:
|
||||
self.exceptions_stack.pop()
|
||||
self.current_exception = self.exceptions_stack[-1]
|
||||
self.veh_depth -= 1
|
||||
|
||||
def get_exception_code(self):
|
||||
"""Return ExceptionCode of current exception"""
|
||||
return self.current_exception[0].ExceptionRecord[0].ExceptionCode
|
||||
|
||||
def get_exception_context(self):
|
||||
"""Return context of current exception"""
|
||||
return self.current_exception[0].ContextRecord[0]
|
||||
|
||||
def single_step(self):
|
||||
"""Make the current thread to single step"""
|
||||
self.get_exception_context().EEFlags.TF = 1
|
||||
return windef.EXCEPTION_CONTINUE_EXECUTION
|
||||
|
||||
def _pass_breakpoint(self, addr, single_step):
|
||||
with windows.utils.VirtualProtected(addr, 1, PAGE_EXECUTE_READWRITE):
|
||||
windows.current_process.write_memory(addr, self._memory_save[addr])
|
||||
self._reput_breakpoint[windows.current_thread.tid] = self.breakpoints[addr], single_step
|
||||
return self.single_step()
|
||||
|
||||
def _local_resolve(self, addr):
|
||||
if not isinstance(addr, basestring):
|
||||
return addr
|
||||
dll, api = addr.split("!")
|
||||
dll = dll.lower()
|
||||
modules = {m.name[:-len(".dll")] if m.name.endswith(".dll") else m.name : m for m in windows.current_process.peb.modules}
|
||||
mod = None
|
||||
if dll in modules:
|
||||
mod = [modules[dll]]
|
||||
if not mod:
|
||||
return None
|
||||
# TODO: optim exports are the same for whole system (32 vs 64 bits)
|
||||
# I don't have to reparse the exports each time..
|
||||
# Try to interpret api as an int
|
||||
try:
|
||||
api_int = int(api, 0)
|
||||
return mod[0].baseaddr + api_int
|
||||
except ValueError:
|
||||
pass
|
||||
exports = mod[0].pe.exports
|
||||
if api not in exports:
|
||||
dbgprint("Error resolving <{0}> in local process".format(addr, target), "DBG")
|
||||
raise ValueError("Unknown API <{0}> in DLL {1}".format(api, dll))
|
||||
return exports[api]
|
||||
|
||||
def callback(self, exc):
|
||||
with self.NewCurrentException(exc):
|
||||
return self.handle_exception(exc)
|
||||
|
||||
def handle_exception(self, exc):
|
||||
exp_code = self.get_exception_code()
|
||||
context = self.get_exception_context()
|
||||
exp_addr = context.pc
|
||||
if exp_code == EXCEPTION_BREAKPOINT and exp_addr in self.breakpoints:
|
||||
res = self.breakpoints[exp_addr].trigger(self, exc)
|
||||
single_step = self.get_exception_context().EEFlags.TF # single step activated by breakpoint
|
||||
if exp_addr in self.breakpoints: # Breakpoint deleted itself ?
|
||||
return self._pass_breakpoint(exp_addr, single_step)
|
||||
return EXCEPTION_CONTINUE_EXECUTION
|
||||
|
||||
if exp_code == EXCEPTION_SINGLE_STEP and windows.current_thread.tid in self._reput_breakpoint:
|
||||
bp, single_step = self._reput_breakpoint[windows.current_thread.tid]
|
||||
self._memory_save[bp._addr] = windows.current_process.read_memory(bp._addr, 1)
|
||||
with windows.utils.VirtualProtected(bp._addr, 1, PAGE_EXECUTE_READWRITE):
|
||||
windows.current_process.write_memory(bp._addr, b"\xcc")
|
||||
del self._reput_breakpoint[windows.current_thread.tid]
|
||||
if single_step:
|
||||
return self.on_exception(exc)
|
||||
return windef.EXCEPTION_CONTINUE_EXECUTION
|
||||
elif exp_code == EXCEPTION_SINGLE_STEP and exp_addr in self._hxbp_breakpoint[windows.current_thread.tid]:
|
||||
res = self._hxbp_breakpoint[windows.current_thread.tid][exp_addr].trigger(self, exc)
|
||||
context.EEFlags.RF = 1
|
||||
return EXCEPTION_CONTINUE_EXECUTION
|
||||
return self.on_exception(exc)
|
||||
|
||||
def on_exception(self, exc):
|
||||
"""Called on exception"""
|
||||
if not self.get_exception_code() in winexception.exception_name_by_value:
|
||||
return windef.EXCEPTION_CONTINUE_SEARCH
|
||||
return windef.EXCEPTION_CONTINUE_EXECUTION
|
||||
|
||||
def del_bp(self, bp, targets=None):
|
||||
"""Delete a breakpoint"""
|
||||
# TODO: check targets..
|
||||
if bp.type == STANDARD_BP:
|
||||
with windows.utils.VirtualProtected(bp.addr, 1, PAGE_EXECUTE_READWRITE):
|
||||
windows.current_process.write_memory(bp.addr, self._memory_save[bp.addr])
|
||||
del self._memory_save[bp.addr]
|
||||
del self.breakpoints[bp.addr]
|
||||
return
|
||||
if bp.type == HARDWARE_EXEC_BP:
|
||||
threads_by_tid = {t.tid: t for t in windows.current_process.threads}
|
||||
for tid in self._hxbp_breakpoint:
|
||||
if bp.addr in self._hxbp_breakpoint[tid] and self._hxbp_breakpoint[tid][bp.addr] == bp:
|
||||
if tid == windows.current_thread.tid:
|
||||
self.remove_hxbp_self_thread(bp.addr)
|
||||
else:
|
||||
self.remove_hxbp_other_thread(bp.addr, threads_by_tid[tid])
|
||||
del self._hxbp_breakpoint[tid][bp.addr]
|
||||
return
|
||||
raise NotImplementedError("Unknow BP type {0}".format(bp.type))
|
||||
|
||||
def add_bp(self, bp, target=None):
|
||||
"""Add a breakpoint, bp is a "class:`Breakpoint`
|
||||
|
||||
If the ``bp`` type is ``STANDARD_BP``, target must be None.
|
||||
|
||||
If the ``bp`` type is ``HARDWARE_EXEC_BP``, target can be None (all threads), or some threads of the process
|
||||
"""
|
||||
if bp.type == HARDWARE_EXEC_BP:
|
||||
return self.add_bp_hxbp(bp, target)
|
||||
if bp.type != STANDARD_BP:
|
||||
raise NotImplementedError("Unknow BP type {0}".format(bp.type))
|
||||
if target not in [None, windows.current_process]:
|
||||
raise ValueError("LocalDebugger: STANDARD_BP doest not support targets {0}".format(targets))
|
||||
addr = self._local_resolve(bp.addr)
|
||||
bp._addr = addr
|
||||
self.breakpoints[addr] = bp
|
||||
self._memory_save[addr] = windows.current_process.read_memory(addr, 1)
|
||||
with windows.utils.VirtualProtected(addr, 1, PAGE_EXECUTE_READWRITE):
|
||||
windows.current_process.write_memory(addr, b"\xcc")
|
||||
return
|
||||
|
||||
def add_bp_hxbp(self, bp, targets=None):
|
||||
if bp.type != HARDWARE_EXEC_BP:
|
||||
raise NotImplementedError("Add non standard-BP in LocalDebugger")
|
||||
if targets is None:
|
||||
targets = windows.current_process.threads
|
||||
for thread in targets:
|
||||
if thread.owner.pid != windows.current_process.pid:
|
||||
raise ValueError("Cannot add HXBP to target in remote process {0}".format(thread))
|
||||
if thread.tid == windows.current_thread.tid:
|
||||
self.setup_hxbp_self_thread(bp.addr)
|
||||
else:
|
||||
self.setup_hxbp_other_thread(bp.addr, thread)
|
||||
self._hxbp_breakpoint[thread.tid][bp.addr] = bp
|
||||
|
||||
def setup_hxbp_callback(self, exc):
|
||||
with self.NewCurrentException(exc):
|
||||
exp_code = self.get_exception_code()
|
||||
if exp_code != windef.EXCEPTION_BREAKPOINT:
|
||||
return windef.EXCEPTION_CONTINUE_SEARCH
|
||||
context = self.get_exception_context()
|
||||
exp_addr = context.pc
|
||||
hxbp_used = self.setup_hxbp_in_context(context, self.data)
|
||||
windows.current_process.write_memory(exp_addr, b"\x90")
|
||||
# Raising in the VEH is a bad idea..
|
||||
# So better give the information to triggerer..
|
||||
if hxbp_used is not None:
|
||||
self.get_exception_context().func_result = exp_addr
|
||||
else:
|
||||
self.get_exception_context().func_result = 0
|
||||
return windef.EXCEPTION_CONTINUE_EXECUTION
|
||||
|
||||
def remove_hxbp_callback(self, exc):
|
||||
with self.NewCurrentException(exc):
|
||||
exp_code = self.get_exception_code()
|
||||
context = self.get_exception_context()
|
||||
exp_addr = context.pc
|
||||
hxbp_used = self.remove_hxbp_in_context(context, self.data)
|
||||
windows.current_process.write_memory(exp_addr, b"\x90")
|
||||
# Raising in the VEH is a bad idea..
|
||||
# So better give the information to triggerer..
|
||||
if hxbp_used is not None:
|
||||
self.get_exception_context().Eax = exp_addr
|
||||
else:
|
||||
self.get_exception_context().Eax = 0
|
||||
return windef.EXCEPTION_CONTINUE_EXECUTION
|
||||
|
||||
def setup_hxbp_in_context(self, context, addr):
|
||||
for i in range(4):
|
||||
is_used = getattr(context.EDr7, "L" + str(i))
|
||||
empty_drx = str(i)
|
||||
if not is_used:
|
||||
context.EDr7.GE = 1
|
||||
context.EDr7.LE = 1
|
||||
setattr(context.EDr7, "L" + empty_drx, 1)
|
||||
setattr(context, "Dr" + empty_drx, addr)
|
||||
return i
|
||||
return None
|
||||
|
||||
def remove_hxbp_in_context(self, context, addr):
|
||||
for i in range(4):
|
||||
target_drx = str(i)
|
||||
is_used = getattr(context.EDr7, "L" + str(i))
|
||||
draddr = getattr(context, "Dr" + target_drx)
|
||||
|
||||
if is_used and draddr == addr:
|
||||
setattr(context.EDr7, "L" + target_drx, 0)
|
||||
setattr(context, "Dr" + target_drx, 0)
|
||||
return i
|
||||
return None
|
||||
|
||||
def setup_hxbp_self_thread(self, addr):
|
||||
if self.current_exception is not None:
|
||||
x = self.setup_hxbp_in_context(self.get_exception_context(), addr)
|
||||
if x is None:
|
||||
raise ValueError("Could not setup HXBP")
|
||||
return
|
||||
|
||||
self.data = addr
|
||||
with winexception.VectoredExceptionHandler(1, self.setup_hxbp_callback):
|
||||
x = self.code()
|
||||
if x is None:
|
||||
raise ValueError("Could not setup HXBP")
|
||||
windows.current_process.write_memory(x, b"\xcc")
|
||||
return
|
||||
|
||||
def setup_hxbp_other_thread(self, addr, thread):
|
||||
thread.suspend()
|
||||
ctx = thread.context
|
||||
x = self.setup_hxbp_in_context(ctx, addr)
|
||||
if x is None:
|
||||
raise ValueError("Could not setup HXBP in {0}".format(thread))
|
||||
thread.set_context(ctx)
|
||||
thread.resume()
|
||||
|
||||
def remove_hxbp_self_thread(self, addr):
|
||||
if self.current_exception is not None:
|
||||
x = self.remove_hxbp_in_context(self.get_exception_context(), addr)
|
||||
if x is None:
|
||||
raise ValueError("Could not setup HXBP")
|
||||
return
|
||||
self.data = addr
|
||||
with winexception.VectoredExceptionHandler(1, self.remove_hxbp_callback):
|
||||
x = self.code()
|
||||
if x is None:
|
||||
raise ValueError("Could not remove HXBP")
|
||||
windows.current_process.write_memory(x, b"\xcc")
|
||||
return
|
||||
|
||||
def remove_hxbp_other_thread(self, addr, thread):
|
||||
thread.suspend()
|
||||
ctx = thread.context
|
||||
x = self.remove_hxbp_in_context(ctx, addr)
|
||||
if x is None:
|
||||
raise ValueError("Could not setup HXBP in {0}".format(thread))
|
||||
thread.set_context(ctx)
|
||||
thread.resume()
|
||||
@@ -0,0 +1,48 @@
|
||||
import windows
|
||||
import windows.generated_def as gdef
|
||||
from windows.pycompat import int_types
|
||||
|
||||
from . import Debugger
|
||||
from . import symbols
|
||||
|
||||
class SymbolDebugger(Debugger):
|
||||
"""A debugger using the symbol API (hence PDB) for name resolution.
|
||||
To use PDB, a correct version of dbghelp should be configured as well as ``_NT_SYMBOL_PATH``.
|
||||
(See :ref:`debug_symbols_module`)
|
||||
|
||||
This debugger add a ``current_resolver`` variable (A :class:`~windows.debug.symbols.ProcessSymbolHandler`) for the ``current_process``.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SymbolDebugger, self).__init__(*args, **kwargs)
|
||||
self._resolvers = {}
|
||||
|
||||
def _internal_on_load_dll(self, load_dll):
|
||||
path = self._get_loaded_dll(load_dll)
|
||||
# Path is used instead of name for naming the module (and can be set to whatever if using file handle)
|
||||
x = self.current_resolver.load_module(load_dll.hFile, path=path, addr=load_dll.lpBaseOfDll)
|
||||
|
||||
def _internal_on_create_process(self, create_process):
|
||||
# Create and setup a symbol resolver for the new process
|
||||
resolver = symbols.ProcessSymbolHandler(self.current_process)
|
||||
self._resolvers[self.current_process.pid] = resolver
|
||||
self.current_resolver = resolver
|
||||
|
||||
def _update_debugger_state(self, debug_event):
|
||||
super(SymbolDebugger, self)._update_debugger_state(debug_event)
|
||||
self.current_resolver = self._resolvers[debug_event.dwProcessId]
|
||||
|
||||
def _resolve(self, addr, target):
|
||||
if isinstance(addr, int_types):
|
||||
return addr
|
||||
if "+" in addr:
|
||||
symbol, deplacement = addr.split("+", 1)
|
||||
deplacement = int(deplacement, 0)
|
||||
else:
|
||||
symbol = addr
|
||||
deplacement = 0
|
||||
try:
|
||||
return self.current_resolver[symbol].addr + deplacement
|
||||
except WindowsError as e:
|
||||
if not e.winerror in (gdef.ERROR_NOT_FOUND, gdef.ERROR_MOD_NOT_FOUND):
|
||||
raise
|
||||
return None
|
||||
@@ -0,0 +1,744 @@
|
||||
import os.path
|
||||
import ctypes
|
||||
import copy
|
||||
import itertools
|
||||
from collections import namedtuple
|
||||
|
||||
import windows
|
||||
import windows.generated_def as gdef
|
||||
from windows import winproxy
|
||||
from windows.pycompat import basestring
|
||||
|
||||
DEFAULT_DBG_OPTION = gdef.SYMOPT_DEFERRED_LOADS + gdef.SYMOPT_UNDNAME
|
||||
|
||||
|
||||
def set_dbghelp_path(path):
|
||||
"""Set the path of the ``dbghelp.dll`` file to use. It allow to configure a different version of the DLL handling PDB downloading.
|
||||
|
||||
If ``path`` is a directory, the final ``dbghelp.dll`` will be computed as
|
||||
``path\<current_process_bitness>\dbghelp.dll``.
|
||||
|
||||
This allow to use the same script transparently in both 32b & 64b python interpreters.
|
||||
"""
|
||||
loaded_modules = [m.name.lower() for m in windows.current_process.peb.modules]
|
||||
if os.path.isdir(path):
|
||||
path = os.path.join(path, str(windows.current_process.bitness), "dbghelp.dll")
|
||||
if "dbghelp.dll" in loaded_modules:
|
||||
raise ValueError("setup_dbghelp_path should be called before any dbghelp function")
|
||||
# Change the DLL used by DbgHelpProxy
|
||||
winproxy.DbgHelpProxy.APIDLL = path
|
||||
return
|
||||
|
||||
# Load symbol config from ENV if present
|
||||
try:
|
||||
env_dbghelp_path = os.environ["PFW_DBGHELP_PATH"]
|
||||
# Setup the dbghelp path used by PFW
|
||||
set_dbghelp_path(env_dbghelp_path)
|
||||
except KeyError as e:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class SymbolInfoBase(object):
|
||||
"""Represent a Symbol.
|
||||
This class in based on the class `SYMBOL_INFO <https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/ns-dbghelp-symbol_info>`_
|
||||
with the handling on displacement embeded into it.
|
||||
"""
|
||||
# Init on ctypes struct is not always called
|
||||
# resolver & displacement should be set manually
|
||||
CHAR_TYPE = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.resolver = kwargs.get("resolver", None)
|
||||
#: POUET POUET
|
||||
self.displacement = kwargs.get("displacement", 0) #: POUET POUET
|
||||
|
||||
|
||||
def as_type(self):
|
||||
# assert self.Address == 0 ?
|
||||
return SymbolType(self.Index, self.ModBase, self.resolver)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The name of the symbol"""
|
||||
if not self.NameLen:
|
||||
return None
|
||||
size = self.NameLen
|
||||
addr = ctypes.addressof(self) + type(self).Name.offset
|
||||
return (self.CHAR_TYPE * size).from_address(addr)[:]
|
||||
|
||||
@property
|
||||
def fullname(self):
|
||||
"""The fullname of the symbol in the windbg format ``mod!sym+displacement``"""
|
||||
return str(self)
|
||||
|
||||
@property
|
||||
def addr(self):
|
||||
"""The address of the symbol"""
|
||||
return self.Address + self.displacement
|
||||
|
||||
@property
|
||||
def start(self):
|
||||
"""The address of the start of the symbol
|
||||
If the symbol include a displacement, it is not taken into account
|
||||
"""
|
||||
return self.Address
|
||||
|
||||
@property # Fixed ?
|
||||
def module(self):
|
||||
"""The module containing the symbol
|
||||
|
||||
:type: :class:`SymbolModule`
|
||||
"""
|
||||
return self.resolver.get_module(self.ModBase)
|
||||
|
||||
@property
|
||||
def tag(self):
|
||||
"""The Tag of the module
|
||||
|
||||
:type: :class:`~windows.generated_def.winstructs.SymTagEnum`
|
||||
"""
|
||||
return gdef.SymTagEnum.mapper[self.Tag]
|
||||
|
||||
def __int__(self):
|
||||
"""An alias for ``addr``"""
|
||||
return self.addr
|
||||
|
||||
def __str__(self):
|
||||
"""The fullname of the symbol in the windbg format ``mod!sym+displacement``"""
|
||||
if self.displacement:
|
||||
return "{self.module.name}!{self.name}+{self.displacement:#x}".format(self=self)
|
||||
return "{self.module.name}!{self.name}".format(self=self)
|
||||
|
||||
def __repr__(self):
|
||||
if self.displacement:
|
||||
return '<{0} name="{1}" start={2:#x} displacement={3:#x} tag={4}>'.format(type(self).__name__, self.name, self.start, self.displacement, self.tag.name)
|
||||
return '<{0} name="{1}" start={2:#x} tag={3}>'.format(type(self).__name__, self.name, self.start, self.tag.name)
|
||||
|
||||
|
||||
class SymbolInfoA(gdef.SYMBOL_INFO, SymbolInfoBase):
|
||||
"""Represent a Symbol.
|
||||
This class in based on the class `SYMBOL_INFO <https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/ns-dbghelp-symbol_info>`_
|
||||
with the handling on displacement embeded into it.s
|
||||
|
||||
Exemple:
|
||||
|
||||
>>> sh = windows.debug.symbols.VirtualSymbolHandler()
|
||||
>>> mod = sh.load_file(r"c:\windows\system32\kernelbase.dll")
|
||||
>>> sym1 = sh["kernelbase!CreateFileW"]
|
||||
>>> sym2 = sh[int(sym1) + 3]
|
||||
>>> sym2
|
||||
<SymbolInfoA name="CreateFileW" start=0x100f20b0 displacement=0x3 tag=SymTagPublicSymbol>
|
||||
>>> hex(sym2.start)
|
||||
'0x100f20b0L'
|
||||
>>> hex(sym2.addr)
|
||||
'0x100f20b3L'
|
||||
>>> hex(sym2.displacement)
|
||||
'0x3L'
|
||||
>>> str(sym2)
|
||||
'kernelbase!CreateFileW+0x3'
|
||||
"""
|
||||
CHAR_TYPE = gdef.CHAR
|
||||
|
||||
class SymbolInfoW(gdef.SYMBOL_INFOW, SymbolInfoBase):
|
||||
CHAR_TYPE = gdef.WCHAR
|
||||
|
||||
# We use the A Api in our code (for now)
|
||||
SymbolInfo = SymbolInfoA
|
||||
|
||||
class SymbolType(object):
|
||||
def __init__(self, typeid, modbase, resolver):
|
||||
# Inheritance ?
|
||||
self.resolver = resolver
|
||||
self._typeid = typeid # Kind of a handle. Different of typeid property.
|
||||
self.modbase = modbase
|
||||
|
||||
def _get_type_info(self, typeinfo, ires=None):
|
||||
res = ires
|
||||
if res is None:
|
||||
res = TST_TYPE_RES_TYPE.get(typeinfo, gdef.DWORD)()
|
||||
windows.winproxy.SymGetTypeInfo(self.resolver.handle, self.modbase, self._typeid, typeinfo, ctypes.byref(res))
|
||||
if ires is not None:
|
||||
return ires
|
||||
newres = res.value
|
||||
if isinstance(res, gdef.LPWSTR):
|
||||
windows.winproxy.LocalFree(res)
|
||||
return newres
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._get_type_info(gdef.TI_GET_SYMNAME)
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self._get_type_info(gdef.TI_GET_LENGTH)
|
||||
|
||||
@property
|
||||
def tag(self):
|
||||
return self._get_type_info(gdef.TI_GET_SYMTAG)
|
||||
|
||||
# Diff type/typeid ?
|
||||
@property
|
||||
def type(self):
|
||||
return self.new_typeid(self._get_type_info(gdef.TI_GET_TYPE))
|
||||
|
||||
@property
|
||||
def typeid(self):
|
||||
return self.new_typeid(self._get_type_info(gdef.TI_GET_TYPEID))
|
||||
|
||||
@property
|
||||
def basetype(self):
|
||||
return gdef.BasicType.mapper[self._get_type_info(gdef.TI_GET_BASETYPE)]
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
return self.new_typeid(self._get_type_info(gdef.TI_GET_CLASSPARENTID))
|
||||
|
||||
@property
|
||||
def datakind(self):
|
||||
return gdef.DataKind.mapper[self._get_type_info(gdef.TI_GET_DATAKIND)]
|
||||
|
||||
@property
|
||||
def udtkind(self):
|
||||
return gdef.UdtKind.mapper[self._get_type_info(gdef.TI_GET_UDTKIND)]
|
||||
|
||||
@property
|
||||
def offset(self):
|
||||
return self._get_type_info(gdef.TI_GET_OFFSET)
|
||||
|
||||
@property
|
||||
def nb_children(self):
|
||||
return self._get_type_info(gdef.TI_GET_CHILDRENCOUNT)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self._get_type_info(gdef.TI_GET_VALUE)
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
count = self.nb_children
|
||||
class res_struct(ctypes.Structure):
|
||||
_fields_ = [("Count", gdef.ULONG), ("Start", gdef.ULONG), ("Types", (gdef.ULONG * count))]
|
||||
x = res_struct()
|
||||
x.Count = count
|
||||
x.Start = 0
|
||||
self._get_type_info(gdef.TI_FINDCHILDREN, x)
|
||||
return [self.new_typeid(ch) for ch in x.Types]
|
||||
|
||||
# Constructor
|
||||
@classmethod
|
||||
def from_symbol_info(cls, syminfo, resolver):
|
||||
return cls(syminfo.TypeIndex, syminfo.ModBase, resolver)
|
||||
|
||||
# Constructor
|
||||
def new_typeid(self, newtypeid):
|
||||
return type(self)(newtypeid, self.modbase, self.resolver)
|
||||
|
||||
def __repr__(self):
|
||||
if self.tag == gdef.SymTagBaseType:
|
||||
return '<{0} <basetype> {1!r}>'.format(type(self).__name__, self.basetype)
|
||||
elif self.tag == gdef.SymTagPointerType:
|
||||
target_type = self.type.name
|
||||
return '<{0} PTR TO "{1}" tag={2}>'.format(type(self).__name__, target_type, self.tag)
|
||||
return '<{0} name="{1}" tag={2}>'.format(type(self).__name__, self.name, self.tag)
|
||||
|
||||
|
||||
class SymbolModule(gdef.IMAGEHLP_MODULE64):
|
||||
"""Represent a loaded symbol module
|
||||
(see `MSDN IMAGEHLP_MODULE64 <https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/ns-dbghelp-imagehlp_module64>`_)
|
||||
|
||||
.. note::
|
||||
|
||||
This represent a module in the ``symbol space`` for symbol resolution.
|
||||
This can be completly virtual (particularly in the case of :class:`VirtualSymbolHandler`
|
||||
"""
|
||||
# Init on ctypes struct is not always called
|
||||
# resolver should be set manually
|
||||
def __init__(self, resolver):
|
||||
self.resolver = resolver
|
||||
|
||||
@property
|
||||
def addr(self):
|
||||
"""The load address of the module"""
|
||||
return self.BaseOfImage
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The name of the module"""
|
||||
return self.ModuleName
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
"""The full path and file name of the file from which symbols were loaded."""
|
||||
return self.LoadedImageName
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""The type of module (:class:`~windows.generated_def.winstructs.SYM_TYPE`),
|
||||
which can be one of:
|
||||
|
||||
=========== =========================
|
||||
SymCoff COFF symbols.
|
||||
SymCv CodeView symbols.
|
||||
SymDeferred Symbol loading deferred.
|
||||
SymDia DIA symbols.
|
||||
SymExport Symbols generated from a DLL export table.
|
||||
SymNone No symbols are loaded.
|
||||
SymPdb PDB symbols.
|
||||
SymSym .sym file.
|
||||
SymVirtual The virtual module created by SymLoadModuleEx with SLMFLAG_VIRTUAL.
|
||||
=========== =========================
|
||||
"""
|
||||
return self.SymType
|
||||
|
||||
@property
|
||||
def pdb(self):
|
||||
"""The local path of the loaded PDB if present
|
||||
|
||||
Exemple:
|
||||
>>> sh = windows.debug.symbols.VirtualSymbolHandler()
|
||||
>>> mod = sh.load_file(r"c:\windows\system32\kernelbase.dll")
|
||||
>>> mod.pdb
|
||||
'd:\\symbols\\wkernelbase.pdb\\017FA9C5278235B7E6BFBA74A9A5AAD91\\wkernelbase.pdb'
|
||||
"""
|
||||
LoadedPdbName = self.LoadedPdbName
|
||||
if not LoadedPdbName:
|
||||
return None
|
||||
return LoadedPdbName
|
||||
|
||||
def __repr__(self):
|
||||
pdb_basename = self.LoadedPdbName.split(b"\\")[-1]
|
||||
return '<{0} name="{1}" type={2} pdb="{3}" addr={4:#x}>'.format(type(self).__name__, self.name, self.type.value.name, pdb_basename, self.addr)
|
||||
|
||||
|
||||
# https://docs.microsoft.com/en-us/windows/win32/debug/symbol-handler-initialization
|
||||
class SymbolHandler(object):
|
||||
"""Base class of symbol handler"""
|
||||
|
||||
def __init__(self, handle, search_path=None, invade_process=False):
|
||||
# https://docs.microsoft.com/en-us/windows/desktop/api/dbghelp/nf-dbghelp-syminitialize
|
||||
# This value should be unique and nonzero, but need not be a process handle.
|
||||
# be sure to use the correct handle.
|
||||
self.handle = handle #: The handle of the symbol handler
|
||||
if not engine.options_already_setup:
|
||||
engine.set_options(DEFAULT_DBG_OPTION)
|
||||
winproxy.SymInitialize(handle, search_path, invade_process)
|
||||
|
||||
|
||||
def load_module(self, file_handle=None, path=None, name=None, addr=0, size=0, data=None, flags=0):
|
||||
"""Load a module at a given ``addr``. The module to load can be pass via a ``file_handle``
|
||||
or the direct ``path`` of the file to load.
|
||||
|
||||
:return: :class:`SymbolModule` -- The loaded module
|
||||
|
||||
.. note::
|
||||
|
||||
The logic of ``SymLoadModuleEx`` seems somewhat strange about the naming of the loaded module.
|
||||
A custom module ``name`` is only taken into account if the file is passed via a File handle.
|
||||
To make it more intuitive, if this function is call with a ``path`` and ``name`` and no ``file_handle``,
|
||||
it will open the path and directly call ``SymLoadModuleEx`` with a file handle and a name.
|
||||
"""
|
||||
|
||||
# Is that a bug in SymLoadModuleEx ?
|
||||
# To get a custom name for a module it use "path"
|
||||
# So we need to use file_handle and set a custom path
|
||||
# ! BUT it means we cannot get a custom name for a module where the path is not explicit and need to be searched
|
||||
if name is not None and file_handle is None and os.path.exists(path):
|
||||
try:
|
||||
f = open(path)
|
||||
file_handle = windows.utils.get_handle_from_file(f)
|
||||
path = name
|
||||
except Exception as e:
|
||||
pass
|
||||
# Expect a-string
|
||||
path = windows.pycompat.raw_encode(path)
|
||||
try:
|
||||
load_addr = winproxy.SymLoadModuleEx(self.handle, file_handle, path, name, addr, size, data, flags)
|
||||
except WindowsError as e:
|
||||
# if e.winerror == 0:
|
||||
# Already loaded ?
|
||||
# What if someone try to load another PE at the same BaseOfDll ?
|
||||
# return BaseOfDll
|
||||
raise
|
||||
return self.get_module(load_addr)
|
||||
|
||||
def load_file(self, path, name=None, addr=0, size=0, data=None, flags=0):
|
||||
"""Load the module ``path`` at ``addr``
|
||||
|
||||
:return: :class:`SymbolModule` -- The loaded module
|
||||
"""
|
||||
return self.load_module(path=path, name=name, addr=addr, size=size, data=data, flags=flags)
|
||||
|
||||
def unload(self, addr):
|
||||
"""Unload the module at ``addr``"""
|
||||
return winproxy.SymUnloadModule64(self.handle, addr)
|
||||
|
||||
|
||||
@staticmethod
|
||||
@ctypes.WINFUNCTYPE(gdef.BOOL, gdef.PCSTR, gdef.DWORD64, ctypes.py_object)
|
||||
def modules_aggregator(modname, modaddr, ctx):
|
||||
ctx.append(modaddr)
|
||||
return True
|
||||
|
||||
@property
|
||||
def modules(self):
|
||||
"""The list of loaded modules
|
||||
|
||||
:return: [:class:`SymbolModule`] -- A list of modules
|
||||
"""
|
||||
res = []
|
||||
windows.winproxy.SymEnumerateModules64(self.handle, self.modules_aggregator, res)
|
||||
return [self.get_module(addr) for addr in res]
|
||||
|
||||
|
||||
def get_module(self, base):
|
||||
modinfo = SymbolModule(self)
|
||||
modinfo.SizeOfStruct = ctypes.sizeof(modinfo)
|
||||
winproxy.SymGetModuleInfo64(self.handle, base, modinfo)
|
||||
return modinfo
|
||||
|
||||
def symbol_and_displacement_from_address(self, addr):
|
||||
displacement = gdef.DWORD64()
|
||||
max_len_size = 0x1000
|
||||
full_size = ctypes.sizeof(SymbolInfo) + (max_len_size - 1)
|
||||
buff = windows.utils.BUFFER(SymbolInfo)(size=full_size)
|
||||
sym = buff[0]
|
||||
sym.SizeOfStruct = ctypes.sizeof(SymbolInfo)
|
||||
sym.MaxNameLen = max_len_size
|
||||
winproxy.SymFromAddr(self.handle, addr, displacement, buff) # SymFromAddrW ?
|
||||
sym.resolver = self
|
||||
sym.displacement = displacement.value
|
||||
return sym
|
||||
|
||||
|
||||
def symbol_from_name(self, name):
|
||||
max_len_size = 0x1000
|
||||
full_size = ctypes.sizeof(SymbolInfo) + (max_len_size - 1)
|
||||
buff = windows.utils.BUFFER(SymbolInfo)(size=full_size)
|
||||
sym = buff[0]
|
||||
sym.SizeOfStruct = ctypes.sizeof(SymbolInfo)
|
||||
sym.MaxNameLen = max_len_size
|
||||
# Expect a-string
|
||||
name = windows.pycompat.raw_encode(name)
|
||||
windows.winproxy.SymFromName(self.handle, name, buff)
|
||||
sym.resolver = self
|
||||
sym.displacement = 0
|
||||
return sym
|
||||
|
||||
def resolve(self, name_or_addr):
|
||||
"""Resolve ``name_or_addr``.
|
||||
|
||||
If its an int -> Return the :class:`SymbolInfo` at the address.
|
||||
If its a string -> Return the :class:`SymbolInfo` corresponding to the symbol name
|
||||
|
||||
:return: :class:`SymbolInfo`
|
||||
|
||||
.. note::
|
||||
|
||||
``__getitem__`` is an alias for ``resolve()``
|
||||
|
||||
Exemple:
|
||||
|
||||
>>> sh = windows.debug.symbols.VirtualSymbolHandler()
|
||||
>>> mod = sh.load_file(r"c:\windows\system32\kernelbase.dll")
|
||||
>>> mod
|
||||
<SymbolModule name="kernelbase" type=SymPdb pdb="wkernelbase.pdb" addr=0x10000000>
|
||||
>>> sh.resolve("kernelbase!CreateFileInternal")
|
||||
<SymbolInfoA name="CreateFileInternal" addr=0x100f2120 tag=SymTagFunction>
|
||||
>>> sh[0x100f2042]
|
||||
<SymbolInfoA name="ReadFile" addr=0x100f1ee0 displacement=0x162 tag=SymTagFunction>
|
||||
>>> str(sh[0x100f2042])
|
||||
'kernelbase!ReadFile+0x162'
|
||||
"""
|
||||
# Only returns None if symbol is not Found ?
|
||||
if isinstance(name_or_addr, windows.pycompat.anybuff):
|
||||
return self.symbol_from_name(name_or_addr)
|
||||
try:
|
||||
return self.symbol_and_displacement_from_address(name_or_addr)
|
||||
except WindowsError as e:
|
||||
if e.winerror != gdef.ERROR_MOD_NOT_FOUND:
|
||||
raise
|
||||
# We could not resolve and address -> return None
|
||||
return None
|
||||
|
||||
__getitem__ = resolve
|
||||
"""Alias to resolve for simpler use"""
|
||||
|
||||
@staticmethod
|
||||
@ctypes.WINFUNCTYPE(gdef.BOOL, ctypes.POINTER(SymbolInfo), gdef.ULONG , ctypes.py_object)
|
||||
def simple_aggregator(info, size, ctx):
|
||||
sym = info[0]
|
||||
fullsize = sym.SizeOfStruct + sym.NameLen
|
||||
cpy = windows.utils.BUFFER(SymbolInfo)(size=fullsize)
|
||||
ctypes.memmove(cpy, info, fullsize)
|
||||
ctx.append(cpy[0])
|
||||
return True
|
||||
|
||||
def search(self, mask, mod=0, tag=0, options=gdef.SYMSEARCH_ALLITEMS, callback=None):
|
||||
"""Search the symbols matching ``mask`` (``Windbg`` like).
|
||||
|
||||
:return: [:class:`SymbolInfo`] -- A list of :class:`SymbolInfo`
|
||||
|
||||
>>> sh = windows.debug.symbols.VirtualSymbolHandler()
|
||||
>>> mod = sh.load_file(r"c:\windows\system32\kernelbase.dll")
|
||||
>>> sh.search("kernelbase!CreateFile*")
|
||||
[<SymbolInfoA name="CreateFileInternal" addr=0x100f2120 tag=SymTagFunction>,
|
||||
<SymbolInfoA name="CreateFileMoniker" addr=0x10117d80 tag=SymTagFunction>,
|
||||
<SymbolInfoA name="CreateFile2" addr=0x1011e690 tag=SymTagFunction>,
|
||||
...]
|
||||
"""
|
||||
res = []
|
||||
if callback is None:
|
||||
callback = self.simple_aggregator
|
||||
else:
|
||||
callback = ctypes.WINFUNCTYPE(gdef.BOOL, ctypes.POINTER(SymbolInfo), gdef.ULONG , ctypes.py_object)(callback)
|
||||
|
||||
addr = getattr(mod, "addr", mod) # Retrieve mod.addr, else us the value directly
|
||||
# Expect A-string
|
||||
mask = windows.pycompat.raw_encode(mask)
|
||||
windows.winproxy.SymSearch(self.handle, gdef.DWORD64(addr), 0, tag, mask, 0, callback, res, options)
|
||||
for sym in res:
|
||||
sym.resolver = self
|
||||
sym.displacement = 0
|
||||
return res
|
||||
|
||||
def get_symbols(self, addr, callback=None):
|
||||
res = []
|
||||
if callback is None:
|
||||
callback = self.simple_aggregator
|
||||
else:
|
||||
callback = ctypes.WINFUNCTYPE(gdef.BOOL, ctypes.POINTER(SymbolInfo), gdef.ULONG , ctypes.py_object)(callback)
|
||||
try:
|
||||
windows.winproxy.SymEnumSymbolsForAddr(self.handle, addr, callback, res)
|
||||
except WindowsError as e:
|
||||
if e.winerror == gdef.ERROR_MOD_NOT_FOUND:
|
||||
return []
|
||||
raise
|
||||
|
||||
for sym in res:
|
||||
sym.resolver = self
|
||||
sym.displacement = 0
|
||||
return res
|
||||
|
||||
# Type stuff
|
||||
def get_type(self, name, mod=0):
|
||||
max_len_size = 0x1000
|
||||
full_size = ctypes.sizeof(SymbolInfo) + (max_len_size - 1)
|
||||
buff = windows.utils.BUFFER(SymbolInfo)(size=full_size)
|
||||
buff[0].SizeOfStruct = ctypes.sizeof(SymbolInfo)
|
||||
buff[0].MaxNameLen = max_len_size
|
||||
windows.winproxy.SymGetTypeFromName(self.handle, mod, name, buff)
|
||||
return SymbolType.from_symbol_info(buff[0], resolver=self)
|
||||
|
||||
|
||||
# TODO: mets de l'huile pour w4kfu
|
||||
class StackWalker(object):
|
||||
def __init__(self, resolver, process=None, thread=None, context=None):
|
||||
self.resolver = resolver
|
||||
if process is None and thread is None:
|
||||
raise ValueError("At least a process or thread must be provided")
|
||||
if process is None:
|
||||
process = thread.owner
|
||||
self.process = process
|
||||
self.thread = thread
|
||||
self.context = context
|
||||
if windows.current_process.bitness == 32 and process.bitness == 64:
|
||||
raise NotImplementedError("StackWalking 64b does not seems to works from 32b process")
|
||||
|
||||
def _stack_frame_generator(self):
|
||||
ctx, machine = self._get_effective_context_and_machine()
|
||||
frame = self._setup_initial_frame_from_context(ctx, machine)
|
||||
thread_handle = self.thread.handle if self.thread else None
|
||||
while True:
|
||||
try:
|
||||
windows.winproxy.StackWalkEx(machine,
|
||||
# dbg.current_process.handle,
|
||||
self.resolver.handle,
|
||||
thread_handle,
|
||||
# 0,
|
||||
frame,
|
||||
ctypes.byref(ctx),
|
||||
None,
|
||||
winproxy.resolve(winproxy.SymFunctionTableAccess64),
|
||||
winproxy.resolve(winproxy.SymGetModuleBase64),
|
||||
None,
|
||||
0)
|
||||
except WindowsError as e:
|
||||
if not e.winerror:
|
||||
return # No_ERROR -> end of stack walking
|
||||
raise
|
||||
yield type(frame).from_buffer_copy(frame) # Make a copy ?
|
||||
|
||||
def __iter__(self):
|
||||
return self._stack_frame_generator()
|
||||
|
||||
# Autorise to force the retrieving of 32b stack when code is currently on 64b code ?
|
||||
def _get_effective_context_and_machine(self):
|
||||
ctx = self.context or self.thread.context
|
||||
if self.process.bitness == 32:
|
||||
# Process is 32b, so the context is inevitably x86
|
||||
return (ctx, gdef.IMAGE_FILE_MACHINE_I386)
|
||||
if windows.current_process.bitness == 32:
|
||||
# If we are 32b, we will only be able to handle x86 stack
|
||||
# ctx is obligatory a 32b one, as the case us32/target64 is handled
|
||||
# in __init__ with a NotImplementedError
|
||||
return (ctx, gdef.IMAGE_FILE_MACHINE_I386)
|
||||
if self.process.bitness == 64:
|
||||
# Process is 64b, so the context is inevitably x64
|
||||
return (ctx, gdef.IMAGE_FILE_MACHINE_AMD64)
|
||||
# Thing get a little more complicated here :)
|
||||
# We are a 64b process and target is 32b.
|
||||
# So we must find-out if we are in 32 or 64b world at the moment.
|
||||
# The context_syswow.SegCS give us the information
|
||||
# The context32.SegCs would be always 32
|
||||
ctxsyswow = dbg.current_thread.context_syswow
|
||||
if ctxsyswow.SegCs == gdef.CS_USER_32B:
|
||||
return (ctx, gdef.IMAGE_FILE_MACHINE_I386)
|
||||
return (ctxsyswow, gdef.IMAGE_FILE_MACHINE_AMD64)
|
||||
|
||||
def _setup_initial_frame_from_context(self, ctx, machine):
|
||||
frame = gdef.STACKFRAME_EX()
|
||||
frame.AddrPC.Mode = gdef.AddrModeFlat
|
||||
frame.AddrFrame.Mode = gdef.AddrModeFlat
|
||||
frame.AddrStack.Mode = gdef.AddrModeFlat
|
||||
frame.AddrPC.Offset = ctx.pc
|
||||
frame.AddrStack.Offset = ctx.sp
|
||||
if machine == gdef.IMAGE_FILE_MACHINE_I386:
|
||||
frame.AddrFrame.Offset = ctx.Ebp
|
||||
# Need RBP on 64b ?
|
||||
return frame
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class VirtualSymbolHandler(SymbolHandler):
|
||||
"""A SymbolHandler where its handle is not a valid process handle
|
||||
Allow to create/resolve symbol in a 'virtual' process
|
||||
But all API needing a real process handle will fail
|
||||
"""
|
||||
VIRTUAL_HANDLER_COUNTER = itertools.count(0x11223344)
|
||||
def __init__(self, search_path=None):
|
||||
handle = next(self.VIRTUAL_HANDLER_COUNTER)
|
||||
super(VirtualSymbolHandler, self).__init__(handle, search_path, False)
|
||||
|
||||
# The VirtualSymbolHandler is not based on an existing process
|
||||
# So load() in its simplest for should just take the path of the file to load
|
||||
load = SymbolHandler.load_file
|
||||
"""An alias for :func:`VirtualSymbolHandler.load_file`"""
|
||||
|
||||
def refresh(self):
|
||||
"""Do nothing for a :class:`VirtualSymbolHandler`"""
|
||||
return False
|
||||
|
||||
|
||||
class ProcessSymbolHandler(SymbolHandler):
|
||||
def __init__(self, process, search_path=None, invade_process=False):
|
||||
super(ProcessSymbolHandler, self).__init__(process.handle, search_path, invade_process)
|
||||
self.target = process
|
||||
|
||||
# The ProcessSymbolHandler is based on an existing process
|
||||
# So load() in its simplest form should be able to load the symbol for an existing
|
||||
# module that is already loaded
|
||||
# Question: should be able to load other module at other address ?
|
||||
def load(self, name):
|
||||
"""Load the :class:`SymbolModule` associated with the loaded module ``name`` (as found in the PEB)
|
||||
|
||||
:return: :class:`SymbolModule`
|
||||
|
||||
Exemple:
|
||||
|
||||
>>> sh = windows.debug.symbols.ProcessSymbolHandler(windows.test.pop_proc_64())
|
||||
<windows.debug.symbols.ProcessSymbolHandler object at 0x033A2C30>
|
||||
>>> sh
|
||||
<windows.debug.symbols.ProcessSymbolHandler object at 0x033A2C30>
|
||||
>>> sh.load("kernelbase.dll")
|
||||
<SymbolModule name="kernelbase" type=SymDeferred pdb="" addr=0x7ffb5b090000>
|
||||
>>> sh["kernelbase!CreateProcessA"]
|
||||
<SymbolInfoA name="CreateProcessA" start=0x7ffb5b2371f0 tag=SymTagPublicSymbol>
|
||||
"""
|
||||
mods = [x for x in self.target.peb.modules if x.name == name]
|
||||
if not mods:
|
||||
raise ValueError("Could not find module <{0}>".format(name))
|
||||
assert len(mods) == 1 # Load all if multiple match ?
|
||||
mod = mods[0]
|
||||
return self.load_module(addr=mod.baseaddr, path=mod.fullname)
|
||||
|
||||
def refresh(self):
|
||||
"""Update the list of loaded modules to match the modules present in the target process
|
||||
|
||||
.. note::
|
||||
This function only call `SymRefreshModuleList <https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symrefreshmodulelist>`_ for now.
|
||||
It seems that this function do not handle refreshing a 64b target from a 32b python
|
||||
|
||||
Also, on a 32b target from a 64b python it seems to only load symbols for the 64b modules (ntdll + syswow dll)
|
||||
|
||||
Exemple:
|
||||
|
||||
>>> sh = windows.debug.symbols.ProcessSymbolHandler(windows.test.pop_proc_64())
|
||||
>>> sh.modules
|
||||
[]
|
||||
>>> sh.refresh()
|
||||
44
|
||||
>>> sh.modules
|
||||
[<SymbolModule name="notepad" type=SymDeferred pdb="" addr=0x7ff772b80000>,
|
||||
<SymbolModule name="ntdll" type=SymDeferred pdb="" addr=0x7ffb5d860000>,
|
||||
<SymbolModule name="KERNEL32" type=SymDeferred pdb="" addr=0x7ffb5bb90000>,
|
||||
<SymbolModule name="KERNELBASE" type=SymDeferred pdb="" addr=0x7ffb5b090000>,
|
||||
...]
|
||||
"""
|
||||
return windows.winproxy.SymRefreshModuleList(self.handle)
|
||||
|
||||
|
||||
def stackwalk(self, ctx):
|
||||
pass
|
||||
|
||||
|
||||
class SymbolEngine(object):
|
||||
"""Represent the global symbol engine. Just a proxy to get/set global engine options
|
||||
|
||||
Its instance can be accessed using ``windows.debug.symbols.engine``
|
||||
|
||||
Exemple:
|
||||
|
||||
>>> windows.debug.symbols.engine.options
|
||||
6L
|
||||
>>> windows.debug.symbols.engine.options = gdef.SYMOPT_UNDNAME
|
||||
>>> windows.debug.symbols.engine.options
|
||||
2L
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
# use to now if we need to call the setup of options
|
||||
# At the first DbgHelp call
|
||||
self.options_already_setup = False
|
||||
|
||||
def set_options(self, options):
|
||||
self.options_already_setup = True
|
||||
return windows.winproxy.SymSetOptions(options)
|
||||
|
||||
def get_options(self):
|
||||
return windows.winproxy.SymGetOptions()
|
||||
|
||||
options = property(get_options, set_options)
|
||||
"""The options of the Symbol engine
|
||||
(`see options <https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetoptions#parameters>`_)
|
||||
|
||||
.. note::
|
||||
|
||||
Default options are: ``gdef.SYMOPT_DEFERRED_LOADS + gdef.SYMOPT_UNDNAME``
|
||||
"""
|
||||
|
||||
engine = SymbolEngine()
|
||||
"""The instance of the :class:`SymbolEngine`"""
|
||||
|
||||
|
||||
TST_TYPE_RES_TYPE = {
|
||||
gdef.TI_GET_SYMNAME: gdef.LPWSTR,
|
||||
gdef.TI_GET_LENGTH: gdef.ULONG64,
|
||||
gdef.TI_GET_ADDRESS: gdef.ULONG64,
|
||||
gdef.TI_GTIEX_REQS_VALID: gdef.ULONG64,
|
||||
gdef.TI_GET_SYMTAG: gdef.SymTagEnum,
|
||||
gdef.TI_GET_VALUE: windows.com.Variant,
|
||||
}
|
||||
Reference in New Issue
Block a user