Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2022-1015-1016 — Traduction en espagnol des CVE-2022-1015 et 1016 découverts et documentés par David. | Kitploit
Outils/GitHubGitHub/zanezhub/cve-2022-1015-1016
Escalade de PrivilègesAnalyse des VulnérabilitésExploitationApprentissage et ÉducationExploitation de Binaires
GitHubzanezhub/cve-2022-1015-1016

CVE-2022-1015-1016

Traduction en espagnol des CVE-2022-1015 et 1016 découverts et documentés par David.

Voir le dépôt
1il y a 4 ansPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager
Site web

CVE-2022-1015 & CVE-2022-1026

Ce README.md est une traduction du blog de David. David a trouvé les CVE 1015 et 1016 dans le noyau Linux. Vous pouvez visiter sa page web pour lire le document original.

Voici ses réseaux sociaux :

  • Twitter
  • Github

Une analyse des deux nouvelles vulnérabilités Linux dans nf_tables

Publié le 2 avril 2022.

  • CVE-2022-1015 permet un accès hors limites (out-of-bounds) causé par de faibles validations des arguments d'entrée, pouvant conduire à l'exécution de code à distance et à une élévation de privilèges locale.
  • CVE-2022-1016 est lié à une mauvaise initialisation des variables stockées sur la stack, ce qui peut être utilisé pour fuiter une grande variété de données du noyau vers l'espace utilisateur (userspace).

Ces problèmes devraient être exploitables dans les configurations par défaut des versions les plus récentes d'Ubuntu et de RHEL. J'ai écrit ma preuve de concept (PoC) pour le CVE-2022-1015 en ciblant la version 5.16-rc3 du noyau d'Arch Linux.

Ce document s'adresse aux personnes ayant une connaissance de base du noyau Linux en termes de fonctionnalité et de sécurité. J'ai essayé de rendre ce document accessible aux personnes qui n'ont pas de connaissances sur la stack réseau afin de le rendre accessible à tous.

Voici un guide de lecture :

  • Si vous êtes ici simplement pour lire à propos de la vulnérabilité, commencez à la Section 4
  • Si vous voulez aussi un peu de contexte sur le sous-système du noyau, commencez par la Section 2
  • Si vous êtes intéressé par un peu plus de contexte, lisez l'intégralité du document

1. Contexte

À la mi-février, le programme de sécurité de Google a annoncé qu'il poursuivrait son programme de primes kCTF, offrant des récompenses allant de 31 337 $ à 91 337 $ pour un exploit du noyau Linux capable d'élever les privilèges jusqu'à l'utilisateur root depuis des processus sans privilèges dans un sandbox nsjail.

Étant un pauvre étudiant, cela a évidemment attiré mon attention. C'était ma première fois à la recherche d'une vulnérabilité du « monde réel », mais lors de mes aventures à jouer au CTF avec mon équipe, je me suis familiarisé avec le noyau Linux en termes de sécurité. Après des heures et des heures avec très peu de progrès, voire aucun (mais avec une meilleure connaissance de Linux), j'ai réussi à trouver quelques vulnérabilités dans le module nf_tables.

Malheureusement, en fin de compte, je me suis rendu compte que ce module n'était pas présent dans les règles du kCTF de Google (donc je n'ai obtenu aucune récompense pour ces deux vulnérabilités). Mais évidemment, je les ai quand même signalées et j'ai écrit un exploit LPE (élévation de privilèges locale) pour le CVE-2022-1015.

1.1 Identification de la cible et stratégie d'audit

Bon, vous avez donc décidé que vous allez trouver des vulnérabilités dans Linux. Et maintenant ? Linux est un projet gigantesque, et il est assez facile de ne pas voir la forêt à cause des arbres (vous vous concentrez tellement sur les détails que vous perdez de vue ce qui est vraiment important, vous n'avez pas une vue d'ensemble de la situation). Pour aggraver les choses, de nombreuses parties ne sont pas documentées et vous devez lire beaucoup de code pour comprendre ce qui se passe.

