From 7ba7232063e1cdc5d271aa54d6052a67a17b8147 Mon Sep 17 00:00:00 2001 From: 13m0n4de <136407746+13m0n4de@users.noreply.github.com> Date: Fri, 1 Nov 2024 04:25:22 +0800 Subject: [PATCH] =?UTF-8?q?feat(ppc/leet-machine):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E9=A2=98=E7=9B=AE=E3=80=81=E9=A2=98=E8=A7=A3=20(#38)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ppc.leet_machine.yml | 50 +++++ challenges/ppc/leet_machine/README.md | 208 ++++++++++++++++++ challenges/ppc/leet_machine/build/Dockerfile | 13 ++ challenges/ppc/leet_machine/build/init.sh | 3 + challenges/ppc/leet_machine/build/machine.py | 90 ++++++++ .../ppc/leet_machine/build/requirements.txt | 1 + challenges/ppc/leet_machine/build/xinetd.conf | 17 ++ challenges/ppc/leet_machine/writeup/solve.py | 31 +++ 8 files changed, 413 insertions(+) create mode 100644 .github/workflows/ppc.leet_machine.yml create mode 100644 challenges/ppc/leet_machine/README.md create mode 100644 challenges/ppc/leet_machine/build/Dockerfile create mode 100644 challenges/ppc/leet_machine/build/init.sh create mode 100644 challenges/ppc/leet_machine/build/machine.py create mode 100644 challenges/ppc/leet_machine/build/requirements.txt create mode 100644 challenges/ppc/leet_machine/build/xinetd.conf create mode 100644 challenges/ppc/leet_machine/writeup/solve.py diff --git a/.github/workflows/ppc.leet_machine.yml b/.github/workflows/ppc.leet_machine.yml new file mode 100644 index 0000000..12f048d --- /dev/null +++ b/.github/workflows/ppc.leet_machine.yml @@ -0,0 +1,50 @@ +name: Challenge 1337 Machine + +on: + push: + branches: ["main", "ppc/leet-machine"] + paths: + - "!**/README.md" + - "challenges/ppc/leet_machine/build/**" + workflow_dispatch: + +env: + TYPE: ppc + NAME: leet_machine + REGISTRY: ghcr.io + +jobs: + challenge-build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ github.repository }}/${{ env.NAME }} + tags: | + latest + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: challenges/${{ env.TYPE }}/${{ env.NAME }}/build + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + push: true diff --git a/challenges/ppc/leet_machine/README.md b/challenges/ppc/leet_machine/README.md new file mode 100644 index 0000000..f2bcfeb --- /dev/null +++ b/challenges/ppc/leet_machine/README.md @@ -0,0 +1,208 @@ +--- +title: 1337 Machine +author: 13m0n4de +difficulty: Normal +category: PPC +image: ghcr.io/svuctf/svuctf-helloworld-2024/leet_machine:latest +port: 70 +writeup_author: 13m0n4de +tags: + - pwntools +reference: +--- + +# 1337 Machine + +## 题目描述 + +教学楼的某个角落,你发现了一台古老而神秘的设备 —— 1337 Machine,这台机器曾经是顶尖的语言转换装置,能将普通文本完美地转化为 [Leet](https://zh.wikipedia.org/zh-cn/Leet) 文本。 + +然而,岁月的侵蚀使得这台机器变得极度不稳定。它的电路板正在冒烟,显示屏闪烁不定,发出令人不安的嗡鸣声。 + +突然,机器苏醒了!它开始疯狂地生成随机英文句子,仿佛在寻求某种解脱。屏幕上闪现出一条紧急消息: + +「系统不稳定,需要立即进行 100 次成功的 Leet 转换以稳定 Flag 核心。」 + +> Hint: Leet(英文中亦称 leetspeak 或 eleet。Leet 拼写法:L337, 3L337, 31337 或 1337),又称黑客语,是指一种发源于西方国家的 BBS、在线游戏和黑客社群所使用的文字书写方式。通常是把拉丁字母转变成数字或是特殊符号,例如 E 写成 3、A 写成 @ 等。或是将单字写成同音的字母或数字,如 to 写成 2、for 写成 4 等等。 + +## 题目解析 + +### 分析题目 + +这是一道自动化编程题目,需要与远程服务进行交互。根据题目描述,这是一台需要修复的 1337 (Leet) 转换机器。 + +连接到远程服务后,机器会提供一个明确的转换规则表: + +``` +$ nc + + .---------. + |.-------.| + ||>1337# || + || || + |"-------'| +.-^---------^-. +| ---~ ---~ | +"-------------' + +================================================== +1337 Machine 唤醒中... +================================================== +系统日志: 检测到核心不稳定 +系统日志: 启动紧急修复程序 +系统日志: 需要执行 100 次成功的 LEET 转换以重新校准系统 +系统日志: 检测到转换模块配置如下 +a -> 4 +e -> 3 +g -> 6 +i -> 1 +o -> 0 +s -> 5 +t -> 7 + +按下回车键开始系统修复... +``` + +题目的核心机制很直观: + +- 机器会随机生成英文句子 +- 我们需要按照给定的规则将这些句子转换为 Leet 格式 +- 这个过程需要成功完成 100 次才能获取 flag,并且有时间限制 +- 每次转换都必须准确,任何错误都会导致程序终止 + +### 编写自动化脚本 + +由于需要处理 100 次转换,手动操作显然不切实际,需要编写脚本自动化整个过程。 + +使用 Python 的 [pwntools](https://docs.pwntools.com/en/stable/) 库编写解题脚本是最直接的方法。这个库提供了便捷的远程交互功能,比较适合这类 PPC 题目。 + +#### 第一步:建立连接 + +使用 pwntools 连接到远程服务: + +```python +from pwn import * + +io = remote("localhost", 1337) +``` + +这里使用 pwntools 的 `remote` 函数创建了一个连接对象,用于与服务器进行交互。 + +#### 第二步:实现转换规则 + +根据题目给出的转换规则,我们需要将特定字母转换为数字。可以用字典来存储这些映射关系: + +```python +LEET_DICT = {"a": "4", "e": "3", "g": "6", "i": "1", "o": "0", "s": "5", "t": "7"} +``` + +然后编写一个转换函数: + +```python +def convert_to_leet(sentence): + return "".join(LEET_DICT.get(char, char) for char in sentence) +``` + +这个函数使用了字典的 `get` 方法,如果字符在字典中存在就进行转换,否则保持原字符不变。使用列表推导式和 `join` 方法可以简洁地完成字符串转换。 + +#### 第三步:处理初始化过程 + +程序开始时需要等待启动提示并发送回车: + +```python +io.recvuntil("按下回车键开始系统修复...".encode()) +io.sendline(b"") +``` + +#### 第四步:实现主要转换循环 + +接下来是主要的转换循环,需要重复 100 次: + +```python +for _ in range(100): + # 接收待转换的文本 + io.recvuntil("输入文本: ".encode()) + sentence = io.recvline().decode().strip() + log.info(sentence) + + # 转换文本 + leet_sentence = convert_to_leet(sentence) + log.info(leet_sentence) + + # 发送转换结果 + io.sendlineafter("输出 LEET 文本 > ".encode(), leet_sentence.encode()) + + # 检查响应 + response = io.recvline().decode() + if "转换成功" not in response: + log.failure(f"转换失败: {response}") + break + + # 打印分隔线方便查看 + log.info("-" * 40) +``` + +#### 第五步:获取 flag + +完成转换后进入交互模式以获取 flag: + +```python +io.interactive() +``` + +### 完整脚本 + +[solve.py](./writeup/solve.py) + +```python +from pwn import * + +io = remote("localhost", 1337) + +LEET_DICT = {"a": "4", "e": "3", "g": "6", "i": "1", "o": "0", "s": "5", "t": "7"} + + +def convert_to_leet(sentence): + return "".join(LEET_DICT.get(char, char) for char in sentence) + + +io.recvuntil("按下回车键开始系统修复...".encode()) +io.sendline(b"") + +for _ in range(100): + io.recvuntil("输入文本: ".encode()) + sentence = io.recvline().decode().strip() + log.info(sentence) + + leet_sentence = convert_to_leet(sentence) + log.info(leet_sentence) + + io.sendlineafter("输出 LEET 文本 > ".encode(), leet_sentence.encode()) + response = io.recvline().decode() + if "转换成功" not in response: + log.failure(f"转换失败: {response}") + break + + log.info("-" * 40) + +io.interactive() +``` + +### 运行效果 + +脚本运行的输出大致如下: + +``` +[*] ---------------------------------------- +[*] Red general similar someone peace question even. +[*] R3d 63n3r4l 51m1l4r 50m30n3 p34c3 qu35710n 3v3n. +[*] ---------------------------------------- +[*] Dark standard guy read. +[*] D4rk 574nd4rd 6uy r34d. +[*] ---------------------------------------- +[*] Switching to interactive mode +系统状态: 100/100 + +系统日志: 修复成功,Flag 核心已稳定 +Flag: flag{test_flag} +``` diff --git a/challenges/ppc/leet_machine/build/Dockerfile b/challenges/ppc/leet_machine/build/Dockerfile new file mode 100644 index 0000000..9d51698 --- /dev/null +++ b/challenges/ppc/leet_machine/build/Dockerfile @@ -0,0 +1,13 @@ +FROM ghcr.io/svuctf/base-v2/python-xinetd:latest + +WORKDIR /app + +COPY machine.py machine.py +COPY requirements.txt requirements.txt + +COPY xinetd.conf /etc/xinetd.conf +COPY --chmod=500 init.sh /init.sh + +RUN pip install -r requirements.txt + +CMD ["xinetd", "-dontfork"] diff --git a/challenges/ppc/leet_machine/build/init.sh b/challenges/ppc/leet_machine/build/init.sh new file mode 100644 index 0000000..bcb5ead --- /dev/null +++ b/challenges/ppc/leet_machine/build/init.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +python -u /app/machine.py diff --git a/challenges/ppc/leet_machine/build/machine.py b/challenges/ppc/leet_machine/build/machine.py new file mode 100644 index 0000000..ba22ece --- /dev/null +++ b/challenges/ppc/leet_machine/build/machine.py @@ -0,0 +1,90 @@ +import os +import random +import time +from faker import Faker + +LEET_DICT = {"a": "4", "e": "3", "g": "6", "i": "1", "o": "0", "s": "5", "t": "7"} +FLAG = os.environ.get("GZCTF_FLAG", "flag{test_flag}") + +fake = Faker() + +ASCII_ART = """ + .---------. + |.-------.| + ||>1337# || + || || + |"-------'| +.-^---------^-. +| ---~ ---~ | +"-------------' +""" + + +def generate_random_sentence(): + return fake.sentence(nb_words=random.randint(3, 10), variable_nb_words=True) + + +def convert_to_leet(sentence): + return "".join(LEET_DICT.get(char, char) for char in sentence) + + +def display_rules(): + print("系统日志: 检测到转换模块配置如下") + for key, value in LEET_DICT.items(): + print(f"{key} -> {value}") + print() + + +def play_round(): + sentence = generate_random_sentence() + correct_leet = convert_to_leet(sentence) + + print(f"输入文本: {sentence}") + start_time = time.time() + user_input = input("输出 LEET 文本 > ") + end_time = time.time() + + if end_time - start_time > 3: + print("系统警告: 响应超时") + return False + + if user_input == correct_leet: + print("转换成功: 系统稳定性 +1") + return True + + print("转换失败: 系统不稳定性增加") + return False + + +def main(): + print(ASCII_ART) + print("=" * 50) + print("1337 Machine 唤醒中...") + print("=" * 50) + time.sleep(1) + print("系统日志: 检测到核心不稳定") + print("系统日志: 启动紧急修复程序") + print("系统日志: 需要执行 100 次成功的 LEET 转换以重新校准系统") + time.sleep(1) + display_rules() + + input("按下回车键开始系统修复...") + + successful_conversions = 0 + for i in range(1, 101): + print(f"\n系统日志: 开始第 {i} 次转换") + if play_round(): + successful_conversions += 1 + print(f"系统状态: {successful_conversions}/100") + else: + print("\n系统日志: 修复失败") + print(f"系统日志: 仅完成 {successful_conversions}/100 次成功转换") + print("系统日志: 进入紧急休眠模式") + return + + print("\n系统日志: 修复成功,Flag 核心已稳定") + print(f"Flag: {FLAG}") + + +if __name__ == "__main__": + main() diff --git a/challenges/ppc/leet_machine/build/requirements.txt b/challenges/ppc/leet_machine/build/requirements.txt new file mode 100644 index 0000000..9626198 --- /dev/null +++ b/challenges/ppc/leet_machine/build/requirements.txt @@ -0,0 +1 @@ +Faker==30.6.0 diff --git a/challenges/ppc/leet_machine/build/xinetd.conf b/challenges/ppc/leet_machine/build/xinetd.conf new file mode 100644 index 0000000..4b67bfb --- /dev/null +++ b/challenges/ppc/leet_machine/build/xinetd.conf @@ -0,0 +1,17 @@ +service ctf +{ + disable = no + id = xinetd + socket_type = stream + protocol = tcp + wait = no + user = root + type = UNLISTED + port = 1337 + bind = 0.0.0.0 + server = /init.sh + # safety options + per_source = 10 # the maximum instances of this service per source IP address + rlimit_cpu = 20 # the maximum number of CPU seconds that the service may use + rlimit_as = 100M # the Address Space resource limit for the service +} diff --git a/challenges/ppc/leet_machine/writeup/solve.py b/challenges/ppc/leet_machine/writeup/solve.py new file mode 100644 index 0000000..6d60eca --- /dev/null +++ b/challenges/ppc/leet_machine/writeup/solve.py @@ -0,0 +1,31 @@ +from pwn import * + +io = remote("localhost", 1337) + +LEET_DICT = {"a": "4", "e": "3", "g": "6", "i": "1", "o": "0", "s": "5", "t": "7"} + + +def convert_to_leet(sentence): + return "".join(LEET_DICT.get(char, char) for char in sentence) + + +io.recvuntil("按下回车键开始系统修复...".encode()) +io.sendline(b"") + +for _ in range(100): + io.recvuntil("输入文本: ".encode()) + sentence = io.recvline().decode().strip() + log.info(sentence) + + leet_sentence = convert_to_leet(sentence) + log.info(leet_sentence) + + io.sendlineafter("输出 LEET 文本 > ".encode(), leet_sentence.encode()) + response = io.recvline().decode() + if "转换成功" not in response: + log.failure(f"转换失败: {response}") + break + + log.info("-" * 40) + +io.interactive()