逆向学习
工具
OllyDbg(OD):https://www.52pojie.cn/thread-350397-1-1.html
PEID:https://www.52pojie.cn/thread-170387-1-1.html
Exeinfo PE:https://www.52pojie.cn/thread-437586-1-1.html
樱花补丁制作工具:https://www.52pojie.cn/thread-62307-1-1.html
内存补丁生成器:https://www.52pojie.cn/thread-162411-1-1.html
注册机生成器:https://www.52pojie.cn/thread-159470-1-1.html
IDA Pro v7:https://www.52pojie.cn/thread-675251-1-1.html
闯关题目
160个CrackMe的打包文件下载地址:
下载地址1:https://pan.baidu.com/s/1cySNkj9uENG_ppK80BmjUQ 密码:8d77
下载链接2:https://pan.baidu.com/s/150wDRlmXTQjwm2-ey-3I6Q 密码:qr21
第一关
首先用工具查看一些基本的信息,可以看到编译程序是Delphi3.0,没有壳
进去的第一个界面,嘲讽一下。。。
然后是程序主体,分别有三个按钮
一个是Serial/Name
,需要输入用户名和注册码才能通过,另外一个Serial
只需要输入一个注册码一类的东西,最后就是退出程序
这里先点击最左侧的Serial/Name
这里提示输入用户名和注册码,随便填入提交,观察提示
点击Check it Baby!
它会弹出一个对话框提示: Sorry, The Serial is incorrect !
再换几个随意试试,发现就这一种情况。
OK,出现了对话框这就很好办,说明作者在校验注册码之后发现如果错误了就直接弹窗,我们只要找到弹出对话框的地方,向上跟踪,就可以找出判断是否正确的地方了
爆破部分
接着就用到OD来分析
看了好久总算晓得一点OD的使用方法
OD打开需要调试的程序
打开之后,点击运行的图标
程序正常运行,可以随时暂停检测行为
使用OD打开程序后,进行上面重复的操作,在出现对话框提示Sorry, The Serial is incorrect !
时,点击暂停,打开主线程调用堆栈信息
有一些攻略是这么写的
这里有两个MessageBox的地址,第一个地址为77D5082F这个地址明显太大,不在模块的领空,不是的。
第二个地址为0042A1AE,和00400100地址非常接近,十有八九就是它了。
右键 show call, 在Call上面设置断点。
然后自己去了解了一下MessageBox
的功能就是显示一个消息对话框,在弹窗时,我们暂停进程,然后在OD中找到那一段来分析判断错误弹窗的条件,我的理解大概就是这样
按照上面的操作,进去之后就是这样的
show call
进去后按F2
设置断点
注意反复调试需要关闭断点,不会一直卡在那个断点处
可以把断点理解为存档
上下查看,发现与弹窗内容相同的字符串,哈哈这可能是个突破点
JNZ会通过它上面的call 004039FC 判断我们的伪码是否正确,判断的结果存在EAX中,如果EAX不等于就跳转到错误提示信息框那里。
我们的目的是无论伪码是否正确都通过验证,所以最简单的办法就是将jnz这句使用NOP填充,我们尝试一下:选择JNZ这句,右键Binary
->Fill with NOPS
.回到原始程序,再次点击Check it baby!
提示Good Job!
通过了!
但是经过测试,必须要用户名大于四位,才能通过
小于四位,运行会停止,这个校验字符串长度是在断点上方,查看上方jeg
部分,如果想要输入的账户长度不大于4,只需要把JGE给修改成JMP让其强制跳转即可
上面这些就是爆破的部分,然后接下来尝试能否分析出注册机算法
算法部分
将所有断点、修改的内容右键UNDO取消,然后重新一遍操作,随便输入然后报错,跳转show call
这里将选中代码复制出来查看
0042FAFE |. E8 F93EFDFF call Acid_bur.004039FC ; 这个CALL的返回值
0042FB03 |. 75 1A jnz XAcid_bur.0042FB1F ; 上一个CALL的返回值不等于0则跳转到错误消息
0042FB05 |. 6A 00 push 0x0
0042FB07 |. B9 CCFB4200 mov ecx,Acid_bur.0042FBCC
0042FB0C |. BA D8FB4200 mov edx,Acid_bur.0042FBD8
0042FB11 |. A1 480A4300 mov eax,dword ptr ds:[0x430A48]
0042FB16 |. 8B00 mov eax,dword ptr ds:[eax]
0042FB18 |. E8 53A6FFFF call Acid_bur.0042A170
0042FB1D |. EB 18 jmp XAcid_bur.0042FB37 ; 强制跳转,跳过了错误显示。
0042FB1F |> 6A 00 push 0x0
0042FB21 |. B9 74FB4200 mov ecx,Acid_bur.0042FB74
0042FB26 |. BA 80FB4200 mov edx,Acid_bur.0042FB80
0042FB2B |. A1 480A4300 mov eax,dword ptr ds:[0x430A48]
0042FB30 |. 8B00 mov eax,dword ptr ds:[eax]
0042FB32 |. E8 39A6FFFF call Acid_bur.0042A170 ; 函数内调用了MessageBox显示错误
所以在0042FAFE
处设置断点,启动调试,注意将窗口最大化方便查看寄存器
这里可以看到,输入的注册码在寄存器中显示,同时上方还有一串字符,大胆猜测为计算出来后的注册码
尝试将输入的注册码替换为寄存器的,保留用户名
emmmm然后就得分析这个注册码怎么来的
就在0042FAFE
往上翻翻,给出的部分注释
0042FA79 |> \8D55 F0 lea edx,[local.4]
0042FA7C |. 8B83 DC010000 mov eax,dword ptr ds:[ebx+0x1DC]
0042FA82 |. E8 D1AFFEFF call Acid_bur.0041AA58 ; 获取账户,字符串,字符串首地址存储在EBP-0X10处
0042FA87 |. 8B45 F0 mov eax,[local.4]
0042FA8A |. 0FB600 movzx eax,byte ptr ds:[eax] ; DWORD DATA = (DWORD)buff[0];
0042FA8D |. F72D 50174300 imul dword ptr ds:[0x431750] ; DATA * 0x43175 == DATA * 0x29
0042FA93 |. A3 50174300 mov dword ptr ds:[0x431750],eax ; 0x431750 = DATA * 0x29;
0042FA98 |. A1 50174300 mov eax,dword ptr ds:[0x431750]
0042FA9D |. 0105 50174300 add dword ptr ds:[0x431750],eax ; 0x431750 *= 2
0042FAA3 |. 8D45 FC lea eax,[local.1]
0042FAA6 |. BA ACFB4200 mov edx,Acid_bur.0042FBAC
0042FAAB |. E8 583CFDFF call Acid_bur.00403708 ; 将CW字符串首地址存储在EBP-0x4处
0042FAB0 |. 8D45 F8 lea eax,[local.2]
0042FAB3 |. BA B8FB4200 mov edx,Acid_bur.0042FBB8 ; CRACKED
0042FAB8 |. E8 4B3CFDFF call Acid_bur.00403708 ; 将字符串地址存储在EBP-0X8处
0042FABD |. FF75 FC push [local.1]
0042FAC0 |. 68 C8FB4200 push Acid_bur.0042FBC8 ; UNICODE "-"
0042FAC5 |. 8D55 E8 lea edx,[local.6]
0042FAC8 |. A1 50174300 mov eax,dword ptr ds:[0x431750]
0042FACD |. E8 466CFDFF call Acid_bur.00406718 ; 将0x431750存储的十六进制转换成字符串,地址存储在EBP-0x18处
0042FAD2 |. FF75 E8 push [local.6]
0042FAD5 |. 68 C8FB4200 push Acid_bur.0042FBC8 ; UNICODE "-"
0042FADA |. FF75 F8 push [local.2]
0042FADD |. 8D45 F4 lea eax,[local.3]
0042FAE0 |. BA 05000000 mov edx,0x5
0042FAE5 |. E8 C23EFDFF call Acid_bur.004039AC ; 字符串拼接。(EBP-0X4)+(EBP-0X18)+(EBP-0X8) 存储在ebp-0xC
0042FAEA |. 8D55 F0 lea edx,[local.4]
0042FAED |. 8B83 E0010000 mov eax,dword ptr ds:[ebx+0x1E0]
0042FAF3 |. E8 60AFFEFF call Acid_bur.0041AA58 ; 得到输入的密码
0042FAF8 |. 8B55 F0 mov edx,[local.4]
0042FAFB |. 8B45 F4 mov eax,[local.3]
0042FAFE |. E8 F93EFDFF call Acid_bur.004039FC ; 输入的密码与程序计算出来的KEY做比较
0042FB03 |. 75 1A jnz XAcid_bur.0042FB1F ; 上一个CALL的返回值不等于0则跳转到错误消息
攻略上把这一段转换成C语言,得出结论:在计算KEY中,只使用了用户的第一个字母,后三位没用。所以,账户abcd和aaaa使用的KEY是相同的
void Decryption(char* mima)
{
char szBuff[260];
unsigned long data = (unsigned long)mima[0];
data *= 0X29;
data *= 2;
sprintf(szBuff, "CW-%d-CRACKED", data);
printf("%s \r\n", szBuff);
}
emmm之后还得研究
第二个按钮,操作类似,暂停之后,往上找到jnz
直接NOP就OK
继续往上看发现注册码是一个固定的值