Outils pour utilisateurs

Outils du site


writeup:ndh2014:mach2

Mach2

Pour l'édition 2014 de la Nuit du Hack, j'ai réalisé une petite épreuve de reverse pour le wargame public, voici la solution que j'ai rédigé.

Le binaire avec les sources est téléchargeable ici : mach2.tar.gz

Découverte du format

Le challenger trouve un fichier fat.bin

$ ls -la fat.bin 
-rwxr-xr-x  1 tlk  staff  1524189 25 mai 17:45 fat.bin

Le fichier est executable. L'un des premiers réflexes est de lancer la commande file et strings pour essayer d'identifier le format de fichier.

$ file fat.bin 
fat.bin: compiled Java class data, version 72.0
$ strings fat.bin
Nuit du hack 2014 - Mac Pwnage :)      
sU:/
ypbuk
...
__PAGEZERO
__TEXT
__text
__TEXT
__symbol_stub
__TEXT
__stub_helper
__TEXT
__cstring
__TEXT
__unwind_info
__TEXT
__DATA
__nl_symbol_ptr
__DATA
__la_symbol_ptr
__DATA
__common
__DATA
__WOW
__beautiful
__WOW
__LINKEDIT
/usr/lib/dyld
/usr/lib/libSystem.B.dylib
@^[]
/home/tlk
strcmp
usage : %s <flag>
HOME
50585be4e3159a71c874c590d2ba12ec
This is the flag ;)
Nope.
@dyld_stub_binder
@_dlopen
@_dlsym
@_fclose
@_fopen$UNIX2003
@_fwrite$UNIX2003
@_printf
@_puts
_mh_execute_header
Agetenv
Estrcmp
Jmain
Ohandle
Tblblbl
libSystem.B
__mh_execute_header
_blblbl
_getenv
_handle
_main
_strcmp
_dlopen
$ xxd fat.bin | head
0000000: cafe babe 0000 0048 004e 7569 7420 6475  .......H.Nuit du
0000010: 2068 6163 6b20 3230 3134 202d 204d 6163   hack 2014 - Mac
0000020: 2050 776e 6167 6520 3a29 2020 2020 2020   Pwnage :)      
0000030: 1aaf 1cc0 81bd 6af2 9a73 553a 2f9d 7d03  ......j..sU:/.}.
0000040: c715 6c8d 91dd 1ab8 b8e9 b623 5eaf 3f92  ..l........#^.?.
0000050: 3ee2 8242 b068 3640 0c50 fc7a c3ef 332a  >..B.h6@.P.z..3*
0000060: 9379 7062 756b 8b49 5589 0b82 ef2a 681b  .ypbuk.IU....*h.
0000070: 9e4b d773 1c66 846a a3fa f6e9 12c2 bde9  .K.s.f.j........
0000080: 46fb b6bd 2ceb f310 e862 b78c 7883 f8a1  F...,....b..x...
0000090: 217d d2ec 0211 a32e 9456 84be 5130 ebbf  !}.......V..Q0..

La commande file indique que le fichier est une classe java compilée. Pourtant en regardant les chaines de caractères du fichier, on ne peut penser qu'à un fichier executable : printf, puts, main, strcmp, … Après quelques recherches sur internet ( __mh_execute_header par exemple), on se dit que le fichier ne peut être qu'un fichier Mach-O. Une personne qui a un mac sous la main peut facilement le vérifier.

$ ./fat.bin 
usage : ./fat.bin <flag>
$ ./fat.bin toto
Nope.

Une seule chose ne semble pas correspondre : le magic number du fichier. Après quelques recherches, on se rend en fait compte que le fichier est un universal binary. Ce même format de fichier semble corrompu, on peut essayer d'écrire un petit script python afin d'essayer d'identifier ce qui cloche.

#!/usr/bin/env python
 
from struct import unpack
 
with open('fat.bin', 'rb') as f:
    f.seek(0, 2)
    fsize = f.tell()
    f.seek(0)
    magic, narchs = unpack('>II', f.read(8))
    print "magic : 0x%08x" % magic
    print "narchs : 0x%08x" % narchs
    for i in range(narchs):
        cputype, cpusubtype, offset, size, align = unpack('>IIIII', f.read(4*5))
        print "[+] arch :"
        print "\t- cputype : 0x%08x" % cputype
        print "\t- cpusubtype : 0x%08x" % cpusubtype
        print "\t- offset : 0x%08x" % offset
        print "\t- size : 0x%08x" % size
        print "\t- align : 0x%08x" % align
        print "\t- valid ? : %s" % ("YES" if offset < fsize else "NO")
