Sudami KillMe

juillet 15th, 2008 at 06:04 admin

Toujours en quête de nouvelles techniques de rootkit pour Windows, je suis tombé en lisant mes RSS sur un POC chinois réalisé par Sudami qui permet de rendre un process immortel. Intéressé par la chose j’ai voulu prendre le code source mais manque de bol d’après le google translate il faut être inscrit et faire partie d’une communauté underground aux yeux bridés pour y avoir accès. Cependant on peut download le binaire librement. Après quelques heures de reverse je vous propose de plonger au coeur de ce nouveau bébé très innovant qui permet de bien faire chier n’importe quel utilisateur et AV. Le must pour un rootkit :]

Dans le .rar, on ne trouve qu’un simple binaire nommé « sudami.exe » qui fait 67,5 Ko, dès le départ on se doute qu’il va devoir charger un driver dans le noyau au vu du titre du post « DKOM to Protect EXE self,without any hook », DKOM signifiant Direct Kernel Object Manipulation on sait que le programme va opérer au niveau noyau. Le binaire est packé avec PECompact 2.x, pour l’unpack rien de très difficile, on trace le programme de SEH en SEH pour tomber sur celui qui saute sur l’OEP (Original Entry Point). Pour info ce saut est en 0x42BF8A (jmp eax). Une fois qu’on est sur l’OEP on peut tranquillement dumper le binaire avec OllyDump ou LordPE+Imprec pour retrouver le binaire d’origine. L’orignal pèse 180 Ko, un coup d’oeil aux ressources avec LordPe nous permet de voir une ressource nommée « SYS » qui, en regardant le dump hexa, commence avec « MZ » (0x4D 0x5A 0×90), ca nous suffit largement pour dire que cette ressource est un binaire, après extraction ce binaire fait 9 Ko. Le champ Subsystem du PE header du binaire extrait vaut 1, ce qui veut dire que celui-ci est un driver. En regardant le plus près le binaire celui-ci va chargé le driver en utilisant le Service Manager avec les APIs OpenSCManager, OpenService et ControlService puis va spawner une window avec plein de mots chinois qu’on comprend pas dedans. Il va ensuite envoyer une série d’IOCTL au driver (0×80002000, 0×80002008, 0×80002010 et 0×80002018) avec l’API DeviceIoControl pour rendre le process immortel. Le mieux est que vous regardiez par vous même le binaire sous IDA ou sous Olly pour voir son fonctionnement. Bref au final on se retrouve avec une popup en chinois qui reste ouverte sans qu’on puisse la killer. Tout cela n’est pas la partie visible de l’iceberg, le vrai stuff intéressant se trouve bien évidemment dans le driver.

Commence donc l’étude du driver, premièrement je le charge en VM histoire de regarder ce qu’il définit comme MajorFunctions et ses Devices :

0: kd> !drvobj \driver\sudami 3
Driver object (815c87e0) is for:
*** ERROR: Module load completed but symbols could not be loaded for sudami.sys
 \Driver\sudami
Driver Extension List: (id , addr)

Device Object list:
813b1490  

DriverEntry:   f9e08005	sudami
DriverStartIo: 00000000	
DriverUnload:  f9e04250	sudami
AddDevice:     00000000	

