Misc

流量分析-1

Challenge

我需要你流量分析😡

首次发起端口扫描的IP是?

将答案经过md5 32位加密后小写形式放入CM{}中

以下是附件链接:

通过网盘分享的文件:抓取流量.pcapng 链接: https://pan.baidu.com/s/1ye0KLzXGqyCYec2kGSlPMw?pwd=CM66 提取码: CM66 —来自百度网盘超级会员v5的分享

Solution

CMCTF2025-1
192.168.37.3

flag
CM{d28ee9d60772acbcd4eca38e1a3c94b8}

流量分析-2

Challenge

扫描次数最多的IP?

将答案经过md5 32位加密后小写形式放入CM{}中

Solution

CMCTF2025-2

192.168.37.3

flag
CM{d28ee9d60772acbcd4eca38e1a3c94b8}

流量分析-3

Challenge

扫描次数第二的IP?

将答案经过md5 32位加密后小写形式放入CM{}中

Solution

图同上题

192.168.37.1

flag
CM{1edaa78b26c43a0cf438b4437f6ceeb3}

流量分析-4

Challenge

哪个IP用了AWVS?

将答案经过md5 32位加密后小写形式放入CM{}中

Solution

IP数量很少,试一下就出来了

192.168.37.1

flag
CM{1edaa78b26c43a0cf438b4437f6ceeb3}

流量分析-6

Challenge

有IP进行了WEB登录爆破😲,提交其IP?

将答案经过md5 32位加密后小写形式放入CM{}中

hint:登录页面为login.php

Solution

直接搜索字符串POST /login.php,一条条看发现192.168.37.87多次连续出现

flag
CM{83779b479698b76581244f6ac8acd8a6}

流量分析-7

Challenge

有IP进行了WEB登录爆破,提交其爆破次数

(将爆破次数比如55, 进行加密)

将答案经过md5 32位加密后小写形式放入CM{}中

Solution

CMCTF2025-3

text
ip.src == 192.168.37.87 && http contains "login.php"

筛选导出得到107条数据,除去第1条GET请求,剩下的106条全都是

flag
CM{f0935e4cd5920aa6c7c996a5ee53a70f}

流量分析-8

Challenge

提交攻击者登录成功admin用户的IP和密码,以&连接

(示例: 答案为 192.168.92.111&CM666, 将上述内容进行加密)

将答案经过md5 32位加密后小写形式放入CM{}中

Solution

CMCTF2025-4

192.168.37.200&zhoudi123

flag
CM{3ca6dd54928fcfe47289ae62439116dd}

段涵涵学姐最爱的音乐

Challenge

王振宇从学姐闺蜜那边了解到学姐最爱的歌手是Taylor Swift,猜猜这个音频有什么秘密吧

flag格式为CM{XXXXXXXXX}

Solution

CMCTF2025-5

flag
CM{U_Kn0w_TaYLOR}

OSINT

杜浩学姐の朋友圈

Challenge

王阵雨辗转反侧,突然刷到了杜浩学姐朋友圈的一张图,猜猜这是在哪里呢

flag格式为CM{所在城市英文拼音-距离图中最近的地铁站名}

例:CM{Shanghai-陆家嘴}

Solution

发现玻璃反射泄露信息,将图片水平镜像后放大发现这两处关键信息

CMCTF2025-6

首先是右边的City花园城,搜索发现它改名为招商花园城了,因此后续的搜索中使用关键词招商花园城

然后是左边盒马的广告,说明这附近有盒马的店,并且极大概率就开设在招商花园城

因此搜索盒马 招商花园城

CMCTF2025-7

发现这篇文章盒马鲜生南京新店开业 市民多了新消费地标_腾讯新闻

文中提到的是南京招商花园城

使用手机高德地图搜索南京招商花园城后搜索其附近的地铁站

CMCTF2025-8

text
CM{Nanjing-万寿}

Web

小猿口算签到重生版

Challenge

考验手速和脑速的挑战

Solution

