docker 学习笔记及其在 ctf 中的应用
docker的基本使用
阮一峰老师的文章:Docker 入门教程,学习过程中大部分参考了这篇文章。
安装docker
我的安装过程如下:
卸载旧版本:
sudo apt-get remove docker docker-engine docker.io containerd runc
根据本地ubuntu版本选择下载要安装docker版本的.deb包:https://download.docker.com/linux/ubuntu/dists/,转到对应版本pool/stable/目录
安装下载的deb包:
sudo dpkg -i /path/to/package.deb
或者使用apt安装
1
2sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io2022/11/27更新:在ubuntu20.04下,用上面的命令安装不成功,解决方案参考Installing Docker in Ubuntu, from repo. Can’t find a repo,命令如下:
1
2
3
4
5
6
7sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io
sudo apt-get install ca-certificates curl gnupg lsb-release
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io测试安装是否成功:
sudo docker run hello-world
正常情况下运行docker是需要sudo权限的,为了防止每次都需要加一个sudo前缀,可以新建一个docker组,并将当前用户添加到这个组里。具体操作如下几条命令:
1 | sudo groupadd docker |
重启后,直接运行docker run hello-world
,发现普通用户也能运行啦!
常用命令
使用docker时,常用命令都列在这儿了
1 | docker image ls #列出本机所有的image文件 |
echo “123” > test.txt
cat > test1.txt <<EOF
1
2
3
4
EOF
实践:制作一个容器并发布
步骤:
编写 Dockerfile 文件
创建 image 文件
1
2
3
4docker image build -t koa-demo .
# 或者
docker image build -t koa-demo:0.0.1 .
# 使用-t参数指定生成的image文件名,冒号后面指定标签(默认标签是latest)。最后的.指定文件所在路径,.表示当前路径生成容器
1
2docker container run --rm -p 8000:3000 -it koa-demo:0.0.1
# 容器的 3000 端口映射到本机的 8000 端口发布 image 文件
实践:使用docker-compose启动容器
1 | sudo curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose |
docker-compose.yml文件解析
1 | version: '2' # 表示该 Docker-Compose 文件使用的是 Version 2 file |
使用docker-compose.yml启动容器
1 | docker-compose up |
实践:docker容器迁移
docker save/load
:用来保存/加载image镜像包
docker export/import
:用来保存/加载container容器包
1 | docker image ls |
ctf中的应用
起docker
通常ctf比赛中提供Dockerfile给我们,我们需要先build出image,然后再运行container
1 | docker build -t nullptr . && docker run -p 1024:1024 --rm -it nullptr |
方式2、带命令行
1 | docker build -t nullptr . && docker run -p 1024:1024 --rm -it nullptr bash |
方式3、新开一个端口,以特权模式运行,并且带命令行
1 | docker build -t nullptr . && docker run -p 1024:1024 -p 1234:1234 --privileged --rm -it nullptr bash |
解决网络的问题
ubuntu 19.10的source.list(注意是http,不是https):
1 | # 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释 |
以ALLES!CTF 2020中的nullptr这个题为例,其Dockerfile做了如下修改:
1 | # docker build -t nullptr . && docker run -p 1024:1024 --rm -it nullptr |
gdbserver调试
有两种方法:
通过Dockerfile生成image,然后启动container时直接拉起目标进程(默认)
docker container run --rm -p 8000:8888 -p 1234:1234 -d <img-name>:latest
进入docker内部,并起一个shell:
docker exec -it <containerID> /bin/bash
(本地)让目标进程停下来:通过python脚本连接docker服务,并在脚本中通过raw_input()停下来,给gdbserver一些时间attach
(docker内)查看目标进程的pid,启动gdbserver,attach到目标进程:
1
gdbserver :1234 --attach <pid>
(本地)启动gdb,连接远程目标:
1
2file <xxx>
target remote :1234
启动container时命令行指定”/bin/bash”,进入docker后再手动起目标进程
docker container run --rm -p 8000:8888 -p 1234:1234 -it <img-name>:latest /bin/bash
(docker内部)运行目标进程:
./xxx &
(本地)让目标进程停下来:通过python脚本连接docker服务,并在脚本中通过raw_input()停下来,给gdbserver一些时间attach
(docker内部)查看目标进程的pid,启动gdbserver,attach到目标进程:
1
gdbserver :1234 --attach <pid>
(本地)启动gdb,连接远程目标:
1
2file <xxx>
target remote :1234
拉取docker中文件
查看docker的 container id:
1 | docker container list |
将docker内文件拉取至本地:
1 | docker cp <containerId>:/file/path/within/container /host/path/target |
在poc脚本中指定如下libc和ld:
1 | myelf = ELF("./note") |
ctf pwn题部署工具
socat
socat可以为每一个连接者提供一个独立的二进制程序执行环境
socat基本用法:
1 | socat - - # 把标准输入和标准输出对接,输入什么显示什么 |
xinetd
在实体机上部署部分题目时(如一人起一个qemu),需要使用xinetd服务
基本使用方法:启动一个二进制
那么,对于一个新手来说,应该怎样入门xinetd的使用呢?这里我记录了几个重要的步骤:
首先,应当写好我们要运行的二进制程序。根据外来的连接请求,我们需要为它们分别起一个新的程序,用于交互。这里以一个简单的打印程序为例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14// gcc aaa.c -o aaa
int main(int argc, char *argv[]){
printf("hello 1, %s\n",argv[0]);
fflush(stdout);
getchar();
printf("hello 2, %s\n",argv[1]);
fflush(stdout);
getchar();
printf("hello 3, %s\n",argv[2]);
fflush(stdout);
getchar();
return 0;
}安装xinetd:
sudo apt install xinetd
xinetd的配置文件位于
/etc/xinetd.d/
目录下,我们需要在该目录下新建一个文件,名字随意1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# /etc/xinetd.d/test
service ctf
{
disable = no
type = UNLISTED
protocol = tcp
socket_type = stream
port = 9999
wait = no
server = /home/bling/aaa
server_args = bbb ccc
user = root
}配置文件确定无误后,可以重启一下xinetd服务,让配置生效:
/etc/init.d/xinetd restart
配置文件中指定了监听端口为9999,所以我们尝试一下连接该端口,得到了跟预期一样的输出。可以多开几个窗口连接试试,看看
netstat -pantu
的结果。1
2
3
4
5
6$ nc 127.0.0.1 9999
hello 1, aaa
hello 2, bbb
hello 3, ccc以上就是对xnetd的简单使用过程,有了基础框架的了解,后续基于此再添加功能也会清晰很多。
使用xinetd启动多个qemu
对于需要启动qemu的情况,由于qemu启动参数较多,可以使用如下方法:
新建一个bash脚本文件,将工作目录切换到qemu运行所需文件的目录,然后执行qemu命令(可设置timeout 120,将每个qemu的运行时间限制在120s内,防止长时间过多占用计算机资源)
1
2
3
4# start.sh
cd /home/bling/optee_v7/out/bin;
timeout 120 /home/bling/optee_v7/qemu-system-arm -nographic -smp 2 -machine virt,secure=on -cpu cortex-a15 -d unimp -semihosting-config enable=on,target=native -m 1057 -bios bl1.bin -object rng-random,filename=/dev/urandom,id=rng0 -device virtio-rng-pci,rng=rng0,max-bytes=1024,period=1000 -netdev user,id=vmnic -device virtio-net-device,netdev=vmnic将xinetd的配置文件的server和server_args改为如下设置,这样xinetd服务restart后,每当有新连接到9999端口时,就会使用sh程序执行start.sh,即启动一个qemu
1
2
3
4
5
6
7
8
9
10
11
12
13
14service ctf
{
disable = yes
type = UNLISTED
protocol = tcp
socket_type = stream
port = 9999
wait = no
server = /bin/sh
server_args = /home/bling/start.sh
user = root
}ps. ctf中对于需要启动qemu的场景,通常需要选手先过一个pow,目的是平衡服务端的性能。那么需要在上文xinetd的配置文件中调用
/usr/bin/python3 /xx/xx/xx/pow.py
,通过pow.py脚本再去启动qemu。一个实际的例子可以参考我出的optee的题(后续上传了再贴连接)。
ctf pwn常用的ctf_xinetd框架
在Docker中需要部署带chroot的xinetd服务时,考虑直接用ctf_xinetd模版
现在大部分题目,都是利用xinetd+docker-compose来快速布置docker题目环境
- docker-compose用于生成docker image并启动docker容器
- xinetd在容器中,当容器开始运行后,它根据配置拉起对应的程序,并充当socat的功能
使用方法:
- 将ctf_xinetd目录下载到本地,并更改
- 增加docker-compose.yml,并配置
- 通过
docker-compose up -d
就能启动容器+启动容器内的二进制程序
POW
出题时用到的
待补充…
解题时用到的
自研多进程暴破版
使用multiprocessing中的pool,参考廖雪峰老师的博客
一个ctf题的例子:python3 this.py tkhYS 26
以后碰到需要多进程跑的题目,改改参数处理,is_valid,calc_start函数就行
1 | # this.py |
比赛时长亭给的版本
1 | # python3 proof_of_work.py xxxx 26 |