第一次干掉 CrackMe && TraceMe

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

 

1. CrackMe

 

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

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

注意刚开始是无法看到右侧“序列号不对,重新再试一次”的字样的,需要查看内存地址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,会进入下方的恭喜提示

 

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

 

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下断。

 

这里的两个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的具体实现:

 

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 = 0
	var userNameIndex = 3
	var 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

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

 

 

 

你可能感兴趣的:(算法)