Dispatch routines:
[00] IRP_MJ_CREATE                      f9e042a0	sudami+0x12a0
[01] IRP_MJ_CREATE_NAMED_PIPE           804f9709	nt!IopInvalidDeviceRequest
[02] IRP_MJ_CLOSE                       f9e042a0	sudami+0x12a0
[03] IRP_MJ_READ                        804f9709	nt!IopInvalidDeviceRequest
[04] IRP_MJ_WRITE                       804f9709	nt!IopInvalidDeviceRequest
[05] IRP_MJ_QUERY_INFORMATION           804f9709	nt!IopInvalidDeviceRequest
[06] IRP_MJ_SET_INFORMATION             804f9709	nt!IopInvalidDeviceRequest
[07] IRP_MJ_QUERY_EA                    804f9709	nt!IopInvalidDeviceRequest
[08] IRP_MJ_SET_EA                      804f9709	nt!IopInvalidDeviceRequest
[09] IRP_MJ_FLUSH_BUFFERS               804f9709	nt!IopInvalidDeviceRequest
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION    804f9709	nt!IopInvalidDeviceRequest
[0b] IRP_MJ_SET_VOLUME_INFORMATION      804f9709	nt!IopInvalidDeviceRequest
[0c] IRP_MJ_DIRECTORY_CONTROL           804f9709	nt!IopInvalidDeviceRequest
[0d] IRP_MJ_FILE_SYSTEM_CONTROL         804f9709	nt!IopInvalidDeviceRequest
[0e] IRP_MJ_DEVICE_CONTROL              f9e043e0	sudami+0x13e0
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL     804f9709	nt!IopInvalidDeviceRequest
[10] IRP_MJ_SHUTDOWN                    804f9709	nt!IopInvalidDeviceRequest
[11] IRP_MJ_LOCK_CONTROL                804f9709	nt!IopInvalidDeviceRequest
[12] IRP_MJ_CLEANUP                     804f9709	nt!IopInvalidDeviceRequest
[13] IRP_MJ_CREATE_MAILSLOT             804f9709	nt!IopInvalidDeviceRequest
[14] IRP_MJ_QUERY_SECURITY              804f9709	nt!IopInvalidDeviceRequest
[15] IRP_MJ_SET_SECURITY                804f9709	nt!IopInvalidDeviceRequest
[16] IRP_MJ_POWER                       804f9709	nt!IopInvalidDeviceRequest
[17] IRP_MJ_SYSTEM_CONTROL              804f9709	nt!IopInvalidDeviceRequest
[18] IRP_MJ_DEVICE_CHANGE               804f9709	nt!IopInvalidDeviceRequest
[19] IRP_MJ_QUERY_QUOTA                 804f9709	nt!IopInvalidDeviceRequest
[1a] IRP_MJ_SET_QUOTA                   804f9709	nt!IopInvalidDeviceRequest
[1b] IRP_MJ_PNP                         804f9709	nt!IopInvalidDeviceRequest


0: kd> !devobj 813b1490 
Device object (813b1490) is for:
 devsudami \Driver\sudami DriverObject 815c87e0
Current Irp 00000000 RefCount 0 Type 00000022 Flags 00000040
Dacl e12a719c DevExt 00000000 DevObjExt 813b1548 
ExtensionFlags (0000000000)  
Device queue is not busy.


0: kd> !object \Global??
Object: e1002898  Type: (817ef418) Directory
    ObjectHeader: e1002880 (old version)
    HandleCount: 1  PointerCount: 126
    Directory Object: e1001be0  Name: GLOBAL??

    Hash Address  Type          Name
    ---- -------  ----          ----
     00  e147efe0 SymbolicLink  D:
         e13173c8 SymbolicLink  NDIS
         e138fbd0 SymbolicLink  DISPLAY1

	 [...................................]
     31  e17b5fe0 SymbolicLink  sudami
     [...................................]

Le driver va donc créer un device nommé sudami avec un SymbolicLink (qui est donc mit dans l’ObjectDirectory \Global??) autorisant la communication depuis le user-land qui s’appelle aussi sudami. On retrouve seulement 2 MajorFunctions, la même pour les IRP_MJ_CREATE et IRP_MJ_CLOSE qui sert juste à complêter l’IRP avec un STATUS_SUCCESS.

0: kd> uf f9e042a0
sudami+0x12a0:
f9e042a0 8bff            mov     edi,edi
f9e042a2 55              push    ebp
f9e042a3 8bec            mov     ebp,esp
f9e042a5 8b450c          mov     eax,dword ptr [ebp+0Ch] ; IRP
f9e042a8 c7401800000000  mov     dword ptr [eax+18h],0 ; IRP.IoStatus.Status=0=STATUS_SUCCESS
f9e042af 8b4d0c          mov     ecx,dword ptr [ebp+0Ch] ; IRP
f9e042b2 c7411c00000000  mov     dword ptr [ecx+1Ch],0 ; IRP.IoStatus.Information=0
f9e042b9 32d2            xor     dl,dl
f9e042bb 8b4d0c          mov     ecx,dword ptr [ebp+0Ch]
f9e042be ff151860e0f9    call    dword ptr [sudami+0x3018 (f9e06018)] (IofCompleteRequest)
f9e042c4 33c0            xor     eax,eax
f9e042c6 5d              pop     ebp
f9e042c7 c20800          ret     8

Enfin on a la MajorFunction qui gère les IOCTL en 0xf9e043e0. Maintenant il est temps de sortir IDA pour analyser la routine DispatchDeviceControl.

