出PWN题可能会遇到的docker相关问题及其解决办法
0x00 前言
最近给战队出了一道pwn题,第一次体验完整的出题流程,涉及到docker的使用。因为是第一次使用docker来封装题目环境,所以遇到了很多问题。网上有一些关于出pwn题的使用方法,但是有些问题没有提到,所以这里记录一下我遇到的问题,还有解决方法。docker的安装网上教程很多这里不多赘述。
顺带一提,我的操作环境是wsl2的Ubuntu 22.04.3 LTS,若有因环境不同而引起的操作不同,具体请查询其他资料。
0x01 一些前置知识or命令
一般来说,docker的命令之前要加一个sudo来提升权限,不然有些命令可能执行不了。
sudo docker images
:查看系统中存有的所有镜像及相关信息。
sudo docker ps -a
:查看系统中所有容器的状态。
sudo docker ps
:查看当前运行中的容器。
镜像和容器的关系就好比C++中的类和其实例对象的关系,一个镜像可以同时有多个容器运行。
sudo docker run -d -p "0.0.0.0:10001:9999" -h "pwn_h" --name="pwn1" pwn
:从名为pwn的镜像中运行一个名为pwn1的容器,并将这个容器映射到10001端口运行。那么我们就可以通过0.0.0.0:10001访问到这个容器里的服务或者运行的程序。9999是docker的内部端口,-h后面接着的是hostname。
sudo docker stop xxx
:xxx是某个容器container ID的前三位,或者这个容器的名称也行,这个命令用于停止某个容器的运行。只是停止不是删除。
sudo docker rm xxx
:xxx是某个容器container ID的前三位,或者这个容器的名称也行,这个命令用于删除一个容器。
sudo docker rmi xxx
:xxx是某个镜像image ID的前三位,或者这个镜像的名称也行,这个命令用于删除一个镜像。注意镜像ID和容器ID是不一样的。
sudo docker build . -t pwn
:在当前目录下通过dockerfile来build一个名为pwn的镜像。别漏了中间那个点。
sudo docker cp pwn:/home/ctf/flag ./flaglocal
:从名为pwn的容器中复制/home/ctf/flag到自己电脑当前目录,并命名为flaglocal。把两个路径反过来就是从电脑复制文件到容器中。
sudo docker exec -it xxx /bin/bash
:xxx是某个容器container ID的前三位,或者这个容器的完整ID也行,这个命令表示以root身份进入到容器中操作。在容器内输入exit或者按ctrl+D即可退出容器回到自己的系统。
sudo docker info
:查看docker概况,包括镜像源。
sudo docker save aaa > aaa.tar
:保存名为aaa的镜像并导出保存为tar。
sudo docker load --input aaa.tar
:从aaa.tar导入镜像
0x02 dockerfile
一般CTF赛制中pwn出题对dockerfile都是有特定要求的,awd赛制又有另一套方案。可以用ctf-xinetd的模板,关于它的使用可以看这个师傅的文章。我是用战队师傅给的dockerfile模板来创建镜像的。
一把来说,CTF的pwn题环境中,为了防止选手搅屎,会限制bin中只有ls
、cat
、sh
三个命令可用,并且限制workdir,这样选手一般就没法搞搞阵了。(如果这还能容器逃逸啥的,可以考虑把sh换成只能显示flag功能)
0x03 国内部分镜像站被关停无法使用
docker.m.daocloud.io
国内依然能用。
1 | sudo vim /etc/docker/daemon.json |
然后就能看到镜像源已经换了。
0x04 本地运行与docker中运行结果不一致
可能是因为docker拉取到的镜像与本地的环境不一样,尤其是libc版本。下面是两个可能的解决方案:
如果题目和内核无关,只和libc版本有关,可以把需要版本的libc和ld放到workdir中,并用patchelf修改程序的libc目录。
根据本地环境中的libc版本,还有linux发行版本,可以上网搜到这个版本的发行,然后我们来到docker网站找到与这个日期相近的tag来作为映像。
举个栗子。现在我的本地环境是ubuntu22.04.3LTS。
如果我在dockerfile中写
FROM ubuntu:22.04
,那么他会默认拉取ubuntu22.04的latest版本。我们打开dockerhub看一眼(需要科学上网)。换句话说,这个时候
FROM ubuntu:22.04
和FROM ubuntu:jammy-20240530
是一样的。但是最新的是ubuntu22.04.4,显然不符合本地环境。可以查到ubuntu22.04.3LTS的发行日期是2023.8.10,那我们就去找tag接近这个日期的映像。
可以逐个去试一试看符不符合运行期望。也可以在dockerfile中,往镜像假如ldd的指令,这样就可以进入到容器中查看libc版本。
这里还会出现一个问题,那就是本地的ubuntu是会不断更新的。比如22.04.3一开始的libc版本是2.35_3.1,但是随着安全升级,会变成2.35_3.8,那就可能不能根据日期来选择tag了,因为旧日期的tag版本,libc版本也是旧的。还有一个问题比较恶心的就是,wsl因为魔改的原因,就算linux版本和libc版本一致了,也会因为内核的原因导致奇奇怪怪的问题。所以尽量避免用wsl出题(避坑)。
出好了题目保存docker之后,最好放到其他机器再测试一下,有时候很鬼畜的不同机器会有不同运行结果的(虽然几率比较小)。
0x05 如何调试docker内运行的程序
xf1les师傅告诉我,本地运行docker的话,gdb是可以attach到docker中运行的程序的。有两种调试方法。
第一种是进入到容器当中运行程序,然后在另一个终端再进一次容器,通过pmap -d pid
来查看程序内存布局。这里的pid是容器内的pid。可以给镜像装gdb,然后就可以在容器里调试了。这个方法有个缺点,那就是在root后的容器里运行程序,他的运行环境不一定是docker环境,可能取决于宿主机。
第二种是在docker外调试。假如我现在通过nc打开了容器里的某个服务,现在我打开另一个终端,在终端里输入ps -auxw
,可以看到刚刚开启的服务的名字,用对应的pid(这个pid是对应本机linux的)来进行attach:gdb attach pid
即可调试容器中的程序。这样调试的话,可以用自己电脑上的pwndbg,比较方便,而且更加准确。但是这样attach是无法自动载入符号表的,需要在pwndbg中手动设置程序和libc和ld。
如果想从脚本调试,可以在脚本中穿插一个pause,然后手动查找pid并attach。
0x06 后记
pwn出题还是相当不容易的。如果后续还遇到了其他问题,我会继续更新。一定不要用wsl出题!!!