Prérequis
Cet article a pour but d’initier à la création de shellcode sous Windows. Beaucoup de shellcodes sous Windows listent les modules pour résoudre des adresses de fonction. Dans cette première partie nous allons écrire en assembleur un programme (et non un shellcode) qui va lister ses propres modules. Voici donc les prérequis pour suivre cet article :
- Base en assembleur Intel x86
- Un système d’exploitation Windows
- Un assembleur http://www.nasm.us/
- Un compilateur http://www.mingw.org/
- Un éditeur de texte https://notepad-plus-plus.org/fr/
- Windbg (x86)
Process Environement Block
Le PEB (Process Environement Block) est une structure accessible en user-mode qui contient un tas d’information sur le processus courant. Cette structure est référencée dans le TEB (Thread Environement Block) accessible via le registre FS du thread courant. Le pointeur vers le PEB est située à 0x30 octets plus loin par rapport au début du TEB.
Commençons par écrire un bout de code qui va afficher son adresse.
; ---------------------------------
; Programme pour inspecter le PEB
; rappel :
; - nasm -f win32 main.asm
; - gcc -m32 main.obj
; - .\a.exe
; ---------------------------------
global _main
extern _printf
section .code
_main:
mov ebx,[fs:0x30] ; Récupère l'adresse du PEB
; affiche l'adresse du PEB
push ebx
push fmt_PEB
call _printf
add esp,8
ret
section .data
fmt_PEB:
db 'PEB : %08.8x',10,0
Parcourir les modules
Le contenu de la structure est partiellement détaillée ici : https://msdn.microsoft.com/fr-fr/library/windows/desktop/aa813706(v=vs.85).aspx
Les symboles de debug microsoft en disent beaucoup plus sur PEB, lançons Windbg et ouvrons un exécutable 32 bits.
Lancez les commandes suivantes dans l’ordre.
- .sympath srv* : défini l’emplacement des symboles (srv* pour télécharger les symboles )
- .reload : recharge les symboles
- dt ntdll!_PEB -r2 : montre récursivement les informations sur la structure _PEB.
Le membre Ldr contient la liste des modules chargés avec le processus. Un module est un exécutable ou une dll et chaque processus est composé de un ou plusieurs modules. Complétons notre code en affichant l’adresse de la structure Ldr.
mov ebx,[ebx+0x0C] ; Récupère le membre _PEB.Ldr
; afficher _PEB.Ldr
push ebx
push fmt_Ldr
call _printf
add esp,8
InLoadOrderModuleList est un item d’une liste chaînée circulaire (Flink item suivante, Blink item précédent). Le membre Flink pointe vers le prochain item du même type qui est contenu dans une structure de type _LDR_DATA_TABLE_ENTRY.
Cette structure contient des informations sur le module notamment son nom et l’adresse où il a été chargé en mémoire. Le code suivant va nous permettre de parcourir la liste,
lea esi,[ebx + 0x0C] ; &Ldr.InLoaderOrderLinks premier item de la liste circulaire
mov edi,esi ; Sauvegarde de l'adresse du premier item
enum_module:
lodsd ; Charge le second item de la liste
cmp eax,edi ; Si == au premier item de la liste tour fini
je exit
mov esi,eax ; esi pointe sur la structure _LDR_DATA_TABLE_ENTRY
lea ebx,[esi + 0x24] ; Récupère l'adresse de FullDllName
push ebx
call _printUnicodeString@4
add esp,4
jmp enum_module
exit:
ret
Comme la liste chaînée est circulaire l’adresse de départ est sauvegarder dans edi. L’instruction lodsd charge le DWORD pointé par esi. Pour savoir si l’on à parcouru toute la liste on compare l’adresse du premier item avec l’adresse courante. Ensuite on récupère l’adresse du membre FullDllName qui correspond au nom du module.
Ce membre est une structure de type _UNICODE_STRING
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
Voici donc une petite procédure pour réaliser l’affichage sans trop s’embêter en utilisant printf.
; ---------------------------------------------------------
; Fonction : _printUnicodeString@4
; Description : Affiche une chaine unicode
; Entrée : adresse d'une structure de type UNICODE_STRING
; Sortie : la chaine de caractère
; Retour : aucun
; ---------------------------------------------------------
_printUnicodeString@4:
push ebp
mov ebp,esp
mov eax,[ebp + 8] ; adresse de la structure UNICODE_STRING
mov eax,[eax + 4] ; contenu l'attribut Buffer
; afficher la chaine unicode
push eax
push fmt_unicode
call _printf
add esp,8
leave
ret
La procédure récupère le membre Buffer puis on réalise l’affichage avec la fonction printf et le format ‘%ws’ pour afficher une chaîne Unicode.
Voici le code complet du programme,
global _main
extern _printf
section .code
; ---------------------------------------------------------
; Fonction : _printUnicodeString@4
; Description : Affiche une chaine unicode
; Entrée : adresse d'une structure de type UNICODE_STRING
; Sortie : la chaine de caractère
; Retour : aucun
; ---------------------------------------------------------
_printUnicodeString@4:
push ebp
mov ebp,esp
mov eax,[ebp + 8] ; adresse de la structure UNICODE_STRING
mov eax,[eax + 4] ; contenu l'attribut Buffer
; afficher la chaine unicode
push eax
push fmt_unicode
call _printf
add esp,8
leave
ret
_main:
mov ebx,[fs:0x30] ; Récupère l'adresse du PEB
; afficher l'adresse du PEB
push ebx
push fmt_PEB
call _printf
add esp, 8
mov ebx,[ebx+0x0C] ; Récupère le contenu de _PEB.Ldr
; afficher _PEB.Ldr
push ebx
push fmt_Ldr
call _printf
add esp,8
lea esi,[ebx + 0x0C] ; &Ldr.InLoaderOrderLinks premier item de la liste circulaire
mov edi,esi ; Sauvegarde de l'adresse du premier item
enum_module:
lodsd ; Charge le second item de la liste
cmp eax,edi ; Si == au premier item de la liste tour fini
je exit
mov esi,eax ; esi pointe sur la structure _LDR_DATA_TABLE_ENTRY
lea ebx,[esi + 0x24] ; Récupère l'adresse de FullDllName
push ebx
call _printUnicodeString@4
add esp,4
jmp enum_module
exit:
ret
section .data
fmt_PEB:
db 'PEB : %08.8x', 10, 0
fmt_Ldr:
db 'LDR : %08.8x', 10, 0
fmt_unicode:
db ' %ws',10,0
fmt_hex:
db '0x%08.8x',10,0
On compile et on exécute,