Vue d’ensemble

Cette suite d’outils implémente une chaîne d’attaque complète, de la génération du payload jusqu’à son exécution furtive en mémoire sur un système Windows x64 :

ComposantRôleArtefact produit
clipboard-loggerPayload DLL — capture le presse-papiers et écrit les données dans un fichier de logclipboard-logger.dll
dll-injectorInjecte la DLL dans un processus cible par Manual Mapping — produit un shellcode autonomeshellcode.bin
cryptic-runnerChiffre le shellcode (Rolling XOR) et génère un exécutable d’apparence anodine qui le déchiffre et l’exécute en mémoireexecutable.exe

Ce projet a été développé par Ruben Petteng Ngongang et Ryan Bouchou dans un cadre strictement académique (exercice Red Team). L'utilisation de ces outils en dehors d'un environnement de test autorisé est illégale et passible de poursuites pénales. Les auteurs déclinent toute responsabilité en cas d'usage malveillant.

Code source Le code source des projets mentionnées est disponible sur Github

Résultats VirusTotal

ArtefactLien VirusTotalSHA-256Archive
executable.exe (chaîne complète)VirusTotal2db91608cabc9fbaf945a36887560712926f93bf84560f044db8d3ef3adaa893Archive.org
helloworld.exe (test libc)VirusTotal
score9 (version pré-correction)VirusTotalArchive.org

Contexte des missions Red Team

Dans le cadre d’une mission Red Team, disposer d’un injecteur de DLL indétectable est indispensable pour assurer la persistance de l’accès obtenu sur un système et procéder ultérieurement aux différents pivots. En pratique, on injecte un payload C2 (Command and Control) afin de réaliser des actions distantes sur la machine infectée depuis un serveur d’attaque.

Schéma d'attaque C2

Ce faisant, un attaquant est à même d’opérer en toute discrétion depuis une base subrepticement cachée au sein d’un processus anodin. Tout l’enjeu réside dans l’obfuscation instillée au sein de l’injecteur afin de garantir l’indétectabilité de l’injection.


Architecture & Workflow

Flux d’exécution complet

Le pipeline de build orchestre les trois composants dans un ordre précis. Le script racine make.sh automatise l’ensemble :

Pipeline de build — Phase Linux

Flux d’exécution au runtime (Windows)

Flux d'exécution au runtime — Windows x64


Détail des composants

1. clipboard-logger — Payload DLL

Le payload est une DLL Windows x64 qui surveille le presse-papiers du système et exfiltre son contenu dans un fichier texte.

Fonctionnement

  • DllMain : à l’attachement (DLL_PROCESS_ATTACH), initialise les APIs dynamiques et crée un thread de polling.
  • Thread de polling : interroge GetClipboardSequenceNumber() toutes les 500 ms. Lorsqu’un changement est détecté, ouvre le presse-papiers, extrait le texte (CF_TEXT) et appelle la fonction de journalisation.
  • Journalisation : écrit le contenu capturé dans C:\Users\<USER>\Desktop\clip.txt via les syscalls directs NtCreateFile et NtWriteFile — aucun appel aux APIs Win32 de fichier.
  • Arrêt propre : sur DLL_PROCESS_DETACH, signale au thread de s’arrêter et attend sa terminaison.

Techniques d’évasion

TechniqueDétail
Résolution dynamique (PEB + FNV-1a)Aucune entrée suspecte dans l’IAT de la DLL
Direct Syscalls (NtCreateFile, NtWriteFile)Écriture fichier sans transiter par kernel32.dll
Fonctions string custom_strlen, _wcslen, _wcscpy, _wcscat — aucune dépendance CRT
Symbol strippingCompilation avec -s — aucun symbole de debug

Structure

clipboard-logger/
├── make.sh                    # Script de build → build/clipboard-logger.dll
├── src/
│   ├── clipboard-logger.c     # Logique principale (polling + logging)
│   ├── direct-syscalls.asm    # Stubs NT : NtCreateFile, NtWriteFile, etc.
│   └── peb-lookup.c           # Résolution dynamique APIs via PEB
└── include/
    ├── peb-lookup.h           # Structures PEB, hashes FNV-1a des APIs
    └── direct-syscalls.h      # Prototypes des stubs syscall