Première chose qui saute aux yeux et nous fait pissé du sang, le code du driver est offusqué, il contient plein de junk code posé dans les fonctions et IDA n’apprécie pas du tout pour l’analyse. Juste pour déconner voici la gueule du DriverEntry du driver :

.text:000110B0
.text:000110B0                ; Attributes: bp-based frame
.text:000110B0
.text:000110B0                sub_110B0       proc near               
.text:000110B0
.text:000110B0                var_18          = dword ptr -18h
.text:000110B0
.text:000110B0 8B FF                          mov     edi, edi
.text:000110B2 55                             push    ebp
.text:000110B3 8B EC                          mov     ebp, esp
.text:000110B5 83 EC 18                       sub     esp, 18h
.text:000110B8 C7 45 E8 00 00+                mov     [ebp+var_18], 0
.text:000110BF
.text:000110BF                loc_110BF:                              
.text:000110BF 8D 05 C5 10 01+                lea     eax, loc_110C5
.text:000110C5
.text:000110C5                loc_110C5:                             
.text:000110C5 83 C0 0E                       add     eax, 0Eh
.text:000110C8 EB 05                          jmp     short loc_110CF
.text:000110CA                ; ---------------------------------------------------------------------------
.text:000110CA EB F3                          jmp     short loc_110BF
.text:000110CA                ; ---------------------------------------------------------------------------
.text:000110CC DB 30 57                       db 0DBh, 30h, 57h
.text:000110CF                ; ---------------------------------------------------------------------------
.text:000110CF
.text:000110CF                loc_110CF:                             
.text:000110CF FF E0                          jmp     eax
.text:000110CF                sub_110B0       endp
.text:000110CF
.text:000110CF                ; ---------------------------------------------------------------------------
.text:000110D1 E9 21 74                       db 0E9h, 21h, 74h
.text:000110D4 03 75 01 E8 8B+                dd 0E8017503h, 0C708458Bh
.text:000110DC                ; ---------------------------------------------------------------------------
.text:000110DC 40                             inc     eax

On retrouve ce schéma de junk dans quasiment toutes les fonctions du driver, l’auteur s’est dit qu’il y aurait bien des gars capables de reverser son driver pour piquer sa technique donc il ajouter du junk dans le code à la main en insérant des macros toutes les 2 lignes de code C pour rendre la tâche du reverser plus longue mais pas impossible ! En effet, en regardant de plus près on a un « lea eax, loc_110C5″ suivit d’un « add eax, 0EH », eax vaut donc loc_110C5+0EH. Ensuite nous avons un « jmp short loc_110CF » qui saute sur un « jmp eax », nous devons donc normalement arriver en loc_110C5+0EH et la IDA n’arrive pas analyser correctement, c’est l’échec :] En 0x110D3 (0x110C5+0xE) on peut voir l’opcode 0×74 qui correspond à un JZ et en 0x110D5 on peut voir l’opcode d’un JNZ (0×75), le plus drôle c’est que le JZ saute 3 bytes plus loin et le JNZ 1 byte plus loin, donc tous les deux sautent au même endroit, c’est à dire en 0x110D8. Hop on nettoie ca sous IDA pour obtenir le code clarifié :

.text:000110B8 C7 45 E8 00 00+    mov dword ptr [ebp-18h], 0
.text:000110BF 8D 05 C5 10 01+                lea     eax, loc_110C5
.text:000110C5
.text:000110C5                loc_110C5:                              
.text:000110C5 83 C0 0E                       add     eax, 0Eh
.text:000110C8 EB 05                          jmp     short loc_110CF
.text:000110C8                ; ---------------------------------------------------------------------------
.text:000110CA EB                             db 0EBh ; Ù
.text:000110CB F3                             db 0F3h ; ¾
.text:000110CC DB                             db 0DBh ; ¦
.text:000110CD 30                             db  30h ; 0
.text:000110CE 57                             db  57h ; W
.text:000110CF                ; ---------------------------------------------------------------------------
.text:000110CF
.text:000110CF                loc_110CF:                             
.text:000110CF FF E0                          jmp     eax
.text:000110CF                ; ---------------------------------------------------------------------------
.text:000110D1 E9                             db 0E9h
.text:000110D2 21                             db  21h ; !
.text:000110D3                ; ---------------------------------------------------------------------------
.text:000110D3 74 03                          jz      short loc_110D8
.text:000110D5 75 01                          jnz     short loc_110D8
.text:000110D5                sub_110B0       endp
.text:000110D5
.text:000110D5                ; ---------------------------------------------------------------------------
.text:000110D7 E8                             db 0E8h ; Þ
.text:000110D8                ; ---------------------------------------------------------------------------
.text:000110D8
.text:000110D8                loc_110D8:                              
.text:000110D8                                                        
.text:000110D8 8B 45 08                       mov     eax, [ebp+arg_0]
.text:000110DB C7 40 38 A0 12+                mov     dword ptr [eax+38h], offset sub_112A0
.text:000110E2 8D 05 E8 10 01+                lea     eax, loc_110E8

