vendredi 8 août 2008

How to pwn lilxam's toolz :).

Un titre un peu provacateur, mais rien de méchant, juste un petit jeu entre lilxam et moi ; autrement dit mon article n'est pas là pour dénigrer son travail ou autre bien au contraire.
Les avertissements étant fait, rentrons dans le vif du sujet .. :).

Il y a quelques temps, j'ai entamé l'écriture d'un petit post concernant une technique de DKOM aboutissant à caché un processus (plus ou moins bien justement).
Le noyau possède un chainage de structures de type EPROCESS (par une liste doublement chainé à l'offset 0x088), il était donc possible d'unlinker une structure en particulier.
Le processus se retrouvait alors protégé de l'énumération par le taskmgr.exe ou encore ProcessExplorer.
C'est à présent que lilxam entre en jeu.

Peu de temps après mon petit Proof of Concept, il met en place une technique userland permettant d'énumérer les processus malgré la modification des structures en mémoire.
Cet technique en question, très ingénieuse au passage, permettant de récupérer le nom des processus en bruteforçant les PIDs de ceux-ci.
Seulement, tout cela m'interpelle, comment était-il possible de retrouver ce processus, alors qu'il n'était plus présent dans les structures EPROCESS ..?

La réponse ne devrait pas être trop complexe à rechercher ; en effet un commentaire de Ivanlef0u nous donne de précieuses informations : il aborde la présence d'une mystérieuse table prénommé PspCidTable référençant des informations concernant les processus à savoir une translation entres PIDs/TIDs et objet EPROCESS/ETHREAD.

Nous voilà sur la bonne route, sortons IDA afin d'analyser les apis utilisées dans le code de lilxam ; le réel problème étant de savoir comment le système se débrouille pour trouver le processus en question, désassemblons un peu OpenProcess exporté par kernel32.dll.
Nous suivons en premier lieu les appels successif à savoir:

->OpenProcess
-->NtOpenProcess
--->ZwOpenProcess

Nous savons bien que le système afin de permettre l'appel d'un syscall va passer par la SSDT pour retrouver l'adresse de la fonction associé à son numéro de syscall (0x7a pour NtOpenProcess).
Balayons alors ce code à la recherche de la translation PID/TID en objet EPROCESS/ETHREAD :

PAGE:004A9C92 cmp [ebp+var_1A], 0
PAGE:004A9C96 jnz loc_52ADDF
PAGE:004A9C9C cmp [ebp+var_19], 0
PAGE:004A9CA0 jz loc_50F76D
PAGE:004A9CA6 mov [ebp+var_30], esi
PAGE:004A9CA9 cmp [ebp+var_28], esi
PAGE:004A9CAC jnz loc_4D88A0
PAGE:004A9CB2 lea eax, [ebp+var_24]
PAGE:004A9CB5 push eax
PAGE:004A9CB6 push [ebp+var_2C]
PAGE:004A9CB9 call _PsLookupProcessByProcessId@8

L'appel à PsLookupProcessByProcessId est plus que révélateur, son nom en dit assez large sur son rôle et de plus cette fonction est renseigné par la msdn.
Désassemblons cette dernière fonction, notre réponse y est surement caché :).


PAGE:004A9B25 mov edi, edi
PAGE:004A9B27 push ebp
PAGE:004A9B28 mov ebp, esp
PAGE:004A9B2A push ebx
PAGE:004A9B2B push esi
PAGE:004A9B2C mov eax, large fs:124h
PAGE:004A9B32 push [ebp+arg_0]
PAGE:004A9B35 mov esi, eax
PAGE:004A9B37 dec dword ptr [esi+0D4h]
PAGE:004A9B3D push _PspCidTable
PAGE:004A9B43 call _ExMapHandleToPointer@8
PAGE:004A9B48 mov ebx, eax
PAGE:004A9B4A test ebx, ebx


Mais que voyons nous ; notre"PspCidTable".
Arrivé ici nous comprenons alors que les apis qu'utilise notre très cher lilxam ; en particulier OpenProcess se base donc sur cette fameuse table pour retrouver notre processus à partir de son PID.
Sans plus attendre je vous recommande (encore) un article de mon noble jedi ; celui-ci traite de long en large les tables de type HANDLE_TABLE au prototype suivant :


