Ce soir c'est OpenBaize !
Les erratas ainsi que les patchs étant disponibles, je me permet de publier mon exploit ainsi que les explications qui vont avec...
Les explications sont sans sucre ajouté... je n'ai pas trop le temps en ce moment. :-(
Peut être que Monsieur xorl donnera plus d'explications comme il le fait trop bien.
* Description de la vulnérabilité.
La vulnérabilité est aussi bête à comprendre qu'elle a été simple à trouver. Lorsque le kernel gère un getsockopt() il alloue un mbuf pour retourner les informations en userland. Par exemple, voici le code pour récupérer le portrange (IP_PORTRANGE) :
*mp = NULL;
(...)
case IP_PORTRANGE:
*mp = m = m_get(M_WAIT, MT_SOOPTS);
m->m_len = sizeof(int);
if (inp->inp_flags & INP_HIGHPORT)
optval = IP_PORTRANGE_HIGH;
else if (inp->inp_flags & INP_LOWPORT)
optval = IP_PORTRANGE_LOW;
else
optval = 0;
*mtod(m, int *) = optval;
break;
On voit bien à la ligne 2, que le kernel demande l'allocation d'un mbuf.
Le problème se trouve pour les levels IP_AUTH_LEVEL, IP_ESP_TRANS_LEVEL, IP_ESP_NETWORK_LEVEL, IP_IPCOMP_LEVEL où les développeurs d'OpenBSD ont oublié d'allouer un nouveau mbuf et ont directement travaillé [1] sur le mbuf m qui vaut NULL.
case IP_AUTH_LEVEL:
case IP_ESP_TRANS_LEVEL:
case IP_ESP_NETWORK_LEVEL:
case IP_IPCOMP_LEVEL:
(...)
m->m_len = sizeof(int); [1]
switch (optname) {
case IP_AUTH_LEVEL:
optval = inp->inp_seclevel[SL_AUTH];
break;
(...)
*mtod(m, int *) = optval; [2]
(...)
À l'exécution de [1] le kernel tente d'écrire la taille d'un int à l'adresse NULL + off(m_len). Si la page à l'adresse NULL n'est pas alouée, c'est le kernel panic.
Je n'ai pas regardé en détail depuis quand cette vulnérabilité était présente dans OpenBSD. Je suis descendu jusqu'à la version 3.9 et elle est vulnérable...
* Exploitation de la vulnérabilité.
Tout d'abord pour pouvoir exploiter cette vulnérabilité, il est évident qu'il faut pouvoir mapper une page à l'adresse NULL. OpenBSD, depuis la version 4.4 et pour toutes les architectures, n'autorisent plus cela. J'ai tenté en vitesse de trouver un moyen de bypasser cette protection mais proutprout. La technique d'exploitation décrite ici s'applique donc aux OpenBSD qui ont une version inférieure à la 4.4.
Dans le code de la vulnérabilité présentée ci-dessus, nous voyons que nous avons en [2] une instruction très intéressante qui va nous permetre d'écrire la valeur comprise dans optval à n'importe quelle adresse mémoire. En effet, la fonction mtod() retourne simplement (int*)m->data.
Vous voyez où je veux en venir ?
Il suffit de placer (en rw) un faux mbuf à l'adresse NULL avec une belle adresse bien poilue dans m->data. Seulement il y'a quelques limites à prendre en considération... Par défaut, la valeur d'optval sera égale à 0x00000001, seul le root peut la modifier par setsockopt(). Nous pouvons donc écrire un 0x00000001 où l'on veut en mémoire.
Que faire ?
Alors là, c'est là où on s'amuse. Je pense qu'il y a plusieurs façons de faire. Au départ j'avais pensé à écraser en 2 coups l'uid dans la structure proc du processus courant mais j'ai abandonné et je suis parti pour écraser une entrée de la table des appels système.
Bein oui, l'adresse d'origine de la table ne bouge pas... il suffit d'écraser une de ces entrées (SYS_fpathconf par exemple) avec un 0x00000001 en mappant un faux mbuf avec m->data = adresse de sysent[SYS_fpathconf]. Ensuite une fois que c'est fait, on mappe (en rx), toujours à NULL, un shellcode qui va modifier l'uid de la structure proc du process courant pour lui donner le root et on appelle l'appel système fpathconf() pour exécuter notre shellcode et bingo. Pinuts hein ?
Si vous pensez à d'autres techniques je suis preneur. ;-)
Pour plus d'explications, je vous laisser regarder les sources.
* Taddam, sans les mains !
$ id
uid=1000(clem1) gid=1000(clem1) groups=1000(clem1), 0(wheel)
$ ./openbaize
\o/ OpenBSD IP_FUCKING_LEVEL getsockopt() remote root
\o/ Found and badcoded by clem1
\o/ Trying to own this 4.0 OpenBSD OS... 0h0h0h
\o/ Patching sysent (0xd0715fc4) for syscall number 192 with 0x1... h0h0h0
\o/ Mapping shellcode at 0x1... calling our new fake evil syscall number 192
\o/ Hoooooooorray r00t.
# id
uid=0(root) gid=1000(clem1) groups=1000(clem1), 0(wheel)
* Pourquoi deux shellcodes ?
Parce que la structure proc et notamment la structure p_ucred à l'intérieur a changée entre la version 4.2 et 4.3... donc c'est juste une histoire de niveau de déférencement pour arriver à l'uid du owner du process et d'offset...
Les explications sont sans sucre ajouté... je n'ai pas trop le temps en ce moment. :-(
Peut être que Monsieur xorl donnera plus d'explications comme il le fait trop bien.
* Description de la vulnérabilité.
La vulnérabilité est aussi bête à comprendre qu'elle a été simple à trouver. Lorsque le kernel gère un getsockopt() il alloue un mbuf pour retourner les informations en userland. Par exemple, voici le code pour récupérer le portrange (IP_PORTRANGE) :
*mp = NULL;
(...)
case IP_PORTRANGE:
*mp = m = m_get(M_WAIT, MT_SOOPTS);
m->m_len = sizeof(int);
if (inp->inp_flags & INP_HIGHPORT)
optval = IP_PORTRANGE_HIGH;
else if (inp->inp_flags & INP_LOWPORT)
optval = IP_PORTRANGE_LOW;
else
optval = 0;
*mtod(m, int *) = optval;
break;
On voit bien à la ligne 2, que le kernel demande l'allocation d'un mbuf.
Le problème se trouve pour les levels IP_AUTH_LEVEL, IP_ESP_TRANS_LEVEL, IP_ESP_NETWORK_LEVEL, IP_IPCOMP_LEVEL où les développeurs d'OpenBSD ont oublié d'allouer un nouveau mbuf et ont directement travaillé [1] sur le mbuf m qui vaut NULL.
case IP_AUTH_LEVEL:
case IP_ESP_TRANS_LEVEL:
case IP_ESP_NETWORK_LEVEL:
case IP_IPCOMP_LEVEL:
(...)
m->m_len = sizeof(int); [1]
switch (optname) {
case IP_AUTH_LEVEL:
optval = inp->inp_seclevel[SL_AUTH];
break;
(...)
*mtod(m, int *) = optval; [2]
(...)
À l'exécution de [1] le kernel tente d'écrire la taille d'un int à l'adresse NULL + off(m_len). Si la page à l'adresse NULL n'est pas alouée, c'est le kernel panic.
Je n'ai pas regardé en détail depuis quand cette vulnérabilité était présente dans OpenBSD. Je suis descendu jusqu'à la version 3.9 et elle est vulnérable...
* Exploitation de la vulnérabilité.
Tout d'abord pour pouvoir exploiter cette vulnérabilité, il est évident qu'il faut pouvoir mapper une page à l'adresse NULL. OpenBSD, depuis la version 4.4 et pour toutes les architectures, n'autorisent plus cela. J'ai tenté en vitesse de trouver un moyen de bypasser cette protection mais proutprout. La technique d'exploitation décrite ici s'applique donc aux OpenBSD qui ont une version inférieure à la 4.4.
Dans le code de la vulnérabilité présentée ci-dessus, nous voyons que nous avons en [2] une instruction très intéressante qui va nous permetre d'écrire la valeur comprise dans optval à n'importe quelle adresse mémoire. En effet, la fonction mtod() retourne simplement (int*)m->data.
Vous voyez où je veux en venir ?
Il suffit de placer (en rw) un faux mbuf à l'adresse NULL avec une belle adresse bien poilue dans m->data. Seulement il y'a quelques limites à prendre en considération... Par défaut, la valeur d'optval sera égale à 0x00000001, seul le root peut la modifier par setsockopt(). Nous pouvons donc écrire un 0x00000001 où l'on veut en mémoire.
Que faire ?
Alors là, c'est là où on s'amuse. Je pense qu'il y a plusieurs façons de faire. Au départ j'avais pensé à écraser en 2 coups l'uid dans la structure proc du processus courant mais j'ai abandonné et je suis parti pour écraser une entrée de la table des appels système.
Bein oui, l'adresse d'origine de la table ne bouge pas... il suffit d'écraser une de ces entrées (SYS_fpathconf par exemple) avec un 0x00000001 en mappant un faux mbuf avec m->data = adresse de sysent[SYS_fpathconf]. Ensuite une fois que c'est fait, on mappe (en rx), toujours à NULL, un shellcode qui va modifier l'uid de la structure proc du process courant pour lui donner le root et on appelle l'appel système fpathconf() pour exécuter notre shellcode et bingo. Pinuts hein ?
Si vous pensez à d'autres techniques je suis preneur. ;-)
Pour plus d'explications, je vous laisser regarder les sources.
* Taddam, sans les mains !
$ id
uid=1000(clem1) gid=1000(clem1) groups=1000(clem1), 0(wheel)
$ ./openbaize
\o/ OpenBSD IP_FUCKING_LEVEL getsockopt() remote root
\o/ Found and badcoded by clem1
\o/ Trying to own this 4.0 OpenBSD OS... 0h0h0h
\o/ Patching sysent (0xd0715fc4) for syscall number 192 with 0x1... h0h0h0
\o/ Mapping shellcode at 0x1... calling our new fake evil syscall number 192
\o/ Hoooooooorray r00t.
# id
uid=0(root) gid=1000(clem1) groups=1000(clem1), 0(wheel)
* Pourquoi deux shellcodes ?
Parce que la structure proc et notamment la structure p_ucred à l'intérieur a changée entre la version 4.2 et 4.3... donc c'est juste une histoire de niveau de déférencement pour arriver à l'uid du owner du process et d'offset...