├── .gitignore ├── AUTHORS ├── LICENSE ├── README.md ├── bin ├── DBGDLL │ ├── dbgeng.dll │ ├── dbghelp.dll │ ├── symsrv.dll │ └── symsrv.yes ├── DBGDLL64 │ ├── dbgeng.dll │ ├── dbghelp.dll │ ├── symsrv.dll │ └── symsrv.yes ├── windbg_driver_x64.sys └── windbg_driver_x86.sys ├── dbgdef.py ├── dbginterface.py ├── dbgtype.py ├── doc ├── Makefile ├── make.bat └── source │ ├── conf.py │ ├── dbginterface.rst │ └── index.rst ├── driver_upgrade.py ├── example ├── PEBS_BTS_demo.py ├── hook_ntcreatefile.py ├── ida_demos_commands.md ├── idt.py ├── output_demo.py ├── pci_vendor.py ├── simple_pci_exploration.py ├── smm_check.py └── type_demo.py ├── resource_emulation.py ├── setup.py ├── simple_com.py ├── test ├── __init__.py └── test_all.py └── windows ├── __init__.py ├── dbgprint.py ├── generated_def ├── __init__.py ├── ntstatus.py ├── windef.py ├── winfuncs.py └── winstructs.py ├── hooks.py ├── injection.py ├── native_exec ├── __init__.py ├── cpuid.py ├── native_function.py ├── simple_x64.py └── simple_x86.py ├── pe_parse.py ├── remotectypes.py ├── syswow64.py ├── test ├── __init__.py └── mytest.py ├── utils ├── __init__.py ├── pythonutils.py └── winutils.py ├── winobject.py └── winproxy.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | doc/build/ 3 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Samuel Chevet 2 | Clement Rouault -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Sogeti ESEC Lab 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Local Kernel Debugger 2 | 3 | Local Kernel Debugger (LKD) is a python wrapper around `dbgengine.dll` to 4 | perform local kernel debugging of a Windows kernel booted in DEBUG mode. 5 | 6 | ## How does LKD work ? 7 | 8 | Local Kernel Debugging is the ability to perform kernel-mode debugging on a 9 | single computer. In other words, the debugger runs on the same computer 10 | that is being debugged. 11 | Windows offers this functionality through WinDbg and KD binaries which allow to 12 | read/write the kernel memory, perform in/out and access MSRs. 13 | WinDbg and KD use `dbgengine.dll` to offer this functionality, but `dbgengine` only 14 | allows binary with name `windbg.exe` or `kd.exe` and also requires that the said 15 | binary embeds a driver used to perform the local kernel debugging actions. 16 | 17 | To bypass these restrictions we used IAT hook against `dbgengine.dll` to emulate the 18 | resource (that is read from a file) and also change the result of 19 | `GetModuleFileNameW` to `C:\\windbg.exe`. 20 | 21 | With that we are able to retrieve some COM interfaces offered by `dbgengine` and 22 | used to perform Local Kernel Debugging. 23 | Last problem is that some functionalities we find useful are missing in the 24 | LKD driver (memory allocation, custom kernel call, ...) so we 25 | use the LKD driver to rewrite some part of it in memory to upgrade its features. 26 | 27 | ## What you need to know to use LKD 28 | 29 | ### Symbols 30 | 31 | The LKD try to use the usual environment variable `_NT_SYMBOL_PATH` to retrieve the symbol path. 32 | If this variable does not exist it will use the `.\symbols` directory in the current path 33 | of `dbginterface.py` 34 | 35 | Some part of LKD need (for now) to have access to the `ntoskrnl` symbols so the 36 | debugged computer must be connected to Internet at least once. 37 | 38 | ### 32bits vs 64bits 39 | 40 | LKD cannot be done in a SysWow64 process, if you try to debug a 64bits kernel 41 | you will need a 64bits python. 42 | 43 | You can find every MSI you need at https://www.python.org/downloads/windows/ 44 | 45 | ### Examples 46 | 47 | See the [example directory][EX_DIRECTORY]. 48 | 49 | ## files / directories description 50 | 51 | - `dbginterface.py` The main file of the project, LKD objects that setup the IAT hooks for the WinDbg imposture, attach to the local kernel, retrieve the COM interfaces and wrap them. 52 | 53 | - `resource_emulation.py` IAT hooks that allow to emulate a resource from a file in the File System. 54 | 55 | - `simple_com.py` Simple wrapper to COM interface (used in [example\output_demo.py][OUTPUT_DEMO]). 56 | 57 | - `driver_upgrade.py` Code that rewrite part of the LKD driver in memory to upgrade its features 58 | 59 | - `bin\windbg_driver_x86.sys` LKD (signed) driver for 32bits kernel extracted from WinDbg's resources 60 | 61 | - `bin\windbg_driver_x64.sys` LKD (signed) driver for 64bits kernel extracted from WinDbg's resources 62 | 63 | - `bin\DBGDLL\` dbgengine + symbol engine for windows 32bits 64 | 65 | - `bin\DBGDLL64\` dbgengine + symbol engine for windows 64bits 66 | 67 | - `test\` Our test, need to be launched on 32bits and 64bits kernels to be complete 68 | 69 | - `example\` Our demos code of the use and interest of LKD 70 | 71 | - `doc\` Sphinx documentation ([Available online][DOC_GEN]) 72 | 73 | - `windows\` 74 | Side project with some windows helpers, some part of this are not directly used by LKD. 75 | The features used are: 76 | - IAT Hooks 77 | - Windows API proxy 78 | - Native execution 79 | - simple_x86 / simple_x64 assembler 80 | 81 | ## Shady part of LKD 82 | 83 | ### `do_in` / `do_out` VS `read_io` / `write_io` 84 | 85 | There is a COM API to perform I/O: `WriteIo` and `ReadIo` but it seems that 86 | these API check (`nt!KdpSysReadIoSpace` & `nt!KdpSysWriteIoSpace`) for some alignment in the port and the size. 87 | 88 | A ReadIo(size=4) must be on a port aligned on 4. 89 | 90 | For performing any I/O, LKD offers two function: `do_in`, `do_out`. 91 | 92 | do_in(self, port, size) 93 | 94 | Perform IN instruction in kernel mode 95 | do_out(self, port, value, size) 96 | 97 | Perform OUT instruction in kernel mode 98 | 99 | ### `expand_address_to_ulong64` / `trim_ulong64_to_address` 100 | 101 | Used to convert a symbol address to an ULONG64 requested by the API and vice versa. 102 | Problem is that in a 32bits kernel the kernel addresses are bit expended. 103 | 104 | `ntoskrnl` base in 32bits kernel would not be `0x8XXXXXXX` but `0xffffffff8XXXXXXX`. 105 | 106 | [EX_DIRECTORY]: https://github.com/sogeti-esec-lab/LKD/tree/master/example 107 | [OUTPUT_DEMO]: https://github.com/sogeti-esec-lab/LKD/blob/master/example/output_demo.py 108 | [DOC_GEN]: https://sogeti-esec-lab.github.io/LKD/dbginterface.html 109 | -------------------------------------------------------------------------------- /bin/DBGDLL/dbgeng.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogeti-esec-lab/LKD/f388b5f8c08b7bba2a31c5a16ea64add6cc2dd1a/bin/DBGDLL/dbgeng.dll -------------------------------------------------------------------------------- /bin/DBGDLL/dbghelp.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogeti-esec-lab/LKD/f388b5f8c08b7bba2a31c5a16ea64add6cc2dd1a/bin/DBGDLL/dbghelp.dll -------------------------------------------------------------------------------- /bin/DBGDLL/symsrv.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogeti-esec-lab/LKD/f388b5f8c08b7bba2a31c5a16ea64add6cc2dd1a/bin/DBGDLL/symsrv.dll -------------------------------------------------------------------------------- /bin/DBGDLL/symsrv.yes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogeti-esec-lab/LKD/f388b5f8c08b7bba2a31c5a16ea64add6cc2dd1a/bin/DBGDLL/symsrv.yes -------------------------------------------------------------------------------- /bin/DBGDLL64/dbgeng.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogeti-esec-lab/LKD/f388b5f8c08b7bba2a31c5a16ea64add6cc2dd1a/bin/DBGDLL64/dbgeng.dll -------------------------------------------------------------------------------- /bin/DBGDLL64/dbghelp.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogeti-esec-lab/LKD/f388b5f8c08b7bba2a31c5a16ea64add6cc2dd1a/bin/DBGDLL64/dbghelp.dll -------------------------------------------------------------------------------- /bin/DBGDLL64/symsrv.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogeti-esec-lab/LKD/f388b5f8c08b7bba2a31c5a16ea64add6cc2dd1a/bin/DBGDLL64/symsrv.dll -------------------------------------------------------------------------------- /bin/DBGDLL64/symsrv.yes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogeti-esec-lab/LKD/f388b5f8c08b7bba2a31c5a16ea64add6cc2dd1a/bin/DBGDLL64/symsrv.yes -------------------------------------------------------------------------------- /bin/windbg_driver_x64.sys: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogeti-esec-lab/LKD/f388b5f8c08b7bba2a31c5a16ea64add6cc2dd1a/bin/windbg_driver_x64.sys -------------------------------------------------------------------------------- /bin/windbg_driver_x86.sys: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogeti-esec-lab/LKD/f388b5f8c08b7bba2a31c5a16ea64add6cc2dd1a/bin/windbg_driver_x86.sys -------------------------------------------------------------------------------- /dbgdef.py: -------------------------------------------------------------------------------- 1 | from simple_com import get_IID_from_raw 2 | 3 | # COM IID for the interface we need 4 | IID_IDebugClient_raw = 0x27fe5639, 0x8407, 0x4f47, 0x83, 0x64, 0xee, 0x11, 0x8f, 0xb0, 0x8a, 0xc8 5 | IID_IDebugDataSpaces_raw = 0x88f7dfab, 0x3ea7, 0x4c3a, 0xae, 0xfb, 0xc4, 0xe8, 0x10, 0x61, 0x73, 0xaa 6 | IID_IDebugDataSpaces2_raw = 0x7a5e852f, 0x96e9, 0x468f, 0xac, 0x1b, 0x0b, 0x3a, 0xdd, 0xc4, 0xa0, 0x49 7 | IID_IDebugSymbols_raw = 0x8c31e98c, 0x983a, 0x48a5, 0x90, 0x16, 0x6f, 0xe5, 0xd6, 0x67, 0xa9, 0x50 8 | IID_IDebugSymbols2_raw = 0x3a707211, 0xafdd, 0x4495, 0xad, 0x4f, 0x56, 0xfe, 0xcd, 0xf8, 0x16, 0x3f 9 | IID_IDebugSymbols3_raw = 0xf02fbecc, 0x50ac, 0x4f36, 0x9a, 0xd9, 0xc9, 0x75, 0xe8, 0xf3, 0x2f, 0xf8 10 | IID_IDebugControl_raw = 0x5182e668, 0x105e, 0x416e, 0xad, 0x92, 0x24, 0xef, 0x80, 0x04, 0x24, 0xba 11 | 12 | IID_IDebugClient = get_IID_from_raw(IID_IDebugClient_raw) 13 | IID_IDebugDataSpaces = get_IID_from_raw(IID_IDebugDataSpaces_raw) 14 | IID_IDebugDataSpaces2 = get_IID_from_raw(IID_IDebugDataSpaces2_raw) 15 | IID_IDebugSymbols = get_IID_from_raw(IID_IDebugSymbols_raw) 16 | IID_IDebugSymbols2 = get_IID_from_raw(IID_IDebugSymbols2_raw) 17 | IID_IDebugSymbols3 = get_IID_from_raw(IID_IDebugSymbols3_raw) 18 | IID_IDebugControl = get_IID_from_raw(IID_IDebugControl_raw) 19 | 20 | 21 | # defines related to IDebug interfaces 22 | 23 | # Symbol options for symopt: 24 | SYMOPT_CASE_INSENSITIVE = 0x00000001 25 | SYMOPT_UNDNAME = 0x00000002 26 | SYMOPT_DEFERRED_LOADS = 0x00000004 27 | SYMOPT_LOAD_LINES = 0x00000010 28 | SYMOPT_OMAP_FIND_NEAREST = 0x00000020 29 | SYMOPT_NO_UNQUALIFIED_LOADS = 0x00000100 30 | SYMOPT_FAIL_CRITICAL_ERRORS = 0x00000200 31 | SYMOPT_AUTO_PUBLICS = 0x00010000 32 | SYMOPT_NO_IMAGE_SEARCH = 0x00020000 33 | 34 | 35 | DEBUG_ATTACH_LOCAL_KERNEL = 1 36 | DEBUG_END_PASSIVE = 0 37 | DEBUG_END_ACTIVE_DETACH = 0x00000002 38 | 39 | SE_DEBUG_NAME = "SeDebugPrivilege" 40 | 41 | DEBUG_DATA_KPCR_OFFSET = 0 42 | DEBUG_DATA_KPRCB_OFFSET = 1 43 | DEBUG_DATA_KTHREAD_OFFSET = 2 44 | DEBUG_DATA_BASE_TRANSLATION_VIRTUAL_OFFSET = 3 45 | DEBUG_DATA_PROCESSOR_IDENTIFICATION = 4 46 | DEBUG_DATA_PROCESSOR_SPEED = 5 47 | 48 | 49 | # Values for MmMapLockedPagesSpecifyCache 50 | UserMode = 1 51 | MmNonCached = 0 52 | NormalPagePriority = 16 -------------------------------------------------------------------------------- /dbgtype.py: -------------------------------------------------------------------------------- 1 | # # Experimental code # # 2 | 3 | # Idea: make 2 type 4 | # One for field (with field name / bitfield / parentclass / etc) 5 | # One for the type above (fieldname.type) 6 | # That have info about array size and co 7 | 8 | import struct 9 | from windows.generated_def.winstructs import * 10 | 11 | class DbgEngTypeBase(object): 12 | def __init__(self, module, typeid, kdbg): 13 | self.module = module 14 | self.module_name = kdbg.get_symbol(module)[0] 15 | self.typeid = typeid 16 | self.kdbg = kdbg 17 | 18 | def SymGetTypeInfo(self, GetType): 19 | return self.kdbg.SymGetTypeInfo(self.module, self.typeid, GetType) 20 | 21 | @property 22 | def size(self): 23 | return self.kdbg.get_type_size(self.module, self.typeid) 24 | 25 | @property 26 | def type(self): 27 | #if not self.is_array: 28 | # raise ValueError("array_type on non array type") 29 | sub_type = self.kdbg.SymGetTypeInfo(self.module, self.typeid, TI_GET_TYPE) 30 | return DbgEngType(self.module, sub_type, self.kdbg) 31 | 32 | @property 33 | def base_type(self): 34 | #if not self.is_array: 35 | # raise ValueError("array_type on non array type") 36 | sub_type = self.kdbg.SymGetTypeInfo(self.module, self.typeid, TI_GET_BASETYPE) 37 | return DbgEngType(self.module, sub_type, self.kdbg) 38 | 39 | @property 40 | def raw_name(self): 41 | return str(self.SymGetTypeInfo(TI_GET_SYMNAME)) 42 | 43 | def __call__(self, addr): 44 | return DbgEngtypeMapping(self, addr) 45 | 46 | class DbgEngType(DbgEngTypeBase): 47 | @property 48 | def name(self): 49 | return self.kdbg.get_type_name(self.module, self.typeid) 50 | 51 | @property 52 | def is_array(self): 53 | "Todo: Il doit bien y avoir un truc moins crade :D" 54 | return self.name.endswith("[]") 55 | 56 | @property 57 | def is_pointer(self): 58 | "Todo: Il doit bien y avoir un truc moins crade :D" 59 | return self.name.endswith("*") 60 | 61 | @property 62 | def fields(self): 63 | children = self.kdbg.get_childs_types(self.module, self.typeid) 64 | return [DbgEngField(self.module, x, self.kdbg) for x in children.Types] 65 | 66 | @property 67 | def fields_dict(self): 68 | return {x.name: x for x in self.fields} 69 | 70 | @property 71 | def number_elt(self): 72 | return self.SymGetTypeInfo(TI_GET_COUNT) 73 | 74 | def __repr__(self): 75 | return ''.format(self.name) 76 | 77 | def __call__(self, addr): 78 | return get_mapped_type(self, addr) 79 | 80 | class DbgEngField(DbgEngTypeBase): 81 | name = DbgEngTypeBase.raw_name 82 | 83 | @property 84 | def parent(self): 85 | parent_typeid = self.SymGetTypeInfo(TI_GET_CLASSPARENTID) 86 | return DbgEngType(self.module, parent_typeid, self.kdbg) 87 | 88 | @property 89 | def offset(self): 90 | return self.SymGetTypeInfo(TI_GET_OFFSET) 91 | 92 | @property 93 | def bitoff(self): 94 | return self.SymGetTypeInfo(TI_GET_BITPOSITION) 95 | 96 | def __repr__(self): 97 | return ' at offset <{2}> of type <{3}>>'.format(self.parent.name, self.raw_name, hex(self.offset), self.type.name) 98 | 99 | 100 | def get_mapped_type(type, addr): 101 | 102 | if type.is_array: 103 | return DbgEngtypeMappingPtr(type, addr) 104 | 105 | if type.is_pointer and type.name not in ["void*"]: 106 | return DbgEngtypeMappingPtr(type, addr) 107 | 108 | # basic type: no fields 109 | if not type.fields: 110 | unpack_by_size = {1:"B", 2:'H', 4:'I', 8:'Q'} 111 | data = type.kdbg.read_virtual_memory(addr, type.size) 112 | return struct.unpack("<" + unpack_by_size[type.size], data)[0] 113 | return DbgEngtypeMapping(type, addr) 114 | 115 | 116 | class DbgEngtypeMapping(object): 117 | def __init__(self, type, addr): 118 | self.type = type 119 | self.type_field_dict = {x.name : x for x in type.fields} 120 | self.addr = addr 121 | self.kdbg = type.kdbg 122 | 123 | def __getattr__(self, name): 124 | if name not in self.type_field_dict: 125 | raise AttributeError 126 | 127 | field = self.type_field_dict[name] 128 | addr = self.addr + field.offset 129 | 130 | # TODO: bitfield 131 | 132 | return get_mapped_type(field.type, addr) 133 | 134 | def __repr__(self): 135 | return "".format(self.type.name, hex(self.addr)) 136 | 137 | 138 | class DbgEngtypeMappingPtr(object): 139 | def __init__(self, type, addr): 140 | self.type = type 141 | self.addr = addr 142 | self.kdbg = type.kdbg 143 | 144 | if not self.type.is_array and not self.type.is_pointer: 145 | raise ValueError('DbgEngtypeMappingPtr on non ptr type') 146 | 147 | def __getitem__(self, n): 148 | if self.type.is_array: 149 | addr = self.addr + self.type.size * n 150 | else: 151 | addr = self.type.kdbg.read_ptr(self.addr) 152 | addr += self.type.size * n 153 | 154 | target_t = self.type.type 155 | return get_mapped_type(target_t, addr) 156 | 157 | 158 | # Example 159 | # >>> k = kdbg.get_type("nt", "_KPRCB") 160 | # >>> t = k(0xfffff8016c167000) 161 | # >>> t.WheaInfo 162 | # 18446708889364968624L 163 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/LocalKernelDebugger.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/LocalKernelDebugger.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/LocalKernelDebugger" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/LocalKernelDebugger" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | set I18NSPHINXOPTS=%SPHINXOPTS% source 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 2> nul 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\LocalKernelDebugger.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\LocalKernelDebugger.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # LocalKernelDebugger documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Sep 24 12:01:27 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | import shlex 18 | import sphinx.ext.autodoc 19 | 20 | # Based on the trick used in PRAW 21 | # http://stackoverflow.com/a/22023805 22 | os.environ['SPHINX_BUILD'] = '1' 23 | 24 | # If extensions (or modules to document with autodoc) are in another directory, 25 | # add these directories to sys.path here. If the directory is relative to the 26 | # documentation root, use os.path.abspath to make it absolute, like shown here. 27 | #sys.path.insert(0, os.path.abspath('.')) 28 | 29 | sys.path.insert(0, os.path.abspath('../..')) 30 | 31 | from sphinx import version_info 32 | 33 | str_version = ".".join(str(x) for x in version_info[:3]) 34 | if str_version >= "1.3.1": 35 | html_theme = 'classic' 36 | else: 37 | html_theme = 'default' 38 | 39 | def autodoc_skip_attribute(app, what, name, obj, skip, options): 40 | if hasattr(obj, "_do_not_generate_doc"): 41 | return True 42 | return skip or (what == "class" and not callable(obj) and not name.startswith("SECTION")) 43 | 44 | #def require_upgraded_driver_docstring(app, what, name, obj, options, lines): 45 | # if "SECTION" in name: 46 | # lines = ['#### NEW SECTION ####', ''] 47 | # return lines 48 | 49 | def setup(app): 50 | app.connect('autodoc-skip-member', autodoc_skip_attribute) 51 | #app.connect('autodoc-process-docstring', require_upgraded_driver_docstring) 52 | return None 53 | 54 | 55 | autodoc_member_order = 'bysource' 56 | 57 | # -- General configuration ------------------------------------------------ 58 | 59 | # If your documentation needs a minimal Sphinx version, state it here. 60 | #needs_sphinx = '1.0' 61 | 62 | # Add any Sphinx extension module names here, as strings. They can be 63 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 64 | # ones. 65 | extensions = [ 66 | 'sphinx.ext.autodoc', 67 | 'sphinx.ext.intersphinx', 68 | ] 69 | 70 | # Add any paths that contain templates here, relative to this directory. 71 | templates_path = ['_templates'] 72 | 73 | # The suffix(es) of source filenames. 74 | # You can specify multiple suffix as a list of string: 75 | # source_suffix = ['.rst', '.md'] 76 | source_suffix = '.rst' 77 | 78 | # The encoding of source files. 79 | #source_encoding = 'utf-8-sig' 80 | 81 | # The master toctree document. 82 | master_doc = 'index' 83 | 84 | # General information about the project. 85 | project = u'LocalKernelDebugger' 86 | copyright = u'2015, Samuel Chevet, Clement Rouault' 87 | author = u'Samuel Chevet, Clement Rouault' 88 | 89 | # The version info for the project you're documenting, acts as replacement for 90 | # |version| and |release|, also used in various other places throughout the 91 | # built documents. 92 | # 93 | # The short X.Y version. 94 | version = '0.42' 95 | # The full version, including alpha/beta/rc tags. 96 | release = '0.42' 97 | 98 | # The language for content autogenerated by Sphinx. Refer to documentation 99 | # for a list of supported languages. 100 | # 101 | # This is also used if you do content translation via gettext catalogs. 102 | # Usually you set "language" from the command line for these cases. 103 | language = None 104 | 105 | # There are two options for replacing |today|: either, you set today to some 106 | # non-false value, then it is used: 107 | #today = '' 108 | # Else, today_fmt is used as the format for a strftime call. 109 | #today_fmt = '%B %d, %Y' 110 | 111 | # List of patterns, relative to source directory, that match files and 112 | # directories to ignore when looking for source files. 113 | exclude_patterns = [] 114 | 115 | # The reST default role (used for this markup: `text`) to use for all 116 | # documents. 117 | #default_role = None 118 | 119 | # If true, '()' will be appended to :func: etc. cross-reference text. 120 | #add_function_parentheses = True 121 | 122 | # If true, the current module name will be prepended to all description 123 | # unit titles (such as .. function::). 124 | #add_module_names = True 125 | 126 | # If true, sectionauthor and moduleauthor directives will be shown in the 127 | # output. They are ignored by default. 128 | #show_authors = False 129 | 130 | # The name of the Pygments (syntax highlighting) style to use. 131 | pygments_style = 'sphinx' 132 | 133 | # A list of ignored prefixes for module index sorting. 134 | #modindex_common_prefix = [] 135 | 136 | # If true, keep warnings as "system message" paragraphs in the built documents. 137 | #keep_warnings = False 138 | 139 | # If true, `todo` and `todoList` produce output, else they produce nothing. 140 | todo_include_todos = False 141 | 142 | 143 | # -- Options for HTML output ---------------------------------------------- 144 | 145 | # The theme to use for HTML and HTML Help pages. See the documentation for 146 | # a list of builtin themes. 147 | 148 | #html_theme = 'classic' 149 | 150 | # Theme options are theme-specific and customize the look and feel of a theme 151 | # further. For a list of options available for each theme, see the 152 | # documentation. 153 | #html_theme_options = {} 154 | 155 | # Add any paths that contain custom themes here, relative to this directory. 156 | #html_theme_path = [] 157 | 158 | # The name for this set of Sphinx documents. If None, it defaults to 159 | # " v documentation". 160 | #html_title = None 161 | 162 | # A shorter title for the navigation bar. Default is the same as html_title. 163 | #html_short_title = None 164 | 165 | # The name of an image file (relative to this directory) to place at the top 166 | # of the sidebar. 167 | #html_logo = None 168 | 169 | # The name of an image file (within the static path) to use as favicon of the 170 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 171 | # pixels large. 172 | #html_favicon = None 173 | 174 | # Add any paths that contain custom static files (such as style sheets) here, 175 | # relative to this directory. They are copied after the builtin static files, 176 | # so a file named "default.css" will overwrite the builtin "default.css". 177 | html_static_path = ['_static'] 178 | 179 | # Add any extra paths that contain custom files (such as robots.txt or 180 | # .htaccess) here, relative to this directory. These files are copied 181 | # directly to the root of the documentation. 182 | #html_extra_path = [] 183 | 184 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 185 | # using the given strftime format. 186 | #html_last_updated_fmt = '%b %d, %Y' 187 | 188 | # If true, SmartyPants will be used to convert quotes and dashes to 189 | # typographically correct entities. 190 | #html_use_smartypants = True 191 | 192 | # Custom sidebar templates, maps document names to template names. 193 | #html_sidebars = {} 194 | 195 | # Additional templates that should be rendered to pages, maps page names to 196 | # template names. 197 | #html_additional_pages = {} 198 | 199 | # If false, no module index is generated. 200 | #html_domain_indices = True 201 | 202 | # If false, no index is generated. 203 | #html_use_index = True 204 | 205 | # If true, the index is split into individual pages for each letter. 206 | #html_split_index = False 207 | 208 | # If true, links to the reST sources are added to the pages. 209 | #html_show_sourcelink = True 210 | 211 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 212 | #html_show_sphinx = True 213 | 214 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 215 | #html_show_copyright = True 216 | 217 | # If true, an OpenSearch description file will be output, and all pages will 218 | # contain a tag referring to it. The value of this option must be the 219 | # base URL from which the finished HTML is served. 220 | #html_use_opensearch = '' 221 | 222 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 223 | #html_file_suffix = None 224 | 225 | # Language to be used for generating the HTML full-text search index. 226 | # Sphinx supports the following languages: 227 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 228 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 229 | #html_search_language = 'en' 230 | 231 | # A dictionary with options for the search language support, empty by default. 232 | # Now only 'ja' uses this config value 233 | #html_search_options = {'type': 'default'} 234 | 235 | # The name of a javascript file (relative to the configuration directory) that 236 | # implements a search results scorer. If empty, the default will be used. 237 | #html_search_scorer = 'scorer.js' 238 | 239 | # Output file base name for HTML help builder. 240 | htmlhelp_basename = 'LocalKernelDebuggerdoc' 241 | 242 | # -- Options for LaTeX output --------------------------------------------- 243 | 244 | latex_elements = { 245 | # The paper size ('letterpaper' or 'a4paper'). 246 | #'papersize': 'letterpaper', 247 | 248 | # The font size ('10pt', '11pt' or '12pt'). 249 | #'pointsize': '10pt', 250 | 251 | # Additional stuff for the LaTeX preamble. 252 | #'preamble': '', 253 | 254 | # Latex figure (float) alignment 255 | #'figure_align': 'htbp', 256 | } 257 | 258 | # Grouping the document tree into LaTeX files. List of tuples 259 | # (source start file, target name, title, 260 | # author, documentclass [howto, manual, or own class]). 261 | latex_documents = [ 262 | (master_doc, 'LocalKernelDebugger.tex', u'LocalKernelDebugger Documentation', 263 | u'Samuel Chevet, Clement Rouault', 'manual'), 264 | ] 265 | 266 | # The name of an image file (relative to this directory) to place at the top of 267 | # the title page. 268 | #latex_logo = None 269 | 270 | # For "manual" documents, if this is true, then toplevel headings are parts, 271 | # not chapters. 272 | #latex_use_parts = False 273 | 274 | # If true, show page references after internal links. 275 | #latex_show_pagerefs = False 276 | 277 | # If true, show URL addresses after external links. 278 | #latex_show_urls = False 279 | 280 | # Documents to append as an appendix to all manuals. 281 | #latex_appendices = [] 282 | 283 | # If false, no module index is generated. 284 | #latex_domain_indices = True 285 | 286 | 287 | # -- Options for manual page output --------------------------------------- 288 | 289 | # One entry per manual page. List of tuples 290 | # (source start file, name, description, authors, manual section). 291 | man_pages = [ 292 | (master_doc, 'localkerneldebugger', u'LocalKernelDebugger Documentation', 293 | [author], 1) 294 | ] 295 | 296 | # If true, show URL addresses after external links. 297 | #man_show_urls = False 298 | 299 | 300 | # -- Options for Texinfo output ------------------------------------------- 301 | 302 | # Grouping the document tree into Texinfo files. List of tuples 303 | # (source start file, target name, title, author, 304 | # dir menu entry, description, category) 305 | texinfo_documents = [ 306 | (master_doc, 'LocalKernelDebugger', u'LocalKernelDebugger Documentation', 307 | author, 'LocalKernelDebugger', 'One line description of project.', 308 | 'Miscellaneous'), 309 | ] 310 | 311 | # Documents to append as an appendix to all manuals. 312 | #texinfo_appendices = [] 313 | 314 | # If false, no module index is generated. 315 | #texinfo_domain_indices = True 316 | 317 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 318 | #texinfo_show_urls = 'footnote' 319 | 320 | # If true, do not generate a @detailmenu in the "Top" node's menu. 321 | #texinfo_no_detailmenu = False 322 | 323 | 324 | # Example configuration for intersphinx: refer to the Python standard library. 325 | intersphinx_mapping = {'https://docs.python.org/': None} 326 | -------------------------------------------------------------------------------- /doc/source/dbginterface.rst: -------------------------------------------------------------------------------- 1 | dbginterface module 2 | =================== 3 | 4 | .. py:class:: Symbol 5 | 6 | | Not a real class, a function accepting a Symbol means it can accept: 7 | | - A valid windbg symbol ("nt!CreateFile") 8 | | - An address 9 | 10 | .. automodule:: dbginterface 11 | :members: LocalKernelDebugger, LocalKernelDebuggerBase 12 | :undoc-members: 13 | :show-inheritance: 14 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | .. LocalKernelDebugger documentation master file, created by 2 | sphinx-quickstart on Thu Sep 24 12:01:27 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to LocalKernelDebugger's documentation! 7 | =============================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | dbginterface.rst 15 | 16 | -------------------------------------------------------------------------------- /example/PEBS_BTS_demo.py: -------------------------------------------------------------------------------- 1 | """A simple demonstration of LKD to activate PEBS and BTS feature from intel CPU""" 2 | import ctypes 3 | import sys 4 | import os 5 | 6 | if os.getcwd().endswith("example"): 7 | sys.path.append(os.path.realpath("..")) 8 | else: 9 | sys.path.append(os.path.realpath(".")) 10 | 11 | from windows.generated_def.winstructs import * 12 | from dbginterface import LocalKernelDebugger 13 | 14 | 15 | # What is BTS?: 16 | # Branch Trace Store (BTS) is an intel's CPU feature that allows to 17 | # store all the branches (src and dst) taken on a CPU to a buffer 18 | # 19 | # To activate the BTS you need to: 20 | # setup the Debug Store (DS) Area 21 | # setup the BTS related fields in DS 22 | # activate BTS 23 | # see in man intel: 24 | # http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-manual-325462.pdf 25 | # Volume 3B: System Programming Guide 17.4.5 (Branch Trace Store (BTS)) 26 | # Volume 3B: System Programming Guide 17.4.9 (BTS and DS area) 27 | 28 | # !! BTS buffer can be configured to be circular or not (see 17.4.9.3 (Setting Up the BTS Buffer)) 29 | 30 | # What is PEBS?: 31 | # Precise Event Based Sampling (PEBS) is an intel's CPU feature that allows to 32 | # store the CPU states (general purpose registers) at a given event. 33 | # This feature rely on the performance counter 34 | # To activate the PEBS you need to: 35 | # Setup the Debug Store (DS) Area 36 | # Setup the PEBS related fields in DS 37 | # Setup the performance counter that will trigger the PEBS (PERFEVTSE0) here 38 | # Activate PEBS 39 | # Activate the counter 40 | # see in man intel: (lot of micro-architecture stuff here) 41 | # http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-manual-325462.pdf 42 | # Volume 3B: System Programming Guide 18.4 (PERFORMANCE MONITORING (PROCESSORS BASED ON INTELCORE MICROARCHITECTURE) 43 | # Volume 3B: System Programming Guide 18.4.4 (Precise Event Based Sampling (PEBS)) 44 | 45 | # Volume 3B: System Programming Guide 18.7 (PERFORMANCE MONITORING FOR PROCESSORS BASED ON INTEL MICROARCHITECTURE CODE NAME NEHALEM) 46 | # Volume 3B: System Programming Guide 18.7.1.1 (Precise Event Based Sampling (PEBS)) 47 | 48 | # TLDR: 49 | # PEBS will trigger a dump of the PEBSRecord (general purpose registers + some other info) when 50 | # the associated performance counter overflow (PERFEVTSE0 in this code) then 51 | # the performance counter value is reset to DsManagementAreaStruct.PEBSCounterResetValue 52 | # So if you want to dump the state of the proc often you should set this to a high value 53 | # !! THE PEBS BUFFER IS NOT circular 54 | # you need the reset it manually when the good interrupt is raise (see 18.4.4.3 (Writing a PEBS Interrupt Service Routine)) 55 | # This require to inject some code in kerneland (not done in this example) 56 | 57 | 58 | 59 | 60 | DEBUG_STORE_MSR_VALUE = 0x600 61 | IA32_DEBUGCTL_MSR_VALUE = 0x1D9 62 | IA32_MISC_ENABLE = 0x1A0 63 | MSR_PEBS_ENABLED = 0x3F1 64 | MSR_PERF_GLOBAL_CTRL = 0x38f 65 | PERF_CAPABILITIES = 0x345 66 | 67 | IA32_PMC0 = 0xC1 68 | PERFEVTSE0 = 0x186 69 | PERFEVTSE1 = 0x186 + 1 70 | PERFEVTSE2 = 0x186 + 2 71 | PERFEVTSE3 = 0x186 + 3 72 | 73 | PEBS_RECORD_COUNTER_VALUE = 0xffffffffff920000 74 | 75 | # Mask for IA32_MISC_ENABLE 76 | # YES this is really BTS_UNAVILABLE in the intel man :D 77 | BTS_UNAVILABLE = 1 << 11 78 | PEBS_UNAVILABLE = 1 << 12 79 | UMON_AVAILABLE = 1 << 7 80 | 81 | 82 | class DsManagementAreaStruct(ctypes.Structure): 83 | """The DS management area for 64bits processors""" 84 | _fields_ = [("BtsBufferBase", ULONG64), 85 | ("BtsIndex", ULONG64), 86 | ("BtsAbsoluteMaximum", ULONG64), 87 | ("BTSThresholdinterupt", ULONG64), 88 | ("PEBSBufferBase", ULONG64), 89 | ("PEBSIndex", ULONG64), 90 | ("PEBSAbsoluteMaximum", ULONG64), 91 | ("PEBSThresholdinterupt", ULONG64), 92 | ("PEBSCounterResetValue", ULONG64), # should be 40bits field 93 | ("PEBSCounterResetValue2", ULONG64), # hack to set it to 0 94 | ("Stuff1", ULONG64), 95 | ("Stuff2", ULONG64), 96 | ] 97 | 98 | 99 | class BtsRecord(ctypes.Structure): 100 | _fields_ = [("BranchFrom", ULONG64), 101 | ("BranchTo", ULONG64), 102 | ("Stuff", ULONG64) 103 | ] 104 | 105 | 106 | # 18-58 in http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-3b-part-2-manual.html 107 | # Thanks to https://github.com/andikleen/pmu-tools/blob/master/pebs-grabber/pebs-grabber.c 108 | class PEBSRecordV0(ctypes.Structure): 109 | _fields_ = [("rflags", ULONG64), 110 | ("rip", ULONG64), 111 | ("rax", ULONG64), 112 | ("rbx", ULONG64), 113 | ("rcx", ULONG64), 114 | ("rdx", ULONG64), 115 | ("rsi", ULONG64), 116 | ("rdi", ULONG64), 117 | ("rbp", ULONG64), 118 | ("rsp", ULONG64), 119 | ("r8", ULONG64), 120 | ("r9", ULONG64), 121 | ("r10", ULONG64), 122 | ("r11", ULONG64), 123 | ("r12", ULONG64), 124 | ("r13", ULONG64), 125 | ("r14", ULONG64), 126 | ("r15", ULONG64) 127 | ] 128 | 129 | 130 | class PEBSRecordV1(PEBSRecordV0): 131 | _fields_ = [("IA32_PERF_FLOBAL_STATUS", ULONG64), 132 | ("DataLinearAddress", ULONG64), 133 | ("DaaSourceEncoding", ULONG64), 134 | ("LatencyValue", ULONG64), 135 | ] 136 | 137 | 138 | class PEBSRecordV2(PEBSRecordV1): 139 | _fields_ = [("EventingIP", ULONG64), 140 | ("TXAbortInformation", ULONG64), 141 | ] 142 | 143 | 144 | def check_feature(kdbg): 145 | """Check that BTS and PEBS features are available""" 146 | misc_enabled = kdbg.read_msr(IA32_MISC_ENABLE) 147 | if misc_enabled & BTS_UNAVILABLE: 148 | print("NO BTS") 149 | if misc_enabled & PEBS_UNAVILABLE: 150 | print("NO PEBS") 151 | if not misc_enabled & UMON_AVAILABLE: 152 | print("UMON NOT AVAILABLE") 153 | 154 | # ================================= BTS 155 | class BTSManager(object): 156 | def __init__(self, kdbg): 157 | self.kdbg = kdbg 158 | 159 | def setup_DsManagementArea(self, proc_nb): 160 | """Setup the setup_DsManagementArea for the proc `proc_nb` 161 | proc_nb must be the number of the current processor 162 | 163 | The write to nt!VfBTSDataManagementArea is where ntokrnl store this information""" 164 | ds_addr, ds_content = self.get_DsManagementArea(proc_nb) 165 | if ds_addr: 166 | return 167 | ds_management_area_addr = self.kdbg.alloc_memory(0x1000) 168 | kdbg.write_virtual_memory(ds_management_area_addr, "\x00" * 0x1000) 169 | VfBTSDataManagementArea = kdbg.get_symbol_offset("nt!VfBTSDataManagementArea") 170 | kdbg.write_ptr(VfBTSDataManagementArea + proc_nb * ctypes.sizeof(PVOID), ds_management_area_addr) 171 | kdbg.write_msr(DEBUG_STORE_MSR_VALUE, ds_management_area_addr) 172 | 173 | def get_DsManagementArea(self, proc_nb): 174 | """Return the DsManagementAreaStruct and address for the `proc_nb` processor""" 175 | VfBTSDataManagementArea = kdbg.get_symbol_offset("nt!VfBTSDataManagementArea") 176 | DataManagementAreaProcX = kdbg.read_ptr(VfBTSDataManagementArea + proc_nb * ctypes.sizeof(PVOID)) 177 | if DataManagementAreaProcX == 0: 178 | return 0, None 179 | DsManagementAreaContent = DsManagementAreaStruct() 180 | self.kdbg.read_virtual_memory_into(DataManagementAreaProcX, DsManagementAreaContent) 181 | return (DataManagementAreaProcX, DsManagementAreaContent) 182 | 183 | def setup_BTS(self, proc_nb, buffer_size=0x1000): 184 | """Setup the DsManagementArea BTS fields for proc `proc_nb`""" 185 | ds_addr, ds_content = self.get_DsManagementArea(proc_nb) 186 | buffer_addr = kdbg.alloc_memory(buffer_size) 187 | ds_content.BtsBufferBase = buffer_addr 188 | ds_content.BtsIndex = buffer_addr 189 | ds_content.BtsAbsoluteMaximum = buffer_addr + buffer_size + 1 190 | ds_content.BtsThresholdinterupt = 0 # or buffer_addr + buffer_size + 1 to trigger it 191 | self.kdbg.write_virtual_memory(ds_addr, ds_content) 192 | 193 | def stop_BTS(self): 194 | """Stop BTS on current processor""" 195 | kdbg.write_msr(IA32_DEBUGCTL_MSR_VALUE, 0x0) 196 | 197 | def start_BTS(self, enable, off_user=0, off_os=0): 198 | """Start the BTS (see 17.4.1 IA32_DEBUGCTL MSR) as circular buffer""" 199 | value = enable << 6 | enable << 7 | off_user << 10 | off_os << 9 200 | kdbg.write_msr(IA32_DEBUGCTL_MSR_VALUE, value) 201 | 202 | def get_number_bts_records(self, proc_nb): 203 | """Get the number of BTS entries stored in the buffer for proc `proc_nb`""" 204 | ds_addr, ds_content = self.get_DsManagementArea(proc_nb) 205 | return (ds_content.BtsIndex - ds_content.BtsBufferBase) / ctypes.sizeof(BtsRecord) 206 | 207 | def get_bts_records(self, proc_nb, max_dump=0xffffffffffffffff): 208 | """Get the BTS entries stored in the buffer for proc `proc_nb`""" 209 | ds_addr, ds_content = self.get_DsManagementArea(proc_nb) 210 | nb_bts_entry = self.get_number_bts_records(proc_nb) 211 | print("Buffer contains {0} entries".format(nb_bts_entry)) 212 | nb_bts_entry = min(nb_bts_entry, max_dump) 213 | print("Dumping {0} first entries".format(nb_bts_entry)) 214 | bts_entries_buffer = (BtsRecord * nb_bts_entry)() 215 | kdbg.read_virtual_memory_into(ds_content.BtsBufferBase, bts_entries_buffer) 216 | return bts_entries_buffer 217 | 218 | def dump_bts(self): 219 | ds_addr, ds_content = self.get_DsManagementArea(0) 220 | print("BtsBufferBase = {0}".format(hex(ds_content.BtsBufferBase))) 221 | records = self.get_bts_records(0, max_dump=20) 222 | for rec in records: 223 | from_sym, from_disp = kdbg.get_symbol(rec.BranchFrom) 224 | from_disp = hex(from_disp) if from_disp is not None else None 225 | from_str = "Jump {0} ({1} + {2})".format(hex(rec.BranchFrom), from_sym, from_disp) 226 | 227 | to_sym, to_disp = kdbg.get_symbol(rec.BranchTo) 228 | to_disp = hex(to_disp) if to_disp is not None else None 229 | to_str = "Jump {0} ({1} + {2})".format(hex(rec.BranchTo), to_sym, to_disp) 230 | 231 | print("{0} -> {1}".format(from_str, to_str)) 232 | 233 | 234 | # ================================= PEBS 235 | class PEBSManager(object): 236 | def __init__(self, kdbg): 237 | self.kdbg = kdbg 238 | pebs_record_v = self.get_pebs_records_version() 239 | if pebs_record_v == 0: 240 | self.PEBSRecord = PEBSRecordV0 241 | elif pebs_record_v == 1: 242 | self.PEBSRecord = PEBSRecordV1 243 | elif pebs_record_v == 2: 244 | self.PEBSRecord = PEBSRecordV2 245 | else: 246 | raise ValueError("Don't know the format of PEBS Records of version {0}".format(pebs_record_v)) 247 | 248 | def setup_perfevtsel0(self, enable, mask=0, eventselect=0xc0, user_mod=1, os_mod=1): 249 | """Setup the PERFEVTSE0 MSR to manage the IA32_PMC0 perf counter 250 | Default eventselect 0xc0 is 'Instruction retired'""" 251 | # Instruction retired 252 | MASK = mask << 8 253 | EVENTSELECT = eventselect << 0 254 | USER_MOD = user_mod << 16 255 | OS_MOD = os_mod << 17 256 | ENABLE = enable << 22 257 | value = MASK | EVENTSELECT | USER_MOD | OS_MOD | ENABLE 258 | self.kdbg.write_msr(PERFEVTSE0, value) 259 | 260 | def get_pebs_records_version(self): 261 | return (self.kdbg.read_msr(PERF_CAPABILITIES) >> 8) & 0xf 262 | 263 | def setup_DsManagementArea(self, proc_nb): 264 | """Setup the setup_DsManagementArea for the proc `proc_nb` 265 | proc_nb must be the number of the current processor 266 | 267 | The write to nt!VfBTSDataManagementArea is where ntokrnl store this information""" 268 | ds_addr, ds_content = self.get_DsManagementArea(proc_nb) 269 | if ds_addr: 270 | return 271 | ds_management_area_addr = self.kdbg.alloc_memory(0x1000) 272 | self.kdbg.write_virtual_memory(ds_management_area_addr, "\x00" * 0x1000) 273 | VfBTSDataManagementArea = kdbg.get_symbol_offset("nt!VfBTSDataManagementArea") 274 | kdbg.write_ptr(VfBTSDataManagementArea + proc_nb * ctypes.sizeof(PVOID), ds_management_area_addr) 275 | kdbg.write_msr(DEBUG_STORE_MSR_VALUE, ds_management_area_addr) 276 | 277 | def get_DsManagementArea(self, proc_nb): 278 | """Return the DsManagementAreaStruct and address for the `proc_nb` processor""" 279 | VfBTSDataManagementArea = kdbg.get_symbol_offset("nt!VfBTSDataManagementArea") 280 | DataManagementAreaProcX = kdbg.read_ptr(VfBTSDataManagementArea + proc_nb * ctypes.sizeof(PVOID)) 281 | if DataManagementAreaProcX == 0: 282 | return 0, None 283 | DsManagementAreaContent = DsManagementAreaStruct() 284 | self.kdbg.read_virtual_memory_into(DataManagementAreaProcX, DsManagementAreaContent) 285 | return (DataManagementAreaProcX, DsManagementAreaContent) 286 | 287 | def setup_pebs(self, proc_nb, buffer_size=0x1000): 288 | """Setup de DsManagementArea PEBS fields for proc `proc_nb`""" 289 | ds_addr, ds_content = self.get_DsManagementArea(proc_nb) 290 | buffer_addr = kdbg.alloc_memory(buffer_size) 291 | ds_content.PEBSBufferBase = buffer_addr 292 | ds_content.PEBSIndex = buffer_addr 293 | ds_content.PEBSAbsoluteMaximum = buffer_addr + buffer_size + 1 294 | ds_content.PEBSThresholdinterupt = 0 # or buffer_addr + buffer_size + 1 to trigger it 295 | ds_content.PEBSThresholdinterupt = 0 296 | ds_content.PEBSCounterResetValue = PEBS_RECORD_COUNTER_VALUE 297 | ds_content.PEBSCounterResetValue2 = 0xffffffffffffffff 298 | self.kdbg.write_virtual_memory(ds_addr, ds_content) 299 | 300 | def stop_PEBS(self): 301 | # Does the second line is enough ? 302 | self.kdbg.write_msr(PERFEVTSE0, 0) 303 | self.kdbg.write_msr(MSR_PERF_GLOBAL_CTRL, 0) 304 | self.kdbg.write_msr(MSR_PEBS_ENABLED, 0) 305 | 306 | def start_PEBS(self): 307 | """Start the PEBS by: 308 | Setup the event counter associated with PEBS (perfevtsel0) 309 | Enable PEBS 310 | Enable the counter perfevtsel0 311 | """ 312 | # Stop counter IA32_PMC0 (needed to write it) 313 | self.stop_PEBS() 314 | # 0xc0 -> Instruction retired 315 | self.setup_perfevtsel0(enable=1, mask=0, eventselect=0xc0, user_mod=1, os_mod=1) 316 | # TODO: change counter value (sign extended) 317 | kdbg.write_msr(IA32_PMC0, 0xfffa0000) 318 | # Activate PEBS 319 | kdbg.write_msr(MSR_PEBS_ENABLED, 1) 320 | # Re-activate IA32_PMC0 321 | kdbg.write_msr(MSR_PERF_GLOBAL_CTRL, 1) 322 | 323 | # PEBS records getters 324 | def get_number_pebs_records(self, proc_nb): 325 | """Get the number of PEBS entries stored in the buffer for proc `proc_nb`""" 326 | ds_addr, ds_content = self.get_DsManagementArea(proc_nb) 327 | return (ds_content.PEBSIndex - ds_content.PEBSBufferBase) / ctypes.sizeof(self.PEBSRecord) 328 | 329 | def get_pebs_records(self, proc_nb, max_dump=0xffffffffffffffff): 330 | """Get the PEBS entries stored in the buffer for proc `proc_nb`""" 331 | ds_addr, ds_content = self.get_DsManagementArea(proc_nb) 332 | nb_pebs_entry = self.get_number_pebs_records(proc_nb) 333 | print("Buffer contains {0} entries".format(nb_pebs_entry)) 334 | nb_pebs_entry = min(nb_pebs_entry, max_dump) 335 | print("Dumping {0} first entries".format(nb_pebs_entry)) 336 | pebs_entries_buffer = (self.PEBSRecord * nb_pebs_entry)() 337 | kdbg.read_virtual_memory_into(ds_content.PEBSBufferBase, pebs_entries_buffer) 338 | return pebs_entries_buffer 339 | 340 | def dump_PEBS_records(self): 341 | ds_addr, ds_content = self.get_DsManagementArea(proc_nb) 342 | print("PEBSBufferBase = {0}".format(hex(ds_content.PEBSBufferBase))) 343 | x = self.get_pebs_records(0) 344 | for pebs_record in x: 345 | print(" {0} = {1}".format("rip", hex(pebs_record.rip))) 346 | 347 | 348 | 349 | # BTS 350 | kdbg = LocalKernelDebugger() 351 | check_feature(kdbg) 352 | kdbg.reload() 353 | kdbg.set_current_processor(0) 354 | btsm = BTSManager(kdbg) 355 | btsm.setup_DsManagementArea(0) 356 | btsm.setup_BTS(0, buffer_size=0x100000) 357 | btsm.start_BTS(enable=1) 358 | import time 359 | time.sleep(1) 360 | btsm.stop_BTS() 361 | btsm.dump_bts() 362 | 363 | 364 | # # PEBS 365 | # kdbg = LocalKernelDebugger() 366 | # check_feature(kdbg) 367 | # kdbg.set_current_processor(0) 368 | # pebsm = PEBSManager(kdbg) 369 | # pebsm.setup_DsManagementArea(0) 370 | # pebsm.setup_pebs(0, buffer_size=0x1000) 371 | # pebsm.start_PEBS() 372 | # pebsm.dump_PEBS_records() 373 | # import time 374 | # time.sleep(1) 375 | # pebsm.dump_PEBS_records() 376 | # pebsm.stop_PEBS() 377 | -------------------------------------------------------------------------------- /example/hook_ntcreatefile.py: -------------------------------------------------------------------------------- 1 | """A demonstration of LKD that display all files opened by hooking nt!NtCreateFile""" 2 | import sys 3 | import time 4 | import ctypes 5 | import os 6 | 7 | if os.getcwd().endswith("example"): 8 | sys.path.append(os.path.realpath("..")) 9 | else: 10 | sys.path.append(os.path.realpath(".")) 11 | 12 | import windows 13 | import windows.native_exec.simple_x86 as x86 14 | import windows.native_exec.simple_x64 as x64 15 | 16 | from dbginterface import LocalKernelDebugger 17 | 18 | kdbg = LocalKernelDebugger() 19 | 20 | if windows.system.bitness != 32: 21 | raise ValueError("Test for kernel32 only") 22 | 23 | def hook_ntcreatefile(kdbg, ignore_jump_space_check=False): 24 | """Hook NtCreateFile, the hook write the filename to a shared memory page""" 25 | nt_create_file = kdbg.resolve_symbol("nt!NtCreateFile") 26 | if not ignore_jump_space_check: 27 | # Check that function begin with mov edi, edi for the hook short jump 28 | if kdbg.read_word(nt_create_file) != 0xff8b: # mov edi, edi 29 | print(hex(kdbg.read_word(nt_create_file))) 30 | raise ValueError("Cannot hook fonction that doest not begin with (/f to force if hook already in place)") 31 | # Check there is 5 bytes free before for the hook long jump 32 | if kdbg.read_virtual_memory(nt_create_file - 5, 5) not in ["\x90" * 5, "\xCC" * 5]: #NOP * 5 ; INT 3 * 5 33 | print(kdbg.read_virtual_memory(nt_create_file - 5, 5)) 34 | raise ValueError("Cannot hook fonction that is not prefixed with 5 nop/int 3") 35 | 36 | # Allocate memory for the shared buffer kernel<->user 37 | # the format is: 38 | # [addr] -> size of size already taken 39 | # then: 40 | # DWORD string_size 41 | # char[string_size] filename 42 | data_kernel_addr = kdbg.alloc_memory(0x1000) 43 | kdbg.write_pfv_memory(data_kernel_addr, "\x00" * 0x1000) 44 | # Map the shared buffer to userland 45 | data_user_addr = kdbg.map_page_to_userland(data_kernel_addr, 0x1000) 46 | # Allocate memory for the hook 47 | shellcode_addr = kdbg.alloc_memory(0x1000) 48 | # shellcode 49 | shellcode = x86.MultipleInstr() 50 | # Save register 51 | shellcode += x86.Push('EAX') 52 | shellcode += x86.Push('ECX') 53 | shellcode += x86.Push('EDI') 54 | shellcode += x86.Push('ESI') 55 | # Check that there is space remaining, else don't write it 56 | shellcode += x86.Cmp(x86.deref(data_kernel_addr), 0x900) 57 | shellcode += x86.Jnb(":END") 58 | # Get 3rd arg (POBJECT_ATTRIBUTES ObjectAttributes) 59 | shellcode += x86.Mov('EAX', x86.mem('[ESP + 0x1c]')) # 0xc + 0x10 for push 60 | # Get POBJECT_ATTRIBUTES.ObjectName (PUNICODE_STRING) 61 | shellcode += x86.Mov('EAX', x86.mem('[EAX + 0x8]')) 62 | shellcode += x86.Xor('ECX', 'ECX') 63 | # Get PUNICODE_STRING.Length 64 | shellcode += x86.Mov('CX', x86.mem('[EAX + 0]')) 65 | # Get PUNICODE_STRING.Buffer 66 | shellcode += x86.Mov('ESI', x86.mem('[EAX + 4]')) 67 | # Get the next free bytes in shared buffer 68 | shellcode += x86.Mov('EDI', data_kernel_addr + 4) 69 | shellcode += x86.Add('EDI', x86.deref(data_kernel_addr)) 70 | # Write (DWORD string_size) in our 'struct' 71 | shellcode += x86.Mov(x86.mem('[EDI]'), 'ECX') 72 | # update size taken in shared buffer 73 | shellcode += x86.Add(x86.deref(data_kernel_addr), 'ECX') 74 | shellcode += x86.Add(x86.deref(data_kernel_addr), 4) 75 | # Write (char[string_size] filename) in our 'struct' 76 | shellcode += x86.Add('EDI', 4) 77 | shellcode += x86.Rep + x86.Movsb() 78 | shellcode += x86.Label(":END") 79 | # Restore buffer 80 | shellcode += x86.Pop('ESI') 81 | shellcode += x86.Pop('EDI') 82 | shellcode += x86.Pop('ECX') 83 | shellcode += x86.Pop('EAX') 84 | # Jump to NtCreateFile 85 | shellcode += x86.JmpAt(nt_create_file + 2) 86 | 87 | # Write shellcode 88 | kdbg.write_pfv_memory(shellcode_addr, shellcode.get_code()) 89 | long_jump = x86.Jmp(shellcode_addr - (nt_create_file - 5)) 90 | # Write longjump to shellcode 91 | kdbg.write_pfv_memory(nt_create_file - 5, long_jump.get_code()) 92 | # Write shortjump NtCreateFile -> longjump 93 | short_jmp = x86.Jmp(-5) 94 | kdbg.write_pfv_memory(nt_create_file, short_jmp.get_code()) 95 | # Return address of shared buffer in userland 96 | return data_user_addr 97 | 98 | class FilenameReader(object): 99 | def __init__(self, data_addr): 100 | self.data_addr = data_addr 101 | self.current_data = data_addr + 4 102 | 103 | def get_current_filenames(self): 104 | res = [] 105 | while True: 106 | t = self.get_one_filename() 107 | if t is None: 108 | break 109 | res.append(t) 110 | return res 111 | 112 | def get_one_filename(self): 113 | # Read string size 114 | size = ctypes.c_uint.from_address(self.current_data).value 115 | if size == 0: 116 | return None 117 | # Read the string 118 | filename = (ctypes.c_char * size).from_address(self.current_data + 4)[:] 119 | try: 120 | filename = filename.decode('utf16') 121 | except Exception as e: 122 | import pdb;pdb.set_trace() 123 | #ignore decode error 124 | pass 125 | self.current_data += (size + 4) 126 | return filename 127 | 128 | def reset_buffer(self): 129 | ctypes.memmove(self.data_addr, "\x00" * 0x1000, 0x1000) 130 | self.current_data = data_addr + 4 131 | 132 | 133 | try: 134 | bypass = sys.argv[1] == "/f" 135 | except: 136 | bypass = False 137 | 138 | data_addr = hook_ntcreatefile(kdbg, bypass) 139 | fr = FilenameReader(data_addr) 140 | 141 | while True: 142 | time.sleep(0.1) 143 | x = fr.get_current_filenames() 144 | fr.reset_buffer() 145 | for f in x: 146 | try: 147 | print(f) 148 | except BaseException as e: 149 | # bypass encode error 150 | print(repr(f)) 151 | -------------------------------------------------------------------------------- /example/ida_demos_commands.md: -------------------------------------------------------------------------------- 1 | List of commands used in IDA for our hack.lu demo: 2 | Notes: 3 | 4 | - IDA must be launched as administrator 5 | - Only works on 32bits kernel as `idaq64.exe` is not a 64 bits process 6 | 7 | Add the path of LKD in list of import directory (you can also install LKD with the `setup.py`) 8 | 9 | import sys 10 | sys.path.append(r"") 11 | 12 | Create a new LocalKernelDebugger 13 | 14 | import dbginterface 15 | kdbg = dbginterface.LocalKernelDebugger() 16 | 17 | Config LKD to print all its output 18 | 19 | kdbg.quiet = False 20 | 21 | Read some virtual memory at symbol `nt!KiSystemStartup` 22 | 23 | kdbg.read_virtual_memory("nt!KiSystemStartup", 9).encode('hex') 24 | 25 | List all modules with name beginning by `nt` 26 | 27 | kdbg.execute("lm m nt*") 28 | 29 | Get the address of the KPCR for the processor 0 30 | 31 | DEBUG_DATA_KPCR_OFFSET = 0 32 | PROC_NUMBER = 0 33 | kpcr_addr = kdbg.read_processor_system_data(PROC_NUMBER, DEBUG_DATA_KPCR_OFFSET) 34 | 35 | Print the IDT and GDB fields of the KPCR struct 36 | (For scriptable type manipulation see `example/type_demo.py`) 37 | 38 | kdbg.execute("dt nt!_KPCR IDT GDT 0n{0}".format(kpcr_addr)) 39 | 40 | 41 | -------------------------------------------------------------------------------- /example/idt.py: -------------------------------------------------------------------------------- 1 | """A simple demonstration of LKD to display IDT and KINTERRUPT associated""" 2 | import sys 3 | import ctypes 4 | import os 5 | 6 | if os.getcwd().endswith("example"): 7 | sys.path.append(os.path.realpath("..")) 8 | else: 9 | sys.path.append(os.path.realpath(".")) 10 | 11 | import windows 12 | from windows.generated_def.winstructs import * 13 | from dbginterface import LocalKernelDebugger 14 | 15 | 16 | # lkd> dt nt!_KIDTENTRY 17 | # +0x000 Offset : Uint2B 18 | # +0x002 Selector : Uint2B 19 | # +0x004 Access : Uint2B 20 | # +0x006 ExtendedOffset : Uint2B 21 | # Struct _IDT32 definitions 22 | class _IDT32(ctypes.Structure): 23 | _fields_ = [ 24 | ("Offset", WORD), 25 | ("Selector", WORD), 26 | ("Access", WORD), 27 | ("ExtendedOffset", WORD) 28 | ] 29 | PIDT32 = POINTER(_IDT32) 30 | IDT32 = _IDT32 31 | 32 | 33 | # lkd> dt nt!_KIDTENTRY64 34 | # +0x000 OffsetLow : Uint2B 35 | # +0x002 Selector : Uint2B 36 | # +0x004 IstIndex : Pos 0, 3 Bits 37 | # +0x004 Reserved0 : Pos 3, 5 Bits 38 | # +0x004 Type : Pos 8, 5 Bits 39 | # +0x004 Dpl : Pos 13, 2 Bits 40 | # +0x004 Present : Pos 15, 1 Bit 41 | # +0x006 OffsetMiddle : Uint2B 42 | # +0x008 OffsetHigh : Uint4B 43 | # +0x00c Reserved1 : Uint4B 44 | # +0x000 Alignment : Uint8B 45 | # Struct _IDT64 definitions 46 | class _IDT64(ctypes.Structure): 47 | _fields_ = [ 48 | ("OffsetLow", WORD), 49 | ("Selector", WORD), 50 | ("IstIndex", WORD), 51 | ("OffsetMiddle", WORD), 52 | ("OffsetHigh", DWORD), 53 | ("Reserved1", DWORD) 54 | ] 55 | PIDT64 = POINTER(_IDT64) 56 | IDT64 = _IDT64 57 | 58 | 59 | # lkd> dt nt!_KINTERRUPT Type DispatchCode 60 | # +0x000 Type : Int2B 61 | # +0x090 DispatchCode : [4] Uint4B 62 | def get_kinterrupt_64(kdbg, addr_entry): 63 | # You can get the type ID of any name from module to which the type belongs 64 | # IDebugSymbols::GetTypeId 65 | # https://msdn.microsoft.com/en-us/library/windows/hardware/ff549376%28v=vs.85%29.aspx 66 | KINTERRUPT = kdbg.get_type_id("nt", "_KINTERRUPT") 67 | # You can get the offset of a symbol identified by its name 68 | # IDebugSymbols::GetOffsetByName 69 | # https://msdn.microsoft.com/en-us/library/windows/hardware/ff548035(v=vs.85).aspx 70 | dispatch_code_offset = kdbg.get_field_offset("nt", KINTERRUPT, "DispatchCode") 71 | type_offset = kdbg.get_field_offset("nt", KINTERRUPT, "Type") 72 | 73 | addr_kinterrupt = addr_entry - dispatch_code_offset 74 | # Read a byte from virtual memory 75 | # IDebugDataSpaces::ReadVirtual 76 | # https://msdn.microsoft.com/en-us/library/windows/hardware/ff554359(v=vs.85).aspx 77 | type = kdbg.read_byte(addr_kinterrupt + type_offset) 78 | if type == 0x16: 79 | return addr_kinterrupt 80 | else: 81 | return None 82 | 83 | 84 | # lkd> dt nt!_KPCR IdtBase 85 | # +0x038 IdtBase : Ptr64 _KIDTENTRY64 86 | # ... 87 | # lkd> dt nt!_UNEXPECTED_INTERRUPT 88 | # +0x000 PushImm : UChar 89 | # +0x001 Vector : UChar 90 | # +0x002 PushRbp : UChar 91 | # +0x003 JmpOp : UChar 92 | # +0x004 JmpOffset : Int4B 93 | def get_idt_64(kdbg, num_proc=0): 94 | l_idt = [] 95 | DEBUG_DATA_KPCR_OFFSET = 0 96 | 97 | KPCR = kdbg.get_type_id("nt", "_KPCR") 98 | # You can get the number of bytes of memory an instance of the specified type requires 99 | # IDebugSymbols::GetTypeSize 100 | # https://msdn.microsoft.com/en-us/library/windows/hardware/ff549457(v=vs.85).aspx 101 | size_unexpected_interrupt = kdbg.get_type_size("nt", "_UNEXPECTED_INTERRUPT") 102 | idt_base_offset = kdbg.get_field_offset("nt", KPCR, "IdtBase") 103 | # You can get the location (VA) of a symbol identified by its name 104 | # IDebugSymbols::GetOffsetByName 105 | # https://msdn.microsoft.com/en-us/library/windows/hardware/ff548035(v=vs.85).aspx 106 | addr_nt_KxUnexpectedInterrupt0 = kdbg.get_symbol_offset("nt!KxUnexpectedInterrupt0") 107 | 108 | # You can data by their type about the specified processor 109 | # IDebugDataSpaces::ReadProcessorSystemData 110 | # https://msdn.microsoft.com/en-us/library/windows/hardware/ff554326(v=vs.85).aspx 111 | kpcr_addr = kdbg.read_processor_system_data(num_proc, DEBUG_DATA_KPCR_OFFSET) 112 | 113 | # You can read a pointer-size value, it doesn't depend of the target computer's architecture processor 114 | idt_base = kdbg.read_ptr(kpcr_addr + idt_base_offset) 115 | for i in xrange(0, 0xFF): 116 | idt64 = IDT64() 117 | # You can read data from virtual address into a ctype structure 118 | kdbg.read_virtual_memory_into(idt_base + i * sizeof(IDT64), idt64) 119 | addr = (idt64.OffsetHigh << 32) | (idt64.OffsetMiddle << 16) | idt64.OffsetLow 120 | if addr < addr_nt_KxUnexpectedInterrupt0 or addr > (addr_nt_KxUnexpectedInterrupt0 + 0xFF * size_unexpected_interrupt): 121 | l_idt.append((addr, get_kinterrupt_64(kdbg, addr))) 122 | else: 123 | l_idt.append((None, None)) 124 | return l_idt 125 | 126 | 127 | # lkd> dt nt!_KPCR PrcbData.VectorToInterruptObject 128 | # +0x120 PrcbData : 129 | # +0x41a0 VectorToInterruptObject : [208] Ptr32 _KINTERRUPT 130 | # ... 131 | # lkd> dt nt!_KINTERRUPT Type 132 | # +0x000 Type : Int2B 133 | def get_kinterrupt_32(kdbg, kpcr_addr, index): 134 | KPCR = kdbg.get_type_id("nt", "_KPCR") 135 | KINTERRUPT = kdbg.get_type_id("nt", "_KINTERRUPT") 136 | pcrbdata_offset = kdbg.get_field_offset("nt", KPCR, "PrcbData.VectorToInterruptObject") 137 | type_offset = kdbg.get_field_offset("nt", KINTERRUPT, "Type") 138 | 139 | if index < 0x30: 140 | return None 141 | addr_kinterrupt = kdbg.read_ptr(kpcr_addr + pcrbdata_offset + (4 * index - 0xC0)) 142 | if addr_kinterrupt == 0: 143 | return None 144 | type = kdbg.read_byte(addr_kinterrupt + type_offset) 145 | if type == 0x16: 146 | return addr_kinterrupt 147 | return None 148 | 149 | 150 | # lkd> dt nt!_KPCR IDT 151 | # +0x038 IDT : Ptr32 _KIDTENTRY 152 | # +0x120 PrcbData : 153 | # +0x41a0 VectorToInterruptObject : [208] Ptr32 _KINTERRUPT 154 | def get_idt_32(kdbg, num_proc=0): 155 | l_idt = [] 156 | DEBUG_DATA_KPCR_OFFSET = 0 157 | 158 | KPCR = kdbg.get_type_id("nt", "_KPCR") 159 | idt_base_offset = kdbg.get_field_offset("nt", KPCR, "IDT") 160 | try: 161 | pcrbdata_offset = kdbg.get_field_offset("nt", KPCR, "PrcbData.VectorToInterruptObject") 162 | except WindowsError: 163 | pcrbdata_offset = 0 164 | addr_nt_KiStartUnexpectedRange = kdbg.get_symbol_offset("nt!KiStartUnexpectedRange") 165 | addr_nt_KiEndUnexpectedRange = kdbg.get_symbol_offset("nt!KiEndUnexpectedRange") 166 | if pcrbdata_offset == 0: 167 | get_kinterrupt = lambda kdbg, addr, kpcr, i: get_kinterrupt_64(kdbg, addr) 168 | else: 169 | get_kinterrupt = lambda kdbg, addr, kpcr, i: get_kinterrupt_32(kdbg, kpcr, i) 170 | 171 | kpcr_addr = kdbg.read_processor_system_data(num_proc, DEBUG_DATA_KPCR_OFFSET) 172 | idt_base = kdbg.read_ptr(kpcr_addr + idt_base_offset) 173 | for i in xrange(0, 0xFF): 174 | idt32 = IDT32() 175 | kdbg.read_virtual_memory_into(idt_base + i * sizeof(IDT32), idt32) 176 | if (idt32.ExtendedOffset == 0 or idt32.Offset == 0): 177 | l_idt.append((None, None)) 178 | continue 179 | addr = (idt32.ExtendedOffset << 16) | idt32.Offset 180 | if (addr < addr_nt_KiStartUnexpectedRange or addr > addr_nt_KiEndUnexpectedRange): 181 | l_idt.append((addr, get_kinterrupt(kdbg, addr, kpcr_addr, i))) 182 | else: 183 | addr_kinterrupt = get_kinterrupt(kdbg, addr, kpcr_addr, i) 184 | if addr_kinterrupt is None: 185 | addr = None 186 | l_idt.append((addr, addr_kinterrupt)) 187 | return l_idt 188 | 189 | 190 | if __name__ == '__main__': 191 | kdbg = LocalKernelDebugger() 192 | if windows.current_process.bitness == 32: 193 | l_idt = get_idt_32(kdbg) 194 | else: 195 | l_idt = get_idt_64(kdbg) 196 | for i in range(len(l_idt)): 197 | if l_idt[i][0] is not None: 198 | if l_idt[i][1] is not None: 199 | print("0x{0:02X} {1} {2} (KINTERRUPT {3})".format(i, hex(l_idt[i][0]), kdbg.get_symbol(l_idt[i][0])[0], hex(l_idt[i][1]))) 200 | else: 201 | print("0x{0:02X} {1} {2}".format(i, hex(l_idt[i][0]), kdbg.get_symbol(l_idt[i][0])[0])) 202 | -------------------------------------------------------------------------------- /example/output_demo.py: -------------------------------------------------------------------------------- 1 | """A simple demonstration of the output possibilities of the LDK""" 2 | import sys 3 | import os 4 | if os.getcwd().endswith("example"): 5 | sys.path.append(os.path.realpath("..")) 6 | else: 7 | sys.path.append(os.path.realpath(".")) 8 | 9 | from dbginterface import LocalKernelDebugger 10 | 11 | # A default LKD can be quiet or not 12 | # A quiet LKD will have no output 13 | # A noisy one will have the exact same output as windbg 14 | kdbg = LocalKernelDebugger(quiet=True) 15 | 16 | # With a quiet LKD this ligne will have no output 17 | print('Executing "lm m nt*" in quiet LKD') 18 | kdbg.execute("lm m nt*") 19 | 20 | # To change the quiet state of the LKD just set the variable 'quiet' 21 | kdbg.quiet = False 22 | print("") 23 | print('Executing "lm m nt*" in noisy LKD') 24 | kdbg.execute("lm m nt*") 25 | 26 | # If you want to parse the output of a command, kdbg.execute accept the argument 'to_string' 27 | # A command with to_string=True will have no output, even with quiet=False 28 | print("") 29 | disas = kdbg.execute("u nt!NtCreateFile", to_string=True) 30 | print('Here is the 3rd line of the command "u nt!NtCreateFile"') 31 | print(disas.split("\n")[2]) 32 | 33 | 34 | # You can also register a new output callabck that must respect the interface 35 | # IDebugOutputCallbacks::Output (https://msdn.microsoft.com/en-us/library/windows/hardware/ff550815%28v=vs.85%29.aspx) 36 | def my_output_callback(comobj, mask, text): 37 | print("NEW MESSAGE <{0}>".format(repr(text))) 38 | # mysocket.send(text) 39 | return 0 40 | 41 | kdbg.set_output_callbacks(my_output_callback) 42 | print("") 43 | print('Executing "u nt!NtCreateFile L1" with custom output callback') 44 | kdbg.execute("u nt!NtCreateFile L1") 45 | -------------------------------------------------------------------------------- /example/pci_vendor.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogeti-esec-lab/LKD/f388b5f8c08b7bba2a31c5a16ea64add6cc2dd1a/example/pci_vendor.py -------------------------------------------------------------------------------- /example/simple_pci_exploration.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import struct 3 | import os 4 | 5 | if os.getcwd().endswith("example"): 6 | sys.path.append(os.path.realpath("..")) 7 | else: 8 | sys.path.append(os.path.realpath(".")) 9 | 10 | import dbginterface 11 | import pci_vendor 12 | 13 | 14 | class PciExplorer(object): 15 | PCIConfiguration = 4 16 | 17 | def __init__(self, quiet=True): 18 | self.kdbg = dbginterface.LocalKernelDebugger(quiet) 19 | 20 | def read_pci(self, busnumber, device, function, offset, size): 21 | return self.kdbg.read_bus_data(self.PCIConfiguration, busnumber, device + (function << 5), offset, size) 22 | 23 | def read_pci_word(self, busnumber, device, function, offset): 24 | raw = self.read_pci(busnumber, device, function, offset, 2) 25 | return struct.unpack(" COM) 7 | IID_PACK = " Python) 28 | def BasicQueryInterface(self, *args): 29 | return 1 30 | 31 | 32 | def BasicAddRef(self, *args): 33 | return 1 34 | 35 | 36 | def BasicRelease(self, *args): 37 | return 0 38 | 39 | 40 | def create_c_callable(func, types, keepalive=[]): 41 | func_type = ctypes.WINFUNCTYPE(*types) 42 | c_callable = func_type(func) 43 | # Dirty, but other methods require native code execution 44 | c_callback_addr = ctypes.c_ulong.from_address(id(c_callable._objects['0']) + 3 * ctypes.sizeof(ctypes.c_void_p)).value 45 | keepalive.append(c_callable) 46 | return c_callback_addr 47 | 48 | 49 | class ComVtable(object): 50 | # Name, types, DefaultImplem 51 | _funcs_ = [("QueryInterface", [ctypes.HRESULT, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p], BasicQueryInterface), 52 | ("AddRef", [ctypes.HRESULT, ctypes.c_void_p], BasicAddRef), 53 | ("Release", [ctypes.HRESULT, ctypes.c_void_p], BasicRelease) 54 | ] 55 | 56 | def __init__(self): 57 | raise NotImplementedError("Nop: use create_vtable") 58 | 59 | @classmethod 60 | def create_vtable(cls, **implem_overwrite): 61 | vtables_names = [x[0] for x in cls._funcs_] 62 | non_expected_args = [func_name for func_name in implem_overwrite if func_name not in vtables_names] 63 | if non_expected_args: 64 | raise ValueError("Non expected function : {0}".format(non_expected_args)) 65 | 66 | implems = [] 67 | for name, types, func_implem in cls._funcs_: 68 | func_implem = implem_overwrite.get(name, func_implem) 69 | if func_implem is None: 70 | raise ValueError("Missing implementation for function <{0}>".format(name)) 71 | implems.append(create_c_callable(func_implem, types)) 72 | 73 | class Vtable(ctypes.Structure): 74 | _fields_ = [(name, ctypes.c_void_p) for name in vtables_names] 75 | 76 | return Vtable(*implems) 77 | 78 | 79 | class IDebugOutputCallbacksVtable(ComVtable): 80 | _funcs_ = ComVtable._funcs_ + [("Output", [ctypes.HRESULT, ctypes.c_void_p, ctypes.c_ulong, ctypes.c_char_p], None)] 81 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | from test_all import IDebugSymbolsTestCase, IDebugDataSpacesTestCase, PCITestCase, DriverUpgradeTestCase 2 | -------------------------------------------------------------------------------- /test/test_all.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append(".") 3 | import unittest 4 | import os 5 | import struct 6 | 7 | import windows.utils 8 | import windows.pe_parse 9 | import windows.native_exec.cpuid as cpuid 10 | 11 | from windows import winproxy 12 | import ctypes 13 | from ctypes import byref, create_string_buffer, WINFUNCTYPE, HRESULT, WinError, sizeof 14 | import dbginterface 15 | from dbginterface import LocalKernelDebugger 16 | from windows.generated_def.winstructs import * 17 | 18 | # Bitness attribute to skip test 19 | 20 | is_32_bits = windows.current_process.bitness == 32 21 | is_64_bits = windows.current_process.bitness == 64 22 | 23 | test_32bit_only = unittest.skipIf(not is_32_bits, "Test for 32bits Kernel only") 24 | test_64bit_only = unittest.skipIf(not is_64_bits, "Test for 64bits Kernel only") 25 | 26 | def get_kl 27 | 28 | class RequireSymbol(object): 29 | def __init__(self, *args): 30 | self.required_symbols = args 31 | 32 | def __call__(self, f): 33 | self.f = f 34 | # non-class associated function to get the wrapped 'self' (type TestCase) and call 35 | # a RequireSymbol method to also have our self (type RequireSymbol) 36 | def mywrapper(testcase): 37 | return self.do_call(testcase) 38 | return mywrapper 39 | 40 | def do_call(self, testcase): 41 | for sym in self.required_symbols: 42 | if testcase.kdbg.get_symbol_offset(sym) is None: 43 | raise testcase.skipTest("Cannot get symbol <{0}>".format(sym)) 44 | return self.f(testcase) 45 | 46 | 47 | 48 | class IDebugSymbolsTestCase(unittest.TestCase): 49 | 50 | def setUp(self): 51 | pass 52 | 53 | @classmethod 54 | def setUpClass(self): 55 | windows.winproxy.SetThreadAffinityMask(dwThreadAffinityMask=(1 << 0)) 56 | self.kdbg = LocalKernelDebugger() 57 | modules = windows.utils.get_kernel_modules() 58 | self.modules = modules 59 | self.ntkernelbase = modules[0].Base 60 | self.kernelpath = modules[0].ImageName[:] 61 | self.kernelpath = os.path.expandvars(self.kernelpath.replace("\SystemRoot", "%SystemRoot%")) 62 | self.kernelmod = winproxy.LoadLibraryA(self.kernelpath) 63 | pe = windows.pe_parse.PEFile(self.kernelmod) 64 | self.NtCreateFileVA = pe.exports['NtCreateFile'] - self.kernelmod + self.ntkernelbase 65 | 66 | def tearDown(self): 67 | #self.kdbg.detach() 68 | self.kdbg = None 69 | 70 | def test_get_symbol_offset(self): 71 | # IDebugSymbols::GetOffsetByName 72 | x = self.kdbg.get_symbol_offset("nt") 73 | self.assertEqual(x, self.ntkernelbase) 74 | 75 | @RequireSymbol("ntdll!NtCreateFile") 76 | def test_get_symbol_offset_user(self): 77 | # IDebugSymbols::GetOffsetByName 78 | x = windows.utils.get_func_addr("ntdll", "NtCreateFile") 79 | y = self.kdbg.get_symbol_offset("ntdll!NtCreateFile") 80 | self.assertEqual(x, y) 81 | 82 | @RequireSymbol("nt!NtCreateFile") 83 | def test_get_symbol(self): 84 | # IDebugSymbols::GetNameByOffset 85 | x = self.kdbg.get_symbol(self.NtCreateFileVA) 86 | self.assertEqual(x[0], 'nt!NtCreateFile') 87 | self.assertEqual(x[1], 0x00) 88 | 89 | @RequireSymbol("ntdll!NtCreateFile") 90 | def test_get_symbol_user(self): 91 | # IDebugSymbols::GetNameByOffset 92 | x = windows.utils.get_func_addr("ntdll", "NtCreateFile") 93 | y = self.kdbg.get_symbol(x) 94 | self.assertIn(y[0], ["ntdll!NtCreateFile", "ntdll!ZwCreateFile"]) 95 | 96 | def test_get_number_modules(self): 97 | # IDebugSymbols::GetNumberModules 98 | loaded, unloaded = self.kdbg.get_number_modules() 99 | 100 | def test_get_module_by_index(self): 101 | # IDebugSymbols::GetModuleByIndex 102 | for i in range(self.kdbg.get_number_modules()[0]): 103 | x = self.kdbg.get_module_by_index(i) 104 | if x == self.ntkernelbase: 105 | return 106 | raise AssertionError("ntoskrnl not found") 107 | 108 | def test_get_module_name_by_index(self): 109 | # IDebugSymbols::GetModuleNames 110 | for i in range(self.kdbg.get_number_modules()[0]): 111 | x = self.kdbg.get_module_name_by_index(i) 112 | if x[1] == "nt": 113 | return 114 | raise AssertionError("ntoskrnl not found") 115 | 116 | def test_symbol_match(self): 117 | # IDebugSymbols::StartSymbolMatch | IDebugSymbols::GetNextSymbolMatch | IDebugSymbols::EndSymbolMatch 118 | x = list(self.kdbg.symbol_match("nt!NtCreateF*")) 119 | self.assertEqual(x[0][0], 'nt!NtCreateFile') 120 | self.assertEqual(x[0][1], self.NtCreateFileVA) 121 | 122 | class IDebugDataSpacesTestCase(unittest.TestCase): 123 | 124 | def setUp(self): 125 | pass 126 | 127 | @classmethod 128 | def setUpClass(self): 129 | windows.winproxy.SetThreadAffinityMask(dwThreadAffinityMask=(1 << 0)) 130 | self.kdbg = LocalKernelDebugger() 131 | modules = windows.utils.get_kernel_modules() 132 | self.ntkernelbase = modules[0].Base 133 | self.kernelpath = modules[0].ImageName[:] 134 | self.kernelpath = os.path.expandvars(self.kernelpath.replace("\SystemRoot", "%SystemRoot%")) 135 | self.kernelbuf = open(self.kernelpath, "rb").read() 136 | self.kernelmod = winproxy.LoadLibraryA(self.kernelpath) 137 | pe = windows.pe_parse.PEFile(self.kernelmod) 138 | self.kernel_section_data = [section for section in pe.sections if section.name == ".data"][0] 139 | 140 | def tearDown(self): 141 | #self.kdbg.detach() 142 | self.kdbg = None 143 | 144 | def test_read_byte(self): 145 | # IDebugDataSpaces::ReadVirtual 146 | x = self.kdbg.read_byte(self.kdbg.get_symbol_offset("nt")) 147 | self.assertEqual(x, ord(self.kernelbuf[0])) 148 | 149 | def test_read_word(self): 150 | # IDebugDataSpaces::ReadVirtual 151 | x = self.kdbg.read_word(self.kdbg.get_symbol_offset("nt")) 152 | self.assertEqual(x, struct.unpack("> ((offset & 2) * 8)) & 0xffff) 305 | 306 | 307 | class PCITestCase(unittest.TestCase): 308 | 309 | def setUp(self): 310 | pass 311 | 312 | @classmethod 313 | def setUpClass(self): 314 | self.pciexplorer = PciExplorer() 315 | 316 | def test_iter_pci_device(self): 317 | bus = 0x00 318 | f = 0 319 | for device in range(32): 320 | pci_vendor = self.pciexplorer.read_pci_word(bus, device, f, 0) 321 | pci_device = self.pciexplorer.read_pci_word(bus, device, f, 2) 322 | 323 | manual_pci_vendor = self.pciexplorer.manual_read_pci_word(bus, device, f, 0) 324 | manual_pci_device = self.pciexplorer.manual_read_pci_word(bus, device, f, 2) 325 | self.assertEqual(pci_vendor, manual_pci_vendor) 326 | self.assertEqual(pci_device, manual_pci_device) 327 | 328 | def tearDown(self): 329 | pass 330 | 331 | class DriverUpgradeTestCase(unittest.TestCase): 332 | @classmethod 333 | def setUpClass(self): 334 | self.kdbg = LocalKernelDebugger() 335 | 336 | def test_alloc_memory(self): 337 | addr = self.kdbg.alloc_memory(0x1000) 338 | self.kdbg.write_byte(addr, 0x42) 339 | self.assertEqual(self.kdbg.read_byte(addr), 0x42) 340 | 341 | self.kdbg.write_byte(addr + 0xfff, 0x42) 342 | self.assertEqual(self.kdbg.read_byte(addr + 0xfff), 0x42) 343 | 344 | def test_full_driver_upgrade(self): 345 | upgrader = self.kdbg.upgrader 346 | upgrader.registered_ioctl = [] 347 | upgrader.full_driver_upgrade() 348 | self.test_alloc_memory() 349 | 350 | def test_retrieve_driver_upgrade(self): 351 | # Get current registered IO 352 | registered_io = self.kdbg.upgrader.registered_ioctl 353 | # Verif that some IO are registered 354 | self.assertTrue(registered_io) 355 | new_upgrader = type(self.kdbg.upgrader)(self.kdbg) 356 | # Verif that new upgrader see that driver is upgraded 357 | self.assertTrue(new_upgrader.is_driver_already_upgraded()) 358 | # Verif IOCTL retrieving 359 | new_upgrader.retrieve_upgraded_info() 360 | self.assertItemsEqual(registered_io, new_upgrader.registered_ioctl) 361 | 362 | def test_map_page_to_userland(self): 363 | kpage = self.kdbg.alloc_memory(0x1000) 364 | userpage = self.kdbg.map_page_to_userland(kpage, 0x1000) 365 | 366 | self.kdbg.write_dword(kpage, 0x11223344) 367 | self.assertEqual(ctypes.c_uint.from_address(userpage).value, 0x11223344) 368 | 369 | ctypes.c_uint.from_address(userpage + 4).value = 0x12345678 370 | self.assertEqual(self.kdbg.read_dword(kpage + 4), 0x12345678) 371 | 372 | if __name__ == '__main__': 373 | alltests = unittest.TestSuite() 374 | alltests.addTest(unittest.makeSuite(IDebugSymbolsTestCase)) 375 | alltests.addTest(unittest.makeSuite(IDebugDataSpacesTestCase)) 376 | alltests.addTest(unittest.makeSuite(PCITestCase)) 377 | alltests.addTest(unittest.makeSuite(DriverUpgradeTestCase)) 378 | unittest.TextTestRunner(verbosity=2).run(alltests) -------------------------------------------------------------------------------- /windows/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Windows for Python 3 | A lot of python object to help navigate windows stuff 4 | 5 | Exported: 6 | 7 | system : :class:`windows.winobject.System` 8 | 9 | current_process : :class:`windows.winobject.CurrentProcess` 10 | 11 | current_thread : :class:`windows.winobject.CurrentThread` 12 | """ 13 | 14 | from . import winproxy 15 | from .utils import VirtualProtected 16 | from .winobject import System, CurrentProcess, CurrentThread 17 | 18 | system = System() 19 | current_process = CurrentProcess() 20 | current_thread = CurrentThread() 21 | 22 | # Late import: other imports should go here 23 | # Do not move it: risk of circular import 24 | 25 | __all__ = ["system", "VirtualProtected", 'current_process', 'current_thread', 'winproxy'] 26 | -------------------------------------------------------------------------------- /windows/dbgprint.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import logging 4 | import inspect 5 | 6 | options = {'active': False, 'cats': None} 7 | 8 | 9 | def get_stack_func_name(lvl): 10 | info = inspect.stack()[lvl] 11 | return info[0], info[3] 12 | 13 | 14 | def do_dbgprint(msg, type=None): 15 | if (options['cats'] is None) or type.upper() in options['cats']: 16 | frame, func = get_stack_func_name(2) 17 | logger = logging.getLogger(frame.f_globals['__name__'] + ":" + func) 18 | logger.debug(msg) 19 | 20 | 21 | def do_nothing(*args, **kwargs): 22 | return None 23 | 24 | 25 | def parse_option(s): 26 | if s[0] == "=": 27 | s = s[1:] 28 | if s: 29 | cats = [x.upper() for x in s.split('-')] 30 | options['cats'] = cats 31 | 32 | formt = 'DBG|%(name)s|%(message)s' 33 | logging.basicConfig(format=formt, level=logging.DEBUG) 34 | 35 | try: 36 | if 'DBGPRINT' in os.environ: 37 | parse_option(os.environ['DBGPRINT']) 38 | dbgprint = do_dbgprint 39 | elif any([opt.startswith("--DBGPRINT") for opt in sys.argv]): 40 | dbgprint = do_dbgprint 41 | option_str = [opt for opt in sys.argv if opt.startswith("--DBGPRINT")][0] 42 | parse_option(option_str[len('--DBGPRINT'):]) 43 | else: 44 | dbgprint = do_nothing 45 | except Exception as e: 46 | dbgprint = do_nothing 47 | print("dbgprint Error: {0}({1})".format(type(e), e)) 48 | x = type(e), e 49 | -------------------------------------------------------------------------------- /windows/generated_def/__init__.py: -------------------------------------------------------------------------------- 1 | import windows 2 | from . import winstructs 3 | 4 | def bitness(): 5 | """Return 32 or 64""" 6 | import platform 7 | bits = platform.architecture()[0] 8 | return int(bits[:2]) 9 | 10 | # Use windows.current_process.bitness ? need to fix problem of this imported before the creation of windows.current_process 11 | if bitness() == 32: 12 | winstructs.CONTEXT = winstructs.CONTEXT32 13 | winstructs.PCONTEXT = winstructs.PCONTEXT32 14 | winstructs.LPCONTEXT = winstructs.LPCONTEXT32 15 | 16 | winstructs.EXCEPTION_POINTERS = winstructs.EXCEPTION_POINTERS32 17 | winstructs.PEXCEPTION_POINTERS = winstructs.PEXCEPTION_POINTERS32 18 | 19 | winstructs.SYSTEM_MODULE = winstructs.SYSTEM_MODULE32 20 | winstructs.SYSTEM_MODULE_INFORMATION = winstructs.SYSTEM_MODULE_INFORMATION32 21 | else: 22 | winstructs.CONTEXT = winstructs.CONTEXT64 23 | winstructs.PCONTEXT = winstructs.PCONTEXT64 24 | winstructs.LPCONTEXT = winstructs.LPCONTEXT64 25 | 26 | winstructs.EXCEPTION_POINTERS = winstructs.EXCEPTION_POINTERS64 27 | winstructs.PEXCEPTION_POINTERS = winstructs.PEXCEPTION_POINTERS64 28 | 29 | winstructs.SYSTEM_MODULE = winstructs.SYSTEM_MODULE64 30 | winstructs.SYSTEM_MODULE_INFORMATION = winstructs.SYSTEM_MODULE_INFORMATION64 31 | 32 | from . import winfuncs 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /windows/generated_def/winfuncs.py: -------------------------------------------------------------------------------- 1 | #Generated file 2 | from ctypes import * 3 | from ctypes.wintypes import * 4 | from .winstructs import * 5 | 6 | functions = ['ExitProcess', 'TerminateProcess', 'GetLastError', 'GetCurrentProcess', 'CreateFileA', 'CreateFileW', 'NtQuerySystemInformation', 'VirtualAlloc', 'VirtualAllocEx', 'VirtualFree', 'VirtualFreeEx', 'VirtualProtect', 'VirtualQuery', 'GetModuleFileNameA', 'GetModuleFileNameW', 'CreateThread', 'CreateRemoteThread', 'VirtualProtect', 'CreateProcessA', 'CreateProcessW', 'GetThreadContext', 'SetThreadContext', 'OpenThread', 'OpenProcess', 'CloseHandle', 'ReadProcessMemory', 'NtWow64ReadVirtualMemory64', 'WriteProcessMemory', 'CreateToolhelp32Snapshot', 'Thread32First', 'Thread32Next', 'Process32First', 'Process32Next', 'Process32FirstW', 'Process32NextW', 'GetProcAddress', 'LoadLibraryA', 'LoadLibraryW', 'OpenProcessToken', 'LookupPrivilegeValueA', 'LookupPrivilegeValueW', 'AdjustTokenPrivileges', 'FindResourceA', 'FindResourceW', 'SizeofResource', 'LoadResource', 'LockResource', 'GetVersionExA', 'GetVersionExW', 'GetVersion', 'GetCurrentThread', 'GetCurrentThreadId', 'GetCurrentProcessorNumber', 'AllocConsole', 'FreeConsole', 'GetStdHandle', 'SetStdHandle', 'SetThreadAffinityMask', 'WriteFile', 'GetExtendedTcpTable', 'GetExtendedUdpTable', 'SetTcpEntry', 'AddVectoredContinueHandler', 'AddVectoredExceptionHandler', 'TerminateThread', 'ExitThread', 'RemoveVectoredExceptionHandler', 'ResumeThread', 'SuspendThread', 'WaitForSingleObject', 'GetThreadId', 'LoadLibraryExA', 'LoadLibraryExW', 'SymInitialize', 'SymFromName', 'SymLoadModuleEx', 'SymSetOptions', 'SymGetTypeInfo', 'DeviceIoControl', 'GetTokenInformation', 'RegOpenKeyExA', 'RegOpenKeyExW', 'RegGetValueA', 'RegGetValueW', 'RegCloseKey', 'Wow64DisableWow64FsRedirection', 'Wow64RevertWow64FsRedirection', 'Wow64EnableWow64FsRedirection'] 7 | 8 | # ExitProcess(uExitCode): 9 | ExitProcessPrototype = WINFUNCTYPE(VOID, UINT) 10 | ExitProcessParams = ((1, 'uExitCode'),) 11 | 12 | # TerminateProcess(hProcess, uExitCode): 13 | TerminateProcessPrototype = WINFUNCTYPE(BOOL, HANDLE, UINT) 14 | TerminateProcessParams = ((1, 'hProcess'), (1, 'uExitCode')) 15 | 16 | # GetLastError(): 17 | GetLastErrorPrototype = WINFUNCTYPE(DWORD) 18 | GetLastErrorParams = () 19 | 20 | # GetCurrentProcess(): 21 | GetCurrentProcessPrototype = WINFUNCTYPE(HANDLE) 22 | GetCurrentProcessParams = () 23 | 24 | # CreateFileA(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile): 25 | CreateFileAPrototype = WINFUNCTYPE(HANDLE, LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE) 26 | CreateFileAParams = ((1, 'lpFileName'), (1, 'dwDesiredAccess'), (1, 'dwShareMode'), (1, 'lpSecurityAttributes'), (1, 'dwCreationDisposition'), (1, 'dwFlagsAndAttributes'), (1, 'hTemplateFile')) 27 | 28 | # CreateFileW(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile): 29 | CreateFileWPrototype = WINFUNCTYPE(HANDLE, LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE) 30 | CreateFileWParams = ((1, 'lpFileName'), (1, 'dwDesiredAccess'), (1, 'dwShareMode'), (1, 'lpSecurityAttributes'), (1, 'dwCreationDisposition'), (1, 'dwFlagsAndAttributes'), (1, 'hTemplateFile')) 31 | 32 | # NtQuerySystemInformation(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength): 33 | NtQuerySystemInformationPrototype = WINFUNCTYPE(NTSTATUS, SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG) 34 | NtQuerySystemInformationParams = ((1, 'SystemInformationClass'), (1, 'SystemInformation'), (1, 'SystemInformationLength'), (1, 'ReturnLength')) 35 | 36 | # VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect): 37 | VirtualAllocPrototype = WINFUNCTYPE(LPVOID, LPVOID, SIZE_T, DWORD, DWORD) 38 | VirtualAllocParams = ((1, 'lpAddress'), (1, 'dwSize'), (1, 'flAllocationType'), (1, 'flProtect')) 39 | 40 | # VirtualAllocEx(hProcess, lpAddress, dwSize, flAllocationType, flProtect): 41 | VirtualAllocExPrototype = WINFUNCTYPE(LPVOID, HANDLE, LPVOID, SIZE_T, DWORD, DWORD) 42 | VirtualAllocExParams = ((1, 'hProcess'), (1, 'lpAddress'), (1, 'dwSize'), (1, 'flAllocationType'), (1, 'flProtect')) 43 | 44 | # VirtualFree(lpAddress, dwSize, dwFreeType): 45 | VirtualFreePrototype = WINFUNCTYPE(BOOL, LPVOID, SIZE_T, DWORD) 46 | VirtualFreeParams = ((1, 'lpAddress'), (1, 'dwSize'), (1, 'dwFreeType')) 47 | 48 | # VirtualFreeEx(hProcess, lpAddress, dwSize, dwFreeType): 49 | VirtualFreeExPrototype = WINFUNCTYPE(BOOL, HANDLE, LPVOID, SIZE_T, DWORD) 50 | VirtualFreeExParams = ((1, 'hProcess'), (1, 'lpAddress'), (1, 'dwSize'), (1, 'dwFreeType')) 51 | 52 | # VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect): 53 | VirtualProtectPrototype = WINFUNCTYPE(BOOL, LPVOID, SIZE_T, DWORD, PDWORD) 54 | VirtualProtectParams = ((1, 'lpAddress'), (1, 'dwSize'), (1, 'flNewProtect'), (1, 'lpflOldProtect')) 55 | 56 | # VirtualQuery(lpAddress, lpBuffer, dwLength): 57 | VirtualQueryPrototype = WINFUNCTYPE(DWORD, LPCVOID, PMEMORY_BASIC_INFORMATION, DWORD) 58 | VirtualQueryParams = ((1, 'lpAddress'), (1, 'lpBuffer'), (1, 'dwLength')) 59 | 60 | # GetModuleFileNameA(hModule, lpFilename, nSize): 61 | GetModuleFileNameAPrototype = WINFUNCTYPE(DWORD, HMODULE, LPSTR, DWORD) 62 | GetModuleFileNameAParams = ((1, 'hModule'), (1, 'lpFilename'), (1, 'nSize')) 63 | 64 | # GetModuleFileNameW(hModule, lpFilename, nSize): 65 | GetModuleFileNameWPrototype = WINFUNCTYPE(DWORD, HMODULE, LPWSTR, DWORD) 66 | GetModuleFileNameWParams = ((1, 'hModule'), (1, 'lpFilename'), (1, 'nSize')) 67 | 68 | # CreateThread(lpThreadAttributes, dwStackSize, lpStartAddress, lpParameter, dwCreationFlags, lpThreadId): 69 | CreateThreadPrototype = WINFUNCTYPE(HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD) 70 | CreateThreadParams = ((1, 'lpThreadAttributes'), (1, 'dwStackSize'), (1, 'lpStartAddress'), (1, 'lpParameter'), (1, 'dwCreationFlags'), (1, 'lpThreadId')) 71 | 72 | # CreateRemoteThread(hProcess, lpThreadAttributes, dwStackSize, lpStartAddress, lpParameter, dwCreationFlags, lpThreadId): 73 | CreateRemoteThreadPrototype = WINFUNCTYPE(HANDLE, HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD) 74 | CreateRemoteThreadParams = ((1, 'hProcess'), (1, 'lpThreadAttributes'), (1, 'dwStackSize'), (1, 'lpStartAddress'), (1, 'lpParameter'), (1, 'dwCreationFlags'), (1, 'lpThreadId')) 75 | 76 | # VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect): 77 | VirtualProtectPrototype = WINFUNCTYPE(BOOL, LPVOID, SIZE_T, DWORD, PDWORD) 78 | VirtualProtectParams = ((1, 'lpAddress'), (1, 'dwSize'), (1, 'flNewProtect'), (1, 'lpflOldProtect')) 79 | 80 | # CreateProcessA(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation): 81 | CreateProcessAPrototype = WINFUNCTYPE(BOOL, LPCSTR, LPSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCSTR, LPSTARTUPINFOA, LPPROCESS_INFORMATION) 82 | CreateProcessAParams = ((1, 'lpApplicationName'), (1, 'lpCommandLine'), (1, 'lpProcessAttributes'), (1, 'lpThreadAttributes'), (1, 'bInheritHandles'), (1, 'dwCreationFlags'), (1, 'lpEnvironment'), (1, 'lpCurrentDirectory'), (1, 'lpStartupInfo'), (1, 'lpProcessInformation')) 83 | 84 | # CreateProcessW(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation): 85 | CreateProcessWPrototype = WINFUNCTYPE(BOOL, LPCWSTR, LPWSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION) 86 | CreateProcessWParams = ((1, 'lpApplicationName'), (1, 'lpCommandLine'), (1, 'lpProcessAttributes'), (1, 'lpThreadAttributes'), (1, 'bInheritHandles'), (1, 'dwCreationFlags'), (1, 'lpEnvironment'), (1, 'lpCurrentDirectory'), (1, 'lpStartupInfo'), (1, 'lpProcessInformation')) 87 | 88 | # GetThreadContext(hThread, lpContext): 89 | GetThreadContextPrototype = WINFUNCTYPE(BOOL, HANDLE, LPCONTEXT) 90 | GetThreadContextParams = ((1, 'hThread'), (1, 'lpContext')) 91 | 92 | # SetThreadContext(hThread, lpContext): 93 | SetThreadContextPrototype = WINFUNCTYPE(BOOL, HANDLE, LPCONTEXT) 94 | SetThreadContextParams = ((1, 'hThread'), (1, 'lpContext')) 95 | 96 | # OpenThread(dwDesiredAccess, bInheritHandle, dwThreadId): 97 | OpenThreadPrototype = WINFUNCTYPE(HANDLE, DWORD, BOOL, DWORD) 98 | OpenThreadParams = ((1, 'dwDesiredAccess'), (1, 'bInheritHandle'), (1, 'dwThreadId')) 99 | 100 | # OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId): 101 | OpenProcessPrototype = WINFUNCTYPE(HANDLE, DWORD, BOOL, DWORD) 102 | OpenProcessParams = ((1, 'dwDesiredAccess'), (1, 'bInheritHandle'), (1, 'dwProcessId')) 103 | 104 | # CloseHandle(hObject): 105 | CloseHandlePrototype = WINFUNCTYPE(BOOL, HANDLE) 106 | CloseHandleParams = ((1, 'hObject'),) 107 | 108 | # ReadProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesRead): 109 | ReadProcessMemoryPrototype = WINFUNCTYPE(BOOL, HANDLE, LPCVOID, LPVOID, SIZE_T, POINTER(SIZE_T)) 110 | ReadProcessMemoryParams = ((1, 'hProcess'), (1, 'lpBaseAddress'), (1, 'lpBuffer'), (1, 'nSize'), (1, 'lpNumberOfBytesRead')) 111 | 112 | # NtWow64ReadVirtualMemory64(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesRead): 113 | NtWow64ReadVirtualMemory64Prototype = WINFUNCTYPE(BOOL, HANDLE, ULONG64, LPVOID, ULONG64, POINTER(PULONG64)) 114 | NtWow64ReadVirtualMemory64Params = ((1, 'hProcess'), (1, 'lpBaseAddress'), (1, 'lpBuffer'), (1, 'nSize'), (1, 'lpNumberOfBytesRead')) 115 | 116 | # WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesWritten): 117 | WriteProcessMemoryPrototype = WINFUNCTYPE(BOOL, HANDLE, LPVOID, LPCVOID, SIZE_T, POINTER(SIZE_T)) 118 | WriteProcessMemoryParams = ((1, 'hProcess'), (1, 'lpBaseAddress'), (1, 'lpBuffer'), (1, 'nSize'), (1, 'lpNumberOfBytesWritten')) 119 | 120 | # CreateToolhelp32Snapshot(dwFlags, th32ProcessID): 121 | CreateToolhelp32SnapshotPrototype = WINFUNCTYPE(HANDLE, DWORD, DWORD) 122 | CreateToolhelp32SnapshotParams = ((1, 'dwFlags'), (1, 'th32ProcessID')) 123 | 124 | # Thread32First(hSnapshot, lpte): 125 | Thread32FirstPrototype = WINFUNCTYPE(BOOL, HANDLE, LPTHREADENTRY32) 126 | Thread32FirstParams = ((1, 'hSnapshot'), (1, 'lpte')) 127 | 128 | # Thread32Next(hSnapshot, lpte): 129 | Thread32NextPrototype = WINFUNCTYPE(BOOL, HANDLE, LPTHREADENTRY32) 130 | Thread32NextParams = ((1, 'hSnapshot'), (1, 'lpte')) 131 | 132 | # Process32First(hSnapshot, lppe): 133 | Process32FirstPrototype = WINFUNCTYPE(BOOL, HANDLE, LPPROCESSENTRY32) 134 | Process32FirstParams = ((1, 'hSnapshot'), (1, 'lppe')) 135 | 136 | # Process32Next(hSnapshot, lppe): 137 | Process32NextPrototype = WINFUNCTYPE(BOOL, HANDLE, LPPROCESSENTRY32) 138 | Process32NextParams = ((1, 'hSnapshot'), (1, 'lppe')) 139 | 140 | # Process32FirstW(hSnapshot, lppe): 141 | Process32FirstWPrototype = WINFUNCTYPE(BOOL, HANDLE, LPPROCESSENTRY32W) 142 | Process32FirstWParams = ((1, 'hSnapshot'), (1, 'lppe')) 143 | 144 | # Process32NextW(hSnapshot, lppe): 145 | Process32NextWPrototype = WINFUNCTYPE(BOOL, HANDLE, LPPROCESSENTRY32W) 146 | Process32NextWParams = ((1, 'hSnapshot'), (1, 'lppe')) 147 | 148 | # GetProcAddress(hModule, lpProcName): 149 | GetProcAddressPrototype = WINFUNCTYPE(FARPROC, HMODULE, LPCSTR) 150 | GetProcAddressParams = ((1, 'hModule'), (1, 'lpProcName')) 151 | 152 | # LoadLibraryA(lpFileName): 153 | LoadLibraryAPrototype = WINFUNCTYPE(HMODULE, LPCSTR) 154 | LoadLibraryAParams = ((1, 'lpFileName'),) 155 | 156 | # LoadLibraryW(lpFileName): 157 | LoadLibraryWPrototype = WINFUNCTYPE(HMODULE, LPCWSTR) 158 | LoadLibraryWParams = ((1, 'lpFileName'),) 159 | 160 | # OpenProcessToken(ProcessHandle, DesiredAccess, TokenHandle): 161 | OpenProcessTokenPrototype = WINFUNCTYPE(BOOL, HANDLE, DWORD, PHANDLE) 162 | OpenProcessTokenParams = ((1, 'ProcessHandle'), (1, 'DesiredAccess'), (1, 'TokenHandle')) 163 | 164 | # LookupPrivilegeValueA(lpSystemName, lpName, lpLuid): 165 | LookupPrivilegeValueAPrototype = WINFUNCTYPE(BOOL, LPCSTR, LPCSTR, PLUID) 166 | LookupPrivilegeValueAParams = ((1, 'lpSystemName'), (1, 'lpName'), (1, 'lpLuid')) 167 | 168 | # LookupPrivilegeValueW(lpSystemName, lpName, lpLuid): 169 | LookupPrivilegeValueWPrototype = WINFUNCTYPE(BOOL, LPCWSTR, LPCWSTR, PLUID) 170 | LookupPrivilegeValueWParams = ((1, 'lpSystemName'), (1, 'lpName'), (1, 'lpLuid')) 171 | 172 | # AdjustTokenPrivileges(TokenHandle, DisableAllPrivileges, NewState, BufferLength, PreviousState, ReturnLength): 173 | AdjustTokenPrivilegesPrototype = WINFUNCTYPE(BOOL, HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD) 174 | AdjustTokenPrivilegesParams = ((1, 'TokenHandle'), (1, 'DisableAllPrivileges'), (1, 'NewState'), (1, 'BufferLength'), (1, 'PreviousState'), (1, 'ReturnLength')) 175 | 176 | # FindResourceA(hModule, lpName, lpType): 177 | FindResourceAPrototype = WINFUNCTYPE(HRSRC, HMODULE, LPCSTR, LPCSTR) 178 | FindResourceAParams = ((1, 'hModule'), (1, 'lpName'), (1, 'lpType')) 179 | 180 | # FindResourceW(hModule, lpName, lpType): 181 | FindResourceWPrototype = WINFUNCTYPE(HRSRC, HMODULE, LPCWSTR, LPCWSTR) 182 | FindResourceWParams = ((1, 'hModule'), (1, 'lpName'), (1, 'lpType')) 183 | 184 | # SizeofResource(hModule, hResInfo): 185 | SizeofResourcePrototype = WINFUNCTYPE(DWORD, HMODULE, HRSRC) 186 | SizeofResourceParams = ((1, 'hModule'), (1, 'hResInfo')) 187 | 188 | # LoadResource(hModule, hResInfo): 189 | LoadResourcePrototype = WINFUNCTYPE(HGLOBAL, HMODULE, HRSRC) 190 | LoadResourceParams = ((1, 'hModule'), (1, 'hResInfo')) 191 | 192 | # LockResource(hResData): 193 | LockResourcePrototype = WINFUNCTYPE(LPVOID, HGLOBAL) 194 | LockResourceParams = ((1, 'hResData'),) 195 | 196 | # GetVersionExA(lpVersionInformation): 197 | GetVersionExAPrototype = WINFUNCTYPE(BOOL, LPOSVERSIONINFOA) 198 | GetVersionExAParams = ((1, 'lpVersionInformation'),) 199 | 200 | # GetVersionExW(lpVersionInformation): 201 | GetVersionExWPrototype = WINFUNCTYPE(BOOL, LPOSVERSIONINFOW) 202 | GetVersionExWParams = ((1, 'lpVersionInformation'),) 203 | 204 | # GetVersion(): 205 | GetVersionPrototype = WINFUNCTYPE(DWORD) 206 | GetVersionParams = () 207 | 208 | # GetCurrentThread(): 209 | GetCurrentThreadPrototype = WINFUNCTYPE(HANDLE) 210 | GetCurrentThreadParams = () 211 | 212 | # GetCurrentThreadId(): 213 | GetCurrentThreadIdPrototype = WINFUNCTYPE(DWORD) 214 | GetCurrentThreadIdParams = () 215 | 216 | # GetCurrentProcessorNumber(): 217 | GetCurrentProcessorNumberPrototype = WINFUNCTYPE(DWORD) 218 | GetCurrentProcessorNumberParams = () 219 | 220 | # AllocConsole(): 221 | AllocConsolePrototype = WINFUNCTYPE(BOOL) 222 | AllocConsoleParams = () 223 | 224 | # FreeConsole(): 225 | FreeConsolePrototype = WINFUNCTYPE(BOOL) 226 | FreeConsoleParams = () 227 | 228 | # GetStdHandle(nStdHandle): 229 | GetStdHandlePrototype = WINFUNCTYPE(HANDLE, DWORD) 230 | GetStdHandleParams = ((1, 'nStdHandle'),) 231 | 232 | # SetStdHandle(nStdHandle, hHandle): 233 | SetStdHandlePrototype = WINFUNCTYPE(BOOL, DWORD, HANDLE) 234 | SetStdHandleParams = ((1, 'nStdHandle'), (1, 'hHandle')) 235 | 236 | # SetThreadAffinityMask(hThread, dwThreadAffinityMask): 237 | SetThreadAffinityMaskPrototype = WINFUNCTYPE(DWORD, HANDLE, DWORD) 238 | SetThreadAffinityMaskParams = ((1, 'hThread'), (1, 'dwThreadAffinityMask')) 239 | 240 | # WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped): 241 | WriteFilePrototype = WINFUNCTYPE(BOOL, HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED) 242 | WriteFileParams = ((1, 'hFile'), (1, 'lpBuffer'), (1, 'nNumberOfBytesToWrite'), (1, 'lpNumberOfBytesWritten'), (1, 'lpOverlapped')) 243 | 244 | # GetExtendedTcpTable(pTcpTable, pdwSize, bOrder, ulAf, TableClass, Reserved): 245 | GetExtendedTcpTablePrototype = WINFUNCTYPE(DWORD, PVOID, PDWORD, BOOL, ULONG, TCP_TABLE_CLASS, ULONG) 246 | GetExtendedTcpTableParams = ((1, 'pTcpTable'), (1, 'pdwSize'), (1, 'bOrder'), (1, 'ulAf'), (1, 'TableClass'), (1, 'Reserved')) 247 | 248 | # GetExtendedUdpTable(pUdpTable, pdwSize, bOrder, ulAf, TableClass, Reserved): 249 | GetExtendedUdpTablePrototype = WINFUNCTYPE(DWORD, PVOID, PDWORD, BOOL, ULONG, UDP_TABLE_CLASS, ULONG) 250 | GetExtendedUdpTableParams = ((1, 'pUdpTable'), (1, 'pdwSize'), (1, 'bOrder'), (1, 'ulAf'), (1, 'TableClass'), (1, 'Reserved')) 251 | 252 | # SetTcpEntry(pTcpRow): 253 | SetTcpEntryPrototype = WINFUNCTYPE(DWORD, PMIB_TCPROW) 254 | SetTcpEntryParams = ((1, 'pTcpRow'),) 255 | 256 | # AddVectoredContinueHandler(FirstHandler, VectoredHandler): 257 | AddVectoredContinueHandlerPrototype = WINFUNCTYPE(PVOID, ULONG, PVECTORED_EXCEPTION_HANDLER) 258 | AddVectoredContinueHandlerParams = ((1, 'FirstHandler'), (1, 'VectoredHandler')) 259 | 260 | # AddVectoredExceptionHandler(FirstHandler, VectoredHandler): 261 | AddVectoredExceptionHandlerPrototype = WINFUNCTYPE(PVOID, ULONG, PVECTORED_EXCEPTION_HANDLER) 262 | AddVectoredExceptionHandlerParams = ((1, 'FirstHandler'), (1, 'VectoredHandler')) 263 | 264 | # TerminateThread(hThread, dwExitCode): 265 | TerminateThreadPrototype = WINFUNCTYPE(BOOL, HANDLE, DWORD) 266 | TerminateThreadParams = ((1, 'hThread'), (1, 'dwExitCode')) 267 | 268 | # ExitThread(dwExitCode): 269 | ExitThreadPrototype = WINFUNCTYPE(VOID, DWORD) 270 | ExitThreadParams = ((1, 'dwExitCode'),) 271 | 272 | # RemoveVectoredExceptionHandler(Handler): 273 | RemoveVectoredExceptionHandlerPrototype = WINFUNCTYPE(ULONG, PVOID) 274 | RemoveVectoredExceptionHandlerParams = ((1, 'Handler'),) 275 | 276 | # ResumeThread(hThread): 277 | ResumeThreadPrototype = WINFUNCTYPE(DWORD, HANDLE) 278 | ResumeThreadParams = ((1, 'hThread'),) 279 | 280 | # SuspendThread(hThread): 281 | SuspendThreadPrototype = WINFUNCTYPE(DWORD, HANDLE) 282 | SuspendThreadParams = ((1, 'hThread'),) 283 | 284 | # WaitForSingleObject(hHandle, dwMilliseconds): 285 | WaitForSingleObjectPrototype = WINFUNCTYPE(DWORD, HANDLE, DWORD) 286 | WaitForSingleObjectParams = ((1, 'hHandle'), (1, 'dwMilliseconds')) 287 | 288 | # GetThreadId(Thread): 289 | GetThreadIdPrototype = WINFUNCTYPE(DWORD, HANDLE) 290 | GetThreadIdParams = ((1, 'Thread'),) 291 | 292 | # LoadLibraryExA(lpFileName, hFile, dwFlags): 293 | LoadLibraryExAPrototype = WINFUNCTYPE(HMODULE, LPCSTR, HANDLE, DWORD) 294 | LoadLibraryExAParams = ((1, 'lpFileName'), (1, 'hFile'), (1, 'dwFlags')) 295 | 296 | # LoadLibraryExW(lpFileName, hFile, dwFlags): 297 | LoadLibraryExWPrototype = WINFUNCTYPE(HMODULE, LPCWSTR, HANDLE, DWORD) 298 | LoadLibraryExWParams = ((1, 'lpFileName'), (1, 'hFile'), (1, 'dwFlags')) 299 | 300 | # SymInitialize(hProcess, UserSearchPath, fInvadeProcess): 301 | SymInitializePrototype = WINFUNCTYPE(BOOL, HANDLE, LPCSTR, BOOL) 302 | SymInitializeParams = ((1, 'hProcess'), (1, 'UserSearchPath'), (1, 'fInvadeProcess')) 303 | 304 | # SymFromName(hProcess, Name, Symbol): 305 | SymFromNamePrototype = WINFUNCTYPE(BOOL, HANDLE, LPCSTR, PSYMBOL_INFO) 306 | SymFromNameParams = ((1, 'hProcess'), (1, 'Name'), (1, 'Symbol')) 307 | 308 | # SymLoadModuleEx(hProcess, hFile, ImageName, ModuleName, BaseOfDll, DllSize, Data, Flags): 309 | SymLoadModuleExPrototype = WINFUNCTYPE(DWORD64, HANDLE, HANDLE, LPCSTR, LPCSTR, DWORD64, DWORD, PMODLOAD_DATA, DWORD) 310 | SymLoadModuleExParams = ((1, 'hProcess'), (1, 'hFile'), (1, 'ImageName'), (1, 'ModuleName'), (1, 'BaseOfDll'), (1, 'DllSize'), (1, 'Data'), (1, 'Flags')) 311 | 312 | # SymSetOptions(SymOptions): 313 | SymSetOptionsPrototype = WINFUNCTYPE(DWORD, DWORD) 314 | SymSetOptionsParams = ((1, 'SymOptions'),) 315 | 316 | # SymGetTypeInfo(hProcess, ModBase, TypeId, GetType, pInfo): 317 | SymGetTypeInfoPrototype = WINFUNCTYPE(BOOL, HANDLE, DWORD64, ULONG, IMAGEHLP_SYMBOL_TYPE_INFO, PVOID) 318 | SymGetTypeInfoParams = ((1, 'hProcess'), (1, 'ModBase'), (1, 'TypeId'), (1, 'GetType'), (1, 'pInfo')) 319 | 320 | # DeviceIoControl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize, lpBytesReturned, lpOverlapped): 321 | DeviceIoControlPrototype = WINFUNCTYPE(BOOL, HANDLE, DWORD, LPVOID, DWORD, LPVOID, DWORD, LPDWORD, LPOVERLAPPED) 322 | DeviceIoControlParams = ((1, 'hDevice'), (1, 'dwIoControlCode'), (1, 'lpInBuffer'), (1, 'nInBufferSize'), (1, 'lpOutBuffer'), (1, 'nOutBufferSize'), (1, 'lpBytesReturned'), (1, 'lpOverlapped')) 323 | 324 | # GetTokenInformation(TokenHandle, TokenInformationClass, TokenInformation, TokenInformationLength, ReturnLength): 325 | GetTokenInformationPrototype = WINFUNCTYPE(BOOL, HANDLE, TOKEN_INFORMATION_CLASS, LPVOID, DWORD, PDWORD) 326 | GetTokenInformationParams = ((1, 'TokenHandle'), (1, 'TokenInformationClass'), (1, 'TokenInformation'), (1, 'TokenInformationLength'), (1, 'ReturnLength')) 327 | 328 | # RegOpenKeyExA(hKey, lpSubKey, ulOptions, samDesired, phkResult): 329 | RegOpenKeyExAPrototype = WINFUNCTYPE(LONG, HKEY, LPCSTR, DWORD, REGSAM, PHKEY) 330 | RegOpenKeyExAParams = ((1, 'hKey'), (1, 'lpSubKey'), (1, 'ulOptions'), (1, 'samDesired'), (1, 'phkResult')) 331 | 332 | # RegOpenKeyExW(hKey, lpSubKey, ulOptions, samDesired, phkResult): 333 | RegOpenKeyExWPrototype = WINFUNCTYPE(LONG, HKEY, LPWSTR, DWORD, REGSAM, PHKEY) 334 | RegOpenKeyExWParams = ((1, 'hKey'), (1, 'lpSubKey'), (1, 'ulOptions'), (1, 'samDesired'), (1, 'phkResult')) 335 | 336 | # RegGetValueA(hkey, lpSubKey, lpValue, dwFlags, pdwType, pvData, pcbData): 337 | RegGetValueAPrototype = WINFUNCTYPE(LONG, HKEY, LPCSTR, LPCSTR, DWORD, LPDWORD, PVOID, LPDWORD) 338 | RegGetValueAParams = ((1, 'hkey'), (1, 'lpSubKey'), (1, 'lpValue'), (1, 'dwFlags'), (1, 'pdwType'), (1, 'pvData'), (1, 'pcbData')) 339 | 340 | # RegGetValueW(hkey, lpSubKey, lpValue, dwFlags, pdwType, pvData, pcbData): 341 | RegGetValueWPrototype = WINFUNCTYPE(LONG, HKEY, LPWSTR, LPWSTR, DWORD, LPDWORD, PVOID, LPDWORD) 342 | RegGetValueWParams = ((1, 'hkey'), (1, 'lpSubKey'), (1, 'lpValue'), (1, 'dwFlags'), (1, 'pdwType'), (1, 'pvData'), (1, 'pcbData')) 343 | 344 | # RegCloseKey(hKey): 345 | RegCloseKeyPrototype = WINFUNCTYPE(LONG, HKEY) 346 | RegCloseKeyParams = ((1, 'hKey'),) 347 | 348 | # Wow64DisableWow64FsRedirection(OldValue): 349 | Wow64DisableWow64FsRedirectionPrototype = WINFUNCTYPE(BOOL, POINTER(PVOID)) 350 | Wow64DisableWow64FsRedirectionParams = ((1, 'OldValue'),) 351 | 352 | # Wow64RevertWow64FsRedirection(OldValue): 353 | Wow64RevertWow64FsRedirectionPrototype = WINFUNCTYPE(BOOL, PVOID) 354 | Wow64RevertWow64FsRedirectionParams = ((1, 'OldValue'),) 355 | 356 | # Wow64EnableWow64FsRedirection(Wow64FsEnableRedirection): 357 | Wow64EnableWow64FsRedirectionPrototype = WINFUNCTYPE(BOOLEAN, BOOLEAN) 358 | Wow64EnableWow64FsRedirectionParams = ((1, 'Wow64FsEnableRedirection'),) 359 | 360 | -------------------------------------------------------------------------------- /windows/hooks.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import ctypes 3 | 4 | import windows.utils as utils 5 | from . import native_exec 6 | from .generated_def import winfuncs 7 | from .generated_def.windef import PAGE_EXECUTE_READWRITE 8 | from .generated_def.winstructs import * 9 | 10 | 11 | class Callback(object): 12 | def __init__(self, *types): 13 | self.types = types 14 | 15 | def __call__(self, func): 16 | func._types_info = self.types 17 | return func 18 | 19 | 20 | class KnownCallback(object): 21 | types = () 22 | 23 | def __call__(self, func): 24 | func._types_info = self.types 25 | return func 26 | 27 | 28 | def add_callback_to_module(callback): 29 | setattr(sys.modules[__name__], type(callback).__name__, callback) 30 | 31 | # Generate IATCallback decorator for all known functions 32 | for func in winfuncs.functions: 33 | prototype = getattr(winfuncs, func + "Prototype") 34 | callback_name = func + "Callback" 35 | 36 | class CallBackDeclaration(KnownCallback): 37 | types = (prototype._restype_,) + prototype._argtypes_ 38 | 39 | CallBackDeclaration.__name__ = callback_name 40 | add_callback_to_module(CallBackDeclaration()) 41 | 42 | 43 | class IATHook(object): 44 | """Look at my hook <3""" 45 | 46 | def __init__(self, IAT_entry, callback, types=None): 47 | if types is None: 48 | if not hasattr(callback, "_types_info"): 49 | raise ValueError("Callback for IATHook has no type infomations") 50 | types = callback._types_info 51 | self.original_types = types 52 | self.callback_types = self.transform_arguments(self.original_types) 53 | self.entry = IAT_entry 54 | self.callback = callback 55 | self.stub = native_exec.generate_callback_stub(self.hook_callback, self.callback_types) 56 | self.realfunction = ctypes.WINFUNCTYPE(*types)(IAT_entry.nonhookvalue) 57 | self.is_enable = False 58 | 59 | def transform_arguments(self, types): 60 | res = [] 61 | for type in types: 62 | if type in (ctypes.c_wchar_p, ctypes.c_char_p): 63 | res.append(ctypes.c_void_p) 64 | else: 65 | res.append(type) 66 | return res 67 | 68 | def enable(self): 69 | with utils.VirtualProtected(self.entry.addr, ctypes.sizeof(PVOID), PAGE_EXECUTE_READWRITE): 70 | self.entry.value = self.stub 71 | self.is_enable = True 72 | 73 | def disable(self): 74 | with utils.VirtualProtected(self.entry.addr, ctypes.sizeof(PVOID), PAGE_EXECUTE_READWRITE): 75 | self.entry.value = self.entry.nonhookvalue 76 | self.is_enable = False 77 | 78 | def hook_callback(self, *args): 79 | adapted_args = [] 80 | for value, type in zip(args, self.original_types[1:]): 81 | if type == ctypes.c_wchar_p: 82 | adapted_args.append(ctypes.c_wchar_p(value)) 83 | elif type == ctypes.c_char_p: 84 | adapted_args.append(ctypes.c_char_p((value))) 85 | else: 86 | adapted_args.append(value) 87 | 88 | def real_function(*args): 89 | if args == (): 90 | args = adapted_args 91 | return self.realfunction(*args) 92 | return self.callback(*adapted_args, real_function=real_function) 93 | -------------------------------------------------------------------------------- /windows/injection.py: -------------------------------------------------------------------------------- 1 | import windows 2 | import windows.utils as utils 3 | 4 | from .native_exec import simple_x86 as x86 5 | from .native_exec import simple_x64 as x64 6 | 7 | 8 | def get_loadlib_getproc(target): 9 | if windows.current_process.bitness == target.bitness: 10 | LoadLibraryA = utils.get_func_addr('kernel32', 'LoadLibraryA') 11 | GetProcAddress = utils.get_func_addr('kernel32', 'GetProcAddress') 12 | return LoadLibraryA, GetProcAddress 13 | else: 14 | k32 = [x for x in target.peb.modules if x.name == "kernel32.dll"][0] 15 | exp = k32.pe.exports 16 | return exp['LoadLibraryA'], exp['GetProcAddress'] 17 | 18 | 19 | # 32 to 32 injection 20 | def generate_python_exec_shellcode_32(target, PYDLL_addr, PyInit, PyRun, PYCODE_ADDR): 21 | LoadLibraryA, GetProcAddress = get_loadlib_getproc(target) 22 | code = x86.MultipleInstr() 23 | # Load python27.dll 24 | code += x86.Push(PYDLL_addr) 25 | code += x86.Mov('EAX', LoadLibraryA) 26 | code += x86.Call('EAX') 27 | # Get PyInit function into pythondll 28 | code += x86.Push('EAX') 29 | code += x86.Pop('EDI') 30 | code += x86.Push(PyInit) 31 | code += x86.Push('EDI') 32 | code += x86.Mov('EBX', GetProcAddress) 33 | code += x86.Call('EBX') 34 | # Call PyInit 35 | code += x86.Call('EAX') 36 | # Get PyRun function into pythondll 37 | code += x86.Push(PyRun) 38 | code += x86.Push('EDI') 39 | code += x86.Call('EBX') 40 | # Call PyRun with python code to exec 41 | code += x86.Push(PYCODE_ADDR) 42 | code += x86.Call('EAX') 43 | code += x86.Pop('EDI') 44 | code += x86.Ret() 45 | return code.get_code() 46 | 47 | 48 | # 64 to 64 injection 49 | def generate_python_exec_shellcode_64(target, PYDLL_addr, PyInit, PyRun, PYCODE_ADDR): 50 | 51 | LoadLibraryA, GetProcAddress = get_loadlib_getproc(target) 52 | 53 | Reserve_space_for_call = x64.MultipleInstr([x64.Push('RDI')] * 4) 54 | Clean_space_for_call = x64.MultipleInstr([x64.Pop('RDI')] * 4) 55 | 56 | code = x64.MultipleInstr() 57 | # Do stack alignement 58 | code += x64.Push('RAX') 59 | # Load python27.dll 60 | code += x64.Mov('RCX', PYDLL_addr) 61 | code += x64.Mov('RAX', LoadLibraryA) 62 | code += Reserve_space_for_call 63 | code += x64.Call('RAX') 64 | code += Clean_space_for_call 65 | code += x64.Push('RAX') 66 | code += x64.Pop('RCX') 67 | # Save RCX 68 | code += x64.Push('RCX') 69 | # Align stack 70 | code += x64.Push('RDI') 71 | # Get PyInit function into pythondll 72 | code += Reserve_space_for_call 73 | code += x64.Mov('RDX', PyInit) 74 | code += x64.Mov('RBX', GetProcAddress) 75 | code += x64.Call('RBX') 76 | # Call PyInit 77 | code += x64.Call('RAX') 78 | code += Clean_space_for_call 79 | # Remove Stack align 80 | code += x64.Pop('RDI') 81 | # Restore pythondll base into rcx 82 | code += x64.Pop('RCX') 83 | # Get PyRun function into pythondll 84 | code += x64.Mov('RDX', PyRun) 85 | code += Reserve_space_for_call 86 | code += x64.Call('RBX') 87 | # Call PyInit with python code to exec 88 | code += x64.Mov('RCX', PYCODE_ADDR) 89 | code += x64.Call('RAX') 90 | code += Clean_space_for_call 91 | # Remove stack alignement 92 | code += x64.Pop('RAX') 93 | code += x64.Ret() 94 | return code.get_code() 95 | 96 | 97 | def inject_python_command(process, code_injected, PYDLL="python27.dll\x00"): 98 | PyInitT = "Py_Initialize\x00" 99 | Pyrun = "PyRun_SimpleString\x00" 100 | PYCODE = code_injected + "\x00" 101 | remote_addr_base = process.virtual_alloc(len(code_injected) + 0x100) 102 | remote_addr = remote_addr_base 103 | 104 | PYDLL_addr = remote_addr 105 | process.write_memory(remote_addr, PYDLL) 106 | remote_addr += len(PYDLL) 107 | 108 | PyInitT_ADDR = remote_addr 109 | process.write_memory(remote_addr, PyInitT) 110 | remote_addr += len(PyInitT) 111 | 112 | Pyrun_ADDR = remote_addr 113 | process.write_memory(remote_addr, Pyrun) 114 | remote_addr += len(Pyrun) 115 | 116 | PYCODE_ADDR = remote_addr 117 | process.write_memory(remote_addr, PYCODE) 118 | remote_addr += len(PYCODE) 119 | 120 | SHELLCODE_ADDR = remote_addr 121 | if process.bitness == 32: 122 | shellcode_generator = generate_python_exec_shellcode_32 123 | else: 124 | shellcode_generator = generate_python_exec_shellcode_64 125 | shellcode = shellcode_generator(process, PYDLL_addr, PyInitT_ADDR, Pyrun_ADDR, PYCODE_ADDR) 126 | process.write_memory(SHELLCODE_ADDR, shellcode) 127 | return SHELLCODE_ADDR 128 | 129 | 130 | def execute_python_code(process, code): 131 | print("me = {0}".format(windows.current_process.bitness)) 132 | print("him = {0}".format(process.bitness)) 133 | if windows.current_process.bitness != process.bitness: 134 | if windows.current_process.bitness == 64 and process.bitness == 32: 135 | raise NotImplementedError("Cannot perform 64 -> 32 injection") 136 | shellcode_remote_addr = inject_python_command(process, code) 137 | return process.create_thread(shellcode_remote_addr, 0) 138 | -------------------------------------------------------------------------------- /windows/native_exec/__init__.py: -------------------------------------------------------------------------------- 1 | from .native_function import generate_callback_stub, create_function 2 | 3 | __all__ = ["generate_callback_stub", "create_function"] 4 | -------------------------------------------------------------------------------- /windows/native_exec/cpuid.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import struct 3 | 4 | import native_function 5 | import simple_x86 as x86 6 | import simple_x64 as x64 7 | from windows.generated_def.winstructs import * 8 | 9 | 10 | def bitness(): 11 | """Return 32 or 64""" 12 | import platform 13 | bits = platform.architecture()[0] 14 | return int(bits[:2]) 15 | 16 | 17 | class X86CpuidResult(ctypes.Structure): 18 | _fields_ = [("EAX", DWORD), 19 | ("EBX", DWORD), 20 | ("ECX", DWORD), 21 | ("EDX", DWORD)] 22 | 23 | 24 | class X64CpuidResult(ctypes.Structure): 25 | _fields_ = [("RAX", ULONG64), 26 | ("RBX", ULONG64), 27 | ("RCX", ULONG64), 28 | ("RDX", ULONG64)] 29 | 30 | 31 | class X86IntelCpuidFamilly(ctypes.Structure): 32 | _fields_ = [("SteppingID", DWORD, 4), 33 | ("ModelID", DWORD, 4), 34 | ("FamilyID", DWORD, 4), 35 | ("ProcessorType", DWORD, 2), 36 | ("Reserved2", DWORD, 2), 37 | ("ExtendedModel", DWORD, 4), 38 | ("ExtendedFamily", DWORD, 8), 39 | ("Reserved", DWORD, 2)] 40 | 41 | 42 | class X86AmdCpuidFamilly(ctypes.Structure): 43 | _fields_ = [("SteppingID", DWORD, 4), 44 | ("ModelID", DWORD, 4), 45 | ("FamilyID", DWORD, 4), 46 | ("Reserved2", DWORD, 4), 47 | ("ExtendedModel", DWORD, 4), 48 | ("ExtendedFamily", DWORD, 8), 49 | ("Reserved", DWORD, 2)] 50 | 51 | 52 | cpuid32_code = x86.MultipleInstr() 53 | cpuid32_code += x86.Push('EDI') 54 | cpuid32_code += x86.Mov('EAX', x86.mem('[ESP + 0x8]')) 55 | cpuid32_code += x86.Mov('EDI', x86.mem('[ESP + 0xc]')) 56 | cpuid32_code += x86.Cpuid() 57 | cpuid32_code += x86.Mov(x86.mem('[EDI + 0x0]'), 'EAX') 58 | cpuid32_code += x86.Mov(x86.mem('[EDI + 0x4]'), 'EBX') 59 | cpuid32_code += x86.Mov(x86.mem('[EDI + 0x8]'), 'ECX') 60 | cpuid32_code += x86.Mov(x86.mem('[EDI + 0xc]'), 'EDX') 61 | cpuid32_code += x86.Pop('EDI') 62 | cpuid32_code += x86.Ret() 63 | do_cpuid32 = native_function.create_function(cpuid32_code.get_code(), [DWORD, DWORD, PVOID]) 64 | 65 | 66 | cpuid64_code = x64.MultipleInstr() 67 | cpuid64_code += x64.Mov('RAX', 'RCX') 68 | cpuid64_code += x64.Mov('R10', 'RDX') 69 | cpuid64_code += x64.Cpuid() 70 | # For now assembler cannot do 32bits register in x64 71 | cpuid64_code += x64.Mov(x64.mem('[R10 + 0x00]'), 'RAX') 72 | cpuid64_code += x64.Mov(x64.mem('[R10 + 0x08]'), 'RBX') 73 | cpuid64_code += x64.Mov(x64.mem('[R10 + 0x10]'), 'RCX') 74 | cpuid64_code += x64.Mov(x64.mem('[R10 + 0x18]'), 'RDX') 75 | cpuid64_code += x64.Ret() 76 | do_cpuid64 = native_function.create_function(cpuid64_code.get_code(), [DWORD, DWORD, PVOID]) 77 | 78 | 79 | def x86_cpuid(req): 80 | cpuid_res = X86CpuidResult() 81 | do_cpuid32(req, ctypes.addressof(cpuid_res)) 82 | return cpuid_res 83 | 84 | 85 | def x64_cpuid(req): 86 | cpuid_res = X64CpuidResult() 87 | do_cpuid64(req, ctypes.addressof(cpuid_res)) 88 | # For now assembler cannot do 32bits register in x64 89 | return X86CpuidResult(cpuid_res.RAX, cpuid_res.RBX, cpuid_res.RCX, cpuid_res.RDX) 90 | 91 | 92 | if bitness() == 32: 93 | do_cpuid = x86_cpuid 94 | else: 95 | do_cpuid = x64_cpuid 96 | 97 | 98 | def get_vendor_id(): 99 | cpuid_res = do_cpuid(0) 100 | return struct.pack("".format(get_vendor_id())) 120 | infos = format.from_buffer_copy(struct.pack("".format(bits)) 74 | return cls.int_size[bits] 75 | 76 | def get_new_page(self, size): 77 | self.maps.append(MyMap.get_map(size)) 78 | self.cur_offset = 0 79 | self.cur_page_size = size 80 | 81 | def reserve_size(self, size): 82 | if size + self.cur_offset > self.cur_page_size: 83 | self.get_new_page((size + 0x1000) & ~0xfff) 84 | addr = self.maps[-1].addr + self.cur_offset 85 | self.cur_offset += size 86 | return addr 87 | 88 | def reserve_int(self, nb_int=1): 89 | int_size = self.get_int_size() 90 | return self.reserve_size(int_size * nb_int) 91 | 92 | def write_code(self, code): 93 | size = len(code) 94 | if size + self.cur_offset > self.cur_page_size: 95 | self.get_new_page((size + 0x1000) & ~0xfff) 96 | self.maps[-1][self.cur_offset: self.cur_offset + size] = code 97 | addr = self.maps[-1].addr + self.cur_offset 98 | self.cur_offset += size 99 | return addr 100 | 101 | allocator = CustomAllocator() 102 | 103 | 104 | def get_functions(): 105 | version = sys.version_info 106 | python_dll = "python" + str(version.major) + str(version.minor) 107 | 108 | PyGILState_Ensure = windows.utils.get_func_addr(python_dll, 'PyGILState_Ensure'.encode()) 109 | PyObject_CallObject = windows.utils.get_func_addr(python_dll, 'PyObject_CallObject'.encode()) 110 | PyGILState_Release = windows.utils.get_func_addr(python_dll, 'PyGILState_Release'.encode()) 111 | return [PyGILState_Ensure, PyObject_CallObject, PyGILState_Release] 112 | 113 | 114 | def analyse_callback(callback): 115 | if not callable(callback): 116 | raise ValueError("Need a callable object :)") 117 | obj_id = id(callback) 118 | if not hasattr(callback, '_objects'): 119 | raise ValueError("Need a ctypes PyCFuncPtr") 120 | return obj_id 121 | 122 | 123 | # For windows 32 bits with stdcall 124 | def generate_stub_32(callback): 125 | c_callback = get_callback_address_32(callback) 126 | 127 | gstate_save_addr = x86.create_displacement(disp=allocator.reserve_int()) 128 | return_addr_save_addr = x86.create_displacement(disp=allocator.reserve_int()) 129 | save_ebx = x86.create_displacement(disp=allocator.reserve_int()) 130 | save_ecx = x86.create_displacement(disp=allocator.reserve_int()) 131 | save_edx = x86.create_displacement(disp=allocator.reserve_int()) 132 | save_esi = x86.create_displacement(disp=allocator.reserve_int()) 133 | save_edi = x86.create_displacement(disp=allocator.reserve_int()) 134 | 135 | ensure, objcall, release = get_functions() 136 | 137 | code = x86.MultipleInstr() 138 | # ## Shellcode ## # 139 | code += x86.Mov(save_ebx, 'EBX') 140 | code += x86.Mov(save_ecx, 'ECX') 141 | code += x86.Mov(save_edx, 'EDX') 142 | code += x86.Mov(save_esi, 'ESI') 143 | code += x86.Mov(save_edi, 'EDI') 144 | 145 | code += x86.Mov('EAX', ensure) 146 | code += x86.Call('EAX') 147 | code += x86.Mov(gstate_save_addr, 'EAX') 148 | 149 | # Save real return addr (for good argument parsing by the callback) 150 | code += x86.Pop('EAX') 151 | code += x86.Mov(return_addr_save_addr, 'EAX') 152 | 153 | code += x86.Mov('EAX', c_callback) 154 | code += x86.Call('EAX') 155 | 156 | # Restore real return value 157 | code += x86.Mov('EBX', return_addr_save_addr) 158 | code += x86.Push('EBX') 159 | 160 | # Save return value 161 | code += x86.Push('EAX') 162 | code += x86.Mov('EBX', gstate_save_addr) 163 | code += x86.Push('EBX') 164 | 165 | code += x86.Mov('EAX', release) 166 | code += x86.Call('EAX') 167 | 168 | # Discard `release` argument 169 | code += x86.Pop('EAX') 170 | # Restore return value 171 | code += x86.Pop('EAX') 172 | code += x86.Mov('EBX', save_ebx) 173 | code += x86.Mov('ECX', save_ecx) 174 | code += x86.Mov('EDX', save_edx) 175 | code += x86.Mov('ESI', save_esi) 176 | code += x86.Mov('EDI', save_edi) 177 | code += x86.Ret() 178 | return code 179 | 180 | 181 | def generate_stub_64(callback): 182 | c_callback = get_callback_address_64(callback) 183 | REG_LEN = ctypes.sizeof(ctypes.c_void_p) 184 | register_to_save = ("RBX", "RCX", "RDX", "RSI", "RDI", "R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15") 185 | 186 | push_all_save_register = x64.MultipleInstr([x64.Push(reg) for reg in register_to_save]) 187 | pop_all_save_register = x64.MultipleInstr([x64.Pop(reg) for reg in reversed(register_to_save)]) 188 | # Reserve parallel `stack` 189 | save_register_space = allocator.reserve_int(len(register_to_save)) 190 | save_register_space_end = save_register_space + (ctypes.sizeof(ctypes.c_void_p) * (len(register_to_save))) 191 | 192 | save_rbx = save_register_space_end - REG_LEN 193 | save_rbx # Fuck the linter :D 194 | save_rcx = save_register_space_end - REG_LEN - REG_LEN 195 | save_rdx = save_register_space_end - REG_LEN - (REG_LEN * 2) 196 | save_rsi = save_register_space_end - REG_LEN - (REG_LEN * 3) 197 | save_rsi # Fuck the linter :D 198 | save_rdi = save_register_space_end - REG_LEN - (REG_LEN * 4) 199 | save_rdi # Fuck the linter :D 200 | save_r8 = save_register_space_end - REG_LEN - (REG_LEN * 5) 201 | save_r9 = save_register_space_end - REG_LEN - (REG_LEN * 6) 202 | 203 | gstate_save_addr = x64.create_displacement(disp=allocator.reserve_int()) 204 | return_addr_save_addr = x64.create_displacement(disp=allocator.reserve_int()) 205 | return_value_save_addr = x64.create_displacement(disp=allocator.reserve_int()) 206 | 207 | Reserve_space_for_call = x64.MultipleInstr([x64.Push('RDI')] * 4) 208 | Clean_space_for_call = x64.MultipleInstr([x64.Pop('RDI')] * 4) 209 | Do_stack_alignement = x64.MultipleInstr([x64.Push('RDI')] * 1) 210 | Remove_stack_alignement = x64.MultipleInstr([x64.Pop('RDI')] * 1) 211 | 212 | ensure, objcall, release = get_functions() 213 | 214 | # ## Shellcode ## # 215 | code = x64.MultipleInstr() 216 | # Save all registers 217 | code += x64.Mov('RAX', save_register_space_end) 218 | code += x64.Xchg('RAX', 'RSP') 219 | code += push_all_save_register 220 | code += x64.Xchg('RAX', 'RSP') 221 | # GOOO 222 | code += x64.Mov('RAX', ensure) 223 | code += Reserve_space_for_call 224 | code += Do_stack_alignement 225 | code += x64.Call('RAX') 226 | code += Remove_stack_alignement 227 | code += Clean_space_for_call 228 | code += x64.Mov(gstate_save_addr, 'RAX') 229 | # Save real return addr (for good argument parsing by the callback) 230 | code += x64.Pop('RAX') 231 | code += x64.Mov(return_addr_save_addr, 'RAX') 232 | # Restore parameters for real function call 233 | code += x64.Mov('RAX', save_rcx) 234 | code += x64.Mov('RCX', x64.mem('[RAX]')) 235 | code += x64.Mov('RAX', save_rdx) 236 | code += x64.Mov('RDX', x64.mem('[RAX]')) 237 | code += x64.Mov('RAX', save_r9) 238 | code += x64.Mov('R9', x64.mem('[RAX]')) 239 | code += x64.Mov('RAX', save_r8) 240 | code += x64.Mov('R8', x64.mem('[RAX]')) 241 | # Call python code 242 | code += x64.Mov('RAX', c_callback) 243 | # no need for stack alignement here as we poped the return addr 244 | # no need for Reserve_space_for_call as we must use the previous one for correct argument parsing 245 | code += x64.Call('RAX') 246 | # Save return value 247 | code += x64.Mov(return_value_save_addr, 'RAX') 248 | # Repush real return value 249 | code += x64.Mov('RAX', return_addr_save_addr) 250 | code += x64.Push('RAX') 251 | # Call release(gstate_save) 252 | code += x64.Mov('RAX', gstate_save_addr) 253 | code += x64.Mov('RCX', 'RAX') 254 | code += x64.Mov('RAX', release) 255 | code += Reserve_space_for_call 256 | code += Do_stack_alignement 257 | code += x64.Call('RAX') 258 | code += Remove_stack_alignement 259 | code += Clean_space_for_call 260 | # Restore registers 261 | code += x64.Mov('RAX', save_register_space) 262 | code += x64.Xchg('RAX', 'RSP') 263 | code += pop_all_save_register 264 | code += x64.Xchg('RAX', 'RSP') 265 | # Restore return value 266 | code += x64.Mov('RAX', return_value_save_addr) 267 | code += x64.Ret() 268 | return code 269 | 270 | 271 | def generate_callback_stub(callback, types): 272 | func_type = ctypes.WINFUNCTYPE(*types) 273 | c_callable = func_type(callback) 274 | if windows.current_process.bitness == 32: 275 | stub = generate_stub_32(c_callable) 276 | else: 277 | stub = generate_stub_64(c_callable) 278 | stub_addr = allocator.write_code(stub.get_code()) 279 | generate_callback_stub.l.append((stub, c_callable)) 280 | return stub_addr 281 | 282 | generate_callback_stub.l = [] 283 | 284 | 285 | def create_function(code, types): 286 | """Create a python function that call raw machine code 287 | 288 | :param str code: Raw machine code that will be called 289 | :param list types: Return type and parameters type (see :mod:`ctypes`) 290 | :return: the created function 291 | :rtype: function 292 | """ 293 | func_type = ctypes.CFUNCTYPE(*types) 294 | addr = allocator.write_code(code) 295 | return func_type(addr) 296 | 297 | 298 | # Return First argument for 32 bits code 299 | raw_code = x86.MultipleInstr() 300 | raw_code += x86.Mov('EAX', x86.mem('[ESP + 4]')) 301 | raw_code += x86.Ret() 302 | get_callback_address_32 = create_function(raw_code.get_code(), [ctypes.c_void_p]) 303 | 304 | 305 | # Return First argument for 64 bits code 306 | raw_code = x64.MultipleInstr() 307 | raw_code += x64.Mov('RAX', 'RCX') 308 | raw_code += x64.Ret() 309 | get_callback_address_64 = create_function(raw_code.get_code(), [ctypes.c_void_p]) 310 | -------------------------------------------------------------------------------- /windows/pe_parse.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import windows 3 | import windows.hooks as hooks 4 | import windows.utils as utils 5 | 6 | from windows.generated_def.winstructs import * 7 | import windows.remotectypes as rctypes 8 | 9 | # This must go to windefs 10 | IMAGE_DIRECTORY_ENTRY_EXPORT = 0 11 | IMAGE_DIRECTORY_ENTRY_IMPORT = 1 12 | 13 | IMAGE_ORDINAL_FLAG32 = 0x80000000 14 | IMAGE_ORDINAL_FLAG64 = 0x8000000000000000 15 | 16 | 17 | def RedefineCtypesStruct(struct, replacement): 18 | class NewStruct(ctypes.Structure): 19 | _fields_ = transform_ctypes_fields(struct, replacement_) 20 | NewStruct.__name__ = struct.__name__ 21 | return NewStruct 22 | 23 | 24 | # type replacement based on name 25 | def transform_ctypes_fields(struct, replacement): 26 | return [(name, replacement.get(name, type)) for name, type in struct._fields_] 27 | 28 | 29 | def PEFile(baseaddr, target=None): 30 | # TODO: 32 with target 32 31 | # 64 with target 64 32 | # For now you can do it by injecting a remote python.. 33 | proc_bitness = windows.current_process.bitness 34 | if target is None: 35 | targetedbitness = proc_bitness 36 | else: 37 | targetedbitness = target.bitness 38 | 39 | if targetedbitness == 32 and proc_bitness == 64: 40 | raise NotImplementedError("Parse 32bits PE with 64bits current_process") 41 | elif targetedbitness == 64 and proc_bitness == 32: 42 | ctypes_structure_transformer = rctypes.transform_type_to_remote64bits 43 | 44 | def create_structure_at(structcls, addr): 45 | return rctypes.transform_type_to_remote64bits(structcls)(addr, target) 46 | elif targetedbitness == proc_bitness: # Does not handle remote of same bitness.. 47 | ctypes_structure_transformer = lambda x: x 48 | 49 | def create_structure_at(structcls, addr): 50 | return structcls.from_address(addr) 51 | else: 52 | raise NotImplementedError("Parsing {0} PE from {1} Process".format(targetedbitness, proc_bitness)) 53 | 54 | if targetedbitness == 32: 55 | IMAGE_ORDINAL_FLAG = IMAGE_ORDINAL_FLAG32 56 | else: 57 | IMAGE_ORDINAL_FLAG = IMAGE_ORDINAL_FLAG64 58 | 59 | class RVA(DWORD): 60 | @property 61 | def addr(self): 62 | return baseaddr + self.value 63 | 64 | def __repr__(self): 65 | return "".format(self.value, hex(self.addr)) 66 | 67 | class StringRVa(RVA): 68 | if proc_bitness == 32 and targetedbitness == 64: 69 | @property 70 | def str(self): 71 | return rctypes.Remote_c_char_p64(self.addr, target=target).value 72 | else: 73 | @property 74 | def str(self): 75 | return ctypes.c_char_p(self.addr).value.decode() 76 | 77 | def __repr__(self): 78 | return "".format(self.value, self.str) 79 | 80 | def __int__(self): 81 | return self.value 82 | 83 | class IMPORT_BY_NAME(ctypes.Structure): 84 | _fields_ = [ 85 | ("Hint", WORD), 86 | ("Name", BYTE) 87 | ] 88 | 89 | class THUNK_DATA(ctypes.Union): 90 | _fields_ = [ 91 | ("Ordinal", PVOID), 92 | ("AddressOfData", PVOID) 93 | ] 94 | 95 | class IATEntry(ctypes.Structure): 96 | _fields_ = [ 97 | ("value", PVOID)] 98 | 99 | @classmethod 100 | def create(cls, addr, ord, name): 101 | self = create_structure_at(cls, addr) 102 | self.addr = addr 103 | self.ord = ord 104 | self.name = name 105 | self.hook = None 106 | self.nonhookvalue = self.value 107 | return self 108 | 109 | def __repr__(self): 110 | return '<{0} "{1}" ordinal {2}>'.format(self.__class__.__name__, self.name, self.ord) 111 | 112 | def set_hook(self, callback, types=None): 113 | hook = hooks.IATHook(self, callback, types) 114 | self.hook = hook 115 | hook.enable() 116 | return hook 117 | 118 | def remove_hook(self): 119 | if self.hook is None: 120 | return False 121 | self.hook.disable() 122 | self.hook = None 123 | return True 124 | 125 | class PEFile(object): 126 | def __init__(self): 127 | self.baseaddr = baseaddr 128 | 129 | def get_DOS_HEADER(self): 130 | return create_structure_at(IMAGE_DOS_HEADER, baseaddr) 131 | 132 | def get_NT_HEADER(self): 133 | return self.get_DOS_HEADER().get_NT_HEADER() 134 | 135 | def get_OptionalHeader(self): 136 | return self.get_NT_HEADER().OptionalHeader 137 | 138 | def get_DataDirectory(self): 139 | return self.get_OptionalHeader().DataDirectory 140 | 141 | def get_IMPORT_DESCRIPTORS(self): 142 | import_datadir = self.get_DataDirectory()[IMAGE_DIRECTORY_ENTRY_IMPORT] 143 | if import_datadir.VirtualAddress == 0: 144 | return [] 145 | import_descriptor_addr = RVA(import_datadir.VirtualAddress).addr 146 | current_import_descriptor = create_structure_at(self.IMAGE_IMPORT_DESCRIPTOR, import_descriptor_addr) 147 | res = [] 148 | while current_import_descriptor.FirstThunk: 149 | res.append(current_import_descriptor) 150 | import_descriptor_addr += ctypes.sizeof(self.IMAGE_IMPORT_DESCRIPTOR) 151 | current_import_descriptor = create_structure_at(self.IMAGE_IMPORT_DESCRIPTOR, import_descriptor_addr) 152 | return res 153 | 154 | def get_EXPORT_DIRECTORY(self): 155 | export_directory_rva = self.get_DataDirectory()[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress 156 | if export_directory_rva == 0: 157 | return None 158 | export_directory_addr = baseaddr + export_directory_rva 159 | return create_structure_at(self._IMAGE_EXPORT_DIRECTORY, export_directory_addr) 160 | 161 | class PESection(ctypes_structure_transformer(IMAGE_SECTION_HEADER)): 162 | @utils.fixedpropety 163 | def name(self): 164 | return ctypes.c_char_p(ctypes.addressof(self.Name)).value 165 | 166 | def __repr__(self): 167 | return "".format(self.name) 168 | 169 | @utils.fixedpropety 170 | def sections(self): 171 | nt_header = self.get_NT_HEADER() 172 | nb_section = nt_header.FileHeader.NumberOfSections 173 | base_section = ctypes.addressof(nt_header) + ctypes.sizeof(nt_header) 174 | sections_array = create_structure_at(self.PESection * nb_section, base_section) 175 | return list(sections_array) 176 | 177 | @utils.fixedpropety 178 | def exports(self): 179 | res = {} 180 | exp_dir = self.get_EXPORT_DIRECTORY() 181 | if exp_dir is None: 182 | return res 183 | raw_exports = exp_dir.get_exports() 184 | for id, rva_addr, rva_name in raw_exports: 185 | res[id] = rva_addr.addr 186 | if rva_name is not None: 187 | res[rva_name.str] = rva_addr.addr 188 | return res 189 | 190 | # TODO: get imports by parsing other modules exports if no INT 191 | @utils.fixedpropety 192 | def imports(self): 193 | res = {} 194 | for import_descriptor in self.get_IMPORT_DESCRIPTORS(): 195 | INT = import_descriptor.get_INT() 196 | IAT = import_descriptor.get_IAT() 197 | if INT is not None: 198 | for iat_entry, (ord, name) in zip(IAT, INT): 199 | # str(name.decode()) -> python2 and python3 compatible for str result 200 | iat_entry.ord = ord 201 | iat_entry.name = str(name.decode()) if name else "" 202 | res.setdefault(import_descriptor.Name.str.lower(), []).extend(IAT) 203 | return res 204 | 205 | # Will be usable as `self.IMAGE_IMPORT_DESCRIPTOR` 206 | class IMAGE_IMPORT_DESCRIPTOR(ctypes.Structure): 207 | _fields_ = transform_ctypes_fields(IMAGE_IMPORT_DESCRIPTOR, {"Name": StringRVa, "OriginalFirstThunk": RVA, "FirstThunk": RVA}) 208 | 209 | def get_INT(self): 210 | if not self.OriginalFirstThunk.value: 211 | return None 212 | int_addr = self.OriginalFirstThunk.addr 213 | int_entry = create_structure_at(THUNK_DATA, int_addr) 214 | res = [] 215 | while int_entry.Ordinal: 216 | if int_entry.Ordinal & IMAGE_ORDINAL_FLAG: 217 | res += [(int_entry.Ordinal & 0x7fffffff, None)] 218 | else: 219 | import_by_name = create_structure_at(IMPORT_BY_NAME, baseaddr + int_entry.AddressOfData) 220 | name_address = baseaddr + int_entry.AddressOfData + type(import_by_name).Name.offset 221 | if proc_bitness == 32 and targetedbitness == 64: 222 | name = rctypes.Remote_c_char_p64(name_address, target=target).value 223 | else: 224 | name = ctypes.c_char_p(name_address).value 225 | res.append((import_by_name.Hint, name)) 226 | int_addr += ctypes.sizeof(type(int_entry)) 227 | int_entry = create_structure_at(THUNK_DATA, int_addr) 228 | return res 229 | 230 | def get_IAT(self): 231 | iat_addr = self.FirstThunk.addr 232 | iat_entry = create_structure_at(THUNK_DATA, iat_addr) 233 | res = [] 234 | while iat_entry.Ordinal: 235 | res.append(IATEntry.create(iat_addr, -1, "??")) 236 | iat_addr += ctypes.sizeof(type(iat_entry)) 237 | iat_entry = create_structure_at(THUNK_DATA, iat_addr) 238 | return res 239 | 240 | # Will be usable as `self._IMAGE_EXPORT_DIRECTORY` 241 | class _IMAGE_EXPORT_DIRECTORY(ctypes.Structure): 242 | _fields_ = transform_ctypes_fields(IMAGE_EXPORT_DIRECTORY, {"Name": StringRVa, "AddressOfFunctions": RVA, "AddressOfNames": RVA, "AddressOfNameOrdinals": RVA}) 243 | 244 | def get_exports(self): 245 | NameOrdinals = create_structure_at((WORD * self.NumberOfNames), self.AddressOfNameOrdinals.addr) 246 | NameOrdinals = list(NameOrdinals) 247 | Functions = create_structure_at((RVA * self.NumberOfFunctions), self.AddressOfFunctions.addr) 248 | Names = create_structure_at((StringRVa * self.NumberOfNames), self.AddressOfNames.addr) 249 | res = [] 250 | for nb, func in enumerate(Functions): 251 | if nb in NameOrdinals: 252 | name = Names[NameOrdinals.index(nb)] 253 | else: 254 | name = None 255 | res.append((nb, func, name)) 256 | return res 257 | 258 | current_pe = PEFile() 259 | 260 | class IMAGE_DOS_HEADER(ctypes.Structure): 261 | _fields_ = [ 262 | ("e_magic", CHAR * 2), 263 | ("e_cblp", WORD), 264 | ("e_cp", WORD), 265 | ("e_crlc", WORD), 266 | ("e_cparhdr", WORD), 267 | ("e_minalloc", WORD), 268 | ("e_maxalloc", WORD), 269 | ("e_ss", WORD), 270 | ("e_sp", WORD), 271 | ("e_csum", WORD), 272 | ("e_ip", WORD), 273 | ("e_cs", WORD), 274 | ("e_lfarlc", WORD), 275 | ("e_ovno", WORD), 276 | ("e_res", WORD * 4), 277 | ("e_oemid", WORD), 278 | ("e_oeminfo", WORD), 279 | ("e_res2", WORD * 10), 280 | ("e_lfanew", DWORD), 281 | ] 282 | 283 | def get_NT_HEADER(self): 284 | if targetedbitness == 32: 285 | return create_structure_at(IMAGE_NT_HEADERS32, baseaddr + self.e_lfanew) 286 | return create_structure_at(IMAGE_NT_HEADERS64, baseaddr + self.e_lfanew) 287 | 288 | return current_pe 289 | 290 | tst = PEFile.__code__.co_consts[13] 291 | -------------------------------------------------------------------------------- /windows/remotectypes.py: -------------------------------------------------------------------------------- 1 | import _ctypes 2 | import ctypes 3 | import ctypes.wintypes 4 | import itertools 5 | from _ctypes import _SimpleCData 6 | 7 | 8 | # ## Utils ### # 9 | def is_pointer(x): 10 | return isinstance(x, _ctypes._Pointer) 11 | 12 | 13 | def is_pointer_type(x): 14 | return issubclass(x, _ctypes._Pointer) 15 | 16 | 17 | def is_array(x): 18 | return isinstance(x, _ctypes.Array) 19 | 20 | 21 | def is_array_type(x): 22 | return issubclass(x, _ctypes.Array) 23 | 24 | 25 | def is_structure_type(x): 26 | return issubclass(x, ctypes.Structure) 27 | 28 | 29 | def is_union_type(x): 30 | return issubclass(x, ctypes.Union) 31 | 32 | # ### My types ### # 33 | 34 | # # 64bits pointer types # # 35 | 36 | # I know direct inheritance from _SimpleCData seems bad 37 | # But it seems to be the only way to have the normal 38 | # ctypes.Structure way of working (need to investigate) 39 | 40 | 41 | class c_void_p64(_SimpleCData): 42 | _type_ = "Q" 43 | 44 | 45 | class c_char_p64(_SimpleCData): 46 | _type_ = "Q" 47 | 48 | 49 | class c_wchar_p64(_SimpleCData): 50 | _type_ = "Q" 51 | 52 | # standard type translation 53 | # don't know how to handle size_t since it's non-distinguable from c_ulong 54 | # maybe force import before ctypes and modif stuff into ctypes ? 55 | 56 | 57 | # # Remote Value 58 | # Used by the RemoteStructure to access the target memory 59 | 60 | class RemoteValue(object): 61 | @classmethod 62 | def from_buffer_with_target(cls, buffer, offset=0, target=None): 63 | x = cls.from_buffer(buffer) 64 | x.target = target 65 | return x 66 | 67 | 68 | class RemotePtr(RemoteValue): 69 | @property 70 | def raw_value(self): 71 | return ctypes.cast(self, ctypes.c_void_p).value 72 | 73 | 74 | class RemoteCCharP(RemotePtr, ctypes.c_char_p): 75 | @property 76 | def value(self): 77 | base = self.raw_value 78 | res = [] 79 | for i in itertools.count(): 80 | x = self.target.read_memory(base + (i * 0x100), 0x100) 81 | if "\x00" in x: 82 | res.append(x.split("\x00", 1)[0]) 83 | break 84 | res.append(x) 85 | return "".join(res) 86 | 87 | 88 | class RemoteWCharP(RemotePtr, ctypes.c_char_p): 89 | @property 90 | def value(self): 91 | base = self.raw_value 92 | res = [] 93 | for i in itertools.count(): 94 | x = self.target.read_memory(base + (i * 0x100), 0x100) 95 | utf16_chars = ["".join(c) for c in zip(*[iter(x)] * 2)] 96 | if "\x00\x00" in utf16_chars: 97 | res.extend(utf16_chars[:utf16_chars.index("\x00\x00")]) 98 | break 99 | res.extend(x) 100 | return "".join(res).decode('utf16') 101 | 102 | 103 | class RemoteStructurePointer(RemotePtr, ctypes.c_void_p): 104 | @classmethod 105 | def from_buffer_with_target_and_ptr_type(cls, buffer, offset=0, target=None, ptr_type=None): 106 | x = cls.from_buffer(buffer) 107 | x.target = target 108 | x.real_pointer_type = ptr_type 109 | return x 110 | 111 | @property 112 | def contents(self): 113 | remote_pointed_type = RemoteStructure.from_structure(self.real_pointer_type._type_) 114 | return remote_pointed_type(self.raw_value, self.target) 115 | 116 | def __repr__(self): 117 | return "".format(self.real_pointer_type._type_.__name__) 118 | 119 | 120 | def create_remote_array(subtype, len): 121 | 122 | class RemoteArray(_ctypes.Array): 123 | _length_ = len 124 | _type_ = subtype 125 | 126 | def __init__(self, addr, target): 127 | self._base_addr = addr 128 | self.target = target 129 | 130 | def __getitem__(self, slice): 131 | if not isinstance(slice, (int, long)): 132 | raise NotImplementedError("RemoteArray slice __getitem__") 133 | if slice >= len: 134 | raise IndexError("Access to {0} for a RemoteArray of size {1}".format(slice, len)) 135 | item_addr = self._base_addr + (ctypes.sizeof(subtype) * slice) 136 | 137 | # TODO: do better ? 138 | class TST(ctypes.Structure): 139 | _fields_ = [("TST", subtype)] 140 | return RemoteStructure.from_structure(TST)(item_addr, target=self.target).TST 141 | return RemoteArray 142 | 143 | 144 | # 64bits pointers 145 | class RemotePtr64(RemoteValue): 146 | def __init__(self, value, target): 147 | self.target = target 148 | super(RemotePtr64, self).__init__(value) 149 | 150 | @property 151 | def raw_value(self): 152 | # Bypass our own 'value' implementation 153 | # Even if we are a subclass of c_ulonglong 154 | my_addr = ctypes.addressof(self) 155 | return ctypes.c_ulonglong.from_address(my_addr).value 156 | 157 | 158 | class Remote_c_void_p64(RemotePtr64, c_void_p64): 159 | pass 160 | 161 | 162 | # base explanation: 163 | # RemotePtr64 for the good `raw_value` implem 164 | # RemoteCCharP for the good `value` implem 165 | # c_char_p64 for the good _type_ (ctypes size) 166 | class Remote_c_char_p64(c_char_p64, RemotePtr64, RemoteCCharP): 167 | def __repr__(self): 168 | return "".format(self.raw_value) 169 | 170 | 171 | class Remote_w_char_p64(c_wchar_p64, RemotePtr64, RemoteWCharP): 172 | def __repr__(self): 173 | return "".format(self.raw_value) 174 | 175 | 176 | class RemoteStructurePointer64(Remote_c_void_p64): 177 | @property 178 | def raw_value(self): 179 | return self.value 180 | 181 | @classmethod 182 | def from_buffer_with_target_and_ptr_type(cls, buffer, offset=0, target=None, ptr_type=None): 183 | x = cls.from_buffer(buffer) 184 | x.target = target 185 | x.real_pointer_type = ptr_type 186 | return x 187 | 188 | @property 189 | def contents(self): 190 | remote_pointed_type = transform_type_to_remote64bits(self.real_pointer_type._sub_ctypes_) 191 | return remote_pointed_type(self.raw_value, self.target) 192 | 193 | 194 | type_32_64_translation_table = { 195 | ctypes.c_void_p: Remote_c_void_p64, 196 | ctypes.c_char_p: Remote_c_char_p64, 197 | ctypes.c_wchar_p: Remote_w_char_p64, 198 | } 199 | 200 | 201 | class RemoteStructureUnion(object): 202 | """Target is a process object""" 203 | _reserved_name = ["_target", "_fields_", "_fields_dict_", "_base_addr", "_get_field_by_name", 204 | "_get_field_descrptor_by_name", "_handle_field_getattr", "_field_type_to_remote_type", 205 | "__getattribute__", "_fields_"] 206 | 207 | _field_type_to_remote_type = { 208 | ctypes.c_char_p: RemoteCCharP, 209 | ctypes.c_wchar_p: RemoteWCharP, 210 | Remote_c_void_p64: Remote_c_void_p64, 211 | Remote_c_char_p64: Remote_c_char_p64, 212 | Remote_w_char_p64: Remote_w_char_p64 213 | } 214 | 215 | def __init__(self, base_addr, target): 216 | self._target = target 217 | self._base_addr = base_addr 218 | self._fields_dict_ = dict(self._fields_) 219 | 220 | def _get_field_by_name(self, fieldname): 221 | try: 222 | return self._fields_dict_[fieldname] 223 | except KeyError: 224 | raise AttributeError(fieldname + "is not a field of {0}".format(type(self))) 225 | 226 | def _get_field_descrptor_by_name(self, fieldname): 227 | return getattr(type(self), fieldname) # ctypes metaclass fill this for us 228 | 229 | def _handle_field_getattr(self, ftype, fosset, fsize): 230 | s = self._target.read_memory(self._base_addr + fosset, fsize) 231 | if ftype in self._field_type_to_remote_type: 232 | return self._field_type_to_remote_type[ftype].from_buffer_with_target(bytearray(s), target=self._target).value 233 | if issubclass(ftype, _ctypes._Pointer): # Pointer 234 | return RemoteStructurePointer.from_buffer_with_target_and_ptr_type(bytearray(s), target=self._target, ptr_type=ftype) 235 | if issubclass(ftype, RemotePtr64): # Pointer to remote64 bits process 236 | return RemoteStructurePointer64.from_buffer_with_target_and_ptr_type(bytearray(s), target=self._target, ptr_type=ftype) 237 | if issubclass(ftype, RemoteStructureUnion): # Structure|Union already transfomed in remote 238 | return ftype(self._base_addr + fosset, self._target) 239 | if issubclass(ftype, ctypes.Structure): # Structure that must be transfomed 240 | return RemoteStructure.from_structure(ftype)(self._base_addr + fosset, self._target) 241 | if issubclass(ftype, ctypes.Union): # Union that must be transfomed 242 | return RemoteUnion.from_structure(ftype)(self._base_addr + fosset, self._target) 243 | if issubclass(ftype, _ctypes.Array): # Arrays 244 | return create_remote_array(ftype._type_, ftype._length_)(self._base_addr + fosset, self._target) 245 | # Normal types 246 | # Follow the ctypes usage: if it's not directly inherited from _SimpleCData 247 | # We do not apply the .value 248 | # Seems weird but it's mandatory AND useful :D (in pe_parse) 249 | if _SimpleCData not in ftype.__bases__: 250 | return ftype.from_buffer(bytearray(s)) 251 | return ftype.from_buffer(bytearray(s)).value 252 | 253 | def __getattribute__(self, fieldname): 254 | if fieldname in type(self)._reserved_name: # Prevent recursion ! 255 | return super(RemoteStructureUnion, self).__getattribute__(fieldname) 256 | try: 257 | t = self._get_field_by_name(fieldname) 258 | except AttributeError: # Not a real attribute 259 | return super(RemoteStructureUnion, self).__getattribute__(fieldname) 260 | descr = self._get_field_descrptor_by_name(fieldname) 261 | return self._handle_field_getattr(t, descr.offset, descr.size) 262 | 263 | @classmethod 264 | def from_structure(cls, structcls): 265 | class MyStruct(cls, structcls): # inherit of structcls to keep property (see winobject.LoadedModule) 266 | _fields_ = structcls._fields_ 267 | 268 | MyStruct.__name__ = "Remote" + structcls.__name__ 269 | return MyStruct 270 | 271 | @classmethod 272 | def from_fields(cls, fields, base_cls=None): 273 | bases = [cls] 274 | if base_cls: 275 | bases.append(base_cls) 276 | # inherit of structcls to keep property (see winobject.LoadedModule) 277 | RemoteStruct = type("RemoteStruct", tuple(bases), {"_fields_": fields}) 278 | if base_cls: 279 | RemoteStruct.__name__ = "Remote" + base_cls.__name__ 280 | return RemoteStruct 281 | 282 | 283 | class RemoteStructure(RemoteStructureUnion, ctypes.Structure): 284 | pass 285 | 286 | 287 | class RemoteUnion(RemoteStructureUnion, ctypes.Union): 288 | pass 289 | 290 | 291 | remote_struct = RemoteStructure.from_structure 292 | 293 | if ctypes.sizeof(ctypes.c_void_p) == 4: 294 | # ctypes 32 -> 64 methods 295 | def MakePtr(type): 296 | class PointerToStruct64(Remote_c_void_p64): 297 | _sub_ctypes_ = (type) 298 | return PointerToStruct64 299 | 300 | def transform_structure_to_remote64bits(structcls): 301 | """Create a remote structure for a 64bits target process""" 302 | new_fields = [] 303 | for fname, ftype in structcls._fields_: 304 | ftype = transform_type_to_remote64bits(ftype) 305 | new_fields.append((fname, ftype)) 306 | return RemoteStructure.from_fields(new_fields, base_cls=structcls) 307 | 308 | def transform_union_to_remote64bits(structcls): 309 | """Create a remote structure for a 64bits target process""" 310 | new_fields = [] 311 | for fname, ftype in structcls._fields_: 312 | ftype = transform_type_to_remote64bits(ftype) 313 | new_fields.append((fname, ftype)) 314 | return RemoteUnion.from_fields(new_fields, base_cls=structcls) 315 | 316 | def transform_type_to_remote64bits(ftype): 317 | if is_pointer_type(ftype): 318 | return MakePtr(ftype._type_) 319 | if is_array_type(ftype): 320 | return create_remote_array(transform_type_to_remote64bits(ftype._type_), ftype._length_) 321 | if is_structure_type(ftype): 322 | return transform_structure_to_remote64bits(ftype) 323 | if is_union_type(ftype): 324 | return transform_union_to_remote64bits(ftype) 325 | # Normal types 326 | return type_32_64_translation_table.get(ftype, ftype) 327 | -------------------------------------------------------------------------------- /windows/syswow64.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import ctypes 3 | import codecs 4 | import windows 5 | import windows.native_exec.simple_x64 as x64 6 | from generated_def.winstructs import * 7 | 8 | # Special code for syswow64 process 9 | CS_32bits = 0x23 10 | CS_64bits = 0x33 11 | 12 | 13 | def genere_return_32bits_stub(ret_addr): 14 | ret_32b = x64.MultipleInstr() 15 | ret_32b += x64.Mov('RCX', (CS_32bits << 32) + ret_addr) 16 | ret_32b += x64.Push('RCX') 17 | ret_32b += x64.Retf32() # 32 bits return addr 18 | return ret_32b.get_code() 19 | 20 | # The format of a jump to 64bits mode 21 | dummy_jump = "\xea" + struct.pack("32 injection for now) 160 | if is_process_64_bits: 161 | raise NotImplementedError("Python execution 64->32") 162 | data = calc.virtual_alloc(0x1000) 163 | remote_python_code = """ 164 | import ctypes 165 | import windows 166 | # windows.utils.create_console() # remove comment for debug 167 | k32 = [m for m in windows.current_process.peb.modules if m.name == "kernel32.dll"][0] 168 | GetCurrentProcessId = k32.pe.exports['GetCurrentProcessId'] 169 | ctypes.c_uint.from_address({1}).value = GetCurrentProcessId 170 | """.format(os.getcwd(), data) 171 | calc.execute_python(textwrap.dedent(remote_python_code)) 172 | time.sleep(0.5) 173 | dword = struct.unpack(" not loaded in target <{1}>".format(dll_name, target)) 28 | mod = name_modules[0] 29 | return mod.pe.exports[func_name] 30 | 31 | 32 | def is_wow_64(hProcess): 33 | try: 34 | fnIsWow64Process = get_func_addr("kernel32.dll", "IsWow64Process") 35 | except winproxy.Kernel32Error: 36 | return False 37 | IsWow64Process = ctypes.WINFUNCTYPE(BOOL, HANDLE, ctypes.POINTER(BOOL))(fnIsWow64Process) 38 | Wow64Process = BOOL() 39 | res = IsWow64Process(hProcess, ctypes.byref(Wow64Process)) 40 | if res: 41 | return bool(Wow64Process) 42 | raise ctypes.WinError() 43 | 44 | 45 | def create_file_from_handle(handle, mode="r"): 46 | """Return a Python :class:`file` arround a windows HANDLE""" 47 | fd = msvcrt.open_osfhandle(handle, os.O_TEXT) 48 | return os.fdopen(fd, mode, 0) 49 | 50 | 51 | def get_handle_from_file(f): 52 | """Get the windows HANDLE of a python :class:`file`""" 53 | return msvcrt.get_osfhandle(f.fileno()) 54 | 55 | 56 | def create_console(): 57 | """Create a new console displaying STDOUT 58 | Useful in injection of GUI process""" 59 | winproxy.AllocConsole() 60 | stdout_handle = winproxy.GetStdHandle(windef.STD_OUTPUT_HANDLE) 61 | console_stdout = create_file_from_handle(stdout_handle, "w") 62 | sys.stdout = console_stdout 63 | 64 | stdin_handle = winproxy.GetStdHandle(windef.STD_INPUT_HANDLE) 65 | console_stdin = create_file_from_handle(stdin_handle, "r+") 66 | sys.stdin = console_stdin 67 | 68 | stderr_handle = winproxy.GetStdHandle(windef.STD_ERROR_HANDLE) 69 | console_stderr = create_file_from_handle(stderr_handle, "w") 70 | sys.stderr = console_stderr 71 | 72 | 73 | def create_process(path, show_windows=False): 74 | proc_info = PROCESS_INFORMATION() 75 | lpStartupInfo = None 76 | if show_windows: 77 | StartupInfo = STARTUPINFOA() 78 | StartupInfo.cb = ctypes.sizeof(StartupInfo) 79 | StartupInfo.dwFlags = 0 80 | lpStartupInfo = ctypes.byref(StartupInfo) 81 | windows.winproxy.CreateProcessA(path, lpProcessInformation=ctypes.byref(proc_info), lpStartupInfo=lpStartupInfo) 82 | proc = [p for p in windows.system.processes if p.pid == proc_info.dwProcessId][0] 83 | return proc 84 | 85 | 86 | def enable_privilege(lpszPrivilege, bEnablePrivilege): 87 | """Enable of disable a privilege: enable_privilege(SE_DEBUG_NAME, True)""" 88 | tp = TOKEN_PRIVILEGES() 89 | luid = LUID() 90 | hToken = HANDLE() 91 | 92 | winproxy.OpenProcessToken(winproxy.GetCurrentProcess(), TOKEN_ALL_ACCESS, byref(hToken)) 93 | winproxy.LookupPrivilegeValueA(None, lpszPrivilege, byref(luid)) 94 | tp.PrivilegeCount = 1 95 | tp.Privileges[0].Luid = luid 96 | if bEnablePrivilege: 97 | tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED 98 | else: 99 | tp.Privileges[0].Attributes = 0 100 | winproxy.AdjustTokenPrivileges(hToken, False, byref(tp), sizeof(TOKEN_PRIVILEGES)) 101 | winproxy.CloseHandle(hToken) 102 | if winproxy.GetLastError() == windef.ERROR_NOT_ALL_ASSIGNED: 103 | raise ValueError("Failed to get privilege {0}".format(lpszPrivilege)) 104 | return True 105 | 106 | 107 | def check_is_elevated(): 108 | """Return True if process is Admin""" 109 | hToken = HANDLE() 110 | elevation = TOKEN_ELEVATION() 111 | cbsize = DWORD() 112 | 113 | winproxy.OpenProcessToken(winproxy.GetCurrentProcess(), TOKEN_ALL_ACCESS, byref(hToken)) 114 | winproxy.GetTokenInformation(hToken, TokenElevation, byref(elevation), sizeof(elevation), byref(cbsize)) 115 | winproxy.CloseHandle(hToken) 116 | return elevation.TokenIsElevated 117 | 118 | 119 | def check_debug(): 120 | """Check that kernel is in debug mode 121 | beware of NOUMEX (https://msdn.microsoft.com/en-us/library/windows/hardware/ff556253(v=vs.85).aspx#_______noumex______)""" 122 | hkresult = HKEY() 123 | cbsize = DWORD(1024) 124 | bufferres = (c_char * cbsize.value)() 125 | 126 | winproxy.RegOpenKeyExA(HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Control", 0, KEY_READ, byref(hkresult)) 127 | winproxy.RegGetValueA(hkresult, None, "SystemStartOptions", RRF_RT_REG_SZ, None, byref(bufferres), byref(cbsize)) 128 | winproxy.RegCloseKey(hkresult) 129 | 130 | control = bufferres[:] 131 | if "DEBUG" not in control: 132 | # print "[-] Enable debug boot!" 133 | # print "> bcdedit /debug on" 134 | return False 135 | if "DEBUG=NOUMEX" not in control: 136 | pass 137 | # print "[*] Warning noumex not set!" 138 | # print "> bcdedit /set noumex on" 139 | return True 140 | 141 | 142 | class FixedInteractiveConsole(code.InteractiveConsole): 143 | def raw_input(self, prompt=">>>"): 144 | sys.stdout.write(prompt) 145 | return raw_input("") 146 | 147 | 148 | def pop_shell(): 149 | """Pop a console with an InterativeConsole""" 150 | create_console() 151 | FixedInteractiveConsole(locals()).interact() 152 | 153 | 154 | def get_kernel_modules(): 155 | cbsize = DWORD() 156 | 157 | winproxy.NtQuerySystemInformation(SystemModuleInformation, None, 0, byref(cbsize)) 158 | raw_buffer = (cbsize.value * c_char)() 159 | buffer = SYSTEM_MODULE_INFORMATION.from_address(ctypes.addressof(raw_buffer)) 160 | winproxy.NtQuerySystemInformation(SystemModuleInformation, byref(raw_buffer), sizeof(raw_buffer), byref(cbsize)) 161 | modules = (SYSTEM_MODULE * buffer.ModulesCount).from_address(addressof(buffer) + SYSTEM_MODULE_INFORMATION.Modules.offset) 162 | return list(modules) 163 | 164 | 165 | class VirtualProtected(object): 166 | """A context manager usable like `VirtualProtect` that will restore the old protection at exit 167 | 168 | Example:: 169 | 170 | with utils.VirtualProtected(IATentry.addr, ctypes.sizeof(PVOID), windef.PAGE_EXECUTE_READWRITE): 171 | IATentry.value = 0x42424242 172 | """ 173 | def __init__(self, addr, size, new_protect): 174 | if (addr % 0x1000): 175 | addr = addr - addr % 0x1000 176 | self.addr = addr 177 | self.size = size 178 | self.new_protect = new_protect 179 | 180 | def __enter__(self): 181 | self.old_protect = DWORD() 182 | winproxy.VirtualProtect(self.addr, self.size, self.new_protect, ctypes.byref(self.old_protect)) 183 | return self 184 | 185 | def __exit__(self, exc_type, exc_value, traceback): 186 | winproxy.VirtualProtect(self.addr, self.size, self.old_protect.value, ctypes.byref(self.old_protect)) 187 | return False 188 | 189 | 190 | class DisableWow64FsRedirection(object): 191 | """A context manager that disable the Wow64 Fs Redirection""" 192 | def __enter__(self): 193 | self.OldValue = PVOID() 194 | winproxy.Wow64DisableWow64FsRedirection(ctypes.byref(self.OldValue)) 195 | return self 196 | 197 | def __exit__(self, exc_type, exc_value, traceback): 198 | winproxy.Wow64RevertWow64FsRedirection(self.OldValue) 199 | return False 200 | --------------------------------------------------------------------------------