lkd> dt nt!_HANDLE_TABLE
+0x000 TableCode : Uint4B
+0x004 QuotaProcess : Ptr32 _EPROCESS
+0x008 UniqueProcessId : Ptr32 Void
+0x00c HandleTableLock : [4] _EX_PUSH_LOCK
+0x01c HandleTableList : _LIST_ENTRY
+0x024 HandleContentionEvent : _EX_PUSH_LOCK
+0x028 DebugInfo : Ptr32 _HANDLE_TRACE_DEBUG_INFO
+0x02c ExtraInfoPages : Int4B
+0x030 FirstFree : Uint4B
+0x034 LastFree : Uint4B
+0x038 NextHandleNeedingPool : Uint4B
+0x03c HandleCount : Int4B
+0x040 Flags : Uint4B
+0x040 StrictFIFO : Pos 0, 1 Bit


Ce type de table est d'ailleurs presque omni-présent dans les processus ; les handles que gèrent un processus sont stockés dans une table de ce genre.
Celle-ci est accessible à l'offset 0x0c4 :


lkd> dt nt!_EPROCESS
[...]
+0x0c4 ObjectTable : Ptr32 _HANDLE_TABLE


A présent, parlons PspCidTable.
En glanant sur le net, on s'aperçoit assez rapidement qu'il y a quelques années la modification
de cet table était une technique à la pointe dirais-je.
Cependant, je me suis vite rendu compte que mes parents ont entrepris ma construction trop tard :p ; en effet mon implémentation ce chargeait d'enlever l'objet EPROCESS du processus à caché, mais malgrès cela RkUnhooker, anti-rootkit digne de ce nom, trouve toujours le moyen de retrouver l'objet EPROCESS, et donc de permettre le kill de celui-ci :/.
Un peu frustré, je commence à m'interroger sur la technique que RkUnhooker pourrait déployer afin de retrouver ce maudit processus.
En analysant la définition de la structure ETHREAD on peut remarquer qu'il existe un lien avec la structure EPROCESS associé, à l'offset 0x220:

lkd> dt nt!_ETHREAD
[...]
+0x220 ThreadsProcess : Ptr32 _EPROCESS


; on ne peut que se demander
si RkUnhooker ce focalise sur les threads, et remonte au processus par le biais de divers fonctions.
Me voilà repartit dans cette table, afin d'enlever les objets ETHREADs .. après l'implémentation, à ma grande surprise le processus devient inactif ; zombie...

C'est donc à ce moment là que les questions jaillissent à travers mon esprit :

- Comment RkUnhooker remet la main sur mon processus ?
- Quel(s) technique(s) utilise t-il pour cela ?
- Existe t-il un autre endroit où une liste des processus lancées sur le système est disponible ?

Voilà concernant la PspCidTable, ma fonction permet alors de supprimer l'entrée concernant le processus à caché dans la table ; l'outil de lilxam est donc finalement inefficace sur ce coup là .:p.



Comme je le racontais plus haut, cette implémentation est trop maigre pour permettre de leurrer RkUnhooker.
C'est donc ainsi que commence mes recherches sur les techniques existantes permettant la protection d'un processus de l'énumération. Après quelques recherches sur la toile, mon regard s'attarde sur cette technique ; en effet le processus vital csrss.exe contiendrait une liste des processus lancée sur le système.
L'auteur de la technique explique très bien les faits et son analyse est détaillée ; en quelques mots, le processus tiens à jour une liste des processus lancés sur le système sous forme de liste chainée.Il est alors aisé pour nous de pratiquer un unlink ..

Pour un projet personnel, j'ai décidé de porter cette unlink en ring0 ; celle-ci devient alors tout de suite bien plus intéréssante car on est obligé de faire appel à pleins de petites choses pour arrivé à nos fin : du parcours de PE, de l'attachement à l'user-space d'un processus et j'en passe.

Les liens :
-PspCidTable PoC
-UnlinkListEntryInCsrss

Sinan si vous ne savez pas quoi faire, je vous conseil d'aller faire un tour sur le site de la F.A.T.
Cette petite team aux membres bien sympathiques publient des codes et papers vraiment géniaux ( je pense au dernier que j'ai lu ; autrement dit le paper concernant win32.leon par kaze ) ..enfin bref à bookmarker pour ceux qui ne connaissent pas :).

Et si vous en voulez encore un peu, je vous conseil de lire les derniers articles de deux de mes amis :

-Celui de rAsM ; monsieur met en place une technique pouvant servir de base à un potentiel keylogger kernel (a condition de patcher les BSODs :))).

-Celui de Ivanlef0u ; mon s1ths préféré propose un petit article concernant le subsystem csrss en abordant notament les processus consoles.

