从最简单的程序开始教你怎么破解(一)
发布于
28号我发了一篇关于破解迅雷的文章,如果你看到了,你就会先被那上千的字数吓住。但是破解(反编译)真的有那么难吗?作为一个不太“菜”的“菜鸟”,我想告诉你,其实破解一个软件只是找到思路后,改改二进制的逻辑罢了。但是真的又有这么简单吗?也不是。
如果你刚接触反编译、破解软件,那就从自己开发的最简单的小程序开始破解。今天这篇文章所讲的,是关于如何在MacOS Catalina系统里,使用Hopper Disassembler破解二进制文件。希望我可以通过这篇文章能帮到你。
入手程序
我们想要破解,可以先从最简单的程序开始——Crackmes。Crackme都是一些公开给别人尝试破解的小程序,制作Crackme的人可能是程序员,想测试一下自己的软件保护技术,也可能是一位Cracker,想挑战一下其它Cracker的破解实力,也可能是一些正在学习破解的人,自己编一些小程序给自己破。CrackMe简称CM。有不少论坛的CM大赛就是指这个。我用Swift语言做了几个简单的Crackme,来教你们怎么破解。先看看第一个CM:

我的这个程序首先会用中文打印出说明书和破解要求,然后要求你输入一个固定的序列号,才能激活,如果错误,会提示让你再输入一遍,直到输入正确为止。而你的工作,就是把程序改成输入任意序列号都能激活。
分析程序
将这个二进制拖入 Hopper (关于 Hopper 的使用,《破解迅雷》那篇文章有所涉及,不过有时间我可以专门出一篇文章讲 Hopper 的使用,这里不再阐述)分析。分析完成后, Hopper 应该会跳转到_main函数。类似下图:

有人说,不对啊,Swift语言没有main主函数这一说啊,为什么会有它呢?因为,Swift中,写法里没有主函数,但是Swift代码是从上往下依次执行的,因袭编译器在编译中,自然会解析到一种进程,但它不能称作主函数,只能用主进程这样的词形容。因此,所有代码都会出现在这里。
面对这么多复杂的汇编指令怎么办?自然是要瞧一瞧伪代码:
int _main(int arg0, int arg1) {
rax = Swift.String.init(82mal9-29anyw-9msfz6, 0x14, 0x1);
rsi = *type metadata for Any;
*Crackme__1.serial : Swift.String = rax;
*qword_100002050 = 0x1;
var_20 = Swift._allocateUninitializedArray(0x1, rsi + 0x8);
rax = Swift.String.init(\xE8\xAF\xB4\xE6\x98\x8E\xEF\xBC\x9A\xE6\xAD\xA4\xE7\xA8\x8B\xE5\xBA\x8F\xE8\xA6\x81\xE6\xB1\x82\xE4\xBD\xA0\xE8\xBE\x93\xE5\x85\xA5\xE4\xB8\x80\xE4\xB8\xAA\xE5\x9B\xBA\xE5\xAE\x9A\xE7\x9A\x84\xE5\xBA\x8F\xE5\x88\x97\xE5\x8F\xB7\xEF\xBC\x8C\xE6\x89\x8D\xE8\x83\xBD\xE6\xBF\x80\xE6\xB4\xBB\xE3\x80\x82\n\xE8\xA6\x81\xE6\xB1\x82\xEF\xBC\x9A\xE6\x8A\x8A\xE7\xA8\x8B\xE5\xBA\x8F\xE6\x94\xB9\xE6\x88\x90\xE8\xBE\x93\xE5\x85\xA5\xE4\xBB\xBB\xE6\x84\x8F\xE5\xBA\x8F\xE5\x88\x97\xE5\x8F\xB7\xE9\x83\xBD\xE8\x83\xBD\xE6\xBF\x80\xE6\xB4\xBB\xE3\x80\x82\n, 0x89, 0x0, 0x0);
*(int64_t *)0x19 = *type metadata for Swift.String;
*(int64_t *)0x1 = rax;
*(int64_t *)0x9 = 0x0;
var_28 = default argument 1 of Swift.print();
Swift.print(var_20, var_28, 0x0, default argument 2 of Swift.print(), 0x0);
swift_bridgeObjectRelease(0x0);
swift_bridgeObjectRelease(0x0);
swift_bridgeObjectRelease(var_20);
Crackme__1.verify(var_20, var_28);
return 0x0;
}
虽然我不太懂OC,也不太懂这代码说的是什么,但是至少知道了如下几点(看代码注释):
int _main(int arg0, int arg1) { // main函数
rax = Swift.String.init(82mal9-29anyw-9msfz6, 0x14, 0x1); // Swift的字符串初始化。从字符串看,很可能是注册码:82mal9-29anyw-9msfz6
...
rax = Swift.String.init(\xE8\xAF\xB4\xE6\x98\x8E\xEF\xBC\x9A\xE6\xAD\xA4\xE7\xA8\x8B\xE5\xBA\x8F\xE8\xA6\x81\xE6\xB1\x82\xE4\xBD\xA0\xE8\xBE\x93\xE5\x85\xA5\xE4\xB8\x80\xE4\xB8\xAA\xE5\x9B\xBA\xE5\xAE\x9A\xE7\x9A\x84\xE5\xBA\x8F\xE5\x88\x97\xE5\x8F\xB7\xEF\xBC\x8C\xE6\x89\x8D\xE8\x83\xBD\xE6\xBF\x80\xE6\xB4\xBB\xE3\x80\x82\n\xE8\xA6\x81\xE6\xB1\x82\xEF\xBC\x9A\xE6\x8A\x8A\xE7\xA8\x8B\xE5\xBA\x8F\xE6\x94\xB9\xE6\x88\x90\xE8\xBE\x93\xE5\x85\xA5\xE4\xBB\xBB\xE6\x84\x8F\xE5\xBA\x8F\xE5\x88\x97\xE5\x8F\xB7\xE9\x83\xBD\xE8\x83\xBD\xE6\xBF\x80\xE6\xB4\xBB\xE3\x80\x82\n, 0x89, 0x0, 0x0); // 这一大长串的东西也是字符串初始化,应该是中文,可能是“说明”和“要求”的字符串
...
var_28 = default argument 1 of Swift.print(); // 打印了那一长串中文
Swift.print(var_20, var_28, 0x0, default argument 2 of Swift.print(), 0x0); // 打印了那一长串中文
...
Crackme__1.verify(var_20, var_28); // 调用了一个叫verify的函数
return 0x0; // 程序结束
}
最吸引人的是verify()函数,它有两个参数:var_20和var_28。这个函数把这两个参数做比较,最后打印出结果。因为函数后面没有打印结果的操作,所以这个打印结果的操作应该在函数里。
双击这个函数,就能查看。这个函数不短(其实源代码很短),我们来看关键点:
int _$s10Crackme__16verifyyyF(int arg0, int arg1) {
... // 定义了一些东西,还打印了说明和要求
if ((var_89 & 0x1) == 0x0) {
// 判断语句
}
else {
// 判断语句
}
rax = outlined consume of Swift.String?(var_70, 0x1);
return rax; // 执行一些结束用的代码
}
我们只要让程序执行正确的语句就行了。但是怎么判断条件语句中if是正确的,还是else是正确的呢?经过仔细观察,我们看见了if中有一个代码:
Crackme__1.verify(var_D0, rsi);
按照程序的思路,用户输错了,程序会提示你错误,然后再次调用函数,让用户重新输入序列号。函数中会嵌套一个同样的函数,不难看出这是递归,按照思路走,递归一般会使用在用户序列号错误的情况,因此:if中是用户输错的情况,else是输对的情况。只要让程序执行else代码块即可。
修改程序
随便在else中选一段代码,切回CFG Mode,可以看到,这段代码在loc_100000ba2里,因此loc_100000ba2是正确代码。沿着上面的箭头找,就会发现上面的主程序是_$s10Crackme__16verifyyyF函数,只需把函数下的jne做一个硬性跳转,换成jmp:
jmp loc_100000ba2

之后按回车。
修改完成
回车后,你的 Hopper 可能会出现这个诡异的画面:

不用怕,这只是 Hopper 的 CFG bug。之所以它显示这个画面,是因为你在CFG模式下,然而你的光标自动跳转到了白色区域的代码,这段代码已经不存在了(被ret了)。因此随便点击 Hopper 的任意位置(白色区域代码外),就可以回到 CFG 了。接下来,让我们在纵观一下 CFG:
怎么样?刚才的两个分支变成了一个分支了。运行一下程序,无论输入什么,都可以成功激活。这么鼓捣下来,还是挺有趣的。
再次声明:无论任何人使用文章中的技术破解任何软件造成的后果,请自行承担。