Le junk consiste donc à un jmp sur 2 jumps conditionnels qui ensemble forment le même saut, jump if zero (JZ) qui saute si le ZeroFlag de l’EFlags est à 1 et jump if not zero (JNZ) qui fait l’inverse. On peut donc remplacer tout ce code par des NOP vu qu’il est inutile et regrouper les NOPs dans un tableau pour obtenir un disass clair. Après avoir fait le ménage on obtient :

.text:000110B8 C7 45 E8 00 00+    mov dword ptr [ebp-18h], 0
.text:000110B8 00 00          ; ---------------------------------------------------------------------------
.text:000110BF 90 90 90 90 90+    db 19h dup(90h)
.text:000110D8                ; ---------------------------------------------------------------------------
.text:000110D8 8B 45 08           mov eax, [ebp+8]

Ce qui est clairement plus lisible ! Il ne reste plus qu’a automatiser la tâche avec un script IDC. Pour le script j’ai fait simple, j’ai recherché tout les « jmp eax » dans le binaire avec la function FindBinary, j’ai noppé les instructions autour qui ne servaient à rien puis regrouper les NOPs dans un tableau. Voici le script très simple :

//
// Cleaner for driver http://hi.baidu.com/sudami/blog/item/1fe5b203005f45e909fa9368.html
// Ivanlef0u
//

#include <idc.idc>

static main()
{
	auto i;
	auto addr;
	
	Message("Starting clean script ...\n");

	addr=0;

	do
	{
		//
		// jmp eax= 0xFF 0xE0
		//
		addr=FindBinary(addr, SEARCH_DOWN, "FF E0");
		Message("jmp eax at : 0x%x\n", addr);
		
		//
		// 16 bytes avant le "jmp eax" commence le junk et se termine 9 bytes plus loin
		//
		for (i=0; i<16+9; i++)
			PatchByte(addr-16+i, 0x90);
	
		//
		// Instrution suivante 
		//	
		addr=addr+2;
	}while(addr!=(-1+2)); //((addr=-1 lorsque plus rien)

	addr=0;
	
	//
	// Met les nop dans des tableaux
	//
	do
	{
		addr=FindBinary(addr, SEARCH_DOWN, "90");
		Message("nop at : 0x%x\n", addr);
		
		for(i=0; i<25; i++)
		{	
			MakeUnknown(addr+i, 1, DOUNK_SIMPLE);
		}	
		MakeArray(addr, 25);
		
		//
		// Instrution suivante 
		//	
		addr=addr+26;
	}while(addr!=(-1+26)); //((addr=-1 lorsque plus rien)
}

Une fois qu'on a nettoyé le driver on peut enfin commencer à analyser son fonctionnement, nous allons juste nous intéresser à la fonction DispatchDeviceControl. On sait que le driver sert à rendre le process immortel, il va donc devoir agir sur la structure EPROCESS et très certainement sur les structures ETHREADs correspondant aux threads du process. Pour analyser la routine de dispatch des IOCTLs nous allons juste suivre les fonctions appelées en fonction des IOCTLs passé au driver.