J'ai commencé par essayer d'avoir une perspective détaillée du modèle de sécurité de Linux. Trouver un bug est une chose ; mais trouver un bon bug en est une autre très différente. Après tout, tous les bugs ne sont pas créés égaux :

  • Si un bug nécessite des privilèges root, il n'existe pas de limite de sécurité significative (à moins que la signature des modules du noyau (kernel module signing) ne soit activée)
    • Parmi les choses qui me viennent à l'esprit, il y a beaucoup de modules de systèmes de fichiers (virtuels). Seul l'utilisateur root initial peut monter ces systèmes de fichiers. L'exception repose sur vfe qui spécifie FS_USERNS_MOUNT, auquel cas vous pouvez les monter dans le user namespace.
  • Si l'on ne peut pas accéder à un bug via les appels système, il ne sera probablement pas exploitable.
    • Cela s'applique à de nombreux pilotes matériels, car vous n'avez pas d'accès physique à la machine. Les pilotes réseau de bas niveau pourraient néanmoins être une bonne cible si vous pouvez, p. ex., envoyer des données via Bluetooth ou 802.11.ac.
    • Évidemment, cela dépend du scénario dans lequel vous vous trouvez.
  • De nombreux bugs nécessitent CAP_SYS_ADMIN ou CAP_NET_ADMIN.
    • Les user namespaces sont activés par défaut, donc ce n'est pas un problème.
    • Sinon, vous devrez d'abord élever vos privilèges vers le namespace de l'utilisateur root dans un conteneur.
  • Tous les modules ne seront pas présents sur votre cible.
    • Linux est un logiciel exceptionnel hautement configurable, donc toutes les configurations peuvent varier d'une multitude de façons.
    • La configuration du noyau est généralement accessible depuis /proc/config.gz. Les modules peuvent être compilés en tant que modules (=m) ou compilés séparément et chargés au moment de l'exécution (=y).

Ces restrictions nous aident à connaître les limites des sous-systèmes dans lesquels nous pouvons chercher des vulnérabilités. Je pense que c'est une bonne idée de prendre votre temps pour essayer de planifier votre attaque contre la cible de votre choix.

J'ai appris ma leçon sur le point précédent. Comme je l'ai mentionné, le module nf_tables n'était pas chargé dans l'instance que kCTF nous a présentée. J'aurais pu m'en rendre compte dès le début et m'épargner la déception :p. D'un autre côté, vous ne seriez probablement pas en train de lire ce blog en ce moment si je m'en étais rendu compte plus tôt ; je suppose que les choses ont bien tourné après tout.

Une explication de la raison pour laquelle le COS, fork Linux optimisé pour les conteneurs de Google, n'avait pas nf_tables peut être trouvée ici et ici.

1.2 nf_tables : pourquoi ?

Après avoir évalué les points mentionnés précédemment, j'ai décidé que ma meilleure route pour commencer serait probablement de regarder le code source réseau. De nombreuses fonctionnalités intéressantes y nécessitent CAP_NET_ADMIN, mais comme je l'ai mentionné, ce n'est en réalité pas un problème. Au contraire, je soupçonne que les composants qui nécessitent des capacités spéciales sont généralement moins sécurisés, car les développeurs du noyau peuvent avoir un faux sentiment de sécurité.

J'ai aussi fait l'effort de choisir le sous-système réseau dont je voulais en savoir plus ; de cette façon, même si vous ne trouvez aucun bug, vous pouvez quand même apprendre plein de choses intéressantes.

J'ai étudié de nombreux sous-systèmes réseau, mais je n'ai rien trouvé d'important. Après avoir parcouru le sous-répertoire net/, je suis tombé sur le module nf_tables. Il semblait un peu complexe, alors j'ai décidé de prendre le temps d'apprendre à le connaître.

2. Introduction à netfilter

Netfilter (net/netfilter) est un sous-système réseau assez grand dans le noyau. En résumé, netfilter place des hooks à travers les modules réseau sur lesquels d'autres modules peuvent enregistrer des gestionnaires. Lorsqu'un hook est atteint, le contrôle est délégué à ces gestionnaires, qui peuvent opérer sur leur structure de paquet réseau respective. Les gestionnaires peuvent accepter, rejeter et modifier des paquets.


4. CVE-2022-1015

