
这个案例适合大家用作实战进阶。今天我们将引入一个逆向工程中非常核心的概念:动态字符串拼接(字符串混淆的雏形)。
上两个 CrackMe 中,密码(”ixiaodi668″ 和 “123456”)是完整地硬编码在程序里的,通过字符串搜索就能瞬间秒杀。而这个程序,开发者学会了“把密码拆碎了再组装”。
今天我们逐步给大家进行拆解讲解:
阶段一:破除迷雾(依然是熟悉的配方)
“同学们,拿到这段代码,第一步永远是把我们认识的老朋友还原出来。通过前面两节课的训练,大家应该能做到‘秒认’了。”
我们一起快速做一遍重命名:
sub_401C85-> 里面有提示语,显然是printf。那个多余的Str1[0]依然是 IDA 分析可变参数时的瑕疵,直接忽略。sub_4011EF-> 带有%s格式化符号,显然是获取用户输入的scanf。sub_403CD8-> 带有 “pause”,显然是执行系统命令的system。
清理完外围后,程序的输入输出骨架就已经非常清晰了,直接进入核心密码生成区。
阶段二:解析关键函数(C语言函数小课堂)
在这里,我们给没有 C 语言基础的学员科普两个极其常用的标准库函数,这是解开本题的关键:
strcpy(目标, 源):String Copy,字符串拷贝。意思是把后面的内容,原封不动地复制给前面的变量。strcat(目标, 追加内容):String Concatenate,字符串拼接。意思是把后面的内容,接在前面字符串的尾巴上。
“开发者为了防止我们一眼看出密码,没有直接写 strcmp(输入, "密码"),而是准备了几个零件:Source、v8、v6,并且准备了一个空的大盒子 Destination(通过 memset 清零了)。接下来,程序就像玩乐高积木一样,开始组装真正的密码了。”
阶段三:沙盘推演(大脑模拟执行)
这时候,大家可以一行一行在大脑中模拟变量的变化,这种“推演感”能让新手村的各位朋友迅速建立起逆向的成就感:
- 准备零件:
-
strcpy(Source, "gam");->Source变成了"gam"strcpy(v8, "ma");->v8变成了"ma"strcpy(v6, "789");->v6变成了"789"
- 开始拼接(核心逻辑):
-
j__strcat(Destination, Source);
-
-
- 大盒子刚开始是空的,拼接后,
Destination="gam"
- 大盒子刚开始是空的,拼接后,
-
-
j__strcat(Destination, v8);
-
-
- 在尾巴上追加
v8,Destination="gam"+"ma"="gamma"
- 在尾巴上追加
-
-
j__strcat(Destination, v6);
-
-
- 继续在尾巴上追加
v6,Destination="gamma"+"789"="gamma789"
- 继续在尾巴上追加
-
推演完毕,最终进入判断逻辑:
if ( !j__strcmp(Str1, Destination) )
到这里自然就恍然大悟了:程序实际上是在拿用户的输入,和拼接好的 "gamma789" 进行比对。通关密码就是 "gamma789"!
阶段四:底层拔高与变量重命名(大师课)
为了更有深度的理解逆向,我们可以观察一下这三个“零件”变量在栈上的定义:
char v6[4]; // [esp+80h] [ebp-Ch]
char Source[4]; // [esp+84h] [ebp-8h]
char v8[4]; // [esp+88h] [ebp-4h]
为什么偏偏都是长度为 4 的数组?
问题与答案:“比如 gam 明明只有 3 个字母,为什么要用 4 个字节存?因为 C 语言的字符串必须以 \0(空字符,占 1 个字节)作为结束标志。所以 g-a-m-\0 刚好占满 4 个字节。这也侧面印证了开发者的用心设计。”
最后,大家可以实践一下变量重构的终极形态,按 N 键重命名变量,让伪代码变得完美:
Str1->user_inputDestination->real_passwordSource->part1v8->part2v6->part3
经过小迪老师的这番拆解,原本带有混淆性质的代码,就会变成极具观赏性的艺术品,大家的逆向思维也会随之完成一次升华。




没有回复内容