2. dll-injector — Injecteur par Manual Mapping

L’injecteur copie manuellement les sections PE de la DLL payload dans la mémoire d’un processus cible (explorer.exe par défaut), sans passer par LoadLibrary. La DLL injectée n’apparaît dans aucune liste de modules du système.

Orchestration de l’injection

  1. ProcessWalking : Énumération des processus via CreateToolhelp32Snapshot + Process32First/Next → PID cible.
  2. MannualMappingDll : Ouverture du handle, allocation distante (NtAllocateVirtualMemory), écriture des headers PE et sections (NtWriteVirtualMemory).
  3. injectManualMappingStub : Injection d’un bloc contigu [ASM stub | C stub | MANUAL_MAPPING_DATA] + création du thread distant (NtCreateThreadEx).

ASM Stub (576 octets, PIC x64)

Shellcode position-independent exécuté dans le processus cible :

  1. Alignement de pile (and rsp, -16)
  2. PEB Walk → InMemoryOrderModuleList
  3. Recherche de KERNEL32.DLL par comparaison WCHAR
  4. Résolution EAT → LoadLibraryA, GetProcAddress, GetModuleHandleA
  5. Complétion de MANUAL_MAPPING_DATA
  6. Appel de C_LoaderStub(pData)
  7. ExitThread(0) — résolu dynamiquement

C Stub (loader PIC, compilé sans CRT)

  1. Relocation de base : parcours .reloc, patch des entrées IMAGE_REL_BASED_DIR64
  2. Résolution IAT : LoadLibraryA + GetProcAddress pour chaque import (par nom ou ordinal)
  3. Appel DllMain : (HMODULE)pBase, DLL_PROCESS_ATTACH, NULL

DLL embarquée dans .text

La DLL payload est embarquée sous forme de tableau unsigned char bytecode[] dans la section .text via encrypt.py. Aucun accès disque au runtime. L’ensemble (injecteur + DLL) est extrait en un seul blob binaire par objcopy --only-section=.textshellcode.bin.

Direct Syscalls

Fonction NTSyscallRôle
NtOpenProcess0x0026Ouverture du handle sur le processus cible
NtAllocateVirtualMemory0x0018Allocation de mémoire distante
NtWriteVirtualMemory0x003aÉcriture DLL + stubs en mémoire distante
NtReadVirtualMemory0x003fLecture de la mémoire distante
NtProtectVirtualMemory0x0050Modification des permissions mémoire
NtCreateThreadEx0x00c9Création du thread distant

Stabilité des direct syscalls Les numéros de syscall sont hardcodés pour la version de Windows cible. En production, il faudrait les résoudre dynamiquement en parsant ntdll.dll ou via une méthode de type FreshyCalls^2.

Fallback APIs (résolution dynamique via PEB)

Fonction Win32Rôle
CreateToolhelp32SnapshotSnapshot de la liste des processus
Process32First / Process32NextItération sur les processus
VirtualFreeExLibération mémoire distante
CloseHandleFermeture des handles

Structure

dll-injector/
├── make.sh                         # Build shellcode (PIC + objcopy)
├── src/
│   ├── main.tpl.c                  # Template d'entrée (DLL embarquée)
│   ├── encrypt.py                  # Embed DLL en tableau C
│   ├── dll-injector.c              # Orchestration : mapping + injection
│   ├── pe-parser.c                 # Chargement PE depuis buffer mémoire
│   ├── loader-stub.c              # Stub C PIC (reloc + IAT + DllMain)
│   ├── asm-stub.asm                # Stub ASM PIC (PEB walk dans la cible)
│   ├── simple-dll.c                # DLL de démonstration (notification systray)
│   └── utils/
│       ├── peb-lookup.c            # Résolution dynamique APIs via PEB + FNV-1a
│       ├── memory.c                # Wrappers : direct syscalls NT + fallback
│       ├── direct-syscalls.asm     # Stubs ASM : appels NT directs
│       └── windows-memory.c        # Wrappers Windows Memory API
├── include/
│   ├── dll-injector/               # En-têtes principaux
│   ├── utils/                      # PEB lookup, macros, direct-syscalls, memory
│   └── windows/                    # Structures PE custom
├── docs/                           # Doxyfile + thème doxygen-awesome-css
└── test/                           # Tests unitaires (PE parser)

