读书人

第一回干掉 CrackMe amp;amp; TraceMe

发布时间: 2012-12-23 11:28:15 作者: rapoo

第一次干掉 CrackMe && TraceMe

这两个exe都是“加密与解密”书中“调试篇”的例子,CrackMe很简单,TraceMe稍微复杂一点。

?

1. CrackMe

?

分析CrackMe无需用OD打断点跟进,该程序逻辑非常简单,仅仅从汇编代码的调用上即可判断出其内部实现。

先用IDA进行CrackMe的反汇编,在最下方可以看到关于序列号的判断:

第一回干掉 CrackMe && TraceMe

注意刚开始是无法看到右侧“序列号不对,重新再试一次”的字样的,需要查看内存地址00403000,发现是一个字符串数组,手动转化为数组后才能看到这些汉字。

?

很显然,关于判断序列号是否正确的代码就是:

.text:004010BD???????????????? push??? edx?????????????????????? ; lpString2
.text:004010BE???????????????? push??? eax?????????????????????? ; lpString1
.text:004010BF???????????????? call??? ds:lstrcmpA
.text:004010C5???????????????? test??? eax, eax
.text:004010C7???????????????? push??? 0?????????????????????????? ; uType
.text:004010C9???????????????? jnz???? short loc_4010E8
.text:004010CB???????????????? push??? offset Caption?????? ; "OK!"
.text:004010D0???????????????? push??? offset Text??????????? ; "恭喜你!"
.text:004010D5???????????????? push??? 0?????????????????????????? ; hWnd
.text:004010D7???????????????? call??? ds:MessageBoxA

?

这里的序列号比对是将用户输入的序列号与预设的一个值进行比较,调用lstrcmpA函数。

如果eax = 1,则test语句执行后ZF = 0,会进入飘黄的跳转部分

如果eax = 0,则test语句执行后ZF = 1,会进入下方的恭喜提示

?

事实上,这里的序列号是以明文的方式写在程序中。

第一回干掉 CrackMe && TraceMe

?

dx中的“9981”就是正确的序列号。这个9981是直接写死在CrackMe.exe的数据区中,运行的时候再被加载到栈里边。

.data:00403034 byte_403034???? db? 39h ; 9
.data:00403035????? ???? ???? ???? ????? db? 39h ; 9
.data:00403036????? ???? ???? ???? ????? db? 38h ; 8
.data:00403037????? ???? ???? ???? ????? db? 31h ; 1
.data:00403038 byte_403038???? db 0

crack的办法已经很清楚了,可以将lstrcmpA的两个参数置为相同,比如都是edx,也可以将JNZ一句替换成NOP指令。

?

2.TraceMe

?

用OD给TraceMe.exe下断,刚开始的时候下的GetWindowTextA,后来几经ctrl-F9,才回到程序领空。

这里发现一个原来还不知道的调用,实际上在GetDlgItemTextA的内部,会调用GetWindowTextA。

程序内部 ----> GetDlgItemTextA ---> GetWindowTextA

因此可以直接对GetDlgItemTextA下断。

第一回干掉 CrackMe && TraceMe

?

这里的两个GetDlgItemTextA调用即是获取用户名、序列号。

?

004011CA?? .? 8A4424 4C????? ???? mov???? al, byte ptr [esp+4C]?????????????? ;取首字节判断用户名是否为空
004011CE?? .? 84C0???? ???? ???? ???? test??? al, al?????????????
004011D0?? .? 74 76???????? ???? ???? je????? short 00401248
004011D2?? .? 83FB 05???? ???? ???? cmp???? ebx, 5?????????????????????????????????????? ;判断用户名长度是否>=5
004011D5?? .? 7C 71???? ???? ??? ???? jl????? short 00401248
004011D7?? .? 8D5424 4C????? ???? lea???? edx, dword ptr [esp+4C]
004011DB?? .? 53??????????? ???? ? ???? push??? ebx
004011DC?? .? 8D8424 A00000>lea???? eax, dword ptr [esp+A0]
004011E3?? .? 52??????????? ?????? ???? push??? edx
004011E4?? .? 50??????????? ?????? ???? push??? eax
004011E5?? .? E8 56010000?? ??? call??? 00401340??????????????????????????????????? ;判断序列号是否真确

最关键的部分是call 00401340,这个函数用来验证用户输入的用户名与序列号是否匹配。

该函数调用之前需要3次压栈,可以大概看出来,这3个参数是(密码,用户名,用户名长度)

下面来看call 00401340的具体实现:

第一回干掉 CrackMe && TraceMe

?

ecx 相当于用户名byte数组的下标

eax 相当于密钥byte数组的下标

?

可以查看内存得知密钥是: 【 0C 0A 13 09 0C 0B 0A 08 】

看懂这段代码即可以很轻松的写出针对TraceMe的注册机。

?

大体上的加密算法为:

function validation(passWord,userName,length){var keyArray = [0x0c,0x0A,0x13,0x09,0x0c,0x0B,0x0A,0x08]var keyArrayIndex = 0var userNameIndex = 3var passWord2 = 0;while(length > userNameIndex) {if (keyArrayIndex > 7) {keyArrayIndex = 0}passWord2 += userName[userNameIndex]*keyArray[keyArrayIndex]userNameIndex++keyArrayIndex++}ToString(passWord2)if(passWord == passWord2){return true}else return false}

?

举个例子:如果用户名为 “abcde”,则序列号为

“d”×0C + “e”×0A = 100 × 12 + 101 ×10 = 2210

?

注意最后的几句:

0040138F? |.? FF15 04404000 call??? dword ptr [<&KERNEL32.lstrcmpA>] ; \lstrcmpA
00401395? |.? F7D8????????? neg???? eax
00401397? |.? 1BC0????????? sbb???? eax, eax
00401399? |.? 5F??????????? pop???? edi????????????????????????????? ;? USER32.GetDlgItemTextA
0040139A? |.? 5E??????????? pop???? esi
0040139B? |.? 40??????????? inc???? eax

0040139C? |.? 5D??????????? pop???? ebp
0040139D? \.? C3??????????? retn

strcmp的结果存放在eax中

只有eax为0的时候,才可以将eax设置为1(因为最后的返回结果也要放在eax中);如果strcmp的结果eax≠0,则要将eax设置为0。

这里用了

neg eax??????????????????? //求补 ,0->0 (CF=0) ,1-> -1(CF=1) ,-1>1(CF=1)

sbb eax,eax??????????? // SBB结果是 -CF

inc eax

这三句来完成,精妙无比。

?

?

?

读书人网 >编程

热点推荐