记录第一次出PWN题可能会遇到的docker相关问题及其解决办法,还有其他可能用得上的一些命令

0x00 前言

最近给战队出了一道pwn题,第一次体验完整的出题流程,涉及到docker的使用。因为是第一次使用docker来封装题目环境,所以遇到了很多问题。网上有一些关于出pwn题的使用方法,但是有些问题没有提到,所以这里记录一下我遇到的问题,还有解决方法。docker的安装网上教程很多这里不多赘述。

顺带一提,我的操作环境是wsl2的Ubuntu 22.04.3 LTS,若有因环境不同而引起的操作不同,具体请查询其他资料。

0x01 一些前置知识or命令

一般来说,docker的命令之前要加一个sudo来提升权限,不然有些命令可能执行不了。

sudo docker images:查看系统中存有的所有镜像及相关信息。

docker_images

sudo docker ps -a:查看系统中所有容器的状态。

docker_ps

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中只有lscatsh三个命令可用,并且限制workdir,这样选手一般就没法搞搞阵了。(如果这还能容器逃逸啥的,可以考虑把sh换成只能显示flag功能)

0x03 国内部分镜像站被关停无法使用

docker.m.daocloud.io国内依然能用。

1
2
3
4
5
6
7
8
9
$ sudo vim /etc/docker/daemon.json

{
"registry-mirrors":["https://docker.m.daocloud.io"]
}

$ systemctl daemon-reload
$ systemctl restart docker.service
$ docker info

然后就能看到镜像源已经换了。

0x04 本地运行与docker中运行结果不一致

可能是因为docker拉取到的镜像与本地的环境不一样,尤其是libc版本。下面是两个可能的解决方案:

  1. 如果题目和内核无关,只和libc版本有关,可以把需要版本的libc和ld放到workdir中,并用patchelf修改程序的libc目录。

  2. 根据本地环境中的libc版本,还有linux发行版本,可以上网搜到这个版本的发行,然后我们来到docker网站找到与这个日期相近的tag来作为映像。

    举个栗子。现在我的本地环境是ubuntu22.04.3LTS。

    如果我在dockerfile中写FROM ubuntu:22.04,那么他会默认拉取ubuntu22.04的latest版本。我们打开dockerhub看一眼(需要科学上网)。dockerhub最新

    换句话说,这个时候FROM ubuntu:22.04FROM ubuntu:jammy-20240530是一样的。但是最新的是ubuntu22.04.4,显然不符合本地环境。

    可以查到ubuntu22.04.3LTS的发行日期是2023.8.10,那我们就去找tag接近这个日期的映像。dockerhub2023tag

    可以逐个去试一试看符不符合运行期望。也可以在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出题!!!

⬆︎TOP