Pour clore ce petit post, un dernier lien ; celui-ci vient tout droit des dragons asiatiques ..Sudami reverseur de talent continus de nous faire réver en nous proposant de nouvelles techniques de DKOMs ; celle-ci aborde le hook d'une fonction stockée dans la structure liée aux Objects Types Initializers ( ..qui m'avait mis en échec ici ).
Bien evidement, raffolant de ce genre de technique je m'empresse de mettre en place un petit driver capable de placer ce hook mais c'est (du moins chez moi (XP SP2 FR)) un échec.. la routine est apparement bien appelé par le system mais en aucun cas elle empèche la fermeture du processus.
Peut-être que sudami explique les limites de sa technique dans son article, ou que celle-ci est valable avec des pré-requis.. Enfin bon si quelqu'un en sait plus qu'il me contacte :).

Voilà pour ce petit article, en espérant que je vous aurrez appris quelque(s) chose(s),
bonne journée à vous.
PS : Bonne rentrée à ceux qui ne l'ont toujours pas faite.

16 commentaires:

Anonyme a dit…

Salut, à la visite de ton blog ça m'a fait sourire en regardant le titre du dernier article.. en effet j'avais posté un commentaire sur le blog de lilxam y'a quelques mois pour l'article qui retrouvait les process cachés par les unlink eprocess.. je vois que t'en ai arrivé aux memes conclusions que les miennes.. en effet on passe par PsLookupProcessById.. donc pwned.

Concernant peut-être les méthodes qu'utilise RkUnhooker pour retrouver ces process cachés, tu peux aussi voir du côté des OBJECT_TABLE (table handle dans chaque EPROCESS), qui sont linkés (simple) entre elles, donc théoriquement, si les passes toutes, tu peux retrouver les process, à part si encore une fois tu unlink.. à tester (je te laisse le tester moi j'ai la flemme ;-)

Et finalement concernant CrsWalker j'avais en effet vu ça y'a quelques temps, intéressant que tu l'aies implémenté ! ce qui faudrait faire c'est un mix de toutes ces techniques pour planquer un process.

bonne continuation, moi je vais devoir faire une pause dans le reverse (études obligents), je sais que tu rentres en iut info, cartonne pour finir major !

A+

0vercl0k a dit…

Tout d'abord merci Taron pour ce commentaire ça fait toujours plaisir :).

Ensuite, oui justement avec lilxam on essait de mettre en place plusieurs techniques pouvant être potentiellement utilisées par RkUnhooker pour remonter aux processus.

Donc j'ai pour l'instant 5/6 techniques combinées..mais RkUnhooker reste toujours vainqueur.
Peut être pas/plus pour longtemps ?:D
Si c'est le cas je m'empresserais d'écrire un petit post avec une description des techniques utilisées.

Et oui la rentrée approche à grand pas (Lundi), et en effet je vais tenter d'être dans la "tête" de classe..merci pour tes encouragements pis fais moi signe dès ton retour (sur irc par exemple).
Bonne continuation Taron et merci !
Cordialement, 0vercl0k.

lilxam a dit…

Salut à vous deux,
tout d'abord bien joué pour cet article et ces deux techniques qui sont pas facile à mettre en place donc félicitation pour les codes :)

Taron ouai j'avais vu ton commentaire sur mon blog c'est pour ça que j'ai voulu me repencher sur le sujet. Et en effet on peut lister les processus avec cette techniques (cf mon nouvel article), c'est ce que j'avais prévu en réponse à cet article :p.

Sinon aussi j'ai entendu parlé d'une technique avec la table KiWaitListHead mais comme dis dans mon article je n'arrive pas à trier les infos qu'elle contient.

Donc voilà il reste encore pas mal de choses à éclaircir.

A quand le hide de driver :p ?

Bonne continuation à tous les deux.
Lilxam.

PS : En me baladant sur rootkit.com je viens de trouver une traduction d'un paper sur le sujet, ici. Cependant tout n'est pas très clair...

Anonyme a dit…

Salut à tous !
J'aimerais réagir sur la technique relative à crswalker. Je voudrais implémenter le tout en user land, et je me pose plusieurs questions :
- Lorsqu'on déclare une variable dans une DLL, celle-ci n'est pas globale ? Est-ce qu'on ne pourrait pas tout simplement charger la DLL csrss.dll en mémoire dans le processus courant afin de retrouver CsrRootProcess ? Ou serait-ce un CsrRootProcess différent de celui maintenu par csrss.exe ?
- Dans son executable, l'auteur du post sur sysinternals appelle les fonctions ZwXxX (par exemple ZwOpenProcess ou ZwAllocateVirtualMemory) pour dialoguer avec csrss.exe . Quel avantage par rapport aux fonctions "classiques", comme OpenProcess ou VirtualAlloc ?
- Enfin, comment faire cela sans le SeDebugPrivilege ? Pour moi, ce token est nécessaire rien que pour OpenProcess... Pourtant, l'executable de sysinternals fonctionne même sans ce token, seul un message d'avertissement apparaît disant que ce n'est pas grave... pourquoi chercher à l'obtenir si ce n'est pas utile ?

