这个CrackMe不是很难,但花去我不少时间,贴在这里和大家分享~
【文章标题】: hysteriaCRKME 破解+算法
【文章作者】: megadeath
【作者邮箱】: massacre@163.com
【作者QQ号】: 84227716
【软件名称】: hysteria_CRKME
【软件大小】: 266,240
【下载地址】: http://crackmes.de/users/haiklr/hysteria_crackme/
【加壳方式】: 无
【保护方式】: 无
【编写语言】: asm
【使用工具】: OllyICE
【操作平台】: WinXPsp2
【软件介绍】: 用户名+Keyfile
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
拿到CrackMe后,执行后只有一个文本框,在里面输入任意的用户名,然后点击Check,在文本框中提示Try harder !,显然注册失败,OK,PEiD检查没有壳没有保护,直接用OD加载,来到程序的入口点:
0040142F >/$ 6A 00 push 0 ; /pModule = NULL <--程序开始入口
00401431 |. E8 86080000 call <jmp.&kernel32.GetModuleHandleA> ; \GetModuleHandleA
00401436 |. A3 9C634000 mov dword ptr [40639C], eax
0040143B |. 6A 03 push 3 ; /RsrcName = 3.
0040143D |. 50 push eax ; |hInst
0040143E |. E8 37080000 call <jmp.&user32.LoadIconA> ; \LoadIconA
00401443 |. A3 A0634000 mov dword ptr [4063A0], eax
我们直接用串式参考搜索,发现有“congratz ! send me your keygen”字样,双击来到程序代码中:
00401C0F |. 68 A5614000 push 004061A5 ; /congratz ! send me your keygen
00401C14 |. 6A 08 push 8 ; |ControlID = 8
00401C16 |. FF75 08 push dword ptr [ebp+8] ; |hWnd
00401C19 |. E8 7A000000 call <jmp.&user32.SetDlgItemTextA> ; \SetDlgItemTextA
往上搜寻,这个函数很长一直到这个函数的开头部分:
00401925 /$ 55 push ebp
00401926 |. 8BEC mov ebp, esp
00401928 |. 57 push edi
00401929 |. 56 push esi
0040192A |. 53 push ebx
0040192B |. 90 nop
00401925 在两个地方被调用,00401736 和 00401698 ,在这两个地方下断点,然后F9运行,这时OD停在了00401736处,先不要进入,再F9运行,这时程序完全启动起来,显示出界面,如果这个时候点击Check后,OD停在了00401698 处。嗯,我们可以估计到在程序启动的时候加载了一次,以后每次点击Chcek按钮就都会调用一次00401925,我们再看一下在00401698 附近的代码:
0040166D . 837D 10 05 cmp dword ptr [ebp+10], 5
00401671 . 75 1C jnz short 0040168F
00401673 . 6A 00 push 0 ; /lParam = NULL
00401675 . 68 91174000 push 00401791 ; |DlgProc = Hysteria.00401791
0040167A . FF75 08 push dword ptr [ebp+8] ; |hOwner
0040167D . 6A 02 push 2 ; |pTemplate = 2
0040167F . FF35 9C634000 push dword ptr [40639C] ; |hInst = 00400000
00401685 . E8 BA050000 call <jmp.&user32.DialogBoxParamA> ; \DialogBoxParamA
0040168A . E9 C5000000 jmp 00401754
0040168F > 837D 10 06 cmp dword ptr [ebp+10], 6
00401693 . 75 0D jnz short 004016A2
00401695 . FF75 08 push dword ptr [ebp+8]
00401698 . E8 88020000 call 00401925
0040169D . E9 B2000000 jmp 00401754
004016A2 > 837D 10 07 cmp dword ptr [ebp+10], 7
004016A6 . 0F85 A8000000 jnz 00401754
004016AC . 6A 00 push 0 ; /lParam = 0
004016AE . 6A 00 push 0 ; |wParam = 0
004016B0 . 6A 10 push 10 ; |Message = WM_CLOSE
004016B2 . FF75 08 push dword ptr [ebp+8] ; |hWnd
004016B5 . E8 D2050000 call <jmp.&user32.SendMessageA> ; \SendMessageA
004016BA . E9 95000000 jmp 00401754
004016BF > 817D 0C 10010>cmp dword ptr [ebp+C], 110
004016C6 . 75 75 jnz short 0040173D
注意0040166D 、0040168F、004016A2这几行,在和5,6,7来比较,到这里能够看出一些端倪,其实这个是Windows回调函数中的对控件消息处理的switch语句,因此在00401698 调用也就是点击Check按钮的一个响应。我们在00401698 处下断点,在文本框中输入用户名,然后点击check按钮,这时OD拦截,进入00401698 函数,我们一点一点往下看:
00401925 /$ 55 push ebp
00401926 |. 8BEC mov ebp, esp
00401928 |. 57 push edi
00401929 |. 56 push esi
0040192A |. 53 push ebx
0040192B |. 90 nop
0040192C |. 90 nop
0040192D |. 90 nop
0040192E |. 90 nop
0040192F |. 90 nop
00401930 |. 90 nop
00401931 |. 90 nop
00401932 |. 90 nop
00401933 |. 6A 1E push 1E ; /Count = 1E (30.)
00401935 |. 68 50624000 push 00406250 ; |Buffer = Hysteria.00406250
0040193A |. 6A 08 push 8 ; |ControlID = 8
0040193C |. FF75 08 push dword ptr [ebp+8] ; |hWnd
0040193F |. E8 24030000 call <jmp.&user32.GetDlgItemTextA> ; \GetDlgItemTextA
00401944 |. 83F8 04 cmp eax, 4
00401947 |. 0F8C D3020000 jl 00401C20 ; 小于4就跳到错误处理
0040194D |. 83F8 18 cmp eax, 18
00401950 |. 0F8F CA020000 jg 00401C20 ; 大于0x18就跳到错误处理
00401956 |. 90 nop
00401957 |. 90 nop
00401958 |. 90 nop
00401959 |. 90 nop
0040195A |. 90 nop
0040195B |. 90 nop
0040195C |. 90 nop
0040195D |. A3 88634000 mov dword ptr [406388], eax
00401962 |. FF35 88634000 push dword ptr [406388] ; 将字符串长度压栈
00401968 |. E8 93F6FFFF call 00401000 ; 计算用户名的CRC32值
0040196D |. A3 69624000 mov dword ptr [406269], eax ; 将用户名的CRC32值放到[00426269]中
00401972 |. A0 50624000 mov al, byte ptr [406250] ; 取得用户名第一个字符放在CRC32的低8位中其余位不变
00401977 |. 50 push eax ; /<%x>
00401978 |. 68 00604000 push 00406000 ; |%x 拼接的字符串放在[00406000]中
0040197D |. 68 03604000 push 00406003 ; |s = Hysteria.00406003
00401982 |. E8 B1020000 call <jmp.&user32.wsprintfA> ; \wsprintfA
00401987 |. 83C4 0C add esp, 0C
0040198A |. 6A 00 push 0 ; /hTemplateFile = NULL
0040198C |. 6A 00 push 0 ; |Attributes = 0
0040198E |. 6A 03 push 3 ; |Mode = OPEN_EXISTING
00401990 |. 6A 00 push 0 ; |pSecurity = NULL
00401992 |. 6A 01 push 1 ; |ShareMode = FILE_SHARE_READ
00401994 |. 68 00000080 push 80000000 ; |Access = GENERIC_READ
00401999 |. 68 03604000 push 00406003 ; |FileName = "7ff2506d"
0040199E |. E8 07030000 call <jmp.&kernel32.CreateFileA> ; \CreateFileA
004019A3 |. 83F8 FF cmp eax, -1 ; 判断文件是否正常打开了
004019A6 |. 0F84 74020000 je 00401C20 ; 如果没有打开文件就跳转到00401c20处,即失败
004019AC |. A3 98634000 mov dword ptr [406398], eax
004019B1 |. 6A 00 push 0 ; /pOverlapped = NULL
004019B3 |. 68 94634000 push 00406394 ; |pBytesRead = Hysteria.00406394
004019B8 |. 68 FF000000 push 0FF ; |BytesToRead = FF (255.)
004019BD |. 68 6D624000 push 0040626D ; |Buffer = Hysteria.0040626D
004019C2 |. FF35 98634000 push dword ptr [406398] ; |hFile = FFFFFFFF
004019C8 |. E8 0D030000 call <jmp.&kernel32.ReadFile> ; \ReadFile
函数很长我们先看到这里,在00401968 处调用了在00401000的函数,这个函数调用其实是计算用户名的CRC32值,我们稍后再详细介绍这个函数,计算好的CRC32值在eax中返回,放到了[00426269]中,一会儿我们还要用到它,随后把用户名的第一个字符放在了CRC32值的低8位中,用这个数值的字符串作为文件名读取文件,这时我们知道这个CrackMe的注册需要Keyfile,并且Keyfile的名称我们已经能够构造出来了。读取文件的内容我们还不知道,明确知道的是文件读取的内容放在了0040626D,并且最长为0xFF个字符。我们停下OD,构造一个文件在CrackMe的目录下,填入足够长的测试数据,然后重新来过一次,断点定在004019CD 处。
004019CD |. 33D2 xor edx, edx
004019CF |. 33F6 xor esi, esi
004019D1 |. BB 01000000 mov ebx, 1
004019D6 |. A1 6D624000 mov eax, dword ptr [40626D] ; Keyfile中的字符串地址
004019DB |. A3 6C634000 mov dword ptr [40636C], eax ; 将Keyfile中的前0-3字符拷贝到[0040636c]中
004019E0 |. 83F8 00 cmp eax, 0 ; 是否没有字符串
004019E3 |. 0F84 37020000 je 00401C20 ; 如果为0的话就跳到错误提示
004019E9 |. A1 71624000 mov eax, dword ptr [406271]
004019EE |. A3 70634000 mov dword ptr [406370], eax
004019F3 |. 83F8 00 cmp eax, 0
004019F6 |. 0F84 24020000 je 00401C20
004019FC |. A1 75624000 mov eax, dword ptr [406275]
00401A01 |. A3 74634000 mov dword ptr [406374], eax
00401A06 |. 83F8 00 cmp eax, 0
00401A09 |. 0F84 11020000 je 00401C20
00401A0F |. A1 79624000 mov eax, dword ptr [406279]
00401A14 |. A3 78634000 mov dword ptr [406378], eax
00401A19 |. 83F8 00 cmp eax, 0
00401A1C |. 0F84 FE010000 je 00401C20
00401A22 |. A1 7D624000 mov eax, dword ptr [40627D]
00401A27 |. A3 7C634000 mov dword ptr [40637C], eax
00401A2C |. 83F8 00 cmp eax, 0
00401A2F |. 0F84 EB010000 je 00401C20
00401A35 |. A1 81624000 mov eax, dword ptr [406281]
00401A3A |. A3 80634000 mov dword ptr [406380], eax
00401A3F |. 83F8 00 cmp eax, 0
00401A42 |. 0F84 D8010000 je 00401C20 ; 从KeyFile字符串中拷贝了前24个字符
这一段是将Keyfile中的字符拷贝24个放到了[0040636C]中,拷贝了24个字符,因此Keyfile中最多出现的注册码也就是24个字符。我们把这个24个字符分为4个字符一组,共6组,如果每组的4个字符都转换为数值的话一组就是一个32位数,为什么要这么做,这个其实是到后面才知道的,这里先写出来,为了后面看得更清楚。记住这6组数值是从[0040636C]开始的,往下看:
00401A48 |. 90 nop
00401A49 |. 90 nop
00401A4A |. 90 nop
00401A4B |. A1 6C634000 mov eax, dword ptr [40636C]
00401A50 |. 3B05 74634000 cmp eax, dword ptr [406374]
00401A56 |. 75 11 jnz short 00401A69 ; 第0组Key值和第2组Key值不能相同
00401A58 |. A1 70634000 mov eax, dword ptr [406370]
00401A5D |. 3B05 78634000 cmp eax, dword ptr [406378]
00401A63 |. 0F84 B7010000 je 00401C20 ; 如果第0组Key值和第2组Key值相同了,那么第1组Key值和第3组Key值不能相同
00401A69 |> A1 6C634000 mov eax, dword ptr [40636C]
00401A6E |. 3B05 7C634000 cmp eax, dword ptr [40637C]
00401A74 |. 75 11 jnz short 00401A87 ; 第0组Key值和第4组Key值不能相同
00401A76 |. A1 70634000 mov eax, dword ptr [406370]
00401A7B |. 3B05 80634000 cmp eax, dword ptr [406380]
00401A81 |. 0F84 99010000 je 00401C20 ; 如果第0组Key值和第4组Key值相同了,那么第1组Key值和第5组Key值不能相同
00401A87 |> A1 74634000 mov eax, dword ptr [406374]
00401A8C |. 3B05 7C634000 cmp eax, dword ptr [40637C]
00401A92 |. 75 11 jnz short 00401AA5 ; 第2组Key值和第4组Key值不能相同
00401A94 |. A1 78634000 mov eax, dword ptr [406378]
00401A99 |. 3B05 80634000 cmp eax, dword ptr [406380]
00401A9F |. 0F84 7B010000 je 00401C20 ; 如果第2组Key值和第4组Key值相同了,那么第3组Key值和第5组Key值不能相同
00401AA5 |> A1 70634000 mov eax, dword ptr [406370]
00401AAA |. 3B05 78634000 cmp eax, dword ptr [406378]
00401AB0 |. 75 11 jnz short 00401AC3 ; 第1组Key值和第3组Key值不能相同
00401AB2 |. A1 6C634000 mov eax, dword ptr [40636C]
00401AB7 |. 3B05 74634000 cmp eax, dword ptr [406374]
00401ABD |. 0F84 5D010000 je 00401C20 ; 如果第1组Key值和第3组Key值相同了,那么第0组Key值和第2组Key值不能相同
00401AC3 |> A1 70634000 mov eax, dword ptr [406370]
00401AC8 |. 3B05 80634000 cmp eax, dword ptr [406380]
00401ACE |. 75 11 jnz short 00401AE1 ; 第1组Key值和第5组Key值不能相同
00401AD0 |. A1 6C634000 mov eax, dword ptr [40636C]
00401AD5 |. 3B05 7C634000 cmp eax, dword ptr [40637C]
00401ADB |. 0F84 3F010000 je 00401C20 ; 如果第1组Key值和第5组Key值相同了,那么第0组Key值和第4组Key值不能相同
00401AE1 |> A1 78634000 mov eax, dword ptr [406378]
00401AE6 |. 3B05 80634000 cmp eax, dword ptr [406380]
00401AEC |. 75 11 jnz short 00401AFF ; 第3组Key值和第5组Key值不能相同
00401AEE |. A1 74634000 mov eax, dword ptr [406374]
00401AF3 |. 3B05 7C634000 cmp eax, dword ptr [40637C]
00401AF9 |. 0F84 21010000 je 00401C20 ; 如果第3组Key值和第5组Key值相同了,那么第2组Key值和第4组Key值不能相同
这一段是比较复杂的一段,主要是Keyfile取得的6组数值的规则,花了好大一阵功夫写出来,再往下
00401AFF |> \A1 69624000 mov eax, dword ptr [406269] ; 用户名的CRC32值放到eax中
00401B04 |. B9 63000000 mov ecx, 63
00401B09 |. F7F9 idiv ecx ; 除以0x63以求余数
00401B0B |. 8915 8C634000 mov dword ptr [40638C], edx ; 余数放到[0040638c]中,记作MOD
00401B11 |. 81F2 07200000 xor edx, 2007
00401B17 |. 8915 90634000 mov dword ptr [406390], edx ; 异或0x2007后放到[00406390]中,记作XOR
没有什么好说的,继续
00401B1D |. 90 nop
00401B1E |. 90 nop
00401B1F |. 8B0D 8C634000 mov ecx, dword ptr [40638C] ; 刚才的MOD放到ecx中
00401B25 |. A1 6C634000 mov eax, dword ptr [40636C] ; 第1组Key值
00401B2A |. 2BC1 sub eax, ecx ; 用第1组Key值减去MOD,得到的值放到eax中
00401B2C |. 8B1D 70634000 mov ebx, dword ptr [406370] ; 第2组Key值
00401B32 |. 8B15 90634000 mov edx, dword ptr [406390] ; XOR赋值给edx
00401B38 |. 2BDA sub ebx, edx ; 用第二组值减去XOR,结果放到ebx中
00401B3A |. 0FAFC3 imul eax, ebx
00401B3D |. A3 84634000 mov dword ptr [406384], eax ; 把结果暂存到[00406384]中
00401B42 |. A1 6C634000 mov eax, dword ptr [40636C] ; 第0组Key值
00401B47 |. 8B0D 7C634000 mov ecx, dword ptr [40637C] ; 第4组Key值
00401B4D |. 2BC1 sub eax, ecx ; 第0组Key值 减去 第4组Key值,结果放到eax中
00401B4F |. 8B1D 70634000 mov ebx, dword ptr [406370] ; 第1组Key值
00401B55 |. 8B15 80634000 mov edx, dword ptr [406380] ; 第5组Key值
00401B5B |. 2BDA sub ebx, edx ; 第1组Key值 减去 第5组Key值,结果放到ebx中
00401B5D |. 0FAFC3 imul eax, ebx
00401B60 |. 0305 84634000 add eax, dword ptr [406384] ; 和刚才的结果相加
00401B66 |. 85C0 test eax, eax
00401B68 |. 0F85 B2000000 jnz 00401C20 ; 如果之和不为0则跳到错误的地方
到这里我们得到第一个公式,即:
(key[0] - MOD)*(key[1] - XOR) + (key[0] - key[4])*(key[1] - key[5]) = 0
往下看
00401B6E |. 8B0D 8C634000 mov ecx, dword ptr [40638C] ; MOD赋值给ecx
00401B74 |. A1 74634000 mov eax, dword ptr [406374] ; 第2组Key值
00401B79 |. 2BC1 sub eax, ecx ; 第2组Key值 减去 MOD,结果放到eax中
00401B7B |. 8B1D 78634000 mov ebx, dword ptr [406378] ; 第3组Key值
00401B81 |. 8B15 90634000 mov edx, dword ptr [406390] ; XOR赋值给edx
00401B87 |. 2BDA sub ebx, edx ; 第3组Key值 减去 XOR,结果放到ebx中
00401B89 |. 0FAFC3 imul eax, ebx
00401B8C |. A3 84634000 mov dword ptr [406384], eax ; 把结果暂存到[00406384]中
00401B91 |. A1 74634000 mov eax, dword ptr [406374] ; 第2组Key值
00401B96 |. 8B0D 7C634000 mov ecx, dword ptr [40637C] ; 第4组Key值
00401B9C |. 2BC1 sub eax, ecx ; 第2组Key值 减去 第4组Key值,结果放到eax中
00401B9E |. 8B1D 78634000 mov ebx, dword ptr [406378] ; 第3组Key值
00401BA4 |. 8B15 80634000 mov edx, dword ptr [406380] ; 第5组Key值
00401BAA |. 2BDA sub ebx, edx ; 第3组Key值 减去 第5组Key值,结果放到ebx中
00401BAC |. 0FAFC3 imul eax, ebx
00401BAF |. 0305 84634000 add eax, dword ptr [406384] ; 和刚才的结果相加
00401BB5 |. 85C0 test eax, eax
00401BB7 |. 75 67 jnz short 00401C20 ; 如果之和不为0则跳到错误的地方
到这里我们得到第二个公式,即:
(key[2] - MOD)*(key[3] - XOR) +(key[2] - key[4])*(key[3] - key[5]) = 0;
往下:
00401BB9 |. 8B0D 8C634000 mov ecx, dword ptr [40638C] ; MOD赋值给ecx
00401BBF |. A1 6C634000 mov eax, dword ptr [40636C] ; 第0组Key值
00401BC4 |. 2BC8 sub ecx, eax ; MOD 减去 第0组Key值,结果放到ecx中
00401BC6 |. 0FAFC9 imul ecx, ecx ; 结果乘平方
00401BC9 |. 8B15 90634000 mov edx, dword ptr [406390] ; XOR赋值给edx
00401BCF |. 8B1D 70634000 mov ebx, dword ptr [406370] ; 第1组Key值
00401BD5 |. 2BD3 sub edx, ebx ; XOR 减去 第1组Key值,结果放到edx中
00401BD7 |. 0FAFD2 imul edx, edx ; 结果乘平方
00401BDA |. 03CA add ecx, edx
00401BDC |. 890D 84634000 mov dword ptr [406384], ecx ; 得到的结果暂存
00401BE2 |. 8B0D 8C634000 mov ecx, dword ptr [40638C] ; MOD赋值给ecx
00401BE8 |. A1 74634000 mov eax, dword ptr [406374] ; 第2组Key值
00401BED |. 2BC8 sub ecx, eax ; MOD 减去 第2组Key值,结果放到ecx中
00401BEF |. 0FAFC9 imul ecx, ecx ; 结果乘平方
00401BF2 |. 8B15 90634000 mov edx, dword ptr [406390] ; XOR赋值给edx
00401BF8 |. 8B1D 78634000 mov ebx, dword ptr [406378] ; 第3组Key值
00401BFE |. 2BD3 sub edx, ebx ; XOR 减去 第3组Key值,结果放到edx中
00401C00 |. 0FAFD2 imul edx, edx ; 结果乘平方
00401C03 |. 03CA add ecx, edx
00401C05 |. 8B1D 84634000 mov ebx, dword ptr [406384]
00401C0B |. 3BCB cmp ecx, ebx
00401C0D |. 75 11 jnz short 00401C20 ; 如果两个表达式不相等就跳到错误的地方
00401C0F |. 68 A5614000 push 004061A5 ; /congratz ! send me your keygen
00401C14 |. 6A 08 push 8 ; |ControlID = 8
00401C16 |. FF75 08 push dword ptr [ebp+8] ; |hWnd
00401C19 |. E8 7A000000 call <jmp.&user32.SetDlgItemTextA> ; \SetDlgItemTextA
00401C1E |. EB 11 jmp short 00401C31
00401C20 |> 68 98614000 push 00406198 ; /try harder !
00401C25 |. 6A 08 push 8 ; |ControlID = 8
00401C27 |. FF75 08 push dword ptr [ebp+8] ; |hWnd
00401C2A |. E8 69000000 call <jmp.&user32.SetDlgItemTextA> ; \SetDlgItemTextA
00401C2F |. EB 00 jmp short 00401C31
00401C31 |> 5B pop ebx
00401C32 |. 5E pop esi
00401C33 |. 5F pop edi
00401C34 |. C9 leave
00401C35 \. C2 0400 retn 4
我们可以看到胜利的地方了,这段也能得到一个公式:
(MOD - key[0])*(MOD - key[0]) + (XOR - key[1])*(XOR - key[1]) = (MOD - key[2])*(MOD - key[2]) + (XOR - key[3])*(XOR - key[3])
整理一下,我们需要解个方程组
(key[0] - MOD)*(key[1] - XOR) + (key[0] - key[4])*(key[1] - key[5]) = 0 (1)
(key[2] - MOD)*(key[3] - XOR) + (key[2] - key[4])*(key[3] - key[5]) = 0 (2)
(MOD - key[0])*(MOD - key[0]) + (XOR - key[1])*(XOR - key[1]) = (MOD - key[2])*(MOD - key[2]) + (XOR - key[3])*(XOR - key[3]) (3)
初看上去这个方程组很难解开,实际上可以用一些巧的方法,我们如果让
key[0]=key[2]=key[4]=MOD 且 key[1]=key[3]=key[5]=XOR
的话,那么方程组就解开了,哦,不要高兴太早,在00401A4B处还有个规则,答案也不止一个,依照规则我们稍微改变一下就可以满足条件,这里列举了两组满足条件的解:
key[0] = MOD - 1 key[1] = XOR
key[2] = MOD key[3] = XOR - 1
key[4] = MOD - 1 key[5] = XOR - 1
或
key[0] = MOD key[1] = XOR - 1
key[2] = MOD + 1 key[3] = XOR
key[4] = MOD + 1 key[5] = XOR - 1
还有其他多个解,不过这里懒得计算了。
另外在00401968 处有个调用用来计算用户名的CRC32值,把这个函数调用再简单说一下
00401000 /$ 55 push ebp
00401001 |. 8BEC mov ebp, esp
00401003 |. 56 push esi
00401004 |. 51 push ecx
00401005 |. 52 push edx
00401006 |. 8D35 50624000 lea esi, dword ptr [406250] ; 存储用户名地址
0040100C |. 33D2 xor edx, edx ; edx清零
0040100E |. 83C8 FF or eax, FFFFFFFF ; eax初始化0xFFFFFFFF
00401011 |. 8B4D 08 mov ecx, dword ptr [ebp+8] ; ecx循环计数
00401014 |> 8A16 /mov dl, byte ptr [esi] ; 取出用户名的每个字符
00401016 |. 32D0 |xor dl, al ; 字符和al相异或
00401018 |. C1E8 08 |shr eax, 8 ; eax右移8位
0040101B |. 330495 2F1040>|xor eax, dword ptr [edx*4+40102F] ; 查表得值再和eax异或
00401022 |. 46 |inc esi
00401023 |. 49 |dec ecx
00401024 |.^ 75 EE \jnz short 00401014 ; 循环
00401026 |. F7D0 not eax ; eax取反
00401028 |. 5A pop edx
00401029 |. 59 pop ecx
0040102A |. 5E pop esi
0040102B |. C9 leave
0040102C \. C2 0400 retn 4
一上来看见初始化0xFFFFFFFF还有最后对eax取反,在单步循环之中每次查询0040102F处的数组来计算每个字符,一下就知道这个是在计算CRC,0xFFFFFFFF和结果求反这两步太明显了。
算法整理出来,写出注册机:
#include <iostream>
#include <string>
using namespace std;
int main(int, char **)
{
unsigned CRC32[256] = {0};
unsigned uiResult = 0;
for (unsigned ii = 0; ii < 256; ++ ii)
{
uiResult = ii;
for (unsigned jj = 0; jj < 8; ++ jj)
{
if (uiResult & 0x01)
uiResult = (uiResult >> 1) ^ 0xEDB88320;
else
uiResult >>= 1;
}
CRC32[ii] = uiResult;
}
string strName;
do
{
cout << "please input your name:" << endl;
cin >> strName ;
if ((4 <= strName.length()) && (0x18 >= strName.length()))
{
goto KeyGen;
}
} while(1);
KeyGen:
unsigned uiCRC = 0xFFFFFFFF;
unsigned uiNameLength = strName.length();
for (unsigned ii = 0; ii < uiNameLength; ++ ii)
{
uiResult = uiCRC;
uiResult = (uiResult & 0xFF)^((unsigned)strName[ii]);
uiCRC >>= 8;
uiCRC ^= (CRC32[uiResult]);
}
uiCRC = ~uiCRC;
unsigned uiMOD = uiCRC % 0x63;
unsigned uiXOR = uiMOD ^ 0x2007;
uiCRC &= 0xFFFFFF00;
uiCRC += (unsigned)strName[0];
char buffer[9] = {0};
sprintf(buffer, "%.8X", uiCRC);
unsigned Key[6] ={0};
//这里随意取一组
Key[0] = uiMOD - 1; Key[1] = uiXOR ;
Key[2] = uiMOD ; Key[3] = uiXOR - 1;
Key[4] = uiMOD - 1; Key[5] = uiXOR - 1;
// Key[0] = uiMOD ; Key[1] = uiXOR - 1;
// Key[2] = uiMOD + 1; Key[3] = uiXOR ;
// Key[4] = uiMOD + 1; Key[5] = uiXOR - 1;
FILE * fp = NULL;
fp = fopen(buffer, "wb");
if (NULL == fp)
return 0;
fwrite(Key, sizeof(unsigned), 6, fp);
fclose(fp);
fp = NULL;
cout << "KeyFile generated." << endl;
cout << "all right!" << endl;
cin.get();
return 0;
}
--------------------------------------------------------------------------------
【经验总结】
这个CrackMe不是明码比较而且也用了一点点数学方面的知识,虽然仍然能够通过爆破解决,但这个CrackMe是让我比较挠头的了,用了不少时间读程序,在计算公式的时候还把方程式(2)计算错了,费了不少时间却原地踏步,最后整理思路重新来过一遍才整理出来。
事实证明在大脑一片混乱事情一片狼藉的时候打个小盹呼吸点新鲜空气对自己是非常有帮助的。
光棍节快乐!
--------------------------------------------------------------------------------
【版权声明】: 转载请注明作者并保持文章的完整, 谢谢!
2007年11月11日 20:57:16
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)