0. Sommaire
1. Pré-requis
2. Qu'est ce que l'inline API Hooking
3. Principe de fonctionnement
4. Un peu de vocabulaire
5. Implémentation technique
6. Exemple !
7. Connaître son ennemi
8. Quelques Liens
1. Pre-requis
Pour être à même de bien comprendre le sujet, il y a quelques fondamentaux dont les bases seront préférables de connaître:
- Une bonne base en Assembleur x86 (nous n'aborderons pas le 64-bits)
- Un minimum de programmation sous Windows, comprenant les APIs
- Optionnellement, le format PE/COFF des exécutables Microsoft
Techniquement :
- Un OS Microsoft à disposition (évidemment)
- FASM pour compiler l'exemple
- Ollydbg (si vous souhaitez débugger l'exemple plus bas)
2. Qu'est ce que l'inline API Hooking
C'est une technique de ring3 (userland) consistant au crochetage de fonctions importées par un executable dans le but de surveiller et/ou modifier leur flux d'exécution. On entend par crochetage la redirection des appels sur des fonctions de remplacement qui pourront par la suite rappeler l'API initiale (ou pas). L'utilité ? Ca dépend ... Les antivirus s'en servent encore aujourd'hui (McAfee par exemple) pour surveiller les comportements suspects pouvant être liés à un malware tandis que les developpeurs de malware l'utilise pour developper des rootkits.
3. Principe de fonctionnement
Lorsqu'un exécutable importe les API d'une DLL (voir doc sur le format PE / COFF), le code de cette dernière (et donc les fonctions qu'elle met à disposition) est chargé dans l'espace mémoire du processus visé, dès son lancement.
L'axe de fonctionnement principal de cette technologie repose sur la réécriture partielle de ces API avec pour objectif la redirection, tel qu' expliqué dans la section précédente. La méthode la plus simple pour atteindre ce but est donc l'écriture d'un saut inconditionnel vers la fonction de remplacement, en écrasant les premiers octets de code de la fonction initiale. On
pourra au préalable va bien sur sauvegarder ces octets au cas ou nous souhaitons rappeler cette dernière après la redirection du flux.
4. Un peu de vocabulaire
Voici les termes utilisés nommant chacune des parties impliquées par la mise en place du dispositif :
Trampoline : Les premiers octets de l'API visée mis à l'abris pour rappel ultérieur, auxquel nous concaténons un saut vers la suite du code de celle-ci.
Detour : Le saut inconditionnel que nous écrivons pour rediriger le flux d'exécution vers notre fonction de remplacement.
5. Implémentation technique
Nous souhaitons donc écrire un saut vers notre fonction. La taille d'un saut est de 5 octets :
jmp [Address] ;jmp = 1 octet - [Address] = 4 octets codés donc sur 32-bits
Nous écraserons donc 5 octets. Le problème essentiel se posant lors de l'implémentation du crochetage, c'est la taille des premiers octets de l'API visée, ceux que nous allons sauvegarder dans le trampoline, si nous souhaitons la rappeler. Nous ne pouvons en effet pas garantir que notre saut ne coupera pas d'instruction en deux. Imaginez le massacre lors de l'execution du trampoline...
Illustration par l'exemple (complètement factice d'ailleurs) :
;imaginons une API commençant par le code suivant
mov edx, eax ;2 octets
mov eax, ebx ;2 octets
call 00403108 ;5 octets
;Si on réécrit 5 octets on coupe le call en deux, le code sera corrompu et il y a 99.99% de chance pour que l'exécutable plante lamentablement :)
Rassurez-vous, pour la majorité des API, le problème ne se posera pas. En effet celles-ci commencent quasiment toute une parcelle d'instructions connue sous le nom de
prologue dont voici un exemple:
mov edi, edi ; 2 octets
push ebp ; 1 octet
mov ebp, esp ; 2 octets
;pile poil 5 octets donc aucun problème pour les sauver et les écraser par un saut :)
Pour celles qui ne correspondent pas à cette règle, nous devrons calculer la taille des intructions avant de réécrire (LDE) - Ce n'est pas le sujet du papier, c'est assez complexe et nécéssite une parfaite connaissance de la composition des instructions, je vous renvoie vers les manuels INTEL.
Deuxième contrainte, les permissions ; une section de code est rarement "writeable", c'est un fait. C'est particulièrement vrai pour l'espace mémoire sur lequel nous essayons d'écrire et fondamentalement, une API c'est du code. Pour cela il existe une fonction parfaitement documentée : VirtualProtect() qui nous permet de changer les permissions d'une page mémoire. Vous trouverez les informations nécéssaires à sont utilisation sur MSDN ainsi que dans l'exemple suivant.
6. Exemple !
Voici un exemple de crochetage utilisant la méthode inline hook. Nous affichons une simple messagebox. Nous crochetons ensuite l'API correspondante pour modifier le message à la volée, puis nous rappelons la fonction pour constater.
Il est codé en asm (FASM). Il est sévèrement commenté pour permettre au plus grand nombre de comprendre tant qu'on a lu les quelques lignes d'explications ci-dessus.
Bien évidemment dans des cas réels d'utilisation, il faudrait que le mecanisme soit implémenté dans un process distant. Crocheter ses propres appels n'a pas grand intérêt, mais l'objectif n'est pas de montrer comment développer un rootkit.
PS : avis aux connaisseurs, ne faites pas attention aux permissions sur les sections, il n'y a pas de error handler non plus ...format PE GUI 4.0
entry start
include "WIN32AX.inc"
include 'KERNEL32.INC'
include 'USER32.INC'
;section code
section '.text' code readable executable writeable
proc start
call Popup ;appel initial
call InstallHook ;happy (api?) hooking :-)
call Popup ;on test le crochetage
ret
endp
;affiche un simple msgbox
proc Popup
push 0
push szCaption
push szOriginalMessage
push 0
call [MessageBox]
ret
endp
proc InstallHook
call WriteTrampoline ;on écrit le trampoline
push 28 ;on récupere les information de la page mémoire
push mbi
push [MessageBox]
call [VirtualQuery]
push mbi ;on change un peu le mode de protection sinon on ne pourra pas écrire à l'adresse voulue (API)
push 040h
push 10
push [MessageBox]
call [VirtualProtect]
call WriteDetour ;on écrit le détour
ret ;normalement avant il faudrait quand même rétablir la protection avant de sortir, c'est pas propre !
endp
proc WriteTrampoline
mov esi, [MessageBox] ;source
mov edi, trampoline ;destination
mov ecx, 5 ;taille du prologue
rep movsb ;écriture
mov dword[edi], 0E9h ;on écrit un jmp
inc edi ;on incremente pour écrire l'adresse de notre adresse de saut juste apres le detour
mov dword[edi],esi ;on ecrit l'adresse absolue qui suit le prologue
sub dword[edi],trampoline ;calcul de l'adresse relative
sub dword[edi],0Ah
ret
endp
proc WriteDetour
mov edi, [MessageBox] ;destination
mov dword[edi], 0E9h ;on écrit un jmp
mov eax, MyMessageBox ;on pointe sur la nouvelle fonction
sub eax, edi ;calcul de l'adresse relative
sub eax, 5
inc edi ;le jmp c'est fait, on veut juste ajouter l'adresse alors on enleve sa taille (au jmp)
stosd ;ecriture
ret
endp
;Fonction de remplacement
proc MyMessageBox
lea ebx,[esp + 8h] ;direction la pile, sur l'adresse contenant le pointeur sur le message
Mov dword[ebx], szBetterMessage ;on la change (l'adresse !)
Jmp trampoline ;on exécute le code du trampoline
endp
;notre backup du prologue suivi d'un saut vers la suite de l'API
trampoline rb 10
;section data
section '.data' data readable writeable
mbi rb 28
szCaption db "Hello !",0
szOriginalMessage db "Hello World",0
szBetterMessage db "Hello NewbieContest World",0
;imports
section '.idata' import data readable writeable
library kernel32, "kernel32.dll", user32, "user32.dll"
7. Connaître son ennemi
L'inline hook est une méthode qui nécéssite d'être couplée à un vecteur d'infection lorsqu'elle est utilisée pour le developpement d'un rootkit. Il faut bien évidemment crocheter les API ciblées dans l'ensemble des processus du système pour cacher les éléments dont la detection mettrait en évidence un acte de piratage. En général, ce vecteur est une injection de code ou de DLL contenant le rootkit en question. Il convient donc se se prémunir en amont à l'aide de logiciels spécifiquement conçus dans l'objectif de protéger la mémoire du système. Process Guard en est l'exemple, il est d'ailleurs particulièrement efficace s'il est correctement configuré. Les antivirus (performance mise à part) vont également (en général) vérifier les comportements de ce type à l'aide d'analyse heuristique. Les firewall personnels (comme BlackIce ou ZoneAlarm) fournissent quant à eux une protection en temps réel en crochetant (et oui) les API (userland ou kernel) servant ce but, et offre ainsi une protection relativement efficace.
Il est également possible de constater un inline hook manuellement, vous chercherez Ice Sword (logiciel gratuit) qui est spécifiquement developpé pour repérer un processus ayant subit l'attaque. RK Unhooker et d'autres programmes anti-rootkit permettent aussi de detecter un rootkit. Le reste du travail : la rétro-ingénierie des fonctions de remplacement mais c'est un autre sujet.
Ceci dit, si vous n'avez aucune protection et que ces utilitaires vous donne un résultat positif, vos données vous ont probablement déja été dérobées ...
8. Quelques liens
Et pour finir, quelques liens vers des documents permettant d'approfondir le sujet :