python
import requests def main():    # 定义目标URL    generate_url = "http://27.25.151.40:32926/generate"    verify_url = "http://27.25.151.40:32926/verify"     # 创建一个Session对象,确保所有请求在同一个session内    with requests.Session() as session:        try:            # 发送GET请求获取表达式            response = session.get(generate_url)            response.raise_for_status()  # 检查请求是否成功             # 解析返回的JSON数据            data = response.json()            expression = data.get("expression")            if not expression:                print("未获取到有效的表达式")                return             print(f"获取到的表达式: {expression}")             # 去掉等号并计算表达式的值            expression_to_eval = expression.replace("=?", "")            try:                result = eval(expression_to_eval)  # 计算表达式结果            except Exception as e:                print(f"表达式计算失败: {e}")                return             print(f"计算结果: {result}")             # 准备POST请求的数据            payload = {                "user_input": str(result)            }             # 发送POST请求提交结果            verify_response = session.post(verify_url, json=payload)            verify_response.raise_for_status()  # 检查请求是否成功             # 输出服务器返回的验证结果            verify_data = verify_response.json()            print(f"验证结果: {verify_data}")         except requests.exceptions.RequestException as e:            print(f"网络请求失败: {e}") if __name__ == "__main__":    main()
flag
flag{CAD709DE7E0B803D8BA72A55C4EB8C50}

lottery签到重生版

Challenge

抽抽抽

flag格式为CM{xxxxxx}!

Solution

找了一圈都没线索,所以就试着爆破一下

python
import requestsimport time  URL = "http://27.25.151.40:33411/spin" # 爆破次数上限MAX_ATTEMPTS = 200 def spin_the_wheel():    """    发送POST请求到 /spin 接口    """    try:        # 参照前端代码,发送一个空的POST请求        headers = {'Content-Type': 'application/json'}        response = requests.post(URL, headers=headers, data='{}', timeout=10)         if response.status_code == 200:            return response.json()        else:            print(f"请求失败,状态码: {response.status_code}")            return None    except requests.exceptions.RequestException as e:        print(f"请求发生错误: {e}")        return None def main():    print(f"[*] 开始爆破,目标URL: {URL}")    print(f"[*] 最大尝试次数: {MAX_ATTEMPTS}")    print("-" * 30)     for i in range(1, MAX_ATTEMPTS + 1):        print(f"[*] 正在进行第 {i} 次尝试...")                result = spin_the_wheel()                if result:            # 打印每次的结果,方便调试            print(f"    [+] 收到结果: {result}")             # 检查是否存在 'flagContent' 字段            if 'flagContent' in result:                print("\n" + "=" * 40)                print(result['flagContent'])                print("=" * 40 + "\n")                return         time.sleep(0.1) if __name__ == "__main__":    main()

结果到了第178次就爆出来了

flag
flag{B4F8EC958F70E3EE2245F97068D00109}

Reverse

IDA

Challenge

flag格式为CM{xxxxxx}!

Solution

CMCTF2025-9

flag
CM{W3lc0me_2_R3ver5e_h@v3_fun!}

Xor

Challenge

flag格式为CM{xxxxxx}!

Solution

python
# 提取 flag 数组内容flag = [    0x5F, 0x55, 0x58, 0x5E, 0x42, 0x61, 0x09, 0x6B, 0x66, 0x08, 0x4A, 0x66,    0x0F, 0x79, 0x4A, 0x08, 0x5A, 0x66, 0x5F, 0x09, 0x4B, 0x66, 0x6B, 0x0A,    0x4F, 0x5C, 0x4B, 0x0C, 0x5C, 0x18, 0x44] # 异或密钥key = 57 # 逆向计算原始输入字符串original_input = ''.join([chr(byte ^ key) for byte in flag]) # 输出结果print("原始输入字符串为:", original_input)
flag
CM{X0R_1s_6@s1c_f0r_R3ver5e!}

maze

Challenge

点击。。。?就。。。?送。。。?诶这些01仿佛组成了一条路

Solution

