所谓非明码就是软件没有在代码了直接写出与其计算出的序列号比较得出是否符合,而是通过一种绕弯的方式进行进行用户名与序列号比较,在OD或者某反汇编中难以找到正确的序列号而是通过一个等式进行判断用户是否输入正确:
K1(用户名) = K2(序列号)
在虚拟机中用OD打开例子程序serialt.exe,运行它、点Help/Register弹出一个对话框随便输入一个“Name :zhanghanyu123”,“123456789”、点击"OK"按钮弹出,在OD中点查找/所有匹配字符串、看这是一件捕获到你输入的Name和序列号了
双击这个地址来到关键代码处:
反汇编代码如下:
00401228 68 8E214000 push serialt.0040218E ; ASCII "ZHANGHANYU"
0040122D E8 4C010000 call serialt.0040137E ; 调用计算用户名得出一个数值
00401232 50 push eax
00401233 68 7E214000 push serialt.0040217E ; ASCII "123456789"
00401238 E8 9B010000 call serialt.004013D8 ; 调用计算一个输入的序列号得出数值
在0040122D这个地方下一个断点,重新跑程序,进入断点处,F7进入CALL调用的函数内部
具体分析如下:
0040137E 8B7424 04 mov esi,dword ptr ss:[esp+0x4] ; esi = 压入栈中的name存放地址
00401382 56 push esi ; 再次将esi压入栈中
00401383 8A06 mov al,byte ptr ds:[esi] ; esi的一个字节给al
00401385 84C0 test al,al ; 比较al是否为空
00401387 74 13 je short serialt.0040139C ; 空的那么就跳到0040139C地址处
00401389 3C 41 cmp al,0x41 ; 字符与41h相比较(与“A”相比较)
0040138B 72 1F jb short serialt.004013AC ; 如果小于A那么就不是字母跳到004013AC
0040138D 3C 5A cmp al,0x5A ; 字符再次与5AH比较(与“z”比较)
0040138F 73 03 jnb short serialt.00401394 ; al不低于5Ah(“z”ASCII值)就跳转00401394
00401391 46 inc esi ; esi+1指向下一个字符(字节)
00401392 ^ EB EF jmp short serialt.00401383 ; 无条件跳到00401383处执行循环
00401394 E8 39000000 call serialt.004013D2
00401399 46 inc esi ; esi+1指向下一个字符
0040139A ^ EB E7 jmp short serialt.00401383 ; 继续循环
0040139C 5E pop esi ; esi出栈
0040139D E8 20000000 call serialt.004013C2 ; 跳004013C2函数处
004013A2 81F7 78560000 xor edi,0x5678 ; edi的字符相加的值域5678h进行异或运算
004013A8 8BC7 mov eax,edi ; 得出的值给eax
004013AA EB 15 jmp short serialt.004013C1 ; 跳004013C1返回函数外部指向下一行代码
004013AC 5E pop esi
004013AD 6A 30 push 0x30
004013AF 68 60214000 push serialt.00402160 ; ASCII "Error! "
004013B4 68 69214000 push serialt.00402169 ; ASCII "Incorrect!,Try Again"
004013B9 FF75 08 push dword ptr ss:[ebp+0x8]
004013BC E8 79000000 call <jmp.&USER32.MessageBoxA> ; 错误信息提示超出范围
004013C1 C3 retn
004013C2 33FF xor edi,edi ; edi清0
004013C4 33DB xor ebx,ebx ; ebx清0
004013C6 8A1E mov bl,byte ptr ds:[esi] ; 拿出esi数据中一个字节给bl
004013C8 84DB test bl,bl ; 判断name中还有字符吗?
004013CA 74 05 je short serialt.004013D1 ; 没有则跳到004013D1返回
004013CC 03FB add edi,ebx ; 将ebx中的字符依次相加
004013CE 46 inc esi ; esi+1指向下一个字符(字节)
004013CF ^ EB F5 jmp short serialt.004013C6 ; 跳到004013C6循环次操作直到name字符没有为止
004013D1 C3 retn ; 返回004013A2处
004013D2 2C 20 sub al,0x20 ; al-20h 相当于吧小写字母变成大写字母
004013D4 8806 mov byte ptr ds:[esi],al ; 得到的大写字母值重新放入esi中
004013D6 C3 retn ; 返回00401399处
返回外部,执行下一行代码!
F7继续执行同理下面CALL也一样,进入CALL内部
代码如下
代码具体分析如下:
004013C2 33FF xor edi,edi ; edi清0
004013C4 33DB xor ebx,ebx ; ebx清0
004013C6 8A1E mov bl,byte ptr ds:[esi] ; 拿出esi数据中一个字节给bl
004013C8 84DB test bl,bl ; 判断name中还有字符吗?
004013CA 74 05 je short serialt.004013D1 ; 没有则跳到004013D1返回
004013CC 03FB add edi,ebx ; 将ebx中的字符依次相加
004013CE 46 inc esi ; esi+1指向下一个字符(字节)
004013CF ^ EB F5 jmp short serialt.004013C6 ; 跳到004013C6循环次操作直到name字符没有为止
004013D1 C3 retn ; 返回004013A2处
004013D2 2C 20 sub al,0x20 ; al-20h 相当于吧小写字母变成大写字母
004013D4 8806 mov byte ptr ds:[esi],al ; 得到的大写字母值重新放入esi中
004013D6 C3 retn ; 返回00401399处
004013D7 C3 retn
004013D8 33C0 xor eax,eax ; eax清0
004013DA 33FF xor edi,edi ; edi清0
004013DC 33DB xor ebx,ebx ; ebx清0
004013DE 8B7424 04 mov esi,dword ptr ss:[esp+0x4] ; 存放序列号的地址给esi
004013E2 B0 0A mov al,0xA ; al=0Ah
004013E4 8A1E mov bl,byte ptr ds:[esi] ; 将esi地址第一个字节(字符)给bl
004013E6 84DB test bl,bl ; bl是否为空(是否全部没有)
004013E8 74 0B je short serialt.004013F5 ; 相等则直接跳走进行异或运算
004013EA 80EB 30 sub bl,0x30 ; bl=bl-30h code[i]-30h
004013ED 0FAFF8 imul edi,eax ; edi=edi*0ah相当与左移1位
004013F0 03FB add edi,ebx ; 将每个字符计算出的数值加起来
004013F2 46 inc esi ; esi指向下一个字符 循环
004013F3 ^ EB ED jmp short serialt.004013E2
004013F5 81F7 34120000 xor edi,0x1234 ; 最后结果进行异或运算
004013FB 8BDF mov ebx,edi ; 值放入ebx中
004013FD C3 retn ; 返回CALL下一行代码
返回我们看见了核心的比较
代码分析如下:
00401241 3BC3 cmp eax,ebx ; eax(用户名的计算结果)是否等于ebx(序列号的计算结果)
00401243 74 07 je short serialt.0040124C ; 相等跳到0040124C执行CALL
00401245 E8 18010000 call serialt.00401362
0040124A ^ EB 9A jmp short serialt.004011E6
0040124C E8 FC000000 call serialt.0040134D
00401251 ^ EB 93 jmp short serialt.004011E6
0040134D具体如下:
{
0040134D 6A 30 push 0x30
0040134F 68 29214000 push serialt.00402129 ; ASCII "Good work!"
00401354 68 34214000 push serialt.00402134 ; ASCII "Great work!GOOD! Now try the next CrackMe!"
00401359 FF75 08 push dword ptr ss:[ebp+0x8]
0040135C E8 D9000000 call <jmp.&USER32.MessageBoxA> ; 执行注册信息成功的函数
}
分析后发现这个ebx和eax使我们主要注意的数据 只要计算结果ebx和eax相等就跳转注册成功的地方那么想办法让ebx和eax相等就行了
我做了如下修改:
我在计算name值函数里面讲xor edi,0x5678 修改成 imul edi,0x0h 这样edi等于0
同理 在序列号函数里面也修改成imul edi,0x0h
然后复制到可执行文件
在外部比较两个计算出的值相等
那么就转向注册成功!
当然你也可以直接将cmp ebx,eax 下面的je指令改为jne指令意思是不相等则跳转 直接转到注册成功!
同样可以成功!
分析完后发现这个例子程序就是利用这个等式进行软件注册的
K1(用户名) = K2(序列号)
完!