Après quelques heures à parcourir l'API de nf_tables (net/netfilter/nf_tables_api.c) pour commencer à comprendre exactement comment elle fonctionne, j'ai décidé d'examiner la validation logique des enregistrements que l'utilisateur envoie, et j'ai trouvé quelques comportements suspects. Après avoir réfléchi à savoir si je devenais fou ou non, j'ai écrit un petit PoC (preuve de concept) pour essayer de déclencher la vulnérabilité que j'avais trouvée : une vulnérabilité connue sous le nom d'OOB ou hors limites, qui permet de lire et d'écrire dans la mémoire stack.

Après avoir trouvé un moyen de fuiter les adresses du noyau, prendre le contrôle du pointeur de mémoire a été assez facile. Après un peu de ROP (programmation orientée retour), le shell avec privilèges root est devenu une réalité.

4.1 Root

Chaque fois que la routine init d'une expression doit analyser un enregistrement d'un message utilisateur netlink, la routine nft_parse_register_load ou nft_parse_register_store est appelée selon qu'il s'agit d'un enregistrement source ou d'un enregistrement de destination. J'ai ajouté quelques commentaires :```c int nft_parse_register_load(const struct nlattr *attr, u8 *sreg, u32 len) {

root@kitploit:~
/* Given a netlink attribute and the length
 * that is required to read the requested data,
 * write a register index to `sreg` or return
 * an error on failure. */

u32 reg;
int err;


reg = nft_parse_register(attr);
err = nft_validate_register_load(reg, len);
if (err < 0)
    return err;

/* Write resulting index to the nft_expr.data structure. */
*sreg = reg;
return 0;

}


static unsigned int nft_parse_register(const struct nlattr attr) { / Convert a register to an index in nft_regs */

root@kitploit:~
unsigned int reg;

/* Get specified register from netlink attribute */
reg = ntohl(nla_get_be32(attr));

switch (reg) {
/* If it's 0 to 4 inclusive,      
 * it's an OG 16-byte register and we need to
 * multiply the index by 4 (4*4=16) */
case NFT_REG_VERDICT...NFT_REG_4:
    return reg * NFT_REG_SIZE / NFT_REG32_SIZE;

/* Else we subtract 4, since we need to account
 * for the OG registers above. */
default:
    return reg + NFT_REG_SIZE / NFT_REG32_SIZE - NFT_REG32_00;
}

/* So supplied values of 1, 2, 3, 4 map to
 * OG 16-byte registers, with indices 4, 8,
 * 12, 16
 * Supplied values of 5, 6, 7 overlap the verdict,
 * 8,9,10,11   overlap with OG register 1
 * 12,13,14,15 overlap with OG register 2
 * etc. */

}


static int nft_validate_register_load(enum nft_registers reg, unsigned int len) { /* We can never read from the verdict register, * so bail out if the index is 0,1,2,3 */ if (reg < NFT_REG_1 * NFT_REG_SIZE / NFT_REG32_SIZE) return -EINVAL;

root@kitploit:~
/* Invalid operation, bail out */
if (len == 0)
    return -EINVAL;

/* If there would be an OOB access whenever
 * `reg` is taken as index and `len` bytes are read,
 * bail out.
 * sizeof_field(struct nft_regs, data) == 0x50 */
if (reg * NFT_REG32_SIZE + len > sizeof_field(struct nft_regs, data)) 
    return -ERANGE;

return 0;

}

root@kitploit:~
Les variantes `*_store` sont virtuellement identiques, sauf qu'elles permettent d'écrire dans le *verbdict* sous certaines conditions.

Après avoir examiné la dernière validation, quelque chose ne va vraiment pas ici :```c
if (reg * NFT_REG32_SIZE + len > sizeof_field(struct nft_regs, data))

Cela semble être un integer overflow, n'est-ce pas ? Si nous pouvons faire en sorte que reg contienne une valeur multipliée par 4 qui génère un overflow lorsqu'on lui ajoute len, nous pouvons satisfaire les conditions. Dans nft_parse_register_load, le dernier octet de poids faible de reg est toujours écrit dans le pointeur u8 *sreg, tombant dans notre nft_expr qui est ensuite utilisé comme index.```c *sreg = reg;

root@kitploit:~
Pouvons-nous vraiment ? `reg` est un `enum nft_registers` dans la validation de la routine, de toute façon. Nous pouvons passer des valeurs comprises entre `0x00000001` et `0xfffffffb` inclus, la plage de `nft_parse_register` ; mais `reg` sera-t-il une valeur de 32 bits dans `nft_validate_register_load` ? On sait que les compilateurs peuvent réduire les *enum types* si un type plus petit peut représenter toutes les valeurs. Obtenons un second avis.

