Flag in your Hand - Writeup by AI

1. 题目描述

  • 题目名称: Flag in your Hand
  • 题目来源: 攻防世界 (Adworld)
  • 题目类型: JavaScript 逆向/密码学

题目提供了一个网页界面,用户需要输入正确的 token 来获取 flag。

2. 考点分析

考点 说明
JavaScript 代码逆向分析 理解压缩混淆的 JS 代码逻辑
MD5 哈希算法实现 识别自定义实现的 MD5 算法
Base64 编码原理 理解 Base64 编码实现细节
字符编码与 ASCII 转换 逐字符验证逻辑分析
函数调用链追踪 从 HTML 到 JS 核心函数的调用关系

3. 解题思路

3.1 代码结构分析

通过阅读 index.htmlscript-min.js 两个文件,可以发现:

  1. index.html 提供了输入界面和调用逻辑

    function getFlag() {
        var token = document.getElementById("secToken").value;
        ic = checkToken(token);  // 验证 token
        fg = bm(token);          // 生成 flag
        showFlag()
    }
    
  2. script-min.js 包含核心函数:

    • checkToken(s): 表面验证函数(幌子)
    • bm(s): 生成 flag 的函数
    • ck(s): 在 MD5 计算过程中调用的真正验证函数
    • rstr(s): MD5 哈希实现
    • rb(ip): Base64 编码实现

3.2 关键函数分析

checkToken() 函数(幌子)
function checkToken(s) {
    return s === "FAKE-TOKEN";
}

这个函数返回固定值,是明显的幌子,不是真正的验证逻辑。

ck() 函数(真正的验证)
function ck(s) {
    try {
        ic
    } catch (e) {
        return;
    }
    var a = [118, 104, 102, 120, 117, 108, 119, 124, 48,123,101,120];
    if (s.length == a.length) {
        for (i = 0; i < s.length; i++) {
            if (a[i] - s.charCodeAt(i) != 3)
                return ic = false;
        }
        return ic = true;
    }
    return ic = false;
}

这个函数在 MD5 的 binl() 函数中被调用,传入了原始输入字符串。这才是真正的验证逻辑!

验证条件:a[i] - s.charCodeAt(i) == 3
即:s.charCodeAt(i) = a[i] - 3

bm() 函数(Flag 生成)
function bm(s) {
    return rb(rstr(str2rstr_utf8(s)));
}

通过跟踪函数调用链:

  • str2rstr_utf8(s): UTF-8 编码转换
  • rstr(s): 实际调用 binl() 实现 MD5 哈希
  • rb(ip): 使用 Base64 字符表进行 Base64 编码

结论:bm() = Base64(MD5(token))

其他辅助函数
函数名 功能 用途
rh(ip) 十六进制编码 将字节转换为 hex 字符串(未使用)
hm(s) MD5+Hex 生成 MD5 的十六进制表示(未使用)
binl(x, len) MD5 核心算法 实现完整的 MD5 压缩函数
rstr2binl(input) 字符串转字节数组 MD5 预处理
binl2rstr(i) 字节数组转字符串 MD5 后处理

4. 详细步骤

步骤 1: 恢复正确的 token

根据 ck() 函数中的验证逻辑:

a = [118, 104, 102, 120, 117, 108, 119, 124, 48, 123, 101, 120]
token = ''.join(chr(code - 3) for code in a)

逐字符计算:

索引 a[i] 计算 ASCII 字符
0 118 118-3=115 115 ‘s’
1 104 104-3=101 101 ‘e’
2 102 102-3=99 99 ‘c’
3 120 120-3=117 117 ‘u’
4 117 117-3=114 114 ‘r’
5 108 108-3=105 105 ‘i’
6 119 119-3=116 116 ‘t’
7 124 124-3=121 121 ‘y’
8 48 48-3=45 45 ‘-’
9 123 123-3=120 120 ‘x’
10 101 101-3=98 98 ‘b’
11 120 120-3=117 117 ‘u’

得到 token: security-xbu

步骤 2: 生成 flag

根据 bm() 函数的逻辑,需要对 token 先进行 MD5 哈希,再进行 Base64 编码:

import hashlib
import base64

md5_hash = hashlib.md5(b'security-xbu').digest()
flag = base64.b64encode(md5_hash).decode('utf-8').rstrip('=')

计算过程:

  • Token: security-xbu
  • Token 字节表示:b'security-xbu'
  • MD5(security-xbu) = 45e9c86f277c16083985ac2f426ed30d
  • MD5 字节数组:[69, 233, 200, 111, 39, 124, 22, 8, 57, 133, 172, 47, 66, 110, 211, 13]
  • Base64(MD5) = RenIbyd8Fgg5hawvQm7TDQ==
  • 去除填充 = RenIbyd8Fgg5hawvQm7TDQ

得到 flag: RenIbyd8Fgg5hawvQm7TDQ

步骤 3: 验证结果

通过在浏览器中打开 index.html,输入 token security-xbu,点击"Get flag!"按钮,页面显示:

  • You got the flag below!!
  • RenIbyd8Fgg5hawvQm7TDQ

✅ 与 Python 脚本输出完全一致!

