å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.

Finding Offset

Using de Brujin Sequence and pwndbg to locate the offset.

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.

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.

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.

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.

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.

Flag: actf{b4by's_f1rst_pwn!_3857ffd6bfdf775e}

Last updated