【攻防世界】reverse | hackme 详细题解 WP
下载附件
sub_400F8E函数伪代码:
__int64 __fastcallsub_400F8E(__int64 a1,inta2,inta3,inta4,inta5,inta6){intv6;// edxintv7;// ecxintv8;// r8dintv9;// r9dintv10;// ecxintv11;// r8dintv12;// r9dcharv14;// [rsp+0h] [rbp-C0h]charv15;// [rsp+0h] [rbp-C0h]charv16[136];// [rsp+10h] [rbp-B0h] BYREFintv17;// [rsp+98h] [rbp-28h]charv18;// [rsp+9Fh] [rbp-21h]intv19;// [rsp+A0h] [rbp-20h]unsigned__int8 v20;// [rsp+A6h] [rbp-1Ah]charv21;// [rsp+A7h] [rbp-19h]intv22;// [rsp+A8h] [rbp-18h]intv23;// [rsp+ACh] [rbp-14h]intv24;// [rsp+B0h] [rbp-10h]intv25;// [rsp+B4h] [rbp-Ch]_BOOL4 v26;// [rsp+B8h] [rbp-8h]inti;// [rsp+BCh] [rbp-4h]sub_407470((unsignedint)"Give me the password: ",a2,a3,a4,a5,a6,a2);sub_4075A0((unsignedint)"%s",(unsignedint)v16,v6,v7,v8,v9,v14);for(i=0;v16[i];++i);v26=i==22;v25=10;do{v10=(int)sub_406D90()%22;v22=v10;v24=0;v21=byte_6B4270[v10];v20=v16[v10];v19=v10+1;v23=0;while(v23<v19){++v23;v24=1828812941*v24+12345;}v18=v24^v20;if(v21!=((unsigned__int8)v24^v20))v26=0;--v25;}while(v25);if(v26)v17=sub_407470((unsignedint)"Congras\n",(unsignedint)v16,v24,v10,v11,v12,v15);elsev17=sub_407470((unsignedint)"Oh no!\n",(unsignedint)v16,v24,v10,v11,v12,v15);return0LL;}exp:
# 核心:22位密码 = 密文字节 ^ 对应索引的哈希值低字节defcalculate_hash(idx):"""计算索引idx对应的哈希值(低字节)"""hash_val=0# 迭代次数 = idx + 1 次for_inrange(idx+1):hash_val=1828812941*hash_val+12345# 仅保留低字节(unsigned __int8)returnhash_val&0xFFdefcrack_full_password():# 1. 密文byte_6B4270的22个字节(idx 0-21)cipher_bytes=[0x5F,0xF2,0x5E,0x8B,0x4E,0x0E,0xA3,0xAA,0xC7,0x93,0x81,0x3D,0x5F,0x74,0xA3,0x09,0x91,0x2B,0x49,0x28,0x93,0x67]password=[]# 2. 遍历每个索引计算密码字符foridxinrange(22):# 计算当前索引的哈希值低字节hash_low=calculate_hash(idx)# 异或逆运算:input[idx] = 密文 ^ 哈希低字节char_byte=cipher_bytes[idx]^hash_low# 转换为ASCII字符(过滤不可打印字符,用.替代)char=chr(char_byte)if32<=char_byte<=126else'.'password.append(char)# 打印中间过程(可选,用于验证)print(f"索引{idx:2d}| 密文:0x{cipher_bytes[idx]:02X}| 哈希低字节:0x{hash_low:02X}| 密码字符:{char}")# 3. 拼接最终密码full_password=''.join(password)returnfull_password# 执行破解if__name__=="__main__":print("=== 22位密码破解过程 ===")final_password=crack_full_password()print("\n=== 最终破解结果 ===")print(f"完整22位密码:{final_password}")运行 exp 脚本:
flag{d826e6926098ef46}【攻防世界】reverse | hackme 详细题解 WP 原理深度解析:
【CTF 逆向实战】攻防世界 hackme 深度解析:从 0 到 1 破解 22 位密码验证逻辑
引言:CTF 逆向中的 “密码验证” 题型
在 CTF 逆向领域,“密码验证类” 题目是经典的入门到进阶题型。这类题目通常隐藏着明确的 “输入 - 验证 - 输出” 链路,核心矛盾在于 “如何从程序逻辑中反推正确输入”。本文以攻防世界hackme为例,从静态分析到动态验证,完整拆解一道 64 位 ELF 程序的密码验证逻辑。
一、题目初探:程序类型与核心场景定位
1. 程序基础信息
通过file命令和初步静态分析:
- 程序类型:64 位 Linux ELF 可执行文件(x86-64 架构)
- 核心功能:接收用户输入的密码,验证通过输出
Congras,失败输出Oh no! - 关键特征:包含字符串
"Give me the password:"、"Congras"、"Oh no!",是典型的交互型密码验证程序
2. 核心函数定位技巧
在逆向分析中,字符串是 “锚点”。通过 IDA 或 Ghidra 的 “交叉引用” 功能,快速定位:
- 输入提示字符串
"Give me the password:"对应函数sub_400F8E(地址sub_400F8E+18) - 成功 / 失败字符串
"Congras"/"Oh no!"同样指向sub_400F8E(地址sub_400F8E+12A和sub_400F8E:loc_4010CC)
由此确定:sub_400F8E是密码验证的核心函数,关键逻辑在此处。
二、核心函数逆向:sub_400F8E 全链路解析
1. 函数框架概览
sub_400F8E的执行流程可简化为 5 步,关键节点:
2. 逐行拆解关键逻辑
(1)输入处理:密码存储位置
// 输出提示:"Give me the password: "sub_407470((unsignedint)"Give me the password: ",...);// 接收输入到v16数组(栈上136字节缓冲区)sub_4075A0((unsignedint)"%s",(unsignedint)v16,...);解析:用户输入的密码被存储在栈上的v16数组中,验证围绕该数组展开。
(2)第一道防线:长度校验
// 计算输入字符串长度for(i=0;v16[i];++i);// 验证长度必须为22v26=i==22;关键结论:密码长度严格固定为 22 位,长度不符直接失败。优先破解可排除大量无效尝试。
(3)核心验证:10 轮随机校验的 “陷阱”
v25=10;// 循环10次do{// 生成0-21的随机索引(覆盖22位密码)v10=(int)sub_406D90()%22;// 取密文和输入的对应字节v21=byte_6B4270[v10];// 密文字节(全局数据)v20=v16[v10];// 输入密码的第v10位// 计算哈希值v24=0;v19=v10+1;// 迭代次数=索引+1for(v23=0;v23<v19;v23++){v24=1828812941*v24+12345;// 哈希公式}// 验证逻辑:密文 == 哈希低字节 ^ 输入字节if(v21!=((unsigned__int8)v24^v20))v26=0;// 任意一次失败则整体失败v25--;}while(v25);逆向突破点:
- 看到 “随机验证 10 个位置”,实则是 CTF 经典陷阱:若存在任何一位错误,10 轮随机验证大概率命中错误位置,因此必须保证 22 位全部正确(“随机即全量”)。
- 验证公式可逆:
输入字节 = 密文字节 ^ (哈希值低字节)(异或运算可逆性:若c = a^b,则a = c^b)。
(4)结果输出:验证成功的标志
if(v26)v17=sub_407470((unsignedint)"Congras\n",...);// 成功elsev17=sub_407470((unsignedint)"Oh no!\n",...);// 失败解析:v26是验证结果标志,仅当长度正确且 10 轮验证全通过时为1。
三、关键数据提取:密文与哈希参数
1. 密文byte_6B4270提取
从程序数据段(.data 节)中,byte_6B4270是长度为 22 字节的全局数组,对应 22 位密码的验证密文。通过静态分析工具提取其值:
cipher_bytes=[0x5F,0xF2,0x5E,0x8B,0x4E,0x0E,0xA3,0xAA,0xC7,0x93,0x81,0x3D,0x5F,0x74,0xA3,0x09,0x91,0x2B,0x49,0x28,0x93,0x67]2. 哈希计算参数确认
从代码中提取哈希公式关键参数:
- 初始值:
hash_val = 0 - 迭代公式:
hash_val = 1828812941 * hash_val + 12345(线性同余公式) - 迭代次数:
idx + 1次(idx为当前密码索引,0-21) - 有效位:仅保留低 8 位(
hash_val & 0xFF,因验证使用unsigned __int8)
四、解题实现:从逻辑到代码
1. 核心思路
基于逆向分析,破解步骤可简化为:
- 对每个索引
idx(0-21),按公式计算哈希值并取低字节; - 利用异或逆运算计算密码字符:
password[idx] = cipher_bytes[idx] ^ (hash_low); - 拼接 22 位字符得到完整密码。
2. Python 解题代码
defcalculate_hash(idx):"""计算索引idx对应的哈希值低8位"""hash_val=0# 迭代次数为idx + 1次for_inrange(idx+1):hash_val=1828812941*hash_val+12345# 仅保留低8位(unsigned __int8)returnhash_val&0xFFdefcrack_password():# 密文byte_6B4270的22个字节cipher_bytes=[0x5F,0xF2,0x5E,0x8B,0x4E,0x0E,0xA3,0xAA,0xC7,0x93,0x81,0x3D,0x5F,0x74,0xA3,0x09,0x91,0x2B,0x49,0x28,0x93,0x67]password=[]foridxinrange(22):# 计算当前索引的哈希低字节hash_low=calculate_hash(idx)# 异或逆运算得到密码字节char_byte=cipher_bytes[idx]^hash_low# 转换为字符(包含扩展ASCII)password.append(chr(char_byte))return''.join(password)if__name__=="__main__":flag=crack_password()print(f"破解结果:{flag}")3. 运行结果与验证
执行代码得到 22 位密码,在 Linux 终端输入后,程序输出Congras,验证成功。
五、原理深度解析
1. 线性同余哈希的确定性
本题哈希公式hash = a * hash + b(a=1828812941,b=12345)是典型的线性同余生成器(LCG),其核心特性是确定性:
- 给定初始值(0)和迭代次数(
idx+1),哈希值唯一确定,可复现计算; - 低字节独立:验证仅使用低 8 位,无需处理高位,简化计算。
2. 异或运算的可逆性
异或是 CTF 中常见的轻量加密手段,因可逆性成为逆向突破口:
- 运算规则:
0^a=a,a^a=0,a^b^b=a; - 本题中,已知密文
c和哈希低字节h,则原始密码p = c ^ h(由c = p ^ h推导)。
3. 随机验证的本质
程序使用sub_406D90()(伪随机函数)生成索引,看到增加破解难度,实则是 “障眼法”:
- 伪随机数在固定种子下可预测,但本题无需预测 ——10 轮验证覆盖 22 位的概率极高,必须保证所有位正确;
- 逆向中遇到 “随机抽样验证”,优先按 “全量验证” 处理(这是 CTF 高频套路)。
六、举一反三:同类题目解题方法论
1. 快速定位核心函数
- 字符串锚点法:搜索
"success"、"correct"、"password"等关键字,其交叉引用往往指向验证函数; - 输入输出跟踪:找到
scanf/fgets(接收输入)和printf(输出结果)的调用处,向上追溯验证逻辑。
2. 关键数据提取技巧
- 密文识别:在数据段中寻找长度与密码长度匹配的字节数组(如本题 22 字节),尤其注意
.data和.rodata节; - 常数记录:哈希 / 加密公式中的常数(如本题的
1828812941、12345)是逆向计算的关键,务必准确提取。
3. 逻辑简化策略
- 长度优先破解:优先确定密码长度(如本题的
i==22),缩小范围; - 去干扰项:忽略线程安全(如原子操作)、日志输出等辅助逻辑,聚焦 “输入→验证→输出” 核心链路;
- 随机即全量:遇到随机索引验证,默认需所有位置满足条件(避免陷入随机数预测误区)。
4. 动态调试辅助
若静态分析存疑(如哈希计算是否正确),用 GDB 验证:
# 启动程序 gdb ./hackme # 在哈希计算后设断点 b *0x400F8E + 0x80 # 运行并输入临时密码 r testpassword123456789 # 查看索引0的哈希低字节 p/x v24 & 0xFF七、总结:逆向的本质是 “逻辑还原”
hackme作为典型的密码验证题,其破解过程完美体现了 CTF 逆向的核心思维:从程序逻辑中还原 “输入与输出的映射关系”。无论是线性哈希、异或验证还是随机干扰,本质都是对 “正确输入” 的约束条件。掌握 “定位核心→提取参数→逆向计算” 的流程,就能应对大多数同类题目。
最终 flag:flag{d826e6926098ef46}
通过上述步骤破解得到的 22 位密码即为本题 flag。