magic : 0xcafebabe
narchs : 0x00000048
[+] arch :
    - cputype : 0x004e7569
    - cpusubtype : 0x74206475
    - offset : 0x20686163
    - size : 0x6b203230
    - align : 0x3134202d
    - valid ? : NO
...
[+] arch :
    - cputype : 0x00000007
    - cpusubtype : 0x00000003
    - offset : 0x00001000
    - size : 0x000042bc
    - align : 0x00000000
    - valid ? : YES

Une seule architecture semble être valide, autant au niveau des valeurs de cputype, cpusubtype que de l'offset dans le fichier. Essayons d'extraire cette architecture.

$ dd if=fat.bin of=fat-extract.bin bs=1 skip=4096 count=17084
17084+0 records in
17084+0 records out
17084 bytes transferred in 0.079635 secs (214529 bytes/sec)
$ file fat-extract.bin
fat-extract.bin: Mach-O i386 executable

Nice! Un fichier Mach-O 32 bits ! Let's start IDA !

Reverse du binaire

L'éxecutable contient encore tous les noms de fonction … La fonction main commence par vérifier si le bon nombre d'argument est passé au programme. Si un flag est bien passé en paramètre, la fonction getenv(“HOME”) commence par être appelé puis strcmp(argv[1], “50585be4e3159a71c874c590d2ba12ec”).

__text:00001E6C                 mov     esi, esp
__text:00001E6E                 mov     [esi+4], edx
__text:00001E71                 mov     [esi], eax
__text:00001E73                 call    _strcmp
__text:00001E78                 cmp     eax, 0

50585be4e3159a71c874c590d2ba12ec semble être le flag, mais il n'en est rien. En vérifiant ce que fait la fonction strcmp, on se rend compte qu'elle cherche à résoudre une nouvelle fonction strcmp depuis un handler sur une dylib puis à appeler cette dernière.

__text:00001DC7                 mov     [esp], eax      ; handle
__text:00001DCA                 mov     [esp+4], esi    ; symbol
__text:00001DCE                 call    _dlsym
__text:00001DD3                 mov     [ebp+var_10], eax
__text:00001DD6                 mov     eax, [ebp+var_10]
__text:00001DD9                 mov     ecx, [ebp+var_8]
__text:00001DDC                 mov     edx, [ebp+var_C]
__text:00001DDF                 mov     [esp], ecx
__text:00001DE2                 mov     [esp+4], edx
__text:00001DE6                 call    eax

En cherchant un peu, on se rend compte qu'en fait ce handler est initialisé dans la fonction getenv. Cette dernière commence par xorer la section WOWbeautiful du binaire avec la clef \x4F\xFF\x9F\x13\x20\x37. Les 64 premiers octets sont utilisés comme nom de fichier, le reste des données est écrit dans ce fichier. Une fois le fichier extrait dans /tmp, le handler est initialisé avec un appel à la fonction dlopen.

#!/usr/bin/env python
 
from struct import unpack
 
with open('fat.bin', 'rb') as f:
    f.seek(0x3000) # __WOW__beautiful section
    data = f.read(0x10c4)
 
KEY = "\x4f\xff\x9f\x13\x20\x37"
ret = ""
for i in range(len(data)):
    ret += chr(ord(data[i]) ^ ord(KEY[i%len(KEY)]))
 
print "name : %s" % ret[:64]
with open('fat-dylib.dylib', 'wb') as f:
    f.write(ret[64:])
$ python fat-lib.py
name : /tmp/Ndh2k14.dylib
$ file fat-dylib.dylib 
fat-dylib.dylib: Mach-O i386 dynamically linked shared library

Allons voir ce que fait la fonction strcmp de cette lib :)

Une fois de plus, le programme semble utiliser un xor. Il xor notre argv[1] avec 4 octets : \xBA\xD0\xC0\xDE, puis compare la chaine obtenue avec la séquence \x86\x9d\xa1\xbd\x8a\xa3\x90\xa9\xd4\xe3\xa4\xe4\x93\xee. Il suffit de xorer cette chaine avec la clef de 4 octets pour obtenir le flag !

#!/usr/bin/env python
 
KEY = "\xBA\xD0\xC0\xDE"
data = "\x86\x9d\xa1\xbd\x8a\xa3\x90\xa9\xd4\xe3\xa4\xe4\x93\xee"
 
ret = ""
for i in range(len(data)):
    ret += chr(ord(data[i]) ^ ord(KEY[i%len(KEY)]))
 
print "Flag : %s" % ret
$ python wp-dat.py
Flag : <Mac0sPwn3d:)>
$ ./fat.bin "<Mac0sPwn3d:)>"
This is the flag ;)
writeup/ndh2014/mach2.txt · Dernière modification: 2014/09/25 16:01 par tlk