LakeCTF '25-'26 Quals re'wp

作者:shixz 发布时间: 2025-11-30 阅读量:5 评论数:0

dilemma

这是经典的100个囚徒问题,先根据反编译chal的主函数来写一个player的解密脚本

import sys

def solve():
    try:
        prompt = sys.stdin.readline().strip()
    except EOFError:
        return

    try:
        player_number = int(prompt.split()[4].strip('.'))
    except:
        return

    current_box = player_number

    for _ in range(50):
        print(current_box)
        sys.stdout.flush()

        try:
            response = sys.stdin.readline().strip()
        except EOFError:
            break

        if not response.startswith("FOUND"):
            break

        try:
            found_number = int(response.split()[1])
        except:
            break

        if found_number == player_number:
            return

        try:
            next_prompt = sys.stdin.readline().strip()
        except EOFError:
            break

        current_box = found_number

solve()
EOF

手动运行一下

nc chall.polygl0ts.ch 6667
Provide Python script for player 1 (end with string 'EOF' on its own line):
import sys

def solve():
    try:
        prompt = sys.stdin.readline().strip()
    except EOFError:
        return

    try:
        player_number = int(prompt.split()[4].strip('.'))
    except:
        return

    current_box = player_number

    for _ in range(50):
        print(current_box)
        sys.stdout.flush()

        try:
            response = sys.stdin.readline().strip()
        except EOFError:
            break

        if not response.startswith("FOUND"):
            break

        try:
            found_number = int(response.split()[1])
        except:
            break

        if found_number == player_number:
            return

        try:
            next_prompt = sys.stdin.readline().strip()
        except EOFError:
            break

        current_box = found_number

solve()
EOF
You are player number 1. You have 50 attempts left. Which box do you want to open?
[python 1] 1
The box 1 contains number 99.
You are player number 1. You have 49 attempts left. Which box do you want to open?
[python 1] 99
The box 99 contains number 36.
You are player number 1. You have 48 attempts left. Which box do you want to open?
[python 1] 36
The box 36 contains number 60.
You are player number 1. You have 47 attempts left. Which box do you want to open?
[python 1] 60
The box 60 contains number 13.
You are player number 1. You have 46 attempts left. Which box do you want to open?
[python 1] 13
The box 13 contains number 27.
You are player number 1. You have 45 attempts left. Which box do you want to open?
[python 1] 27
The box 27 contains number 20.
You are player number 1. You have 44 attempts left. Which box do you want to open?
[python 1] 20
The box 20 contains number 4.
You are player number 1. You have 43 attempts left. Which box do you want to open?
[python 1] 4
The box 4 contains number 55.
You are player number 1. You have 42 attempts left. Which box do you want to open?
[python 1] 55
The box 55 contains number 1.
Player 1 succeeded.
Provide Python script for player 2 (end with string 'EOF' on its own line):

成功运行,但是100个player去一个个运行明显不现实,于是需要一个自动化的脚本去上传

from pwn import *

HOST = "chall.polygl0ts.ch"
PORT = 6667

payload_script = r"""
import sys

def solve():
    try:
        prompt = sys.stdin.readline().strip()
    except EOFError:
        return

    try:
        player_number = int(prompt.split()[4].strip('.'))
    except:
        return

    current_box = player_number

    for _ in range(50):
        print(current_box)
        sys.stdout.flush()

        try:
            response = sys.stdin.readline().strip()
        except EOFError:
            break

        if not response.startswith("FOUND"):
            break

        try:
            found_number = int(response.split()[1])
        except:
            break

        if found_number == player_number:
            return

        try:
            next_prompt = sys.stdin.readline().strip()
        except EOFError:
            break

        current_box = found_number

solve()
EOF
"""

def main():
    r = remote(HOST, PORT)
    buffer = b""

    while True:
        try:
            data = r.recv(timeout=1)
        except EOFError:
            print("[*] connection closed by remote host")
            break
        except:
            continue

        if not data:
            continue

        print(data.decode(errors="ignore"), end="")  # 打印服务器输出
        buffer += data

        # 检测上传请求
        if b"Provide Python script for player" in buffer:
            print("\n[*] 发送脚本...\n")
            r.send(payload_script.encode())
            buffer = b""  # 重置缓冲

    r.close()

if __name__ == "__main__":
    main()

All players succeeded!

Flag: EPFL{wow_such_puzzle_did_you_google_the_solution_or_did_you_just_came_up_with_it?}

Another Android Applaketion

通过主函数可知test调用了80次去检查,且test在native层,使用IDA打开so文件

test是通过拼接类名和方法名去调用方法,且通过寻找一个方法可知

每次调用之后会校验,且更新aaa,bbb的值。

于是保存java源文件,使用脚本将所有类的方法放在一起

import os
import json

JAVA_FILES_DIR = "\\新建文件夹\\sources\\com\\lake\\ctf\\" 

# 存储所有校验数据的字典
all_check_data = {}