Merci d'avance !

0vercl0k a dit…

Salut à toi "Shadow",
je m'excuse tout d'abord pour le retard ; en semaine je suis loin de mon "chez moi" et donc je n'ai (pour l'instant?) pas de connexion à ma disposition, bref.

Alors tout d'abord, d'après ce que j'ai compris de la technique, la liste se trouverait dans la mémoire du processus en question et non dans la dll ; en effet sinan j'aurais fais un tout simple LoadLibrary ; une autre technique serait d'injecter une dll dans le processus si tu préfères, à toi de voir.

Ensuite, concernant les appels des fonctions ZwXXX, il n'y a aucun "avantage" dans ce cas-ci à les utiliser en userland ; les apis tels que OpenProcess, WriteProcessMemory te faciliteront bien plus la tâche que d'appeler directement les apis natifs.

Concernant le changement de token, il est chez moi obligatoire ; autrement mon programme ne marche pas.
Pour moi le token est nécessaire pour lire/écrire des données dans le subsystem csrss.

Et finalement si tu ne parviens pas à implémenter la fonction en userland n'hésite pas à me contacter par irc par exemple ; je pourrais te montrer mon - crade - portage de la fonction en ring3 :).

Bonne soirée à toi, et encore merci.
Cordialement 0vercl0k.

Anonyme a dit…

Merci beaucoup !
J'ai avancé et j'ai encore 3 questions ^^ :
- A quoi sert le "self pointer" du TEB, puisque l'adresse du TEB est celle de FS ?
- Où puis-je te trouver sur IRC ?
- J'ai réussi à lire dans csrss.exe l'adresse de csrsrv.dll . Il faut maintenant que je trouve l'adresse de la fonction CsrLockProcessByClientId ! Je sais que je peux le faire comme toi en parcourant l'eat de la DLL, mais je me demandais si je ne pouvais pas utiliser la champs loadedImports de LDR_DATA_TABLE_ENTRY ! Tu sais à quoi il correspond ?

Merci :)

0vercl0k a dit…

Salut "Shadow",
Alors tout d'abord tu peux me retrouver sur IRC sur le channel #carib0u du serveur irc.worldnet.net.

Ensuite concernant le champs "self pointer", j'aurais tendance à dire que j'en sais pas plus que toi, je ne vois pas vraiment son utilité..promis je vais me renseigner et sonder quelques de mes amis.

Concernant ton code j'ai bien peur que tu te casses la tête pour rien.
Je te fais le plan de mon code ci dessous en espérant que ca puisse t'aider :).

1)Je charge ntdll.dll et csrsrv.dll.
2)Je récupère la fonction CsrGetProcessId exporté par ntdll.dll ; celle-ci nous renvoit directement le PID du processus csrss.exe (bien que tu puisses sauter cette étape en retrouvant son PID avec un simple snapshot).
3)Je récupère aussi l'adresse de la fonction CsrLockProcessByClientId exporté par csrsrv.dll (c'est cette fonction qu'on va scanner).
4)J'active le DebugPrivilege ; j'ouvre un handle sur le csrss.
5)Je scan "manuellement" la fonction CsrLockProcessByClientId à la recherche d'une suite d'octet annonçant l'adresse de la liste doublement chainée.
Une fois cette suite trouvé, il nous suffit de lire dans la mémoire du processus son adresse.
6)A présent il nous reste juste a la parcourir ; et cela simplement basé sur les apis [Read/Write]ProcessMemory.

Voilà en gros, le plan que j'ai choisis de suivre.
Mon implémentation pour te donner une idée fais environ 200lignes.

En espérant avoir répondu à tes question, cordialement 0vercl0k.

Anonyme a dit…

Salut à toi!

J'étais en train d'essayer de reimplementer csrwalker en r0 lorsque je suis tombé sur ton code pour unliker. Sympa...
Par contre tu ne bypasse pas csrwalker... il manque la partie unlink sur la liste des threads stockée dans csrss (CsrHashThread). Apparement l'auteur, EP_X0FF, enfin je crois que c'est lui, retrouve des PID en parsant cette liste.

A+

0vercl0k a dit…

Salut à toi "Anonyme",
Tout d'abord merci, ensuite chez moi CsrWalker est belle est bien bypassé ; en lisant son advisory d'ailleurs on comprend qu'il existe aussi un autre moyen ; celui dont auquel tu fais références ; mais il ne l'implente pas dans son outil.