Commençons par le premier IOCTL (0x80002000) celui-ci nous va utliser IoGetCurrentProcess pour récupérer l'EPROCESS du process courant, donc celle du programme à protéger et à partir du pointeur sur la double liste chainée ThreadListHead de la KPROCESS (offset KPROCESS+0x50) va parcourir la liste de structures ETHREAD représentant les threads du process pour changer le champ KernelApcDisable (offset 0xD4 de la KTRHEAD), si ce champ est à 0 alors il le passe à la valeur 0xA98AC7 (grut?!). En regardant les symbols on peut voir que ce champ fait 2 bytes, alors d'après ce que j'ai compris ce champ agirait comme un compteur. Je m’explique lorsqu'on envoie un APC (Asynchronous Procedure Call) à un thread 3 routines sont mises en place, une KernelRoutine qui est appelée directement en préemptant le thread en kernel-land à un IRQL de APC_LEVEL, une NormalRoutine qui est généralement appelée en user-land lorsque le thread est en state Waiting puis une RundownRoutine qui est la routine qui sera appelée lorsque le thread sera killé et qui à pour rôle de faire le ménage sur les APC qui n'auront pas été exécutés. La fonction KiInsertQueueApc va vérifier la valeur du champ KernelApcDisable si celui-ci est à 0 alors la fonction queue un APC kernel pour le thread. Lorsque le thread appel une des APIs KeEnterCriticalRegion ou KeEnterGuardedRegion le champ KernelApcDisable est décrémenté inversement pour les APIS KeLeaveCriticalRegion et KeLeaveGuardedRegion, ce qui faut retenir c'est que la valeur de KernelApcDisable est différente de 0 dès que le thread rentre dans une région critique. Du fait qu'il est possible d'avoir des plusieurs appels à KeEnterCriticalRegione et KeEnterGuardedRegion un compteur est mis en place afin de connaître le nombre d'appels à ces APIs, après je n'arrive pas à comprendre pourquoi ce dernier compte à l'envers ... Je reviendrais sur rôle des APCs kernel plus loin dans ce post, d'abord voyons les actions des autres IOCTLs.

Continuons avec le second IOCTL envoyé au driver (0x80002008), la fonction associée va aussi parcourir la liste des threads du process mais cette fois ci pour modifier le champ State (offset 0x2D de la KTHREAD) à la valeur 4 qui correspond à l'état Terminated, celui la même qui est définit lorsqu'on appel TerminateThread. Juste pour rappel voici les différents états possibles pour un thread sous Windows :

//
// Thread scheduling states.
//

typedef enum _KTHREAD_STATE {
    Initialized,
    Ready,
    Running,
    Standby,
    Terminated, //=4
    Waiting,
    Transition,
    DeferredReady,
    GateWait
} KTHREAD_STATE;

Le troisième IOCTL (0x80002010) est beaucoup plus freestyle, la fonction chargée de gérer cet IOCTL va commencer par récupérer l'adresse de la fonction PsTerminateSystemThread et scanner ses instructions une par une en utilisant un LDE (Length Disassembly Engine) qui permet d'obtenir la taille des instructions en fonction de leurs opcodes, ca se voit sous IDA vu la bouillie que crée la fonction LDE. La fonction de scan recherche l'instruction commençant par (0x80F6) puis lit le word situé 2 bytes plus loin par rapport au début de l'instruction. Pour savoir à quoi ça correspond il suffit de désassembler la fonction PsTerminateSystemThread :

lkd> uf PsTerminateSystemThread
nt!PsTerminateSystemThread:
805d2c2a 8bff            mov     edi,edi
805d2c2c 55              push    ebp
805d2c2d 8bec            mov     ebp,esp
805d2c2f 64a124010000    mov     eax,dword ptr fs:[00000124h]
805d2c35 f6804802000010  test    byte ptr [eax+248h],10h
805d2c3c 7507            jne     nt!PsTerminateSystemThread+0x1b (805d2c45)

nt!PsTerminateSystemThread+0x14:
805d2c3e b80d0000c0      mov     eax,0C000000Dh
805d2c43 eb09            jmp     nt!PsTerminateSystemThread+0x24 (805d2c4e)

nt!PsTerminateSystemThread+0x1b:
805d2c45 ff7508          push    dword ptr [ebp+8]
805d2c48 50              push    eax
805d2c49 e828fcffff      call    nt!PspTerminateThreadByPointer (805d2876)

nt!PsTerminateSystemThread+0x24:
805d2c4e 5d              pop     ebp
805d2c4f c20400          ret     4