Extrait du manuel de GCC :```
The integer type compatible with each enumerated type (C90 6.5.2.2, C99 and C11 6.7.2.2).
Normally, the type is unsigned int if there are no negative values 
in the enumeration, otherwise int. If -fshort-enums is specified,
then if there are negative values it is the first 
of signed char, short and int that can represent all the values, 
otherwise it is the first of unsigned char, unsigned short and unsigned int 
that can represent all the values.

On some targets, -fshort-enums is the default; this is determined by the ABI.

TL;DR ? Cela dépend de l'ABI et du degré d'optimisation possible. Je n'ai trouvé aucune preuve concrète que cette option soit activée par défaut dans les builds Linux.

Mais l'assembleur ne ment jamais. Jetons un coup d'œil :```objdump.x86asm 0000000000001b60 <nft_parse_register_load>: 1b60: e8 00 00 00 00 call 1b65 <nft_parse_register_load+0x5> 1b65: 55 push rbp 1b66: 8b 47 04 mov eax,DWORD PTR [rdi+0x4] 1b69: 0f c8 bswap eax 1b6b: 89 c7 mov edi,eax 1b6d: 8d 48 fc lea ecx,[rax-0x4] 1b70: c1 e7 04 shl edi,0x4 1b73: 48 89 e5 mov rbp,rsp 1b76: c1 ef 02 shr edi,0x2 1b79: 83 f8 04 cmp eax,0x4 1b7c: 89 f8 mov eax,edi 1b7e: 0f 47 c1 cmova eax,ecx 1b81: 85 d2 test edx,edx 1b83: 74 13 je 1b98 <nft_parse_register_load+0x38> 1b85: 83 f8 03 cmp eax,0x3 1b88: 76 0e jbe 1b98 <nft_parse_register_load+0x38> 1b8a: 8d 14 82 lea edx,[rdx+rax*4] 1b8d: 83 fa 50 cmp edx,0x50 1b90: 77 0d ja 1b9f <nft_parse_register_load+0x3f> 1b92: 88 06 mov BYTE PTR [rsi],al 1b94: 5d pop rbp 1b95: 31 c0 xor eax,eax 1b97: c3 ret
1b98: b8 ea ff ff ff mov eax,0xffffffea 1b9d: 5d pop rbp 1b9e: c3 ret
1b9f: b8 de ff ff ff mov eax,0xffffffde 1ba4: 5d pop rbp 1ba5: c3 ret

root@kitploit:~
Les appels de fonction sont assez bien alignés. Les opérations importantes sont à `1b8a`:```objdump.x86asm
lea    edx, [rdx+rax*4]
cmp    edx, 0x50
ja     1b9f <nft_parse_register_load+0x3f>
mov    BYTE PTR [rsi], al

rax est le résultat de ntf_parse_register, rdx est len fournie, et rsi est le pointeur sreg. Nous avons déjà levé le doute.

nft_parse_register_store présente le même comportement. Tant que les registres vivent sur la stack, notre vulnérabilité OOB sera évidemment relative à la stack. C'est une bonne chose, car avec un peu de chance, nous pourrons écraser et retourner directement dans la mémoire.

Pour donner un exemple d'entrée vulnérable, un registre de 0xfffffffb et une longueur de 0x20 vont évaluer 0xfffffffb * 4 + 0x20 = 0x0c < 0x50. Après la validation, (u8)0xfffffffb = 0xfb sera écrit dans *sreg.

Cependant, il y a un problème : existe-t-il des expressions qui nous permettent d'utiliser une longueur pouvant provoquer un overflow lors de l'addition ? Après quelques recherches, j'ai découvert que nft_bitwise et nft_payload vous permettent de fournir votre propre longueur en entrée, de 0x00 à 0xff. Beaucoup d'autres expressions semblent avoir des longueurs statiques très petites.

Pour l'instant, cela semble prometteur. L'étape suivante consiste à prendre ces exploit primitives (capacité générique acquise lors d'un exploit) et à les utiliser.

4.2 Examen des exploit primitives

Si nous pouvons définir le type de pouvoir que notre exploit peut nous donner, exploiter cette vulnérabilité devrait être plus facile. Alors, faites preuve d'un peu de patience car nous allons voir un peu d'arithmétique.

Il y a trois points que nous pouvons utiliser pour notre overflow lors de la multiplication du registre, car elle est multipliée par 4 = 2^2 : 2^32 - 1, 2^31 - 1 et 2^30 - 1 (respectivement 0xffffffff, 0x7fffffff et 0x3fffffff). Ces valeurs peuvent diminuer jusqu'à ce que nous ajoutions notre longueur maximale autorisée ; après multiplication par quatre, cela ne résultera plus en un overflow. Un autre point à prendre en compte est que nous ne pouvons pas utiliser de valeurs supérieures à 0xfffffffb, comme mentionné précédemment.

En donnant une longueur spécifique, les valeurs d'octets les moins significatives qui peuvent permettre un overflow avec cette longueur formeront notre intervalle d'indices OOB que nous pouvons utiliser.

Après tout, peu importe quels points d'overflow sont utilisés. Prenons par exemple les valeurs suivantes avec un LSB (bit le moins significatif) de 0xf0 :``` 0xfffffff0 * 4 = 0xffffffc0 0x7ffffff0 * 4 = 0xffffffc0 0x3ffffff0 * 4 = 0xffffffc0

