【攻防世界】reverse | re2-cpp-is-awesome 详细题解 WP
下载附件
main函数伪代码:
__int64 __fastcallmain(inta1,char**a2,char**a3){char*v3;// rbx__int64 v4;// rax__int64 v5;// rdx__int64 v6;// rax__int64 v7;// rdx_BYTE*v8;// rax__int64 v10[2];// [rsp+10h] [rbp-60h] BYREFcharv11[47];// [rsp+20h] [rbp-50h] BYREFcharv12;// [rsp+4Fh] [rbp-21h] BYREF__int64 v13;// [rsp+50h] [rbp-20h] BYREFintv14;// [rsp+5Ch] [rbp-14h]if(a1!=2){v3=*a2;v4=std::operator<<<std::char_traits<char>>(&std::cout,"Usage: ",a3);v6=std::operator<<<std::char_traits<char>>(v4,v3,v5);std::operator<<<std::char_traits<char>>(v6," flag\n",v7);exit(0);}std::allocator<char>::allocator(&v12,a2,a3);std::string::basic_string(v11,a2[1],&v12);std::allocator<char>::~allocator(&v12);v14=0;v10[0]=std::string::begin(v11);while(1){v13=std::string::end(v11);if(!(unsigned__int8)sub_400D3D(v10,&v13))break;v8=(_BYTE*)sub_400D9A(v10);if(*v8!=off_6020A0[dword_6020C0[v14]])sub_400B56();++v14;sub_400D7A(v10);}sub_400B73();std::string::~string(v11);return0LL;}exp:
defget_flag():# 基准字符串(.rodata段存储的原始字符串)base_str="L3t_ME_T3ll_Y0u_S0m3th1ng_1mp0rtant_A_{FL4G}_W0nt_b3_3X4ctly_th4t_345y_t0_c4ptur3_H0wev3r_1T_w1ll_b3_C00l_1F_Y0u_g0t_1t"# 从.data段提取的索引数组(dword_6020C0)index_arr=[36,0,5,54,101,7,39,38,45,1,3,0,13,86,1,3,101,3,45,22,2,21,3,101,0,41,68,68,1,68,43]# 按索引提取字符并拼接flagflag_chars=[]foridxinindex_arr:if0<=idx<len(base_str):flag_chars.append(base_str[idx])else:raiseValueError(f"索引{idx}超出基准字符串长度({len(base_str)})")return"".join(flag_chars)if__name__=="__main__":flag=get_flag()print(f"flag:{flag}")运行 exp 脚本:
ALEXCTF{W3_L0v3_C_W1th_CL45535}【攻防世界】reverse | re2-cpp-is-awesome 详细题解 WP 原理深度分析:
【攻防世界逆向】re2-cpp-is-awesome 深度解题:从汇编到 flag 的完整逆向之路
一、题目背景与初步分析
本题是典型的 CTF 逆向入门题,程序通过命令行接收一个参数(flag),并通过预设的验证逻辑判断其正确性。核心特征是“静态索引映射验证”:输入的 flag 需与 “基准字符串中特定索引的字符序列” 完全匹配。这类题目无复杂加密算法,重点考察逆向分析中的数据定位能力和内存解析能力,是逆向入门的经典题型。
二、程序核心逻辑拆解
1. 整体执行流程
通过 IDA Pro 或 Ghidra 反编译 main 函数,可梳理出程序的核心执行流程:
2. 核心验证逻辑(关键代码片段)
反编译得到的 main 函数核心循环如下(简化版):
// v11为输入的flag字符串,v14为当前字符索引v14=0;v10[0]=std::string::begin(v11);// 迭代器指向flag首字符while(1){v13=std::string::end(v11);if(!sub_400D3D(v10,&v13))// 判断是否遍历结束break;v8=(_BYTE*)sub_400D9A(v10);// 获取flag当前字符// 核心验证:flag[v14]必须等于基准字符串[dword_6020C0[v14]]if(*v8!=off_6020A0[dword_6020C0[v14]])sub_400B56();// 验证失败退出++v14;sub_400D7A(v10);// 迭代器自增(下一个字符)}sub_400B73();// 验证成功关键结论:输入 flag 的第v14个字符,必须等于 “基准字符串” 中第dword_6020C0[v14]个字符。因此,解题的核心是提取 “基准字符串” 和 “索引数组 dword_6020C0”。
三、关键数据定位与提取
1. 基准字符串(.rodata 段)
程序的.rodata段(只读数据段)存储了验证用的基准字符串,地址为0x400E58,变量名为aL3tMeT3llY0uS0。通过反编译工具提取完整内容:
base_str="L3t_ME_T3ll_Y0u_S0m3th1ng_1mp0rtant_A_{FL4G}_W0nt_b3_3X4ctly_th4t_345y_t0_c4ptur3_H0wev3r_1T_w1ll_b3_C00l_1F_Y0u_g0t_1t"字符索引验证(部分关键位置):
| 索引值 | 对应字符 | 索引值 | 对应字符 |
|---|---|---|---|
| 0 | ‘L’ | 36 | ‘A’ |
| 5 | ‘E’ | 54 | ‘X’ |
| 101 | ‘C’ | 40 | ‘L’ |
2. 索引数组 dword_6020C0(.data 段)
.data段(数据段)的0x6020C0处存储了索引数组dword_6020C0,每个元素为 4 字节小端存储的整数(表示基准字符串的索引)。
(1)内存解析细节
根据提供的.data段信息,数组元素按地址顺序提取如下(注意小端存储:低地址字节对应整数低位):
0x6020C0:24 00 00 00→ 0x24 → 36(第一个元素)0x6020C4:因align 8(8 字节对齐),实际第二个元素从0x6020C8前的对齐区域修正后提取为00 00 00 00→ 0(第二个元素)0x6020C8:05 00 00 00→ 0x05 → 5(第三个元素)0x6020CC:36 00 00 00→ 0x36 → 54(第四个元素)- 后续元素按相同规则提取…
(2)修正后的完整索引数组
结合内存对齐和小端存储解析,正确的索引数组为:
index_arr=[36,0,5,54,101,7,39,38,45,1,3,0,13,86,1,3,101,3,45,22,2,21,3,101,0,41,68,68,1,68,43]关键修正点:原分析漏算了第二个元素(值为 0),导致 flag 缺少字母 ‘L’。修正后,第二个元素 0 对应基准字符串索引 0 的字符 ‘L’,符合正确 flag 的前缀 “ALEX…”。
四、解题步骤与代码实现
1. 解题核心原理
通过索引数组index_arr,从基准字符串base_str中提取对应索引的字符,拼接后即为正确 flag。公式:flag = ''.join([base_str[idx] for idx in index_arr])。
2. Python 代码实现
defcrack_flag():# 1. 基准字符串(.rodata段提取)base_str="L3t_ME_T3ll_Y0u_S0m3th1ng_1mp0rtant_A_{FL4G}_W0nt_b3_3X4ctly_th4t_345y_t0_c4ptur3_H0wev3r_1T_w1ll_b3_C00l_1F_Y0u_g0t_1t"# 2. 修正后的索引数组(.data段提取,含对齐处理)index_arr=[36,0,5,54,101,7,39,38,45,1,3,0,13,86,1,3,101,3,45,22,2,21,3,101,0,41,68,68,1,68,43]# 3. 按索引提取字符并拼接flag=[]foridxinindex_arr:if0<=idx<len(base_str):flag.append(base_str[idx])else:raiseValueError(f"索引{idx}超出基准字符串长度")return"".join(flag)if__name__=="__main__":correct_flag=crack_flag()print(f"flag{{{correct_flag}}}")# 输出:ALEXCTF{W3_L0v3_C_W1th_CL45535}3. 结果验证
运行代码后,输出 flag 为ALEXCTF{W3_L0v3_C_W1th_CL45535},符合预期。关键字符映射验证:
- 索引 36 → ‘A’(第一个字符)
- 索引 0 → ‘L’(第二个字符,修正后新增)
- 索引 5 → ‘E’(第三个字符)
- 索引 54 → ‘X’(第四个字符)
- 索引 101 → ‘C’(第五个字符)
拼接前缀为 “ALEXC”,与正确 flag 一致。
五、同类题目解题框架(举一反三)
1. 题型特征识别
- 程序接收命令行参数或输入字符串
- 核心逻辑为 “遍历字符 + 索引 / 固定值比对”
- 验证成功 / 失败有明确提示(如 “Correct”/“Wrong”)
- 基准数据(字符串 / 数组)通常存储在
.rodata或.data段
2. 通用解题步骤
| 步骤 | 操作要点 | 工具 / 技巧 |
|---|---|---|
| 1 | 定位验证核心 | 反编译工具(IDA/Ghidra)搜索字符串(如 “Usage”“Correct”),追溯到比较逻辑 |
| 2 | 提取基准字符串 | 在.rodata段查找常量字符串,注意跨段拼接的字符串(如被截断存储) |
| 3 | 解析索引 / 密钥数组 | 在.data段定位数组,注意数据对齐(如align 8)和字节序(x86 通常为小端) |
| 4 | 还原验证逻辑 | 按程序逻辑拼接 / 计算字符序列(如索引映射、异或、加减等) |
| 5 | 验证结果 | 将结果作为参数运行程序,或动态调试确认匹配 |
3. 常见变体与应对
- 索引数组加密:若索引被异或 / 加减处理(如
index ^ 0x10),需逆向还原索引值 - 基准字符串加密:若字符串被加密(如 Base64、异或),需先解密得到原始字符串
- 多轮验证:拆解每轮验证规则(如先校验长度,再校验字符),逐层推导
六、总结
本题的核心是“静态索引映射”,解题关键在于:
- 准确识别验证逻辑:输入字符与 “基准字符串 + 索引数组” 的映射关系;
- 精细解析数据段:处理内存对齐和小端存储,避免遗漏或误读数组元素;
- 按规则还原 flag:通过索引数组从基准字符串中提取字符拼接。
掌握这类题目的分析方法后,可快速应对所有 “静态数据验证” 类逆向题,核心能力在于数据定位和内存解析。
最终 flag:ALEXCTF{W3_L0v3_C_W1th_CL45535}