Cordialement, bonne soirée 0vercl0k.

Anonyme a dit…

Salut,

désolé pour le anonyme...
Je ne suis pas sure que nous ayons la même version de cwalker.
Dans la version que j'ai, lorsque je regarde la fonction implémentée à l'adresse 4019F4h, je vois que l'auteur récupère l'adresse de CsrLockThreadByClientId puis scan la fonction pour trouver CsrHashThread.
Une fois CsrHashThread en poche, l'auteur parcours le tableau de 256 LIST_ENTRY qu'il va également parcourir. Chaque élément de ces LIST_ENTRY pointe sur une structure CSR_THREAD... Je ne suis pas aller plus loin dans l'étude de son code mais j'imagine qu'il va ensuite s'interesser à l'élément Process de cette structure.

Dans la version de cwalker que j'ai récupéré sur le forum de sysinternals, si tu unlink uniquement du CSR_PROCESS, tu verra que cwalker trouve ton process en le mettant à la fin de la liste... Ce détail en dit long sur la façon de travailler de cwalker.

Sinon, bon courage pour l'IUT ;-)

0vercl0k a dit…

Salut Orkblutt,
Humm en effet on ne doit posséder les mêmes versions ..!
Comme quoi les technologies/techniques sont toujours en marche :)).
Merci pour tes commentaires, et pour l'iut :).
Bonne soirée cordialement, 0vercl0k.

Anonyme a dit…

:)

Tu peux trouver la même version de CsrWalker sur http://forum.sysinternals.com/forum_posts.asp?TID=15457&KW

Pour bypasser son scan sur les CSR_THREAD tu peux faire quelque chose du style (en me basant sur ton implémentation... sauf que je passe le PID du process à unlinker et pas son nom):

for( i = 0 ; i < (int)pImgExportDirectory->NumberOfFunctions ; i++)
{
if( strcmp("CsrLockThreadByClientId" , (const char *)imgBaseCsrsrv + rvaNameTable[i]) == 0 )
{
CsrLockThreadByClientId = imgBaseCsrsrv + rvaAdressTable[i];
break;
}
}

if( CsrLockThreadByClientId == NULL )
{
KeUnstackDetachProcess( &kApcState );
return 0;
}


for( i = 0 ; i < 50 ; i++ )
{
if( (*(CsrLockThreadByClientId+i) == 0x8D) && (*(CsrLockThreadByClientId+i+2) == 0xC5))
{
CsrThreadHashTable = (PUCHAR)(*(PULONG)(CsrLockThreadByClientId+i+3));
break;
}
}


for(i = 0; i < 256; i++)
{
PLIST_ENTRY ListHead, NextEntry;

ListHead = (PLIST_ENTRY)(CsrThreadHashTable + (8 * i));

NextEntry = ListHead->Flink;

while (NextEntry != ListHead)
{
pCsrHashThread = CONTAINING_RECORD(NextEntry, CSR_THREAD, HashLinks);
if(pCsrHashThread)
{
if((ULONG)pCsrHashThread->Process->ClientId.UniqueProcess == Pid)
{
*(PULONG)(NextEntry->Blink) = (ULONG) NextEntry->Flink;
*(PULONG)((PUCHAR)NextEntry->Flink + 4) = (ULONG)NextEntry->Blink;
}
}
NextEntry = NextEntry->Flink;
}
}

sous Vista, il faut répéter l'opération pour chaque instance de csrss.exe...

Mais bon, comme le dit l'auteur, ce style de détection est un peu inutile... c'est juste pour le sport ;)

A+

0vercl0k a dit…

Héhé super,
je te remercie beaucoup :) bonne soirée à toi !.
Cordialement, 0vercl0k.

Anonyme a dit…

J'ai pris la liberté de poster ton code (un tout petit peu modifé) à la suite du post de Diablo sur sysinternals... Histoire de référencer ton blog dans la cours des "grands" et de pwned notre ami russe ;)

Bon weekend!

0vercl0k a dit…

Salut,
Encore merci pour le post c'est vraiment gentil ; cependant le seul bémol tu as mal orthographié mon pseudo :))) (chieur hein :)), mon pseudo c'est "0vercl0k" et non "0vercl0ck" ou "overclock".
Encore merci et à plus tard j'espere, bon week end à toi, cordialement 0vercl0k.

l33ckma a dit…

Hi, merci pour tes encouragements. J'ai finalement changé d'adresse pour tuxfamily. Tu mettras à jour quand t'auras le temps: http://l33ckma.tuxfamily.org
A très bientot