5. 完整代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CTF 题目:Flag in your Hand - 解题脚本
来源:攻防世界 (Adworld) - Crypto
类型:JavaScript 逆向/密码学
"""

import hashlib
import base64


def recover_token(a, offset=3):
    """根据 ck() 函数的验证逻辑恢复 token"""
    return ''.join(chr(code - offset) for code in a)


def generate_flag(token):
    """模拟 bm() 函数生成 flag: Base64(MD5(token))"""
    md5_hash = hashlib.md5(token.encode('utf-8')).digest()
    return base64.b64encode(md5_hash).decode('utf-8').rstrip('=')


def verify_token(token, a, offset=3):
    """验证 token 是否满足 ck() 函数的条件"""
    if len(token) != len(a):
        return False
    return all(a[i] - ord(token[i]) == offset for i in range(len(a)))


def solve():
    # ck() 函数中的验证数组
    a = [118, 104, 102, 120, 117, 108, 119, 124, 48, 123, 101, 120]
    
    # 恢复 token
    token = recover_token(a)
    
    # 验证 token 正确性
    assert verify_token(token, a), "Token verification failed!"
    
    print("=" * 60)
    print("步骤 1: 恢复 Token")
    print("=" * 60)
    print(f"验证数组 a: {a}")
    print(f"偏移量:3")
    print(f"恢复的 token: {token}")
    print(f"Token 长度:{len(token)}")
    
    # 详细验证过程
    print("\n验证过程:")
    for i in range(len(a)):
        char_code = ord(token[i])
        diff = a[i] - char_code
        print(f"  a[{i:2d}]={a[i]:3d}, token[{i:2d}]='{token[i]}'(ASCII:{char_code:3d}), 差值={diff}")
    
    print("\n" + "=" * 60)
    print("步骤 2: 计算 MD5 哈希")
    print("=" * 60)
    md5_hash = hashlib.md5(token.encode('utf-8')).digest()
    print(f"Token 字节表示:{token.encode('utf-8')}")
    print(f"MD5 哈希 (hex): {md5_hash.hex()}")
    print(f"MD5 哈希 (bytes): {list(md5_hash)}")
    
    print("\n" + "=" * 60)
    print("步骤 3: Base64 编码")
    print("=" * 60)
    flag = base64.b64encode(md5_hash).decode('utf-8').rstrip('=')
    print(f"MD5 哈希字节数:{len(md5_hash)}")
    print(f"Base64 编码 (带=): {base64.b64encode(md5_hash).decode('utf-8')}")
    print(f"Base64 编码 (去=): {flag}")
    
    print("\n" + "=" * 60)
    print("最终结果")
    print("=" * 60)
    print(f"Token: {token}")
    print(f"Flag: {flag}")
    print("=" * 60)
    
    return token, flag


if __name__ == "__main__":
    token, flag = solve()

6. 总结

关键点

  1. 识别真正的验证函数: checkToken() 是幌子,隐藏在 MD5 实现中的 ck() 才是真正的验证逻辑
  2. 理解验证条件: a[i] - s.charCodeAt(i) == 3 是核心公式,可逆向推导出 token
  3. 识别复合编码方式: bm() 函数实现的是 Base64(MD5(token)) 的复合变换
  4. 函数调用链分析: bm()str2rstr_utf8()rstr()binl()rb()

解题技巧

  • 对于压缩/混淆的 JavaScript 代码,要仔细追踪函数调用链
  • 注意在哈希算法中嵌入的验证逻辑这种隐蔽手法
  • 识别常见的编码算法特征(如 Base64 的字符表、MD5 的初始值和运算)
  • 使用 Python 快速批量计算字符转换,避免手动逐个推算
  • 重要: 通过浏览器实际验证结果,确保脚本输出正确

易错点

  • ❌ 误以为 checkToken() 是真正的验证函数
  • ❌ 误以为 bm() 只是简单的 Base64 编码(实际是 MD5+Base64)
  • ❌ 忽略 ck() 函数在 binl() 中被调用的隐蔽位置
  • ✅ 正确识别 ck() 函数的验证逻辑
  • ✅ 正确分析 bm() 的复合变换过程
  • ✅ 通过实际运行验证结果一致性

最终答案

  • 正确 Token: security-xbu
  • Flag: RenIbyd8Fgg5hawvQm7TDQ

补充说明

JavaScript 函数映射关系

getFlag() (HTML)
  ├─ checkToken(token) → 返回 false (幌子)
  └─ bm(token)
      ├─ str2rstr_utf8(s) → UTF-8 编码
      ├─ rstr(s) → MD5 哈希
      │   └─ binl(s, len) → 核心 MD5 算法
      │       └─ ck(s) → 真正的验证函数 ✓
      └─ rb(ip) → Base64 编码

验证数组的快速计算

# 方法 1: 列表推导式
a = [118, 104, 102, 120, 117, 108, 119, 124, 48, 123, 101, 120]
token = ''.join(chr(x - 3) for x in a)  # security-xbu

# 方法 2: 手动计算每个字符
# 118-3=115→'s', 104-3=101→'e', 102-3=99→'c', ...

MD5 + Base64 组合

import hashlib
import base64

token = "security-xbu"
md5_hash = hashlib.md5(token.encode('utf-8')).digest()  # 16 字节
flag = base64.b64encode(md5_hash).decode('utf-8').rstrip('=')
# 结果:RenIbyd8Fgg5hawvQm7TDQ

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