python
import sys # 增加递归深度限制,以防迷宫过大导致栈溢出sys.setrecursionlimit(2000) # 从C++代码中复制的地图数据MAP_STRING = "$11111111100111111111010000111001011011101101101110000110111111110011111111011111111101111111110000#"WIDTH = 10HEIGHT = len(MAP_STRING) // WIDTH def solve_maze():    """    解析并解决迷宫问题    """    # 1. 将一维字符串地图转换为二维列表    maze = []    for i in range(HEIGHT):        row = list(MAP_STRING[i * WIDTH : (i + 1) * WIDTH])        maze.append(row)     print("--- Maze Layout ---")    for row in maze:        print("".join(row))    print("--------------------")     # 起点是 (x=0, y=0)    start_pos = (0, 0)     # 2. 定义深度优先搜索 (DFS) 函数    # path: 记录移动指令 (e.g., "SSDDW...")    # visited: 记录访问过的坐标 (x, y),防止走回头路或无限循环    def find_path(x, y, path, visited):        # --- 检查边界条件和失败条件 ---         # 检查是否越界        if not (0 <= x < WIDTH and 0 <= y < HEIGHT):            return None         # 检查是否撞墙 ('1')        if maze[y][x] == '1':            return None         # 检查是否访问过 (防止循环)        if (x, y) in visited:            return None         # --- 检查胜利条件 ---                # 如果当前位置是终点        if maze[y][x] == '#':            # 并且路径长度正好是28            if len(path) == 28:                print(f"[*] Path found with length {len(path)}!")                return path            else:                # 找到了终点,但路径长度不对,这条路是错的                return None         # 如果路径已经超过28步,没必要继续了        if len(path) > 28:            return None                    # --- 递归探索 ---                # 标记当前点为已访问        new_visited = visited.copy()        new_visited.add((x, y))         # 尝试四个方向: S(下), D(右), A(左), W(上)        # 这个顺序可以随便定,但会影响找到的第一条解                # Move Down (S)        solution = find_path(x, y + 1, path + 'S', new_visited)        if solution: return solution         # Move Right (D)        solution = find_path(x + 1, y, path + 'D', new_visited)        if solution: return solution         # Move Left (A)        solution = find_path(x - 1, y, path + 'A', new_visited)        if solution: return solution         # Move Up (W)        solution = find_path(x, y - 1, path + 'W', new_visited)        if solution: return solution                # 所有方向都走不通        return None     # 3. 从起点开始搜索    print("[*] Searching for a path of length 28...")    # 初始路径为空,访问过的集合只包含起点    solution_path = find_path(start_pos[0], start_pos[1], "", set())     # 4. 输出结果    if solution_path:        print("\n[+] Success! Found the correct input:")        print(solution_path)    else:        print("\n[-] Failed to find a valid path of length 28.")  if __name__ == "__main__":    solve_maze()
flag
CM{SDSSASSDDDWWWDDDSSSSASSSDDDD}

sw1f7’s TEA

Challenge

相传sw1f7学姐喜欢做甜点,我猜她应该也喜欢泡茶,只有她认可的人才能喝到茶

flag格式为CM{xxxxxx}!

Solution

把checkdebug给nop掉,然后在第19行下断点

CMCTF2025-10

动调拿到密文

text
.data:0000000000404020 flag            dd 5B5C5F08h, 2766AE05h, 8C4D477Dh, 554F7F8Dh, 0E20BD674h.data:0000000000404020                                         ; DATA XREF: sub_114514(void)+1D↑w.data:0000000000404020                                         ; sub_114514(void)+57↑o ....data:0000000000404034                 dd 0BE678AAh, 0F44B5224h, 0CA619F04h

然后AI一把梭

