首页
社区
课程
招聘
再抓个CrackMe来玩玩代码还原。(只分析出算法还没想好KeyGen程序怎么写)
发表于: 2006-7-12 20:11 7339

再抓个CrackMe来玩玩代码还原。(只分析出算法还没想好KeyGen程序怎么写)

2006-7-12 20:11
7339

下载地址:http://www.crackmes.de/users/haggar/keyme1/

使用工具:
反编译及调试工具:OllyDbg,IDA,ResHacker
程序开发包:MASM32

1. 程序结构分析

  首先用OllyDbg载入程序。原程序在入口点之前有相当长一段花指令,如下所示:

=================================================================
00401000 > $ /EB 01         jmp     short 00401003
00401002     |E9            db      E9
00401003   > \EB 01         jmp     short 00401006
00401005      E9            db      E9
00401006   >  EB 01         jmp     short 00401009
00401008      E9            db      E9
00401009   >  EB 01         jmp     short 0040100C
0040100B      E9            db      E9
0040100C   >  EB 01         jmp     short 0040100F
0040100E      EB            db      EB
0040100F   >  EB 01         jmp     short 00401012
00401011      EB            db      EB
00401012   >  75 03         jnz     short 00401017
00401014   .  74 01         je      short 00401017
00401016      74            db      74                               ;  CHAR 't'
00401017   .  77 03         ja      short 0040101C
00401019   .  72 01         jb      short 0040101C
0040101B   .  75            db      75                               ;  CHAR 'u'
0040101C   >  75 03         jnz     short 00401021
0040101E   .  74 01         je      short 00401021
00401020      74            db      74                               ;  CHAR 't'
00401021   .  77 03         ja      short 00401026
00401023   .  72 01         jb      short 00401026
00401025   .  75            db      75                               ;  CHAR 'u'
00401026   >  75 03         jnz     short 0040102B
00401028   .  74 01         je      short 0040102B
0040102A      74            db      74                               ;  CHAR 't'
//以下代码略
=================================================================

这里跳转指令后跟字节数据显然是打算欺骗反汇编程序,但开头几个跳转都是绝对跳
转,用绝对跳转似乎做不了什么手脚。相比jnz/je组合可能在某些场合有效。另外有
疑问的地方是ja/jb组合,这个组合从语义上不等同于绝对跳转,当CF=0且ZF=1时它
是不跳的。不过这看来不是什么大问题,一来调试器中加载完毕后CF总是等于1,二
来不管执行的流程怎么跳,正常情况下总要跳到入口点执行,如果最后导致异常而退
出,那就是程序自己的错了。

  本着这个思想找到程序的第一句有用的代码是在地址401462处,而之前从401000
到这一句之前都是垃圾代码,可以用nop之类的“白色指令”刷掉。同样,从地址
401543到401B01(好长@_@)的部分也将其刷掉。把修改过的文件保存,再用IDA载入
进行分析。

  程序的入口点处代码如下:

=================================================================
                public start
start                proc near
                push        NULL                ; lpModuleName
                call        GetModuleHandleA
                mov        hInstance, eax
                mov        dword ptr szDllName, 72657375h
                mov        dword ptr szDllName+4, 642E3233h
                mov        dword ptr szDllName+8, 6C6Ch
                push        offset szDllName ; lpLibFileName
                call        LoadLibraryA        ; Load user32.dll
                mov        dword ptr szProcName, 636F6C42h
                mov        dword ptr szProcName+4,        706E496Bh
                mov        dword ptr szProcName+8,        7475h
                push        offset szProcName ; lpProcName
                push        eax                ; hModule
                call        GetProcAddress
                push        TRUE
                call        eax                ; BlockInput
                push        NULL                ; lpWindowName
                push        offset szODChrStr ; "OLLYDBG"
                call        FindWindowA
                cmp        eax, 0
                jz        short loc_4014EE
                push        MB_ICONWARNING        ; uType
                push        offset szCapODFound ; "Got you!"
                push        offset szTextODFound ; "Buahaha        ha ha ... Dude , what are you g"...
                push        NULL                ; hWnd
                call        MessageBoxA
                push        0                ; uExitCode
                call        ExitProcess

