Je me décide finalement à publier un petit quelque chose, malgré tous le taff que les études peuvent me demander en ce moment :).
Entrons dans le vif du sujet, "phook" est le nom donné à un PoC réalisé par Shearer and Dreg concernant une technique de hook peu commune, je dirais même nouvelle.
Un paper à d'ailleurs été publié dans la dernière issues de phrack (65).
Après sa lecture, la mise en place de la technique reste assez floue, je decide donc de partager mon PoC (même pas complet !) ; celui-ci m'a permis de me rendre compte qu'au final la technique est loin d'être parfaite et imprenable.
Pour situer le sujet, les auteurs nous propose de combiner de "l'iat patching", avec leur nouvel technique donc, pour nous assurer un total contrôle des apis.
La nouveauté dans tout cela, c'est qu'avec une simple modification de la table des importations d'un binaire, nous allons contrôler seulement les appels "statiques", ceux qui ne seront pas résolus dynamiquement.
En effet, si dans le cadre d'un hook d'iat nous retrouvons nos fonctions dynamiquement en utilisant GetModuleHandle et GetProcAddress et bien la technique devient inutile.
Nos deux compères nous proposent alors un périple dans le PEB, pour être plus précis, ils nous proposent de falsifier les champs DllBase, EntryPoint et SizeOfImage des structures LDR_DATA_TABLE_ENTRY pointé par chaque entrée de la liste doublement chainée PEB.Ldr.InLoadOrderModuleList.
Voici sa structure :
lkd> dt nt!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY
+0x008 InMemoryOrderLinks : _LIST_ENTRY
+0x010 InInitializationOrderLinks : _LIST_ENTRY
+0x018 DllBase : Ptr32 Void
+0x01c EntryPoint : Ptr32 Void
+0x020 SizeOfImage : Uint4B
+0x024 FullDllName : _UNICODE_STRING
+0x02c BaseDllName : _UNICODE_STRING
+0x034 Flags : Uint4B
+0x038 LoadCount : Uint2B
+0x03a TlsIndex : Uint2B
+0x03c HashLinks : _LIST_ENTRY
+0x03c SectionPointer : Ptr32 Void
+0x040 CheckSum : Uint4B
+0x044 TimeDateStamp : Uint4B
+0x044 LoadedImports : Ptr32 Void
+0x048 EntryPointActivationContext : Ptr32 Void
+0x04c PatchInformation : Ptr32 Void
Pour rappel, cette liste stocke les modules chargés par un binaire dans l'ordre de chargement en mémoire, on est donc censé trouvé en premier lieu ntdll.dll bien sur suivit de kernel32.dll etc :
kd> !process 0 0 calc.exe
PROCESS 814ad740 SessionId: 0 Cid: 01a4 Peb: 7ffdb000 ParentCid: 05bc
DirBase: 0d88d000 ObjectTable: e15ccbc0 HandleCount: 37.
Image: calc.exe
kd> .process 814ad740
Implicit process is now 814ad740
WARNING: .cache forcedecodeuser is not enabled
kd> !peb
PEB at 7ffdb000
InheritedAddressSpace: No
ReadImageFileExecOptions: No
BeingDebugged: No
ImageBaseAddress: 01000000
Ldr 001a1e90
Ldr.Initialized: Yes
Ldr.InInitializationOrderModuleList: 001a1f28 . 001a2d18
Ldr.InLoadOrderModuleList: 001a1ec0 . 001a2d08
Ldr.InMemoryOrderModuleList: 001a1ec8 . 001a2d10
Base TimeStamp Module
1000000 3b7d8410 Aug 17 22:52:32 2001 C:\WINDOWS\system32\calc.exe
7c910000 4125331a Aug 20 01:09:14 2004 C:\WINDOWS\system32\ntdll.dll
7c800000 4623a01c Apr 16 18:11:08 2007 C:\WINDOWS\system32\kernel32.dll
7c9d0000 4125330f Aug 20 01:09:03 2004 C:\WINDOWS\system32\SHELL32.dll
77be0000 45d97cd4 Feb 19 11:32:52 2007 C:\WINDOWS\system32\msvcrt.dll
77ef0000 45f030c5 Mar 08 16:50:29 2007 C:\WINDOWS\system32\GDI32.dll
7e390000 45f030c6 Mar 08 16:50:30 2007 C:\WINDOWS\system32\USER32.dll
77da0000 412532e8 Aug 20 01:08:24 2004 C:\WINDOWS\system32\ADVAPI32.dll
[...]
Pour comprendre le principe même de la technique, il faut savoir que l'api GetModuleHandle par exemple, va parser cette liste afin de récupérer l'image base d'une dll par exemple.
Imaginer que l'on echange le champs DllBase de la dll kernel32.dll, avec une dll factice ; et bien un GetModuleHandle("kernel32.dll") nous renverrais l'image base de notre dll factice !
C'est en fait le coeur de la technique, car celle-ci nécessite quelques pré-requis pour sa mise en place.
Notre dll devra donc répondre à quelques exigences, en effet elle devra exporter le même nombre de fonctions que la dll original, avec les mêmes noms de fonctions, ainsi que les mêmes ordinaux.
Le premier problème auquel on est confronté, c'est alors de créer une dll qui exporte les mêmes noms de fonctions que kernel32.dll par exemple, en effet si l'on veut créer une exportation pour GetModuleHandle (par exemple), et bien, bien sur le compilateur nous crie dessus étant donné que cette fonction est déjà définie/déclarée dans les headers windows.
Nos amis utilisent alors des fichiers de définitions (.def) à lier au binaire lors de l'édition des liens (avec mingw c'est l'option --def, et avec vc c'est /DEF:).
Par exemple, pour exporter GetModuleHandleA, nous définirons/déclarerons la fonction comme suit :
DLLEXPORT void GetModuleHandleA_()
{
//do the stuff
}
Et notre fichier de définitions sera comme cela :
LIBRARY kernel32
EXPORTS
GetModuleHandleA=GetModuleHandleA_ @ 29
Ou 29 est l'ordinal d'exportation !
Vous devez commencer à comprendre que notre dll, va être ni plus ni moins qu'une espèce de proxy entre le binaire, et la véritable dll ; cependant si l'appel nous intéresse nous le ré-définirons à notre guise, et nous appellerons l'api originel, à la manière d'un hook donc.
Le second problème, est alors de retrouver l'adresse de cette api originel, pour cela nous utiliserons une macro qui grosso-modo réalisera :
GetProcAddress(GetModuleHandleA("kernel32_.dll"), "NotreApi")
Ou kernel32_.dll est le nom de NOTRE dll, car si vous reflechissez 30 secondes, une fois que le PEB hook aura été réalisé, et bien les images bases auront été echangées ; or il nous faut récupérer l'image base de la véritable dll, ce qui sera donc obtenu avec GetModuleHandleA("Kernel32_.dll") .
La macro proposée par les auteurs est la suivante :
unsigned long tmp;
#define JMP( lib, func ) \
__asm \
( "pushad \n" \
" push edx \n" \
" push %1 \n" \
" call eax \n" \
" pop edx \n" \
" push %2 \n" \
" push eax \n" \
" call edx \n" \
" mov %4, eax \n" \
" popad \n" \
\
: : \
"a" (GetModuleHandle) , \
"g" (lib) , \
"g" (func) , \
"d" (GetProcAddress) , \
"g" (tmp) \
); \
asm ( "jmp %0" : : "g" (tmp) );
Je vous l'accorde ça pique les yeux, c'est très moche, mais ça compile avec mingw (avec l'option de compilation -masm=intel) c'est pour cela que je n'ai pas transposé cette macro à coup de _asm compilable seulement avec vcpp.
La macro réalise donc une sauvegarde des registres avec un pushad/popad afin de préserver l'environnement dont nécessite l'api, nous stockons l'adresse de la véritable api dans une variable, et enfin nous sautons sur l'api.
Cette dernière étape soulève un problème un peu plus important, celui-ci se situe au niveau de la pile et donc des arguments.
Il faut être, dans ce code, capable de fournir une pile propre, et utilisable par l'api et cela sans passer par un prototype spécifique ; en effet toutes les fonctions qui nous intéresserons pas, leurs prototypes seront :
void MaFonction_();
En tant normal, une tel fonction n'est pas censé recevoir de paramètre, cependant une option nous permet de réaliser cela, et donc de trouver une solution à notre problème : il faut compiler avec l'option -fomit-frame-pointer.
C'est en fait, le problème le plus délicat à résoudre dans ce projet, je ne sais pas si il est d'ailleurs possible de procéder autrement, j'ai simplement suivie la technique proposé par nos hookeurs de peb.
J'ai d'ailleurs tenté de compiler le code sans spécifiée l'option, et il semblerait que le tout fonctionne toujours..si quelqu'un peut m'éclairer à ce niveau là :].
Bon, une fois réalisé tout cela, nous obtenons un contrôle sur la résolution dynamique des apis ; si nous voulons obtenir un contrôle total, il faut mettre en place une reconstruction de la table d'importation du binaire, afin de faire pointer les importations de kernel32.dll vers NOTRE dll.
Et enfin, il faut aussi reconstruire les tables des importations des autres modules que notre binaire utilisent, à condition que ceux ci importent des fonctions de kernel32.dll ; tous cela dans le but de les rediriger vers NOTRE dll ! Et dans ce cas là, nous avons le contrôle sur tous les appels fait sur les apis de kernel32.dll.
Maintenant, il reste deux/trois détails à résoudre, en effet si l'on veut pouvoir intercepter tous les appels de fonctions en destination de kernel32.dll, nous devons créer le processus dans un état suspendu, à ce moment là on peut enfiler notre casquette "trappeur de PEB" et corrompre les différents champs.
C'est pour moi le point faible de la technique, en effet il faut pouvoir créer le processus pour pouvoir mettre en place correctement l'attaque.
A présent parlons implémentation, dreg & shearer ont choisis d'injecter un code qui va charger la dll (celle qui va mettre en place le peb hook et la reconstructions des imports), leurs code est plutôt original, il injecte en mémoire une structure formé des opcodes nécessaire au chargement de la dll ; les opcodes sont forgés à la volée : funny :)).
Pour ma part, j'ai tout simplement choisis de créer un thread distant sur la fonction LoadLibraryA afin de chargé ma dll.
Celle-ci se chargera de mettre en place le peb hook, et la reconstructions des importations du binaire, par manque d'envie je n'ai pas codé la reconstructions successive des IATs des différents modules chargés par le binaire.
Concernant leurs PoC, après l'avoir testé il semble défectueux chez moi.
En effet, au lieu d'injecter une dll réalisant le peb hook et le patch des imports, ils ont décidés d'interfacer la mise en place de l'attaque par le biais d'une espece de console.
Vous êtes alors contraint de vous connectez à une socket, on vous propose alors un menu (n'utilisez pas putty :p) ..enfin bon je trouve que le poc est entouré de fonction qui n'apporte pas de réel plus value au projet !
Dans la suite d'outil qu'ils nous proposent on trouve, bien sur,un générateur de code, celui-ci est capable de nous générer le code d'une dll avec les mêmes exportations etc ; celui-ci étant codé en C, j'ai décidé de me coder un petit script python (mon premier !:]]) avec le module pefile.
Cela m'a permis d'appréhender python pour finalement lacher le perl !
Je pense en effet qu'il est plus judicieux d'utilisé un language de script pour réaliser une tache de ce genre !
En lançant mon implémentation, si tout ce passe correctement, vous devriez obtenir un dump du genre :
[3876] [ PebPwnDll implémentation par 0vercl0k, idée original de Shearer & Dreg ].
[3876] [DBG] RtlGetCurrentPeb : 0x7c970e89.
[3876] [DBG] PEB en : 0x7ffdf000.
[3876] [DBG] PEB_LDR_DATA en : 0x361e90.
[3876] [DBG] LDR_DATA_TABLE_ENTRY : 361ec0.
[3876] [DBG] LDR_DATA_TABLE_ENTRY : 361f18.
[3876] [DBG] LDR_DATA_TABLE_ENTRY : 361fc0.
[3876] [DBG] LDR_DATA_TABLE_ENTRY : 362060.
[3876] [DBG] LDR_DATA_TABLE_ENTRY : 362100.
[3876] [DBG] LDR_DATA_TABLE_ENTRY : 3622d0.
[3876] [DBG] LDR_DATA_TABLE_ENTRY : 362328.
[3876] [DBG] LDR_DATA_TABLE_ENTRY : 3623c8.
[3876]
[3876] [DBG] LDR_DATA_ENTRY_TABLE de la dll hook : 0x3622d0.
[3876] ImageBase : 0x623c0000
[3876] EntryPoint : 0x623c1000
[3876] SizeOfImage : 0x9f000
[3876]
[3876] [DBG] LDR_DATA_ENTRY_TABLE de la dll hooké : 0x361fc0.
[3876] ImageBase : 0x7c800000
[3876] EntryPoint : 0x7c80b5be
[3876] SizeOfImage : 0x105000
[3876]
[3876] [DBG] Kernel32.dll en 0x623c0000.
[3876] [DBG] Eat dumped !
[3876] [DBG] Patch de l'iat du binaire..
[3876] [DBG] Patch de l'iat du binaire, concernant la dll 'kernel32.dll'.
[3876] [DBG] Patch du binaire terminé.
[3876] [DBG] Attaque complète.
Pour tester la mise en place de la technique, j'ai tout simplement rajouter une MessageBox lors d'un appel à l'api Beep().
Si vous voulez tracer le binaire pour constater le fonctionnement, j'ai glissé une int 3, il vous suffit donc de définir OllyDbg comme JIT debugger.
Maintenant, on peut s'interroger si la technique est utilisable dans le cadre d'un malware..j'aurais tendance à dire oui bien sur, seulement un peb hook reste très simple à détecter : il suffit de constater les champs DllBase/EntryPoint/SizeOfImage en mémoire, et ceux du binaire en dur. On se rend vite compte que sa mise en place est aussi contraignante, car beaucoup d'actions sont réalisées, et que plus il y a d'actions à réalisées, plus il y a de chances pour que l'attaque tombe à l'eau (si l'une d'entres elles echoue, c'est la technique entière qui se voit tomber en miettes).
Cependant, une technique de ce type peut-être intéressante pour virtualiser certaines apis (les auteurs en parlent en fin de leur article) ou encore pour réaliser un traceur d'api ; c'est peut-être un peu lourd je vous l'accorde.
Enfin bref, c'est vraiment un article que j'ai A.D.O.R.E, jouer comme cela avec des structures accessible en userland c'est vraiment des techniques que je kiffe :).
Il est temps de lacher les sources, pour cette fois je vous fait une archive contenant source et binaire :
UnderstandPhooksInternals.zip
Sinan voici quelques liens en vrac :
Voilà, j'en ai finis pour aujourd'hui, en espérant que ça vous aura plu ;).