root@kitploit:~
Désormais, nous allons utiliser des valeurs de registre proches de `0x7fffffff`.

Précédemment, nous avons parlé de `nft_payload` et `nft_bitwise`. Certaines propriétés de ces expressions sont :

* `nft_payload` ne peut effectuer que des écritures *OOB*, tandis que `nft_bitwise` peut effectuer des écritures et des lectures *OOB*.

* `nft_payload` peut effectuer des écritures *OOB* jusqu'à 0xff octets de données arbitraires.

* `nft_bitwise` ne peut en réalité écrire que jusqu'à `0x40` octets de données arbitraires et ne peut lire que `0x40` octets de données situées dans la *pile* de l'espace de registres .
  
  * `nft_bitwise` nécessite un `sreg` et un `dreg`, qui doivent passer la validation avec la même valeur de longueur.
  
  * Nous n'avons que `0x40` octets d'espace de registres, donc nous voulons soit lire soit écrire depuis l'espace de registres, mais nous ne pouvons pas passer la validation avec une longueur supérieure à `0x40`.

Nous pouvons utiliser une valeur de longueur plus grande pour `nft_bitwise`, mais cela signifie que `sreg` et `dreg` doivent être hors limites, ce qui ne serait pas très utile pour nos objectifs. Donc, pour l'instant, nous travaillerons avec la longueur de `0x40`.

Compte tenu de tout cela, quels types d'*exploits* pourrons-nous utiliser ?

`nft_bitwise` a une longueur maximale de `0x40`. Cela signifie que la valeur de registre multipliée par quatre devrait être au moins `0xffffffc0`. La plus grande valeur que nous pouvons obtenir en multipliant par quatre est `0xfffffffb`, et comme `0xfffffffb + 0x40 = 0x3b <= 0x50`, cela passera la validation.

`0x7ffffff0 * 4 = 0xffffffc0` : la limite inférieure est `0xf0`.
`0x7fffffff * 4 = 0xfffffffb` : la limite supérieure est `0xff`.

