Un shellcode fait appel à des fonctions du système: par exemple, execve qui permet de lancer un programme. Windows place ses fonctions dans des librairies au format Dynamic Link Library (DLL) (par comparaison, Linux fournit un accès direct au Kernel via les appels système int 0x80). Cependant, ces adresses évoluent d'une version de l'OS à l'autre. Microsoft a fait ce choix pour ne pas être limité dans son nombre d'appels systèmes.
Deux fonctions permettent de trouver l'adresse d'une librairie, et celle d'une fonction. Il s'agit de LoadLibraryA et de GetProcAddress, toutes deux situées dans le module kernel32.dll. Il nous suffira donc de connaître ces trois adresses pour ensuite accéder à celles d'autres fonctions. Le but de ce tutoriel est de présenter cette méthode.
Ce tutoriel fait suite aux tutoriels d'initiation au Shellcoding sous Linux (cf référence n°1).
Outils
- gcc, g++, ld, objdump
Ces programmes Linux ont été portés sous windows grâce à MinGW. Téléchargez l'installeur Windows sur le site officiel http://www.mingw.org/wiki/Getting_Started .
- gdb
Nous n'utilisons en fait pas gdb dans ce tutoriel. Mais il serait dommage de ne pas l'avoir installé :) . Le désassembleur gdb n'est pas inclus par défaut avec MinGW. Téléchargez le sur le site de MinGW, il sera installé avec les autres utilitaires dans C:\MinGW\bin
- ollydbg
- nasm
Remarque: pour éviter de devoir à chaque fois taper tout le chemin C:\MinGW\bin\gcc et C:\Program Files\NASM, modifiez les variables d'environnement: clic droit sur Poste de Travail, Propriétés, Avancé, Variables d'environnement, PATH, Modifier. Ajoutez ;C:\MinGW\bin;C:\Program Files\NASM . Relancez votre ordinateur.
- Windbg et les Windows symbol packages:
Windbg est l'outil de débugging fourni par Microsoft. Il permet une analyse du kernel. Le logiciel et ses compléments sont téléchargeables sur le site de Microsoft: http://www.microsoft.com/whdc/devtools/debugging/default.mspx (référence n°7).
Windows symbol packages: Ces symboles sont nécessaires pour fournir les structures de données de Windows à Windbg. Il existe un fichier (environ 200 Mo, allez prendre un café pendant le téléchargement...) pour chaque version de Windows. Dans cet article, nous utilisons la version Windows XP SP3 32 bits. Ils se téléchargent sur le même site que Windbg. Le répertoire d'installation par défaut est C:\Windows\Symbols. Dans Windbg, tapez CTRL-K pour ouvrir le mode "Kernel Debug", choisissez l'onglet "Local", puis OK. Tapez CTRL-S qui ouvre la fenêtre "Symbol File Path", entrez le chemin vers le "downstream store"
SRV*C:\Windows\Symbols*http://msdl.microsoft.com/download/symbols , cochez la case "Reload" puis OK.
- DLL export viewer:
DLL export viewer est un utilitaire freeware édité par nirsoft ( http://www.nirsoft.net/utils/dll_export_viewer.html ). Il permet d'afficher la liste des fonctions exportées par une DLL, ainsi que leurs adresses. Nous verrons plus tard ce que cela signifie.
- PE Tools
PE Tools permet d'obtenir un dump d'une DLL placée en mémoire. http://www.uinc.ru (ajout: attention: cette version contient un trojan :)
- HexWorkShop
HexWorkShop permet d'afficher un dump. http://www.hexworkshop.com
- odfhex.cpp (cf annexe 1) outil créé par Steve Hanna (mailto:shanna@uiuc.edu), cf référence n°2, pour extraire le shellcode fourni par "objdump -d" et formater une chaîne de caractères de type shellcode "\xb0\x80...".
Dans un fichier sleep.asm, tapez:
char code[] = "\x31\xc0\xbb\x46\x24\x80\x7c\x66\xb8\x88\x13\x50\xff\xd3";
Ce shellcode suspend le processus parent durant 5 secondes. Bien sûr, après les 5 secondes, le programme crash probablement, puisque la pile a été modifiée!
"\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xeb\x37\x59\x88\x51\x0a\xbb\x7b\x1d"\
"\x80\x7c\x51\xff\xd3\xeb\x39\x59\x31\xd2\x88\x51\x0b\x51\x50\xbb\x40"\
"\xae\x80\x7c\xff\xd3\xeb\x39\x59\x31\xd2\x88\x51\x0c\x31\xd2\x52\x51"\
"\x51\x52\xff\xd0\x31\xc0\x50\xb8\x12\xcb\x81\x7c\xff\xd0\xe8\xc4\xff"\
"\xff\xff\x75\x73\x65\x72\x33\x32\x2e\x64\x6c\x6c\x4e\xe8\xc2\xff\xff"\
"\xff\x4d\x65\x73\x73\x61\x67\x65\x42\x6f\x78\x41\x4e\xe8\xc2\xff\xff"\
"\xff\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21\x4e\xff";
Compilons:
Nous avons donc vu comment loader un module en mémoire et utiliser une fonction au sein de ce module. Pour cela, nous avons utilisé les adresses en mémoire statiques des fonction LoadLibrary et GetProcAddress.
Nous allons à présent voir comment trouver de façon dynamique l'adresse des fonctions LoadLibrary et GetProcAddress. Cependant, il nous faut d'abord trouver l'adresse du module qui les contient: Kernel32.
Pour assurer la portabilité de notre shellcode d'une version à l'autre de Windows, nous devons pouvoir trouver dynamiquement l'adresse de kernel32.dll . Pour cela, trois méthodes sont documentées dans l'article de Jérôme Athias (référence n°3). Nous nous baserons également sur l'article de Skape (référence n°15). Etudions pour commencer la méthode utilisant le Process Environment Block (PEB).
"\x31\xc0\x64\x8b\x40\x30\x8b\x40\x0c\x8b\x70\x1c\xad\x8b\x40\x08\x31"\
"\xc0\x50\xb8\x12\xcb\x81\x7c\xff\xd0\xff"
Compilons:


Quelques explications s'imposent.
A moins que son code n'ait été compilé en statique, le processus cible
- d'une part load systématiquement dans l'ordre, les modules ntdll.dll et kernel32.dll,
- d'autre part crée une liste de modules dans l'ordre de leur initialisation. Dans ce cas, kernel32.dll est le premier module initialisé.
Pour accéder à ce dernier, nous avons donc deux choix. Soit nous nous intéressons aux modules:
- dans leur ordre de mise en mémoire (InLoadOrderModuleList),
- dans leur ordre d'initialisation (InInitializationOrderModuleList).
Le second choix nécessitant une étape en moins, nous le préférerons au premier.
Nous avons donc:
- copié fs+0x30 dans eax: Cela stocke dans eax l'adresse du Process Environment Block (PEB). Cette adresse se trouve à l'offset 0x30 dans le Thread Environment Block (TEB). L'adresse du TEB est placée dans le registre fs.
Puis, nous avons copié successivement le contenu situé à l'adresse :
- eax+0x0c dans eax. Cela stocke dans eax l'adresse du loader de modules: Ldr (possédant une structure PEB_LDR_DATA).
- eax+0x1c dans eax. Cela stocke dans eax l'adresse de la tête (head) de la liste InInitializationOrderModuleList. Nous reviendrons sur les "doubly linked list" après.
- lodsd. Cela stocke dans eax l'adresse de la structure (de type LDR_DATA_TABLE_ENTRY) suivante dans la liste. Elle décrit l'accès à kernel32.dll,
- eax+0x08. Une petite soustraction est nécessaire. Le Flink de InInitializationOrderModuleList est placé à l'octet 0x10 de la structure. Celui de l'adresse qui nous intéresse est placé à l'octet 0x18.
Donc nous ajoutons 0x08. Cela stocke dans eax l'adresse de kernel32.dll.

Le TEB (auparavant appelé Thread Information Block) est une structure, placée en mémoire user land. Il décrit le thread.
Le PEB est une structure, placée en mémoire user land, qui décrit le processus. cf Wikipédia (référence n°6) pour davantage d'informations.
Le loader Ldr a une structure de type PEB_LDR_DATA. L'information qui nous intéresse à l'intérieur est la "doubly linked list" InInitializeOrderModuleList.
Les "doubly linked list" (cf MSDN, référence n°10) forment une chaîne ciculaire de structures. Chacune de ces structures possède dans ses éléments une _LIST_ENTRY constituée de deux pointeurs:
- Flink pointe sur la _LIST_ENTRY de la structure suivante,
- Blink pointe sur la _LIST_ENTRY de la structure précédente.
Dans notre cas, les structures concernées sont des LDR_DATA_TABLE_ENTRY (cf la description de la structure PEB_LDR_DATA dans MSDN référence n°11)
Il est donc très facile de "sauter" d'une LDR_DATA_TABLE_ENTRY à une autre.
La structure LDR_DATA_TABLE_ENTRY (cf MSDN, référence n°11) donne des informations sur le module loadé. Entre autre son nom, et ... son adresse!
Et si, comme Saint Thomas, vous ne croyez que ce que vous voyez, décomposons ces différentes structures de données pour mieux comprendre. Cette méthode est tirée de l'article de lilxam (référence n°5). Remarque: Si vous préférez utiliser OllyDbg, la méthode est bien expliquée dans le wiki de Cipher et Skylined (référence n°8).
Cette méthode a un inconvénient. Il est possible que le programme implémente son propre système de gestion des exceptions. Dans ces rares cas, ce shellcode ne fonctionnera évidemment pas.
"\x31\xc9\x64\x8b\x01\xf7\xd1\x89\xc6\xad\x39\x08\x75\xf9\x8b\x46\x04"\
"\x48\x66\x31\xc0\x66\x81\x38\x4d\x5a\x75\xf5\x31\xc0\x50\xb8\x12\xcb"\
"\x81\x7c\xff\xd0";
Compilons:


Quelques explications s'imposent.
Pour accéder à la liste des Exception Handlers, nous passons par le TEB vu précédemment. Son premier enregistrement pointe vers une structure appelée _NT_TIB.

Figure: Walking the chain of exception registration records. Extrait de l'article de Skape (référence n°16)
Lorsque toute la liste des Exception Handlers a été parcourue, l'offset 0x04 de l'avant dernier enregistrement pointe sur une fonction contenue dans le module kernel32.
Une fois l'adresse de cette fonction trouvée, il faut trouver le début de kernel32. Pour cela, observons à quoi ressemble une librairie en mémoire.
Lorsque le module Kernel32 est loadé, l'OS copie en mémoire une image du fichier Kernel32.dll. Celle-ci possède la même structure que le fichier. (cf tutoriel de Joachim Bauch sur la facon de loader une librairie depuis la mémoire, référence n°13). Les fichiers DLL possèdent une structure de type Portable Exécutable (PE). La voici (cf référence n°12):

Le début de cette structure est matérialisé par le mot 'MZ' (0x5a4d) aligné sur le début d'un bloc de 64 bits. Remarque: L'acronyme MZ provient des initiales de Mark Zbikowski, ancien employé de Microsoft, inventeur du format EXE. Comme nous avons trouvé l'un des éléments inclus dans la structure de Kernel32, nous remontons mot par mot dans la structure en vérifiant à chaque fois si le début du bloc dans lequel nous nous trouvons est 'MZ'.
Nous avons donc vu la méthode SEH pour accéder à l'adresse Kernel32. Remarque: Skape propose dans son article (référence n°16) une méthode pour du SEH overflow: il est possible d'injecter du code dans l'un des exception_registration_record. Il propose aussi un moyen de s'en prémunir.
Voyons à présent la méthode pour trouver Kernel32 par TOPSTACK.
"\x31\xc0\x64\x8b\x40\x04\x8b\x40\xe4\x48\x66\x31\xc0\x66\x81\x38\x4d"\
"\x5a\x75\xf5\x31\xc0\x50\xb8\x12\xcb\x81\x7c\xff\xd0";
Compilons:

A quoi correspond l'adresse placée en 28ème position de la pile?
S'agit il d'une fonction? Pour le savoir, utilisons l'utilitaire DLL export viewer. Nous constatons que ce n'est pas le cas puisque aucune fonction exportée n'a pour adresse celle affichée par Ollydbg:

Il s'agit donc d'une adresse située dans la fonction ValidateLocale, à l'offset 2b0.
Voyons un dump de Kernel32 en mémoire. Utilisons PE Tools puis HexWorkShop:

L'adresse qui nous intéresse est placée à l'offset 0x 39ad8 dans Kernel32:

Nous pouvons remarquer que les caractères "Nls Section" sont placés quelques octets avant l'offset 0x39AD8 au sein de Kernel32.
A ce stade, nous n'avons pas encore trouvé à quoi servait ce pointeur...
[UNDER CONSTRUCTION]
Nous avons donc trouvé une adresse située dans kernel32. Il nous suffit alors de rechercher le MZ comme dans l'exemple précédent.
Grâce aux précédents exemples, vous avez pu comprendre le chaînage des structures qui décrivent un processus. Ainsi il vous a été possible de retrouver l'adresse de kernel32.dll de trois facons différentes. Mais cette adresse ne nous sert à rien pour le moment. Nous avons besoin des adresses des fonctions LoadLibrary et GetProcAddress. Ce que nous allons voir dans les exemples suivants.
"\x31\xc0\x31\xdb\x31\xc9\x31\xf6\x31\xff\x64\x8b\x40\x30\x8b\x40\x0c"\
"\x8b\x70\x1c\xad\x8b\x50\x08\x8b\x5a\x3c\x8b\x5c\x1a\x78\x01\xd3\x8b"\
"\x4b\x18\x8b\x43\x20\x01\xd0\x49\x38\xc0\x74\x38\x5f\x8b\x34\x88\x01"\
"\xd6\x51\x31\xc9\x80\xc1\x0e\xf3\xa6\x59\x75\xea\x8b\x43\x24\x01\xd0"\
"\x66\x8b\x0c\x48\x66\x8b\x43\x10\x66\x01\xc1\x66\x49\x8b\x43\x1c\x01"\
"\xd0\x8b\x04\x88\x01\xd0\x50\x31\xc0\x50\xb8\x12\xcb\x81\x7c\xff\xd0"\
"\xe8\xc3\xff\xff\xff\x47\x65\x74\x50\x72\x6f\x63\x41\x64\x64\x72\x65"\
"\x73\x73";
Compilons:

Relancez l'exécution. A présent observez la pile, elle contient l'adresse de GetProcAddress:

Quelques explications s'imposent à nouveau.
Lorsque la librairie Kernel32 est loadée, l'OS copie en mémoire une image du fichier Kernel32.dll. Celle-ci possède la même structure que le fichier. (cf tutoriel de Joachim Bauch sur la facon de loader une librairie depuis la mémoire, référence n°13). Les fichiers DLL possèdent une structure de type Portable Exécutable (PE). La voici (cf référence n°12):

En gros, nous avons:
MSDOS Stub < - structure _IMAGE_DOS_HEADER
------------------------
Unused
------------------------
PE Header <- structure _IMAGE_NT_HEADERS
------------------------
Section Headers, etc.
Nous allons surtout nous intéresser au PE Header. Quelle est l'adresse qui permet d'y accéder?
Avant tout, il faut savoir que tous les pointeurs à l'intérieur du PE sont des Relative Virtual Address (RVA). Cela signifie que ce sont des offsets relatifs à l'adresse du début de la structure. Ainsi:
Adresse = Kernel32 + offset
Le dernier élément du MSDOS STUB pointe vers le premier élément du PE Header. En effet, dans MSDN - Microsoft Portable Executable and Common Object File Format Specification (référence n°12, page 7):
"At location 0x3c, the stub has the file offset to the PE signature. This information enables Windows to properly execute the image file, even though it has an MS‑DOS stub. This file offset is placed at location 0x3c during linking."
Vérifions avec Windbg que le dernier élément du MSDOS Stub est bien placé à l'offset 0x3c:
Donc, si nous récapitulons, adresse PE Header = adresse DLL + 0x3c
PE Header = Kernel32.dll + 0x3c
Le Optional Header possède trois grandes parties:
- les champs standards (standard fields),
- les champs spécifiques Windows (Windows-specific fields),
- les data directories.
La première est celle qui nous intéresse. Il s'agit de l'Export Directory Table: la table qui liste les fonctions exportées. Son offset est:
offset de PE signature + offset du Optional Header + offset Data Directory = PE_signature + 0x18 + 0x60
donc
ExportDirectoryTable = PE_signature + 0x78
A quoi ressemble cette Export Directory Table? cf MSDN (référence n°12, chapitre 6.3.1, page 51):
Cette table ne nous donne pas tout de suite l'adresse de notre fonction. Par contre, elle nous donne toutes les informations qui nous seront nécessaires :
- l'offset de la table Export Name Pointer Table (32d = 0x20),
- le nombre de fonctions accessibles depuis la DLL (Number of Name Pointers 24d = 0x18).
- l'offset de la table Ordinal Table (36d = 0x24),
- l'offset de la table Export Address Table (28d = 0x1c),
- le nombre ordinal de départ, généralement égal à 1: Ordinal Base (16d=0x10)
Pour trouver l'adresse de la fonction, nous devons d'abord trouver l'index de son nom, puis son ordinal, et enfin son adresse.
L'Export Name Pointer Table est un tableau de pointeurs qui pointent vers les noms des fonctions, indexés dans l'ordre lexical des noms de fonctions:
L'Ordinal Table est un tableau d'ordinaux (des nombres codés sur 16 bits, donc 2 octets. C'est utile à savoir car il faudra multiplier par deux l'index trouvé), indexés dans ce même ordre lexical.
L'Export Address Table est un tableau d'offsets qui pointent vers les fonctions, indexés dans l'ordre ordinal.
Voici un schéma tiré du livre Expert .NET 2.0 IL Assember (référence n°14)

Schéma d'interactions entre les tables Export Directory Table, Export Name Pointer Table et Export Address Table (référence n°14)
Récapitulons, nous devons :
- trouver la position du nom de notre fonction dans l'Export Name Pointer Table,
- récupérer l'ordinal situé à la même position dans l'Ordinal Table, sans oublier de multiplier par deux l'index trouvé
- récupérer l'adresse de la fonction située à l'ordinal trouvé dans l'Export Address Table.
A ce stade, nous avons su trouver l'adresse de la fonction GetProcAddress. Il nous suffit de reproduire la méthode pour trouver celle de LoadLibraryA.
"\x31\xc0\x31\xdb\x31\xf6\x31\xff\x64\x8b\x40\x30\x8b\x40\x0c\x8b\x70"\
"\x1c\xad\x8b\x50\x08\x8b\x5a\x3c\x8b\x5c\x1a\x78\x01\xd3\x31\xc9\x51"\
"\xe3\x53\x80\xf9\x01\x74\x61\x38\xc0\x74\x40\x8b\x4b\x18\x8b\x43\x20"\
"\x01\xd0\x49\x5f\x57\x8b\x34\x88\x01\xd6\x51\x31\xc9\x80\xc1\x0c\xf3"\
"\xa6\x59\x75\xed\x5f\x8b\x43\x24\x01\xd0\x66\x8b\x0c\x48\x66\x8b\x43"\
"\x10\x66\x01\xc1\x66\x49\x8b\x43\x1c\x01\xd0\x8b\x04\x88\x01\xd0\x59"\
"\x41\x50\x38\xc0\x74\xb5\x59\x31\xc0\x50\xb8\x12\xcb\x81\x7c\xff\xd0"\
"\xe8\xb1\xff\xff\xff\x47\x65\x74\x50\x72\x6f\x63\x41\x64\x64\x72\x65"\
"\x73\x73\xe8\x9e\xff\xff\xff\x4c\x6f\x61\x64\x4c\x69\x62\x72\x61\x72"\
"\x79\x41";
Compilons:

conclusion
Arrivés à ce point du tutoriel, vous avez pu écrire vos premiers shellcodes Windows. Vous avez utilisé un panel varié d'outils. Vous avez pu également constater que l'une des difficultés est le linkage des librairies.
Par la suite, nous pourrons aborder l'ouverture d'un shell distant avec privilèges d'administrateur sous Windows.
Références
1) Ghosts in the Stack - article de Trance - Les Shellcodes - http://www.ghostsinthestack.org/article-5-les-shellcodes.html
2) Vividmachines - article de Steve Hanna - Shellcoding for Linux and Windows Tutorial - http://www.vividmachines.com/shellcode/shellcode.html
3) Securinfos - article de Jerôme Athias - Trouver l'adresse de base de kernel32.dll : Trois méthodes PEB, SEH et TopStack - https://www.securinfos.info/jerome/DOC/Trouver_kernel32.pdf
4) Nibbles, article de lilxam - Playing with PEB, listing process modules - http://nibbles.tuxfamily.org/?p=66
5) article de Neitsa - Ecrire sa propre méthode pour LoadLibrary et GetProcAddress - http://neitsabes.free.fr/RE/HIRE/OwnGetProc1.html
6) Wikipedia - Win32 Thread Information Block - http://en.wikipedia.org/wiki/Win32_Thread_Information_Block
7) Microsoft Windows Hardware Developer Central - Debugging Tools and Symbols - http://www.microsoft.com/whdc/devtools/debugging/debugstart.mspx
8) wiki skypher.com - Cipher et Skylined - TEB et PEB - http://skypher.com/wiki/index.php/Hacking/Windows_internals/Process/Memory/TEB
9) blog de j04n Calvet - Cacher un module en mémoire - http://joe-is-a-rocknroll-star.blogspot.com/2008/08/cachez-ce-module-que-je-ne-saurai-voir.html
10) MSDN - Singly and doubly linked lists - http://msdn.microsoft.com/en-us/library/aa489548.aspx
11) MSDN - PEB_LDR_DATA structure - http://msdn.microsoft.com/en-us/library/aa813708%28VS.85%29.aspx
12) MSDN - Microsoft Portable Executable and Common Object File Format Specification - http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx
13) tutoriel de Joachim Bauch - loading a dll from memory - http://www.joachim-bauch.de/tutorials/load_dll_memory.html
14) livre de Serge Lidin - Expert .NET 2.0 IL Assember - édition Apress - 2006 - page 64 - http://books.google.fr/books?id=oAcCRKd6EZgC&pg=PA64
15) tutoriel de skape - Understanding Windows Shellcode - www.hick.org/code/skape/papers/win32-shellcode.pdf
16) article de skape - Preventing the Exploitation of SEH Overwrites - http://uninformed.org/index.cgi?v=5&a=2&p=4
17) article de The Last Stage of Delirium Research Group - Win32 Assembly Components - http://ivanlef0u.free.fr/repo/windoz/shellcoding/winasm-1.0.1.pdf
Annexe: code source de odfhex.c
/**************************************
odfhex - objdump hex extractor
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:
g++ odfhex.cpp -o odfhex
note: the XOR option works
perfectly, but i haven't implemented
the full x86 payload decoder yet.
so that option is mostly useless.
this program extracts the hex values
from an "objdump -d".
after doing this, it converts the
hex into escaped hex for use in
a c/c++ program.
happy shellcoding!
***************************************/
#include < stdio.h >
#include < unistd.h >
#include < memory.h >
#include < string.h >
#include < stdlib.h >
#include < math.h >
#define HEX_PER_LINE 17
char symbols[37] = "0123456789abcdefghijklmnopqrstuvwxyz";
const int MAX_BASE = 36;
int GetIndex(char * pString, char search);
int BaseToDec(char* number, int base)
{
if( base < 2 || base > MAX_BASE)
return 0; //Failed
int NumLength = strlen(number);
int PlaceValue, total = 0;
PlaceValue = (int)pow(base,NumLength-1);
for(int i=0;i < NumLength;i++)
{
total += GetIndex(symbols,*number)*PlaceValue;
number++;
PlaceValue /= base; //Next digit's place value (previous/base)
}
return total;
}
int GetIndex(char * pString, char search)
{
int index = 0;
while(*pString != '0')
{
if(*pString==search)
break;
pString++;
index++;
}
return index;
}
int main(int argc, char** argv)
{
FILE* dump = NULL;
long length = 0;
char* content;
int i=0;
int count =0;
int total=0;
int XORvalue=0;
bool XORit = false;
char HexNumber[3]={'\0'};
printf("\nOdfhex - object dump shellcode extractor - by steve hanna - v.01\n");
if(argc < 2)
{
printf("%s: < object file dump > [-x xor offset in decimal] \n",argv[0]);
return -1;
}
dump = fopen(argv[1],"r");
if(!dump)
{
printf("Error: Couldn't open file.\n");
return -1;
}
fseek(dump,0,SEEK_END);
length = ftell(dump);
content = new char[length+1];
memset(content,0,sizeof(content));
printf("Trying to extract the hex of %s which is %d bytes long\n",argv[1],length);
if (argc > 3 && !strcmp(argv[2],"-x"))
{
XORit =true;
XORvalue = BaseToDec(argv[3],16);
printf("XORing with 0x%02x\n",XORvalue);
}
fseek(dump,0,SEEK_SET);
for(int i=0; i < length; i++)
{
content[i] = fgetc(dump);
}
fclose(dump);
while(count !=4)
{
if(content[i] == ':')
count++;
i++;
}
count = 0;
printf("\"");
while(i < length)
{
if( (content[i-1] == ' ' || content[i-1]=='\t') &&
(content[i+2] == ' ' ) &&
(content[i] != ' ') &&
(content[i+1] != ' ') &&
((content[i]>='0' && content[i]< ='9') || (content[i]>='a' && content[i]< ='f')) &&
((content[i+1]>='0' && content[i+1]< ='9') || (content[i+1]>='a' && content[i+1]< ='f'))
)
{
if(XORit)
{
HexNumber[0] = content[i];
HexNumber[1] = content[i+1];
printf("\\x%02x",BaseToDec(HexNumber,16) ^ XORvalue);
}
else
printf("\\x%c%c",content[i],content[i+1]);
count++;
total++;
}
if(i+1 == length)
{
printf("\";\n");
}
else if(count == HEX_PER_LINE)
{
printf("\"\\\n\"");
count =0;
}
i++;
}
delete[] content;
printf("\n%d bytes extracted.\n\n",total);
return 0;
}
Deux fonctions permettent de trouver l'adresse d'une librairie, et celle d'une fonction. Il s'agit de LoadLibraryA et de GetProcAddress, toutes deux situées dans le module kernel32.dll. Il nous suffira donc de connaître ces trois adresses pour ensuite accéder à celles d'autres fonctions. Le but de ce tutoriel est de présenter cette méthode.
Ce tutoriel fait suite aux tutoriels d'initiation au Shellcoding sous Linux (cf référence n°1).
- Exemple 1: Attendez 5 secondes...
- Exemple 2: Une boîte de dialogue "Hello World!"
- Exemple 3: Trouver l'adresse de kernel32.dll: méthode du PEB
- Exemple 4: Trouver l'adresse de kernel32.dll: méthode du SEH
- Exemple 5: Trouver l'adresse de kernel32.dll: méthode du TOPSTACK
- exemple 6: Trouver l'adresse de GetProcAddress
- exemple 7: Trouver l'adresse de GetProcAddress et LoadLibraryA
Outils
- gcc, g++, ld, objdump
Ces programmes Linux ont été portés sous windows grâce à MinGW. Téléchargez l'installeur Windows sur le site officiel http://www.mingw.org/wiki/Getting_Started .
- gdb
Nous n'utilisons en fait pas gdb dans ce tutoriel. Mais il serait dommage de ne pas l'avoir installé :) . Le désassembleur gdb n'est pas inclus par défaut avec MinGW. Téléchargez le sur le site de MinGW, il sera installé avec les autres utilitaires dans C:\MinGW\bin
- ollydbg
- nasm
Remarque: pour éviter de devoir à chaque fois taper tout le chemin C:\MinGW\bin\gcc et C:\Program Files\NASM, modifiez les variables d'environnement: clic droit sur Poste de Travail, Propriétés, Avancé, Variables d'environnement, PATH, Modifier. Ajoutez ;C:\MinGW\bin;C:\Program Files\NASM . Relancez votre ordinateur.
- Windbg et les Windows symbol packages:
Windbg est l'outil de débugging fourni par Microsoft. Il permet une analyse du kernel. Le logiciel et ses compléments sont téléchargeables sur le site de Microsoft: http://www.microsoft.com/whdc/devtools/debugging/default.mspx (référence n°7).
Windows symbol packages: Ces symboles sont nécessaires pour fournir les structures de données de Windows à Windbg. Il existe un fichier (environ 200 Mo, allez prendre un café pendant le téléchargement...) pour chaque version de Windows. Dans cet article, nous utilisons la version Windows XP SP3 32 bits. Ils se téléchargent sur le même site que Windbg. Le répertoire d'installation par défaut est C:\Windows\Symbols. Dans Windbg, tapez CTRL-K pour ouvrir le mode "Kernel Debug", choisissez l'onglet "Local", puis OK. Tapez CTRL-S qui ouvre la fenêtre "Symbol File Path", entrez le chemin vers le "downstream store"
SRV*C:\Windows\Symbols*http://msdl.microsoft.com/download/symbols , cochez la case "Reload" puis OK.
- DLL export viewer:
DLL export viewer est un utilitaire freeware édité par nirsoft ( http://www.nirsoft.net/utils/dll_export_viewer.html ). Il permet d'afficher la liste des fonctions exportées par une DLL, ainsi que leurs adresses. Nous verrons plus tard ce que cela signifie.
- PE Tools
PE Tools permet d'obtenir un dump d'une DLL placée en mémoire. http://www.uinc.ru (ajout: attention: cette version contient un trojan :)
- HexWorkShop
HexWorkShop permet d'afficher un dump. http://www.hexworkshop.com
- odfhex.cpp (cf annexe 1) outil créé par Steve Hanna (mailto:shanna@uiuc.edu), cf référence n°2, pour extraire le shellcode fourni par "objdump -d" et formater une chaîne de caractères de type shellcode "\xb0\x80...".
Pour le compiler, placez odfhex.cpp dans C:\temp et tapez- arwin.c (cf annexe 2) outil créé également par Steve Hanna pour trouver l'adresse absolue d'une fonction windows pour une DLL donnée.
C:> g++ -o odfhex C:\temp\odfhex.cpp puis récupérez l'exécutable créé.
Pour le compiler, placez arwin.c dans C:\temp et tapez- shellcodetest.c programme pour tester les shellcodes que nous aurons élaborés:
C:> gcc -o arwin C:\temp\arwin.c puis récupérez l'exécutable créé.
/*shellcodetest.c*/
char code[] = "mettre la chaîne de caractère du shellcode ici";
int main(int argc, char **argv)
{
int (*func)();
func = (int (*)()) code;
(int)(*func)();
}
Pour le compiler, placez shellcodetest.c dans C:\temp et tapez
C:> gcc -o shellcodetest C:\temp\shellcodetest.c puis récupérez l'exécutable créé.
Exemple 1: Attendez 5 secondes...
La première chose à faire est de définir quelles fonctions seront nécessaires dans notre shellcode, et trouver leur adresse absolue. Utilisons d'abord la fonction Sleep, contenue dans kernel32.dll.C:> arwin kernel32.dll Sleep
arwin - win32 address resolution program - by steve hanna - v.01
Sleep is located at 0x7c802446 in kernel32.dll
Dans un fichier sleep.asm, tapez:
;sleep.asm
xor eax,eax
mov ebx, 0x7c802446 ;adresse de Sleep
mov ax, 5000 ;pause durant 5000ms
push eax
call ebx ;Sleep(ms);
C: > nasm -f elf sleep.asmRemplacez le code dans Shellcodetest.c par
C: > ld -o sleep sleep.o
C: > objdump -d sleep
sleep: file format pei-i386
Disassembly of section .text:
00401000 < _start >:
401000: 31 c0 xor %eax,%eax
401002: bb 46 24 80 7c mov $0x7c802446,%ebx
401007: 66 b8 88 13 mov $0x1388,%ax
40100b: 50 push %eax
40100c: ff d3 call *%ebx
char code[] = "\x31\xc0\xbb\x46\x24\x80\x7c\x66\xb8\x88\x13\x50\xff\xd3";
Ce shellcode suspend le processus parent durant 5 secondes. Bien sûr, après les 5 secondes, le programme crash probablement, puisque la pile a été modifiée!
Exemple 2: Une boîte de dialogue "Hello World!"
Ce programme affiche une boîte de dialogue "Hello World!". Il illustre l'utilisation de LoadLibraryA et GetProcAddress pour trouver dynamiquement l'adresse de MessageBoxA.C: > arwin kernel32.dll LoadLibraryADans un fichier msgbox.asm, tapez:
arwin - win32 address resolution program - by steve hanna - v.01
LoadLibraryA is located at 0x7c801d7b in kernel32.dll
C: > arwin kernel32.dll GetProcAddress
arwin - win32 address resolution program - by steve hanna - v.01
GetProcAddress is located at 0x7c80ae40 in kernel32.dll
C: > arwin kernel32.dll ExitProcess
arwin - win32 address resolution program - by steve hanna - v.01
ExitProcess is located at 0x7c81cb12 in kernel32.dll
;msgbox.asm
xor eax,eax
xor ebx,ebx ;registres à zero
xor ecx,ecx
xor edx,edx
jmp short @@AllerLibrairie
@@RetourLibrairie:
pop ecx ;pop l'adresse du début de la chaîne du module dans ecx
mov [ecx + 10], dl ;insère NULL à la fin de la chaîne
mov ebx, 0x7c801d7b ;LoadLibraryA(libraryname);
push ecx ;début de user32.dll
call ebx ;stocke l'adresse du module dans eax
jmp short @@AllerMessageBox
@@RetourMessageBox:
pop ecx ;pop l'adresse du début de la chaîne MessageBoxA
xor edx,edx
mov [ecx + 11],dl ;insère NULL
push ecx
push eax
mov ebx, 0x7c80ae40 ;GetProcAddress(hmodule,functionname);
call ebx ;stocke l'adresse de la fonction MessageBoxA dans eax
jmp short @@AllerMessage
@@RetourMessage:
pop ecx ;pop l'adresse du début de la chaîne Hello World!
xor edx,edx
mov [ecx+12],dl ;insère NULL
xor edx,edx
push edx ;MB_OK
push ecx ;titre
push ecx ;message
push edx ;NULL window handle
call eax ;MessageBoxA(windowhandle,msg,title,type); Address
@@SortieProcessus:
xor eax,eax
push eax
mov eax, 0x7c81cb12 ;exitprocess(exitcode);
call eax
;le N à la fin de chaque chaîne correspond à l'emplacement du NULL
@@AllerLibrairie:
call @@RetourLibrairie
db 'user32.dllN'
@@AllerMessageBox:
call @@RetourMessageBox
db 'MessageBoxAN'
@@AllerMessage:
call @@RetourMessage
db 'Hello World!N'
C: > nasm -f elf msgbox.asmEt pour se simplifier la vie:
C: > ld -o msgbox msgbox.o
C: > objdump -d msgbox
msgbox: file format pei-i386
Disassembly of section .text:
00401000 < _debut >:
401000: 31 c0 xor %eax,%eax
401002: 31 db xor %ebx,%ebx
401004: 31 c9 xor %ecx,%ecx
401006: 31 d2 xor %edx,%edx
401008: eb 37 jmp 401041 < _debut+0x41 >
40100a: 59 pop %ecx
40100b: 88 51 0a mov %dl,0xa(%ecx)
40100e: bb 7b 1d 80 7c mov $0x7c801d7b,%ebx
401013: 51 push %ecx
401014: ff d3 call *%ebx
401016: eb 39 jmp 401051 < _debut+0x51 >
401018: 59 pop %ecx
401019: 31 d2 xor %edx,%edx
40101b: 88 51 0b mov %dl,0xb(%ecx)
40101e: 51 push %ecx
40101f: 50 push %eax
401020: bb 40 ae 80 7c mov $0x7c80ae40,%ebx
401025: ff d3 call *%ebx
401027: eb 39 jmp 401062 < _debut+0x62 >
401029: 59 pop %ecx
40102a: 31 d2 xor %edx,%edx
40102c: 88 51 0c mov %dl,0xc(%ecx)
40102f: 31 d2 xor %edx,%edx
401031: 52 push %edx
401032: 51 push %ecx
401033: 51 push %ecx
401034: 52 push %edx
401035: ff d0 call *%eax
401037: 31 d2 xor %edx,%edx
401039: 50 push %eax
40103a: b8 12 cb 81 7c mov $0x7c81cb12,%eax
40103f: ff d0 call *%eax
401041: e8 c4 ff ff ff call 40100a < _debut+0xa >
401046: 75 73 jne 4010bb < __DTOR_LIST__+0x3f >
401048: 65 gs
401049: 72 33 jb 40107e < __DTOR_LIST__+0x2 >
40104b: 32 2e xor (%esi),%ch
40104d: 64 fs
40104e: 6c insb (%dx),%es:(%edi)
40104f: 6c insb (%dx),%es:(%edi)
401050: 4e dec %esi
401051: e8 c2 ff ff ff call 401018 < _debut+0x18 >
401056: 4d dec %ebp
401057: 65 gs
401058: 73 73 jae 4010cd < __DTOR_LIST__+0x51 >
40105a: 61 popa
40105b: 67 addr16
40105c: 65 gs
40105d: 42 inc %edx
40105e: 6f outsl %ds:(%esi),(%dx)
40105f: 78 41 js 4010a2 < __DTOR_LIST__+0x26 >
401061: 4e dec %esi
401062: e8 c2 ff ff ff call 401029 < _debut+0x29 >
401067: 48 dec %eax
401068: 65 gs
401069: 6c insb (%dx),%es:(%edi)
40106a: 6c insb (%dx),%es:(%edi)
40106b: 6f outsl %ds:(%esi),(%dx)
40106c: 20 57 6f and %dl,0x6f(%edi)
40106f: 72 6c jb 4010dd < __DTOR_LIST__+0x61 >
401071: 64 21 4e ff and %ecx,%fs:-0x1(%esi)
C: > objdump -d msgbox > msgbox.tmpLes derniers octets n'appartenant pas à la section _debut, nous les supprimons. D'où le shellcode que nous injectons dans shellcodetest.c:
C: > odfhex msgbox.tmp
Odfhex - object dump shellcode extractor - by steve hanna - v.01
Trying to extract the hex of msgbox.tmp which is 3715 bytes long
"\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xeb\x37\x59\x88\x51\x0a\xbb\x7b\x1d"\
"\x80\x7c\x51\xff\xd3\xeb\x39\x59\x31\xd2\x88\x51\x0b\x51\x50\xbb\x40"\
"\xae\x80\x7c\xff\xd3\xeb\x39\x59\x31\xd2\x88\x51\x0c\x31\xd2\x52\x51"\
"\x51\x52\xff\xd0\x31\xc0\x50\xb8\x12\xcb\x81\x7c\xff\xd0\xe8\xc4\xff"\
"\xff\xff\x75\x73\x65\x72\x33\x32\x2e\x64\x6c\x6c\x4e\xe8\xc2\xff\xff"\
"\xff\x4d\x65\x73\x73\x61\x67\x65\x42\x6f\x78\x41\x4e\xe8\xc2\xff\xff"\
"\xff\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21\x4e\xff\xff\xff"\
"\xff\xff\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00";
131 bytes extracted.
"\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xeb\x37\x59\x88\x51\x0a\xbb\x7b\x1d"\
"\x80\x7c\x51\xff\xd3\xeb\x39\x59\x31\xd2\x88\x51\x0b\x51\x50\xbb\x40"\
"\xae\x80\x7c\xff\xd3\xeb\x39\x59\x31\xd2\x88\x51\x0c\x31\xd2\x52\x51"\
"\x51\x52\xff\xd0\x31\xc0\x50\xb8\x12\xcb\x81\x7c\xff\xd0\xe8\xc4\xff"\
"\xff\xff\x75\x73\x65\x72\x33\x32\x2e\x64\x6c\x6c\x4e\xe8\xc2\xff\xff"\
"\xff\x4d\x65\x73\x73\x61\x67\x65\x42\x6f\x78\x41\x4e\xe8\xc2\xff\xff"\
"\xff\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21\x4e\xff";
Compilons:
C: > gcc -o shellcodetest shellcodetest.cEt notre shellcode fonctionne... tout au moins sous windows XP SP3 :)
C: > shellcodetest
Nous avons donc vu comment loader un module en mémoire et utiliser une fonction au sein de ce module. Pour cela, nous avons utilisé les adresses en mémoire statiques des fonction LoadLibrary et GetProcAddress.
Nous allons à présent voir comment trouver de façon dynamique l'adresse des fonctions LoadLibrary et GetProcAddress. Cependant, il nous faut d'abord trouver l'adresse du module qui les contient: Kernel32.
Exemple 3: Trouver l'adresse de kernel32.dll: méthode du PEB
Comment trouver dynamiquement l'adresse de kernel32.dll?Pour assurer la portabilité de notre shellcode d'une version à l'autre de Windows, nous devons pouvoir trouver dynamiquement l'adresse de kernel32.dll . Pour cela, trois méthodes sont documentées dans l'article de Jérôme Athias (référence n°3). Nous nous baserons également sur l'article de Skape (référence n°15). Etudions pour commencer la méthode utilisant le Process Environment Block (PEB).
C: > arwin kernel32.dll ExitProcessDans Kernel32PEB.asm, tapez:
arwin - win32 address resolution program - by steve hanna - v.01
ExitProcess is located at 0x7c81cb12 in kernel32.dll
;kernel32PEB.asm
@@TrouveKernel32:
xor eax,eax
mov eax, [fs:eax+0x30] ; eax = adresse du PEB
mov eax, [eax + 0x0c] ; eax = adresse du loader
mov esi, [eax + 0x1c] ; esi = adresse de la liste des modules initialisés par le processus
lodsd ; esi = entrée suivante de la liste qui concerne kernel32.dll
mov eax, [eax + 0x8] ; eax = adresse du module kernel32.dll
@@SortieProcessus:
xor eax,eax
push eax
mov eax, 0x7c81cb12 ; exitprocess(exitcode);
call eax
C: > nasm -f elf kernel32PEB.asmNous obtenons le Shellcode suivant que nous collons dans shellcodetest.c :
C: > ld -o kernel32PEB kernel32PEB.o
C: > objdump -d kernel32PEB > kernel32PEB.tmp
C: > odfhex kernel32PEB.tmp
"\x31\xc0\x64\x8b\x40\x30\x8b\x40\x0c\x8b\x70\x1c\xad\x8b\x40\x08\x31"\
"\xc0\x50\xb8\x12\xcb\x81\x7c\xff\xd0\xff"
Compilons:
C: > gcc -o shellcodetest shellcodetest.cEffectivement, ce programme ne renvoit rien. En fait, l'adresse de kernel32.dll se trouve dans eax lors de la sortie du processus. Contentons nous de l'afficher grâce à Ollydbg:
C: > shellcodetest
Dans Ollydbg:
tapez la touche F3 et choisissez shellcodetest.exe
sélectionnez la première valeur "Hex Dump" (en bas à gauche) en cliquant dessus, puis placez un breakpoint sur le début du shellcode:(clic-droit sur 64, BreakPoint, Memory on Access)
Lancez ensuite le programme avec F9.
Avancez pas à pas avec F7 jusqu'à arriver à XOR EAX,EAX. L'adresse de kernel32.dll est placée alors dans eax:
Quelques explications s'imposent.
A moins que son code n'ait été compilé en statique, le processus cible
- d'une part load systématiquement dans l'ordre, les modules ntdll.dll et kernel32.dll,
- d'autre part crée une liste de modules dans l'ordre de leur initialisation. Dans ce cas, kernel32.dll est le premier module initialisé.
Pour accéder à ce dernier, nous avons donc deux choix. Soit nous nous intéressons aux modules:
- dans leur ordre de mise en mémoire (InLoadOrderModuleList),
- dans leur ordre d'initialisation (InInitializationOrderModuleList).
Le second choix nécessitant une étape en moins, nous le préférerons au premier.
Nous avons donc:
- copié fs+0x30 dans eax: Cela stocke dans eax l'adresse du Process Environment Block (PEB). Cette adresse se trouve à l'offset 0x30 dans le Thread Environment Block (TEB). L'adresse du TEB est placée dans le registre fs.
Puis, nous avons copié successivement le contenu situé à l'adresse :
- eax+0x0c dans eax. Cela stocke dans eax l'adresse du loader de modules: Ldr (possédant une structure PEB_LDR_DATA).
- eax+0x1c dans eax. Cela stocke dans eax l'adresse de la tête (head) de la liste InInitializationOrderModuleList. Nous reviendrons sur les "doubly linked list" après.
- lodsd. Cela stocke dans eax l'adresse de la structure (de type LDR_DATA_TABLE_ENTRY) suivante dans la liste. Elle décrit l'accès à kernel32.dll,
- eax+0x08. Une petite soustraction est nécessaire. Le Flink de InInitializationOrderModuleList est placé à l'octet 0x10 de la structure. Celui de l'adresse qui nous intéresse est placé à l'octet 0x18.
Donc nous ajoutons 0x08. Cela stocke dans eax l'adresse de kernel32.dll.
Le TEB (auparavant appelé Thread Information Block) est une structure, placée en mémoire user land. Il décrit le thread.
Le PEB est une structure, placée en mémoire user land, qui décrit le processus. cf Wikipédia (référence n°6) pour davantage d'informations.
Le loader Ldr a une structure de type PEB_LDR_DATA. L'information qui nous intéresse à l'intérieur est la "doubly linked list" InInitializeOrderModuleList.
Les "doubly linked list" (cf MSDN, référence n°10) forment une chaîne ciculaire de structures. Chacune de ces structures possède dans ses éléments une _LIST_ENTRY constituée de deux pointeurs:
- Flink pointe sur la _LIST_ENTRY de la structure suivante,
- Blink pointe sur la _LIST_ENTRY de la structure précédente.
Dans notre cas, les structures concernées sont des LDR_DATA_TABLE_ENTRY (cf la description de la structure PEB_LDR_DATA dans MSDN référence n°11)
Il est donc très facile de "sauter" d'une LDR_DATA_TABLE_ENTRY à une autre.
La structure LDR_DATA_TABLE_ENTRY (cf MSDN, référence n°11) donne des informations sur le module loadé. Entre autre son nom, et ... son adresse!
Et si, comme Saint Thomas, vous ne croyez que ce que vous voyez, décomposons ces différentes structures de données pour mieux comprendre. Cette méthode est tirée de l'article de lilxam (référence n°5). Remarque: Si vous préférez utiliser OllyDbg, la méthode est bien expliquée dans le wiki de Cipher et Skylined (référence n°8).
Ouvrez Windbg, tapez CTRL-K pour entrer en mode Kernel, choisissez l'onglet "Local", puis OK. Eventuellement, redéfinissez le "Symbol File Path".Affichons la structure du TEB. Le pointeur vers le PEB est placé en 0x30:
lkd > dt nt!_TEBAffichons la structure PEB. Le pointeur vers le loader LDR est placé en 0x0c:
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : Ptr32 Void
+0x02c ThreadLocalStoragePointer : Ptr32 Void
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB
+0x034 LastErrorValue : Uint4B
lkd > dt nt!_PEBAffichons la structure PEB_LDR_DATA. Elle contient trois pointeurs vers des structures LIST_ENTRY, dont l'une liste les modules dans leur ordre d'initialisation (Nous nous rapprochons...). Le pointeur vers cette liste est placé en 0x1c. Remarque: Cette structure peut d'ailleurs être exploitée pour cacher l'utilisation d'une DLL malveillante. Des outils comme ProcessExplorer n'y verront alors que du feu. Cf article de j04n Calvet (référence n°9).
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 SpareBool : UChar
+0x004 Mutant : Ptr32 Void
+0x008 ImageBaseAddress : Ptr32 Void
+0x00c Ldr : Ptr32 _PEB_LDR_DATA
+0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS
lkd > dt nt!_PEB_LDR_DATAAffichons la structure _LIST_ENTRY. Elle contient deux pointeurs: Flink pointe vers le Flink de la structure suivante, Blink pointe vers le Flink de la structure précédente. Dans notre cas, la liste contient autant de structures LDR_DATA_TABLE_ENTRY qu'il y a de modules loadés dans le processus.
+0x000 Length : Uint4B
+0x004 Initialized : UChar
+0x008 SsHandle : Ptr32 Void
+0x00c InLoadOrderModuleList : _LIST_ENTRY
+0x014 InMemoryOrderModuleList : _LIST_ENTRY
+0x01c InInitializationOrderModuleList : _LIST_ENTRY
+0x024 EntryInProgress : Ptr32 Void
lkd > dt nt!_LIST_ENTRYAffichons la structure _LDR_DATA_TABLE_ENTRY: L'adresse du module loadé est en 0x018.
+0x000 Flink : Ptr32 _LIST_ENTRY
+0x004 Blink : Ptr32 _LIST_ENTRY
lkd > dt nt!_ldr_data_table_entryRemarque: la taille du buffer qui contient le nom du module est placée à l'adresse 0x24. L'adresse du buffer qui contient son nom est placée en 0x28 (0x24 + 0x04). En effet:
+0x000 InLoadOrderLinks : _LIST_ENTRY
+0x008 InMemoryOrderLinks : _LIST_ENTRY
+0x010 InInitializationOrderLinks : _LIST_ENTRY
+0x018 DllBase : Ptr32 Void
+0x01c EntryPoint : Ptr32 Void
+0x020 SizeOfImage : Uint4B
+0x024 FullDllName : _UNICODE_STRING
+0x02c BaseDllName : _UNICODE_STRING
...
lkd > dt nt!_unicode_string
+0x000 Length : Uint2B
+0x002 MaximumLength : Uint2B
+0x004 Buffer : Ptr32 Uint2B
Exemple 4: Trouver l'adresse de kernel32.dll: méthode du SEH
La seconde méthode pour trouver l'adresse de Kernel32.dll utilise le Structure Exception Handling (SEH). Par défaut, le Unhandled Exception Handler est configuré pour utiliser une fonction du module Kernel32. Pour de plus amples informations sur le SEH, vous pouvez vous référer à l'excellent article de skape: Preventing the Exploitation of SEH Overwrites (référence n°16).Cette méthode a un inconvénient. Il est possible que le programme implémente son propre système de gestion des exceptions. Dans ces rares cas, ce shellcode ne fonctionnera évidemment pas.
Dans Kernel32SEH.asm, tapez:
; Kernel32SEH.asm
xor ecx,ecx ; ecx = 0
mov eax,[fs:ecx] ; eax = EnvironmentPointer du _TEB
not ecx ; ecx = 0xffffffff
@@Boucle:
mov esi,eax ; esi = Exception_Registration_Record N
lodsd ; eax = Exception_Registration_Record N+1
cmp [eax],ecx ; si Ptr32 _Exception_Registration_Record = 0xffffffff
; alors fin de la liste
; la fonction pointée dans l'avant derner Record
; est située dans Kernel32
jne @@Boucle
mov eax,[esi+0x04] ; eax = fonction dans Kernel32
@@BaseBoucle:
dec eax ; décrémente eax. permet de remonter dans la structure de Kernel32
xor ax,ax ; aligne eax sur un bloc de 64 bits
cmp word [eax],0x5a4d ; vérifie si eax = 'MZ' (début de Kernel32)
jne @@BaseBoucle
@@SortieProcessus:
xor eax,eax
push eax
mov eax, 0x7c81cb12 ; exitprocess(exitcode);
call eax
C: > nasm -f elf kernel32SEH.asmNous obtenons le Shellcode suivant que nous collons dans shellcodetest.c :
C: > ld -o kernel32SEH kernel32SEH.o
C: > objdump -d kernel32SEH > kernel32SEH.tmp
C: > odfhex kernel32SEH.tmp
"\x31\xc9\x64\x8b\x01\xf7\xd1\x89\xc6\xad\x39\x08\x75\xf9\x8b\x46\x04"\
"\x48\x66\x31\xc0\x66\x81\x38\x4d\x5a\x75\xf5\x31\xc0\x50\xb8\x12\xcb"\
"\x81\x7c\xff\xd0";
Compilons:
C: > gcc -o shellcodetest shellcodetest.cBien sûr, là encore le programme ne fait rien. Lancons Ollydbg
C: > shellcodetest
Dans Ollydbg:
tapez la touche F3 et choisissez shellcodetest.exe
sélectionnez l'une des valeurs sous "Hex Dump" (en bas à gauche) en cliquant dessus, puis CTRL-B pour rechercher une chaîne de caractères
entrez 31C931 (les premiers octets de notre shellcode). Placez ensuite un breakpoint sur le début du shellcode (clic-droit sur 32, BreakPoint, Memory on Access).
Lancez une première fois avec F9. Le programme s'arrête au début du shellcode.
Sélectionnez la ligne XOR EAX,EAX et placez y un second breakpoint: clic-droit sur l'adresse (ici 040201C), BreakPoint, Memory on Access
Run (F9): le programme s'arrête. Regardez alors eax qui affiche bien l'adresse de Kernel32:
Quelques explications s'imposent.
Pour accéder à la liste des Exception Handlers, nous passons par le TEB vu précédemment. Son premier enregistrement pointe vers une structure appelée _NT_TIB.
lkd > dt nt!_TEBLe premier élément de la structure _NT_TIB est la liste des exception handlers. Il s'agit d'une liste chaînée (non circulaire cette fois ci):
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
+0x020 ClientId : _CLIENT_ID
...
lkd > dt nt!_nt_tib
+0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x004 StackBase : Ptr32 Void
+0x008 StackLimit : Ptr32 Void
...
Figure: Walking the chain of exception registration records. Extrait de l'article de Skape (référence n°16)
Lorsque toute la liste des Exception Handlers a été parcourue, l'offset 0x04 de l'avant dernier enregistrement pointe sur une fonction contenue dans le module kernel32.
Une fois l'adresse de cette fonction trouvée, il faut trouver le début de kernel32. Pour cela, observons à quoi ressemble une librairie en mémoire.
Lorsque le module Kernel32 est loadé, l'OS copie en mémoire une image du fichier Kernel32.dll. Celle-ci possède la même structure que le fichier. (cf tutoriel de Joachim Bauch sur la facon de loader une librairie depuis la mémoire, référence n°13). Les fichiers DLL possèdent une structure de type Portable Exécutable (PE). La voici (cf référence n°12):
Le début de cette structure est matérialisé par le mot 'MZ' (0x5a4d) aligné sur le début d'un bloc de 64 bits. Remarque: L'acronyme MZ provient des initiales de Mark Zbikowski, ancien employé de Microsoft, inventeur du format EXE. Comme nous avons trouvé l'un des éléments inclus dans la structure de Kernel32, nous remontons mot par mot dans la structure en vérifiant à chaque fois si le début du bloc dans lequel nous nous trouvons est 'MZ'.
Nous avons donc vu la méthode SEH pour accéder à l'adresse Kernel32. Remarque: Skape propose dans son article (référence n°16) une méthode pour du SEH overflow: il est possible d'injecter du code dans l'un des exception_registration_record. Il propose aussi un moyen de s'en prémunir.
Voyons à présent la méthode pour trouver Kernel32 par TOPSTACK.
Exemple 5: Trouver l'adresse de kernel32.dll: méthode du TOPSTACK
Dans Kernel32Topstack.asm, tapez:
; Kernel32Topstack.asm
xor eax,eax
mov eax, [fs:eax+0x04] ; eax = StackBase
mov eax,[eax-0x1c] ; eax = adresse pointée par le 28ème (0x1c) mot de la pile
@@Boucle:
dec eax
xor ax,ax
cmp word [eax],0x5a4d
jne @@Boucle
@@SortieProcessus:
xor eax,eax
push eax
mov eax, 0x7c81cb12 ; exitprocess(exitcode);
call eax
C: > nasm -f elf kernel32Topstack.asmNous obtenons le Shellcode suivant que nous collons dans shellcodetest.c :
C: > ld -o kernel32Topstack kernel32Topstack.o
C: > objdump -d kernel32Topstack > kernel32Topstack.tmp
C: > odfhex kernel32Topstack.tmp
"\x31\xc0\x64\x8b\x40\x04\x8b\x40\xe4\x48\x66\x31\xc0\x66\x81\x38\x4d"\
"\x5a\x75\xf5\x31\xc0\x50\xb8\x12\xcb\x81\x7c\xff\xd0";
Compilons:
C: > gcc -o shellcodetest shellcodetest.cComme précédemment, nous commencons avec le TEB:
C: > shellcodetest
lkd > dt nt!_tebEt nous utilisons l'offset 0x004 de la structure _NT_TIB qui pointe sur le haut de la pile.
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
...
lkd > dt nt!_nt_tibPassons à Ollydbg
+0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x004 StackBase : Ptr32 Void
+0x008 StackLimit : Ptr32 Void
...
Comme précédemment, placez un breakpoint sur le début du shellcode puis pas à pas jusqu'à DEC EAX.Vous pouvez alors constater que eax pointe vers une adresse placée dans kernel32.
A quoi correspond l'adresse placée en 28ème position de la pile?
S'agit il d'une fonction? Pour le savoir, utilisons l'utilitaire DLL export viewer. Nous constatons que ce n'est pas le cas puisque aucune fonction exportée n'a pour adresse celle affichée par Ollydbg:
Il s'agit donc d'une adresse située dans la fonction ValidateLocale, à l'offset 2b0.
Voyons un dump de Kernel32 en mémoire. Utilisons PE Tools puis HexWorkShop:
L'adresse qui nous intéresse est placée à l'offset 0x 39ad8 dans Kernel32:
Nous pouvons remarquer que les caractères "Nls Section" sont placés quelques octets avant l'offset 0x39AD8 au sein de Kernel32.
A ce stade, nous n'avons pas encore trouvé à quoi servait ce pointeur...
[UNDER CONSTRUCTION]
Nous avons donc trouvé une adresse située dans kernel32. Il nous suffit alors de rechercher le MZ comme dans l'exemple précédent.
Grâce aux précédents exemples, vous avez pu comprendre le chaînage des structures qui décrivent un processus. Ainsi il vous a été possible de retrouver l'adresse de kernel32.dll de trois facons différentes. Mais cette adresse ne nous sert à rien pour le moment. Nous avons besoin des adresses des fonctions LoadLibrary et GetProcAddress. Ce que nous allons voir dans les exemples suivants.
exemple 6: Trouver l'adresse de GetProcAddress
Nous connaissons l'adresse de Kernel32. Recherchons à présent l'adresse de GetProcAddress:C: > arwin kernel32.dll ExitProcess
arwin - win32 address resolution program - by steve hanna - v.01
ExitProcess is located at 0x7c81cb12 in kernel32.dll
Dans TrouveGetProcAddress.asm, tapez:
;TrouveGetProcAddress.asm
@@RazRegistres:
xor eax,eax
xor ebx,ebx ;registres à zero
xor ecx,ecx
xor esi,esi
xor edi,edi
@@TrouveKernel32:
mov eax, [fs:eax+0x30] ; eax = adresse du PEB
mov eax, [eax + 0x0c] ; eax = adresse du loader
mov esi, [eax + 0x1c] ; esi = adresse de la liste des modules initialisés par le processus
lodsd ; esi = entrée suivante de la liste qui concerne kernel32.dll
mov edx, [eax + 0x8] ; edx = adresse du module kernel32.dll
@@TrouveExportDirectoryTable:
mov ebx, [edx + 0x3c] ; eax = offset signature PE dans Kernel32
mov ebx, [edx + ebx + 0x78] ; ebx = offset Export Directory Table dans Kernel32
add ebx,edx ; ebx = adresse Export Directory Table
@@TrouveGetProcAddress:
mov ecx, [ebx+0x18] ; ecx = nbre de fonctions exportées (compteur)
mov eax, [ebx+0x20] ; eax = Offset Export Name Pointer Table dans Kernel32
add eax,edx ; eax = Adresse Export Name Pointer Table
@@GetProcAddressParcourir:
dec ecx ; dec compteur nombre exports
cmp al,al ; <=> jmp. Astuce évite les x00
je @@AllerGetProcAddress ; pile <- adresse string 'GetProcAddress'
@@RetourGetProcAddress:
pop edi ; edi = 'GetProcAddress'
mov esi,[eax+ecx*4] ; esi = ordinal 'NomFonction\n' dans Name Pointer Table
add esi,edx ; esi = adresse 'NomFonction\n' dans Name Pointer Table
push ecx ; sauvegarde ecx
xor ecx,ecx
add cl,14 ; ecx = nbre caractères dans GetProcAddress
repe cmpsb ; compare chaines edi et esi
pop ecx ; ecx = compteur nombre exports
jnz @@GetProcAddressParcourir
mov eax, [ebx+0x24] ; eax = offset Ordinal Table dans Kernel32
add eax,edx ; eax = adresse Ordinal Table
mov cx, [eax+ecx*2] ; cx = ordinal de la fonction - numéro du 1er ordinal
mov ax,[ebx+0x10] ; eax = numéro du premier ordinal de la table
add cx,ax ; cx = ordinal de la fonction
dec cx ; pour tomber juste (ordinal débute à 0)
mov eax,[ebx+0x1c] ; eax = offset Export Address Table
add eax,edx ; eax = adresse Export Address Table
mov eax,[eax+ecx*4] ; eax = offset de GetProcAddress dans Kernel32
add eax,edx ; eax = adresse GetProcAddress
push eax ; pile <- adresse GetProcAddress
@@SortieProcessus:
xor eax,eax
push eax
mov eax, 0x7c81cb12 ; exitprocess(exitcode);
call eax
@@AllerGetProcAddress:
call @@RetourGetProcAddress
db 'GetProcAddress'
C: > nasm -f elf TrouveFonctions.asmNous obtenons le Shellcode suivant que nous collons dans shellcodetest.c :
C: > ld -o TrouveFonctions TrouveFonctions.o
C: > objdump -d TrouveFonctions > TrouveFonctions.tmp
C: > odfhex TrouveFonctions.tmp
"\x31\xc0\x31\xdb\x31\xc9\x31\xf6\x31\xff\x64\x8b\x40\x30\x8b\x40\x0c"\
"\x8b\x70\x1c\xad\x8b\x50\x08\x8b\x5a\x3c\x8b\x5c\x1a\x78\x01\xd3\x8b"\
"\x4b\x18\x8b\x43\x20\x01\xd0\x49\x38\xc0\x74\x38\x5f\x8b\x34\x88\x01"\
"\xd6\x51\x31\xc9\x80\xc1\x0e\xf3\xa6\x59\x75\xea\x8b\x43\x24\x01\xd0"\
"\x66\x8b\x0c\x48\x66\x8b\x43\x10\x66\x01\xc1\x66\x49\x8b\x43\x1c\x01"\
"\xd0\x8b\x04\x88\x01\xd0\x50\x31\xc0\x50\xb8\x12\xcb\x81\x7c\xff\xd0"\
"\xe8\xc3\xff\xff\xff\x47\x65\x74\x50\x72\x6f\x63\x41\x64\x64\x72\x65"\
"\x73\x73";
Compilons:
C: > gcc -o shellcodetest shellcodetest.c
C: > shellcodetest
Comme précédemment, placez un breakpoint sur le début du shellcode. Lancez une première fois l'exécution avec F9. Placez ensuite un second breakpoint sur la première opération de @@SortieProcessus: (clic droit sur XXX, BreapkPoint, Memory on access).
Relancez l'exécution. A présent observez la pile, elle contient l'adresse de GetProcAddress:
Quelques explications s'imposent à nouveau.
Lorsque la librairie Kernel32 est loadée, l'OS copie en mémoire une image du fichier Kernel32.dll. Celle-ci possède la même structure que le fichier. (cf tutoriel de Joachim Bauch sur la facon de loader une librairie depuis la mémoire, référence n°13). Les fichiers DLL possèdent une structure de type Portable Exécutable (PE). La voici (cf référence n°12):
En gros, nous avons:
MSDOS Stub < - structure _IMAGE_DOS_HEADER
------------------------
Unused
------------------------
PE Header <- structure _IMAGE_NT_HEADERS
------------------------
Section Headers, etc.
Nous allons surtout nous intéresser au PE Header. Quelle est l'adresse qui permet d'y accéder?
Avant tout, il faut savoir que tous les pointeurs à l'intérieur du PE sont des Relative Virtual Address (RVA). Cela signifie que ce sont des offsets relatifs à l'adresse du début de la structure. Ainsi:
Adresse = Kernel32 + offset
Le dernier élément du MSDOS STUB pointe vers le premier élément du PE Header. En effet, dans MSDN - Microsoft Portable Executable and Common Object File Format Specification (référence n°12, page 7):
"At location 0x3c, the stub has the file offset to the PE signature. This information enables Windows to properly execute the image file, even though it has an MS‑DOS stub. This file offset is placed at location 0x3c during linking."
Vérifions avec Windbg que le dernier élément du MSDOS Stub est bien placé à l'offset 0x3c:
lkd > dt nt!_image_dos_headerQu'est ce que cette PE signature? Voyons avec Windbg:
+0x000 e_magic : Uint2B
+0x002 e_cblp : Uint2B
+0x004 e_cp : Uint2B
+0x006 e_crlc : Uint2B
+0x008 e_cparhdr : Uint2B
+0x00a e_minalloc : Uint2B
+0x00c e_maxalloc : Uint2B
+0x00e e_ss : Uint2B
+0x010 e_sp : Uint2B
+0x012 e_csum : Uint2B
+0x014 e_ip : Uint2B
+0x016 e_cs : Uint2B
+0x018 e_lfarlc : Uint2B
+0x01a e_ovno : Uint2B
+0x01c e_res : [4] Uint2B
+0x024 e_oemid : Uint2B
+0x026 e_oeminfo : Uint2B
+0x028 e_res2 : [10] Uint2B
+0x03c e_lfanew : Int4B
lkd > dt nt!_image_nt_headersIl s'agit en fait du premier élément de la structure _IMAGE_NT_HEADERS qui constitue le PE Header.
+0x000 Signature : Uint4B
+0x004 FileHeader : _IMAGE_FILE_HEADER
+0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER
Donc, si nous récapitulons, adresse PE Header = adresse DLL + 0x3c
PE Header = Kernel32.dll + 0x3c
Le Optional Header possède trois grandes parties:
- les champs standards (standard fields),
- les champs spécifiques Windows (Windows-specific fields),
- les data directories.
lkd > dt nt!_image_optional_headerEn fait, il n'y a pas qu'un seul DataDirectory, mais plusieurs. Le premier élément de chacune des structures _IMAGE_DATA_DIRECTORY est un pointeur qui pointe vers une table.
+0x000 Magic : Uint2B \
+0x002 MajorLinkerVersion : UChar |
+0x003 MinorLinkerVersion : UChar |
+0x004 SizeOfCode : Uint4B |
+0x008 SizeOfInitializedData : Uint4B | Standard fields
+0x00c SizeOfUninitializedData : Uint4B |
+0x010 AddressOfEntryPoint : Uint4B |
+0x014 BaseOfCode : Uint4B |
+0x018 BaseOfData : Uint4B /
+0x01c ImageBase : Uint4B \
+0x020 SectionAlignment : Uint4B |
+0x024 FileAlignment : Uint4B |
+0x028 MajorOperatingSystemVersion : Uint2B |
+0x02a MinorOperatingSystemVersion : Uint2B |
+0x02c MajorImageVersion : Uint2B |
+0x02e MinorImageVersion : Uint2B |
+0x030 MajorSubsystemVersion : Uint2B |
+0x032 MinorSubsystemVersion : Uint2B |
+0x034 Win32VersionValue : Uint4B |
+0x038 SizeOfImage : Uint4B | Windows specific fields
+0x03c SizeOfHeaders : Uint4B |
+0x040 CheckSum : Uint4B |
+0x044 Subsystem : Uint2B |
+0x046 DllCharacteristics : Uint2B |
+0x048 SizeOfStackReserve : Uint4B |
+0x04c SizeOfStackCommit : Uint4B |
+0x050 SizeOfHeapReserve : Uint4B |
+0x054 SizeOfHeapCommit : Uint4B |
+0x058 LoaderFlags : Uint4B |
+0x05c NumberOfRvaAndSizes : Uint4B /
+0x060 DataDirectory : [16] _IMAGE_DATA_DIRECTORY <- data directories
lkd > dt nt!_IMAGE_DATA_DIRECTORYVous pouvez trouver la liste des tables dans MSDN (référence n°12, page 15).
+0x000 VirtualAddress : Uint4B
+0x004 Size : Uint4B
La première est celle qui nous intéresse. Il s'agit de l'Export Directory Table: la table qui liste les fonctions exportées. Son offset est:
offset de PE signature + offset du Optional Header + offset Data Directory = PE_signature + 0x18 + 0x60
donc
ExportDirectoryTable = PE_signature + 0x78
A quoi ressemble cette Export Directory Table? cf MSDN (référence n°12, chapitre 6.3.1, page 51):
Cette table ne nous donne pas tout de suite l'adresse de notre fonction. Par contre, elle nous donne toutes les informations qui nous seront nécessaires :
- l'offset de la table Export Name Pointer Table (32d = 0x20),
- le nombre de fonctions accessibles depuis la DLL (Number of Name Pointers 24d = 0x18).
- l'offset de la table Ordinal Table (36d = 0x24),
- l'offset de la table Export Address Table (28d = 0x1c),
- le nombre ordinal de départ, généralement égal à 1: Ordinal Base (16d=0x10)
Pour trouver l'adresse de la fonction, nous devons d'abord trouver l'index de son nom, puis son ordinal, et enfin son adresse.
L'Export Name Pointer Table est un tableau de pointeurs qui pointent vers les noms des fonctions, indexés dans l'ordre lexical des noms de fonctions:
L'Ordinal Table est un tableau d'ordinaux (des nombres codés sur 16 bits, donc 2 octets. C'est utile à savoir car il faudra multiplier par deux l'index trouvé), indexés dans ce même ordre lexical.
L'Export Address Table est un tableau d'offsets qui pointent vers les fonctions, indexés dans l'ordre ordinal.
Voici un schéma tiré du livre Expert .NET 2.0 IL Assember (référence n°14)
Schéma d'interactions entre les tables Export Directory Table, Export Name Pointer Table et Export Address Table (référence n°14)
Récapitulons, nous devons :
- trouver la position du nom de notre fonction dans l'Export Name Pointer Table,
- récupérer l'ordinal situé à la même position dans l'Ordinal Table, sans oublier de multiplier par deux l'index trouvé
- récupérer l'adresse de la fonction située à l'ordinal trouvé dans l'Export Address Table.
A ce stade, nous avons su trouver l'adresse de la fonction GetProcAddress. Il nous suffit de reproduire la méthode pour trouver celle de LoadLibraryA.
exemple 7: Trouver l'adresse de GetProcAddress et LoadLibraryA
Reprenons l'exemple précédent, en y ajoutant une boucle qui permet de trouver deux fonctions:Dans TrouveFonctions.asm, tapez:
;TrouveFonctions.asm
@@RazRegistres:
xor eax,eax
xor ebx,ebx ;registres à zero
xor esi,esi
xor edi,edi
@@TrouveKernel32:
mov eax, [fs:eax+0x30] ; eax = adresse du PEB
mov eax, [eax + 0x0c] ; eax = adresse du loader
mov esi, [eax + 0x1c] ; esi = adresse de la liste des modules initialisés par le processus
lodsd ; esi = entrée suivante de la liste qui concerne kernel32.dll
mov edx, [eax + 0x8] ; edx = adresse du module kernel32.dll
@@TrouveExportDirectoryTable:
mov ebx, [edx + 0x3c] ; eax = offset signature PE dans Kernel32
mov ebx, [edx + ebx + 0x78] ; ebx = offset Export Directory Table dans Kernel32
add ebx,edx ; ebx = adresse Export Directory Table
xor ecx,ecx
@@ChoixFonction:
push ecx ; sauvegarde ecx
jecxz @@AllerGetProcAddress ; si ecx == 0 pile <- adresse string 'GetProcAddress'
cmp cl,1
je @@AllerLoadLibrary ; si ecx == 1 pile <- adresse string 'LoadLibraryA'
cmp al,al ; toujours vrai (astuce pour éviter les 0x00 dans le shellcode)
je @@SortieProcessus ; sinon jmp sortie
@@RetourFonction:
mov ecx, [ebx+0x18] ; ecx = nbre de fonctions exportées (compteur)
mov eax, [ebx+0x20] ; eax = offset Export Name Pointer Table dans Kernel32
add eax,edx ; eax = adresse Export Name Pointer Table
@@Parcourir:
dec ecx ; dec compteur nombre exports
pop edi ; edi = 'GetProcAddress' ou 'LoadLibrary'
push edi ; pile < - 'GetProcAddress' ou 'LoadLibrary'
mov esi,[eax+ecx*4] ; esi = ordinal 'NomFonction\n' pris depuis Name Pointer Table
add esi,edx ; esi = adresse 'NomFonction\n' pris depuis Name Pointer Table
push ecx ; sauvegarde ecx
xor ecx,ecx
add cl,12 ; ecx = nbre caractères dans 'LoadLibrary'
repe cmpsb ; compare les 12 premiers caractères des chaines edi et esi
pop ecx ; ecx = compteur nombre exports
jnz @@Parcourir
pop edi ; enlève l'adresse laissée précédemment sur la pile
mov eax, [ebx+0x24] ; eax = offset Ordinal Table dans Kernel32
add eax,edx ; eax = adresse Ordinal Table
mov cx, [eax+ecx*2] ; cx = ordinal de la fonction - numéro du 1er ordinal
mov ax,[ebx+0x10] ; eax = numéro du premier ordinal de la table
add cx,ax ; cx = ordinal de la fonction
dec cx ; pour tomber juste
mov eax,[ebx+0x1c] ; eax = offset Export Address Table
add eax,edx ; eax = adresse Export Address Table
mov eax,[eax+ecx*4] ; eax = offset de Fonction dans Kernel32
add eax,edx ; eax = adresse Fonction
pop ecx ; récupère le compteur de fonctions
inc ecx
push eax ; pile < - adresse Fonction
cmp al,al ; toujours vrai. Pour éviter les 0x00
je @@ChoixFonction
pop ecx
@@SortieProcessus:
xor eax,eax
push eax
mov eax, 0x7c81cb12 ; exitprocess(exitcode);
call eax
@@AllerGetProcAddress:
call @@RetourFonction
db 'GetProcAddress'
@@AllerLoadLibrary:
call @@RetourFonction
db 'LoadLibraryA'
C: > nasm -f elf TrouveFonctions.asmNous obtenons le Shellcode suivant que nous collons dans shellcodetest.c :
C: > ld -o TrouveFonctions TrouveFonctions.o
C: > objdump -d TrouveFonctions > TrouveFonctions.tmp
C: > odfhex TrouveFonctions.tmp
"\x31\xc0\x31\xdb\x31\xf6\x31\xff\x64\x8b\x40\x30\x8b\x40\x0c\x8b\x70"\
"\x1c\xad\x8b\x50\x08\x8b\x5a\x3c\x8b\x5c\x1a\x78\x01\xd3\x31\xc9\x51"\
"\xe3\x53\x80\xf9\x01\x74\x61\x38\xc0\x74\x40\x8b\x4b\x18\x8b\x43\x20"\
"\x01\xd0\x49\x5f\x57\x8b\x34\x88\x01\xd6\x51\x31\xc9\x80\xc1\x0c\xf3"\
"\xa6\x59\x75\xed\x5f\x8b\x43\x24\x01\xd0\x66\x8b\x0c\x48\x66\x8b\x43"\
"\x10\x66\x01\xc1\x66\x49\x8b\x43\x1c\x01\xd0\x8b\x04\x88\x01\xd0\x59"\
"\x41\x50\x38\xc0\x74\xb5\x59\x31\xc0\x50\xb8\x12\xcb\x81\x7c\xff\xd0"\
"\xe8\xb1\xff\xff\xff\x47\x65\x74\x50\x72\x6f\x63\x41\x64\x64\x72\x65"\
"\x73\x73\xe8\x9e\xff\xff\xff\x4c\x6f\x61\x64\x4c\x69\x62\x72\x61\x72"\
"\x79\x41";
Compilons:
C: > gcc -o shellcodetest shellcodetest.cNous appliquons la même méthode que précédemment dans Ollydbg. Voici un extrait de la pile obtenue:
C: > shellcodetest
conclusion
Arrivés à ce point du tutoriel, vous avez pu écrire vos premiers shellcodes Windows. Vous avez utilisé un panel varié d'outils. Vous avez pu également constater que l'une des difficultés est le linkage des librairies.
Par la suite, nous pourrons aborder l'ouverture d'un shell distant avec privilèges d'administrateur sous Windows.
Références
1) Ghosts in the Stack - article de Trance - Les Shellcodes - http://www.ghostsinthestack.org/article-5-les-shellcodes.html
2) Vividmachines - article de Steve Hanna - Shellcoding for Linux and Windows Tutorial - http://www.vividmachines.com/shellcode/shellcode.html
3) Securinfos - article de Jerôme Athias - Trouver l'adresse de base de kernel32.dll : Trois méthodes PEB, SEH et TopStack - https://www.securinfos.info/jerome/DOC/Trouver_kernel32.pdf
4) Nibbles, article de lilxam - Playing with PEB, listing process modules - http://nibbles.tuxfamily.org/?p=66
5) article de Neitsa - Ecrire sa propre méthode pour LoadLibrary et GetProcAddress - http://neitsabes.free.fr/RE/HIRE/OwnGetProc1.html
6) Wikipedia - Win32 Thread Information Block - http://en.wikipedia.org/wiki/Win32_Thread_Information_Block
7) Microsoft Windows Hardware Developer Central - Debugging Tools and Symbols - http://www.microsoft.com/whdc/devtools/debugging/debugstart.mspx
8) wiki skypher.com - Cipher et Skylined - TEB et PEB - http://skypher.com/wiki/index.php/Hacking/Windows_internals/Process/Memory/TEB
9) blog de j04n Calvet - Cacher un module en mémoire - http://joe-is-a-rocknroll-star.blogspot.com/2008/08/cachez-ce-module-que-je-ne-saurai-voir.html
10) MSDN - Singly and doubly linked lists - http://msdn.microsoft.com/en-us/library/aa489548.aspx
11) MSDN - PEB_LDR_DATA structure - http://msdn.microsoft.com/en-us/library/aa813708%28VS.85%29.aspx
12) MSDN - Microsoft Portable Executable and Common Object File Format Specification - http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx
13) tutoriel de Joachim Bauch - loading a dll from memory - http://www.joachim-bauch.de/tutorials/load_dll_memory.html
14) livre de Serge Lidin - Expert .NET 2.0 IL Assember - édition Apress - 2006 - page 64 - http://books.google.fr/books?id=oAcCRKd6EZgC&pg=PA64
15) tutoriel de skape - Understanding Windows Shellcode - www.hick.org/code/skape/papers/win32-shellcode.pdf
16) article de skape - Preventing the Exploitation of SEH Overwrites - http://uninformed.org/index.cgi?v=5&a=2&p=4
17) article de The Last Stage of Delirium Research Group - Win32 Assembly Components - http://ivanlef0u.free.fr/repo/windoz/shellcoding/winasm-1.0.1.pdf
Annexe: code source de odfhex.c
/**************************************
odfhex - objdump hex extractor
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:
g++ odfhex.cpp -o odfhex
note: the XOR option works
perfectly, but i haven't implemented
the full x86 payload decoder yet.
so that option is mostly useless.
this program extracts the hex values
from an "objdump -d
after doing this, it converts the
hex into escaped hex for use in
a c/c++ program.
happy shellcoding!
***************************************/
#include < unistd.h >
#include < memory.h >
#include < string.h >
#include < stdlib.h >
#include < math.h >
char symbols[37] = "0123456789abcdefghijklmnopqrstuvwxyz";
const int MAX_BASE = 36;
int GetIndex(char * pString, char search);
int BaseToDec(char* number, int base)
{
if( base < 2 || base > MAX_BASE)
return 0; //Failed
int NumLength = strlen(number);
int PlaceValue, total = 0;
PlaceValue = (int)pow(base,NumLength-1);
for(int i=0;i
total += GetIndex(symbols,*number)*PlaceValue;
number++;
PlaceValue /= base; //Next digit's place value (previous/base)
}
return total;
}
int GetIndex(char * pString, char search)
{
int index = 0;
while(*pString != '0')
{
if(*pString==search)
break;
pString++;
index++;
}
return index;
}
int main(int argc, char** argv)
{
FILE* dump = NULL;
long length = 0;
char* content;
int i=0;
int count =0;
int total=0;
int XORvalue=0;
bool XORit = false;
char HexNumber[3]={'\0'};
printf("\nOdfhex - object dump shellcode extractor - by steve hanna - v.01\n");
if(argc < 2)
{
return -1;
}
dump = fopen(argv[1],"r");
if(!dump)
{
printf("Error: Couldn't open file.\n");
return -1;
}
fseek(dump,0,SEEK_END);
length = ftell(dump);
content = new char[length+1];
memset(content,0,sizeof(content));
printf("Trying to extract the hex of %s which is %d bytes long\n",argv[1],length);
if (argc > 3 && !strcmp(argv[2],"-x"))
{
XORit =true;
XORvalue = BaseToDec(argv[3],16);
printf("XORing with 0x%02x\n",XORvalue);
}
fseek(dump,0,SEEK_SET);
for(int i=0; i < length; i++)
{
content[i] = fgetc(dump);
}
fclose(dump);
while(count !=4)
{
if(content[i] == ':')
count++;
i++;
}
count = 0;
printf("\"");
while(i < length)
{
if( (content[i-1] == ' ' || content[i-1]=='\t') &&
(content[i+2] == ' ' ) &&
(content[i] != ' ') &&
(content[i+1] != ' ') &&
((content[i]>='0' && content[i]< ='9') || (content[i]>='a' && content[i]< ='f')) &&
((content[i+1]>='0' && content[i+1]< ='9') || (content[i+1]>='a' && content[i+1]< ='f'))
)
{
if(XORit)
{
HexNumber[0] = content[i];
HexNumber[1] = content[i+1];
printf("\\x%02x",BaseToDec(HexNumber,16) ^ XORvalue);
}
else
printf("\\x%c%c",content[i],content[i+1]);
count++;
total++;
}
if(i+1 == length)
{
printf("\";\n");
}
else if(count == HEX_PER_LINE)
{
printf("\"\\\n\"");
count =0;
}
i++;
}
delete[] content;
printf("\n%d bytes extracted.\n\n",total);
return 0;
}
Ah très fun le blog. L'article est bon!
RépondreSupprimerLes divers méthodes sont rappellées, avec exemple, commentaire et illustration.(pour info http://www.harmonysecurity.com/blog/2009/08/calling-api-functions.html ; une méthode basé sur sur le PEB + un hashage des noms de fonction pour ne pas s'abstreindre à des problèmes d'encodage)
tips : pour éviter le pas à pas dans olly ou autre; tu peux poser un \x03(int 3) au début du shellcode pour la trap au debugger.
Continues comme ca et bon courage pour la suite.
merci