3. cryptic-runner — Crypter / Packer

Le runner chiffre un shellcode binaire brut avec un algorithme de Rolling XOR (clé aléatoire de 16 octets) et l’enveloppe dans un exécutable Windows qui le déchiffre et l’exécute en mémoire au runtime.

Chiffrement (build time — format.py)

  1. Lecture du binaire d’entrée (shellcode brut)
  2. Génération d’une clé aléatoire de 16 octets
  3. Chiffrement : encrypted[i] = plaintext[i] ^ key[i % 16]
  4. Génération de build/main.c à partir du template src/main.tpl.c avec les placeholders :
    • SET_BYTECODE_SIZE → taille du shellcode
    • SET_BYTECODE_ARRAY → tableau C du bytecode chiffré
    • SET_KEY_SIZE16
    • SET_KEY_ARRAY → tableau C de la clé

Déchiffrement & exécution (runtime — main.tpl.c)

  1. Anti-sandbox (check_available_ressources()) : interroge GlobalMemoryStatusEx() — si la RAM < 5 Go, exécute un hello_world() anodin et termine.
  2. Déchiffrement (sort_array()) : bytecode[i] ^= key[i % 16]
  3. Exécution : cast du buffer en pointeur de fonction et saut direct. Le bytecode est placé en section .text via --omagic, ce qui rend la mémoire exécutable sans appel à VirtualProtect.

Flags de compilation notables

FlagEffet
-Wl,--omagic.text et .data partagent le même segment — le bytecode est exécutable sans VirtualProtect
-Wl,--disable-nxcompatDésactive le marquage DEP dans le PE
-Wl,--disable-dynamicbaseDésactive l’ASLR

Structure

cryptic-runner/
├── make.sh                    # Build : format.py + mingw gcc → build/executable.exe
├── test.sh                    # Build des tests (PEB lookup + extraction shellcode)
├── helloworld.c               # Programme de test simple (Hello world)
├── src/
│   ├── main.tpl.c             # Template du wrapper (anti-sandbox + XOR + exec)
│   ├── format.py              # Chiffrement Rolling XOR + génération main.c
│   └── peb-lookup.c           # Résolution dynamique APIs via PEB (pour les tests)
├── include/
│   └── peb-lookup.h           # En-tête PEB lookup
└── test/
    └── test.c                 # Tests unitaires PEB lookup

Techniques mises en œuvre

TechniqueComposantDescription
Manual Mappingdll-injectorCopie manuelle du PE en mémoire distante, sans LoadLibrary — la DLL n’apparaît dans aucune liste de modules
API Hashing (FNV-1a)dll-injector, clipboard-loggerRésolution des APIs au runtime via PEB — aucun import suspect dans l’IAT
ASM Stub PICdll-injectorShellcode x64 position-independent : PEB walk dans le processus cible
C Loader Stub PICdll-injectorRelocation de base, résolution IAT, appel DllMain — compilé sans CRT
Direct Syscalls (NT)dll-injector, clipboard-loggerAppels directs au noyau via syscall, court-circuitant les hooks userland
DLL embarquée (.text)dll-injectorPayload compilé dans l’injecteur — aucun accès disque au runtime
Extraction shellcodedll-injectorobjcopy --only-section=.text → blob binaire sans en-têtes PE
Rolling XOR Cryptercryptic-runnerChiffrement du shellcode avec clé aléatoire 16 octets — signature différente à chaque build
Anti-Sandboxcryptic-runnerVérification de la RAM physique (≥ 5 Go) avant déchiffrement
Linker Evasioncryptic-runner--omagic + --disable-nxcompat + --disable-dynamicbase — exécution sans appels mémoire suspects

Prérequis & Compilation

Dépendances

OutilVersionUsage
x86_64-w64-mingw32-gcc≥ 12Cross-compilation Linux → Windows x64
x86_64-w64-mingw32-ldLinkage PIC sans CRT (dll-injector)
x86_64-w64-mingw32-objcopyExtraction section .text
nasm≥ 2.15Assemblage des stubs ASM (format win64)
python3≥ 3.6Scripts d’embed (encrypt.py) et de chiffrement (format.py)
doxygenDocumentation (optionnel)

