环境:Ubuntu64位,阿里云容器镜像服务 ACR 控制台 (aliyun.com)
面向平台:gzctf(应该也可以上西电平台,但没试过)
借助:CTF-Archives/ctf-docker-template: Deployment template for docker target machine in ctf for CTFd and other platforms that support dynamic flags (github.com)
前期准备
- docker安装:每个人的环境不同,这一块网上教程挺多的
- 梯子
- 阿里云的账号:容器镜像服务 ACR 控制台 (aliyun.com),里面有教程,跟着教程创建好一个

动手
一、登录
访问容器镜像服务 ACR 控制台 (aliyun.com)

sudo docker login --username=[自己的名字] [仓库公网]
有时候需要上梯子才能登录
二、项目编写
这一块讲如何写main.py
import socketserver
import signal
from secrets import randbits
from Crypto.Util.number import bytes_to_long
import os
flag = "" # 我选择了静态flag
# 用于写交互一开始显示的东西,可以写提示等等
BANNER = '''
'''.encode("UTF-8")
# 服务器处理逻辑
class Task(socketserver.BaseRequestHandler):
def _recvall(self):
BUFF_SIZE = 2048
data = b''
while True:
part = self.request.recv(BUFF_SIZE)
data += part
if len(part) < BUFF_SIZE:
break
return data.strip()
def send(self, msg, newline=True):
try:
if newline:
msg += b'\n'
self.request.sendall(msg)
except:
pass
def recv(self, prompt='> '.encode("UTF-8")):
self.send(prompt, newline=False)
return self._recvall()
def handle(self): # 主要处理交互的地方
signal.alarm(300) # 过了300秒后就服务端自主断开连接
self.send(BANNER)
self.send("".encode("UTF-8")) # 发送信息
answer = self.recv().decode().strip() # 接收信息
self.send(flag.encode()) # 完成某些挑战后就发送flag
self.send("\n连接已关闭 =.= ".encode("UTF-8"))
self.request.close()
# 服务器类
class ThreadedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
class ForkedServer(socketserver.ForkingMixIn, socketserver.TCPServer):
pass
# 主函数
if __name__ == "__main__":
HOST, PORT = '0.0.0.0', 9999
print("服务器地址 " + HOST + ":" + str(PORT))
server = ForkedServer((HOST, PORT), Task)
server.allow_reuse_address = True
server.serve_forever()
主要部分是
def handle(self): # 主要处理交互的地方
signal.alarm(300) # 过了300秒后就服务端自主断开连接
self.send(BANNER)
self.send("".encode("UTF-8")) # 发送信息
answer = self.recv().decode().strip() # 接收信息
self.send(flag.encode()) # 完成某些挑战后就发送flag
self.send("\n连接已关闭 =.= ".encode("UTF-8"))
self.request.close()
这一块实现交互,可以按照你的需求来改(后面会有例子)
注意编码问题,如果你想发送中文,需要用UTF-8编码。
面向对象主要用Windows的话,可以用gbk编码等等
例子
nc连上后,会给选手一个数,让ta先判断是否为素数,然后再计算欧拉函数,一共20轮。0,11都给素数,让选手了解欧拉函数怎么计算,素数的欧拉函数计算。
import socketserver
import signal
import random
from Crypto.Util.number import *
# 标志
flag = ""
BANNER = '''
欢迎来到简单计算,接下来我会给你20个数,你需要判断是否为质数,并计算其的欧拉函数,并提交给我
'''.encode("UTF-8")
def euler_phi(n):
result = n # 初始化结果为 n
p = 2
while p * p <= n:
if n % p == 0:
while n % p == 0:
n //= p
result -= result // p
p += 1
if n > 1: # 如果 n 是质数
result -= result // n
return result
# 服务器处理逻辑
class Task(socketserver.BaseRequestHandler):
def _recvall(self):
BUFF_SIZE = 2048
data = b''
while True:
part = self.request.recv(BUFF_SIZE)
data += part
if len(part) < BUFF_SIZE:
break
return data.strip()
def send(self, msg, newline=True):
try:
if newline:
msg += b'\n'
self.request.sendall(msg)
except:
pass
def recv(self, prompt='> '.encode("UTF-8")):
self.send(prompt, newline=False)
return self._recvall()
def handle(self):
# signal.alarm(30)
self.send(BANNER)
for _ in range(20):
if _ % 11 == 0:
num = getPrime(10)
else:
num = random.randint(1, 1000)
self.send("这个数为:".encode("UTF-8") + str(num).encode())
self.send("\n这个数是否为质数?(输入1表示是质数,输入0表示不是质数): ".encode("UTF-8"))
try:
answer = self.recv().decode().strip()
if answer == '1' and isPrime(num):
self.send("回答正确!\n".encode("UTF-8"))
elif answer == '0' and not isPrime(num):
self.send("回答正确!\n".encode("UTF-8"))
else:
self.send("回答错误!\n".encode("UTF-8"))
self.send(f"系统关闭\n连接已关闭 =.= ".encode("UTF-8"))
self.request.close()
except Exception as r:
self.send("输入错误,请输入1或0\n".encode("UTF-8"))
self.send("\n这个数的欧拉函数为: ".encode("UTF-8"))
answer = self.recv().decode().strip()
if int(answer) == euler_phi(num):
self.send("回答正确!\n".encode("UTF-8"))
else:
self.send("回答错误!\n".encode("UTF-8"))
self.send(f"系统关闭\n连接已关闭 =.= ".encode("UTF-8"))
self.request.close()
self.send(flag.encode("UTF-8"))
self.send("\n连接已关闭 =.= ".encode("UTF-8"))
self.request.close()
# 服务器类
class ThreadedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
class ForkedServer(socketserver.ForkingMixIn, socketserver.TCPServer):
pass
# 主函数
if __name__ == "__main__":
HOST, PORT = '0.0.0.0', 9999
print("服务器地址 " + HOST + ":" + str(PORT))
server = ForkedServer((HOST, PORT), Task)
server.allow_reuse_address = True
server.serve_forever()
三、docker启动!生成镜像
将上面的main.py替换下面文件夹中的同名文件