python
import struct def decrypt(v, k):    """    这是提供的 encrypt 函数的逆函数。    它将一个 8 字节的数据块 v (拆分为 v0, v1) 进行 32 轮解密。     参数:    v (tuple): 一个包含2个32位无符号整数的元组 (v0, v1)。    k (tuple): 一个包含4个32位无符号整数的元组,代表密钥 (k0, k1, k2, k3)。        返回:    tuple: 解密后的 (v0, v1) 元组。    """    # 将元组解包到单独的变量中以便操作    v0, v1 = v    k0, k1, k2, k3 = k        # 这是C代码中的魔数: 1640531527 == 0x61C88647    delta = 1640531527        # ---- 逆向 sum 的计算 ----    # 在加密函数中,sum 的初始值为 0,然后循环 32 次 sum -= delta。    # 所以,在解密开始时,sum 的值应该是加密结束时的值,即 0 - (32 * delta)。    # 使用 & 0xFFFFFFFF 是为了模拟32位无符号整数的环绕溢出行为。    current_sum = (0 - (32 * delta)) & 0xFFFFFFFF        # ---- 解密循环 ----    # 加密是从 0 到 31 轮,解密则需要逆向这个过程。    for i in range(32):        # 1. 逆向 v1 的更新操作 (必须先做这一步)        #    原始公式: v1 += (v0 + sum) ^ (k[2] + 16 * v0) ^ ((v0 >> 5) + k[3]);        #    逆向公式: v1 -= (v0 + sum) ^ (k[2] + 16 * v0) ^ ((v0 >> 5) + k[3]);        #    注意: v0 << 4 等价于 16 * v0        term_v1 = (((v0 + current_sum) & 0xFFFFFFFF) ^ (k2 + (v0 << 4)) ^ (((v0 >> 5) & 0xFFFFFFFF) + k3)) & 0xFFFFFFFF        v1 = (v1 - term_v1) & 0xFFFFFFFF                # 2. 逆向 v0 的更新操作 (后做这一步)        #    原始公式: v0 += (v1 + sum) ^ (*k + 16 * v1) ^ ((v1 >> 5) + k[1]);        #    逆向公式: v0 -= (v1 + sum) ^ (*k + 16 * v1) ^ ((v1 >> 5) + k[1]);        term_v0 = (((v1 + current_sum) & 0xFFFFFFFF) ^ (k0 + (v1 << 4)) ^ (((v1 >> 5) & 0xFFFFFFFF) + k1)) & 0xFFFFFFFF        v0 = (v0 - term_v0) & 0xFFFFFFFF                # 3. 逆向 sum 的更新操作        #    加密时 sum -= delta,所以解密时 sum += delta        current_sum = (current_sum + delta) & 0xFFFFFFFF            return (v0, v1) def solve():    """    主求解函数,整合所有信息并执行解密。    """    # 1. 从 main 函数中提取的密钥    # key[0] = 36; key[1] = 66; key[2] = 82; key[3] = 118;    key = (36, 66, 82, 118)    print(f"[*] 使用密钥: {key}")     # 2. 从 IDA .data 段中获取的加密后的 flag 数据 (8个32位整数)    encrypted_flag_words = [        0x5B5C5F08, 0x2766AE05, 0x8C4D477D, 0x554F7F8D,        0xE20BD674, 0x0BE678AA, 0xF44B5224, 0xCA619F04    ]        # 3. 将32位整数列表转换为小端序(Little-Endian)的字节串,以匹配内存布局    #    '<L' 表示小端序的无符号长整型 (4字节)    encrypted_flag_bytes = b''.join([struct.pack('<L', word) for word in encrypted_flag_words])    print(f"[*] 待解密的密文 (hex): {encrypted_flag_bytes.hex()}")     decrypted_result = b''        # 4. 将32字节的密文分成4个8字节的块,并对每个块进行解密    print("\n[+] 开始解密...")    num_blocks = len(encrypted_flag_bytes) // 8    for i in range(num_blocks):        # 提取当前块        block_start = i * 8        block_end = block_start + 8        encrypted_block = encrypted_flag_bytes[block_start:block_end]                # 将8字节块解包成两个32位无符号整数 (v0, v1)        # '<II' 表示两个小端序的无符号整型 (4字节 + 4字节)        v = struct.unpack('<II', encrypted_block)                # 调用解密函数        decrypted_v = decrypt(v, key)                # 将解密后的 (v0, v1) 打包回8字节的块        decrypted_block = struct.pack('<II', decrypted_v[0], decrypted_v[1])        decrypted_result += decrypted_block        print(f"  - 块 {i+1} 解密完成,得到: {decrypted_block.decode('ascii', errors='ignore')}")     # 5. 打印最终的、完整的解密结果    # 使用 .decode('ascii') 将最终的字节串转换为人类可读的字符串    # .strip('\x00') 用于移除末尾可能存在的空字符填充    final_flag = decrypted_result.decode('ascii').strip('\x00')    print(f"\n{final_flag}") # 当脚本被直接运行时,调用 solve() 函数if __name__ == "__main__":    solve()

得到输出:

text
[*] 使用密钥: (36, 66, 82, 118)[*] 待解密的密文 (hex): 085f5c5b05ae66277d474d8c8d7f4f5574d60be2aa78e60b24524bf4049f61ca[+] 开始解密...  - 块 1 解密完成,得到: flag{sw1  - 块 2 解密完成,得到: f7's_Tea  - 块 3 解密完成,得到: _is_clas  - 块 4 解密完成,得到: sical!!}flag{sw1f7's_Tea_is_classical!!}
flag
CM{sw1f7's_Tea_is_classical!!}

sw1f7’s XXTEA

Challenge

sw1f7学姐的茶被喝了,这次她决定泡一壶更浓厚的茶,在走之前放了个盖子

flag格式为CM{xxxxxx}!

Solution

AI一把梭了

加密算法是XXTEA

密文 (Ciphertext): 存储在 flag 地址的数据。

text
.data:0000000000403020 flag dd 19EA7A62h, 5BE6801h, 0D2AD8A17h, 1A1456A1h.data:0000000000403030      dd 843B635Bh, 0E2369508h, 0BF552654h, 0FC87047Ch

这些是32位的DWORD(双字),在小端序(Little-Endian)的x86架构中,内存中的字节序是反的。不过,当我们将它们作为uint32_t数组处理时,数值就是这些。
ciphertext = [0x19EA7A62, 0x05BE6801, 0xD2AD8A17, 0x1A1456A1, 0x843B635B, 0xE2369508, 0xBF552654, 0xFC87047C]

密钥 (Key):key = {36, 66, 82, 118}

参数:n = 8,rounds = 12

python
import struct def decrypt(v, n, key):    """    XXTEA解密函数    v: uint32_t 整数列表,代表密文数据    n: 数据块的数量    key: uint32_t 整数列表,代表密钥    """    # 定义算法常量和参数    ROUNDS = 52 // n + 6    DELTA = 0x9E3779B9     # 解密的初始sum值    sum_val = (ROUNDS * DELTA) & 0xFFFFFFFF        # y用于传播上一个解密后的块,初始值为v[0]    y = v[0]     # 解密轮循环    for _ in range(ROUNDS):        e = (sum_val >> 2) & 3                # 内部循环,从p=n-1递减到1        # y持有v[p+1]解密后的值,z持有v[p-1]加密时的值        for p in range(n - 1, 0, -1):            z = v[p - 1]            term1 = (y ^ sum_val)            term2 = (z ^ key[(p & 3) ^ e])            term3 = ((y << 2) ^ (z >> 5)) & 0xFFFFFFFF            term4 = ((y >> 3) ^ (z << 4)) & 0xFFFFFFFF                        # 逆运算:减法            v[p] = (v[p] - ((term1 + term2) ^ (term3 + term4))) & 0xFFFFFFFF            y = v[p]         # 处理第0个块(循环边界情况)        z = v[n - 1]        term1 = (y ^ sum_val)        term2 = (z ^ key[(0 & 3) ^ e])        term3 = ((y << 2) ^ (z >> 5)) & 0xFFFFFFFF        term4 = ((y >> 3) ^ (z << 4)) & 0xFFFFFFFF        v[0] = (v[0] - ((term1 + term2) ^ (term3 + term4))) & 0xFFFFFFFF        y = v[0]                # 更新sum        sum_val = (sum_val - DELTA) & 0xFFFFFFFF            return v def main():    # 从.data段提取的密文数据 (8个DWORD)    ciphertext = [        0x19EA7A62, 0x05BE6801, 0xD2AD8A17, 0x1A1456A1,        0x843B635B, 0xE2369508, 0xBF552654, 0xFC87047C    ]        # 程序中硬编码的密钥    key = [36, 66, 82, 118]        # 块数量    n = len(ciphertext)     # 执行解密    decrypted_data = decrypt(ciphertext, n, key)        print(f"解密后的整数数组: { [hex(x) for x in decrypted_data] }")        # 将解密后的uint32_t数组转换回字节串    # '<' 表示小端序, 'I' 表示32位无符号整数    plaintext_bytes = b''    for dword in decrypted_data:        plaintext_bytes += struct.pack('<I', dword)            # 打印最终结果    try:        flag = plaintext_bytes.decode('utf-8')        print(f"\n[+] 成功找到Flag: {flag}")    except UnicodeDecodeError:        print(f"\n[-] 解码失败,原始字节: {plaintext_bytes}")  if __name__ == '__main__':    main()

得到输出:

text
解密后的整数数组: ['0x67616c66', '0x3177737b', '0x73273766', '0x5458585f', '0x695f6165', '0x6f6d5f73', '0x79666964', '0x7d676e69'][+] 成功找到Flag: flag{sw1f7's_XXTea_is_modifying}
flag
CM{sw1f7's_XXTea_is_modifying}

Mobile

base_android

Challenge

flag格式为CM{xxxxxxx}!

Solution

逆向发现程序从 assets 文件夹中读取 timg_2.zip 文件的内容,然后将这些内容一字不差地写入到 /data/data/com.example.test.ctf02/databases/img.jpg 文件中

因此手动将assets/timg_2.zip提取出来,然后把.zip后缀改为.jpg

CMCTF2025-11

flag
CM{08067-wlecome}