# ----------------------------------------------------
# 字符串分割提取函数 (比正则更可靠)
# ----------------------------------------------------
def extract_data_from_content(content, filename):
    """使用字符串查找和分割来提取 Class ID 和所有校验方法的数据"""
    
    # 1. 提取 Class ID (AAA)
    class_id = None
    # 查找 com.lake.ctf.Check 后面的哈希值
    class_id_start_key = "com.lake.ctf.Check"
    start_index = content.find(class_id_start_key)
    if start_index != -1:
        start_index += len(class_id_start_key)
        # 假设哈希值后面紧跟着空格或注释结束符
        end_index = content.find(" ", start_index) 
        if end_index == -1:
             end_index = content.find("*/", start_index)
        
        if end_index != -1:
            class_id = content[start_index:end_index].strip()

    if not class_id:
        # 对于 C0320R.java 和 MainActivity.java 等文件,这是正常的
        print(f"警告: 无法从 {filename} 中提取 Class ID。跳过。")
        return None, 0
    
    
    # 2. 提取所有 Method Checks (遍历所有 Check 标识符)
    extracted_count = 0
    methods_data = {}
    
    # 我们知道每个方法都以 /* renamed from: Check... */ 开头
    method_marker = "/* renamed from: Check"
    current_pos = content.find(method_marker)
    
    while current_pos != -1:
        # A. 提取 Method ID (BBB)
        method_id_start = current_pos + len(method_marker)
        method_id_end = content.find(" */", method_id_start)
        method_id = content[method_id_start:method_id_end].strip()

        # B. 查找 if 语句和 nop 调用
        if_start = content.find("if (", method_id_end)
        if if_start == -1:
            current_pos = content.find(method_marker, method_id_end)
            continue
        
        # C. 提取校验逻辑 (Logic)
        logic_start = if_start + len("if (")
        logic_end = content.find(") {", logic_start)
        logic = content[logic_start:logic_end].strip()
        
        # D. 提取 Next AAA 和 Next BBB
        nop_start = content.find("nop(", logic_end)
        
        if nop_start != -1:
            nop_args_start = nop_start + len("nop(")
            nop_args_end = content.find(");", nop_args_start)
            
            # 提取 nop(...) 中的所有参数字符串
            nop_args_str = content[nop_args_start:nop_args_end].strip()
            
            # 分割参数并去除引号
            try:
                # nop("AAA", "BBB") -> "AAA", "BBB"
                args_parts = [arg.strip().replace('"', '') for arg in nop_args_str.split(',')]
                next_aaa = args_parts[0]
                next_bbb = args_parts[1]
                
                # 规范化逻辑格式 (用于 Z3 转换)
                logic = logic.replace("str.charAt", "F")

                methods_data[method_id] = {
                    "logic": logic,
                    "next_aaa": next_aaa,
                    "next_bbb": next_bbb
                }
                extracted_count += 1
            except IndexError:
                # 忽略格式不正确的 nop 调用
                pass
        
        # 移动到下一个方法的开始位置
        current_pos = content.find(method_marker, method_id_end + 1)

    return class_id, extracted_count, methods_data


# ----------------------------------------------------
# 主函数
# ----------------------------------------------------
def extract_all_data():
    global all_check_data
    
    for filename in os.listdir(JAVA_FILES_DIR):
        if filename.endswith(".java"):
            filepath = os.path.join(JAVA_FILES_DIR, filename)
            
            # 强制使用 UTF-8 编码读取文件,解决编码问题
            try:
                with open(filepath, 'r', encoding='utf-8') as f: 
                    content = f.read()
            except UnicodeDecodeError:
                # 如果 UTF-8 失败,尝试 Latin-1 或其他常用编码
                try:
                    with open(filepath, 'r', encoding='latin-1') as f: 
                        content = f.read()
                except Exception as e:
                    print(f"致命错误: 无法读取文件 {filename},跳过。")
                    continue
            
            # 提取数据
            class_id, count, methods_data = extract_data_from_content(content, filename)
            
            if class_id:
                all_check_data[class_id] = methods_data
                print(f"成功处理 {filename}。提取了 {count} 个校验方法。")

    # 保存数据到 JSON 文件
    with open('check_data.json', 'w') as f:
        json.dump(all_check_data, f, indent=4)
    
    print("\n--- 完成 ---")
    print(f"所有校验数据已保存到 'check_data.json' 文件中。总共处理了 {len(all_check_data)} 个类。")

if __name__ == "__main__":
    extract_all_data()
import json
from z3 import *

# 初始状态 (从第一个文件 Check86e50149... 中的 JNI 调用 Init 中获取)
INITIAL_AAA = "86e50149658661312a9e0b35558d84f6c6d3da797f552a9657fe0558ca40cdef"
INITIAL_BBB = "031b4af5197ec30a926f48cf40e11a7dbc470048a21e4003b7a3c07c5dab1baa"

FLAG_LEN = 55  # 根据您提取的最高索引 F[i] 确定,此处假设为 55
TOTAL_CHECKS = 80 # 总共需要追踪 80 步

def solve_flag():
    # 1. 加载数据
    try:
        with open('check_data.json', 'r') as f:
            all_check_data = json.load(f)
    except FileNotFoundError:
        print("错误: 找不到 'check_data.json' 文件。请先运行 1_extract_data.py。")
        return

    # 2. 初始化 Z3 求解器和 Flag 变量
    s = Solver()
    # F[0] 到 F[FLAG_LEN - 1]
    F = [Int(f'F_{i}') for i in range(FLAG_LEN)]

    # 约束 1: 可见 ASCII 范围 (32 - 126)
    for i in range(FLAG_LEN):
        s.add(F[i] >= ord('!'))
        s.add(F[i] <= ord('~'))

    # 3. 追踪 80 步并添加约束
    current_aaa = INITIAL_AAA
    current_bbb = INITIAL_BBB
    
    print("--- 追踪执行路径并构建 Z3 约束 ---")
    
    for step in range(1, TOTAL_CHECKS + 1):
        try:
            check_info = all_check_data[current_aaa][current_bbb]
        except KeyError:
            print(f"!!! 追踪失败 !!! 找不到第 {step} 步的校验方法。")
            print(f"    当前 Class ID: {current_aaa}")
            print(f"    当前 Method ID: {current_bbb}")
            return

        logic_str = check_info["logic"]
        
        try:
            constraint = eval(logic_str)
            s.add(constraint)
        except Exception as e:
            print(f"!!! Z3 转换失败 !!! 第 {step} 步的逻辑: {logic_str}")
            print(f"    错误: {e}")
            return
        
        print(f"[{step:02d}/{TOTAL_CHECKS}] - 添加约束: {logic_str}")
        
        # 更新下一个状态
        current_aaa = check_info["next_aaa"]
        current_bbb = check_info["next_bbb"]

    # 4. 求解
    print("\n--- 求解 Z3 约束 ---")
    if s.check() == sat:
        m = s.model()
        flag = "".join(chr(m[F[i]].as_long()) for i in range(FLAG_LEN))
             
        print("Flag: ", flag)

if __name__ == "__main__":
    solve_flag()

#Flag:  EPFL{Wh1_3v3n_b0th3r_w1th_J4v4_1n_th3_f1rst_Pl4c3?????}

drum machine

## drum machine

通过ida分析 main 函数,可以知道:

1、读取用户输入字符串。

2、调用 decomposeInput 将字符串转换为 vector<Step>。

3、初始化 DrumMachine 对象(加载巨大的转移表)。

4、调用 DrumMachine::playBeat 执行状态机。

5、检查 DrumMachine::getState() == 181。如果成立,打印 "Congratulations"。

同时main函数还定义了字符到状态机输入的转换规则:

遍历输入字符串的每一个字符。