将整个文件夹传到虚拟机中

在相对应的地方输入命令
docker build .
成功后(可能遇到问题python拉去失败,下一节讲)查看镜像容器
docker images
看到有没刚刚建好的容器,接下来传到阿里云的私有仓库中

去到镜像仓库里面,有操作指南

docker tag [ImageId] 仓库公网域名/名称/仓库名称:[镜像版本号]
ImageId
是先前docker images
查看的IMAGE ID
镜像版本号就是前面命的名 tag
然后再push(就是把上条命令的tag和镜像id换成push)
docker push 仓库公网域名/名称/仓库名称:[镜像版本号]

这时候镜像就push到公网上了(西电平台好像直接可以传容器的)。
四、docker上传可能遇到的问题
1.python拉取失败
如下
saga@saga-virtual-machine:~/ctf-docker-template-main/crypto-python_3.10-with_socket$ docker build .
[+] Building 54.4s (2/2) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 912B 0.0s
=> ERROR [internal] load metadata for docker.io/library/python:3.10-sli 54.4s
------
> [internal] load metadata for docker.io/library/python:3.10-slim:
------
Dockerfile:1
--------------------
1 | >>> FROM python:3.10-slim
2 |
3 | # 制作者信息
--------------------
ERROR: failed to solve: python:3.10-slim: failed to resolve source metadata for docker.io/library/python:3.10-slim: unexpected status from HEAD request to https://ar4s47rs.mirror.aliyuncs.com/v2/library/python/manifests/3.10-slim?ns=docker.io: 403 Forbidden
解决办法
单独拉去一下python
docker pull python:3.10.13-slim-bullseye
后面改成自己dockerfile里面用的


遇到网络问题,修改etc/docker/daemon.json为
{
"dns": ["8.8.8.8", "8.8.4.4"],
"registry-mirrors": [
"https://registry.docker-cn.com",
"http://hub-mirror.c.163.com",
"https://docker.mirrors.ustc.edu.cn",
"https://pee6w651.mirror.aliyuncs.com",
"https://docker.m.daocloud.io/",
"https://huecker.io/",
"https://dockerhub.timeweb.cloud",
"https://noohub.ru/",
"https://dockerproxy.com",
"https://docker.mirrors.ustc.edu.cn",
"https://docker.nju.edu.cn",
"https://xx4bwyg2.mirror.aliyuncs.com",
"http://f1361db2.m.daocloud.io",
"https://registry.docker-cn.com",
"http://hub-mirror.c.163.com"
],
"runtimes": {
"nvidia": {
"path": "nvidia-container-runtime",
"runtimeArgs": []
}
}
}
然后
sudo systemctl daemon-reload
sudo systemctl restart docker
sudo systemctl status docker
五、上传题目
登录gzctf平台

在红色框填写上面push命令中push后的内容 仓库公网域名/名称/仓库名称:[镜像版本号]
更换服务端口为dockerfile里面的数字

六、更换题目
需要删除前面生成的docker镜像
docker rmi [镜像id]

用shell工具上传更换新的文件后
然后重复上面步骤
docker build .
docker images
docker tag [ImageId] 仓库公网域名/名称/仓库名称:[镜像版本号]
docker push 仓库公网域名/名称/仓库名称:[镜像版本号]
七、密码出题小tip
结语
累
如果上面有任何问题,请联系我 2146983392@qq.com