En traduisant en [*décalages d'octets*](https://es.wikipedia.org/wiki/Offset_(inform%C3%A1tica)) :```
0xc1 * 4        = 0x304
0xeb * 4 + 0xff = 0x4ab

nft_payload puede escribir fuera de límites a través de los offsets [0x304, 0x4ab] desde struct nft_regs.

Maintenant que tout cela est clarifié, qu'est-ce qui se trouve réellement sur la pile à ces offsets ?

La routine nft_do_chain peut être appelée via de nombreux chemins de code. De nombreux facteurs changeront la forme de la pile avant le stack frame (cadre de pile) de nft_do_chain :

  • Que le chain hook soit un input ou un output.

    • Si nous avons un chain hook configuré comme input, le hook se déclenchera dans le contexte softirq du dispositif réseau respectif avec la pile softirq.
    • Si nous avons un chain hook configuré comme output, le hook se déclenchera dans le contexte de syscall (appel système) send* avec la pile de syscall.
  • Le protocole que nous utilisons.

    • Envoyer un paquet IP brut aura un call stack assez différent de, p. ex., un paquet UDP.

Je pense que vous pouvez obtenir de nombreuses variations de call stacks en utilisant différentes combinaisons de protocoles, d'interfaces et d'emplacements de hooks. Pour le moment, nous utiliserons un chain hook configuré comme un output avec un paquet UDP.

Diagramme de la pile avec output et UDP

Disposition de la pile et portées hors limites dans nft_do_chain lorsqu'un paquet UDP envoyé atteint un hook configuré en output

4.3 Fuite d'informations par canal latéral (side-channel)

Pour pouvoir créer un exploit stable, nous devrons d'abord filtrer l'adresse de l'image du noyau.

L'adresse de l'image du noyau a 9 bits d'entropie (mesure de l'incertitude existante face à un ensemble de messages, dont un seul sera reçu), ce qui signifie qu'il y a 512 positions différentes dans lesquelles le noyau peut être chargé. Selon le scénario de votre attaque, il y a une probabilité de 1 sur 512 que l'attaque fonctionne correctement ; mais il serait préférable que nous puissions obtenir un exploit plus stable.

L'étape la plus simple est d'essayer d'utiliser notre capacité de lecture hors limites que nft_bitwise nous a fournie pour copier certaines données de la pile dans nos registres. Comme l'intervalle total que nous pouvons lire a une longueur de0x7c octets, il existe une assez bonne probabilité que l'adresse du noyau s'y trouve.

Portée hors limites de nft_bitwise

Portée hors limites de nft_bitwise

C'est notre jour ! Il en existe deux :``` gef➤ x/bx 0xffffffff815b49c1 0xffffffff815b49c1 <import_iovec+49>: 0xc9 gef➤ x/bx 0xffffffff819ac3ec 0xffffffff819ac3ec <copy_msghdr_from_user+92>: 0xba

root@kitploit:~
Écrire cela dans les registres est une chose, mais les extraire en est une autre. Après avoir investigué, il semble qu'il n'existe pas de moyen simple de lire directement les registres lorsque `nft_do_chain` est en cours d'exécution.

