Handling SO Hell in CTF Pwn
在以前的CTF比赛中,大多使用Ubuntu系列来进行题目的部署,大家也比较习惯用LD_PRELOAD来加载远程libc。而近期的CTF比赛当中,经常出现一些非Ubuntu发行版的libc,例如Debian, CentOS, Arch等等。直接在Ubuntu下就不能用LD_PRELOAD来加载了。
SO Hell
由于不同libc发行版和版本号之间可能ABI不同,不同的loader(ld.so
)和libc(libc.so.6
)如果混用可能无法正常加载。就算同样是Ubuntu系统,不同libc版本之间也不一定能够使用LD_PRELOAD
来进行加载。一般来说,我会使用下载对应版本的ld或者直接使用docker来解决libc加载的问题。
识别libc版本
可以直接尝试运行libc,./libc.so.6
,可以得到libc相关的信息
1 | GNU C Library (Ubuntu GLIBC 2.23-0ubuntu4) stable release version 2.23, by Roland McGrath et al. |
也可以直接strings libc并查找GNU C Library
来寻找
用相应版本的ld启动
可以直接google寻找对应版本的libc相关的rpm, deb包等等,然后不需要安装直接解包,提取其中的ld-2.x.so,其他的可以不管。由于ld本身是一个自举的library,可以直接启动并加载对应的libc
1 | LD_PRELOAD=./libc.so.6 ./ld-2.28.so ./chal |
在这样启动的情况下,ld将被作为一个PIE的程序先被系统的loader加载到对应位置上,而chal则相当于作为一个库加载到地址空间中,实际的地址空间分布将会和直接加载chal有区别。
使用docker解决依赖
docker本身出现的目的之一就是解决应用依赖相关的问题,由于container实际上与host共用同一内核,而且在container中的进程虽然处于自己的namespace中,但在host上依然能够看到对应的进程,这就意味着我们可以使用container启动challenge,并在host上用gdb attach. 这样既解决了库依赖的问题,又不需要在container内部再装工具。
以Hack.lu 2018为例,Pwnable大多使用了Arch进行部署,libc版本很高(2.28),导致Ubuntu无法加载。先pull一个archlinux的镜像。
1 | docker pull base/archlinux |
如果没有其他依赖的话,可以直接以当前用户启动
1 | sudo docker run -i --rm \ |
这样在host上可以看到一个已经启动的chal进程,这时就可以与其交互了,当程序退出之后,container会自动销毁.
结合Pwntools
我在之前的blog中介绍了Pwntools的一些调试相关的用法,但现在Pwntools升级之后,实际不需要再用pidof再去获得pid,而是可以直接通过传一个process对象或者进程名的方式进行attach.
1 | p = process('./chal') |
可以将启动docker的脚本保存为一个shell script,然后利用进程名进行attach,同时指定executable file
1 | p = process('./launch.sh', shell=True) |
这样就和平常的调试体验非常相近了
内核相关
但如果实际发行版的内核版本和host不一样,而题目利用方式又恰好和内核相关的话,docker就无能为力了,这种就需要借助虚拟化+gdbserver来复现远程环境了。