To search

w3pwnz

w3pwnz, therefore we are

Tag - unpacking

Entries feed Comments feed

Thursday, October 18 2012 16:01

HackYou CTF - Reverse100, Reverse200, Reverse300 Writeups

Reverse 100 - Open-Source



Download file code.c.
Simply read the source...

% ./code `python2 -c 'print 0xcafe'` 25 h4cky0u
Brr wrrr grr
Get your key: c0ffee

Flag: c0ffee

Reverse 200 - LoseYou



Download file rev200.zip.

Extract the archive to obtain task2.bin and task2.exe.
I decided to study task2.bin.
The routine to reverse is sub_80483DC.
We quicky notice this:

hackyou_reverse200.png

That's very basic, eax will contain our guessed number, ecx will contain the randomly generated number.
All we need to do is to break on the "cmp eax, ecx" at 08048503 and set eax to ecx.

(gdb) b *0x08048503
Breakpoint 1 at 0x8048503
(gdb) r
Starting program: /tmp/task2.bin 
warning: Could not load shared library symbols for linux-gate.so.1.
Do you need "set solib-search-path" or "set sysroot"?
Welcome to the LoseYou lottery!
Generating random.....
Make your guess (number 0 or 1): tg
 
Breakpoint 1, 0x08048503 in ?? ()
(gdb) set $eax=$ecx
(gdb) c
Continuing.
You... you... win??? so lucky! Grab the flag:
::: oh_you_cheat3r :::
[Inferior 1 (process 27619) exited normally]
(gdb)

Flag: oh_you_cheat3r

Reverse 300 - ashtree



Download file rev300.zip.
Once again I decided to study the ELF version: task3.bin.

Step 1 - Unpack

The binary seems to be packed by a modified UPX (at least, UPX string is replaced by LOL...).
Let's trace execution:

% strace ./task3.bin 
execve("./task3.bin", ["./task3.bin"], [/* 34 vars */]) = 0
[ Process PID=5640 runs in 32 bit mode. ]
getpid()                                = 5640
gettimeofday({1350215641, 677903}, NULL) = 0
unlink("/tmp/upxCRBOGQOAFQI")           = -1 ENOENT (No such file or directory)
open("/tmp/upxCRBOGQOAFQI", O_RDWR|O_CREAT|O_EXCL, 0700) = 3
ftruncate(3, 9036)                      = 0
old_mmap(NULL, 9036, PROT_READ|PROT_WRITE, MAP_SHARED, 3, 0) = 0xf775a000
old_mmap(0xf775d000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xf775d000
munmap(0xfffffffff775a000, 9036)        = 0
close(3)                                = 0
open("/tmp/upxCRBOGQOAFQI", O_RDONLY)   = 3
getpid()                                = 5640
access("/proc/5640/fd/3", R_OK|X_OK)    = 0
unlink("/tmp/upxCRBOGQOAFQI")           = 0
fcntl(3, F_SETFD, FD_CLOEXEC)           = 0
execve("/proc/5640/fd/3", ["./task3.bin"], [/* 41 vars */]) = 0
[...]

The binary is unpacking itself into a file "/tmp/upxCRBOGQOAFQI", which is a randomly generated name. Then it is unlinked and executed by execve().

The unlink() function is guaranteed to unlink the file from the file system hierarchy but keep the file on disk until all open instances of the file are closed.

Once the execution is over, the file is deleted, so we need to find the file name, and copy the file elsewhere before it's unlinked. Let's toggle a breakpoint before execve() then.

(gdb) b *0x004024dd
Breakpoint 1 at 0x4024dd
(gdb) r
Starting program: /tmp/task3.bin 
 
Breakpoint 1, 0x004024dd in ?? ()
(gdb) x/s $ebx
0xffffd494:	"/proc/12767/fd/7"
(gdb)
% file /proc/12767/fd/7
/proc/12767/fd/7: broken symbolic link to `/tmp/upxC5RF3NOAMO5 (deleted)'

Ok! Now we can work on the unpacked binary.

Step 2 - Keygen

Our routine is sub_8048617.
Several conditions must be met:

  • - argv[1] = username
  • - argv[2] = password
  • - username != 'hackyou'
  • - len(password) == 14
  • - password[4] == '-' and password[8] == '-'
  • - sub_804838C(username, password) == True
  • - sub_804844B(username, password[5:]) == True
  • - sub_804850A(username, password[10:]) == True

Although our username must not be 'hackyou', if you take a look at sub_80485F0, the goodboy message, you'll notice it prints "Great! Now submit the license key for 'hackyou'".
sub_804838C, sub_804844B and sub_804850A all proceed in the same way, which finally compares individually 4 bytes of the given password parameter:

hackyou_rev300_ida.png

Our password character is in edx, the expected one is in eax. Lazy as i am, i didn't go much deeper :)

All we need now is to break on:

.text:0804842B                 cmp     eax, edx

... dump eax, and fix edx so that it will be equal to eax. Same for the two last routines. Let's create a basic pythonGDB script to do the work for us.

import gdb
 
passwd = ""
passwd_len = 0
 
def callback_username_condition():
    gdb.execute("set $eax=1")
 
def callback_compare_password():
    global passwd, passwd_len
 
    gdb.execute("set $edx=$eax")
 
    if passwd_len in (4, 9):
        passwd += '-'
        passwd_len += 1
 
    passwd += chr(gdb.parse_and_eval("$eax"))
    passwd_len += 1
 
    print "[+]", passwd
 
class HitBreakpoint(gdb.Breakpoint):
    def __init__(self, loc, callback):
        super(HitBreakpoint, self).__init__(
            loc, gdb.BP_BREAKPOINT, internal=False
        )
        self.callback = callback
 
    def stop(self):
        self.callback()
 
        return False
 
HitBreakpoint("*0x08048665", callback_username_condition)
HitBreakpoint("*0x0804842B", callback_compare_password)
HitBreakpoint("*0x080484EA", callback_compare_password)
HitBreakpoint("*0x080485A9", callback_compare_password)

And run it...

(gdb) source script.py 
Breakpoint 1 at 0x8048665
Breakpoint 2 at 0x804842b
Breakpoint 3 at 0x80484ea
Breakpoint 4 at 0x80485a9
(gdb) r hackyou 0123-4567-8910
Starting program: /tmp/unpacked_rev300 hackyou 0123-4567-8910
warning: Could not load shared library symbols for linux-gate.so.1.
Do you need "set solib-search-path" or "set sysroot"?
[+] k
[+] ke
[+] kec
[+] kecc
[+] kecc-h
[+] kecc-ha
[+] kecc-hac
[+] kecc-hack
[+] kecc-hack-y
[+] kecc-hack-yo
[+] kecc-hack-yo0
[+] kecc-hack-yo0u
Great! Now submit the license key for 'hackyou'
[Inferior 1 (process 14524) exited with code 01]
(gdb)

Flag: kecc-hack-yo0u

Thursday, October 20 2011 17:34

Unpack FSG2 - Reconstruction manuelle de l'IAT

La cible :


Je vous propose aujourd’hui mon premier tutoriel concernant l’unpack d’un programme packé avec FSG2. Et j’en profiterai pour vous montrer comment on peut reconstruire l’IAT manuellement.

Tout d’abord notre cible =>UnPackMe_FSG2.0.rar, on confirme qu'il est bien packé en l'ouvrant dans PEid qui nous détecte "FSG 2.0 -> bart/xt"

PEiD_FSG

Dump du programme :


La première étape est de retrouver l'OEP (Original Entry Point) du programme et de faire un dump du programme. Ouvrons donc le programme dans Olly, il nous averti que l'OEP est en dehors du code et que le programme est sûrement packé.

Olly_Detect_Packer
Pour trouver l'OEP, faire F9 dans Olly jusqu'à tomber sur la section de code 0040XXXX où l'on peux tomber sur le code suivant correspondant au début de l'unpack du code :

00400154 > 8725 D03E4100    XCHG DWORD PTR DS:[413ED0],ESP        // Déplace ESP à l'adresse 413ED0
0040015A   61               POPAD                                                               // Récupération des registres 
0040015B   94               XCHG EAX,ESP                                                    // Déplace ESP dans EAX
0040015C   55               PUSH EBP                                                           // Push EBP = 401000

On peut voir en regardant la mémoire à l'adresse 401000 (Ctrl+G dans la zone en bas à droite correspondant à la mémoire) et en débugguant pas à pas que le programme est en train de s'unpacker en mémoire à partir de cette adresse.

En parcourant la suite du code, on tombe sur une succession de sauts conditionnels suivi d'un jmp vers l'OEP, typique au FSG :

004001CD  ^78 F3            JS SHORT UnPackMe.004001C2
004001CF   75 03            JNZ SHORT UnPackMe.004001D4
004001D1   FF63 0C          JMP DWORD PTR DS:[EBX+C]

Il suffit de poser un BP sur le dernier JMP pour voir vers quelle adresse le programme va sauter et nous avons alors notre OEP => 401000. Maintenant que nous avons notre OEP en main, il va falloir faire un dump du programme. Pour cela nous allons utiliser un plugin Olly nommé Ollydump. Faire F8 pour basculer sur notre OEP et faire un click droit Analysis / Remove Analysis from module pour voir le code du programme.
Olly_Remove_Analysis
Pour faire un dump du programme, il suffit maintenant de faire un click droit - Dump Debugged Process Une nouvelle fenêtre va s'ouvrir où l'on va pouvoir spécifier notre nouvel EP et lancer notre dump :
OllyDump

Attention : Pensez à décocher Rebuild Import qui permet de reconstruire les imports depuis Ollydump car ce n'est pas sa spécialité et le but ici est de reconstruire l'IAT manuellement.

Reconstruction de l'IAT :


Avant de commencer, pour les plus novices vous vous demandez sûrement qu'est ce que l'IAT ( Import Address Table). Il faut savoir que la plupart des programmes font appel à de nombreuses APIs disponibles dans des fichiers dll. On peux prendre par exemple l'API MessageBoxA disponible dans la dll user32.dll permettant d'afficher une boîte de dialogue. Toutes ces APIs sont donc disponibles au travers d'appels aux fonctions contenus dans les dll mais les API selon l'architecture et le système d'exploitation ne sont pas au même emplacement mémoire. Chaque programme comporte donc une table permettant au programme de retrouver l'adresse des fonctions qu'il va utiliser. Cette table va se remplir automatiquement lors de son lancement avec les bonnes adresses. Si vous souhaitez des explications plus détaillées et plus techniques, je vous envoie vers un article de mysterie

Après le dump d'un programme, il faut savoir que l'IAT n'est pas correct. Il faut donc le reconstruire.

La méthode "classique" :


Après le dump du programme fait avec Ollydump, on conserve Ollydbg ouvert et on lance ImportRec. On sélectionne dans le champ du haut le process à attacher, dans notre cas unpackme_fsg2.0.exe.

ImpRec

1) On modifie l'OEP par celui que l'on a trouvé précédemment : 404000, on indique donc dans le champs OEP la valeur RVA : 4000

2) On clique sur IAT AutoSearch, il va alors chercher pour nous les imports de fonctions à effectuer, il va nous afficher à partir de quelle adresse (RVA) il récupère les fonctions et quelle taille il doit récupérer. On peux alors vérifier sous Olly qu'il n'en a pas oublié. Pour cela sélectionner la zone en bas à gauche d'Olly qui liste les octets en mémoire et faire un CTRL+G pour aller à l'adresse indiquée : 004011E8.

olly_mem

On voit donc bien les différents appels de fonctions que l'on peux repérer par leur adresses commençant par 75XXXXXX, mais en remontant un peu dans la zone mémoire, on se rend compte que d'autres adresses vers des fonctions sont stockées à partir de l'adresse 00401198. On va donc modifier dans ImportRec le champ RVA par l'adresse que l'on a trouvé RVA : 11998 et on va définir une taille suffisamment grande pour ne rien oublier Size : 100

3) On clique sur Get Imports, il va alors nous afficher les dlls qu'il a trouvé

ImpRecImportFonction

On voit que certains thunks sont invalides. On va donc cliquer sur Show Invalid pour voir ce qui cloche. On voit alors deux imports incorrects. Il suffit de cliquer sur chacun d'eux et faire Cut Thunks.

Nous n'avons plus qu'à ajouter ces imports au dump que l'on a effectué précédemment, pour cela :

4) On clique sur FixDump et on sélectionne notre dump que l'on a sauvegardé précédemment. On peux vérifier qu'il fonctionne bien en l’exécutant et on peux également l'ouvrir avec PEiD qui ne détecte plus de packer.

La méthode "manuelle" :


Et « just for fun » comme on dit, voici comment faire manuellement ce que fait ImportRec.

La première étape est de noter l'ensemble des API utilisées par le programme. Pour cela on retourne dans Olly juste après avoir fait le dump et on fait un click droit Search for / All intermodular call et il nous affiche alors l'ensemble des API utilisées.
olly_intermodular_call

On va maintenant ouvrir le dump que l'on a effectué tout à l'heure et aller rajouter manuellement l'ensemble des fonctions utilisées dans notre IAT. Pour cela on utilise LordPe, on clique sur PE Editor on sélectionne notre dump. Une nouvelle fenêtre va s'ouvrir avec l'ensemble des informations du header de notre EXE. On va cliquer sur Directories puis sur les 3 petits points à coté du champ Import Table

LordPe_IAT

LordPe va alors nous afficher l'IAT. Dans notre cas il nous affichera un peu n'importe quoi vu que l'IAT est corrompu. On va donc rajouter nous même l'ensemble des imports dont nous avons besoin, pour cela faire un click dans la partie haute de la fenêtre sur l'import en erreur et sélectionner Add Import

LordPe_AddImport

Une nouvelle fenêtre va alors s'afficher où l'on va pouvoir ajouter l'ensemble de nos appels de fonctions. Pour cela rien de plus facile, il suffit de renseigner le nom de la dll et les API dont le programme a besoin :

LordPe_adddll

Il faudra effectuer cette opération pour chacune des dlls que le programme utilise. Lorsque vous avez ajouté tout ce qu'il vous faut, vous pouvez supprimer l'import en error. Pour cela faire un click droit sur l'import et sélectionner KillImageImportDescriptor. Vous pouvez alors fermer cette fenêtre et cliquer sur save pour sauvegarder tout le travail que l'on vient de faire.

Nous avons donc maintenant un dump du programme non packé avec une table d'import correct, une dernière étape reste à faire : modifier les différents appels à ces fonctions dans le programme. On va donc ouvrir notre dump dans OllyDbg et faire un click droit Search For / All Intermodulars Calls où l'on va retrouver la liste des fonctions que l'on avait noté à la première étape et pour chacun des ces calls nous allons modifier l'adresse par celle qui est spécifiée dans notre table d'import.

olly_fixcall

Par exemple pour la fonction GetModuleHandleA il faudra modifier le CALL DWORD PTR DS:4011F4 par CALL DWORD PTR DS:4140C1. Pour cela sous Olly il suffit de double cliquer sur la fonction que l'on veux modifier, OllyDBG va nous placer sur l'endroit du code où l'appel est passé et il faudra de nouveau double-cliquer dessus pour modifier le code.

Après avoir effectué cette opération pour l'ensemble des CALL, il faudra sauvegarder les modifications. Pour cela sélectionner l'ensemble du code en faisant click droit Copy / Select All puis click droit Copy to executable / Selection et enfin dans la fenêtre qui s'affiche faire un click droit puis Save File.

Vous aurez alors un programme complètement unpacké avec sa table d'import reconstruite manuellement, enfin presque. Vous pouvez si vous le souhaitez aller encore plus loin et reconstruire l'IAT en modifiant manuellement le PE header du fichier sans passer par LordPE...

Pour terminer, comme vous avez pu le constater la méthode manuelle est tout de même très fastidieuse, surtout si votre programme fait appel à de nombreuses API mais je pense qu'il est toujours intéressant de faire à la main au moins une fois cette opération pour bien comprendre son fonctionnement.