Sur Arch Linux :

sudo pacman -S mingw-w64-gcc nasm python

Sur Debian/Ubuntu :

sudo apt install gcc-mingw-w64-x86-64 binutils-mingw-w64-x86-64 nasm python3

Compilation de la chaîne complète

Le script racine make.sh orchestre le build des trois composants dans l’ordre :

# Depuis la racine du projet :
./make.sh

Ce script exécute séquentiellement :

# 1. Build du payload DLL
cd clipboard-logger && ./make.sh
# → clipboard-logger/build/clipboard-logger.dll
 
# 2. Build de l'injecteur + extraction shellcode
cd dll-injector && ./make.sh ../clipboard-logger/build/clipboard-logger.dll
# → dll-injector/build/shellcode.bin
 
# 3. Build du runner chiffré
cd cryptic-runner && ./make.sh ../dll-injector/build/shellcode.bin
# → cryptic-runner/build/executable.exe

Compilation individuelle

clipboard-logger

cd clipboard-logger
./make.sh
# → build/clipboard-logger.dll

dll-injector

cd dll-injector
 
# Mode shellcode (PIC pur → .bin)
./make.sh /chemin/vers/payload.dll
# → build/shellcode.bin

cryptic-runner

cd cryptic-runner
 
# Chiffre et emballe un shellcode quelconque
./make.sh /chemin/vers/shellcode.bin
# → build/executable.exe
 
# Test avec un simple Hello World
./make.sh helloworld.c
# → build/executable.exe

Artefacts produits

RépertoireFichierDescription
clipboard-logger/build/clipboard-logger.dllPayload DLL (monitoring presse-papiers)
dll-injector/build/shellcode.binShellcode PIC autonome (injecteur + DLL embarquée)
cryptic-runner/build/main.cSource généré (bytecode chiffré + clé)
cryptic-runner/build/executable.exeLivrable final — exécutable FUD

Usage / Ligne de commande

Lancer la chaîne complète (build + déploiement)

# 1. Compiler la suite
./make.sh
 
# 2. Transférer executable.exe vers la machine Windows cible
cp cryptic-runner/build/executable.exe ~/windows_share/
 
# 3. Sur la machine Windows (avec explorer.exe en cours d'exécution) :
executable.exe

Vérification

Après exécution sur la cible Windows :

  1. executable.exe s’exécute et vérifie l’environnement (RAM ≥ 5 Go)
  2. Le shellcode est déchiffré en mémoire et exécuté
  3. L’injecteur résout les APIs via PEB et localise explorer.exe
  4. La DLL clipboard-logger est injectée par manual mapping dans explorer.exe
  5. Un fichier clip.txt apparaît sur le Bureau de l’utilisateur, contenant les données du presse-papiers

Test unitaire — DLL de démonstration

Pour tester l’injection sans le payload clipboard, la DLL de démonstration (simple-dll.c) affiche une notification dans le system tray :

cd dll-injector
# Compiler avec la DLL de démo au lieu du clipboard-logger
./make.sh build/injected-dll.dll

Injection distante — Détail

Étapes de l’injection

L’injection se déroule en six étapes séquentielles, dont les quatre dernières impliquent des opérations sur la mémoire du processus cible.

Injection de processus par Manual Mapping

Diagramme d’architecture interne

Architecture interne — Injecteur vs Processus cible

Erreurs possibles et mitigations

ÉtapeErreurMitigation
NtOpenProcessSTATUS_ACCESS_DENIED — processus protégé (PPL)Cibler un processus non-protégé dans le même contexte d’intégrité
NtAllocateVirtualMemorySTATUS_QUOTA_EXCEEDEDPasser NULL comme adresse pour déléguer le choix au noyau
NtWriteVirtualMemorySTATUS_PARTIAL_COPY — région non-accessibleS’assurer de l’allocation préalable avec PAGE_EXECUTE_READWRITE
NtCreateThreadExSTATUS_PROCESS_IS_TERMINATINGValider que le processus cible est actif avant injection
CreateToolhelp32SnapshotINVALID_HANDLE_VALUEVérifier que le processus cible est en cours d’exécution

