mirror of
https://github.com/hakril/PythonForWindows
synced 2026-06-08 14:31:45 +00:00
599 lines
22 KiB
Markdown
599 lines
22 KiB
Markdown
# PythonForWindows
|
|
|
|
[](https://github.com/hakril/PythonForWindows/actions/workflows/tests.yml)
|
|
|
|
PythonForWindows (PFW) is a base of code aimed to make interaction with `Windows` (on X86/X64) easier (for both 32 and 64 bits Python).
|
|
Its goal is to offer abstractions around some of the OS features in a (I hope) pythonic way.
|
|
It also tries to make the barrier between python and native execution thinner in both ways.
|
|
There is no external dependencies but it relies heavily on the `ctypes` module.
|
|
|
|
Let's say that the codebase evolves with my needs, my researches and my curiosity.
|
|
|
|
Complete online documentation is available [here][ONLINE_DOC]
|
|
You can find some examples of code in the [samples directory][SAMPLE_DIR] or [online][ONLINE_SAMPLE].
|
|
|
|
PythonForWindows is principally known for:
|
|
|
|
- its ALPC-RPC Client (see [samples](https://hakril.github.io/PythonForWindows/build/html/sample.html#windows-rpc))
|
|
- its generated [ctypes definitions](https://github.com/hakril/PythonForWindows/tree/master/windows/generated_def).
|
|
|
|
|
|
|
|
If you have any issue, question or suggestion do not hesitate to create an issue or reach me out.
|
|
I am always glad to have feedbacks from people using this project.
|
|
|
|
## Installation
|
|
|
|
|
|
PythonForWindows is available on [Pypi](https://pypi.org/project/PythonForWindows/) an this can be installed with
|
|
|
|
``
|
|
python -m pip install PythonForWindows
|
|
``
|
|
|
|
You can also install PythonForWindows by cloning it and using the ``setup.py`` script:
|
|
|
|
``
|
|
python setup.py install
|
|
``
|
|
|
|
## Encoding & unicode
|
|
|
|
PythonForWindows support `python2.7` & `python3` and is currently tested for `Python2.7`, `3.6` & `3.11` via [Github Workflow](https://github.com/hakril/PythonForWindows/actions/workflows/tests.yml)
|
|
|
|
Since 1.0.0, the code uses "wide APIs" whenever possible and accept/returns python3 `str` (py2.7 `unicode` type) almost everywhere. Any functions/APIs not accepting unicode string can be considered a bug if its not stated explicitly in the documentation.
|
|
|
|
### Python2
|
|
|
|
PythonForWindows continues to support python2.7 as its the only way to have it running on `Windows XP` & `Windows Server 2003` which are sadly still seen in production.
|
|
Encoding errors at print time might be awkward for unicode string on python2, see the [PythonForWindows encoding guide](https://hakril.github.io/PythonForWindows/build/html/encoding.html) in the documentation.
|
|
|
|
|
|
## Overview
|
|
|
|
### Processes / Threads
|
|
|
|
PythonForWindows offers objects around processes and allows you to:
|
|
|
|
- Retrieve basic process informations (pid, name, ppid, bitness, ...)
|
|
- Perform basic interprocess operation (allocation, create thread, read/write memory)
|
|
- Explore the PEB (Process Environment Block)
|
|
- Execute `native` and `Python` code in the context of a process.
|
|
|
|
I try my best to make those features available for every cross-bitness processes (`32 <-> 64` in both ways).
|
|
This involves relying on non-documented `Windows` functions/behaviours and also injecting code in the 64bits world of a `Syswow64` process.
|
|
All those operations are also available for the `current_process`.
|
|
|
|
You can also make some operation on threads (suspend/resume/wait/get(or set) context/ kill)
|
|
|
|
```python
|
|
>>> import windows
|
|
>>> windows.current_process.bitness
|
|
32
|
|
>>> windows.current_process.token.integrity
|
|
SECURITY_MANDATORY_MEDIUM_RID(0x2000)
|
|
>>> proc = [p for p in windows.system.processes if p.name == "notepad.exe"][0]
|
|
>>> proc
|
|
<WinProcess "notepad.exe" pid 16520 at 0x544e410>
|
|
>>> proc.bitness
|
|
64
|
|
>>> proc.peb.modules[:3]
|
|
[<RemoteLoadedModule64 "notepad.exe" at 0x3671e90>, <RemoteLoadedModule64 "ntdll.dll" at 0x3671030>, <RemoteLoadedModule64 "kernel32.dll" at 0x3671080>]
|
|
>>> k32 = proc.peb.modules[2]
|
|
>>> hex(k32.pe.exports["CreateFileW"])
|
|
'0x7ffee6761550L'
|
|
>>> proc.threads[0]
|
|
<WinThread 17688 owner "notepad.exe" at 0x53b47f0>
|
|
>>> hex(proc.threads[0].context.Rip)
|
|
'0x7ffee68b54b0L'
|
|
>>> proc.execute_python("import os")
|
|
True
|
|
>>> proc.execute_python("exit(os.getpid() + 1)")
|
|
# execute_python raise if process died
|
|
Traceback (most recent call last):
|
|
...
|
|
WindowsError: <WinProcess "notepad.exe" pid 16520 (DEAD) at 0x579f610> died during execution of python command
|
|
>>> calc
|
|
<WinProcess "notepad.exe" pid 16520 (DEAD) at 0x579f610>
|
|
>>> calc.exit_code
|
|
16521L
|
|
```
|
|
|
|
### System information
|
|
|
|
Information about the Windows computer running the script are available through the `windows.system` object.
|
|
|
|
```python
|
|
>>> windows.system
|
|
<windows.winobject.system.System object at 0x03FEED10>
|
|
>>> windows.system.bitness
|
|
64
|
|
>>> windows.system.computer_name
|
|
'DESKTOP-VKUGISR'
|
|
>>> windows.system.product_type
|
|
VER_NT_WORKSTATION(0x1)
|
|
>>> windows.system.version
|
|
(10, 0)
|
|
>>> windows.system.version_name
|
|
'Windows 10'
|
|
>>> windows.system.build_number
|
|
'10.0.15063.608'
|
|
|
|
# windows.system also contains dynamic lists about processes / threads / handles / ...
|
|
>>> windows.system.handles[-2:]
|
|
[<Handle value=<0x5cc> in process pid=14360>, <Handle value=<0x28e4> in process pid=14360>]
|
|
>>> windows.system.processes[:2]
|
|
[<WinProcess "[System Process]" pid 0 at 0x433f7d0>, <WinProcess "System" pid 4 at 0x433fd30>]
|
|
>>> windows.system.logicaldrives[0]
|
|
<LogicalDrive "C:\" (DRIVE_FIXED)>
|
|
>>> windows.system.services[23]
|
|
<ServiceA "Appinfo" SERVICE_RUNNING(0x4)>
|
|
|
|
```
|
|
|
|
### IAT Hook
|
|
|
|
This codebase is born from my need to have IAT hooks implemented in Python.
|
|
So the features is present (See [online documentation][ONLINE_IATHOOK] about IAT hooks).
|
|
|
|
|
|
### Winproxy
|
|
|
|
A wrapper around some Windows functions. Arguments name and order are the same,
|
|
but some have default values and the functions raise exception on call error (I don't like `if` around all my call).
|
|
|
|
```python
|
|
>>> import windows
|
|
>>> help(windows.winproxy.VirtualAlloc)
|
|
# Help on function VirtualAlloc in module windows.winproxy:
|
|
# VirtualAlloc(lpAddress=0, dwSize=NeededParameter, flAllocationType=MEM_COMMIT(0x1000L), flProtect=PAGE_EXECUTE_READWRITE(0x40L))
|
|
# Errcheck:
|
|
# raise WinproxyError if result is 0
|
|
|
|
# Positional arguments
|
|
>>> windows.winproxy.VirtualAlloc(0, 0x1000)
|
|
34537472
|
|
|
|
# Keyword arguments
|
|
>>> windows.winproxy.VirtualAlloc(dwSize=0x1000)
|
|
34603008
|
|
|
|
# NeededParameter must be provided
|
|
>>> windows.winproxy.VirtualAlloc()
|
|
"""
|
|
Traceback (most recent call last):
|
|
File "<stdin>", line 1, in <module>
|
|
File "windows\winproxy.py", line 264, in VirtualAlloc
|
|
return VirtualAlloc.ctypes_function(lpAddress, dwSize, flAllocationType, flProtect)
|
|
File "windows\winproxy.py", line 130, in perform_call
|
|
raise TypeError("{0}: Missing Mandatory parameter <{1}>".format(self.func_name, param_name))
|
|
TypeError: VirtualAlloc: Missing Mandatory parameter <dwSize>
|
|
"""
|
|
|
|
# Error raises exception
|
|
>>> windows.winproxy.VirtualAlloc(dwSize=0xffffffff)
|
|
"""
|
|
Traceback (most recent call last):
|
|
File "<stdin>", line 1, in <module>
|
|
File "windows\winproxy.py", line 264, in VirtualAlloc
|
|
return VirtualAlloc.ctypes_function(lpAddress, dwSize, flAllocationType, flProtect)
|
|
File "windows\winproxy.py", line 133, in perform_call
|
|
return self._cprototyped(*args)
|
|
File "windows\winproxy.py", line 59, in kernel32_error_check
|
|
raise WinproxyError(func_name)
|
|
windows.winproxy.error.WinproxyError: VirtualAlloc: [Error 87] The parameter is incorrect.
|
|
"""
|
|
```
|
|
|
|
|
|
### Native execution
|
|
|
|
To make the barrier between `native` and `Python` code thinner,
|
|
PythonForWindows allows you to create native function callable from Python (thanks to `ctypes`) and also embed
|
|
a simple x86/x64 assembler.
|
|
|
|
```python
|
|
>>> import windows.native_exec.simple_x86 as x86
|
|
>>> code = x86.MultipleInstr()
|
|
>>> code += x86.Mov("EAX", 41)
|
|
>>> code += x86.Inc("EAX")
|
|
>>> code += x86.Ret()
|
|
>>> code.get_code()
|
|
'\xc7\xc0)\x00\x00\x00@\xc3'
|
|
# Create a function that takes no parameters and return an uint
|
|
>>> f = windows.native_exec.create_function(code.get_code(), [ctypes.c_uint])
|
|
>>> f()
|
|
42L
|
|
# Assemblers can also be used in a more standard way
|
|
>>> x86.assemble("cmp edi, 0; jnz :end; mov eax, 1; label :end; ret")
|
|
'\x81\xff\x00\x00\x00\x00u\x06\xc7\xc0\x01\x00\x00\x00\xc3'
|
|
```
|
|
|
|
### Token / Security Descriptor
|
|
|
|
Objects easing access to some information about ``Token`` and ``SecurityDescriptor`` are also available.
|
|
|
|
```python
|
|
>>> import windows.security
|
|
>>> import windows.generated_def as gdef
|
|
>>> tok = windows.current_process.token
|
|
>>> tok
|
|
<Token TokenId=0x6a2b4550 Type=TokenPrimary(0x1)>
|
|
>>> tok.username
|
|
u'hakril'
|
|
>>> tok.type
|
|
tagTOKEN_TYPE.TokenPrimary(0x1)
|
|
>>> tok.integrity
|
|
SECURITY_MANDATORY_MEDIUM_RID(0x2000)
|
|
>>> tok.duplicate(type=gdef.TokenImpersonation, impersonation_level=gdef.SecurityIdentification)
|
|
<Token TokenId=0x6a3532ce Type=TokenImpersonation(0x2) ImpersonationLevel=SecurityIdentification(0x1)>
|
|
|
|
# Security Descriptor
|
|
>>> sd = windows.security.SecurityDescriptor.from_filename("c:\windows\system32\kernel32.dll")
|
|
>>> sd
|
|
<SecurityDescriptor object at 0x054E3DF0>
|
|
>>> windows.utils.lookup_sid(sd.owner)
|
|
(u'NT SERVICE', u'TrustedInstaller')
|
|
>>> sd.dacl
|
|
<Acl count=6>
|
|
>>> list(sd.dacl)
|
|
[<AccessAllowedACE mask=2032127>, <AccessAllowedACE mask=1179817>, <AccessAllowedACE mask=1179817>, <AccessAllowedACE mask=1179817>, <AccessAllowedACE mask=1179817>, <AccessAllowedACE mask=1179817>]
|
|
>>> sd.dacl[1].sid
|
|
<PSID "S-1-5-32-544">
|
|
```
|
|
|
|
### Wintrust
|
|
|
|
To easily script some signature check script, PythonForWindows implements some wrapper functions around ``wintrust.dll``
|
|
|
|
```python
|
|
>>> import windows.wintrust
|
|
>>> windows.wintrust.is_signed(r"C:\Windows\system32\ntdll.dll")
|
|
True
|
|
>>> windows.wintrust.is_signed(r"C:\Windows\system32\python27.dll")
|
|
False
|
|
>>> windows.wintrust.full_signature_information(r"C:\Windows\system32\ntdll.dll")
|
|
SignatureData(signed=True,
|
|
catalog=u'C:\\Windows\\system32\\CatRoot\\{F750E6C3-38EE-11D1-85E5-00C04FC295EE}\\Package_35_for_KB3128650~31bf3856ad364e35~amd64~~6.3.1.2.cat',
|
|
catalogsigned=True, additionalinfo=0L)
|
|
>>> windows.wintrust.full_signature_information(r"C:\Windows\system32\python27.dll")
|
|
SignatureData(signed=False, catalog=None, catalogsigned=False, additionalinfo=TRUST_E_NOSIGNATURE(0x800b0100))
|
|
```
|
|
|
|
### WMI
|
|
|
|
To extract/play with even more information about the system, PythonForWindows is able to perform WMI request.
|
|
|
|
```python
|
|
>>> import windows
|
|
>>> windows.system.wmi.select
|
|
<bound method WmiNamespace.select of <WmiNamespace "root\cimv2">>
|
|
>>> windows.system.wmi.select("Win32_Process")[:3]
|
|
[<WmiObject instance of "Win32_Process">, <WmiObject instance of "Win32_Process">, <WmiObject instance of "Win32_Process">]# Get WMI data for current process
|
|
>>> windows.system.wmi.select("Win32_Process")[42]["Name"]
|
|
u'svchost.exe'
|
|
>>> wmi_cp = [p for p in windows.system.wmi.select("Win32_Process") if int(p["Handle"]) == windows.current_process.pid][0]
|
|
>>> wmi_cp["CommandLine"], wmi_cp["HandleCount"]
|
|
(u'"C:\\Python27\\python.exe"', 227)
|
|
```
|
|
|
|
### Registry
|
|
|
|
The project also contains some wrapping classes around `_winreg` for simpler use.
|
|
|
|
```python
|
|
>>> import windows
|
|
>>> from windows.generated_def import KEY_WRITE, KEY_READ, REG_QWORD
|
|
>>> registry = windows.system.registry
|
|
>>> cuuser_software = registry(r'HKEY_CURRENT_USER\Software')
|
|
>>> cuuser_software
|
|
<PyHKey "HKEY_CURRENT_USER\Software">
|
|
>>> cuuser_software.sam
|
|
KEY_READ(0x20019)
|
|
# Explore subkeys
|
|
>>> cuuser_software.subkeys[:3]
|
|
[<PyHKey "HKEY_CURRENT_USER\Software\7-Zip">, <PyHKey "HKEY_CURRENT_USER\Software\AppDataLow">, <PyHKey "HKEY_CURRENT_USER\Software\Audacity">]
|
|
>>> tstkey = registry('HKEY_CURRENT_USER\TestKey', KEY_WRITE | KEY_READ)
|
|
# Get / Set individual value
|
|
>>> tstkey["VALUE"] = 'a_value_for_my_key'
|
|
>>> tstkey["VALUE"]
|
|
KeyValue(name='VALUE', value=u'a_value_for_my_key', type=1)
|
|
>>> tstkey["MYQWORD"] = (123456789987654321, REG_QWORD) # Default is REG_DWORD for int/long
|
|
>>> tstkey["MYQWORD"]
|
|
KeyValue(name='MYQWORD', value=123456789987654321L, type=11)
|
|
# Explore Values
|
|
>>> tstkey.values
|
|
[KeyValue(name='MYQWORD', value=123456789987654321L, type=11), KeyValue(name='VALUE', value=u'a_value_for_my_key', type=1)]
|
|
```
|
|
|
|
### Object manager
|
|
|
|
PythonForWindows uses the native Windows NT API to display some information about the object in the Object Manager's name space.
|
|
Just like the well-known tools ``winobj.exe``
|
|
|
|
```python
|
|
>>> windows.system.object_manager.root
|
|
<KernelObject "\" (type="Directory")>
|
|
# The objects of type "Directory" can be acceded just like a dict
|
|
>>> list(windows.system.object_manager.root)[:3]
|
|
[u'PendingRenameMutex', u'ObjectTypes', u'storqosfltport']
|
|
# Find an object by its path
|
|
>>> windows.system.object_manager["KnownDLLs\\kernel32.dll"]
|
|
<KernelObject "\KnownDLLs\kernel32.dll" (type="Section")>
|
|
>>> k32 = windows.system.object_manager["KnownDLLs\\kernel32.dll"]
|
|
>>> k32.name, k32.fullname, k32.type
|
|
('kernel32.dll', '\\KnownDLLs\\kernel32.dll', u'Section')
|
|
# Follow SymbolicLink object
|
|
>>> windows.system.object_manager["\\KnownDLLs\\KnownDLLPath"]
|
|
<KernelObject "\KnownDLLs\KnownDLLPath" (type="SymbolicLink")>
|
|
>>> windows.system.object_manager["\\KnownDLLs\\KnownDLLPath"].target
|
|
u'C:\\WINDOWS\\System32'
|
|
```
|
|
|
|
### Scheduled Task
|
|
|
|
The ``windows.system.task_scheduler`` object allows to query and create scheduled task.
|
|
|
|
**This part is still in developpement and the API may evolve**
|
|
|
|
```python
|
|
>>> windows.system.task_scheduler
|
|
<TaskService at 0x4774670>
|
|
>>> windows.system.task_scheduler.root
|
|
<TaskFolder "\" at 0x4774710>
|
|
>>> task = windows.system.task_scheduler.root.tasks[2]
|
|
>>> task
|
|
<Task "DemoTask" at 0x47748f0>
|
|
>>> task.name
|
|
u'DemoTask'
|
|
# Explore task actions
|
|
>>> task.definition.actions[1]
|
|
<ExecAction at 0x4774800>
|
|
>>> task.definition.actions[1].path
|
|
u'c:\\windows\\python\\python.exe'
|
|
>>> task.definition.actions[1].arguments
|
|
u'yolo.py --test'
|
|
```
|
|
|
|
### Event logs
|
|
|
|
The ``windows.system.event_log`` object allows to query event logs.
|
|
|
|
**This part is still in developpement and the API may evolve**
|
|
|
|
```python
|
|
>>> windows.system.event_log
|
|
<windows.winobject.event_log.EvtlogManager object at 0x04A78270>
|
|
# Find a channel by its name
|
|
>>> chan = windows.system.event_log["Microsoft-Windows-Windows Firewall With Advanced Security/Firewall"]
|
|
>>> chan
|
|
<EvtChannel "Microsoft-Windows-Windows Firewall With Advanced Security/Firewall">
|
|
# Open .evtx files
|
|
>>> windows.system.event_log["test.evtx"]
|
|
<EvtFile "test.evtx">
|
|
# Query a channel for all events
|
|
>>> chan.query().all()[:2]
|
|
[<EvtEvent id="2004" time="2018-07-12 07:44:08.081504">, <EvtEvent id="2006" time="2018-07-12 07:57:59.806938">]
|
|
# Query a channel for some ids
|
|
>>> chan.query(ids=2004).all()[:2]
|
|
[<EvtEvent id="2004" time="2018-07-12 07:44:08.081504">, <EvtEvent id="2004" time="2018-07-12 07:57:59.815156">]
|
|
# Query a channel via XPATH
|
|
>>> evt = chan.query("Event/EventData[Data='Netflix']").all()[0]
|
|
# Explore event information
|
|
>>> evt
|
|
<EvtEvent id="2006" time="2018-07-17 10:32:39.160423">
|
|
>>> evt.data
|
|
{u'ModifyingUser': 69828304, u'RuleName': u'Netflix', u'ModifyingApplication': ...}
|
|
```
|
|
|
|
### ALPC-RPC
|
|
|
|
#### ALPC
|
|
|
|
Classes around **A**dvanced **L**ocal **P**rocedure **C**all (**ALPC**) syscalls allows to simply
|
|
write client and server able to send **ALPC** messages.
|
|
|
|
```python
|
|
>>> import windows.alpc
|
|
# Test server juste reply to each message with "REQUEST '{msg_data}' RECEIVED"
|
|
>>> client = windows.alpc.AlpcClient(r"\RPC Control\PythonForWindowsTESTPORT")
|
|
>>> response = client.send_receive("Hello world !")
|
|
>>> response
|
|
<windows.alpc.AlpcMessage object at 0x04C0D5D0>
|
|
>>> response.data
|
|
"REQUEST 'Hello world !' RECEIVED"
|
|
```
|
|
|
|
Full client/server code for this example is available is the [ALPC samples][ONLINE_SAMPLE_ALPC] along with a more complex example.
|
|
|
|
|
|
#### RPC
|
|
|
|
An RPC-Client based using **ALPC** communication is also integred
|
|
|
|
```python
|
|
# Server (port ALPC '\RPC Control\HelloRpc') offers:
|
|
# Interface '41414141-4242-4343-4444-45464748494a' version 1.0
|
|
# Method 1 -> int Add(int a, int b) -> return a + b
|
|
# This Test server is a real RPC Server using rpcrt4.dll and compiled with VS2015.
|
|
|
|
>>> import windows.rpc
|
|
>>> from windows.rpc import ndr
|
|
>>> client = windows.rpc.RPCClient(r"\RPC Control\HelloRpc")
|
|
>>> client
|
|
<windows.rpc.client.RPCClient object at 0x0411E130>
|
|
>>> iid = client.bind("41414141-4242-4343-4444-45464748494a")
|
|
>>> ndr_params = ndr.make_parameters([ndr.NdrLong] * 2)
|
|
# NDR pack + Make RPC call to method 1.
|
|
>>> resp = client.call(iid, 1, ndr_params.pack([41414141, 1010101]))
|
|
# Unpack the NDR response
|
|
>>> result = ndr.NdrLong.unpack(ndr.NdrStream(resp))
|
|
>>> result
|
|
42424242
|
|
```
|
|
|
|
A sample with the **U**ser **A**ccount **C**ontrol (**UAC**) and one with `lsasrv.dll` are available in the [RPC samples][ONLINE_SAMPLE_RPC].
|
|
|
|
|
|
### Debugger
|
|
|
|
PythonForWindows provides a standard debugger to debug other processes.
|
|
|
|
```python
|
|
import windows
|
|
import windows.debug
|
|
import windows.test
|
|
import windows.native_exec.simple_x86 as x86
|
|
import windows.generated_def as gdef
|
|
|
|
from windows.test import pop_proc_32
|
|
|
|
class MyDebugger(windows.debug.Debugger):
|
|
def on_exception(self, exception):
|
|
code = exception.ExceptionRecord.ExceptionCode
|
|
addr = exception.ExceptionRecord.ExceptionAddress
|
|
print("Got exception {0} at 0x{1:x}".format(code, addr))
|
|
if code == gdef.EXCEPTION_ACCESS_VIOLATION:
|
|
print("Access Violation: kill target process")
|
|
self.current_process.exit()
|
|
|
|
calc = windows.test.pop_proc_32(dwCreationFlags=gdef.DEBUG_PROCESS)
|
|
d = MyDebugger(calc)
|
|
calc.execute(x86.assemble("int3; mov [0x42424242], EAX; ret"))
|
|
d.loop()
|
|
|
|
## Ouput ##
|
|
Got exception EXCEPTION_BREAKPOINT(0x80000003) at 0x77e13c7d
|
|
Got exception EXCEPTION_BREAKPOINT(0x80000003) at 0x230000
|
|
Got exception EXCEPTION_ACCESS_VIOLATION(0xc0000005) at 0x230001
|
|
Access Violation: kill target process
|
|
```
|
|
|
|
The debugger handles
|
|
|
|
* Standard breakpoint ``int3``
|
|
* Hardware Execution breakpoint ``DrX``
|
|
* Memory breakpoint ``virtual protect``
|
|
|
|
|
|
#### LocalDebugger
|
|
|
|
You can also debug your own process (or debug a process by injection) via the LocalDebugger.
|
|
|
|
The LocalDebugger is an abstraction around Vectored Exception Handler (VEH)
|
|
|
|
```python
|
|
import windows
|
|
from windows.generated_def.winstructs import *
|
|
import windows.native_exec.simple_x86 as x86
|
|
|
|
class SingleSteppingDebugger(windows.debug.LocalDebugger):
|
|
SINGLE_STEP_COUNT = 4
|
|
def on_exception(self, exc):
|
|
code = self.get_exception_code()
|
|
context = self.get_exception_context()
|
|
print("EXCEPTION !!!! Got a {0} at 0x{1:x}".format(code, context.pc))
|
|
self.SINGLE_STEP_COUNT -= 1
|
|
if self.SINGLE_STEP_COUNT:
|
|
return self.single_step()
|
|
return EXCEPTION_CONTINUE_EXECUTION
|
|
|
|
class RewriteBreakpoint(windows.debug.HXBreakpoint):
|
|
def trigger(self, dbg, exc):
|
|
context = dbg.get_exception_context()
|
|
print("GOT AN HXBP at 0x{0:x}".format(context.pc))
|
|
# Rewrite the infinite loop with 2 nop
|
|
windows.current_process.write_memory(self.addr, "\x90\x90")
|
|
# Ask for a single stepping
|
|
return dbg.single_step()
|
|
|
|
|
|
d = SingleSteppingDebugger()
|
|
# Infinite loop + nop + ret
|
|
code = x86.assemble("label :begin; jmp :begin; nop; ret")
|
|
func = windows.native_exec.create_function(code, [PVOID])
|
|
print("Code addr = 0x{0:x}".format(func.code_addr))
|
|
# Create a thread that will infinite loop
|
|
t = windows.current_process.create_thread(func.code_addr, 0)
|
|
# Add a breakpoint on the infinite loop
|
|
d.add_bp(RewriteBreakpoint(func.code_addr))
|
|
t.wait()
|
|
print("Done!")
|
|
|
|
## Output ##
|
|
|
|
Code addr = 0x6a0002
|
|
GOT AN HXBP at 0x6a0002
|
|
EXCEPTION !!!! Got a EXCEPTION_SINGLE_STEP(0x80000004) at 0x6a0003
|
|
EXCEPTION !!!! Got a EXCEPTION_SINGLE_STEP(0x80000004) at 0x6a0004
|
|
EXCEPTION !!!! Got a EXCEPTION_SINGLE_STEP(0x80000004) at 0x6a0005
|
|
EXCEPTION !!!! Got a EXCEPTION_SINGLE_STEP(0x80000004) at 0x770c7c04
|
|
Done!
|
|
|
|
```
|
|
|
|
The local debugger handles
|
|
|
|
* Standard breakpoint ``int3``
|
|
* Hardware Execution breakpoint ``DrX``
|
|
|
|
### Symbols
|
|
|
|
Classes around the Symbols APIs of `dbghelp.dll` are also implemented and can be used independently of the Debugger.
|
|
The path of `dbghelp.dll` can also be given via the `PFW_DBGHELP_PATH` environment variable.
|
|
|
|
|
|
```python
|
|
# Python3
|
|
|
|
>>> from windows.debug import symbols
|
|
>>> # symbols.set_dbghelp_path(MY_DBGHELP_PATH)
|
|
>>> symbols.engine.options = 0 # Disable defered load
|
|
>>> sh = symbols.VirtualSymbolHandler()
|
|
>>> ntmod = sh.load_file(r"c:\windows\system32\ntdll.dll", addr=0x420000)
|
|
>>> ntmod
|
|
<SymbolModule name="ntdll" type=SymPdb pdb="ntdll.pdb" addr=0x420000>
|
|
>>> ntmod.name
|
|
'ntdll'
|
|
>>> ntmod.path
|
|
'c:\\windows\\system32\\ntdll.dll'
|
|
>>> ntmod.pdb
|
|
'c:\\Symbols\\ntdll.pdb\\8D5D5ED5D5B8AA609A82600C14E3004D1\\ntdll.pdb'
|
|
>>> sym = sh["ntdll!LdrLoadDll"]
|
|
>>> sym
|
|
<SymbolInfoW name="LdrLoadDll" start=0x44a160 tag=SymTagFunction>
|
|
>>> sym.fullname
|
|
'ntdll!LdrLoadDll'
|
|
>>> hex(sym.addr)
|
|
'0x44a160'
|
|
>>> sh.search("ntdll!*CreateFile")
|
|
[<SymbolInfoW name="EtwpCreateFile" start=0x47d9ec tag=SymTagFunction>, <SymbolInfoW name="EtwpCreateFile" start=0x47d9ec tag=SymTagPublicSymbol>, <SymbolInfoW name="NtCreateFile" start=0x4c03e0 tag=SymTagPublicSymbol>, <SymbolInfoW name="ZwCreateFile" start=0x4c03e0 tag=SymTagPublicSymbol>, <SymbolInfoW name="__imp_NtCreateFile" start=0x55cb70 tag=SymTagPublicSymbol>]
|
|
# Some types exploration
|
|
>>> peb = sh.get_type("ntdll!_PEB")
|
|
>>> peb
|
|
<SymbolType name="_PEB" tag=_SymTagEnum.SymTagUDT(0xb)>
|
|
>>> peb.size
|
|
2000
|
|
>>> peb.children[:3]
|
|
[<SymbolType name="InheritedAddressSpace" tag=_SymTagEnum.SymTagData(0x7)>, <SymbolType name="ReadImageFileExecOptions" tag=_SymTagEnum.SymTagData(0x7)>, <SymbolType name="BeingDebugged" tag=_SymTagEnum.SymTagData(0x7)>]
|
|
>>> peb.children[2].offset
|
|
2
|
|
```
|
|
|
|
### Other stuff (see doc / samples)
|
|
|
|
- Network
|
|
- COM
|
|
|
|
## Acknowledgments
|
|
|
|
* clmntb for his initial work on ``windows.security``
|
|
|
|
|
|
[LKD_GITHUB]: https://github.com/sogeti-esec-lab/LKD/
|
|
[SAMPLE_DIR]: https://github.com/hakril/PythonForWindows/tree/master/samples
|
|
[ONLINE_DOC]: https://hakril.github.io/PythonForWindows/
|
|
[ONLINE_SAMPLE]: https://hakril.github.io/PythonForWindows/build/html/sample.html
|
|
[ONLINE_SAMPLE_ALPC]: https://hakril.github.io/PythonForWindows/build/html/sample.html#windows-alpc
|
|
[ONLINE_SAMPLE_RPC]: https://hakril.github.io/PythonForWindows/build/html/sample.html#windows-rpc
|
|
[ONLINE_IATHOOK]: https://hakril.github.io/PythonForWindows/build/html/iat_hook.html |