L'instruction qui commence par 0x80F6 est "test byte ptr [eax+248h],10h", 2 bytes après le début des opcodes on retrouve la valeur 0x0248 qui tout l'offset d'un champ dans la structure ETHREAD. L'auteur à tout simplement implémenter une méthode générique pour retrouver l'offset de ce champ en se basant sur le code de la fonction noyau PsTerminateSystemThread, c'est pas mal, je dirais même bien mais je ne pense pas que ca soit nécessaire, un coup de PsGetVersion avec un switch pour définir les offsets suffit aussi et à l'avantage d'être beaucoup moins lourd. Quoiqu'il en soit à l'offset 0x248 de la structure on retrouve le champ CrossThreadFlags, un byte servant à définir différents flags pour le thread :

lkd> dt nt!_ETHREAD
[...]
+0x248 CrossThreadFlags : Uint4B
+0x248 Terminated       : Pos 0, 1 Bit
+0x248 DeadThread       : Pos 1, 1 Bit
+0x248 HideFromDebugger : Pos 2, 1 Bit
+0x248 ActiveImpersonationInfo : Pos 3, 1 Bit
+0x248 SystemThread     : Pos 4, 1 Bit
+0x248 HardErrorsAreDisabled : Pos 5, 1 Bit
+0x248 BreakOnTermination : Pos 6, 1 Bit
+0x248 SkipCreationMsg  : Pos 7, 1 Bit
+0x248 SkipTerminationMsg : Pos 8, 1 Bit
[...]

La fonction va parcourir la liste des threads du process et mettre le flag Terminated à 1 pour tous les threads.

Enfin le dernier IOCTL (0x80002018) va modifier le PID du process au niveau du champ UniqueProcessId à l'offset 0x84 de la structure EPROCESS à la valeur de 7 et le propager aux ETHREADS en modifiant le champ UniqueProcess de la structure CLIENT_ID située à l'offset 0x1Ec de la structure ETHREAD. Je suppose que cette modification est faite pour tromper la PspCidTable, cette table de handles qui correspond aux PID et qui permet de faire la translation PID->EPROCESS afin de tromper les APIs kernel qui normalement vérifient que les 2 valeurs de PID, celle envoyée et celle trouvée, correspondent, à vérifier quand même.

Ouf, finit le reverse d'IOCTLs, résumons à peu ce que nous avons 4 IOCTLS qui activent 4 protections différentes :

  • Le premier sert à désactiver les APC kernel sur tout les threads du process.
  • Le second va changer l'état du thread (State) à Terminated.
  • Le troisième modifie les flags des différentes threads du process en activant le flag Terminated. La différence avec le point précédent c'est que le champ State sert uniquement au thread scheduler alors que le flag est utilisé par les APIs noyau pour vérifier l'état du thread.
  • Enfin le dernier IOCTL change le PID du process et celui de ses threads à la valeur 7, une valeur qui n'existe pas dans la PspCidTable.

Je reviens sur la désactivation des APC noyau, le point qui me gênait le plus. En fait lorsqu'on appel l'API NtTerminateThread celle-ci appelle PspTerminateThreadByPointer après avoir référencé l'objet ETHREAD avec ObReferenceObjectByHandle et va envoyer un APC sur le thread avec la fonction KeInsertQueueApc. Remarquez que même la fonction PsTerminateSystemThread qui sert à killer un thread kernel en étant appelé par le thread lui même va appeler PspTerminateThreadByPointer. En fait en y pensant un peu cela est normal, un APC est une routine qui est exécutée dans le context d'un thread, il est normal qu'un thread ne puisse pas s'auto détruise, ca serait comme couper la branche sur laquelle il est, c'est donc une routine externe qui s'en charge. En l'occurrence cette routine s'appelle PsExitSpecialApc et lance PspExitThread. Donc comme le driver désactive les APC noyau en faisant croire que celui-ci est une région critique, l'APC chargé de killé le thread n'est pas lancé et ainsi le thread n'est pas détruit, tout s'explique ! (ou bien j’ai rien compris :p)

Pour conclure, je trouve le taff de Sudami vraiment très intéressant, ca innove et c'est basé sur des choses simples, on voit qu'il connaît très bien l'architecture du noyau Windows. En fait son code revient juste à faire croire au kernel que les threads du process sont déjà killés, le noyau ne va donc pas essayer de les détruire dans ce cas là et ils se verront toujours runner. La partie déobffuscation de code était aussi marrante, ce n’est pas très méchant, moi qui n'est pas vraiment l'habitude de voir des binaires comme ça j'ai apprit quelques trucs, cependant vivement IDA 5.3 qui permettra de faire du script avec des languages comme python ou ruby :]

