CISCN 2017 baby_driver

一道入门的kernel pwn题,本意是恶补内核知识,看完题感觉难度不大,可是调试花了好久......

给了三个东西,一个cpio,一个启动脚本,还有一个bzImage,先检查一下:

QQ20260821-110648.png

cpio是一个gz压缩后的文件,boot.sh也是正常启动。

这里本来打算直接把qemu复制过来就启动,结果报错是kvm有问题,因为我使用的是虚拟机,kvm可能要用真实硬件。

(于是我又尝试把Windows主机里一模一样的内核和cpio拉过来启动,结果也失败了,可能必须是跑Linux的物理机才行)

于是直接把-kvm参数删掉了,果然跑起来了。

QQ20260821-111914.png

有一个很显眼的init脚本,挂载sys,proc和tmp之后是锁flag,然后调用insmod加载了一个名为babydriver.ko的驱动模块,显然就是漏洞利用点。

然后直接把它拖进IDA:

QQ20260821-122500.png
QQ20260821-122512.png
QQ20260821-122523.png
QQ20260821-122535.png
QQ20260821-122547.png
QQ20260821-122558.png

逻辑也是直来直去的,init函数创建一个字符设备,open会kmalloc一块内存,然后指针存给全局变量,close函数只执行kfree但没有置为NULL,ioctl的特定cmd可以修改这块内存为指定大小。

说一下fd的问题,当调用open函数时,在对应进程的task_struct实例里,存有一个指针,指向一个files_struct实例(文件描述符表),该表的fd_array数组就会多一个元素,该元素同样是一个指针,指向文件的核心结构体file的实例,file实例会维护open,write,ioctl对应的驱动注册函数,注册的详细情况可以看图中伪代码里的init部分,(比如这里babyopen就会注册到open),同时还维护有pos,也就是文件偏移量,这正是为什么打开同一个文件时,不同fd的偏移量也会不一样。

利用思路大概就是创建两个fd(fd1和fd2)(因为open时调用kmalloc会把指针存在全局变量里,所以这里在生成fd2时,其实会发生一次覆盖,不过没什么用),随后使用ioctl修改这个内存的大小,让它正好跟cred的大小相同,随后把它kfree掉,进入cred的专属链表,这样如果创建新的子进程,它就直接被申请出来给子进程当cred实例来使用了。

而恰好我们还有一个UAF,这时就可以改掉那块内存里的uid,euid,guid等变量,统统覆盖为0,子进程也就完成提权了。

然后写exp,先把cpio解出来,写好exp,静态编译之后再cpio打包一次,就把exp传进去了。

QQ20260821-190306.png

这里第一遍exp是使用了execve传/bin/sh而不是system,然后......qemu就崩溃重启了。

改用system就不会崩溃了,但是会提权失败。

这里事后跟AI讨论了一下原因,我觉得比较合理的是,system是fork+execve来执行的,这意味着会有一个孙进程出现,在孙进程执行期间,子进程会陷入wait状态,而如果子进程直接调用execve的话,父进程会马上return 0退出,同时关闭fd2,直接就double free了。

而只改用system还不够,因为子进程和孙进程虽然还挂着,但是父进程仍然会跳过if马上退出(好在因为子进程挂着fd2,所以不会double free,也不会崩溃),失去了父进程的子进程和孙进程就会被systemd收养,但它显然不会把控制台交给孙进程。

所以还需要加一个wait,确保父进程在子进程和孙进程执行完之后才能退出。

这是最终exp:

QQ20260821-201941.png
QQ20260821-202014.png

kernel pwn的体验感是,比用户态堆好玩多了......

评论