06 序列号保护 学习分析(字符串)
《加密与解密》学习小结
0x01 序列号产生方式
序列号保护机制一般情况下,我们所使用一个程序如果是要付费的话最常见的方法应该就是使用一个序列号即可,那么这个序列号总是明着(暗着)会提供一个(几个)特殊信息来对其特殊转换最终呈现为一个用于交易的注册码(这里的信息就是类似于用户名密码,或者是单纯的用户名,甚至是我们唯一的硬件上的识别码)一般的验证情况有以下几种:
这个方法就是把用户输入的注册码和此时返回得注册码做比较的,同时这个也可以是一个编写注册码的方法,先讨论为验证注册的情况
这种的话是比较不安全的一种方法,因为此时我们的序列号是一个明显的值(可能是字符串或是某个变量,此时这些信息都是在内存里面,或者是寄存器eax等,都是可以直接得到的)
我们现在讨论一下F为编写注册码的方法但是要求此时保证了函数F可逆,那么就会有:这样子的形式,此时就会相对安全一些,但是由于可逆,所以我们可以直接对F-1做分析直接得到一个注册机,或者是我们直接用一个用户名来穷举注册码,这样子会很花费时间,其实最简单的方法就是随机注册码然后直接使用返回的用户名为自己的用户名就可以了。
上面这两个方法的推广可以是:
这个方法破解的方法也和上面的类似
另外的方法:
这种的话对于写一个注册机就会有一点难度但是对于破解,我们可以直接对那个特定值做修改,让它永远都是那个特定值而不进行验证。
0x02 序列号保护突破口
一种方法是通过跟踪输入注册码之后的判断,从而找到注册码。一般都是在一个编辑框中输入注册码,软件需要调用一些标准的API将编辑框中输入的注册码字符串拷贝到自己的缓冲区中。利用调试器提供的针对API设断点的功能,就有可能找到判断注册码的地方。这些常用的API包括GetWindowTextA(W)、GetDlgItemTextA(W)、GetDlgItemInt等。程序注册完注册码后,一般显示一个对话框,告诉用户注册码是否正确,这也是一个切入点。显示对话框的常用API函数包括MessageBoxA(W)、MessageBoxExA(W)、
DialogBoxParamA(W)、CreateDialogIndirectParamA(W)、DialogBoxIndirectParamA(W)、CreateDialogParamA(W)、MessageBoxIndirectA(W)、ShowWindow等。
另外一种方法就是追踪程序启动时对注册码的判断,因为程序每次启动时都需要将注册码读出来加以判断,从而决定是否以注册版的模式工作。根据序列号的存放位置的不同,可以使用不同的API断点。如果序列号存放在注册表中,可以用RegQueryValueExA(W);如果序列号存放在INI文件中,可以用GetPrivateProfileStringA(W)、GetProfileStringA(W)、GetPrivateProfileIntA(W)、GetProfileIntA(W)等函数;如果序列号存放在一般的文件中,可以用CreateFileA(W)、_lopen()等函数。
0x03 OllydDBG分析寻找用户名注册码
直接点击“Register now !”,弹出窗口:
将错误提示字符串“Wrong Serial, try again!”作为突破口,在ollyDBG中进行分析:
在ollyDBG中选择“FIile"-->"open",选择要分析的目标文件:
打开之后按F9停到这里:
在反汇编窗口中右键,选择“Search for”-->"All reference text string" 来查找字符串:
在新弹出的出的窗口中,右键选择“Search for text”查找字符串:
在弹出的窗口中输入:
点击OK进行搜索:
在找到的字符串那一行右键选择在反汇编中跟随:
选择后,将鼠标放在对应行上,可以看到信息窗口显示出跳转来自何处:
F2在此处下一个断点:
F9运行程序,弹出输入用户名和注册码的窗口:在用户名栏中输入“SHReverse”:
点击“Register now !”,到达断点:
F8单步执行看到了输入的字符串:
选中信息窗口中的字符串,右键选择“Follow value in dump”跟随输入字符串:
此时在数据窗口和堆栈窗口中也能看到输入字符串了:
F8单步到此处:
F7进入CALL:
00403B2C /$ 53 PUSH EBX 00403B2D |. 56 PUSH ESI 00403B2E |. 57 PUSH EDI 00403B2F |. 89C6 MOV ESI,EAX //将之前放入EAX中的输入字符串放入ESI 00403B31 |. 89D7 MOV EDI,EDX //将之前放入EDX内中的字符串“Registered User”放入EDI 00403B33 |. 39D0 CMP EAX,EDX //比较两个字符串是否相同 00403B35 |. 0F84 8F000000 JE CrackMe3.00403BCA //相同则跳转 00403B3B |. 85F6 TEST ESI,ESI //判断ESI中是否有数据 00403B3D |. 74 68 JE SHORT CrackMe3.00403BA7//为空跳转 00403B3F |. 85FF TEST EDI,EDI 00403B41 |. 74 6B JE SHORT CrackMe3.00403BAE 00403B43 |. 8B46 FC MOV EAX,DWORD PTR DS:[ESI-4] //用户名长度放入EAX 00403B46 |. 8B57 FC MOV EDX,DWORD PTR DS:[EDI-4] // “Registered User”字串的长度放入EDX 00403B49 |. 29D0 SUB EAX,EDX //长度做差 00403B4B |. 77 02 JA SHORT CrackMe3.00403B4F //用户名长度大于“Registered User”长度则跳转 00403B4D |. 01C2 ADD EDX,EAX //把相减后的值与“Registered User”长度相加,即输入字符串长度 00403B4F |> 52 PUSH EDX 00403B50 |. C1EA 02 SHR EDX,2 //输入字符串长度(用户名)值右移2位,当于长度除以4 00403B53 |. 74 26 JE SHORT CrackMe3.00403B7B //长度如果小于4,跳转 00403B55 |> 8B0E MOV ECX,DWORD PTR DS:[ESI] //输入字符串放入ECX 00403B57 |. 8B1F |MOV EBX,DWORD PTR DS:[EDI] //将“Registered User”放入EBX 00403B59 |. 39D9 |CMP ECX,EBX //字符串比较 00403B5B |. 75 58 |JNZ SHORT CrackMe3.00403BB5 //不等则跳转(完蛋~) 00403B5D |. 4A |DEC EDX 00403B5E |. 74 15 |JE SHORT CrackMe3.00403B75 00403B60 |. 8B4E 04 |MOV ECX,DWORD PTR DS:[ESI+4] 00403B63 |. 8B5F 04 |MOV EBX,DWORD PTR DS:[EDI+4] 00403B66 |. 39D9 |CMP ECX,EBX 00403B68 |. 75 4B |JNZ SHORT CrackMe3.00403BB5 00403B6A |. 83C6 08 |ADD ESI,8 00403B6D |. 83C7 08 |ADD EDI,8 00403B70 |. 4A |DEC EDX 00403B71 |.^75 E2 \JNZ SHORT CrackMe3.00403B55 00403B73 |. EB 06 JMP SHORT CrackMe3.00403B7B 00403B75 |> 83C6 04 ADD ESI,4 00403B78 |. 83C7 04 ADD EDI,4 00403B7B |> 5A POP EDX 00403B7C |. 83E2 03 AND EDX,3 00403B7F |. 74 22 JE SHORT CrackMe3.00403BA3
有由上面的分析,可以得知用户名是“Registered User”。
用相同的分析方式,也可以得出注册码是“GFX-754-IER-954”,不在赘述。