loc_4014EE:                                ; CODE XREF: start+4D2j
                mov        dword ptr szDllName, 72657375h
                mov        dword ptr unk_403200, 642E3233h
                mov        dword_403204, 6C6Ch
                push        offset szDllName ; lpLibFileName
                call        LoadLibraryA        ; Load user32.dll
                mov        szProcName, 636F6C42h
                mov        dword_403264, 706E496Bh
                mov        dword_403268, 7475h
                push        offset szProcName ; lpProcName
                push        eax                ; hModule
                call        GetProcAddress
                push        FALSE
                call        eax                ; BlockInput
                call        GetVersion
                cmp        al, 5
                jnz        short loc_401B8B
                mov        dword ptr szDllName, 6E72656Bh
                mov        dword ptr szDllName+4, 32336C65h
                mov        dword ptr szDllName+8, 6C6C642Eh
                mov        dword ptr szDllName+0Ch, 0
                push        offset szDllName ; lpLibFileName
                call        LoadLibraryA
                mov        dword ptr szProcName, 65447349h
                mov        dword ptr szProcName+4,        67677562h
                mov        dword ptr szProcName+8,        72507265h
                mov        dword ptr szProcName+0Ch, 6E657365h
                mov        dword ptr szProcName+10h, 74h
                push        offset szProcName ; lpProcName
                push        eax                ; hModule
                call        GetProcAddress
                call        eax                ; IsDebuggerPresent
                cmp        eax, 0
                jz        short loc_401B8B
                push        0                ; dwReserved
                push        EWX_LOGOFF        ; uFlags
                call        ExitWindowsEx

loc_401B8B:                                ; CODE XREF: start+B0Aj start+B80j
                push        0                ; dwInitParam
                push        offset DialogFunc ; lpDialogFunc
                push        NULL                ; hWndParent
                push        DLG_MAIN        ; lpTemplateName
                push        hInstance        ; hInstance
                call        DialogBoxParamA
                push        eax                ; uExitCode
                call        ExitProcess
start                endp
=================================================================

程序在建立主窗口之前有一些抗调试的动作,其大意为:

  (1) 先用BlockInput(TRUE)锁住键盘和鼠标。如果调试的过程中不小心跟踪执行
到这个语句,就会死机。(注:Ctrl+Alt+Del调出任务管理器还是可以响应的)

    (2) 用FindWindow("OLLYDBG",NULL)检测OllyDbg,若检测到就会退出程序。
(看雪版的OD把标题改成了OllyICE,这一检测随之无效了)检测完后用
BlockInput(FALSE)解锁。

  (3) 用IsDebuggerPresent()检测Ring3调试器,若检测到则用
ExitWindowsEx(EWX_LOGOFF, 0)注销当前用户。由于这是WinNT以上才有的功能,在
执行这段代码之前调用GetVersion(),如果返回值的al<5,就跳过这段执行。

  有意思的是程序中虽然用到user32.dll中的API,但user32.dll却不是初始化
时自动装入,而是程序中调用LoadLibrary装入的;装入时使用的参数"user32.dll"
也不是预定义的字符串,而是在代码中赋值给字符串变量的,看上去跟赋值给普通的
双字变量一样。由于在这里不需要动态装入DLL带来的好处,同时为了明白起见,在
还原的代码中改成常规装入方法,以便将BlockInput和IsDebuggerPresent的调用明
确地写出来。

  对话框的回调过程位于原程序地址401BAA处(去掉一大堆垃圾指令后,就会发现
其实它离入口点也不远),这个过程中只有对WM_COMMAND及WM_CLOSE的处理,其中
WM_COMMAND包含3个按钮的分支,按照按钮的标签分别取名为ID_CHECK、ID_ABOUT和
ID_EXIT。WM_CLOSE和WM_COMMAND的后两个分支都很简单,这里略去具体分析。而
ID_CHECK的分支则是我们主要关注的内容。

2.验证注册码算法分析

  “Check”按钮被按下时将执行如下代码:

=================================================================
loc_401BC7:                                ; CODE XREF: DialogFunc+14j
                cmp        eax, ID_CHECK
                jnz        short loc_401BE9
                push        30                ; nMaxCount
                push        offset szSerialString ;        lpString
                push        EDT_SERIAL        ; nIDDlgItem
                push        [ebp+hDlg]        ; hDlg
                call        GetDlgItemTextA
                call        sub_401C16
                jmp        short loc_401C10
=================================================================

其中跟进过程sub_401C16看一下:

=================================================================
sub_401C16        proc near                ; CODE XREF: DialogFunc+38p

var_8                = dword        ptr -8

                cmp        eax, 0
                jnz        short loc_401C26
                retn

loc_401C26:                                ; CODE XREF: sub_401C16+3j
                cmp        eax, 29                ; 序列号必须是29字符
                jz        short loc_401C3E
                retn

loc_401C3E:                                ; CODE XREF: sub_401C16+13j
                cmp        szSerialString+5, '-'
                jz        short loc_401C53
                retn

loc_401C53:                                ; CODE XREF: sub_401C16+2Fj
                cmp        szSerialString+11, '-'
                jz        short loc_401C6E
                retn

loc_401C6E:                                ; CODE XREF: sub_401C16+44j
                cmp        szSerialString+17, '-'
                jz        short loc_401C86
                retn