Voilà, c'est fini, j'espère que vous avez appris pas mal de trucs, maintenant si quelqu'un est motivé pour recoder les fonctionnalités du driver, je suis preneur ;) Je vous file une archive avec le binaire original, le binaire unpacké, le driver extrait et l'ouput html du reverse sous IDA de ce dernier. Vous trouverez tout ça ici : http://ivanlef0u.fr/repo/sudami_KillMe.rar

Enjoy !

Entry Filed under: RE

18 Comments

  • 1. marc  |  juillet 15th, 2008 at 20:11

    Même pour un ignare de mon calibre, cette escalade de subtile perversité entre l’auteur du code et le désassembleur est jubilatoire et époustouflante.

    Ca vaut bien une tomme pour accompagner les mojito du Falstaff.


  • 2. ollep  |  juillet 15th, 2008 at 21:53

    Dommage qu’il y ait pas de easter egg pour killer la fenêtre quand même :)


  • 3. Sha  |  juillet 16th, 2008 at 06:34

    Ils sont quand même forts ces chinois :]
    Superbe article dude, ça change de la musique trash et des picz gores héhé ;)


  • 4. Neitsa  |  juillet 16th, 2008 at 10:54

    Salut Ivan :D

    Chouette post et joli reverse ! Ha, ils sont forts ces chinois quand même…

    En regardant les symbols on peut voir que ce champ fait 2 bytes

    Oui sous 2k3 (pas regardé pour Vista), mais pas sous XP et O.S précédents (où le champ fait 4 octets).
    Les offsets sont différents aussi (0×70 sous 2k3, mais 0×40 sous XP), alors attention, il y a un gros risque de bugcheck sous un autre O.S que sous XP !

    Si on voulait être « portable », il faudrait faire quelque chose comme ça – il me semble – pour changer la valeur de KernelDisableApc :

    //decrement KernelDisableApc until it is == 0
    while( !KeAreApcsDisabled() ) //check if KernelDisableApc is 0
    {
         KeEnterCriticalRegion(); //KernelDisableApc --;
    }
    
    //now, set KernelDisableApc to its maximum value
    KeEnterCriticalRegion();
    

    après je n’arrive pas à comprendre pourquoi ce dernier compte à l’envers

    Parce que KiDeliverApc() vérifiera si le champ vaut 0 ou non (donc n’importe qu’elle autre valeur fait l’affaire si != 0). Si la valeur est != de 0 alors on ne délivre pas d’APC. Je vois pas d’autre raison…

    Ceci dit, autant la méthode est chouette et bien pensée, autant je me demande ce qu’on faire avec un process planqué de la sorte (plus du tout d’APC délivrées, des threads à Terminated, un PID fantôme, etc.).

    Le système laisse t’il un liberté d’action à un process et des threads dans un tel état (possible d’ouvrir un fichier par exemple ?) ou faut-il tout « réparer » , faire une action (ex. : ouverture de fichier) et revenir à un état « zombie » ?


  • 5. admin  |  juillet 16th, 2008 at 11:02

    @Marc
    Tu peux me mailler ta tomme ?

    @Ollep
    Nan la DialogFunc de CreateDialogParam ne support pas les messages de type WM_DESTROY donc c’est mort :)

    @Sha
    Merci jeune folle.

    @Neitsa
    Oups erreur de ma part, le champ KernelApcDisable fait bien 4 bytes sous XP. Pour les APCs je ne suis pas d’accord avec ton code, la valeur max est tout simplement 1 ! Il ne pas oublier que KeLeaveCriticalRegion incrémente le compteur donc l’écart le plus grand avec 0 en incrémentant est égal à 0-0xFFFFFF=1 (en unsigned). Sinon le POC ne faisait aucune action après l’envoie des IOCTLs je ne sais pas comment se comporte l’OS avec un thread dans un tel état, pour être sur le mieux serait de tester.


  • 6. ITI  |  juillet 16th, 2008 at 16:23

    Coooool….. J’avais jamais lu un truc sympa comme ça, bien éclairci/traduit pour un noob du code comme moi (QB4, où est-tu ????) :o) merci.

    A quand le kit de codage dans lequel on injecte les exploits et qui maintient les failles ouvertes tant qu’on a pas pu tuer ce super-loader ? :o] gruntz…


  • 7. ollep  |  juillet 16th, 2008 at 16:51

    @ITI, si on reloge le code dans un process « vitale » de windows, ça revient plus ou moins au même en pratique ;)

    Je vois qu’il y a des fans de gorilla.bas


  • 8. CyberPunk  |  juillet 17th, 2008 at 01:11

    Les PID sont des multis de 4, a ce que j’ai compris la le PID fait 7.
    J’ai déja vu un POC qui permetait bypassé les FW en changent le PID de la sorte…


  • 9. sudami  |  juillet 17th, 2008 at 05:07

    Had inadvertently intruded into your treasure land, see this article


    I was the author of this process, I am glad that you patronize my blog,

    and the conduct of this procedure.
    In fact, the method 3, KernelApcDidable, did not play a role in this

    process.
    there not too many tests and improved, so it can not compatible with

    many platforms. Just run under XP SP2.
    The most important point is: This procedure is released just for fun,

    can not be used in the formal products, so the writing is very rough.

    In addition, the author of this article analysis « drv.sys » very well,

    but I had already share the source code http://www.debugman.com. add some jump

    code in the drv.sys is juest for test,the source Code in fact is so

    poor, so I’s not necessary to prevent others to reverse. Oh~~~

    - –
    French football,in this year’s European Cup, performance not well. ah,

    the lack of a central figure such as Zidane; Henry is old …
    and sad about that~

    sorry for my pool English. ~


  • 10. newsoft  |  juillet 18th, 2008 at 13:11

    * Le pb de PsGetVersion() c’est que tu dois faire la table de switch toi-même … donc avoir toutes les versions de Windows et/ou les symboles sous la main …

    * IDAPython et IDARuby ça existe déjà depuis un certain temps :)

    * nibbles.bas forever


  • 11. Taron  |  juillet 18th, 2008 at 15:21

    Sympa ce killme…, c’est vrai qu’il utilise des trucs assez simples, ,et ça marche pas mal, beau reverse !


  • 12. SSTA  |  juillet 20th, 2008 at 16:19

    Une autre perversion du même genre signé par MJ une camarade de Sudami, posté sur son blog ( http://hi.baidu.com/mj0011/blog/item/90adc462293510d9e7113a11.html) et discuté en anglais sur les forums Sysinternals ( http://forum.sysinternals.com/forum_posts.asp?TID=14328 ).

    D’autres codes intéressants sur Debugman/http://www.debugman.com/ (se familiariser avec un bon dico français/chinois est indispensable, et après quelques mois de pratique, il devient facile de naviguer sur n’importe quel site chinois. Si besoin est, les codes sont disponibles à toute personne intéressée); un peu moins sur Antiprotect (http://www.antiprotect.com/ ).

    Bonne continuation.


  • 13. sloshy  |  juillet 21st, 2008 at 17:01

    Hello,
    Une question me turlupine, en modifiant le processus de la manière que tu décris, celui n’est plus actif, si?

    çàd qu’il executera une action (afficher une fenêtre à l’écran pour le cas présent) mais ne sera pas réactif.
    Dans le cas d’une application concrète, notepad par exemple, il ne sera plus utilisable après l’avoir rendu « invincible » n’est ce pas? ce qui avouons le, limite le PoC en question (il me semble).

    Ou peut être suis-je passé à coté d’une énormité?


  • 14. admin  |  juillet 21st, 2008 at 17:06

    @sloshy
    Tu as ta réponse là http://0vercl0k.blogspot.com/2008/07/sudami-killme.html


  • 15. sloshy  |  juillet 21st, 2008 at 19:00

    merci, me voici donc avec un blog de plus à lire :D


  • 16. Nameless  |  juillet 28th, 2008 at 15:33

    C’est HS mais j’ai pas trouvé comment te contacter:
    Une chose interessante pour detecter les process hidden :
    http://forum.sysinternals.com/forum_posts.asp?TID=15457

    a+


  • 17. admin  |  juillet 28th, 2008 at 15:35

    Merci Nameless, très intéressant j’avais déjà vu cette tech, pour me contact, ivanlef0u@security-labs.org.


  • 18. none  |  décembre 15th, 2008 at 00:59

    Juste pour info, l’outils Antirootkit GMER kill très bien sudami, sans broncher.


Trackback this post


Calendar

novembre 2019
L Ma Me J V S D
« fév    
 123
45678910
11121314151617
18192021222324
252627282930  

Most Recent Posts