From e70c97cbfc76e84feed61146402107cb2157f73a Mon Sep 17 00:00:00 2001 From: hakril Date: Mon, 18 May 2020 22:48:12 +0200 Subject: [PATCH] Added symbol sample + SymbolDebugger --- samples/debug/symbol_debugger.py | 36 +++++++++++ samples/debug/symbols/processsymdemo.py | 50 +++++++++++++++ samples/debug/symbols/symsearch.py | 28 +++++++++ samples/debug/symbols/virtsymdemo.py | 59 ++++++++++++++++++ windows/debug/debugger.py | 4 ++ windows/debug/symboldbg.py | 48 ++++++++++++++ windows/debug/symbols.py | 83 +++++++++++++++++-------- windows/winproxy/apis/version.py | 26 +++++++- 8 files changed, 308 insertions(+), 26 deletions(-) create mode 100644 samples/debug/symbol_debugger.py create mode 100644 samples/debug/symbols/processsymdemo.py create mode 100644 samples/debug/symbols/symsearch.py create mode 100644 samples/debug/symbols/virtsymdemo.py create mode 100644 windows/debug/symboldbg.py diff --git a/samples/debug/symbol_debugger.py b/samples/debug/symbol_debugger.py new file mode 100644 index 0000000..d7275da --- /dev/null +++ b/samples/debug/symbol_debugger.py @@ -0,0 +1,36 @@ +import argparse +import os + +import windows +import windows.debug +import windows.test + + +parser = argparse.ArgumentParser(prog=__file__, formatter_class=argparse.ArgumentDefaultsHelpFormatter) +parser.add_argument('--dbghelp', help='The path of DBG help to use (default use env:PFW_DBGHELP_PATH)') +args = parser.parse_args() +print(args) + +if args.dbghelp: + symbols.set_dbghelp_path(args.dbghelp) +else: + if "PFW_DBGHELP_PATH" not in os.environ: + print("Not dbghelp path given and no environ var 'PFW_DBGHELP_PATH' sample may fail") + + +class MyInfoBP(windows.debug.Breakpoint): + COUNT = 0 + def trigger(self, dbg, exc): + cursym = dbg.current_resolver[exc.ExceptionRecord.ExceptionAddress] + print("Breakpoint triggered at: {0}".format(cursym)) + print(repr(cursym)) + MyInfoBP.COUNT += 1 + if MyInfoBP.COUNT == 4: + print("Quitting") + dbg.current_process.exit() + print("") + +dbg = windows.debug.SymbolDebugger.debug(r"c:\windows\system32\notepad.exe") +dbg.add_bp(MyInfoBP("kernelbase!CreateFileInternal+2")) +dbg.add_bp(MyInfoBP("ntdll!LdrpInitializeProcess")) +dbg.loop() \ No newline at end of file diff --git a/samples/debug/symbols/processsymdemo.py b/samples/debug/symbols/processsymdemo.py new file mode 100644 index 0000000..9437b28 --- /dev/null +++ b/samples/debug/symbols/processsymdemo.py @@ -0,0 +1,50 @@ +import os +import argparse + +import windows +import windows.test +import windows.generated_def as gdef +from windows.debug import symbols + + +parser = argparse.ArgumentParser(prog=__file__, formatter_class=argparse.ArgumentDefaultsHelpFormatter) +parser.add_argument('--dbghelp', help='The path of DBG help to use (default use env:PFW_DBGHELP_PATH)') +args = parser.parse_args() +print(args) + +if args.dbghelp: + symbols.set_dbghelp_path(args.dbghelp) +else: + if "PFW_DBGHELP_PATH" not in os.environ: + print("Not dbghelp path given and no environ var 'PFW_DBGHELP_PATH' sample may fail") + + +if windows.current_process.bitness == 32: + target = windows.test.pop_proc_32() +else: + target = windows.test.pop_proc_64() + +print("Target is {0}".format(target)) +sh = symbols.ProcessSymbolHandler(target) +import time;time.sleep(0.1) # Just wait for the process initialisation +sh.refresh() # Refresh symbol list (Only meaningful for ProcessSymbolHandler) + +print("Some loaded modules are:".format()) +for sm in sh.modules[:3]: + print(" * {0}".format(sm)) + +createserv = sh["advapi32!CreateServiceEx"] + +print("") +TEST_FUNCTION = "advapi32!CreateServiceEx" +print("Resolving function <{0}>".format(TEST_FUNCTION)) +createserv = sh[TEST_FUNCTION] +print("Symbol found !") +print(" * __repr__: {0!r}".format(createserv)) +print(" * __str__: {0}".format(createserv)) +print(" * addr: {0:#x}".format(createserv.addr)) +print(" * name: {0}".format(createserv.name)) +print(" * fullname: {0}".format(createserv.fullname)) +print(" * module: {0}".format(createserv.module)) + +target.exit() \ No newline at end of file diff --git a/samples/debug/symbols/symsearch.py b/samples/debug/symbols/symsearch.py new file mode 100644 index 0000000..4d476f6 --- /dev/null +++ b/samples/debug/symbols/symsearch.py @@ -0,0 +1,28 @@ +import argparse +import os + +import windows +import windows.debug.symbols as symbols + + +parser = argparse.ArgumentParser(prog=__file__, formatter_class=argparse.ArgumentDefaultsHelpFormatter) +parser.add_argument('pattern') +parser.add_argument('file', help="The PE file to load") +parser.add_argument('--addr', type=lambda x: int(x, 0), default=0, help="The load address of the PE") +parser.add_argument('--tag', type=lambda x: int(x, 0), default=0) +parser.add_argument('--dbghelp', help='The path of DBG help to use (default use env:PFW_DBGHELP_PATH)') + +args = parser.parse_args() +if args.dbghelp: + symbols.set_dbghelp_path(args.dbghelp) +else: + if "PFW_DBGHELP_PATH" not in os.environ: + print("Not dbghelp path given and no environ var 'PFW_DBGHELP_PATH' sample may fail") + + +sh = symbols.VirtualSymbolHandler() +mod = sh.load_file(path=args.file, addr=args.addr) +res = sh.search(args.pattern, mod=mod, tag=args.tag) +print("{0} symbols found:".format(len(res))) +for sym in res: + print(" * {0!r}".format(sym)) diff --git a/samples/debug/symbols/virtsymdemo.py b/samples/debug/symbols/virtsymdemo.py new file mode 100644 index 0000000..18e40e5 --- /dev/null +++ b/samples/debug/symbols/virtsymdemo.py @@ -0,0 +1,59 @@ +import os +import windows +import windows.generated_def as gdef +from windows.debug import symbols +import argparse + + +parser = argparse.ArgumentParser(prog=__file__, formatter_class=argparse.ArgumentDefaultsHelpFormatter) +parser.add_argument('--dbghelp', help='The path of DBG help to use (default use env:PFW_DBGHELP_PATH)') +args = parser.parse_args() +print(args) + +if args.dbghelp: + symbols.set_dbghelp_path(args.dbghelp) +else: + if "PFW_DBGHELP_PATH" not in os.environ: + print("Not dbghelp path given and no environ var 'PFW_DBGHELP_PATH' sample may fail") + + +symbols.engine.options = 0 # Disable defered load +sh = symbols.VirtualSymbolHandler() + +ntmod = sh.load_file(r"c:\windows\system32\ntdll.dll", addr=0x420000) + +print("Ntdll module is: {0}".format(ntmod)) +print(" * name = {0}".format(ntmod.name)) +print(" * addr = {0:#x}".format(ntmod.addr)) +print(" * path = {0:}".format(ntmod.path)) +print(" * type = {0:}".format(ntmod.type)) +print(" * pdb = {0:}".format(ntmod.pdb)) + +print("") +TEST_FUNCTION = "LdrLoadDll" +print("Resolving function <{0}>".format(TEST_FUNCTION)) +loaddll = sh["ntdll!" + TEST_FUNCTION] +print("Symbol found !") +print(" * __repr__: {0!r}".format(loaddll)) +print(" * __str__: {0}".format(loaddll)) +print(" * addr: {0:#x}".format(loaddll.addr)) +print(" * name: {0}".format(loaddll.name)) +print(" * fullname: {0}".format(loaddll.fullname)) +print(" * module: {0}".format(loaddll.module)) + +print("") +print("Loading kernelbase") +kbasemod = sh.load_file(r"c:\windows\system32\kernelbase.dll", addr=0x1230000) +print("Loaded modules are: {0}".format(sh.modules)) +LOOKUP_ADDR = 0x1231242 +print("Looking up address: {0:#x}".format(LOOKUP_ADDR)) +lookupsym = sh[LOOKUP_ADDR] +print("Symbol resolved !") +print(" * __repr__: {0!r}".format(lookupsym)) +print(" * __str__: {0}".format(lookupsym)) +print(" * start: {0:#x}".format(lookupsym.start)) +print(" * addr: {0:#x}".format(lookupsym.addr)) +print(" * displacement: {0:#x}".format(lookupsym.displacement)) +print(" * name: {0}".format(lookupsym.name)) +print(" * fullname: {0}".format(lookupsym.fullname)) +print(" * module: {0}".format(lookupsym.module)) diff --git a/windows/debug/debugger.py b/windows/debug/debugger.py index 9df225a..82add3a 100644 --- a/windows/debug/debugger.py +++ b/windows/debug/debugger.py @@ -771,6 +771,7 @@ class Debugger(object): self.breakpoints[self.current_process.pid] = {} self._memory_save[self.current_process.pid] = {} self._module_by_process[self.current_process.pid] = {} + self._internal_on_create_process(create_process) # Allow hook for symbol-debugger self._update_debugger_state(debug_event) self._add_exe_to_module_list(create_process) self._setup_pending_breakpoints_new_process(self.current_process) @@ -836,6 +837,9 @@ class Debugger(object): del self._breakpoint_to_reput[self.current_thread.tid] return retvalue + def _internal_on_create_process(self, create_process): + return None + def _internal_on_load_dll(self, load_dll): return None diff --git a/windows/debug/symboldbg.py b/windows/debug/symboldbg.py new file mode 100644 index 0000000..3774771 --- /dev/null +++ b/windows/debug/symboldbg.py @@ -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 \ No newline at end of file diff --git a/windows/debug/symbols.py b/windows/debug/symbols.py index ef3465d..d70e968 100644 --- a/windows/debug/symbols.py +++ b/windows/debug/symbols.py @@ -41,15 +41,18 @@ except KeyError as e: class SymbolInfoBase(object): """Represent a Symbol. - This class in based on the class `SYMBOL_INFO`_ - with the handling on displacement embeded into it.""" + This class in based on the class `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) - self.displacement = kwargs.get("displacement", 0) + #: POUET POUET + self.displacement = kwargs.get("displacement", 0) #: POUET POUET + def as_type(self): # assert self.Address == 0 ? @@ -89,6 +92,14 @@ class SymbolInfoBase(object): """ 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 @@ -101,20 +112,35 @@ class SymbolInfoBase(object): 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) - return '<{0} name="{1}" start={2:#x} tag={3}>'.format(type(self).__name__, self.name, self.start, self.tag) + 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 `_ - with the handling on displacement embeded into it.""" + 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 + + >>> 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): - """Represent a Symbol. - This class in based on the class `SYMBOL_INFO `_ - with the handling on displacement embeded into it.""" CHAR_TYPE = gdef.WCHAR # We use the A Api in our code (for now) @@ -407,9 +433,9 @@ class SymbolHandler(object): >>> mod >>> sh.resolve("kernelbase!CreateFileInternal") - + >>> sh[0x100f2042] - + >>> str(sh[0x100f2042]) 'kernelbase!ReadFile+0x162' """ @@ -445,9 +471,9 @@ class SymbolHandler(object): >>> sh = windows.debug.symbols.VirtualSymbolHandler() >>> mod = sh.load_file(r"c:\windows\system32\kernelbase.dll") >>> sh.search("kernelbase!CreateFile*") - [, - , - , + [, + , + , ...] """ res = [] @@ -455,7 +481,10 @@ class SymbolHandler(object): callback = self.simple_aggregator else: callback = ctypes.WINFUNCTYPE(gdef.BOOL, ctypes.POINTER(SymbolInfo), gdef.ULONG , ctypes.py_object)(callback) - windows.winproxy.SymSearch(self.handle, gdef.DWORD64(mod), 0, tag, mask, 0, callback, res, options) + + addr = getattr(mod, "addr", mod) # Retrieve mod.addr, else us the value directly + + 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 @@ -490,6 +519,7 @@ class SymbolHandler(object): 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 @@ -607,15 +637,14 @@ class ProcessSymbolHandler(SymbolHandler): Exemple: - >>> x = windows.debug.symbols.ProcessSymbolHandler(windows.test.pop_proc_64()) + >>> sh = windows.debug.symbols.ProcessSymbolHandler(windows.test.pop_proc_64()) - >>> x + >>> sh - >>> x.load("kernelbase.dll") + >>> sh.load("kernelbase.dll") - >>> x["kernelbase!CreateProcessA"] - - + >>> sh["kernelbase!CreateProcessA"] + """ mods = [x for x in self.target.peb.modules if x.name == name] if not mods: @@ -635,12 +664,12 @@ class ProcessSymbolHandler(SymbolHandler): Exemple: - >>> x = windows.debug.symbols.ProcessSymbolHandler(windows.test.pop_proc_64()) - >>> x.modules + >>> sh = windows.debug.symbols.ProcessSymbolHandler(windows.test.pop_proc_64()) + >>> sh.modules [] - >>> x.refresh() + >>> sh.refresh() 44 - >>> x.modules + >>> sh.modules [, , , @@ -683,6 +712,10 @@ class SymbolEngine(object): options = property(get_options, set_options) """The options of the Symbol engine (`see options `_) + + .. note:: + + Default options are: ``gdef.SYMOPT_DEFERRED_LOADS + gdef.SYMOPT_UNDNAME`` """ engine = SymbolEngine() diff --git a/windows/winproxy/apis/version.py b/windows/winproxy/apis/version.py index ce5b9a3..2c00d0d 100644 --- a/windows/winproxy/apis/version.py +++ b/windows/winproxy/apis/version.py @@ -15,29 +15,53 @@ def GetFileVersionInfoA(lptstrFilename, dwHandle=0, dwLen=None, lpData=NeededPar dwLen = len(lpData) return GetFileVersionInfoA.ctypes_function(lptstrFilename, dwHandle, dwLen, lpData) + @VersionProxy() def GetFileVersionInfoW(lptstrFilename, dwHandle=0, dwLen=None, lpData=NeededParameter): if dwLen is None and lpData is not None: dwLen = len(lpData) return GetFileVersionInfoW.ctypes_function(lptstrFilename, dwHandle, dwLen, lpData) + +@VersionProxy() +def GetFileVersionInfoExA(dwFlags, lpwstrFilename, dwHandle, dwLen, lpData): + return GetFileVersionInfoExA.ctypes_function(dwFlags, lpwstrFilename, dwHandle, dwLen, lpData) + + +@VersionProxy() +def GetFileVersionInfoExW(dwFlags, lpwstrFilename, dwHandle, dwLen, lpData): + return GetFileVersionInfoExW.ctypes_function(dwFlags, lpwstrFilename, dwHandle, dwLen, lpData) + + @VersionProxy() def GetFileVersionInfoSizeA(lptstrFilename, lpdwHandle=None): if lpdwHandle is None: lpdwHandle = ctypes.byref(gdef.DWORD()) return GetFileVersionInfoSizeA.ctypes_function(lptstrFilename, lpdwHandle) + @VersionProxy() def GetFileVersionInfoSizeW(lptstrFilename, lpdwHandle=None): if lpdwHandle is None: lpdwHandle = ctypes.byref(gdef.DWORD()) return GetFileVersionInfoSizeW.ctypes_function(lptstrFilename, lpdwHandle) + +@VersionProxy() +def GetFileVersionInfoSizeExA(dwFlags, lpwstrFilename, lpdwHandle=None): + return GetFileVersionInfoSizeExA.ctypes_function(dwFlags, lpwstrFilename, lpdwHandle) + + +@VersionProxy() +def GetFileVersionInfoSizeExW(dwFlags, lpwstrFilename, lpdwHandle=None): + return GetFileVersionInfoSizeExW.ctypes_function(dwFlags, lpwstrFilename, lpdwHandle) + + @VersionProxy() def VerQueryValueA(pBlock, lpSubBlock, lplpBuffer, puLen): return VerQueryValueA.ctypes_function(pBlock, lpSubBlock, lplpBuffer, puLen) + @VersionProxy() def VerQueryValueW(pBlock, lpSubBlock, lplpBuffer, puLen): return VerQueryValueW.ctypes_function(pBlock, lpSubBlock, lplpBuffer, puLen) -