本文详细记录了ASIS CTF 2016逆向题目"Licensable"的完整分析过程。通过对一个Windows可执行程序的深入分析,我们逐步揭示其license验证机制,最终获取正确的授权码并计算出Flag。本文涵盖了PE文件分析、字符串提取、静态分析、动态调试等逆向工程核心技术。
题目名称: Licensable
来源: ASIS CTF 2016
类型: Reverse Engineering
难度: 中等
目标文件: licensable.exe
程序要求用户提供邮箱地址和license密钥,当输入正确license时,程序会输出"res = 1"。
首先使用file命令分析目标文件:
$ file licensable.exe
licensable.exe: PE32 executable for MS Windows 6.00 (console), Intel i386, 5 sections
分析结果:
文件格式:PE32(32位Windows可执行文件)
目标系统:Windows 6.00(Windows Vista/Server 2008及以上)
架构:Intel i386(x86架构)
类型:控制台应用程序
节区数量:5个
$ ls -la licensable.exe
-rwxr-xr-x 1 user user 170496 Sep 9 2016 licensable.exe
关键信息:
文件大小:170496字节(约167KB)
创建时间:2016年9月9日(CTF比赛期间)
文件权限:可执行
尝试运行程序查看其行为模式:
$ ./licensable.exe
usage: mailhash.exe mail license
从输出信息可以看出:
程序需要两个参数:邮箱地址(mail)和license密钥
程序的原名可能是"mailhash.exe"
这是命令行界面程序
$ ./licensable.exe [email protected] 1234
calculating : 99.916817 %
res = 0
观察到的行为:
程序接受了邮箱地址"[email protected]"
执行了某种计算过程,显示进度达到99.916817%
最终输出"res = 0",表示验证失败
我们的目标是让程序输出"res = 1"
使用grep命令提取程序中的字符串:
$ grep -ao "usage.*mail.*license" licensable.exe
usage: mailhash.exe mail license
$ grep -ao "Sorry.*email.*not valid" licensable.exe
Sorry, your email address is not valid
$ grep -ao "calculating.*%%" licensable.exe
calculating : %f %%
$ grep -ao "res =" licensable.exe
res =
发现的字符串及其意义:
"usage: mailhash.exe mail license" - 程序使用说明
"Sorry, your email address is not valid" - 邮箱验证失败提示
"calculating : %f %%" - 计算进度显示格式
"res =" - 结果输出前缀
$ grep -ao "MailHash.*pdb" licensable.exe
MailHash\Release\MailHash.pdb
重要发现:
程序原始名称:MailHash
编译路径包含作者信息:alireza用户
程序功能推测:对邮箱进行哈希运算
$ grep -ao "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" licensable.exe
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
关键发现:程序包含标准Base64编码字符集,说明程序内部使用了Base64编码技术。
根据分析目标,我们选择以下工具:
IDA Pro:专业反汇编器,用于深度静态分析
OllyDbg:用户态调试器,用于动态调试
radare2:开源逆向工程框架(替代IDA Pro)
由于程序可能包含复杂的算法,我们采用"动静结合"的策略:
静态分析确定程序结构和关键函数
动态调试观察程序运行时的数据流
通过内存访问找到正确的license
通过字符串交叉引用,可以定位到主要验证函数。在IDA Pro中:
查找"res ="字符串的引用
追踪到main函数
找到关键的验证调用
发现的调用链:
main() -> 验证函数(0x004025B0) -> 返回结果
验证函数sub_4025B0的特征:
接收邮箱地址和license作为参数
执行复杂的计算过程
返回验证结果(0或1)
函数代码量较大,包含大量数值运算
静态分析面临的问题:
函数代码超过1000行,包含复杂的数学运算
可能包含反调试技术或代码混淆
完全逆向算法时间成本过高
动态调试的优势:
直接观察程序运行时的真实数据
避免复杂的算法逆向
可以快速定位关键验证点
OllyDbg配置:
加载licensable.exe
设置命令行参数:[email protected] test123
在关键位置设置断点
初步尝试的断点策略:
在license参数地址设置内存访问断点:未成功触发
在"calculating"字符串输出后单步跟踪:成功找到关键点
成功的方法:
等待程序显示计算进度
在calculating : 99.xxx %输出后暂停
使用F7/F8单步执行
密切观察寄存器和内存变化
在单步跟踪过程中,发现了一个重要的比较操作:
Address: 0x0040xxxx
CMP ECX, EDX ; 比较两个值
JNE loc_fail ; 不相等则跳转到失败
寄存器状态:
ECX = 0x00000058 (十进制: 88)
EDX = 0x00000058 (十进制: 88)
两个寄存器的值都是88,这表明程序在进行长度比较。
查看寄存器指向的内存地址:
地址1(用户输入):
指向我们输入的测试字符串"test123"
长度不足88字节
地址2(程序内部生成):
OTA0ZDQyZWIxN2YzMjQxNDc2NzVkYjg3YWYzOWU0ZmU5MTlhNTQxN2I5NGExNzhhNzFlODU1ZjViMzhhZjA5ZA==
关键发现:这就是正确的license密钥!
$ echo -n "OTA0ZDQyZWIxN2YzMjQxNDc2NzVkYjg3YWYzOWU0ZmU5MTlhNTQxN2I5NGExNzhhNzFlODU1ZjViMzhhZjA5ZA==" | wc -c
88
特征分析:
长度:88字符
结尾:包含"=="填充符
字符集:符合Base64编码特征
$ echo "OTA0ZDQyZWIxN2YzMjQxNDc2NzVkYjg3YWYzOWU0ZmU5MTlhNTQxN2I5NGExNzhhNzFlODU1ZjViMzhhZjA5ZA==" | base64 -d
904d42eb17f324147675db87af39e4fe919a5417b94a178a71e855f5b38af09d
解码结果分析:
$ echo -n "904d42eb17f324147675db87af39e4fe919a5417b94a178a71e855f5b38af09d" | wc -c
64
解码后得到64个十六进制字符,这正是SHA256哈希的特征:
SHA256输出:256位 = 32字节
十六进制表示:32 × 2 = 64字符
$ ./licensable.exe [email protected] "OTA0ZDQyZWIxN2YzMjQxNDc2NzVkYjg3YWYzOWU0ZmU5MTlhNTQxN2I5NGExNzhhNzFlODU1ZjViMzhhZjA5ZA=="
calculating : 99.916817 %
res = 1
验证成功!程序输出"res = 1"。
根据CTF题目的常见模式,最终的flag通常是关键数据的哈希值。本题要求计算license字符串的MD5值。
$ echo -n "OTA0ZDQyZWIxN2YzMjQxNDc2NzVkYjg3YWYzOWU0ZmU5MTlhNTQxN2I5NGExNzhhNzFlODU1ZjViMzhhZjA5ZA==" | md5sum
3e5206e41a327a158426599de8494bfc -
Flag: 3e5206e41a327a158426599de8494bfc
┌─────────────────────┐
│ 接收邮箱和License │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ 验证邮箱格式 │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ 计算邮箱的哈希值 │ ← 核心算法
│ 生成64字符hex字符串 │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Base64编码哈希值 │
│ 得到88字符字符串 │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ 比较用户输入license │
│ 与生成的Base64字符串 │
└──────────┬──────────┘
│
┌─────┴─────┐
▼ ▼
相等 不等
│ │
res=1 res=0
从解码结果看,程序使用了SHA256哈希算法:
证据链:
Base64解码后得到64个十六进制字符
64字符 = 32字节 = 256位
这正是SHA256哈希的标准输出长度
验证方法:
尝试直接计算邮箱的SHA256:
$ echo -n "[email protected]" | sha256sum
a688ecd8f67a592d6760761bed49f4a6a59fe99b4d26d2fe88f9a5938fe2fe61
结果与程序生成的哈希值不匹配,说明程序使用了更复杂的算法,可能包含:
加盐(Salt):在邮箱前后添加固定字符串
多重哈希:多次SHA256计算
自定义算法:特殊的混淆逻辑
时间成本对比:
完全逆向算法:需要数小时到数天
动态调试获取结果:10-30分钟
技术原因:
程序可能使用了专有的哈希算法
包含大量的混淆代码
可能有反调试技术
CTF比赛中时间宝贵,需要高效解决方案
文件分析:PE文件格式、字符串提取、元数据分析
静态分析:函数识别、调用链分析、代码结构理解
动态调试:断点设置、单步跟踪、内存监控
密码学识别:哈希算法识别、编码方式判断
工具链使用:IDA Pro、OllyDbg、命令行工具
字符串驱动分析:从输出字符串倒推关键代码位置
内存访问断点:监控数据读写,避免在循环中迷失
寄存器观察:重点观察比较指令(CMP)相关的寄存器
单步执行策略:在关键计算完成后开始详细跟踪
过度依赖静态分析:复杂算法通过静态分析效率低
断点设置不当:过于频繁的断点会影响调试效率
忽略字符串信息:程序中的字符串往往是最有价值的线索
工具选择错误:不同阶段需要使用不同的专业工具
书籍:
《逆向工程权威指南》
《加密与解密》
《IDA Pro权威指南》
在线平台:
crackmes.one(逆向练习平台)
CTFtime(CTF比赛信息)
看雪论坛(安全技术社区)
本题成功展示了逆向工程中"动静结合"分析方法的威力。通过系统的分析流程,我们从程序的基本信息收集开始,逐步深入到其核心验证机制,最终成功获取了正确的license并计算出flag。
关键收获:
系统性的分析流程比单一的技巧更重要
在复杂算法面前,动态调试往往是更高效的解决方案
字符串分析是逆向工程中最直接有效的线索来源
工具的熟练使用和策略的正确制定同样重要
最终Flag: 3e5206e41a327a158426599de8494bfc
本文仅用于网络安全教育和研究目的,请勿用于非法用途。