做题踩坑实录,赛后复现.
step1: init
拿到附件,查看启动脚本,smep, smap, kaslr,应该还有pti
1 |
|
ext4 镜像挂载
1 | mkdir rootfs |
查看init, etc/init.d 下的文件
1 |
|
修改内容,将ko文件拷贝一份,etc/init.d/rcS内容修改一下,然后umount,启动。(修改效果生效必须要umount)
1 | sudo mount ./rootfs |
内核版本 5.10.186
step2: ko
逆向分析ko文件(直接给出了c文件,也可以不看)。漏洞所在的地方,类似入门经典题目kernel UAF - CTF Wiki。但是结构体大小0x20
1 | static struct mmsg_head *mmsg_head; |
step3: exploit
尝试ROP,但是不太会🤡
1
原文内容: 「PWN」内核 PWN 题目的第一次尝试
- 像,很像呀。看了一下,发现及其类似,于是尝试使用类似的exp进行做
- 调试,找地址,先关闭kaslr,通过泄露获得offset
1 | /* |
rop
1 | // 之前的都一样 |
使用xchg 进行类似栈迁移的操作,从而进行劫持函数执行流
在本地执行时,直接kernel panic。查看报错信息,发现是can't access memory in 0x????(是个用户地址)
。
2
后来看来一下这篇文章发现原因:Kernel Pwn Struct seq_operations and Struct pt_regs
- 这一题开启了smap,而 pwnhub 的那一题中没有。而smap: kernel space 不能 access user space 的东西。
- 这篇文章中说了一个
pt_regs
的结构体,在使用syscall 时,会将某些寄存器内容压入内核栈的栈底
.
1 | struct pt_regs { |
在系统调用的过程中, 不是所有的寄存器都会被改变, 比如 r8 - r15, 他们会在压入 pt_regs 的时保持 syscall 之前的值. 这就为我们提供了布置数据的可能性. 如果在仅能劫持 rip 的情况下 (比如上面介绍的 seq_operations), 跳转到某个形如
add, rsp val; ret
的 gadget, 那么就有可能将 rsp 设置到内核栈的 pt_regs 上, 从而执行我们布置的 ROP 链.
也就是我们 rop 往 内核栈的 pt_regs 中跳转,就不会绕过了smap
如何将寄存器压入: 使用了巧妙地方法,syscall 调用 read,将寄存器压入,并且可以通过seq_operations->start执行rop
调用模板+解释的比较详细的文章:seq_operations+pt_regs
1 | __asm__( |
gadget: 改变rsp, add rsp, xxx; ret,进行栈迁移
1 | pwndbg> x/2wi 0xffffffff81909b8c |
但是我们需要事先知道执行start 时与pt_regs 距离多远。
直接使用没有布局的脚本,自然会kernel panic,可以看到rip的内容,然后与上述的payload进行对比,获得偏移
并且这并不是一个万能的方法
3
还是不对,因此再看参考文章,如下也存在uaf问题。
1 | // module_ioctl |
4
偏移确定
add rsp val,我们需要一个比较具体的值
大概是 0x100+
的gadget吧,不太会,但是此结构体大小大于0x100,并且要开启syscall的栈帧
- 应该可以在ioctl 下断点,但是我失败了😥
执行流程
- 直接看报错: BUG: unable to handle page fault for address: 0000000044444444 ; RIP: 0010:0x44444444获得我们rip指针控制的寄存器 r14
- syscall 不会改变 r8-r15内容。理想情况下r8-r15内容不变,但是可能会产生奇妙的变化。调试,si会走到start指针的操作,从而获得栈结构
- 假设理想化从r14-r8没有发生改变
- si 一路走,但是看不到对应内容?
swapgs; iretq 返回用户态; ret rip,在此处没有使用ret指令,直接iretq, 直接r9为user_rsp就行
1 | swapgs |
swapgs_restore_regs_and_return_to_usermode: 这个比上个复杂一点,需要在迁移一次
1 | swapgs_restore_regs_and_return_to_usermode + 22 |
errors
- qemu cpu为
host
也必须开启kvm, 同时就是这一点,导致我一直不成功,后来去除掉kvm将cpu改为kvm64成功。应该是本机的CPU的安全防护导致一直失败😥。
exploit
signal bypass kpti。执行用户态的任意代码都会报出信号SIGSEGV
,那么在程序开始时将SIGSEGV
与shell函数绑定在一起,那么访问用户态代码时就会报出信号SIGSEGV
,就会执行信号函数。
1 |
|
bypass kpti,修改cr3,在高版本使用,在 +22
地址是我们利用的gadget。
1 | swapgs_restore_regs_and_return_to_usermode |
看别人的做法,好像不需要关注rip后面的内容,但是本题我没有使用这种方式做出来 👀。
- 最后需要我们调用getshell函数。
1 | __asm__( |
tips
commit_creds(prepare_kernel_cred (0)) => 简化为 commit_creds(&init_cred) init_cred: init 进程的权限,为root,在 /proc/kallsyms
内
寻找 gadget 时可能寻找到的gadget不能使用,报错为 kernel tried to execute NX-protected page
,说明其地址不可访问?那就只能换了
为什么找不对gadget?或者根本没有找到🤡。和参考的看看了一下,发现gadget地址根本就没找对。
- ropper + ROPgadget + ropr 三个工具一起使用,获得三个gadget文件。
- extract-vmlinux + vmlinux-to-elf 工具
如何下断点?
- 在想暂停的的地方使用
getchar()
停止后一路si - 在固定的指令地址下断点,但是需要事先知道地址。但是rop时,地址一般都是知道的。
在固定的指令下断点,比如此题就可以在在 add rsp 那一条指令下断点
1 | 0xffffffff81909b8c add rsp, module_ioctl+56 <0x168> |