对每个字符的 8 个比特位(0-7)进行检查。

如果第 j 位是 1,则生成一个数值为 j 的 Hit。

关键特性:对于同一个字符,生成的 Hit 序列是严格递增的(从小到大遍历比特位)。一旦 Hit 数值变小,意味着开始处理下一个字符。

因此可以知道这是一个典型的 DFA (确定性有限自动机) 实现。

转移表硬编码在 DrumMachine 的构造函数中

void fastcall DrumMachine::DrumMachine(DrumMachine *this, int64 a2)

{

_DWORD *v2; // rdi

std::vector<Step>::vector(this, a2);

v2 = (_DWORD )((char )this + 24);

memset(v2, 0, 0x1700u);

*v2 = 1;

*((_DWORD *)this + 15) = 1;
*((_DWORD *)this + 16) = 2;
*((_DWORD *)this + 25) = 2;
*((_DWORD *)this + 26) = 4;
*((_DWORD *)this + 27) = 5;
*((_DWORD *)this + 28) = 3;
*((_DWORD *)this + 30) = 6;
*((_DWORD *)this + 31) = 3;
*((_DWORD *)this + 32) = 3;
*((_DWORD *)this + 34) = 4;
*((_DWORD *)this + 37) = 6;
*((_DWORD *)this + 43) = 4;
*((_DWORD *)this + 44) = 5;
*((_DWORD *)this + 46) = 5;
*((_DWORD *)this + 47) = 6;
*((_DWORD *)this + 53) = 5;
*((_DWORD *)this + 56) = 7;
*((_DWORD *)this + 65) = 10;
*((_DWORD *)this + 66) = 10;
*((_DWORD *)this + 67) = 7;
*((_DWORD *)this + 68) = 8;
*((_DWORD *)this + 70) = 4;
*((_DWORD *)this + 71) = 11;
*((_DWORD *)this + 72) = 9;
*((_DWORD *)this + 77) = 11;
*((_DWORD *)this + 81) = 10;
*((_DWORD *)this + 90) = 6;
*((_DWORD *)this + 91) = 12;
*((_DWORD *)this + 92) = 11;
*((_DWORD *)this + 94) = 12;
*((_DWORD *)this + 101) = 11;
*((_DWORD *)this + 103) = 13;
*((_DWORD *)this + 112) = 16;
*((_DWORD *)this + 113) = 14;
*((_DWORD *)this + 122) = 15;
*((_DWORD *)this + 131) = 16;
*((_DWORD *)this + 140) = 17;
*((_DWORD *)this + 142) = 19;
*((_DWORD *)this + 143) = 19;
*((_DWORD *)this + 144) = 18;
*((_DWORD *)this + 149) = 13;
*((_DWORD *)this + 153) = 21;
*((_DWORD *)this + 154) = 21;
*((_DWORD *)this + 155) = 19;
*((_DWORD *)this + 164) = 20;
*((_DWORD *)this + 166) = 21;
*((_DWORD *)this + 173) = 22;
*((_DWORD *)this + 175) = 21;
*((_DWORD *)this + 176) = 21;
*((_DWORD *)this + 177) = 17;
*((_DWORD *)this + 178) = 21;
*((_DWORD *)this + 179) = 22;
*((_DWORD *)this + 188) = 23;
*((_DWORD *)this + 190) = 25;
*((_DWORD *)this + 191) = 24;
*((_DWORD *)this + 197) = 25;
*((_DWORD *)this + 200) = 25;
*((_DWORD *)this + 209) = 26;
*((_DWORD *)this + 218) = 28;
*((_DWORD *)this + 219) = 27;
*((_DWORD *)this + 228) = 28;
*((_DWORD *)this + 230) = 29;
*((_DWORD *)this + 237) = 28;
*((_DWORD *)this + 239) = 30;
*((_DWORD *)this + 248) = 26;
*((_DWORD *)this + 249) = 30;
*((_DWORD *)this + 250) = 26;
*((_DWORD *)this + 251) = 31;
*((_DWORD *)this + 260) = 32;
*((_DWORD *)this + 262) = 33;
*((_DWORD *)this + 269) = 32;
*((_DWORD *)this + 271) = 35;
*((_DWORD *)this + 272) = 34;
*((_DWORD *)this + 281) = 36;
*((_DWORD *)this + 282) = 37;
*((_DWORD *)this + 283) = 35;
*((_DWORD *)this + 292) = 36;
*((_DWORD *)this + 294) = 37;
*((_DWORD *)this + 301) = 36;
*((_DWORD *)this + 303) = 38;
*((_DWORD *)this + 312) = 39;
*((_DWORD *)this + 321) = 40;
*((_DWORD *)this + 330) = 41;
*((_DWORD *)this + 339) = 41;
*((_DWORD *)this + 340) = 42;
*((_DWORD *)this + 342) = 43;
*((_DWORD *)this + 349) = 45;
*((_DWORD *)this + 351) = 45;
*((_DWORD *)this + 352) = 43;
*((_DWORD *)this + 353) = 46;
*((_DWORD *)this + 354) = 43;
*((_DWORD *)this + 355) = 44;
*((_DWORD *)this + 364) = 45;
*((_DWORD *)this + 366) = 47;
*((_DWORD *)this + 367) = 41;
*((_DWORD *)this + 368) = 46;
*((_DWORD *)this + 373) = 41;
*((_DWORD *)this + 377) = 47;
*((_DWORD *)this + 386) = 49;
*((_DWORD *)this + 387) = 48;
*((_DWORD *)this + 396) = 49;
*((_DWORD *)this + 398) = 50;
*((_DWORD *)this + 405) = 52;
*((_DWORD *)this + 407) = 51;
*((_DWORD *)this + 416) = 52;
*((_DWORD *)this + 425) = 53;
*((_DWORD *)this + 434) = 55;
*((_DWORD *)this + 435) = 54;
*((_DWORD *)this + 444) = 55;
*((_DWORD *)this + 446) = 58;
*((_DWORD *)this + 447) = 56;
*((_DWORD *)this + 453) = 57;
*((_DWORD *)this + 456) = 57;
*((_DWORD *)this + 465) = 58;
*((_DWORD *)this + 474) = 60;
*((_DWORD *)this + 475) = 59;
*((_DWORD *)this + 484) = 60;
*((_DWORD *)this + 486) = 61;
*((_DWORD *)this + 493) = 60;
*((_DWORD *)this + 495) = 62;
*((_DWORD *)this + 504) = 63;
*((_DWORD *)this + 513) = 66;
*((_DWORD *)this + 514) = 66;
*((_DWORD *)this + 515) = 64;
*((_DWORD *)this + 524) = 65;
*((_DWORD *)this + 526) = 66;
*((_DWORD *)this + 533) = 68;
*((_DWORD *)this + 535) = 67;
*((_DWORD *)this + 544) = 68;
*((_DWORD *)this + 553) = 69;
*((_DWORD *)this + 562) = 70;
*((_DWORD *)this + 571) = 66;
*((_DWORD *)this + 572) = 71;
*((_DWORD *)this + 574) = 73;
*((_DWORD *)this + 575) = 74;
*((_DWORD *)this + 576) = 72;
*((_DWORD *)this + 581) = 67;
*((_DWORD *)this + 585) = 74;
*((_DWORD *)this + 586) = 73;
*((_DWORD *)this + 595) = 74;
*((_DWORD *)this + 604) = 75;
*((_DWORD *)this + 606) = 76;
*((_DWORD *)this + 613) = 75;
*((_DWORD *)this + 615) = 77;
*((_DWORD *)this + 624) = 78;
*((_DWORD *)this + 633) = 79;
*((_DWORD *)this + 642) = 82;
*((_DWORD *)this + 643) = 80;
*((_DWORD *)this + 652) = 81;
*((_DWORD *)this + 654) = 82;
*((_DWORD *)this + 661) = 81;
*((_DWORD *)this + 663) = 83;
*((_DWORD *)this + 672) = 84;
*((_DWORD *)this + 681) = 85;
*((_DWORD *)this + 690) = 86;
*((_DWORD *)this + 699) = 88;
*((_DWORD *)this + 700) = 87;
*((_DWORD *)this + 702) = 89;
*((_DWORD *)this + 703) = 87;
*((_DWORD *)this + 704) = 88;
*((_DWORD *)this + 709) = 83;
*((_DWORD *)this + 713) = 91;
*((_DWORD *)this + 714) = 89;
*((_DWORD *)this + 723) = 90;
*((_DWORD *)this + 732) = 91;
*((_DWORD *)this + 734) = 94;
*((_DWORD *)this + 735) = 87;
*((_DWORD *)this + 736) = 87;
*((_DWORD *)this + 737) = 92;
*((_DWORD *)this + 741) = 93;
*((_DWORD *)this + 746) = 88;
*((_DWORD *)this + 747) = 93;
*((_DWORD *)this + 756) = 94;
*((_DWORD *)this + 758) = 95;
*((_DWORD *)this + 765) = 97;
*((_DWORD *)this + 767) = 97;
*((_DWORD *)this + 768) = 96;
*((_DWORD *)this + 777) = 99;
*((_DWORD *)this + 778) = 99;
*((_DWORD *)this + 779) = 97;
*((_DWORD *)this + 788) = 98;
*((_DWORD *)this + 790) = 99;
*((_DWORD *)this + 797) = 100;
*((_DWORD *)this + 799) = 100;
*((_DWORD *)this + 808) = 101;
*((_DWORD *)this + 817) = 102;
*((_DWORD *)this + 826) = 103;
*((_DWORD *)this + 835) = 99;
*((_DWORD *)this + 836) = 104;
*((_DWORD *)this + 838) = 106;
*((_DWORD *)this + 839) = 105;
*((_DWORD *)this + 845) = 100;
*((_DWORD *)this + 848) = 108;
*((_DWORD *)this + 849) = 108;
*((_DWORD *)this + 850) = 101;
*((_DWORD *)this + 851) = 106;
*((_DWORD *)this + 860) = 107;
*((_DWORD *)this + 862) = 108;
*((_DWORD *)this + 869) = 107;
*((_DWORD *)this + 871) = 109;
*((_DWORD *)this + 880) = 109;
*((_DWORD *)this + 881) = 109;
*((_DWORD *)this + 882) = 110;
*((_DWORD *)this + 891) = 111;
*((_DWORD *)this + 894) = 107;
*((_DWORD *)this + 895) = 111;
*((_DWORD *)this + 896) = 112;
*((_DWORD *)this + 900) = 114;
*((_DWORD *)this + 901) = 114;
*((_DWORD *)this + 905) = 108;
*((_DWORD *)this + 906) = 113;
*((_DWORD *)this + 915) = 114;
*((_DWORD *)this + 918) = 116;
*((_DWORD *)this + 919) = 114;
*((_DWORD *)this + 920) = 115;
*((_DWORD *)this + 924) = 110;
*((_DWORD *)this + 925) = 110;
*((_DWORD *)this + 929) = 115;
*((_DWORD *)this + 930) = 116;
*((_DWORD *)this + 939) = 117;
*((_DWORD *)this + 948) = 118;
*((_DWORD *)this + 950) = 119;
*((_DWORD *)this + 957) = 120;
*((_DWORD *)this + 959) = 120;
*((_DWORD *)this + 968) = 121;
*((_DWORD *)this + 977) = 122;
*((_DWORD *)this + 986) = 123;
*((_DWORD *)this + 995) = 125;
*((_DWORD *)this + 996) = 124;
*((_DWORD *)this + 998) = 125;
*((_DWORD *)this + 1005) = 124;
*((_DWORD *)this + 1007) = 126;
*((_DWORD *)this + 1016) = 127;
*((_DWORD *)this + 1025) = 128;
*((_DWORD *)this + 1034) = 130;
*((_DWORD *)this + 1035) = 129;
*((_DWORD *)this + 1044) = 130;
*((_DWORD *)this + 1046) = 133;
*((_DWORD *)this + 1047) = 131;
*((_DWORD *)this + 1053) = 126;
*((_DWORD *)this + 1056) = 132;
*((_DWORD *)this + 1065) = 128;
*((_DWORD *)this + 1066) = 132;
*((_DWORD *)this + 1067) = 133;
*((_DWORD *)this + 1076) = 134;
*((_DWORD *)this + 1078) = 135;
*((_DWORD *)this + 1085) = 136;
*((_DWORD *)this + 1087) = 136;
*((_DWORD *)this + 1096) = 137;
*((_DWORD *)this + 1105) = 138;
*((_DWORD *)this + 1114) = 139;
*((_DWORD *)this + 1123) = 142;
*((_DWORD *)this + 1124) = 140;
*((_DWORD *)this + 1126) = 142;
*((_DWORD *)this + 1127) = 143;
*((_DWORD *)this + 1128) = 141;
*((_DWORD *)this + 1133) = 140;
*((_DWORD *)this + 1137) = 144;
*((_DWORD *)this + 1138) = 142;
*((_DWORD *)this + 1147) = 143;
*((_DWORD *)this + 1156) = 144;
*((_DWORD *)this + 1158) = 147;
*((_DWORD *)this + 1159) = 144;
*((_DWORD *)this + 1160) = 146;
*((_DWORD *)this + 1161) = 145;
*((_DWORD *)this + 1165) = 146;
*((_DWORD *)this + 1170) = 141;
*((_DWORD *)this + 1171) = 146;
*((_DWORD *)this + 1180) = 147;
*((_DWORD *)this + 1182) = 148;
*((_DWORD *)this + 1189) = 147;
*((_DWORD *)this + 1191) = 148;
*((_DWORD *)this + 1192) = 149;
*((_DWORD *)this + 1201) = 151;
*((_DWORD *)this + 1202) = 151;
*((_DWORD *)this + 1203) = 150;
*((_DWORD *)this + 1212) = 151;
*((_DWORD *)this + 1214) = 152;
*((_DWORD *)this + 1221) = 154;
*((_DWORD *)this + 1223) = 153;
*((_DWORD *)this + 1232) = 154;
*((_DWORD *)this + 1241) = 155;
*((_DWORD *)this + 1250) = 156;
*((_DWORD *)this + 1259) = 156;
*((_DWORD *)this + 1260) = 157;
*((_DWORD *)this + 1262) = 157;
*((_DWORD *)this + 1263) = 158;
*((_DWORD *)this + 1269) = 160;
*((_DWORD *)this + 1272) = 159;
*((_DWORD *)this + 1281) = 159;
*((_DWORD *)this + 1282) = 155;
*((_DWORD *)this + 1283) = 160;
*((_DWORD *)this + 1292) = 161;
*((_DWORD *)this + 1294) = 164;
*((_DWORD *)this + 1295) = 164;
*((_DWORD *)this + 1296) = 162;
*((_DWORD *)this + 1301) = 161;
*((_DWORD *)this + 1305) = 163;
*((_DWORD *)this + 1314) = 159;
*((_DWORD *)this + 1315) = 164;
*((_DWORD *)this + 1324) = 165;
*((_DWORD *)this + 1326) = 167;
*((_DWORD *)this + 1327) = 161;
*((_DWORD *)this + 1328) = 166;
*((_DWORD *)this + 1333) = 168;
*((_DWORD *)this + 1337) = 169;
*((_DWORD *)this + 1338) = 167;
*((_DWORD *)this + 1347) = 168;
*((_DWORD *)this + 1350) = 169;
*((_DWORD *)this + 1356) = 171;
*((_DWORD *)this + 1357) = 170;
*((_DWORD *)this + 1359) = 170;
*((_DWORD *)this + 1368) = 171;
*((_DWORD *)this + 1377) = 167;
*((_DWORD *)this + 1378) = 173;
*((_DWORD *)this + 1379) = 172;
*((_DWORD *)this + 1388) = 173;
*((_DWORD *)this + 1390) = 174;
*((_DWORD *)this + 1397) = 169;
*((_DWORD *)this + 1399) = 170;
*((_DWORD *)this + 1400) = 174;
*((_DWORD *)this + 1401) = 177;
*((_DWORD *)this + 1402) = 177;
*((_DWORD *)this + 1403) = 175;
*((_DWORD *)this + 1406) = 176;
*((_DWORD *)this + 1412) = 175;
*((_DWORD *)this + 1413) = 177;
*((_DWORD *)this + 1415) = 176;
*((_DWORD *)this + 1416) = 177;
*((_DWORD *)this + 1425) = 178;
*((_DWORD *)this + 1434) = 179;
*((_DWORD *)this + 1443) = 180;
*((_DWORD *)this + 1452) = 181;
*((_DWORD *)this + 1461) = 184;
*((_DWORD *)this + 1478) = 0;
*((_DWORD *)this + 1479) = 23;
*((_DWORD *)this + 1480) = 21;
*((_DWORD *)this + 1481) = 32;
*((_DWORD *)this + 1482) = 19;
*((_DWORD *)this + 1483) = 17;
*((_DWORD *)this + 1484) = 31;
*((_DWORD *)this + 1485) = 38;
*((_DWORD *)this + 1486) = 0;
*(_QWORD *)((char *)this + 5948) = 0;
*(_QWORD *)((char *)this + 5956) = 0;
*(_QWORD *)((char *)this + 5964) = 0;
*(_QWORD *)((char *)this + 5972) = 0;
}

