环境: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)

前期准备

  1. docker安装:每个人的环境不同,这一块网上教程挺多的
  2. 梯子
  3. 阿里云的账号:容器镜像服务 ACR 控制台 (aliyun.com),里面有教程,跟着教程创建好一个

image-20250511211053183.png

动手

一、登录

访问容器镜像服务 ACR 控制台 (aliyun.com)

image-20250511211309159.png

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替换下面文件夹中的同名文件

image-20250511213245453.png

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

image-20250511213337798.png

在相对应的地方输入命令

docker build .

成功后(可能遇到问题python拉去失败,下一节讲)查看镜像容器

docker images

看到有没刚刚建好的容器,接下来传到阿里云的私有仓库中

image-20250511214235984.png

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

image-20250511213925982.png

docker tag [ImageId] 仓库公网域名/名称/仓库名称:[镜像版本号]

ImageId是先前docker images查看的IMAGE ID

镜像版本号就是前面命的名 tag

然后再push(就是把上条命令的tag和镜像id换成push)

docker push 仓库公网域名/名称/仓库名称:[镜像版本号]

image-20250511214120092.png

这时候镜像就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里面用的

image-20250511214624064.png
image-20250430162912803.png

遇到网络问题,修改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平台

image-20250511215102034.png

在红色框填写上面push命令中push后的内容 仓库公网域名/名称/仓库名称:[镜像版本号]

更换服务端口为dockerfile里面的数字

image-20250511215152825.png

六、更换题目

需要删除前面生成的docker镜像

docker rmi [镜像id]

image-20250511215333502.png

用shell工具上传更换新的文件后

然后重复上面步骤

docker build .
docker images
docker tag [ImageId] 仓库公网域名/名称/仓库名称:[镜像版本号]
docker push 仓库公网域名/名称/仓库名称:[镜像版本号]

七、密码出题小tip

  • 跟ai说需求,让它写
  • 限制时间,防爆破

结语

如果上面有任何搜索引擎解决不了的问题,请联系我 2146983392@qq.com

最后修改:2025 年 09 月 28 日
很强的定力