I’m focusing on exploit development at the moment and it’s time to raise the level to my personal next challenge: I’ve rm -rf’ed my Windows XP virtual machine! Now I’m happy to announce and document my first full VirtualProtect() ROP Remote Code Execution Exploit, which bypasses all known security mechanisms on Windows 7 - like SafeSEH, ASLR and DEP. Sounds like some funny pwnage…
I’m not going to dig too deep into DEP theory here - Corelan did already a great job on this! Plus I’m not going to use their mona.py tool to generate a complete ROP chain, because of two major reasons:
- An exploiter who didn’t write his first ROP exploit by himself, is a script kiddie - and I am not ;-)
- Even if I would use it, it wouldn’t work because of a lot of restrictions in this special case :-(
(I only use it to find the different ROP gadgets because doing this manually really, really sucks)
Neither the applications itself nor the vulnerability is very interesting - it’s a common issue, but the exploitation process is really thrilling - I promise! During the exploit development process, I had to deal with a lot of problems like 0x00 bytes and really creepy call restrictions, but more about this later…
Some words about the root-cause of the vulnerability
It’s a previously unknown Remote Code Execution vulnerability in the software called VirtualCharge Studio v184.108.40.2065 and it’s exploitable in a MITM (e.g. DNS-Spoofing or DNS-Cache Poisoning) scenario, where the attacker controls the IP behind the domain www.viceocharge.com to redirect the traffic. The complete Full-Disclosure posting can be found here.
The root-cause of the vulnerability is a combination of failing to validate the HTTP Content-Length value and the usage of the insecure function strcpy().
The application sends several HTTP GET requests to www.videocharge.com in different situations like checking for available updates or during the license activation process. The HTTP responses of the website are parsed using the following function from cc.dll:
This function reads the contents of the response page using an InternetReadFile() call with dwNumberOfBytesToRead set to 745 bytes. Afterwards the application uses an insecure strcpy() call to further process the received data:
Although 745 bytes are enough to trigger the buffer overflow, the application additionally does not perform a validation of the Content-Length value of the HTTP response, and will execute the InternetReadFile call a second time if the Content-Length value is greater than 745 with the dwNumberOfBytesToRead argument set to [Content-Length - 745] to make sure all bytes from the response are read, and uses the same strcpy() call to further process the received data, resulting in huge amounts of memory, that can be controlled by an attacker. This leads to a stack-based buffer overflow with an overwritten SEH chain, resulting in remote code execution.
The vulnerable code part:
Some words about the following exploit documentation
I’m going to exploit the license activation process in this documentation. For a successfull activation you need at least two things:
- A valid (client-side generated) serial number. Otherwise the application won’t let you perform its “Auto-activation through the internet”. I’m not going to write a tutorial about how to reverse engineer the serial generation process here - due to legal reasons. If you want to try it yourself - it’s your challenge!
- To exploit this vulnerability you do not need an active subscription/registration on videocharge.com. If you have a valid serial, the application checks this against the activation servers and even a “oops, we did not found your serial” answer is exploitable. This is what I will demonstrate in the following documentation.
Let’s get to work - How does the license activation process work?
Let’s first have a look what happens in the communication process between the installed application and the servers of VideoCharge. During the start of the application, you can activate the application right away:
At this point Wireshark captured the following request made by the application:
resulting in the corresponding HTTP response from the server:
Interesting! If the serial number is invalid, the web-server responds with a simple “unksn”. That’s the target! The vulnerability can be exploited by replacing the “unksn” with some arbitrary shellcode - stuff, which is read and parsed by the above mentioned InternetReadFile() and strcpy() call.
The following Python script simply reads the request made by the application and responses with the arbitrary value. Please remember that you have to spoof the DNS A-record for www.videocharge.com for this to work:
The increased Content-Length value of 4000 triggers the stack-based buffer overflow, which results in an overwritten SEH - chain:
Some very few basics about DEP
Since I’ve enabled DEP for all applications in my testing lab, I have to mess with memory limitations. Quoted from Wikipedia:
DEP protects against some program errors, and helps prevent certain malicious exploits, those that store executable instructions in a data area via a buffer overflow for example. It does not protect against attacks that do not rely on execution of instructions in the data area. Other security features such as address space layout randomization, structured exception handler overwrite protection (SEHOP) and Mandatory Integrity Control, can be used in conjunction with DEP.
This simply means that the shellcode, which is placed on the stack cannot be executed, because it’s arbitrary and doesn’t belong to the applications data area. So,if you launch a classic SEH-based exploit against a DEP-enabled target like:
…it’ll result in an Access Violation when it comes to the execution of the supplied exploit chain:
Bypassing DEP using ROP and VirtualProtect
The solution for the above DEP-related Access Violation exception is quite simple in its idea:
If you are not able to execute your own code directly on the stack, you need to find a way to build your exploit based on what you’ve already got: the data section of the application. But one problem remains:
The shellcode is still placed on the stack and needs to be executed there. No way around this!
The solution for this problem is built-in Microsoft Windows and is called VirtualProtect() (its home: kernel32.dll). This function is able to change the protection flag for the supplied memory space to make it executable for the calling process. The call structure is quite easy:
In theory all you need is to put the call to VirtualProtect() and its parameters on the stack and execute the VirtualProtect() call to unprotect your shellcode-area and then move on to your shellcode. This can be accomplished by searching for appropriate instructions in the data section of the corresponding application binaries that are followed by a RETN and those addresses are put on the stack instead of e.g. the SHORT JUMP - in case of SEH.
Summarized: Using this Return-Oriented-Programming (ROP) method, you modify the stack by putting executable data offsets on it, with the goal of putting the parameters for the VirtualProtect() call and the call itself on the stack and finally execute this sequence. These offsets contain instructions from the data section of the executable instead of the stack directly, and because every instruction set (ROP gadget) ends with a RETN it automatically executes the next offset on the stack. This quite nice hopping is called ROP chain - let’s see it in action.
Some special limitations
- In this special case of VideCharge Studio: You cannot use any special characters in the HTTP response like 0x00, 0x0a or 0x0d - these will break your complete exploit and therefore have to be avoided!
- You only have 3 potential modules you can work with. All others are at least rebased and ASLR enabled. That shrinks the available options dramatically!
- You cannot use vcstudio.exe completely, because it starts with a 0x00! But wait. The vcstudio.exe is the only one that contains references to the VirtualProtect() function. Too bad - but there’s a way around this!
The ROP registers structure
To keep track of things, I’ll use the following registers for the VirtualProtect parameters:
EBP - VirtualProtect() call
ESP - lpAddress
EBX - dwSize
EDX - flNewProtect
ECX - lpflOldProtect
EAX - is only used for crafting/calculations
I’m going to craft the different parameters using ROP and put them accordingly into the mentioned registers. Important is that once you’ve put the right value into the register, it shouldn’t be modified anymore. Otherwise it could be very hard to regain the modified value.
Step 1 - Stackpivoting from SEH
This is quite usual for a SEH-based exploit: at some point you’ve control over EIP. Find this location and perform some crazy stackpivoting to your controlled area. Luckily the developers left one module (zlib1.dll) without SafeSEH - protection leading to complete SafeSEH - bypass:
EIP control is gained after 1277 bytes of junk, but it’s quite a long distance (at least ESP+698) to the controlled memory space:
The only option you have in this case (and I’m glad it’s there) is to take the instruction at 0x61b84af1:
this moves the EIP to the controlled area.
Step 2 - Craft VirtualProtect() offset 0x0080D816 via [DE27BAF3 XOR DEADBEEF] and MOV to EBP
Let’s start with the most complex one. Do you remember that you cannot use 0x00 ? Have a look at this:
0x008A041C - damn. The only reference in vcstudio.exe starts with a 0x00 :-(
Side notice: Using the following trick I tried to generate the shown address 0x008A041C, but during testing the application always threw an unhandled exception, sadly - I don’t know why. That’s the reason why I’m sticking with the following VirtualProtect() call at 0x0080D816:
Since there are only a few instructions between the call of VirtualProtect (forget about the arguments at the moment) at 0x0080D816 and the RETN at 0x0080D83A, I’m using this way of calling the VirtualProtect() function. You only have to make sure that all instructions between both offsets are able to work with values that do not throw an exception and therefore terminating the exploitation flow. We will recap this important point at the end, when we’ll actually call this function!
Let’s get dirty. Since you cannot use 0x00, you need to somehow calculate the offset 0x0080D816 using given instructions. One easy and - in my opinion the sortest way of doing that is using XOR!
First of all, let’s XCHG the values of EAX and EAX to make sure that the following XOR will work:
The next instructions, will simply POP the values 0xDE2D66F9 to EDI and 0xDEADBEEF to EBX.
It’s quite hard to find an appropriate XOR instruction in the supplied application modules, but this one will do the work:
At this point you see that there is an ADD instruction that actually copies something to DL. That’s the reason you have to execute the first XCHG to get a valid address into EAX, otherwise the application will crash, because the parameters before the XCHG were:
EAX = 0x00000000 = bad for the ADD instruction
ECX = some valid memory address
Since you’ve executed the XCHG instruction first, ECX now contains 0x61B84AF1 and the XOR instruction can finish silently. The result is amazing: Now EDI contains 0x0080D816 - the targeted VirtualProtect() call!
Finally: MOV EDI to EAX and XCHG EAX with EBP (there is no other way to XCHG EDI with EBP):
Side-Notice: 0xDEADBEEF is always a filler, except in this XOR calculation ;-)
Step 3 - Craft VirtualProtect() dwSize in EAX and move to EBX
This one is easy:
Clearing EAX, ADDing 0x500 on top of it and XCHG with EBX
0x500 (1280bytes) are more than enough for the shellcode.
Step 4 - Craft VirtualProtect() flNewProtect in EAX and move to EDX
Remember: flNewProtect needs to be 0x40 and needs to be put into EDX. This value is quite small and can easily be calculated after clearing EAX using XOR:
Step 5 - Put writable offset for VirtualProtect() lpflOldProtect to ECX
That’s easy. Thanks to Immunity Debugger’s memory view, you can easily select a random address which has to be writable:
I’ve chosen one from zlib1.dll resources section (0x61B96180).
Step 6 - VirtualProtect() lpAddress and prepare for PUSHAD!
Great. You’re next to the end :-). Let’s have a look at the current register contents:
EAX = It’s empty…nobody actually cares!
ECX = VirtualProtect() lpflOldProtect
EDX= VirtualProtect() flNewProtect
EBX = VirtualProtect() dwSize
ESP = VirtualProtect() lpAddress
EBP = VirtualProtect() call from vcstudio.exe
As ESP is always pointing to the top of the stack, this automatically solves the problem of getting the correct VirtualProtect() lpAddress value, the place where the nopsled / shellcode begins. This is nearly the perfect layout for a PUSHAD instruction. PUSHAD pushes the values of all registers on the stack, beginning with EAX and ending with EDI. This would lead to the following stack layout after PUSHAD:
Great - the offset to the vcstudio.exe’s VirtualProtect() call is located at ESP+8, followed by it’s arguments :-) !
You only have to deal with the values from ESI (0x00000000) and EDI (0xDEADBEEF) that’ll be executed next (since they are on the top of the stack!). This means you’ve got two possible ROP gadgets left to use. This is a perfect match, since you really, really need them…Let’s review the VirtualProtect() call starting at 0x0080D816:
The four different instructions at 0x0080D81F, 0x0080D82C, 0x0080D82E and 0x0080D831 either read or write to the offsets at ESI or EDI. If you would leave the values in ESI (0x00000000) and EDI (0xDEADBEEF) as they are, the application would terminate, because it’s not possible to read or write to these locations.
Solution: Use the first ROP gadget to put some readable/writable offset to ESI:
…and the second ROP gadget to Double-POP-EDI to make sure you can reach the VirtualProtect() call and at the same time representing a readable/writable address:
And last but not least:
Step 7 - Finally execute VirtualProtect() and move on to the shellcode
After the PUSHAD, the stack layout is as following:
0x61B849B6 points to the mentioned POP EDI instruction to move the value 0x61B96180 to the register and finally executes the VirtualProtect() call at 0x0080D816
…using the parameters from the stack:
Achievement unlocked: Pwnage!
Step 8 - Finalize the exploit!
The VirtualProtect() code part ends with the instruction RETN 0C at 0x0080D83A, which magically returns into the unprotected stackpart:
Placing a JMP ESP (e.g. from 0x102340e8) at this point will lead to code execution:
The complete exploit…
Have fun ;-)
Some problems during shellcode execution
I tried to use a standard Metasploit calc.exe shellcode in my exploit script:
…but noticed that the application crashes using an unhandled exception, but starts the calc.exe in some kind of “broken” state:
Immunity Debugger reports the following unhandled exception in ntdll.dll:
That’s the reason why I have used the small MessageBox shellcode from </span>Giuseppe D’Amore here, which works perfectly on Windows 7 x64 German:
If someone likes to investigate the root-cause for this issue, feel free and leave a message below this post :-)