Justification du processus cible

Le processus explorer.exe est choisi car :

  • il est toujours en cours d’exécution sur un poste Windows ;
  • il est détenu par l’utilisateur courant (pas besoin de privilèges élevés) ;
  • il possède déjà de nombreuses DLL chargées, ce qui rend une allocation mémoire supplémentaire moins suspecte.

Choix techniques

Pourquoi le Manual Mapping ?

Contrairement à l’utilisation classique de LoadLibrary, le manual mapping réimplémente le chargeur Windows (Ldr) en mode utilisateur pour copier manuellement les sections de la DLL dans l’espace mémoire de la cible. Ce faisant, on évite :

  • de faire appel à une fonction critique de l’API Windows (centrale et surveillée) ;
  • de recenser la DLL injectée dans la InLoadOrderModuleList.
AvantageInconvénient
Furtivité accrue : la DLL n’apparaît pas dans la liste des modules chargésComplexité : gestion manuelle des relocalisations, IAT et callbacks TLS
Bypass ETW : évite les événements générés par LoadLibraryStabilité : sensible aux variations de structures PE complexes

Alternatives écartées

  • LoadLibrary (DLL Injection standard) : extrêmement surveillée par les EDR, détectable via l’analyse des modules chargés.
  • Process Hollowing : le remplacement de l’image d’un processus légitime est une signature comportementale forte pour les antivirus modernes.

Stratégie FUD (Fully Undetectable)

Actions entreprises

  1. Résolution Dynamique d’API (PEB Hashing FNV-1a) : suppression de l’IAT — fonctions résolues au runtime par parcours du PEB.
  2. Direct Syscalls : appels NT directs via syscall, court-circuitant ntdll.dll et ses hooks userland.
  3. Anti-Sandbox : vérification de la RAM physique (< 5 Go → comportement anodin).
  4. Rolling XOR Crypter : signature binaire différente à chaque build grâce à la clé aléatoire.
  5. DLL embarquée + extraction shellcode : aucun fichier DLL sur le disque au runtime ; l’ensemble est un blob .bin autonome intégré dans le runner.
  6. Compilation PIC pure : -ffreestanding -nostdlib — aucune dépendance CRT, aucun import PE classique.
  7. Linker flags : --omagic (pas de VirtualProtect), --disable-nxcompat, --disable-dynamicbase.

Résultats de tests

  • Tests Locaux : exécution réussie sur Windows 11 avec Windows Defender (Protection en temps réel activée). Aucune alerte déclenchée, payload opérationnel.
  • VirusTotal : voir le tableau en tête de document.

Limites connues

Ce qui reste détectable

  • Analyse de la Call Stack : un EDR peut détecter que l’exécution provient d’une région mémoire unbacked (non associée à un fichier sur disque).
  • Scanner de mémoire (YARA) : si les chaînes du payload sont déchiffrées en mémoire sans nettoyage, elles peuvent être détectées par un scan périodique de la RAM.
  • Analyse comportementale : l’accès répété au presse-papiers par un processus injecté peut être considéré comme suspect sur une longue période.

Points à améliorer

  • Stack Spoofing : masquer l’origine de l’exécution dans la pile d’appels.
  • Polymorphisme : chiffrer le stub assembleur pour changer sa signature à chaque exécution.
  • Module Overloading : écraser un module légitime peu utilisé au lieu d’allouer une nouvelle page mémoire.
  • Chiffrement AES : remplacer le Rolling XOR par un chiffrement plus robuste (AES-CTR).
  • Résolution dynamique des SSN : parser ntdll.dll au runtime au lieu de hardcoder les numéros de syscall (compatibilité multi-versions Windows).

Bibliographie

  • Maldev Academy : documentation sur le Manual Mapping et le PEB Walking.
  • Alice Climent-Pommeret (FreshyCalls)^2 : tri de l’Export Address Table pour la résolution de SSN.
  • Jackson_T (SysWhispers) : implémentation des stubs assembleur pour les syscalls directs.
  • Microsoft Technical Documentation : structures internes ntdll.dll et Native API.
  • Ired.team : techniques d’évasion et API Hashing.
  • j00ru/windows-syscalls^1 : tables de numéros de syscalls par version de Windows.