跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Python算法

CTF easy_hash 题目解析:多项式与自定义哈希逆向

对 CTF 平台上的 easy_hash 题目进行逆向分析。题目涉及自定义哈希函数 myhash 和多项式加密。解题步骤包括:利用已知节点计算哈希链后续值;通过多项式方程反推初始哈希值 a[0];根据 CRC32 校验结构逆向还原 Flag。最终成功获取 flag 内容。

禅心发布于 2026/3/26更新于 2026/9/1265 浏览

CTF easy_hash 题目解析:多项式与自定义哈希逆向

一、题目信息

  • 题目名称:easy_hash
  • 比赛名称:DASCTF NOV X 联合出题人 2022 年度积分榜争夺赛
  • 题目描述:闲来无事自己写了个 hash,你能看懂嘛?
  • 附件:interesting.py

二、题目分析

拿到题目后,首先分析附件代码的逻辑。题目主要包含两个核心函数:myhash 和 encode。

1. 自定义哈希函数 myhash

这个函数是题目的核心难点之一,它的处理流程如下:

def myhash(x):
    res = []
    end = b""
    bytescipher = long_to_bytes(x)
    # 1. 处理前缀(无法被 8 整除的剩余部分)
    a = bytescipher[:len(bytescipher) % 8]
    res.append(a)
    res.append(long_to_bytes(crc32(a)))
    # 添加前缀的 CRC32 校验值
    # 2. 处理 8 字节对齐的数据块
    t = (len(bytescipher) // 8)
    bytescipher = bytescipher[len(bytescipher) % 8:]
    for i in range(t):
        a = bytescipher[i*8:i*8+8]
        res.append(a)
        res.append(long_to_bytes(crc32(a)))
        # 添加每个数据块的 CRC32 校验值
    # 3. 拼接并转换
    for i in res:
        end += i
    res = bytes_to_long(end)
    # 4. 截断操作
    res = (res + (res >> 500)) & (2**500 - 1)
    return res

关键点分析:

  • 输入数据被分割成若干块:一个长度为 len % 8 的前缀,和若干个 8 字节的块。
  • 输出结构为:[前缀] + [CRC32(前缀)] + [块 1] + [CRC32(块 1)] + ...。
  • 最后进行了截断,保留了低 500 位。
2. 加密函数 encode
def encode(pt):
    a = []
    b = []
    a.append(myhash(pt))
    for i in range(3):
        a.append(myhash(a[i]))
    # 构建哈希链
    # 计算多项式
    for j in range(4):
        secret = (a[0] + a[1]*a[j] + a[2]*a[j]**2 + a[3]*a[j]**3) % P
        b.append([a[j], secret])
    return b

逻辑梳理:

  • a[0] = myhash(flag)
  • a[1] = myhash(a[0])
  • a[2] = myhash(a[1])
  • a[3] = myhash(a[2])
  • 对于每个 a[j],计算多项式 $f(x) = a_0 + a_1 x + a_2 x^2 + a_3 x^3 \pmod P$。
3. 已知条件

题目给出了 FLAG[1] 的值:

FLAG[1] = [a[1], secret]

其中 a[1] 和对应的 secret 是已知的整数,大质数 P 也是已知的。

三、解题思路

步骤一:计算哈希链的后续节点

题目给出了 a[1],根据哈希链的迭代关系:

  • $a[2] = \text{myhash}(a[1])$
  • $a[3] = \text{myhash}(a[2])$

我们可以直接计算出 a[2] 和 a[3]。

步骤二:反推 a[0]

我们知道多项式方程: $$ secret \equiv a[0] + a[1] \cdot a[1] + a[2] \cdot a[1]^2 + a[3] \cdot a[1]^3 \pmod P $$

现在除了 $a[0]$ 以外,其他所有变量都已知。我们可以轻松反推 $a[0]$: $$ a[0] \equiv secret - a[1]^2 - a[2] \cdot a[1]^2 - a[3] \cdot a[1]^3 \pmod P $$

步骤三:验证并逆向 myhash

计算出 a[0] 后,我们可以验证 myhash(a[0]) 是否等于已知的 a[1]。如果相等,说明我们的计算正确。 接下来是最关键的一步:从 a[0] 逆向还原出原始的 flag。 观察 myhash 的结构,它是 [数据块] + [CRC32 校验值] 的拼接。 由于 CRC32 是确定性的校验算法,且输出为 4 字节,我们可以尝试解析 a[0] 的字节流:

  1. 尝试不同的前缀长度(0-7 字节)。
  2. 提取前缀后的 4 字节,验证是否为该前缀的 CRC32 值。
  3. 如果匹配,继续以'8 字节数据 + 4 字节 CRC32'的格式解析后续数据。
  4. 校验所有块的 CRC32 值,如果全部正确,则提取出的数据块拼接起来即为原始 flag。

四、详细解题过程

1. 计算 a[2] 和 a[3]

利用题目给出的 myhash 函数和已知的 a[1]:

a2 = myhash(known_a1)
a3 = myhash(a2)
2. 求解 a[0]

代入多项式方程求解:

# f(a[1]) = a[0] + a[1]*a[1] + a[2]*a[1]**2 + a[3]*a[1]**3
term1 = known_a1 * known_a1
term2 = a2 * (known_a1 ** 2)
term3 = a3 * (known_a1 ** 3)
a0 = (known_secret - term1 - term2 - term3) % P

验证 myhash(a[0]) == known_a1,结果为 True,验证通过。

3. 逆向解析 Flag

得到的 a[0] 转换为字节后长度为 54 字节。我们编写脚本尝试解析:

a0_bytes = long_to_bytes(a0)
# 尝试前缀长度从 0 到 7
for prefix_len in range(8):
    # ... 提取前缀和 CRC ...
    # 验证 CRC32
    # ... 提取后续数据块并验证 ...

经过尝试,当前缀长度为 2 时,所有 CRC32 校验均通过:

  • 前缀 (2 字节): DA
  • 数据块 1 (8 字节): SCTF{th1
  • 数据块 2 (8 字节): s_is_the
  • 数据块 3 (8 字节): _fe3st_q
  • 数据块 4 (8 字节): uest1on}

拼接得到完整 Flag。

五、完整解题代码

from Crypto.Util.number import bytes_to_long, long_to_bytes
from zlib import crc32

# ==================== 已知参数 ====================
P = 93327214260434303138080906179883696131283277062733597039773430143631378719403851851296505697016458801222349445773245718371527858795457860775687842513513120173676986599209741174960099561600915819416543039173509037555167973076303047419790245327596338909743308199889740594091849756693219926218111062780849456373

# 题目给出的 FLAG[1]
known_a1 = 1768672211043417187765307394749760760531160781992300779145800061219666992833602547480090118225665457075744297987672863699370162614965380859290914620736
known_secret = 89139545215288033432210221492974990584987914397112840989583439688211128705545477536596587262069032020212762581490561288493533363888589066045095054475929099275247145877699370608950340925139625068446642116123285918461312297390611577025368805438078034230342490499137494400676347225155752865648820807846513044723

# ==================== 函数定义 ====================
def myhash(x):
    res = []
    end = b""
    bytescipher = long_to_bytes(x)
    a = bytescipher[:len(bytescipher) % 8]
    res.append(a)
    res.append(long_to_bytes(crc32(a)))
    t = (len(bytescipher) // 8)
    bytescipher = bytescipher[len(bytescipher) % 8:]
    for i in range(t):
        a = bytescipher[i*8:i*8+8]
        res.append(a)
        res.append(long_to_bytes(crc32(a)))
    for i in res:
        end += i
    res = bytes_to_long(end)
    res = (res + (res >> 500)) & (2**500 - 1)
    return res

# ==================== 解题步骤 ====================
# Step 1: 计算哈希链后续节点
a2 = myhash(known_a1)
a3 = myhash(a2)

# Step 2: 从多项式方程反推 a[0]
# f(a[1]) = a[0] + a[1]*a[1] + a[2]*a[1]**2 + a[3]*a[1]**3
term1 = known_a1 * known_a1
term2 = a2 * (known_a1 ** 2)
term3 = a3 * (known_a1 ** 3)
a0 = (known_secret - term1 - term2 - term3) % P

# Step 3: 验证哈希链
assert myhash(a0) == known_a1, "验证失败"

# Step 4: 逆向解析 myhash
a0_bytes = long_to_bytes(a0)
flag_bytes = b""
# 尝试不同的前缀长度
for prefix_len in range(8):
    if len(a0_bytes) < prefix_len + 4:
        continue
    prefix = a0_bytes[:prefix_len]
    prefix_crc = a0_bytes[prefix_len:prefix_len+4]
    if prefix_crc != long_to_bytes(crc32(prefix)):
        continue
    # 前缀 CRC 匹配,开始解析后续块
    blocks = [prefix]
    pos = prefix_len + 4
    valid = True
    while pos + 12 <= len(a0_bytes):
        block = a0_bytes[pos:pos+8]
        block_crc = a0_bytes[pos+8:pos+12]
        if block_crc == long_to_bytes(crc32(block)):
            blocks.append(block)
            pos += 12
        else:
            valid = False
            break
    if valid and pos == len(a0_bytes):
        flag_bytes = b''.join(blocks)
        break

try:
    print(f"Flag: {flag_bytes.decode()}")
except:
    print(f"Flag (hex): {flag_bytes.hex()}")

六、运行结果

Flag: DASCTF{th1s_is_the_fe3st_quest1on} 

七、总结

这道题虽然名为 easy_hash,但考察的知识点比较全面:

  1. 代码审计能力:理解自定义哈希函数和加密流程。
  2. 代数基础:利用模运算性质求解多项式方程中的未知数。
  3. 逆向思维:在无法直接求逆的情况下,利用哈希函数的结构特性(数据 + 校验)进行数据还原。
  4. CRC32 特性:理解 CRC32 是校验码而非加密算法,可用于数据完整性验证和结构解析。

整体来说是一道质量很高的密码学题目,不仅考察了数学推导,还考验了对数据结构的分析能力。

目录

  1. CTF easy_hash 题目解析:多项式与自定义哈希逆向
  2. 一、题目信息
  3. 二、题目分析
  4. 1. 自定义哈希函数 myhash
  5. 2. 加密函数 encode
  6. 3. 已知条件
  7. 三、解题思路
  8. 步骤一:计算哈希链的后续节点
  9. 步骤二:反推 a[0]
  10. 步骤三:验证并逆向 myhash
  11. 四、详细解题过程
  12. 1. 计算 a[2] 和 a[3]
  13. 2. 求解 a[0]
  14. f(a[1]) = a[0] + a[1]a[1] + a[2]a[1]*2 + a[3]a[1]**3
  15. 3. 逆向解析 Flag
  16. 尝试前缀长度从 0 到 7
  17. 五、完整解题代码
  18. ==================== 已知参数 ====================
  19. 题目给出的 FLAG[1]
  20. ==================== 函数定义 ====================
  21. ==================== 解题步骤 ====================
  22. Step 1: 计算哈希链后续节点
  23. Step 2: 从多项式方程反推 a[0]
  24. f(a[1]) = a[0] + a[1]a[1] + a[2]a[1]*2 + a[3]a[1]**3
  25. Step 3: 验证哈希链
  26. Step 4: 逆向解析 myhash
  27. 尝试不同的前缀长度
  28. 六、运行结果
  29. 七、总结

更多推荐文章

查看全部
  • 如何在 VS Code 中集成并使用 Cursor AI
  • Hadoop 基础架构、进程及常用命令详解
  • Xilinx 7 系列 FPGA 数据手册
  • 国产大模型市场现状:挑战、机遇与理性发展分析
  • 基于 Linphone 的 Android 音视频通话开发实战
  • C# 调用豆包 AI 模型实现首尾帧视频生成
  • OpenClaw v2026.3.1 版本更新:本地 AI Agent 功能升级与 Android 集成
  • Python 使用 Turtle 库绘制动态樱花树及飘落效果
  • 从零开始微调大模型:基于 LLaMA-Factory 的实战指南
  • 基于 OpenClaw 与飞书搭建专属 AI 机器人指南
  • OpenClaw 底层原理深度拆解:从指令到执行
  • LLaMA-Factory 微调:如何选择正确的精度类型
  • 树的基本概念与堆的功能实现
  • ComfyUI-Easy-Use完整指南:快速提升AI绘画效率的终极解决方案
  • 量化入门实战:Windows 下使用 Python 调用 TdxQuant 获取 A 股行情
  • Python 构建 AI 助手:文档总结、代码生成与智能检索实战
  • VSCode Copilot 认证失败排查与修复实战
  • 无经验如何成为 AI 产品经理
  • 大模型基于 llama.cpp 量化详解
  • 滑动窗口算法核心原理与经典例题解析

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online