Domains
Les domaines http://ivanlef0u.fr et http://www.ivanlef0u.fr (pour le blog) sont fonctionnels. Mettez à jour vos liens.
Si vous trouvez ce blog trop calme, suivez moi sur Twitter.
6 comments février 9th, 2011
Les domaines http://ivanlef0u.fr et http://www.ivanlef0u.fr (pour le blog) sont fonctionnels. Mettez à jour vos liens.
Si vous trouvez ce blog trop calme, suivez moi sur Twitter.
6 comments février 9th, 2011
Avant de lancer un exploit patché sur une machine Windows on préfère savoir à l’avance si la box est à jours ou non. Cela évite certaines mauvaises surprises et perte de temps à tester dans tous les sens un exploit codé par un chinois.
Comme vous le savez Microsoft édite ses patchs tous les seconds mardis de chaque mois. Le lendemain du patch tuesday est souvent consacré à differ (à l’aide de turbodiff ou de patchdiff2) les mises à jours. Après avoir localisé le bug et pondu un POC qui trigger la vuln on peut enfin coder quelque chose de plus sérieux :] En général un local EOP (Escalation Of Privilege) est le bienvenu.
Par la suite le jour arrive ou il s’agit de balancer notre exploit sur la machine cible. A ce moment on ressent la goute de sueur froide qui coule le long de notre dos parce que la loi du chaos peut tout foutre en l’air. L’un des paramètres qui décidera ou non de la réussite de notre attaque est bien sur le niveau de mise à jour de la machine.
Sous Windows XP connaitre les udpates présentes est plutôt simple. Il existe des outils dédiés comme WinUpdatesList de NirSoft En fonction des ‘KB’ installés on sait ou non si la machine est vulnérable.
Pour information WinUpdatesList va lire la clé de registre HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Hotfix.
Problème, sous Vista et 7 cette clé n’existe plus. On peut toujours passer par le menu ‘Installed Updates‘ mais c’est fastidieux si on doit vérifier beaucoup de patches.
Par chance Windows implémente WMI (Windows Management Instrumentation). Il s’agit en gros d’une interface standard d’échange d’information entre différentes applications. On peut la scripter avec du VBS et du PowerShell. Chose que seul news0ft sait faire ..
En interne Windows maintient un ensemble de classes contenant diverses informations sur le systèmes et ses applications regroupé sur une arborescence CIM (Common Information Model). Il est possible de voir l’ensemble de ces classes avec l’outil WMI Explorer.
Par exemple, si vous enregistrez le code suivant dans un .vbs et que l’exécutez il vous donnera les noms de tous les patches présents sur votre machine.
strComputer = "." Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2") Set colQuickFixes = objWMIService.ExecQuery("Select * from Win32_QuickFixEngineering") For Each objQuickFix in colQuickFixes Wscript.Echo "Patch : " & objQuickFix.ServicePackInEffect Next |
La même chose est possible via l’outil en ligne de commande wmic. La commande ‘wmic QFE list full /format:htable’ vous sort un dump HTML des différents patches.
Remarquez que dans les 2 cas on a fait une requête sur la table Win32_QuickFixEngineering. La requête se présente sous la forme d’un langage de ‘query’ appelé WQL. En fait on récupère l’ensemble des instances de cette classe via un SELECT puis on énumère la property ServicePackInEffect de chaque objet.
Bien sur comme on préfère coder en C il existe une interface COM pour WMI. Pour l’utiliser il suffit d’instancier une instance de CLSID_WbemLocator afin se connecter et d’obtenir un objet IWbemServices. A partir de là on peut appeler la méthode ExecQuery avec du WQL pour récupérer ce qui nous intéresse.
Le code suivant est inspiré de l’exemple de la MSDN ‘Example: Creating a WMI Application‘ et permet de savoir si un patch est présent ou pas.
#define ANSI #define _WIN32_DCOM #define _CRT_SECURE_NO_WARNINGS #include <windows.h> #include <comdef.h> #include <Wbemidl.h> #include <stdio.h> #pragma comment(lib, "wbemuuid.lib") #pragma comment(lib, "comsuppw.lib") BOOL IsKBInstalled(PCHAR Kb) { BOOL Status=TRUE; HRESULT hres; IWbemLocator *pLoc=0; IWbemServices *pSvc=0; IEnumWbemClassObject* pEnumerator=NULL; IWbemClassObject *pclsObj; ULONG uReturn=0; VARIANT vtProp; CHAR Buffer[256]; DWORD i=0; //Initialize COM hres=CoInitializeEx(0, COINIT_MULTITHREADED); if(FAILED(hres)) { printf("[-] Error with CoInitializeEx : 0x%x\n", hres); goto end; } //Initialize hres=CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE,NULL); if(FAILED(hres)) { printf("[-] Error with CoInitializeSecurity : 0x%x\n", hres); CoUninitialize(); goto end; } //Obtain the initial locator to Windows Management //on a particular host computer. hres=CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *)&pLoc); if(FAILED(hres)) { printf("[-] Error with CoCreateInstance : 0x%x\n", hres); CoUninitialize(); goto end; } // Connect to the root\cimv2 namespace with the // current user and obtain pointer pSvc // to make IWbemServices calls. hres=pLoc->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), NULL, NULL, 0, NULL, 0, 0, &pSvc); if(FAILED(hres)) { printf("[-] Error with ConnectServer : 0x%x\n", hres); pLoc->Release(); CoUninitialize(); goto end; } // Set the IWbemServices proxy so that impersonation // of the user (client) occurs. hres=CoSetProxyBlanket(pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE); if(FAILED(hres)) { //printf("[-] Error with CoSetProxyBlanket : 0x%x\n", hres); pSvc->Release(); pLoc->Release(); CoUninitialize(); goto end; } // Use the IWbemServices pointer to make requests of WMI. // Make requests here: _snprintf(Buffer, sizeof(Buffer)-1, "select * from Win32_QuickFixEngineering where ServicePackInEffect=\"KB%s\"", Kb); hres=pSvc->ExecQuery(bstr_t("WQL"), bstr_t(Buffer), WBEM_FLAG_FORWARD_ONLY|WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator); if(FAILED(hres)) { pSvc->Release(); pLoc->Release(); CoUninitialize(); goto end; } else { i=0; while(pEnumerator) { hres=pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn); if(uReturn==0) break; // Get the value of the Name property hres=pclsObj->Get(L"ServicePackInEffect", 0, &vtProp, 0, 0); //wprintf(L"ServicePackInEffect: %s\n", vtProp.bstrVal); VariantClear(&vtProp); i++; } } if(i==0) Status=FALSE; end: // Cleanup pSvc->Release(); pLoc->Release(); CoUninitialize(); return Status; } int __cdecl main(int argc, char *argv[]) { if(argc!=2) { printf("[-] Usage is : %s <KB patch number>\n", argv[0]); return 0; } printf("KB%s is %s\n", argv[1], IsKBInstalled(argv[1]) ? "present" : "not present"); } |
Exemple avec le patch du mois d’octobre 2010 MS10-073 (KB981957).
C:\wmi.exe 981957
KB981957 is present |
Maintenant vous pouvez ripper ce code et le mettre dans votre exploit qui pourra par lui même vérifier si le système est à jour sur 2000, XP, Vista et 7 :]
Code et binaire:
http://ivanlef0u.fr/repo/wmi.rar
4 comments novembre 13th, 2010
Rien de nouveau dans ce post. Juste un exemple de petit deamon sous Windows qui permet d’émuler un accept()+fork()+execve() comme on en voit souvent lors de challenges. D’abord pour montrer que c’est faisable et ensuite pour avoir du fun avec les potes :]
Le binaire que je fournis ne possède aucune mitigation. A vous de voir donc si vous voulez utiliser du GS, de l’ASLR, du DEP, voir du SAFESEH le tout avec du SEHOP. Vous pouvez bien sûr toujours changer la vuln :]
#include <winsock2.h> #include <windows.h> #include <stdio.h> #pragma comment (lib, "ws2_32.lib") #define PORT 1337 // // // int Fork(SOCKET Csock) { CHAR ProcessName[MAX_PATH]; STARTUPINFO StartupInfo; PROCESS_INFORMATION ProcessInformation; GetModuleFileName(GetModuleHandle(NULL), ProcessName, sizeof(ProcessName)-sizeof(CHAR)); printf("[*] Forking \"%s\" process \n", ProcessName); RtlZeroMemory(&StartupInfo, sizeof(StartupInfo)); RtlZeroMemory(&ProcessInformation, sizeof(ProcessInformation)); StartupInfo.cb=sizeof(StartupInfo); StartupInfo.dwFlags=STARTF_USESTDHANDLES; StartupInfo.hStdInput=(HANDLE)Csock; StartupInfo.hStdOutput=(HANDLE)Csock; StartupInfo.hStdError=(HANDLE)Csock; if(CreateProcess(ProcessName, NULL, NULL, NULL, TRUE, DETACHED_PROCESS, NULL, NULL, &StartupInfo, &ProcessInformation)==0) { printf("[-] Error in fork() with CreateProcess: %u\n", GetLastError()); return 0; } CloseHandle(ProcessInformation.hProcess); CloseHandle(ProcessInformation.hThread); printf("[+] New process %u created !\n", ProcessInformation.dwProcessId); return 1; } // // // int Parent() { int AddrLen; BOOL OptVal; SOCKET Ssock, Csock; SOCKADDR_IN Ssaddr, Csaddr; printf("[*] Windows vulnerable deamon by Ivanlef0u - H4CKZ ME - BE M4D!\n"); // // Use WSASocket() instead of socket() because we don't want an asynchronous socket. // Ssock=WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0); if(Ssock==INVALID_SOCKET) { printf("[-] Error in Parent() with WSASocket(): %d\n", WSAGetLastError()); goto end; } OptVal=TRUE; if(setsockopt(Ssock, SOL_SOCKET, SO_REUSEADDR, (char*)&OptVal, sizeof(OptVal))==SOCKET_ERROR) { printf("[-] Error in Parent() with setsockopt(): %d\n", WSAGetLastError()); goto end; } Ssaddr.sin_family=AF_INET; Ssaddr.sin_port=htons(PORT); Ssaddr.sin_addr.s_addr=INADDR_ANY; if(bind(Ssock, (struct sockaddr *)&Ssaddr, sizeof(Ssaddr))==SOCKET_ERROR) { printf("[-] Error in Parent() with bind(): %d\n", WSAGetLastError()); goto end; } if(listen(Ssock, 10)==SOCKET_ERROR) { printf("[-] Error in Parent() with listen(): %d\n", WSAGetLastError()); goto end; } while(1) { AddrLen=sizeof(Csaddr); Csock=accept(Ssock, (struct sockaddr *)&Csaddr, &AddrLen); if(Csock==INVALID_SOCKET) continue; printf("[*] Connection from : %s:%hu\n", inet_ntoa(Csaddr.sin_addr), ntohs(Csaddr.sin_port)); if(Fork(Csock)==0) printf("[-] Error in Parent() with Fork() :(\n"); closesocket(Csock); } end: closesocket(Ssock); WSACleanup(); return 1; } // // // int Child() { CHAR Buff[256]; printf("How it taste motherfucker ?!\n"); gets(Buff); // BOFME ! printf("%s\n", Buff); return 1; } // // // int __cdecl main(int argc, char * argv[]) { int Err, AddrLen; SOCKADDR_IN Csaddr; SOCKET Csock; WSADATA WSAData; Err=WSAStartup(WINSOCK_VERSION, &WSAData); if(Err!=0) { printf("[-] Error in main() with WSAStartup(): %d\n", Err); return 0; } // // Check if STD_INPUT_HANDLE is a socket. // If yes then we are the child. // If no we are the parend so we start the deamon. // Csock=(SOCKET)GetStdHandle(STD_INPUT_HANDLE); AddrLen=sizeof(Csaddr); if(getsockname(Csock, (struct sockaddr *)&Csaddr, &AddrLen)==0) { // // We don't need standard IO buffering // setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); Child(); closesocket(Csock); WSACleanup(); TerminateProcess(GetCurrentProcess(), 0); } else Parent(); return 0; } |
Source+binaire:
http://ivanlef0u.fr/repo/exploitme.rar
hf!
2 comments septembre 5th, 2010
Microsoft Security Advisory (2286198)
Microsoft Windows automatically executes code specified in shortcut files
Vulnerability in Windows « LNK » files?
http://ivanlef0u.fr/repo/suckme.rar
Testé sous XP SP3.
kd> g Breakpoint 1 hit eax=00000001 ebx=00f5ee7c ecx=0000c666 edx=00200003 esi=00000001 edi=7c80a6e4 eip=7ca78712 esp=00f5e9c4 ebp=00f5ec18 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202 SHELL32!_LoadCPLModule+0x10d: 001b:7ca78712 ff15a0159d7c call dword ptr [SHELL32!_imp__LoadLibraryW (7c9d15a0)] ds:0023:7c9d15a0={kernel32!LoadLibraryW (7c80aeeb)} kd> dd esp 00f5e9c4 00f5ee7c 000a27bc 00f5ee78 00000000 00f5e9d4 00000020 00000008 00f5ee7c 00000000 00f5e9e4 00000000 0000007b 00000000 00000000 00f5e9f4 00200073 002000e0 0000064c 0000028c 00f5ea04 1530000a 00000000 003a0043 0064005c 00f5ea14 006c006c 0064002e 006c006c 006d002e 00f5ea24 006e0061 00660069 00730065 00000074 00f5ea34 00090608 7c92005d 00000000 00000007 kd> db 00f5ee7c 00f5ee7c 43 00 3a 00 5c 00 64 00-6c 00 6c 00 2e 00 64 00 C.:.\.d.l.l...d. 00f5ee8c 6c 00 6c 00 00 00 92 7c-c8 f2 f5 00 00 17 72 02 l.l....|......r. 00f5ee9c 4b d2 00 00 d8 f2 f5 00-8b d2 a1 7c 00 00 00 00 K..........|.... 00f5eeac ac 80 9d 7c 30 d8 0d 00-34 d8 0d 00 b8 d7 0d 00 ...|0...4....... 00f5eebc 9a d2 a1 7c 30 d8 0d 00-c8 f2 f5 00 50 40 15 00 ...|0.......P@.. 00f5eecc 50 40 15 00 00 00 00 00-b8 00 92 7c 40 b7 0c 00 P@.........|@... 00f5eedc a8 ef f5 00 41 00 92 7c-18 07 09 00 5d 00 92 7c ....A..|....]..| 00f5eeec c8 f2 f5 00 00 ef f5 00-00 00 00 00 b8 00 92 7c ...............| kd> kv ChildEBP RetAddr Args to Child 00f5ec18 7ca81a74 00f5ee7c 000a27bc 00f5f2c4 SHELL32!_LoadCPLModule+0x10d (FPO: [1,145,4]) 00f5ee50 7ca82543 00f5ee74 000a27bc 000a27c0 SHELL32!CPL_LoadAndFindApplet+0x4a (FPO: [4,136,4]) 00f5f294 7cb56065 000a25b4 000a27bc 000a27c0 SHELL32!CPL_FindCPLInfo+0x46 (FPO: [4,264,4]) 00f5f2b8 7ca13714 00000082 00000000 00000104 SHELL32!CCtrlExtIconBase::_GetIconLocationW+0x7b (FPO: [5,0,0]) 00f5f2d4 7ca1d306 000a25ac 00000082 00f5f570 SHELL32!CExtractIconBase::GetIconLocation+0x1f (FPO: [6,0,0]) 00f5f410 7ca133b6 000dd7e0 00000082 00f5f570 SHELL32!CShellLink::GetIconLocation+0x69 (FPO: [6,68,4]) 00f5f77c 7ca03c88 000dd7e0 00000000 0015aa00 SHELL32!_GetILIndexGivenPXIcon+0x9c (FPO: [5,208,4]) 00f5f7a4 7ca06693 00131c60 000dd7e0 0015aa00 SHELL32!SHGetIconFromPIDL+0x90 (FPO: [5,0,4]) 00f5fe20 7ca12db0 00131c64 0015aa00 00000000 SHELL32!CFSFolder::GetIconOf+0x24e (FPO: [4,405,4]) 00f5fe40 7ca15e3c 00131c60 00131c64 0015aa00 SHELL32!SHGetIconFromPIDL+0x20 (FPO: [5,0,0]) 00f5fe68 7ca03275 000f8090 0014d5b0 0014a910 SHELL32!CGetIconTask::RunInitRT+0x47 (FPO: [1,2,4]) 00f5fe84 75f11b9a 000f8090 75f11b18 75f10000 SHELL32!CRunnableTask::Run+0x54 (FPO: [1,1,4]) 00f5fee0 77f49598 00155658 000cb748 77f4957b BROWSEUI!CShellTaskScheduler_ThreadProc+0x111 (FPO: [1,17,0]) 00f5fef8 7c937ac2 000cb748 7c98e440 0014cfe0 SHLWAPI!ExecuteWorkItem+0x1d (FPO: [1,0,4]) 00f5ff40 7c937b03 77f4957b 000cb748 00000000 ntdll!RtlpWorkerCallout+0x70 (FPO: [Non-Fpo]) 00f5ff60 7c937bc5 00000000 000cb748 0014cfe0 ntdll!RtlpExecuteWorkerRequest+0x1a (FPO: [3,0,0]) 00f5ff74 7c937b9c 7c937ae9 00000000 000cb748 ntdll!RtlpApcCallout+0x11 (FPO: [4,0,0]) 00f5ffb4 7c80b729 00000000 00edfce4 00edfce8 ntdll!RtlpWorkerThread+0x87 (FPO: [1,7,0]) 00f5ffec 00000000 7c920250 00000000 00000000 kernel32!BaseThreadStart+0x37 (FPO: [Non-Fpo])
42 comments juillet 18th, 2010
Depuis Vista et 7 le fonctionnement de l’API CreateRemoteThread à quelque peu changé. En fait il n’est plus possible d’aller injecter des threads dans des processus qui ne nous appartiennent pas ou pour être plus précis ceux qui sont dans d’autres sessions. Cela pose problème lorsqu’on veut jouer avec l’OS pour par exemple injecter des DLLs dans les processus d’autres utilisateurs. Après avoir regardé de plus près ce qui ne marche pas, je vous propose une petite solution simple mais efficace pour contourner cette restriction.
On se place dans le contexte d’un user loggé sous 7 appartenant au groupe ‘Administrateurs’ avec niveau d’UAC par défaut.
Avant de pouvoir faire un CreateRemoteThread sur le processus d’un autre utilisateur il faut pouvoir ouvrir un handle sur le process visé avec OpenProcess vu qu’on appartient au groupe Administrateurs on peut activer le SeDebugPrivilege et lancer notre process avec le token Administrateur qu’on a obtenu lorsqu’on s’est loggé à l’aide de la commande runas. Par défaut avec l’UAC même lorsqu’on se logge en admin on ne travaille pas directement avec le vrai token administrateur mais avec un token utilisateur. Lorsqu’une action administrative est nécessaire, l’UAC (si activé) prompt un avertissement pour lancer l’application avec le token administrateur.
La doc de CreateRemoteThread spécifie que :
‘Terminal Services isolates each terminal session by design. Therefore, CreateRemoteThread fails if the target process is in a different session than the calling process.’
Au moins ce comportement est documenté :]
Lorsqu’on essaye d’appeler CreateRemoteThread avec un handle sur le process d’un autre utilisateur la fonction nous renvoie NULL et GetLastError donne 8 (ERROR_NOT_ENOUGH_MEMORY). Déjà on se dit WTF?!. Traçons la fonction pour voir d’ou provient l’erreur. Avant de plonger plus loin on n’oublie pas que depuis 7 les fonctions de kernel32.dll ont été découpées dans plusieurs DLLs virtuelles. Même si la table des imports de kernel32.dll indique que CreateRemoteThread se situe dans API-MS-Win-Core-ProcessThreads-L1-1-0.dll on arrive au dans final kernelbase.dll et c’est pareil pour le reste des fonctions de kernel32.dll.
Au final on tombe dans kernelbase!CreateRemoteThreadEx. En traçant cette fonction l’erreur ne provient pas du syscall NtCreateThreadEx mais de CsrClientCallServer qui renvoie 0xC0000001 (STATUS_UNSUCCESSFUL). Plus loin CreateRemoteThreadEx transforme cette erreur en 0xC00000017 (STATUS_NO_MEMORY) et BaseSetLastNTError met à jour le GetLastError à 8. La bonne nouvelle c’est que ce n’est pas le syscall en lui même qui échoue mais un appel au subsystem csrss qui fait tout foirer. En fait le thread a été créé avec le CREATE_SUSPENDED, il attend donc un appel à ResumeThread ou (ZwResumeThread) pour être lancé. Il y effectivement cet appel dans CreateRemoteThreadEx mais il ne s’exécute qu’en cas de réussite de CsrClientCallServer.
Petit description du process csrss.exe sous 7 (from Windows Internals 5th) :
Session space contains information global to each session.A session consists of the processes and other system objects (such as the window station, desktops, and windows) that represent a single user’s logon session. Each session has a session-specific paged pool area used by the kernel-mode portion of the Windows subsystem (Win32k.sys) to allocate session-private GUI data structures. In addition, each session has its own copy of the Windows subsystem process (Csrss.exe) and logon process (Winlogon.exe). The session manager process (Smss.exe) is responsible for creating new sessions, which includes loading a session-private copy of Win32k.sys, creating the sessionprivate object manager namespace, and creating the session-specific instances of the Csrss and Winlogon processes.
En fait chaque utilisateur a donc son process csrss.exe. Voici le prototype de CsrClientCallServer et le code d’appel dans CreateRemoteThreadEx :
NTSTATUS NTAPI CsrClientCallServer( struct _CSR_API_MESSAGE *Request, struct _CSR_CAPTURE_BUFFER *CaptureBuffer OPTIONAL, ULONG ApiNumber, ULONG RequestLength ); kernelbase.dll 7597BD24 6A 0C PUSH 0C 7597BD26 68 01000100 PUSH 10001 7597BD2B 53 PUSH EBX 7597BD2C 8D85 F0FDFFFF LEA EAX, DWORD PTR SS:[EBP-210] 7597BD32 50 PUSH EAX 7597BD33 FF15 00129775 CALL NEAR DWORD PTR DS:[<&ntdll.CsrClientCallServer>] ; ntdll.CsrClientCallServer 7597BD39 8B85 10FEFFFF MOV EAX, DWORD PTR SS:[EBP-1F0] 7597BD3F 8985 E8FDFFFF MOV DWORD PTR SS:[EBP-218], EAX 7597BD45 399D E8FDFFFF CMP DWORD PTR SS:[EBP-218], EBX 7597BD4B 0F8C 13D80100 JL KERNELBA.75999564
On s’intéresse au paramètre ApiNumber qui est défini par la macro :
#define CSR_MAKE_API_NUMBER( DllIndex, ApiIndex ) \ (CSR_API_NUMBER)(((DllIndex) << 16) | (ApiIndex))
Ici on a ApiIndex qui vaut 1 et DllIndex qui vaut 1. On sort les tables faites par j00ru et on voit que CsrClientCallServer va appeler
basesrv!BaseSrvCreateThread. Après debug on s'aperçoit que c'est csrsrv!CsrLockProcessByClientId qui pose problème. Hop on sort IDA et on analyse CsrLockProcessByClientId
unsigned int __stdcall CsrLockProcessByClientId(int a1, int ret) { int v2; // ebx@1 int v3; // edi@1 int v4; // esi@1 int v6; // edx@6 unsigned int v7; // [sp+18h] [bp+Ch]@1 v3 = ret; *(_DWORD *)ret = 0; v2 = CsrRootProcess + 8; v7 = 0xC0000001u; v4 = CsrRootProcess + 8; RtlEnterCriticalSection(&CsrProcessStructureLock); while ( *(_DWORD *)(v4 - 8 ) != a1 ) { v4 = *(_DWORD *)v4; if ( v4 == v2 ) { RtlLeaveCriticalSection(&CsrProcessStructureLock); return v7; } } v7 = 0; CsrLockedReferenceProcess(v4 - 8); *(_DWORD *)v3 = v6; return v7; }
Directement en voyant CsrRootProcess je me suis rappelé le CsrWalker. Pour faire simple sous XP le process csrss maintient une liste de tous les processes et threads lancés sur le système. Ainsi si on extrait cette liste on peut s'en servir comme d'un anti-rootkit (ou pour mieux cacher un rootkit :]).
Sous 7 cela change. Ce n'est plus la liste de tous les process qu'on a dans csrss mais bien ceux qui sont dans la même session (comprendre même user) que ce processus. Ainsi CsrLockProcessByClientId est appelé par BaseSrvCreateThread pour justement mettre à jour la liste des threads qui appartiennent au process cible. Or vu que le process existe dans une autre session csrsrv!CsrLockProcessByClientId échoue et renvoie 0xC0000001 (STATUS_UNSUCCESSFUL) relayé par basesrv!BaseSrvCreateThread. Voila l'origine de la valeur de retour de CsrClientCallServer.
Pendant que j'y suis il est possible de dumper les CLIENT_ID des processes de la CsrRootProcess à l'aide du kernel debugger:
1: kd> p eax=024df7a0 ebx=00000000 ecx=000003b0 edx=00000008 esi=024df7f0 edi=003f7ea0 eip=75884458 esp=024df770 ebp=024df7a4 iopl=0 nv up ei pl nz ac po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000212 basesrv!BaseSrvCreateThread+0x3f: 001b:75884458 ff1504108875 call dword ptr [basesrv!_imp__CsrLockProcessByClientId (75881004)] ds:0023:75881004={CSRSRV!CsrLockProcessByClientId (75895ad5)} 1: kd> !list -x "dt nt!_CLIENT_ID @$extret-8 " poi(CsrRootProcess)+8 +0x000 UniqueProcess : 0x000001a8 +0x004 UniqueThread : 0x000001ac +0x000 UniqueProcess : 0x000001e4 +0x004 UniqueThread : 0x000001e8 +0x000 UniqueProcess : 0x000000f0 +0x004 UniqueThread : 0x0000006c +0x000 UniqueProcess : 0x00000550 +0x004 UniqueThread : 0x0000078c +0x000 UniqueProcess : 0x00000480 +0x004 UniqueThread : 0x0000043c +0x000 UniqueProcess : 0x00000258 +0x004 UniqueThread : 0x000004dc +0x000 UniqueProcess : 0x0000019c +0x004 UniqueThread : 0x00000388 +0x000 UniqueProcess : 0x00000b20 +0x004 UniqueThread : 0x00000b24 +0x000 UniqueProcess : 0x00000b28 +0x004 UniqueThread : 0x00000b2c +0x000 UniqueProcess : 0x00000d1c +0x004 UniqueThread : 0x00000d20 +0x000 UniqueProcess : 0x000006d0 +0x004 UniqueThread : 0x00000288 +0x000 UniqueProcess : 0x00000120 +0x004 UniqueThread : 0x00000428 +0x000 UniqueProcess : 0x00000f5c +0x004 UniqueThread : 0x000001d8
En fait rien ne nous oblige à notifier le subsystem lorsqu'on crée un nouveau thread. Par exemple un des rôles du process csrss est de killer tous les processes avant un shutdown, c'est pour cela qu'il maintient la liste CsrRootProcess de chaque user sous 7. Donc ne pas le prévenir qu'un nouveau thread a été crée ne pose pas vraiment de problème pour la stabilité du système. Justement on ne va pas le faire :]
Plusieurs solutions existent pour pouvoir créer un thread dans le process d'une autre session. La première et la plus bourrin serait de directement passer par l'API native ZwCreateThreadEx. Pourquoi pas mais cela demande de manipuler une API et des structures non documentées.
Une autre possibilité serait de passer par ntdll!RtlCreateUserThread comme montré ici. Pareil dans ce cas on manipule toujours des structures et API non documentées.
De mon coté j'ai choisi de faire plus simple. Comme CsrClientCallServer est importé par kernelbase.dll. Il suffit de hooker son IAT pour remplacer temporairement CsrClientCallServer par notre fonction qui renvoie toujours une valeur de succès. Au moins on peut se baser sur les headers du SDK pour le faire. J'ai envie de dire que c'est une solution comme une autre et qu'elle ne propose rien de vraiment innovant mais au moins on travaille avec des APIs documentées et donc fiables. La seule chose bancale c'est qu'il ne suffit pas de modifier la valeur de retour de CsrClientCallServer mais aussi le champ ReturnValue de la structure CSR_API_MESSAGE passée en argument. Pour ça on modifie directement en fonction du disass, c'est à dire qu'on met à jour la valeur pointée par ebp-0x1F0.
Le POC permet donc d'injecter une DLL dans n'importe quel processus sous 7. Tant qu'on l'exécute depuis un shell administrateur. La DLL qui est injectée fait appel à OutputDebugString donc le comportement a changé aussi sous 7. Donc mettez à jour la valeur de HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter.
Les codes et binaires ici :
http://ivanlef0u.fr/repo/CreateRemoteThread7.rar
Enjoy!
18 comments mai 26th, 2010
Je commence enfin à jouer avec Windows 7. Depuis le temps que je m’amusais avec mon vieux XP il fallait bien que cela change. Justement arrivé sur Win7 j’ai voulu manipuler NtSystemDebugControl l’API qui permet de faire tout ce qu’on veut depuis l’user-land sans se fatiguer avec un driver ou \Device\PhysicalMemory. Sauf qu’avec Win7 une bonne partie des features cools ne sont plus disponibles. Embêtant lorsqu’on veut bidouiller son OS, surtout si on est sur la version 64bits ou un driver signé est obligatoire. Justement en bootant en mode DEBUG, Windbg des Debugging Tools for Windows est capable de faire des choses sympa en Local Kernel Debugging. Justement sachant qu’il fonctionne en user-land, par où passe-t-il pour récupérer des informations sur la mémoire noyau ou sur les MSRs ? Avant il passait par NtSystemDebugControl mais sous Win7 ?
En fait rien de nouveau, Alex Ionescu avait déjà parlé de ce mécanisme à Recon 2k6 dans son talk ‘Subverting Windows 2003 SP1 Kernel Integrity Protection‘. Une version bridée de l’API NtSystemDebugControl est toujours accessible depuis l’user-land simplement avec le SeDebugPrivilege mais les fonctionnalités intéressantes sont accessibles uniquement en kernel-land à travers l’API exportée par le noyau : KdSystemDebugControl. Voici son prototype:
NTSTATUS NTAPI KdSystemDebugControl( SYSDBG_COMMAND Command, PVOID InputBuffer, ULONG InputBufferLength, PVOID OutputBuffer, ULONG OutputBufferLength, PULONG ReturnLength, KPROCESSOR_MODE PreviousMode );
Maintenant il faut comprendre comment Windbg fait pour parler à cette API depuis l’user-land. En fait c’est assez simple, il passe par un driver ! Il s’agit de %systemroot%\system32\kldgbdrv.sys qui agit en tant que wrapper pour KdSystemDebugControl. On retrouve le chargement de ce driver à l’aide du Service Control Manager dans la fonction LocalLiveKernelTargetInfo::InitDriver(void) de dbgeng.dll
Quelques infos sur ce driver :
lkd> !devobj \device\kldbgdrv Device object (87bca750) is for: kldbgdrv \Driver\kldbgdrv DriverObject 898b59e0 Current Irp 00000000 RefCount 1 Type 00000022 Flags 00000040 Dacl 8b250ca0 DevExt 87bca808 DevObjExt 87bca810 ExtensionFlags (0x00000800) Unknown flags 0x00000800 Device queue is not busy. lkd> !drvobj \Driver\kldbgdrv 3 Driver object (898b59e0) is for: \Driver\kldbgdrv Driver Extension List: (id , addr) Device Object list: 87bca750 DriverEntry: 94191e00 DriverStartIo: 00000000 DriverUnload: 94191a10 AddDevice: 00000000 Dispatch routines: [00] IRP_MJ_CREATE 94191a50 +0x94191a50 [01] IRP_MJ_CREATE_NAMED_PIPE 828f4537 nt!IopInvalidDeviceRequest [02] IRP_MJ_CLOSE 94191a50 +0x94191a50 [03] IRP_MJ_READ 828f4537 nt!IopInvalidDeviceRequest [04] IRP_MJ_WRITE 828f4537 nt!IopInvalidDeviceRequest [05] IRP_MJ_QUERY_INFORMATION 828f4537 nt!IopInvalidDeviceRequest [06] IRP_MJ_SET_INFORMATION 828f4537 nt!IopInvalidDeviceRequest [07] IRP_MJ_QUERY_EA 828f4537 nt!IopInvalidDeviceRequest [08] IRP_MJ_SET_EA 828f4537 nt!IopInvalidDeviceRequest [09] IRP_MJ_FLUSH_BUFFERS 828f4537 nt!IopInvalidDeviceRequest [0a] IRP_MJ_QUERY_VOLUME_INFORMATION 828f4537 nt!IopInvalidDeviceRequest [0b] IRP_MJ_SET_VOLUME_INFORMATION 828f4537 nt!IopInvalidDeviceRequest [0c] IRP_MJ_DIRECTORY_CONTROL 828f4537 nt!IopInvalidDeviceRequest [0d] IRP_MJ_FILE_SYSTEM_CONTROL 828f4537 nt!IopInvalidDeviceRequest [0e] IRP_MJ_DEVICE_CONTROL 94191a80 +0x94191a80 [0f] IRP_MJ_INTERNAL_DEVICE_CONTROL 828f4537 nt!IopInvalidDeviceRequest [10] IRP_MJ_SHUTDOWN 828f4537 nt!IopInvalidDeviceRequest [11] IRP_MJ_LOCK_CONTROL 828f4537 nt!IopInvalidDeviceRequest [12] IRP_MJ_CLEANUP 828f4537 nt!IopInvalidDeviceRequest [13] IRP_MJ_CREATE_MAILSLOT 828f4537 nt!IopInvalidDeviceRequest [14] IRP_MJ_QUERY_SECURITY 828f4537 nt!IopInvalidDeviceRequest [15] IRP_MJ_SET_SECURITY 828f4537 nt!IopInvalidDeviceRequest [16] IRP_MJ_POWER 828f4537 nt!IopInvalidDeviceRequest [17] IRP_MJ_SYSTEM_CONTROL 828f4537 nt!IopInvalidDeviceRequest [18] IRP_MJ_DEVICE_CHANGE 828f4537 nt!IopInvalidDeviceRequest [19] IRP_MJ_QUERY_QUOTA 828f4537 nt!IopInvalidDeviceRequest [1a] IRP_MJ_SET_QUOTA 828f4537 nt!IopInvalidDeviceRequest [1b] IRP_MJ_PNP 828f4537 nt!IopInvalidDeviceRequest
Grâce à la forme des IRP_MJ_* on devine que Windbg parle au driver avec des IOCTLs (classique). Justement voici l’ensemble des fonctions de la classe LocalLiveKernelTargetInfo de dbgeng.dll
LocalLiveKernelTargetInfo::CheckLowMemory(void) LocalLiveKernelTargetInfo::DebugControl(_SYSDBG_COMMAND,void *,ulong,void *,ulong,ulong *) LocalLiveKernelTargetInfo::GetDescription(ushort *,ulong,ulong *) LocalLiveKernelTargetInfo::GetTargetContext(ThreadInfo *,unsigned __int64,void *) LocalLiveKernelTargetInfo::GetTargetKdVersion(_DBGKD_GET_VERSION64 *) LocalLiveKernelTargetInfo::InitDriver(void) LocalLiveKernelTargetInfo::Initialize(void) LocalLiveKernelTargetInfo::LocalLiveKernelTargetInfo(void) LocalLiveKernelTargetInfo::ReadBusData(ulong,ulong,ulong,ulong,void *,ulong,ulong *) LocalLiveKernelTargetInfo::ReadControl(ulong,unsigned __int64,void *,ulong,ulong *) LocalLiveKernelTargetInfo::ReadIo(ulong,ulong,ulong,unsigned __int64,void *,ulong,ulong *) LocalLiveKernelTargetInfo::ReadMsr(ulong,unsigned __int64 *) LocalLiveKernelTargetInfo::ReadPhysical(unsigned __int64,void *,ulong,ulong,ulong *) LocalLiveKernelTargetInfo::ReadVirtual(ProcessInfo *,unsigned __int64,void *,ulong,ulong *) LocalLiveKernelTargetInfo::WaitForEvent(ulong,ulong,ulong,ulong *) LocalLiveKernelTargetInfo::WriteBusData(ulong,ulong,ulong,ulong,void *,ulong,ulong *) LocalLiveKernelTargetInfo::WriteControl(ulong,unsigned __int64,void *,ulong,ulong *) LocalLiveKernelTargetInfo::WriteIo(ulong,ulong,ulong,unsigned __int64,void *,ulong,ulong *) LocalLiveKernelTargetInfo::WriteMsr(ulong,unsigned __int64) LocalLiveKernelTargetInfo::WritePhysical(unsigned __int64,void *,ulong,ulong,ulong *) LocalLiveKernelTargetInfo::WriteVirtual(ProcessInfo *,unsigned __int64,void *,ulong,ulong *) LocalLiveKernelTargetInfo::`scalar deleting destructor'(uint) LocalLiveKernelTargetInfo::~LocalLiveKernelTargetInfo(void)
Cool ! Si Windbg à accès à tout ça on devrait nous aussi pouvoir en profiter. Reste à déterminer la forme des IOCTLs envoyés au driver. Pour cela on va reverser à la fois dbgeng.dll et kldbgdrv.sys.
Après avoir passé un peu de temps dessus on obtient les informations suivantes :
typedef struct _KLDBG { SYSDBG_COMMAND DbgCommandClass; PVOID DbgCommand; DWORD DbgCommandLen; }KLDBG, * PKLDBG;
nInBufferSize vaut donc 0xC. Quand aux paramètres lpOutBuffer et nOutBufferSize ils prennent les mêmes valeurs que les champs DbgCommand et DbgCommandLen;
Il serait possible d’activer le debugger kernel en appelant KdEnableDebugger mais il faudrait charger un driver non signé pour cela. En 32 bits ca se fait bien tant qu’on ne veut pas le charger au boot mais en 64 bits c’est plus compliqué.
Maintenant il est temps de coder un POC. On va donc s’interfacer avec le driver et lui envoyer des IOCTLs. Mais d’abord on va vérifier plusieurs choses :
J’ai testé tout cela avec succès sur un Win7 32 bits à jour. Bien sur il faut lancer le binaire en tant qu’administrateur à cause de l’UAC. Je vous conseille de regarder le kdtypes.h du NDK afin de jouer avec les autres features de cette API. A noter qu’il ne faut pas modifier grand chose pour faire tourner la version 64 bits :p De plus en DEBUG PatchGuard n’est pas activé.
Vous trouvez le code et le binaire du POC ici :
http://ivanlef0u.fr/repo/kldbg.rar
Enjoy !
Autres refs :
Enabling the local kernel debugger on Vista RTM
Bypassing PatchGuard on Windows x64
Subverting PatchGuard Version 2
PatchGuard Reloaded: A Brief Analysis of PatchGuard Version 3
5 comments mars 30th, 2010
Depuis quelques temps je participe à différent CTFs online avec la team Nibbles. Je m’intéresse particulièrement à l’exploitation et le reverse des binaires sous Linux. Dernièrement nous avons participé au Padocon CTF et nous sommes bien amusé. J’ai écrit sur le blog de Nibbles une writeup sur une épreuve qui demandait d’exploiter une format string dans une situation assez difficile. Mais je préfère vous laissez découvrir l’article sur le blog :]
2 comments février 15th, 2010
Un petit post juste pour vous dire que j’ai bougé le repository afin de ne plus avoir de problèmes par la suite à cause de fichiers pas très clean
Voici la nouvelle adresse :
http://ivanlef0u.fr/repo/
Enjoy !
4 comments janvier 25th, 2010
Après avoir passé un peu de temps sur du linux ARM et PPC je retourne sous Windows parce que c’est la vraie vie. Rien de très innovant cette fois, juste un trick permettant de retrouver les DLLs chargées par un processus. Bien sur je veux parler de DLLs qui tenteraient de se cacher par rapport à certaines APIs et outils. On va voir que le loader de Windows maintient des structures en internes pour se simplifier la vie et que celles ci peuvent nous servir aussi.
Une liste des DLL qu’un processus à en mémoire est maintenu par le loader au niveau du PEB. On parle aussi bien des DLLs chargées statiquement que dynamiquement. Cette structure de type PEB_LDR_DATA
se situe au niveau du champ Ldr (offset 0xC) du PEB :
// // Loader Data stored in the PEB // typedef struct _PEB_LDR_DATA { ULONG Length; BOOLEAN Initialized; PVOID SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; PVOID EntryInProgress; } PEB_LDR_DATA, *PPEB_LDR_DATA;
Comme vous pouvez le voir il y a 3 listes qui contiennent les mêmes informations mais pas dans le même ordre :
Ces listes sont en fait maintenues dans des structures de type LDR_DATA_TABLE_ENTRY :
// // Loader Data Table Entry // typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; USHORT LoadCount; USHORT TlsIndex; union { LIST_ENTRY HashLinks; PVOID SectionPointer; }; ULONG CheckSum; union { ULONG TimeDateStamp; PVOID LoadedImports; }; PVOID EntryPointActivationContext; PVOID PatchInformation; } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
On retrouve des informations comme le nom de la DLL, son ImageBase et sa taille (SizeOfImage). Il existe aussi un champ assez peu connu dans cette structure : HashLinks. En fait lorsque le loader doit charger une DLL dans un processus il n’utilise pas les listes qu’on a vues plus haut pour des raisons de performances, en effet parcourir une liste chainée se fait en temps linéaire et peut être long s’il y a beaucoup d’éléments. Pour aller plus vite le loader maintient une hashtable afin de diminuer le temps de recherche. Le champ HashLinks fait justement partie de cette hashtable.
En fait on parle de la variable globale LdrpHashTable, c’est un tableau de 32 LIST_ENTRY. Les DLLs y sont reparties en fonction de leur nom, la fonction de hashage prend la première lettre du nom de la DLL en uppercase et le soustrait à L’A’, le tout est bien sur passé dans un modulo 32. Au final tout cela se résume par ces définitions :
#define LDRP_HASH_TABLE_SIZE 32 #define LDRP_HASH_MASK (LDRP_HASH_TABLE_SIZE-1) #define LDRP_COMPUTE_HASH_INDEX(wch) ( (RtlUpcaseUnicodeChar((wch)) - (WCHAR)'A') & LDRP_HASH_MASK ) LIST_ENTRY LdrpHashTable[LDRP_HASH_TABLE_SIZE];
LdrpHashTable est accédée par :
Maintenant imaginons une DLL malveillante qui s’enlèverait des listes InLoadOrderModuleList, InMemoryOrderModuleList et InInitializationOrderModuleList. La question se pose si il est possible de la retrouver sans scanner la mémoire en cherchant des patterns du format PE dans un try{}except(){} tout moche.
Justement comme la LdrpHashTable est souvent oublié par les attaquants on peut l’utiliser pour énumérer les DLL d’un processus. J’ai codé un petit tool qui parcourt la mémoire d’un processus à l’aide de ReadProcessMemory() pour dumper cette hashtable.
C:\ProgHack\c\LdrpHashTable>GetModuleListByHashTable.exe Process DLLs dumper using LdrpHashTable By Ivanlef0u BE M4D ! Usage is : GetModuleListByHashTable.exe(0 mean this process) C:\ProgHack\c\LdrpHashTable>GetModuleListByHashTable.exe 0 Process DLLs dumper using LdrpHashTable By Ivanlef0u BE M4D ! LdrpHashTable is at 7C98E260 Dumping Dlls ListHead[0] : 0x7C98E260 ListHead[1] : 0x7C98E268 ListHead[2] : 0x7C98E270 ListHead[3] : 0x7C98E278 ListHead[4] : 0x7C98E280 ListHead[5] : 0x7C98E288 ListHead[6] : 0x7C98E290 ListEntry : 0x00241EFC FullDllName : C:\ProgHack\c\LdrpHashTable\GetModuleListByHashTable.exe ListHead[7] : 0x7C98E298 ListHead[8] : 0x7C98E2A0 ListHead[9] : 0x7C98E2A8 ListHead[10] : 0x7C98E2B0 ListEntry : 0x00241FFC FullDllName : C:\WINDOWS\system32\kernel32.dll ListHead[11] : 0x7C98E2B8 ListHead[12] : 0x7C98E2C0 ListEntry : 0x0024209C FullDllName : C:\WINDOWS\system32\MSVCRT.dll ListHead[13] : 0x7C98E2C8 ListEntry : 0x00241F54 FullDllName : C:\WINDOWS\system32\ntdll.dll ListHead[14] : 0x7C98E2D0 ListHead[15] : 0x7C98E2D8 ListHead[16] : 0x7C98E2E0 ListHead[17] : 0x7C98E2E8 ListHead[18] : 0x7C98E2F0 ListHead[19] : 0x7C98E2F8 ListHead[20] : 0x7C98E300 ListHead[21] : 0x7C98E308 ListHead[22] : 0x7C98E310 ListHead[23] : 0x7C98E318 ListHead[24] : 0x7C98E320 ListHead[25] : 0x7C98E328 ListHead[26] : 0x7C98E330 ListHead[27] : 0x7C98E338 ListHead[28] : 0x7C98E340 ListHead[29] : 0x7C98E348 ListHead[30] : 0x7C98E350 ListHead[31] : 0x7C98E358 C:\ProgHack\c\LdrpHashTable>
Maintenant raison de plus de développer à coté un code qui enlève une DLL de ces 4 listes :]
C:\ProgHack\c\LdrpHashTable>HideDll.exe Hide ntdll from InMemoryOrderModuleList, InLoadOrderModuleList, InInitialization OrderModuleList and LdrpHashTable By Ivanlef0u BE M4D ! # Ici le process est en attente
On effectue un dump de ce process avec GetModuleListByHashTable :
C:\ProgHack\c\LdrpHashTable>tasklist | find "HideDll.exe" HideDll.exe 3436 0 708 Ko C:\ProgHack\c\LdrpHashTable>GetModuleListByHashTable.exe 3436 Process DLLs dumper using LdrpHashTable By Ivanlef0u BE M4D ! LdrpHashTable is at 7C98E260 Dumping Dlls ListHead[0] : 0x7C98E260 ListHead[1] : 0x7C98E268 ListHead[2] : 0x7C98E270 ListHead[3] : 0x7C98E278 ListHead[4] : 0x7C98E280 ListHead[5] : 0x7C98E288 ListHead[6] : 0x7C98E290 ListHead[7] : 0x7C98E298 ListEntry : 0x00241EFC FullDllName : C:\ProgHack\c\LdrpHashTable\HideDll.exe ListHead[8] : 0x7C98E2A0 ListHead[9] : 0x7C98E2A8 ListHead[10] : 0x7C98E2B0 ListEntry : 0x00241FFC FullDllName : C:\WINDOWS\system32\kernel32.dll ListHead[11] : 0x7C98E2B8 ListHead[12] : 0x7C98E2C0 ListEntry : 0x0024209C FullDllName : C:\WINDOWS\system32\MSVCRT.dll ListHead[13] : 0x7C98E2C8 ListHead[14] : 0x7C98E2D0 ListHead[15] : 0x7C98E2D8 ListHead[16] : 0x7C98E2E0 ListHead[17] : 0x7C98E2E8 ListHead[18] : 0x7C98E2F0 ListHead[19] : 0x7C98E2F8 ListHead[20] : 0x7C98E300 ListHead[21] : 0x7C98E308 ListHead[22] : 0x7C98E310 ListHead[23] : 0x7C98E318 ListHead[24] : 0x7C98E320 ListHead[25] : 0x7C98E328 ListHead[26] : 0x7C98E330 ListHead[27] : 0x7C98E338 ListHead[28] : 0x7C98E340 ListHead[29] : 0x7C98E348 ListHead[30] : 0x7C98E350 ListHead[31] : 0x7C98E358 C:\ProgHack\c\LdrpHashTable>
Hop, on voit bien que ntdll.dll n’apparait plus. Pour information des outils comme Process Explorer et LordPE ne ‘voient’ plus la DLL lorsqu’elle est unlink des 3 premières doubles listes chainées. De ce fait beaucoup de malwares le font pour être tranquille. Avec la LdrpHashTable on est capable de voir ces vilaines DLLs.
Sauf que les choses ne sont pas si simples. Il existe des outils comme VMMap qui sont capable de détecter nos DLLs autrement. Comment ? En fait lorsqu’une DLL est chargé en mémoire, elle est mappée, le système maintient donc en interne un objet de type Section le fichier qui est mappé. A l’aide de VirtualQueryEx() on peut connaître le type de mémoire et avec GetMappedFileName() le nom du fichier mappé. Ces APIs utilisent toutes deux le syscall ZwQueryVirtualMemory. Comme par hasard VMMap aussi :]
Je me suis donc codé un petit tool qui fonctionne pareil.
# On reprend le process vu plus haut qui cache ses DLLs C:\ProgHack\c\LdrpHashTable>GetModuleListByVirtualQuery.exe 3436 Process DLLs dumper using VirtualQueryEx+GetMappedFileName By Ivanlef0u BE M4D ! [*] Dumping Dlls MappedFile : \Device\HarddiskVolume1\ProgHack\c\LdrpHashTable\HideDll.exe (Addre ss : 0x400000 - SizeOfImage : 0x960) MappedFile : \Device\HarddiskVolume1\WINDOWS\system32\msvcrt.dll (Address : 0x77be0000 - SizeOfImage : 0x58000) MappedFile : \Device\HarddiskVolume1\WINDOWS\system32\kernel32.dll (Address : 0x7c800000 - SizeOfImage : 0x106000) MappedFile : \Device\HarddiskVolume1\WINDOWS\system32\ntdll.dll (Address : 0x7c910000 - SizeOfImage : 0xb9000) C:\ProgHack\c\LdrpHashTable>
Cool on retrouve bien notre ntdll.dll ! D’après ce que je sais, le seul moyen pour planquer notre module c’est de hooker le syscall ZwQueryVirtualMemory en kernel-land …
Au final vous avez compris qu’il existe plusieurs manières pour retrouver les DLLs d’un processus. On a vu que pour être sur de nos infos il faut utiliser ZwQueryVirtualMemory mais dans le cas ou cette API est hookée et que l’attaquant oublie la LdrpHashTable on peut encore s’en sortir. Bien sur ce n’est pas la technique ultime mais il est toujours bon de l’avoir sous le bras, c’est n’est pas difficile à comprendre et à implémenter en plus.
Peut être qu’un jour le monstrueux DLL Hell arrêtera de nous poser des problèmes.
Vous trouverez le code est les binaires ici :
http://ivanlef0u.fr/repo/LdrpHashTable.rar
Pour finir une petite selection de son parce que vous le valez bien
An Inch Above Sand du dernier album ‘What We All Come To Need’ de Pelican.
An Inch Above Sand
Postponed. End de l’EP ‘Samsara’ de Deviniance.
Postponed. End
Hordes To War de l’album ‘All Shall Fall’ de Immortal.
Hordes To War
Enfin Bleed de l’album ‘Obzen’ de Meshuggah parce que ca rigole zéro :
Bleed
12 comments décembre 28th, 2009
L | Ma | Me | J | V | S | D |
---|---|---|---|---|---|---|
« fév | ||||||
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 |