Dans mon rapport original à [email protected], on m'a informé que l'expression `nft_dynset` par un mainteneur de netfilter, qui prend en charge les [*ensembles dynamiques*](https://en.wikipedia.org/wiki/Dynamic_set) qui peuvent agir comme une sorte de base de données pouvant écrire et lire à travers différentes exécutions de `nft_do_chain`. Apparemment, `nft_payload` a également la capacité d'écrire au paquet lui-même, je ne m'en étais pas rendu compte.

À la place, j'ai décidé de continuer avec mon [*side-channel attack*](https://es.wikipedia.org/wiki/Ataque_de_canal_lateral). En raison de la nature de `nf_tables`, vous pouvez provoquer des effets secondaires. En fait, on pourrait dire que ce ne sont même pas des effets secondaires, mais des effets primaires.

En créant des règles qui suppriment ou acceptent le paquet en se basant sur la valeur de l'adresse mémoire du kernel que nous copions, nous pouvons peu à peu déduire cette valeur en examinant si les paquets que nous envoyons ont également été reçus.

1. Créer une *socket* UDP qui reçoit des paquets sur `127.0.0.1:9999` :
* Il devrait recevoir les paquets dans un thread séparé.

* Un message devrait être renvoyé pour chaque paquet reçu.
2. Ajoutez une règle qui :
   
   1. Copie l'adresse du kernel dans les registres avec `nft_bitwise`.
   
   2. Utilise `nft_cmp_expr` pour comparer l'adresse à une constante.
   
   3. Supprime un paquet si la comparaison évaluée est vraie.

3. Envoyez un paquet UDP à `127.0.0.1:9999`
   
   1. Nous pouvons déterminer une partie de l'information concernant l'adresse du kernel en fonction de si nous recevons un message en retour.

4. Répétez les étapes 2 et 3 avec les valeurs appropriées jusqu'à ce que vous ayez suffisamment d'informations pour déterminer l'information par vous-même.

![](https://assets.kitploit.com/production/public/readmes/35022/71fabf101d1c962c5434bff7babccd3f294cd3d278765f1b03121429c5417b12.png)

Il existe encore quelques avertissements. Par exemple, le paquet que nous recevons pourrait également être supprimé sans aucun avertissement préalable. Pour atténuer cela, nous pouvons ajouter une réduction de bruit, pour laquelle nous aurons besoin d'une *base chain* et d'une *auxiliary regular chain*.

Règle dans la chaîne de base :

| #   | Expression        | Arguments                                                                                               | Commentaire                                                                                               |
| --- | -------------------- | -------------------------------------------------------------------------------------------------------- |:---------------------------------------------------------------------------------------------------------- |
| 0   | `nft_payload`        | base=NFT_PAYLOAD_TRANSPORT_HEADER<br/>offset=offsetof(udphdr, dport)<br/>len=sizeof_field(udphdr, dport) | Écrire le port de destination du paquet dans le registre 8.                                                |
| 1   | `nft_cmp_expr`       | op=NFT_CMP_EQ<br/>sreg=8<br/>data=9999                                                                   | Comparer le port de destination à `9999`, et retourner `NFT_BREAK` si le résultat n'est pas égal.          |
| 2   | `nft_payload`        | base=NFT_PAYLOAD_INNER_HEADER<br/>offset=0<br/>len=8                                                     | Écrire les huit premiers octets du paquet dans le registre 8.                                              |
| 3   | `nft_cmp_expr`       | op=NFT_CMP_EQ<br/>sreg=8<br/>data=0xdeadbeef0badc0de                                                     | Comparer les huit premiers octets à la valeur magique, et retourner `NFT_BREAK` s'ils ne sont pas égaux.   |
| 4   | `nft_immediate_expr` | verdict=NFT_JUMP<br/>chain=aux_chain                                                                     | Comme la règle est encore en cours d'évaluation, les conditions doivent correspondre, et appeler notre *auxiliary chain*. |

Règle dans la chaîne auxiliaire :

| #   | Expression     | Arguments                                                              | Commentaire                                                                                                                                                                                  |
| --- | --------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0   | `nft_bitwise`   | op=NFT_BITWISE_RSHIFT<br/>data=SHIFT_AMT<br/>dreg=OOB_OFFSET<br/>sreg=8 | Écrit l'adresse du kernel dans les registres en utilisant la lecture hors limites, décalée par les bits de `SHIFT_AMT` pour obtenir l'octet de l'adresse souhaitée dans le bon registre.    |
| 1   | `nft_cmp`       | op=NFT_CMP_GT<br/>sreg=ADDRESS_OFFSET<br/>data=COMPARAND                | Comparer les octets de l'adresse du kernel avec `COMPARAND`, retourner `NFT_BREAK` si ce résultat n'est pas égal.                                                                            |
| 2   | `nft_immediate` | verdict=NFT_DROP                                                        | Supprimer le paquet si l'octet de l'adresse est plus grand que `COMPARAND`.                                                                                                                  |

En vérifiant le port de destination et en comparant les huit premiers octets intérieurs de l'*header* à une valeur magique, nous pouvons activer les effets secondaires pour les paquets que nous voulons.

En changeant dynamiquement `COMPARAND`, nous pouvons effectuer une recherche binaire pour trouver l'octet de l'adresse du kernel en `0(log(n))` fois. En changeant dynamiquement `SHIFT_AMT` aux prochains multiples de huit, nous pouvons passer à l'octet de mémoire suivant et recommencer.

#### 4.3.1 Filtrer le pseudo-code

Un peu de code python pour filtrer l'adresse mémoire. Le plus drôle, c'est que j'aurais facilement pu implémenter cela en python. Rappelez-vous que vous n'avez pas toujours à faire vos exploits pour un kernel en C :p```python
'''
Asumimos que un hilo secundario está recibiendo
paquetes UDP en 127.0.0.1:9999 y todo lo relacionado
con nf_tables ya está configurado
p. ej. table, base y auxiliary chain
'''

def leak_byte(pos):
    s = socket.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
    s.settimeout(200) # 200ms debería ser más que suficiente
    s.bind(("127.0.0.1", 1234))

    # buscar los límites
    low = 0, high = 255

    while True:
        mid = (low + high) // 2

        # si encontramos el valor, lo regresamos 
        if low == high:
            s.close()
            return mid

        set_leak_rule(SHIFT_AMT=pos*8, COMPARAND=mid)

        # Enviar el paquete y activar la auxiliary chain
        s.sendto(pack(0xdeadbeef0badc0de), ("127.0.0.1", 9999))

        # El hilo secundario regresa a 127.0.0.1:1234
        res = s.recvfrom(0x2000)

        if not res:
            '''
            nuestro paquete fue soltado
            ya que no se regresó nada en los 200ms
            lo que significa que 

            byte to leak >= mid
            el byte a filtrar es mayor o igual a mid (127)
            '''
            low = mid
        else:
            '''
            [sanity check o prueba de cordura]

            se usa para evaluar rápidamente si 
            el valor a calcular es siquiera posible

            https://es.wikipedia.org/wiki/Prueba_de_cordura
            '''

            if res != b"MSG_OK":
                print("Something went wrong")
                return None

            '''
            Nuestro paquete fue aceptado, lo que
            significa que 

            byte to leak < mid
            byte a filtrar es menor a mid (127)
            '''
            high = mid - 1

leak_bytes = lambda: [leak_byte(i*8) for i in range(4)]

4.4 Exécution arbitraire de code (Arbitrary code execution)

Maintenant que nous avons obtenu le leak, l'exécution arbitraire de code devrait être très facile. L'écriture hors limites de nft_payload devrait pouvoir écrire une attaque RoP en chaîne pour le stack, n'est-ce pas ?

Nope. Nous n'avons pas eu beaucoup de chance, du moins sur ce kernel en particulier. L'écriture hors limites de nft_payload s'aligne presque entièrement avec le stack frame de la routine udp_sendmsg. L'adresse de udp_sendmsg se trouve à l'offset +0x2f8 relatif aux registres ; cette localisation est trop basse pour être atteinte avec nft_payload ou nft_bitwise (nous pouvons commencer à écrire à partir de l'offset +0x304, si près...). L'adresse inet_sendmsg est localisée à l'offset +0x4a8. Techniquement, nous pouvons l'atteindre (et écraser les trois octets inférieurs), mais il y a un stack canary (technique utilisée pour la détection d'un stack buffer overflow avant que l'exécution de code malveillant puisse se produire) à l'adresse +0x0458 que nous devons également écraser pour y parvenir. Cela ferait évidemment crasher le kernel, donc faire cela n'est pas une option.

J'ai réussi à utiliser cette méthode sur une autre build du kernel, mais il semble que tenter de faire de même pour le kernel que j'utilise pour ce blog sera un peu plus difficile.

Maintenant, peut-être pourrions-nous faire un peu de contrived stack frame hacking pour écraser les variables locales dans udp_sendmsg. Nous pourrions aussi essayer d'écraser le verdict chain pointer, en utilisant une valeur des registres p. ex. 0x7fffff00 (je pense que cela pourrait être une technique géniale ; compte tenu du défi).

Essayons de changer la base chain hook que nous utilisions. Nous utilisions une chain output, que se passerait-il si nous la changions en une input ?

Le diagramme de la portée hors limites dans nft_do_chain si un paquet UDP envoyé atteint le hook input

Ça a l'air un peu mieux ! Nous pouvons écraser l'adresse de retour du frame de __netif_receive_skb_one_core (offset +0x328), qui retourne vers __netif_receive_skb. Étant donné qu'elle est relativement proche de la portée hors limites de notre nft_payload, nous pouvons faire pointer notre index OOB (hors limites) directement vers cette adresse de retour, en contournant le stack canary à l'offset +0x310. L'offset +0x328 correspond à l'index 0xca.

Pour déclencher l'écrasement de l'adresse de retour, nous créons une nouvelle chain input dans la table, et nous y ajoutons une règle avec un nft_payload qui écrit 0xff octets depuis le header intérieur du paquet vers l'index 0xca. Ensuite, nous envoyons un paquet avec le payload, et boom.

🥳 🥳 🥳 🥳 🥳

Télécharger l’outil
  • Vous pouvez utiliser /proc/modules et /proc/kallsyms, mais ils ne sont pas toujours fiables, car les modules peuvent être chargés dynamiquement dans le noyau (p. ex. request_module).
  • Si vous n'êtes pas sûr, écrivez un petit programme qui essaie d'interagir avec le module.