ångstromCTF 2023: gaga2
70 points, 220 solves
TL;DR
Leak
putsaddress, 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: testPerforming 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 therdiregister 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 fromgaga2binary. In this case, we will leak the address ofputsfunction.elf.plt.puts: The function of choice invoked to leakgot.putsaddress. The reason whyputsis used again here, is becauseputsonly requires 1 arguments, so we only need to prepare 1 registerrdi. Technically speaking, we can also usewriteorprintfhere, but it requires more arguments, hence more ROPGadgets to prepare.elf.symbols.main: Finally, we want to redirect program execution back tomainto 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