通过观察状态机的逻辑,我们发现这是一个 Flag 校验机。这意味着正确的输入会让状态机按部就班地晋级:State 0→State 1→State 2⋯→State 181

因此这是一条单向链表

所以exp为

import re

# ==========================================
# 题目数据 (完整构造函数代码)
# ==========================================
code_raw = """
  std::vector<Step>::vector(this, a2);
  v2 = (_DWORD *)((char *)this + 24);
  memset(v2, 0, 0x1700u);
  *v2 = 1;
  *((_DWORD *)this + 15) = 1;
  *((_DWORD *)this + 16) = 2;
  *((_DWORD *)this + 25) = 2;
  *((_DWORD *)this + 26) = 4;
  *((_DWORD *)this + 27) = 5;
  *((_DWORD *)this + 28) = 3;
  *((_DWORD *)this + 30) = 6;
  *((_DWORD *)this + 31) = 3;
  *((_DWORD *)this + 32) = 3;
  *((_DWORD *)this + 34) = 4;
  *((_DWORD *)this + 37) = 6;
  *((_DWORD *)this + 43) = 4;
  *((_DWORD *)this + 44) = 5;
  *((_DWORD *)this + 46) = 5;
  *((_DWORD *)this + 47) = 6;
  *((_DWORD *)this + 53) = 5;
  *((_DWORD *)this + 56) = 7;
  *((_DWORD *)this + 65) = 10;
  *((_DWORD *)this + 66) = 10;
  *((_DWORD *)this + 67) = 7;
  *((_DWORD *)this + 68) = 8;
  *((_DWORD *)this + 70) = 4;
  *((_DWORD *)this + 71) = 11;
  *((_DWORD *)this + 72) = 9;
  *((_DWORD *)this + 77) = 11;
  *((_DWORD *)this + 81) = 10;
  *((_DWORD *)this + 90) = 6;
  *((_DWORD *)this + 91) = 12;
  *((_DWORD *)this + 92) = 11;
  *((_DWORD *)this + 94) = 12;
  *((_DWORD *)this + 101) = 11;
  *((_DWORD *)this + 103) = 13;
  *((_DWORD *)this + 112) = 16;
  *((_DWORD *)this + 113) = 14;
  *((_DWORD *)this + 122) = 15;
  *((_DWORD *)this + 131) = 16;
  *((_DWORD *)this + 140) = 17;
  *((_DWORD *)this + 142) = 19;
  *((_DWORD *)this + 143) = 19;
  *((_DWORD *)this + 144) = 18;
  *((_DWORD *)this + 149) = 13;
  *((_DWORD *)this + 153) = 21;
  *((_DWORD *)this + 154) = 21;
  *((_DWORD *)this + 155) = 19;
  *((_DWORD *)this + 164) = 20;
  *((_DWORD *)this + 166) = 21;
  *((_DWORD *)this + 173) = 22;
  *((_DWORD *)this + 175) = 21;
  *((_DWORD *)this + 176) = 21;
  *((_DWORD *)this + 177) = 17;
  *((_DWORD *)this + 178) = 21;
  *((_DWORD *)this + 179) = 22;
  *((_DWORD *)this + 188) = 23;
  *((_DWORD *)this + 190) = 25;
  *((_DWORD *)this + 191) = 24;
  *((_DWORD *)this + 197) = 25;
  *((_DWORD *)this + 200) = 25;
  *((_DWORD *)this + 209) = 26;
  *((_DWORD *)this + 218) = 28;
  *((_DWORD *)this + 219) = 27;
  *((_DWORD *)this + 228) = 28;
  *((_DWORD *)this + 230) = 29;
  *((_DWORD *)this + 237) = 28;
  *((_DWORD *)this + 239) = 30;
  *((_DWORD *)this + 248) = 26;
  *((_DWORD *)this + 249) = 30;
  *((_DWORD *)this + 250) = 26;
  *((_DWORD *)this + 251) = 31;
  *((_DWORD *)this + 260) = 32;
  *((_DWORD *)this + 262) = 33;
  *((_DWORD *)this + 269) = 32;
  *((_DWORD *)this + 271) = 35;
  *((_DWORD *)this + 272) = 34;
  *((_DWORD *)this + 281) = 36;
  *((_DWORD *)this + 282) = 37;
  *((_DWORD *)this + 283) = 35;
  *((_DWORD *)this + 292) = 36;
  *((_DWORD *)this + 294) = 37;
  *((_DWORD *)this + 301) = 36;
  *((_DWORD *)this + 303) = 38;
  *((_DWORD *)this + 312) = 39;
  *((_DWORD *)this + 321) = 40;
  *((_DWORD *)this + 330) = 41;
  *((_DWORD *)this + 339) = 41;
  *((_DWORD *)this + 340) = 42;
  *((_DWORD *)this + 342) = 43;
  *((_DWORD *)this + 349) = 45;
  *((_DWORD *)this + 351) = 45;
  *((_DWORD *)this + 352) = 43;
  *((_DWORD *)this + 353) = 46;
  *((_DWORD *)this + 354) = 43;
  *((_DWORD *)this + 355) = 44;
  *((_DWORD *)this + 364) = 45;
  *((_DWORD *)this + 366) = 47;
  *((_DWORD *)this + 367) = 41;
  *((_DWORD *)this + 368) = 46;
  *((_DWORD *)this + 373) = 41;
  *((_DWORD *)this + 377) = 47;
  *((_DWORD *)this + 386) = 49;
  *((_DWORD *)this + 387) = 48;
  *((_DWORD *)this + 396) = 49;
  *((_DWORD *)this + 398) = 50;
  *((_DWORD *)this + 405) = 52;
  *((_DWORD *)this + 407) = 51;
  *((_DWORD *)this + 416) = 52;
  *((_DWORD *)this + 425) = 53;
  *((_DWORD *)this + 434) = 55;
  *((_DWORD *)this + 435) = 54;
  *((_DWORD *)this + 444) = 55;
  *((_DWORD *)this + 446) = 58;
  *((_DWORD *)this + 447) = 56;
  *((_DWORD *)this + 453) = 57;
  *((_DWORD *)this + 456) = 57;
  *((_DWORD *)this + 465) = 58;
  *((_DWORD *)this + 474) = 60;
  *((_DWORD *)this + 475) = 59;
  *((_DWORD *)this + 484) = 60;
  *((_DWORD *)this + 486) = 61;
  *((_DWORD *)this + 493) = 60;
  *((_DWORD *)this + 495) = 62;
  *((_DWORD *)this + 504) = 63;
  *((_DWORD *)this + 513) = 66;
  *((_DWORD *)this + 514) = 66;
  *((_DWORD *)this + 515) = 64;
  *((_DWORD *)this + 524) = 65;
  *((_DWORD *)this + 526) = 66;
  *((_DWORD *)this + 533) = 68;
  *((_DWORD *)this + 535) = 67;
  *((_DWORD *)this + 544) = 68;
  *((_DWORD *)this + 553) = 69;
  *((_DWORD *)this + 562) = 70;
  *((_DWORD *)this + 571) = 66;
  *((_DWORD *)this + 572) = 71;
  *((_DWORD *)this + 574) = 73;
  *((_DWORD *)this + 575) = 74;
  *((_DWORD *)this + 576) = 72;
  *((_DWORD *)this + 581) = 67;
  *((_DWORD *)this + 585) = 74;
  *((_DWORD *)this + 586) = 73;
  *((_DWORD *)this + 595) = 74;
  *((_DWORD *)this + 604) = 75;
  *((_DWORD *)this + 606) = 76;
  *((_DWORD *)this + 613) = 75;
  *((_DWORD *)this + 615) = 77;
  *((_DWORD *)this + 624) = 78;
  *((_DWORD *)this + 633) = 79;
  *((_DWORD *)this + 642) = 82;
  *((_DWORD *)this + 643) = 80;
  *((_DWORD *)this + 652) = 81;
  *((_DWORD *)this + 654) = 82;
  *((_DWORD *)this + 661) = 81;
  *((_DWORD *)this + 663) = 83;
  *((_DWORD *)this + 672) = 84;
  *((_DWORD *)this + 681) = 85;
  *((_DWORD *)this + 690) = 86;
  *((_DWORD *)this + 699) = 88;
  *((_DWORD *)this + 700) = 87;
  *((_DWORD *)this + 702) = 89;
  *((_DWORD *)this + 703) = 87;
  *((_DWORD *)this + 704) = 88;
  *((_DWORD *)this + 709) = 83;
  *((_DWORD *)this + 713) = 91;
  *((_DWORD *)this + 714) = 89;
  *((_DWORD *)this + 723) = 90;
  *((_DWORD *)this + 732) = 91;
  *((_DWORD *)this + 734) = 94;
  *((_DWORD *)this + 735) = 87;
  *((_DWORD *)this + 736) = 87;
  *((_DWORD *)this + 737) = 92;
  *((_DWORD *)this + 741) = 93;
  *((_DWORD *)this + 746) = 88;
  *((_DWORD *)this + 747) = 93;
  *((_DWORD *)this + 756) = 94;
  *((_DWORD *)this + 758) = 95;
  *((_DWORD *)this + 765) = 97;
  *((_DWORD *)this + 767) = 97;
  *((_DWORD *)this + 768) = 96;
  *((_DWORD *)this + 777) = 99;
  *((_DWORD *)this + 778) = 99;
  *((_DWORD *)this + 779) = 97;
  *((_DWORD *)this + 788) = 98;
  *((_DWORD *)this + 790) = 99;
  *((_DWORD *)this + 797) = 100;
  *((_DWORD *)this + 799) = 100;
  *((_DWORD *)this + 808) = 101;
  *((_DWORD *)this + 817) = 102;
  *((_DWORD *)this + 826) = 103;
  *((_DWORD *)this + 835) = 99;
  *((_DWORD *)this + 836) = 104;
  *((_DWORD *)this + 838) = 106;
  *((_DWORD *)this + 839) = 105;
  *((_DWORD *)this + 845) = 100;
  *((_DWORD *)this + 848) = 108;
  *((_DWORD *)this + 849) = 108;
  *((_DWORD *)this + 850) = 101;
  *((_DWORD *)this + 851) = 106;
  *((_DWORD *)this + 860) = 107;
  *((_DWORD *)this + 862) = 108;
  *((_DWORD *)this + 869) = 107;
  *((_DWORD *)this + 871) = 109;
  *((_DWORD *)this + 880) = 109;
  *((_DWORD *)this + 881) = 109;
  *((_DWORD *)this + 882) = 110;
  *((_DWORD *)this + 891) = 111;
  *((_DWORD *)this + 894) = 107;
  *((_DWORD *)this + 895) = 111;
  *((_DWORD *)this + 896) = 112;
  *((_DWORD *)this + 900) = 114;
  *((_DWORD *)this + 901) = 114;
  *((_DWORD *)this + 905) = 108;
  *((_DWORD *)this + 906) = 113;
  *((_DWORD *)this + 915) = 114;
  *((_DWORD *)this + 918) = 116;
  *((_DWORD *)this + 919) = 114;
  *((_DWORD *)this + 920) = 115;
  *((_DWORD *)this + 924) = 110;
  *((_DWORD *)this + 925) = 110;
  *((_DWORD *)this + 929) = 115;
  *((_DWORD *)this + 930) = 116;
  *((_DWORD *)this + 939) = 117;
  *((_DWORD *)this + 948) = 118;
  *((_DWORD *)this + 950) = 119;
  *((_DWORD *)this + 957) = 120;
  *((_DWORD *)this + 959) = 120;
  *((_DWORD *)this + 968) = 121;
  *((_DWORD *)this + 977) = 122;
  *((_DWORD *)this + 986) = 123;
  *((_DWORD *)this + 995) = 125;
  *((_DWORD *)this + 996) = 124;
  *((_DWORD *)this + 998) = 125;
  *((_DWORD *)this + 1005) = 124;
  *((_DWORD *)this + 1007) = 126;
  *((_DWORD *)this + 1016) = 127;
  *((_DWORD *)this + 1025) = 128;
  *((_DWORD *)this + 1034) = 130;
  *((_DWORD *)this + 1035) = 129;
  *((_DWORD *)this + 1044) = 130;
  *((_DWORD *)this + 1046) = 133;
  *((_DWORD *)this + 1047) = 131;
  *((_DWORD *)this + 1053) = 126;
  *((_DWORD *)this + 1056) = 132;
  *((_DWORD *)this + 1065) = 128;
  *((_DWORD *)this + 1066) = 132;
  *((_DWORD *)this + 1067) = 133;
  *((_DWORD *)this + 1076) = 134;
  *((_DWORD *)this + 1078) = 135;
  *((_DWORD *)this + 1085) = 136;
  *((_DWORD *)this + 1087) = 136;
  *((_DWORD *)this + 1096) = 137;
  *((_DWORD *)this + 1105) = 138;
  *((_DWORD *)this + 1114) = 139;
  *((_DWORD *)this + 1123) = 142;
  *((_DWORD *)this + 1124) = 140;
  *((_DWORD *)this + 1126) = 142;
  *((_DWORD *)this + 1127) = 143;
  *((_DWORD *)this + 1128) = 141;
  *((_DWORD *)this + 1133) = 140;
  *((_DWORD *)this + 1137) = 144;
  *((_DWORD *)this + 1138) = 142;
  *((_DWORD *)this + 1147) = 143;
  *((_DWORD *)this + 1156) = 144;
  *((_DWORD *)this + 1158) = 147;
  *((_DWORD *)this + 1159) = 144;
  *((_DWORD *)this + 1160) = 146;
  *((_DWORD *)this + 1161) = 145;
  *((_DWORD *)this + 1165) = 146;
  *((_DWORD *)this + 1170) = 141;
  *((_DWORD *)this + 1171) = 146;
  *((_DWORD *)this + 1180) = 147;
  *((_DWORD *)this + 1182) = 148;
  *((_DWORD *)this + 1189) = 147;
  *((_DWORD *)this + 1191) = 148;
  *((_DWORD *)this + 1192) = 149;
  *((_DWORD *)this + 1201) = 151;
  *((_DWORD *)this + 1202) = 151;
  *((_DWORD *)this + 1203) = 150;
  *((_DWORD *)this + 1212) = 151;
  *((_DWORD *)this + 1214) = 152;
  *((_DWORD *)this + 1221) = 154;
  *((_DWORD *)this + 1223) = 153;
  *((_DWORD *)this + 1232) = 154;
  *((_DWORD *)this + 1241) = 155;
  *((_DWORD *)this + 1250) = 156;
  *((_DWORD *)this + 1259) = 156;
  *((_DWORD *)this + 1260) = 157;
  *((_DWORD *)this + 1262) = 157;
  *((_DWORD *)this + 1263) = 158;
  *((_DWORD *)this + 1269) = 160;
  *((_DWORD *)this + 1272) = 159;
  *((_DWORD *)this + 1281) = 159;
  *((_DWORD *)this + 1282) = 155;
  *((_DWORD *)this + 1283) = 160;
  *((_DWORD *)this + 1292) = 161;
  *((_DWORD *)this + 1294) = 164;
  *((_DWORD *)this + 1295) = 164;
  *((_DWORD *)this + 1296) = 162;
  *((_DWORD *)this + 1301) = 161;
  *((_DWORD *)this + 1305) = 163;
  *((_DWORD *)this + 1314) = 159;
  *((_DWORD *)this + 1315) = 164;
  *((_DWORD *)this + 1324) = 165;
  *((_DWORD *)this + 1326) = 167;
  *((_DWORD *)this + 1327) = 161;
  *((_DWORD *)this + 1328) = 166;
  *((_DWORD *)this + 1333) = 168;
  *((_DWORD *)this + 1337) = 169;
  *((_DWORD *)this + 1338) = 167;
  *((_DWORD *)this + 1347) = 168;
  *((_DWORD *)this + 1350) = 169;
  *((_DWORD *)this + 1356) = 171;
  *((_DWORD *)this + 1357) = 170;
  *((_DWORD *)this + 1359) = 170;
  *((_DWORD *)this + 1368) = 171;
  *((_DWORD *)this + 1377) = 167;
  *((_DWORD *)this + 1378) = 173;
  *((_DWORD *)this + 1379) = 172;
  *((_DWORD *)this + 1388) = 173;
  *((_DWORD *)this + 1390) = 174;
  *((_DWORD *)this + 1397) = 169;
  *((_DWORD *)this + 1399) = 170;
  *((_DWORD *)this + 1400) = 174;
  *((_DWORD *)this + 1401) = 177;
  *((_DWORD *)this + 1402) = 177;
  *((_DWORD *)this + 1403) = 175;
  *((_DWORD *)this + 1406) = 176;
  *((_DWORD *)this + 1412) = 175;
  *((_DWORD *)this + 1413) = 177;
  *((_DWORD *)this + 1415) = 176;
  *((_DWORD *)this + 1416) = 177;
  *((_DWORD *)this + 1425) = 178;
  *((_DWORD *)this + 1434) = 179;
  *((_DWORD *)this + 1443) = 180;
  *((_DWORD *)this + 1452) = 181;
  *((_DWORD *)this + 1461) = 184;
  *((_DWORD *)this + 1478) = 0;
  *((_DWORD *)this + 1479) = 23;
  *((_DWORD *)this + 1480) = 21;
  *((_DWORD *)this + 1481) = 32;
  *((_DWORD *)this + 1482) = 19;
  *((_DWORD *)this + 1483) = 17;
  *((_DWORD *)this + 1484) = 31;
  *((_DWORD *)this + 1485) = 38;
  *((_DWORD *)this + 1486) = 0;
  *(_QWORD *)((char *)this + 5948) = 0;
  *(_QWORD *)((char *)this + 5956) = 0;
  *(_QWORD *)((char *)this + 5964) = 0;
  *(_QWORD *)((char *)this + 5972) = 0;
"""


