hxp2020-pfoten
1. 前言
更新时间 | 更新内容 |
---|---|
2020-12-28 | 初稿 |
附件仍然可以从ctftime上下载[1].
一个很有意思的内核题, 赛后跟着2019师傅的wp[2]复现了一遍, 踩了一些坑, 记录一下.
2. 正文
与常见的内核题目不同, 这个题目中没有一些恶意内核模块, 使用的也是标准的linux内核.
问题出在启动文件中:
1 |
|
启动脚本中, 一开始是一些常规初始化操作. 然后进行如下操作:
- 使用
umask
[3]将新建文件时的权限设为rw-rw-rw
. (默认的umask值是002, 此时新建文件时权限是rw-rw-r--
). - 使用
dd
新建一个大小为10MB的文件/swap
, 用0填充 - 使用
losetup
[4]将/swap
这个文件这个一个loop device/dev/loop0
关联起来 - 使用
mkswap
[5]在/dev/loop0
这个设备中建立swap空间. - 使用
swapon
启用/dev/loop0
这个swap空间.
简单来说, swap空间的目的就是使用硬盘空间来替代内存空间, 当内存空间不足时, 内核会将部分不常用的物理内存挪到swap空间中. 用户可以通过设置swappiness
参数来修改替换策略. swappiness
默认值是60. 即当物理内存使用达到40%时, 就开始启用swap空间. 有关swap空间的更多信息参考[6].
此时已经可以发现漏洞点了: 因为/swap
的权限是rw-rw-rw
, 所以我们可以通过修改/swap
文件实现对物理内存的修改.
具体思路如下: 因为1号init进程是root权限执行的, 所以如果可以修改这个进程代码段执行shellcode的话, 就可以实现提权拿flag了. 经过对2019师傅wp的参考和多次尝试, 最终选择修改busybox中的syscall
函数, 可以比较稳定地触发shellcode. 2019师傅一开始找的是_exit
. 但是碰巧syscall
正好在_exit
后面, 所以发现替换syscall
可以稳定触发shellcode. 关于如何定位syscall函数, 可以编译一个带符号的busybox, 然后通过比较来确定.
1 | .text:00000000004D128D loc_4D128D: ; CODE XREF: sub_4CE339+2↑j |
通过使用syscall附近的代码作为特征去搜索 /swap
文件, 同时不断用mmap
来消耗占用内存, 直到发现 syscall
函数并将其后代码替换成shellcode.
关于从IDA中复制内存推荐一个工具 LazyIDA[7], 可以一键生成python代码或者c代码, 很方便.
最后exp如下:
1 |
|
虽然思路很清晰, 但是写exp的过程中还是踩了很多坑:
- PAGE_SIZE的调整, 感觉就是玄学…
- printf的位置, 在发现needle和写swap文件之间加了printf就不行. 可能因为printf用了syscall.
- 这个exp成功率也不是100%, 差不多30%左右吧..
- 直接
./run.sh
不行;./ynetd -t 300 -lt 30 -lm -1 /home/ctf/run.sh
行…. - 在shell里面执行exit 不会退出, 这个导致一开始想的修改init返回时的代码失败
- gdb调试时想断在busybox 的
init_main
函数总是断不下来, 导致看不了init进程的栈上的值. 不知道是不是哪儿操作不对…
最后附上一些辅助脚本:
1 | #!python3 |
1 | #!python3 |
对于这种需要静态编译exp并上传的题目推荐使用musl-gcc
编译, 可以大幅缩小可执行文件的体积(思路来源[8]):
1 | $ du -h ./a_* |
3. 结语
只是因为用umask设置了错误的掩码, 就导致系统被提权. 太神奇了.
本题应该还有一些别的解法. 比如修改init进程的栈构造rop(enlx师傅的做法[9]), 或者改内核啥的.
写这种复杂系统的漏洞利用时思路和写堆利用时的思路还是挺不一样的. 系统太复杂了, 一个不起眼的变化就可能导致exp失败, 比如printf的位置. 写这个exp时一定要构造好思路再小心写, 否则调试时真的是令人绝望…