loc_401C86:                                ; CODE XREF: sub_401C16+5Fj
                cmp        szSerialString+23, '-'
                jz        short loc_401C9F
                retn

loc_401C9F:                                ; CODE XREF: sub_401C16+77j
                cmp        szSerialString+29, 0
                jz        short loc_401CB2
                retn

loc_401CB2:                                ; CODE XREF: sub_401C16+90j

                push        eax
                rdtsc
                push        eax
                rdtsc
                sub        eax, [esp]
                cmp        eax, 100h
                jb        short loc_401CDF

loc_401CDD:                                ; CODE XREF: sub_401C16:loc_401CDDj
                jmp        short loc_401CDD

loc_401CDF:                                ; CODE XREF: sub_401C16+C5j
                pop        eax
                pop        eax
                call        sub_401D35
                cmp        eax, 0
                jz        short loc_401CEF
                retn

loc_401CEF:                                ; CODE XREF: sub_401C16+D6j
                call        sub_401D72
                cmp        eax, 0
                jz        short loc_401CFA
                retn

loc_401CFA:                                ; CODE XREF: sub_401C16+E1j
                call        sub_401DAF
                cmp        eax, 0
                jz        short loc_401D05
                retn

loc_401D05:                                ; CODE XREF: sub_401C16+ECj
                call        sub_401DEC
                cmp        eax, 0
                jz        short loc_401D10
                retn

loc_401D10:                                ; CODE XREF: sub_401C16+F7j
                call        sub_401E29
                cmp        eax, 0
                jz        short loc_401D1B
                retn

loc_401D1B:                                ; CODE XREF: sub_401C16+102j
                call        sub_401E6B
                retn
sub_401C16        endp
=================================================================

这个过程中原本还有垃圾字节,现在所看到的是去掉垃圾字节后的版本。从开头几句
可以看出,注册码的格式应该是:

         XXXXX-XXXXX-XXXXX-XXXXX-XXXXX

下面还有一点零星的调试检测:求两次rdtsc指令的差值,这段代码正常执行的情形
一般不会超过100h个时钟周期,而在调试器中单步跟踪则十分缓慢,会远远超过这个
数值,因此如果检测到调试器或Loader,就进入一个jmp $的死循环。接下来就是验
证注册码了,注册码分5个小节,验证的时候是逐小节进行验证的,其算法都差不
多,以验证第一小节的过程为例:

=================================================================
sub_401D35        proc near                ; CODE XREF: sub_401C16+CEp
                xor        eax, eax
                xor        ebx, ebx
                movzx        eax, szSerialString
                movzx        ebx, szSerialString+1
                add        eax, ebx
                mul        ebx
                movzx        ebx, szSerialString+2
                add        eax, ebx
                mul        ebx
                movzx        ebx, szSerialString+3
                add        eax, ebx
                mul        ebx
                movzx        ebx, szSerialString+4
                add        eax, ebx
                mul        ebx
                xor        eax, 31CF15A9h
                retn
sub_401D35        endp
=================================================================

这个过程返回的时候检查eax的值,等于0时方为合法。也就是说:若把这个小节
记作abcde其中abcde为字符变量。则

  ((((a+b)*b+c)*c+d)*d+e)*e == 31CF15A9h

才是合法的。这里估计一下,ASCII码都在0到127之间,5个字符乘起来的结果最
大可能是2的35次方,eax已容纳不下。因此各字符的范围必须有所限制。这样,
字符e应该是31CF15A9h的一个因子,而31CF15A9h除以e后减去e得到的值必须有
另外一个能作为字符ASCII码的因子,依次类推。

  检验5个小节可以用一个统一的过程实现。这样就可以构建出整个程序的代
码了。

  上传文件内容:1.asm 还原的代码
                  1.rc  资源脚本


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 7
支持
分享
最新回复 (7)
雪    币: 47147
活跃值: (20455)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
2
转份本地
上传的附件:
2006-7-12 20:16
0
雪    币: 433
活跃值: (176)
能力值: ( LV13,RANK:1250 )
在线值:
发帖
回帖
粉丝
3
斑竹今天动作好快,谢谢哦
2006-7-12 20:19
0
雪    币: 461
活跃值: (93)
能力值: ( LV9,RANK:1170 )
在线值:
发帖
回帖
粉丝
4
好文章,学习!
2006-7-12 21:01
0
雪    币: 97697
活跃值: (200834)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
5
sustain.
2006-7-12 21:05
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
好文章,可惜我看不明白
2006-7-13 12:56
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
不错,收了,我喜欢
2006-7-13 15:26
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
下载试试,感谢楼主!
2006-7-18 22:42
0
游客
登录 | 注册 方可回帖
返回
//