def solve_linear_chain():
    print("[*] Parsing code...")

    # 构建状态转移表:transitions[state][hit] = next_state
    graph = {}

    # 正则匹配形如 + 15) = 1 的代码
    pattern = re.compile(r'\+\s*(\d+)\)\s*=\s*(\d+);')
    matches = pattern.findall(code_raw)

    # 1. 自动解析代码
    for offset_str, value_str in matches:
        offset = int(offset_str)
        value = int(value_str)

        # 过滤非转移表数据
        if offset >= 1478: continue
        rel_offset = offset - 6
        if rel_offset < 0: continue

        state = rel_offset // 8
        hit = rel_offset % 8

        if state not in graph: graph[state] = {}
        graph[state][hit] = value

    # 2. 手动补充 *v2 = 1 这一行 (State 0, Hit 0 -> State 1)
    # 因为 v2 是 (char*)this + 24,即 offset 6。
    if 0 not in graph: graph[0] = {}
    graph[0][0] = 1

    print(f"[*] Graph parsed with {len(graph)} source states.")
    print("[*] Tracing the linear chain (0 -> 1 -> ... -> 181)...")

    # 3. 线性追踪
    # 我们假设 Flag 的路径就是:State i -> Hit -> State i+1
    path = []

    for current_state in range(181):
        found_hit = -1

        if current_state in graph:
            # 在所有可能的 Hit 中寻找能跳到 current_state + 1 的那个
            for hit, next_state in graph[current_state].items():
                if next_state == current_state + 1:
                    found_hit = hit
                    break

        if found_hit != -1:
            path.append(found_hit)
        else:
            print(f"[-] Broken chain at State {current_state}! Could not find transition to {current_state + 1}")
            break

    print(f"[*] Chain complete. Length: {len(path)}")

    # 4. 解码 Flag
    flag = ""
    val = 0
    last_hit = -1

    for hit in path:
        # 如果当前 Hit <= 上一个 Hit,说明是新字符的开始
        if hit <= last_hit:
            flag += chr(val)
            val = 0
            last_hit = -1

        val |= (1 << hit)
        last_hit = hit

    flag += chr(val)  # 加上最后一个字符

    print("\n" + "=" * 40)
    print(f"FLAG: {flag}")
    print("=" * 40)


if __name__ == "__main__":
    solve_linear_chain()

评论