solly 发表于 2019-5-11 02:04

160 个 CrackMe 之 55 -- Elraizer.1 详细分析和追码过程

本帖最后由 solly 于 2019-5-11 13:53 编辑

160 个 CrackMe 之 55 -- Elraizer.1是 32 位原生CrackMe程序,首先检查一下文件信息:

没有加壳,节的情况如下:


这个CrackMe包括一共含9个子过程,有一个统一的入口列表,如下所示:
71131004   .CC            int3
71131005   $E9 F6070000   jmp   71131800                                    ;显示错误信息对话框
7113100A   .E9 71080000   jmp   71131880                                    ;NAG的DlgProc,有时间事件和修改内存指令的处理
7113100F   .E9 4C000000   jmp   71131060                                    ;WndProc,Windows消息处理回调函数
71131014   .E9 C7030000   jmp   711313E0                                    ;HookProc,键盘Hook的回调处理函数
71131019   .E9 82090000   jmp   711319A0                                    ;Timer回调函数
7113101E   .E9 2D010000   jmp   71131150                                    ;主窗口对话框的DlgProc,有对序列号进行处理的过程
71131023   $E9 B8040000   jmp   711314E0                                    ;注册窗口类
71131028   $E9 84050000   jmp   711315B1                                    ;WinMain()入口, 显示NAG和创建窗口,并调用WinMain2()
7113102D   .E9 49050000   jmp   7113157B                                    ; WinMain2(),创建主窗口对话框(主界面),由WinMain()调用
71131032      CC            int3

这些子过程的流程关系,如下图:

下面分别説明这些子过程:

一、WinMain()过程
该过程主要完成下面几个工作:
1),动态生成一部分反调试代码,但存在数据节,还没有写入代码节。
2),显示NAG对话框,也可以説是Splash屏,因为8秒后会自动关闭。
3),从指令节读取4字节的反调试代码数据,通过这个数据计算出WinMain2()子过程的入口地址并调用,如果这个代码被Crack,则地址计算错误,调用会失败。
4),计算主对话框的资源ID值并保存,在WinMain2()中用到这个ID值。
5),通过CreateWindowEx创建一个标准Windows窗口,但没有显示该窗口就退出CrackMe了。
具体每条代码分析,见下面代码:
; WinMain() 函数,程序入口
711315B1   >55            push    ebp                                       ;_WinMain()。入口主函数
711315B2   .8BEC          mov   ebp, esp
711315B4   .6A FF         push    -1
711315B6   .68 00701371   push    71137000                                    ;hInstance
711315BB   .68 841C1371   push    71131C84                                    ;SE 处理程序安装
711315C0   .64:A1 0000000>mov   eax, dword ptr fs:
711315C6   .50            push    eax
711315C7   .64:8925 00000>mov   dword ptr fs:, esp                     ; 安装新的 SEH
711315CE   .83C4 E0       add   esp, -20
711315D1   .53            push    ebx
711315D2   .56            push    esi
711315D3   .57            push    edi
711315D4   .8965 E8       mov   dword ptr , esp
; 初始化变量,主要是动态生成两组汇编指令,这里只生成了一部分,还有一部分在NAG的DlgProc中补充生成的。
; 另外, 这个变量是记录系统中是否驻留了SoftICE,并且初始化值0x0F386就表示SoftICE运行了,后面会通过动态生成的 int 68 指令修改其值(在主对话框的DlgProc中)。
711315D7   .C745 FC 00000>mov   dword ptr , 0
711315DE   .C605 27961371>mov   byte ptr , 0B4                  ;汇编指令1
711315E5   .66:C705 948A1>mov   word ptr , 0F386                  ;记录SoftICE调试状态的变量,初始化成 SoftICE 在运行,后面在主对话框的DlgProc中会修改,在HookProc中也会有影响
711315EE   .C605 10941371>mov   byte ptr , 0A3                  ;汇编指令2,第2组~共5字节,拼成 mov dword ptr , eax
711315F5   .C605 11941371>mov   byte ptr , 9C                     ;汇编指令2
711315FC   .C605 12941371>mov   byte ptr , 93                     ;汇编指令2
71131603   .C605 13941371>mov   byte ptr , 13                     ;汇编指令2
7113160A   .C605 14941371>mov   byte ptr , 71                     ;汇编指令2
71131611   .C605 24961371>mov   byte ptr , 90                     ;汇编指令1,第1组共8字节,分散在各处填充的,构成一个 int 68 中断,检测SoftICE,但 WinNT 4~10 系列不支持 int 68,会报错,运行不了。
71131618   .8B45 08       mov   eax, dword ptr                       ;hInstance
7113161B   .A3 18941371   mov   dword ptr , eax                   ;保存 hInstance
71131620   .C605 26961371>mov   byte ptr , 0C0                  ;汇编指令1
71131627   .C745 E4 655CF>mov   dword ptr , BFF55C65                ;修改传入参数的值, 0xBFF55C65
7113162E   .C605 29961371>mov   byte ptr , 0CD                  ;汇编指令1
; 显示 NAG 对话框,就是显示一张图片,8秒后自己关闭,应该算是一个 splash 封面吧,而且这个 NAG不是纯粹的显示,还有其它功能,在后面交待。
71131635   .6A 00         push    0                                           ; /lParam = NULL
71131637   .68 0A101371   push    7113100A                                    ; |DlgProc = ELRAIZER.7113100A
7113163C   .6A 00         push    0                                           ; |hOwner = NULL
7113163E   .68 E9030000   push    3E9                                       ; |pTemplate = 3E9
71131643   .8B0D 18941371 mov   ecx, dword ptr                    ; |
71131649   .51            push    ecx                                       ; |hInst => NULL
7113164A   .FF15 50A31371 call    dword ptr [<&USER32.DialogBoxParamA>]       ; \DialogBoxParamA
; 读取TimerProc中动态生成的汇编指令,读取其中4个字节(0x68CD43B4, 就是 mov ah, 0x43 / int 68 两条指令),作为计算基数,计算出另一个函数的地址,如果直接NOP掉前面的NAG显示代码,后面就会出错,无法调用函数。
71131650   .8D55 DC       lea   edx, dword ptr                      ;开始验证写入的汇编指令是否成功并且未被破解修改
71131653   .52            push    edx                                       ; /pBytesRead
71131654   .6A 04         push    4                                           ; |BytesToRead = 4
71131656   .8D45 E0       lea   eax, dword ptr                      ; |EAX===>保存4字节地址的缓冲区
71131659   .50            push    eax                                       ; |Buffer
7113165A   .68 FC121371   push    711312FC                                    ; |pBaseAddress = 711312FC
7113165F   .FF15 40A21371 call    dword ptr [<&KERNEL32.GetCurrentProcess>]   ; |[GetCurrentProcess
71131665   .50            push    eax                                       ; |hProcess
71131666   .FF15 34A21371 call    dword ptr [<&KERNEL32.ReadProcessMemory>]   ; \ReadProcessMemory
; 下面通过计算,生成一个地址值(0x7113157B),作为 call 的地址参数
7113166C   .8B4D E0       mov   ecx, dword ptr                      ;0x000000FF, 上面读的4字节内容,转换成后面的call指令的地址参数
7113166F   .81E1 FF000000 and   ecx, 0FF
71131675   .8B55 E1       mov   edx, dword ptr                      ;0x0000FF00,FF表示读取的数据的字节位置,下面几个也一样。
71131678   .81E2 FF000000 and   edx, 0FF
7113167E   .2BCA          sub   ecx, edx
71131680   .C1E1 18       shl   ecx, 18
71131683   .8B45 E1       mov   eax, dword ptr                      ;0x0000FF00
71131686   .25 FF000000   and   eax, 0FF
7113168B   .83E8 30       sub   eax, 30
7113168E   .C1E0 10       shl   eax, 10
71131691   .0BC8          or      ecx, eax
71131693   .8B55 E2       mov   edx, dword ptr                      ;0x00FF0000
71131696   .81E2 FF000000 and   edx, 0FF
7113169C   .81EA B8000000 sub   edx, 0B8
711316A2   .C1E2 08       shl   edx, 8
711316A5   .0BCA          or      ecx, edx
711316A7   .8B45 E3       mov   eax, dword ptr                      ;0xFF000000
711316AA   .25 FF000000   and   eax, 0FF
711316AF   .83C0 13       add   eax, 13
711316B2   .0BC8          or      ecx, eax
711316B4   .894D D8       mov   dword ptr , ecx                     ;合成下面的 call 调用地址,ecx = 0x7113157B 才是正确值
;下面这个全局变量,是非常重要的一个变量,会影响多个地方的计算,包括后面 SN 的计算,其值为 -1(0xFFFFFFFF) 才正确,其在NAG的DlgProc中会初始化。
711316B7   .8B0D 9C8A1371 mov   ecx, dword ptr                    ;ecx = -1, NAG的DlgProc中检查SoftIce的标志
;再次计算地址值
711316BD   .8B55 D8       mov   edx, dword ptr                      ;下面的 call 调用地址
711316C0   .8D444A 02   lea   eax, dword ptr                 ;eax = 0x7113157B + (-1)*2 + 2 = 0x7113157B
711316C4   .A3 FC931371   mov   dword ptr , eax                   ;保存下面调用的地址(0x7113157B)
711316C9   .8B0D 9C8A1371 mov   ecx, dword ptr                    ;ecx = -1
711316CF   .8D9409 EA0300>lea   edx, dword ptr                 ;计算得到主对话的框的资源ID,edx = (-1) + (-1) + 0x03EA = 0x03E8
711316D6   .66:8915 08941>mov   word ptr , dx                     ;dx=0x03E8=1000,主对话的框的资源ID
711316DD   .FF15 FC931371 call    dword ptr                         ;call _WinMain2(), ==7113157B
;输入 SN 并按"Verify ?"按钮后,如果记录的 SN 正确则来到这里,否则会出错退出。
711316E3   .6A 00         push    0                                           ; /lParam = NULL
711316E5   .A1 18941371   mov   eax, dword ptr                    ; |hInstance
711316EA   .50            push    eax                                       ; |hInst => NULL
711316EB   .6A 00         push    0                                           ; |hMenu = NULL
711316ED   .6A 00         push    0                                           ; |hParent = NULL
711316EF   .68 96000000   push    96                                          ; |Height = 96 (150.)
711316F4   .68 F4010000   push    1F4                                       ; |Width = 1F4 (500.)
711316F9   .6A 00         push    0                                           ; |Y = 0
711316FB   .6A 00         push    0                                           ; |X = 0
711316FD   .68 0000CF90   push    90CF0000                                    ; |Style = WS_POPUP|WS_MINIMIZEBOX|WS_MAXIMIZEBOX|WS_VISIBLE|WS_SYSMENU|WS_THICKFRAME|WS_CAPTION
71131702   .68 388B1371   push    71138B38                                    ; |WindowName = "ATPTeam CrackMe zer0"
71131707   .68 E88A1371   push    71138AE8                                    ; |Class = "_#_"
7113170C   .68 00000200   push    20000                                       ; |ExtStyle = WS_EX_STATICEDGE
71131711   .FF15 4CA31371 call    dword ptr [<&USER32.CreateWindowExA>]       ; \CreateWindowExA
71131717   .8B0D E0931371 mov   ecx, dword ptr                    ; == 0,作为本函数返回值
7113171D   .894D D4       mov   dword ptr , ecx                     ;保存返回值
71131720   .C745 FC FFFFF>mov   dword ptr , -1
71131727   .8B45 D4       mov   eax, dword ptr                      ;取得返回值
7113172A   .EB 1F         jmp   short 7113174B                              ;没有错误则跳入正常返回代码
7113172C   .8B55 EC       mov   edx, dword ptr
7113172F   .52            push    edx
71131730   .E8 D0F8FFFF   call    71131005                                    ;显示创建窗口错误
71131735   .C3            retn
71131736   .8B65 E8       mov   esp, dword ptr
71131739   .A1 E0931371   mov   eax, dword ptr
7113173E   .8945 D0       mov   dword ptr , eax
71131741   .C745 FC FFFFF>mov   dword ptr , -1
71131748   .8B45 D0       mov   eax, dword ptr
7113174B   >8B4D F0       mov   ecx, dword ptr                      ;取得原SEH
7113174E   .64:890D 00000>mov   dword ptr fs:, ecx                     ;恢复 SEH
71131755   .5F            pop   edi
71131756   .5E            pop   esi
71131757   .5B            pop   ebx
71131758   .8BE5          mov   esp, ebp
7113175A   .5D            pop   ebp
7113175B   .C2 1000       retn    10
7113175E      CC            int3
7113175F      CC            int3


二、NAG 的 DlgProc 过程
该子过程主要完成以下工作:
1)、补充WinMain()中还没有生成的4个字节的汇编指令,用于反调试;
2)、通过 CreateFile("\\\\.\\FrogSICE")来检查是否 SoftICE 调试器在运行,并通过检查结果初始化一个重要的全局变量,这个变量等于 -1 才是正确值;
3)、创建一个定时器,间隔为1000ms。
如果SoftICE和FrogSICE在内存中加载,则GetLastError会返回0,则全局变量会初始化为一个不正确的值,当前过程不会有问题,但其它过程会引用该值,会引起内存访问错误。
NAG的界面如下:

具体代码分析如下:
; NAG 对话框的 DlgProc,主要功能就是检测SoftICE并初始化一个非常重要的全局变量,同时生成定时器。
71131880/>55            push    ebp                                       ;NAG对话框DlgProc
71131881|.8BEC          mov   ebp, esp
71131883|.83EC 08       sub   esp, 8
71131886|.8B45 0C       mov   eax, dword ptr
71131889|.8945 F8       mov   dword ptr , eax
7113188C|.837D F8 10    cmp   dword ptr , 10                     ;WM_CLOSE
71131890|.0F84 96000000 je      7113192C
71131896|.817D F8 10010>cmp   dword ptr , 110                      ;WM_INITDIALOG
7113189D|.74 05         je      short 711318A4
7113189F|.E9 A8000000   jmp   7113194C
711318A4|>C605 2A961371>mov   byte ptr , 68                     ;汇编指令1,补充填充用的汇编代码
711318AB|.8B4D 08       mov   ecx, dword ptr
711318AE|.890D 04941371 mov   dword ptr , ecx
711318B4|.C605 2B961371>mov   byte ptr , 90                     ;汇编指令1,补充填充用的汇编代码
; 以下代码是检查 FrogSICE是否在运行,并按返回结果初始化全局变量
711318BB|.6A 00         push    0                                           ; /hTemplateFile = NULL
711318BD|.68 00000004   push    4000000                                     ; |Attributes = DELETE_ON_CLOSE
711318C2|.6A 03         push    3                                           ; |Mode = OPEN_EXISTING
711318C4|.6A 00         push    0                                           ; |pSecurity = NULL
711318C6|.6A 03         push    3                                           ; |ShareMode = FILE_SHARE_READ|FILE_SHARE_WRITE
711318C8|.68 000000C0   push    C0000000                                    ; |Access = GENERIC_READ|GENERIC_WRITE
711318CD|.8B15 988A1371 mov   edx, dword ptr                    ; |ELRAIZER.71138AD8
711318D3|.52            push    edx                                       ; |FileName => "\\.\FROGSICE"
711318D4|.FF15 48A21371 call    dword ptr [<&KERNEL32.CreateFileA>]         ; \CreateFileA
711318DA|.8945 FC       mov   dword ptr , eax
711318DD|.C605 28961371>mov   byte ptr , 43                     ;汇编指令1,补充填充用的汇编代码
711318E4|.FF15 44A21371 call    dword ptr [<&KERNEL32.GetLastError>]      ; [GetLastError
711318EA|.8B0D 9C8A1371 mov   ecx, dword ptr                    ;调试标志:0x1F,需变成0xFFFFFFFF
711318F0|.33C8          xor   ecx, eax                                    ;eax==2, 表示“文件未找到”的错误
711318F2|.83E9 1E       sub   ecx, 1E                                     ;ecx=0x1D-0x1E=0xFFFFFFFF
711318F5|.890D 9C8A1371 mov   dword ptr , ecx                   ;调试标志:0x1F,在这里变成0xFFFFFFFF
; 关闭句柄
711318FB|.837D FC FF    cmp   dword ptr , -1
711318FF|.74 0A         je      short 7113190B
71131901|.8B55 FC       mov   edx, dword ptr
71131904|.52            push    edx                                       ; /hChange
71131905|.FF15 50A21371 call    dword ptr [<&KERNEL32.CloseHandle>]         ; \FindCloseChangeNotification
; 补充填充用的汇编代码
7113190B|>C605 25961371>mov   byte ptr , 33                     ;汇编指令1,补充填充用的汇编代码
; 生成定时器,在定时器中填充主Dlg的汇编代码
71131912|.68 19101371   push    71131019                                    ; /Timerproc = ELRAIZER.71131019
71131917|.68 E8030000   push    3E8                                       ; |Timeout = 1000 ms
7113191C|.6A 01         push    1                                           ; |TimerID = 1
7113191E|.A1 04941371   mov   eax, dword ptr                    ; |
71131923|.50            push    eax                                       ; |hWnd => NULL
71131924|.FF15 40A31371 call    dword ptr [<&USER32.SetTimer>]            ; \SetTimer
7113192A|.EB 24         jmp   short 71131950
; WM_CLOSE处理,消毁定时器,并关闭NAG对话框
7113192C|>6A 01         push    1                                           ; /TimerID = 1
7113192E|.8B0D 04941371 mov   ecx, dword ptr                    ; |
71131934|.51            push    ecx                                       ; |hWnd => NULL
71131935|.FF15 44A31371 call    dword ptr [<&USER32.KillTimer>]             ; \KillTimer
7113193B|.6A 01         push    1                                           ; /Result = 1
7113193D|.8B15 04941371 mov   edx, dword ptr                    ; |
71131943|.52            push    edx                                       ; |hWnd => NULL
71131944|.FF15 68A31371 call    dword ptr [<&USER32.EndDialog>]             ; \EndDialog
7113194A|.EB 04         jmp   short 71131950
7113194C|>33C0          xor   eax, eax
7113194E|.EB 05         jmp   short 71131955
71131950|>B8 01000000   mov   eax, 1
71131955|>8BE5          mov   esp, ebp
71131957|.5D            pop   ebp
71131958\.C2 1000       retn    10
7113195B      CC            int3


三、定时器回调过程(Callback Proc)
该过程完成以下工作:
1)将前面写入数据节的两段代码,调用WriteProcessMemory函数写入代码节,将 CrackMe的原有NOP代码和错误代码覆盖,不覆盖会引起非法内存访问错误;代码是分成8次写入的,每次只写1个字节
2)8次时间事件后,会发送WM_CLOSE关闭NAG;
3)计算代码执行时间,用于反单步调试,超时则退出,会导致代码写入不完整;
这里将数据区的代码数据写入代码区,这些代码是用于反 SoftICE 调试的,对目前用的OD没什么用,但其中一条 int 68 指令在Windows NT/2000/XP/8/10 下是不支持的,会导致程序异常,报错并退出,错误如下:

西班牙文,大概意思是:“如果你看到了这条信息,那是因为程序代码被篡改了,或者你录入了一段非常糟糕的代码;)(或者加载了SoftICE / FrogSICE............ )...因此你造成了“一般保护性错误”!!! ...”,谷~歌~翻译的。
因此,这条 int 68 指令要处理,但WinMain()中又会读取这条指令数据来计算WinMain2()的入口地址,如何修改,在后面再交待,现在先不管。。。
如果直接NOP掉NAG显示代码,就不会有这些代码填充,但在调试器中会引起类似如下的错误:

具体代码分析如下:
; Timer Proc 定时器回调函数,完成主对话框DlgProc中的汇编的填充,不然主对话框DlgProc会报非法内存访问的错误
; 本函数共执行8次,分成两个填充过程,一个填充5字节,一个填充8字节,一起完成代码的填充。
711319A0/>55            push    ebp                                       ;Timer回调函数
711319A1|.8BEC          mov   ebp, esp
711319A3|.8B45 14       mov   eax, dword ptr                      ;Timer回调TimerProc时的系统时间
711319A6|.A3 B0961371   mov   dword ptr , eax                   ;保存时间,用于检测单步调试
; 开始代码填充
711319AB|.8B0D 9C8A1371 mov   ecx, dword ptr                    ;取到-1才正确,否则就是检测到了softice或单步调试
711319B1|.83C1 01       add   ecx, 1                                    ;ecx = 0
711319B4|.6BC9 03       imul    ecx, ecx, 3                                 ;ecx = 0
711319B7|.81C9 F4121371 or      ecx, 711312F4                               ;711312F4是需要填充汇编指令2的位置(5个字节)
711319BD|.030D C4961371 add   ecx, dword ptr                    ;加上偏移量,也是个计数器,每进一次时间事件,加1
711319C3|.890D BC961371 mov   dword ptr , ecx                   ;保存地址
; 下面填充8字节,分成8次填充完成,每次填充1字节
711319C9|.8B15 9C8A1371 mov   edx, dword ptr                    ;取到-1才正确,否则就是检测到了softice或单步调试
711319CF|.83C2 01       add   edx, 1                                    ;edx = 0
711319D2|.6BD2 03       imul    edx, edx, 3                                 ;edx = 0
711319D5|.81CA F9121371 or      edx, 711312F9                               ;711312F9是需要填充汇编指令1的位置(8个字节)
711319DB|.0315 C0961371 add   edx, dword ptr                    ;加上偏移量,也是个计数器,每进一次时间事件,加1
711319E1|.8915 B4961371 mov   dword ptr , edx                   ;保存地址
711319E7|.A1 B8961371   mov   eax, dword ptr                    ;写指令,每次写一字节
711319EC|.50            push    eax                                       ; /pBytesWritten => NULL
711319ED|.6A 01         push    1                                           ; |BytesToWrite = 1
711319EF|.8B0D C0961371 mov   ecx, dword ptr                    ; |偏移量
711319F5|.81C1 24961371 add   ecx, 71139624                               ; |数据来源,在WinMain中生成
711319FB|.51            push    ecx                                       ; |Buffer
711319FC|.8B15 B4961371 mov   edx, dword ptr                    ; |
71131A02|.52            push    edx                                       ; |Address => 0
71131A03|.FF15 40A21371 call    dword ptr [<&KERNEL32.GetCurrentProcess>]   ; |[GetCurrentProcess
71131A09|.50            push    eax                                       ; |hProcess
71131A0A|.FF15 4CA21371 call    dword ptr [<&KERNEL32.WriteProcessMemory>]; \WriteProcessMemory
; 下面填充5字节,分成5次填充完成,每次填充1字节
71131A10|.833D C4961371>cmp   dword ptr , 5                     ;下面只写5字节指令,超过5则跳过
71131A17|.7D 29         jge   short 71131A42
71131A19|.A1 B8961371   mov   eax, dword ptr                    ;写指令,每次写一字节
71131A1E|.50            push    eax                                       ; /pBytesWritten => NULL
71131A1F|.6A 01         push    1                                           ; |BytesToWrite = 1
71131A21|.8B0D C4961371 mov   ecx, dword ptr                    ; |
71131A27|.81C1 10941371 add   ecx, 71139410                               ; |
71131A2D|.51            push    ecx                                       ; |Buffer
71131A2E|.8B15 BC961371 mov   edx, dword ptr                    ; |取得前面生成的地址(在 0x711319C3处保存的地址)
71131A34|.52            push    edx                                       ; |Address => 0
71131A35|.FF15 40A21371 call    dword ptr [<&KERNEL32.GetCurrentProcess>]   ; |[GetCurrentProcess
71131A3B|.50            push    eax                                       ; |hProcess
71131A3C|.FF15 4CA21371 call    dword ptr [<&KERNEL32.WriteProcessMemory>]; \WriteProcessMemory
; 下面是写入次数的处理,达到8次数后就发送关闭NAG的消息
71131A42|>A1 C0961371   mov   eax, dword ptr
71131A47|.83C0 01       add   eax, 1                                    ;计数器1 + 1
71131A4A|.A3 C0961371   mov   dword ptr , eax
71131A4F|.8B0D C4961371 mov   ecx, dword ptr
71131A55|.83C1 01       add   ecx, 1                                    ;计数器2 + 1
71131A58|.890D C4961371 mov   dword ptr , ecx
71131A5E|.833D C0961371>cmp   dword ptr , 7                     ;判断写了多少节字,少于8字节则继续写,也就是继续进入时间事件
71131A65|.7E 13         jle   short 71131A7A                              ;大于7则关闭NAG,停止填充汇编代码
71131A67|.6A 00         push    0                                           ; /lParam = 0
71131A69|.6A 00         push    0                                           ; |wParam = 0
71131A6B|.6A 10         push    10                                          ; |Message = WM_CLOSE
71131A6D|.8B15 04941371 mov   edx, dword ptr                    ; |
71131A73|.52            push    edx                                       ; |hWnd => NULL
71131A74|.FF15 54A31371 call    dword ptr [<&USER32.SendMessageA>]          ; \SendMessageA
; 反调试代码,防止单步调试
71131A7A|>FF15 3CA21371 call    dword ptr [<&KERNEL32.GetTickCount>]      ; [取当前时间
71131A80|.2B05 B0961371 sub   eax, dword ptr                    ;取得时间差
71131A86|.83F8 07       cmp   eax, 7                                    ;大于7ms则表示在调试,Post一个退出消息,关闭NAG,会导致代码填充不完整等问题
71131A89|.76 12         jbe   short 71131A9D
71131A8B|.6A 00         push    0                                           ; /lParam = 0
71131A8D|.6A 00         push    0                                           ; |wParam = 0
71131A8F|.6A 10         push    10                                          ; |Message = WM_CLOSE
71131A91|.A1 04941371   mov   eax, dword ptr                    ; |
71131A96|.50            push    eax                                       ; |hWnd => NULL
71131A97|.FF15 58A31371 call    dword ptr [<&USER32.PostMessageA>]          ; \PostMessageA
71131A9D|>5D            pop   ebp
71131A9E\.C2 1000       retn    10
71131AA1      CC            int3
71131AA2      CC            int3
71131AA3      CC            int3


四、WinMain2()过程
这个子过程相对简单,功能如下:
1)显示主界面对话框;
2)根据主对框的返回值,决定是返回WinMain(),还是报错并退出
具体代码分析如下:
; WinMain2(),由WinMain()调用,用来显示主界面 DialogBox,进行下一步的 SN 录入及验证。
7113157B/>55            push    ebp                                       ;_WinMain2(),显示主对话框
7113157C|.8BEC          mov   ebp, esp
7113157E|.6A 00         push    0                                           ; /lParam = NULL
71131580|.68 1E101371   push    7113101E                                    ; |DlgProc = ELRAIZER.7113101E
71131585|.6A 00         push    0                                           ; |hOwner = NULL
71131587|.33C0          xor   eax, eax                                    ; |
71131589|.66:A1 0894137>mov   ax, word ptr                      ; |=1000(这是主对话框资源ID,是在WinMain()中计算出来的,见0x711316D6处指令)
7113158F|.50            push    eax                                       ; |pTemplate
71131590|.8B0D 18941371 mov   ecx, dword ptr                    ; |
71131596|.51            push    ecx                                       ; |hInst => NULL
71131597|.FF15 50A31371 call    dword ptr [<&USER32.DialogBoxParamA>]       ; \DialogBoxParamA, 显示主对话框
7113159D|.83F8 FF       cmp   eax, -1
711315A0|.75 0D         jnz   short 711315AF
711315A2|.68 EC8A1371   push    71138AEC                                    ; /MessageText = "Error during creation of window ! Try to reload program ...."
711315A7|.6A 00         push    0                                           ; |Action = 0
711315A9|.FF15 30A21371 call    dword ptr [<&KERNEL32.FatalAppExitA>]       ; \FatalAppExitA
711315AF|>5D            pop   ebp
711315B0\.C3            retn


五、主对话框 DlgProc 过程
这是该 CrackMe 的主要部分,这部分会Hook键盘输入进行SN的录入,还会通过SN计算 一个WndProc地址,用于注册窗口类,具体功能如下:
1)在WM_INITDIALOG消息处理过程中,创建一个了线程的键盘Hook,记录所有的键盘操作,包括功能键的操作;
2)在按钮“Verify ?”的Click()事件中,对记录下的键码进行计算,生成一个地址,用于后面窗口类的注册,同时,发WM_CLOSE关闭主界面
3)在关闭对话框时,会取消键盘Hook,并调用注册窗口类的一个过程。
4)前面説的动态生成代码,就是写入到这个过程的代码中,见下面代码分析,从 0x711312F4处开始的代码,共13字节代码是后期生成的。这些代码是用于反调试的,其中 int 68指令在目前的NT系列内核的系统中是不能运行的,会导致异常并退出。
这里会对动态生成的代码如何破解进行説明,并在下面代码分析中交待。
主对话框界面如下:

具体代码分析如下:
; 主对话框 DlgProc,进行 SN 验证计算,其结果作为 WndProc 地址,进行窗口注册。
71131150/>55            push    ebp                                       ;主对话框的 DlgProc
71131151|.8BEC          mov   ebp, esp
71131153|.83EC 18       sub   esp, 18
71131156|.53            push    ebx
71131157|.56            push    esi
71131158|.57            push    edi
71131159|.C745 F8 00000>mov   dword ptr , 0
71131160|.C645 FC 43    mov   byte ptr , 43
71131164|.8B45 0C       mov   eax, dword ptr                       ;message id
71131167|.8945 EC       mov   dword ptr , eax
7113116A|.837D EC 10    cmp   dword ptr , 10                      ;WM_CLOSE
7113116E|.0F84 35010000 je      711312A9
71131174|.817D EC 10010>cmp   dword ptr , 110                     ;WM_INITDIALOG
7113117B|.0F84 5B010000 je      711312DC
71131181|.817D EC 11010>cmp   dword ptr , 111                     ;WM_COMMAND
71131188|.74 05         je      short 7113118F
7113118A|.E9 B5010000   jmp   71131344
7113118F|>8B4D 10       mov   ecx, dword ptr
71131192|.894D E8       mov   dword ptr , ecx
71131195|.837D E8 01    cmp   dword ptr , 1                     ;按钮ID,处理按钮的 Click 事件
71131199|.74 05         je      short 711311A0
7113119B|.E9 04010000   jmp   711312A4
; 以下是按钮"Verify"的Click事件处理过程,对 SN 进行处理,生成WndProc地址值
711311A0|>833D A8931371>cmp   dword ptr , 0                     ;标志,不等于0才处理该事件,在窗口的HookProc修改该值(hookProc执行次数,也就是 SN 的长度不为0才处理)
711311A7|.75 05         jnz   short 711311AE
711311A9|.E9 F6000000   jmp   711312A4
711311AE|>0FBE15 359413>movsx   edx, byte ptr                   ;SN索引值基值, == 0x0
711311B5|.8955 F8       mov   dword ptr , edx
711311B8|.8B45 F8       mov   eax, dword ptr
711311BB|.83C0 01       add   eax, 1                                    ;i=eax=0+1
711311BE|.2B05 9C8A1371 sub   eax, dword ptr                    ;eax=i-(-1)=2, 0x71138A9C==>调用入口地址偏移量 0xFFFFFFFF
711311C4|.0FBE88 948913>movsx   ecx, byte ptr                 ;ecx=sn,eax=2时,生成指向序列号的地址
711311CB|.83E1 0F       and   ecx, 0F                                     ;atoi(),仅针对数字
711311CE|.C1E1 1C       shl   ecx, 1C                                     ;ecx=3 0000000(调试时输入的假码,不是正确值)
711311D1|.8B55 F8       mov   edx, dword ptr                       ;edx=0
711311D4|.83C2 02       add   edx, 2                                    ;i=edx=2
711311D7|.2B15 9C8A1371 sub   edx, dword ptr                    ;edx = i-(-1)=3
711311DD|.0FBE82 948913>movsx   eax, byte ptr                 ;sn
711311E4|.83E0 0F       and   eax, 0F
711311E7|.C1E0 18       shl   eax, 18
711311EA|.0BC8          or      ecx, eax                                    ;ecx=34 000000(调试时输入的假码,不是正确值)
711311EC|.8B55 F8       mov   edx, dword ptr                       ;edx=0
711311EF|.83C2 08       add   edx, 8                                    ;i=edx=8
711311F2|.2B15 9C8A1371 sub   edx, dword ptr                    ;edx=i-(-1)-9
711311F8|.0FBE82 948913>movsx   eax, byte ptr                 ;eax=sn
711311FF|.83E0 0F       and   eax, 0F
71131202|.C1E0 14       shl   eax, 14
71131205|.0BC8          or      ecx, eax                                    ;ecx=340 00000(调试时输入的假码,不是正确值)
71131207|.8B55 F8       mov   edx, dword ptr                       ;edx=0
7113120A|.83C2 05       add   edx, 5                                    ;i=edx=5
7113120D|.2B15 9C8A1371 sub   edx, dword ptr                    ;i-(-1)
71131213|.0FBE82 948913>movsx   eax, byte ptr                 ;eax=sn
7113121A|.83E0 0F       and   eax, 0F
7113121D|.C1E0 10       shl   eax, 10
71131220|.0BC8          or      ecx, eax
71131222|.8B55 F8       mov   edx, dword ptr                       ;ecx=3407 0000(调试时输入的假码,不是正确值)
71131225|.83C2 07       add   edx, 7                                    ;i=edx=7
71131228|.2B15 9C8A1371 sub   edx, dword ptr                    ;i-(-1)
7113122E|.0FBE82 948913>movsx   eax, byte ptr                 ;eax=sn
71131235|.83E0 0F       and   eax, 0F
71131238|.C1E0 0C       shl   eax, 0C
7113123B|.0BC8          or      ecx, eax                                    ;ecx=34079 000(调试时输入的假码,不是正确值)
7113123D|.8B55 F8       mov   edx, dword ptr                       ;edx=0
71131240|.83C2 09       add   edx, 9                                    ;i=edx=9
71131243|.2B15 9C8A1371 sub   edx, dword ptr                    ;i-(-1)
71131249|.0FBE82 948913>movsx   eax, byte ptr                 ;eax=sn
71131250|.83E0 0F       and   eax, 0F
71131253|.C1E0 08       shl   eax, 8
71131256|.0BC8          or      ecx, eax                                    ;ecx=340791 00(调试时输入的假码,不是正确值)
71131258|.8B55 F8       mov   edx, dword ptr                       ;edx=0
7113125B|.83C2 06       add   edx, 6                                    ;i=edx=6
7113125E|.2B15 9C8A1371 sub   edx, dword ptr                    ;i-(-1)
71131264|.0FBE82 948913>movsx   eax, byte ptr                 ;eax=sn
7113126B|.83E0 0F       and   eax, 0F
7113126E|.C1E0 04       shl   eax, 4
71131271|.0BC8          or      ecx, eax                                    ;ecx=3407918 0(调试时输入的假码,不是正确值)
71131273|.8B55 F8       mov   edx, dword ptr                       ;edx=0
71131276|.83C2 03       add   edx, 3                                    ;i=edx=3
71131279|.2B15 9C8A1371 sub   edx, dword ptr                    ;i-(-1)
7113127F|.0FBE82 948913>movsx   eax, byte ptr                 ;eax=sn
71131286|.83E0 0F       and   eax, 0F
71131289|.0BC8          or      ecx, eax                                    ;ecx=34079185(调试时输入的假码,不是正确值,正确应该为 71131060 或 7113100F)
7113128B|.890D 1C941371 mov   dword ptr , ecx                   ;保存序列号计算后的数值,保存后就发关闭CrackMe的消息
; 完在 SN 计算出WndProc地址后,直接发送 WM_CLOSE 消息,去关闭主对话框。
71131291|.6A 00         push    0                                           ; /lParam = 0
71131293|.6A 00         push    0                                           ; |wParam = 0
71131295|.6A 10         push    10                                          ; |Message = WM_CLOSE
71131297|.8B0D F8931371 mov   ecx, dword ptr                    ; |
7113129D|.51            push    ecx                                       ; |hWnd => NULL
7113129E|.FF15 54A31371 call    dword ptr [<&USER32.SendMessageA>]          ; \发送关闭CrackMe主界面的消息,退出CrackMe
711312A4|>E9 9F000000   jmp   71131348
; 接收到 WM_CLOSE 消息,取消键盘Hook,并关闭对话框
711312A9|>8B15 0C941371 mov   edx, dword ptr                    ; hook handler
711312AF|.52            push    edx                                       ; /hHook => NULL
711312B0|.FF15 64A31371 call    dword ptr [<&USER32.UnhookWindowsHookEx>]   ; \UnhookWindowsHookEx
711312B6|.6A 01         push    1                                           ; /Result = 1
711312B8|.A1 F8931371   mov   eax, dword ptr                    ; |
711312BD|.50            push    eax                                       ; |hWnd => NULL
711312BE|.FF15 68A31371 call    dword ptr [<&USER32.EndDialog>]             ; \EndDialog
; 调用另一个函数,注册一个窗口类,如果上面计算的地址有误,则会产生系统错误,程序也会退出。
711312C4|.E8 5AFDFFFF   call    71131023                                    ; 调用函数注册一个窗口类,该类的WndProc地址为前面通过SN计算得到的地址
711312C9|.25 FF000000   and   eax, 0FF
711312CE|.85C0          test    eax, eax
711312D0|.75 08         jnz   short 711312DA
711312D2|.6A 01         push    1                                           ; /ExitCode = 1
711312D4|.FF15 38A21371 call    dword ptr [<&KERNEL32.ExitProcess>]         ; \ExitProcess
711312DA|>EB 6C         jmp   short 71131348
; 下面中 WM_INITDIALOG 消息处理过程,其中有部分代码是在 NAG 的 Timer 事件中会填充为正确的代码
711312DC|>8B4D 08       mov   ecx, dword ptr                       ;处理 WM_INITDIALOG 消息
711312DF|.890D F8931371 mov   dword ptr , ecx                   ;Dialog handler?
711312E5|.FF15 3CA21371 call    dword ptr [<&KERNEL32.GetTickCount>]      ; [GetTickCount
711312EB|.8945 F4       mov   dword ptr , eax                      ;取当前时间
711312EE|.FF15 A8A21371 call    dword ptr [<&KERNEL32.GetCurrentThreadId>]; [GetCurrentThreadId
; 以下是CrackMe填充前的代码
;711312F4|.90            nop                                                ;nop,TimerProc中填充,破解后预填充
;711312F5|.90            nop                                                ;nop,TimerProc中填充,破解后预填充
;711312F6|.90            nop                                                ;nop,TimerProc中填充,破解后预填充
;711312F7|.90            nop                                                ;nop,TimerProc中填充,破解后预填充
;711312F8|.90            nop                                                ;nop,TimerProc中填充,破解后预填充
;711312F9|.B8 39425573   mov   eax, 73554239                              ;这里也会在TimerProc中填充,改变指令,破解后预填充
;711312FE|.C600 C4       mov   byte ptr , 0C4                        ;这里也会在TimerProc中填充,改变指令,破解后预填充
;以下是CrackMe程序填充后的代码
;711312F4|.A3 9C931371   mov   dword ptr , eax                  ;修改指令起点,保存线程ID
;711312F9|.90            nop
;711312FA|?33C0          xor   eax, eax
;711312FC|?B4 43         mov   ah, 43                                     ; 这里在WinMain1()中有检测,用于生成调用WinMain2()的地址
;711312FE|.CD 68         int   68                                       ; 这里在WinMain1()中有检测,用于生成调用WinMain2()的地址
;71131300|?90            nop
;以下修改是为了防止在 Windows NT系列内核的系统上报异常,破解NAG后手工填充的代码,就是跳过 int 68 指令的执行
711312F4|.A3 9C931371   mov   dword ptr , eax                   ;保存ThreadID
711312F9|.90            nop
711312FA      EB 04         jmp   short 71131300                              ;将 xor eax, eax 修改成 jmp 71131300
711312FC|.B4 43         mov   ah, 43
711312FE|.CD 68         int   68
71131300|.90            nop
; 保存检查值,如果在Windows 9x下,没有检查到SoftICE,则ax=0x4300,破解NAG后,ax中前面的线程ID。
71131301|.66:A3 948A137>mov   word ptr , ax                     ; 保存检查值,破解后保存的是前面取得的线程ID,只要线程ID不等于0xF386就不影响CrackMe后面的运行
; 以下是反调试代码,如果单步执行,就会有问题,会导致全局变量的值会改变,不再等于 -1(0xFFFFFFFF),会影响其它代码运算出错,如 SN 的计算。
71131307|.FF15 3CA21371 call    dword ptr [<&KERNEL32.GetTickCount>]      ; [GetTickCount
7113130D|.8945 F0       mov   dword ptr , eax                     ;再次取得当前时间
71131310|.8B55 F0       mov   edx, dword ptr
71131313|.2B55 F4       sub   edx, dword ptr                       ;计算时间差,用于反调试,修改 0xFFFFFFFF 为其它值,影响其它地方的指令执行
71131316|.A1 9C8A1371   mov   eax, dword ptr                    ;eax=0xFFFFFFFF,前面检查Softice的标志值
7113131B|.2BC2          sub   eax, edx                                    ;修改 0xFFFFFFFF 为其它值,影响其它地方的指令执行
7113131D|.A3 9C8A1371   mov   dword ptr , eax                   ;eax = 0xFFFFFFFF 才是合法数据
;下面设置一个键盘 Hook,用来记录当前线程所有的键盘击键操作
71131322|.8B0D 9C931371 mov   ecx, dword ptr
71131328|.51            push    ecx                                       ; /ThreadID => 0
71131329|.8B15 18941371 mov   edx, dword ptr                    ; | hInstance
7113132F|.52            push    edx                                       ; |hModule => NULL
71131330|.68 14101371   push    71131014                                    ; |Hookproc = ELRAIZER.71131014
71131335|.6A 02         push    2                                           ; |HookType = WH_KEYBOARD
71131337|.FF15 60A31371 call    dword ptr [<&USER32.SetWindowsHookExA>]   ; \SetWindowsHookExA
7113133D|.A3 0C941371   mov   dword ptr , eax                   ; 返回结果保存在,用于UnhookWindowsHookEx的参数
71131342|.EB 04         jmp   short 71131348
71131344|>33C0          xor   eax, eax
71131346|.EB 05         jmp   short 7113134D
71131348|>B8 01000000   mov   eax, 1
7113134D|>5F            pop   edi
7113134E|.5E            pop   esi
7113134F|.5B            pop   ebx
71131350|.8BE5          mov   esp, ebp
71131352|.5D            pop   ebp
71131353\.C2 1000       retn    10
71131356      CC            int3
71131357      CC            int3
序列号的分析:
椐据上面代码中对输入的 SN 的处理算法,就是截取SN中8个字符的ASCII值的后4bit作为一位16进位的数字,然后将8位数字组合成一个32bit的整数,组合顺序如下:
最终计算后的SN数字结果 = SN SN SN SN SN SN SN SN
由此可以看出,输入的SN至少要11个字符,其中 SN,SN,SN三个位置的字符没有用到,因此可以为任意字符,11个字符的后面还可任意添加字符,不影响计算。


六、键盘 Hook 过程
该CrackMe的序列号的录入,是通过键盘Hook完成的,并不是界面上取的字符串,界面上只是纯显示,并且,Hook过程处理所有击键的键码,包括功能键,比如删除、回退、F8等,都会记录并保存,所以内存缓冲区中保存的SN与对话框界面显示的SN可能并不一致。另外,Hook进入256次,不再记录键盘事件的键码了。
1)Hook记录键码,并通过一个变量,过滤掉 KeyUp事件,只记录 KeyDown事件中的键码;
2)对主对话框 DlgProc 对SoftICE 的检查结果进行核查,如果 SoftICE 在运行,则退出CrackMe。
具体分析如下:
; 键盘 Hook 的回调函数,记录所有的键盘输入,包括功能键,在OD中调试时的按键也会影响
711313E0/>55            push    ebp                                       ;HookProc
711313E1|.8BEC          mov   ebp, esp
711313E3|.83EC 08       sub   esp, 8
711313E6|.8B45 08       mov   eax, dword ptr                       ;message
711313E9|.8945 F8       mov   dword ptr , eax
711313EC|.837D F8 00    cmp   dword ptr , 0                        ;HC_ACTION = 0
711313F0|.74 08         je      short 711313FA                              ;nCode==HC_ACTION(即等到于0)时,在hook中进行处理
711313F2|.837D F8 03    cmp   dword ptr , 3                        ;HC_NOREMOVE = 3
711313F6|.74 78         je      short 71131470
711313F8|.EB 78         jmp   short 71131472
;下面是按钮事件处理过程,通过一个bool变量,针对KeyDown和KeyUp事件,只记录一次键码。
711313FA|>33C9          xor   ecx, ecx
711313FC|.8A0D A0931371 mov   cl, byte ptr                      ;bool标志, 0 和 1,主要用来做 KeyDown 和 KeyUp 事件处理,只记录一次事件
71131402|.85C9          test    ecx, ecx
71131404|.75 3F         jnz   short 71131445                              ;bool标志不等于0则跳过,不记录键值
71131406|.813D A8931371>cmp   dword ptr , 100                   ;Hook进入次数
71131410|.73 2A         jnb   short 7113143C                              ;不低于是0x100就跳转
71131412|.8B15 A8931371 mov   edx, dword ptr                    ;次数
71131418|.8A45 0C       mov   al, byte ptr                         ;al = wParam
7113141B|.8882 94891371 mov   byte ptr , al               ;记录键值存入序列号缓冲区(0x0~0x49, 0x100/2)
71131421|.8B0D A8931371 mov   ecx, dword ptr
71131427|.83C1 01       add   ecx, 1                                    ;进入次数+1
7113142A|.890D A8931371 mov   dword ptr , ecx
71131430|.33D2          xor   edx, edx
71131432|.66:8B15 948A1>mov   dx, word ptr                      ;取得 int 68 检查 SoftICE 调试的检查标志值
71131439|.8955 FC       mov   dword ptr , edx                      ;保存这个值到局部变量,不过好象没有地方用到这个局部变量
7113143C|>C605 A0931371>mov   byte ptr , 1                      ;bool标志 = 1
71131443|.EB 29         jmp   short 7113146E
71131445|>C605 A0931371>mov   byte ptr , 0                      ;bool标志 = 0
; 下面是反调试代码,不过对 OD 无效
7113144C|.33C0          xor   eax, eax
7113144E|.66:A1 948A137>mov   ax, word ptr                      ;该值在WinMain初始化为0x0F386,在MainDialog的初始化事件中有修改。上面有读取,未修改。反SoftICE调试用的
71131454|.3D 86F30000   cmp   eax, 0F386                                  ;反SoftIce调试,eax不能等于0x0F386
71131459|.75 13         jnz   short 7113146E                              ;如果检测到SoftICE调试,则Post关闭消息退出CrackMe
7113145B|.6A 00         push    0                                           ; /lParam = 0
7113145D|.6A 00         push    0                                           ; |wParam = 0
7113145F|.6A 10         push    10                                          ; |Message = WM_CLOSE
71131461|.8B0D F8931371 mov   ecx, dword ptr                    ; |
71131467|.51            push    ecx                                       ; |hWnd => NULL
71131468|.FF15 58A31371 call    dword ptr [<&USER32.PostMessageA>]          ; \Post关闭消息退出CrackMe
7113146E|>EB 1D         jmp   short 7113148D
71131470|>EB 1B         jmp   short 7113148D
; 下面是Hook传递,不然,界面文本框无法得到键码并显示(因此,本CrackMe的录入和显示是分开的,没有相关性,由于有功能键操作,可能录入的与显示的不一样)
71131472|>8B55 10       mov   edx, dword ptr
71131475|.52            push    edx                                       ; /lParam
71131476|.8B45 0C       mov   eax, dword ptr                       ; |
71131479|.50            push    eax                                       ; |wParam
7113147A|.8B4D 08       mov   ecx, dword ptr                       ; |
7113147D|.51            push    ecx                                       ; |HookCode
7113147E|.8B15 0C941371 mov   edx, dword ptr                    ; |
71131484|.52            push    edx                                       ; |hHook => NULL
71131485|.FF15 5CA31371 call    dword ptr [<&USER32.CallNextHookEx>]      ; \CallNextHookEx
7113148B|.EB 18         jmp   short 711314A5
7113148D|>8B45 10       mov   eax, dword ptr
71131490|.50            push    eax                                       ; /lParam
71131491|.8B4D 0C       mov   ecx, dword ptr                       ; |
71131494|.51            push    ecx                                       ; |wParam
71131495|.8B55 08       mov   edx, dword ptr                       ; |
71131498|.52            push    edx                                       ; |HookCode
71131499|.A1 0C941371   mov   eax, dword ptr                    ; |
7113149E|.50            push    eax                                       ; |hHook => NULL
7113149F|.FF15 5CA31371 call    dword ptr [<&USER32.CallNextHookEx>]      ; \CallNextHookEx
711314A5|>8BE5          mov   esp, ebp
711314A7|.5D            pop   ebp
711314A8\.C2 0C00       retn    0C
711314AB      CC            int3


七、窗口注册 Proc
这个函数注册了一个窗口类,并且该类的 WndProc就是主对话框按钮Click()事件中计算出来的地址,如果输入的SN不对,则此地址无效,也会引起程序直接被系统关闭。
功能如下:
1)调用 RegisterClass注册一个标准 Windows 窗口类;
具体代码如下:
; 本函数用来注册一个Windows的窗口类,这个窗口类有WndProc地址是通过录入的SN计算得来的
711314E0/>55            push    ebp                                       ;注册窗口类
711314E1|.8BEC          mov   ebp, esp
711314E3|.51            push    ecx
711314E4|.C745 FC 6243F>mov   dword ptr , BFF54362
711314EB|.A1 1C941371   mov   eax, dword ptr                    ;eax=计算后的序列号
711314F0|.A3 F4931371   mov   dword ptr , eax                   ;计算后的序列号
711314F5|.C705 B0931371>mov   dword ptr , 3
711314FF|.8B0D F4931371 mov   ecx, dword ptr                    ;ecx=eax=计算后的序列号
71131505|.890D B4931371 mov   dword ptr , ecx                   ;WndClass.lpfnWndProc = eax,窗口的消息处理函数入口
7113150B|.C705 B8931371>mov   dword ptr , 0
71131515|.C705 BC931371>mov   dword ptr , 0
7113151F|.8B15 18941371 mov   edx, dword ptr
71131525|.8915 C0931371 mov   dword ptr , edx
7113152B|.C705 C4931371>mov   dword ptr , 0
71131535|.C705 C8931371>mov   dword ptr , 0
7113153F|.C705 CC931371>mov   dword ptr , 6
71131549|.C705 D0931371>mov   dword ptr , 0
71131553|.C705 D4931371>mov   dword ptr , 71138AE8            ;ASCII "_#_"
7113155D|.68 B0931371   push    711393B0                                    ; /pWndClass = ELRAIZER.711393B0
71131562|.FF15 48A31371 call    dword ptr [<&USER32.RegisterClassA>]      ; \RegisterClassA, 注册一个窗口类
71131568|.25 FFFF0000   and   eax, 0FFFF
7113156D|.85C0          test    eax, eax
7113156F|.75 04         jnz   short 71131575
71131571|.32C0          xor   al, al
71131573|.EB 02         jmp   short 71131577
71131575|>B0 01         mov   al, 1
71131577|>8BE5          mov   esp, ebp
71131579|.5D            pop   ebp
7113157A\.C3            retn


八、Window Callback Proc
这个就是一个窗口类的 WndProc,就是上面过程中注册窗口类要用到的这个Proc。因为CrackMe并没有指明这个函数就是 WndProc,指向这个函数的地址也是计算出来的,为什么确定其就是一个 WndProc呢,很简单:其调用了系统的一个函数 DefWindowProc(),而这个函数是标准的 WndProc 才用得到,同时也只有这个过程调用这个函数,因此确定此函数就是 WndProc,并且其入口地址为:0x71131060,也可以是 0x7113100F(见最前面的入口列表),要根据这个地址及主对话框 DlgProc中的SN处理算法,就可以确定序列号了。
有效的序列号如下,当然不限于这些,只是这几个简单:
1)xx710x36110
2)xx71/x30110
3)xx71?x30110
4)xx71Ox30110
5)xx71_x30110
6)xx71ox30110
每个SN中有3个x,x可以为任意字符,11个字符后也可以输入任意字符。只是注意,录入时不要按其它键,只能一次性按顺序输入所有字符。如下,是一个有效的输入:

这个WndProc的代码如下:
; WndProc 过程,窗口消息处理回调函数
71131060/>55            push    ebp                                       ;窗口消息处理WinProc
71131061|.8BEC          mov   ebp, esp
71131063|.83EC 08       sub   esp, 8
71131066|.8B45 0C       mov   eax, dword ptr
71131069|.8945 F8       mov   dword ptr , eax
7113106C|.837D F8 10    cmp   dword ptr , 10                     ;WM_CLOSE
71131070|.77 14         ja      short 71131086                              ;大于 0x10
71131072|.837D F8 10    cmp   dword ptr , 10                     ;WM_CLOSE
71131076|.74 19         je      short 71131091                              ;等于 WM_CLOSE
71131078|.837D F8 01    cmp   dword ptr , 1
7113107C|.74 1F         je      short 7113109D
7113107E|.837D F8 0F    cmp   dword ptr , 0F                     ;WM_PAINT,这个消息没什么用,可以算干扰代码
71131082|.74 24         je      short 711310A8
71131084|.EB 78         jmp   short 711310FE
71131086|>817D F8 11010>cmp   dword ptr , 111                      ;WM_COMMAND
7113108D|.74 0C         je      short 7113109B
7113108F|.EB 6D         jmp   short 711310FE                              ;其它消息
71131091|>6A 00         push    0                                           ; /ExitCode = 0
71131093|.FF15 70A31371 call    dword ptr [<&USER32.PostQuitMessage>]       ; \PostQuitMessage
71131099|.EB 7B         jmp   short 71131116
7113109B|>EB 79         jmp   short 71131116
7113109D|>8B4D 08       mov   ecx, dword ptr
711310A0|.890D A4931371 mov   dword ptr , ecx
711310A6|.EB 6E         jmp   short 71131116
711310A8|>8B15 A4931371 mov   edx, dword ptr                    ;以下代码不会执行到,没有ShowWindow,也没有WM_PAINT消息产生
711310AE|.52            push    edx                                       ; /hWnd => NULL
711310AF|.FF15 74A31371 call    dword ptr [<&USER32.GetDC>]               ; \GetDC
711310B5|.8945 FC       mov   dword ptr , eax
711310B8|.6A 2B         push    2B                                          ; /StringSize = 2B (43.)
711310BA|.68 60891371   push    71138960                                    ; |指向西班牙语:我靠。。。。实际上这是多余的
711310BF|.6A 32         push    32                                          ; |YStart = 32 (50.)
711310C1|.6A 32         push    32                                          ; |XStart = 32 (50.)
711310C3|.8B45 FC       mov   eax, dword ptr                       ; |
711310C6|.50            push    eax                                       ; |hDC
711310C7|.FF15 00A21371 call    dword ptr [<&GDI32.TextOutA>]               ; \TextOutA
711310CD|.6A 25         push    25                                          ; /StringSize = 25 (37.)
711310CF|.68 30891371   push    71138930                                    ; |指向西班牙语:我甚至都没有意识到这一点!!!
711310D4|.6A 46         push    46                                          ; |YStart = 46 (70.)
711310D6|.6A 32         push    32                                          ; |XStart = 32 (50.)
711310D8|.8B4D FC       mov   ecx, dword ptr                       ; |
711310DB|.51            push    ecx                                       ; |hDC
711310DC|.FF15 00A21371 call    dword ptr [<&GDI32.TextOutA>]               ; \TextOutA
711310E2|.6A 00         push    0                                           ; /pRect = NULL
711310E4|.8B55 08       mov   edx, dword ptr                       ; |
711310E7|.52            push    edx                                       ; |hWnd
711310E8|.FF15 6CA31371 call    dword ptr [<&USER32.ValidateRect>]          ; \ValidateRect
711310EE|.8B45 FC       mov   eax, dword ptr
711310F1|.50            push    eax                                       ; /hDC
711310F2|.8B4D 08       mov   ecx, dword ptr                       ; |
711310F5|.51            push    ecx                                       ; |hWnd
711310F6|.FF15 38A31371 call    dword ptr [<&USER32.ReleaseDC>]             ; \ReleaseDC
711310FC|.EB 18         jmp   short 71131116
711310FE|>8B55 14       mov   edx, dword ptr                      ;其它消息默认处理
71131101|.52            push    edx                                       ; /lParam
71131102|.8B45 10       mov   eax, dword ptr                      ; |
71131105|.50            push    eax                                       ; |wParam
71131106|.8B4D 0C       mov   ecx, dword ptr                       ; |
71131109|.51            push    ecx                                       ; |Message
7113110A|.8B55 08       mov   edx, dword ptr                       ; |
7113110D|.52            push    edx                                       ; |hWnd
7113110E|.FF15 34A31371 call    dword ptr [<&USER32.DefWindowProcA>]      ; \DefWindowProcA
71131114|.EB 02         jmp   short 71131118
71131116|>33C0          xor   eax, eax
71131118|>8BE5          mov   esp, ebp
7113111A|.5D            pop   ebp
7113111B\.C2 1000       retn    10
7113111E      CC            int3
7113111F      CC            int3


九、系统错误提示消息框
这个过程只是显示一个固定的消息框。当系统出错就会调用本过程,代码如下:
;全局SEH异常处理,显示出错信息
71131800/> \55            push    ebp                                       ;显示错误信息消息框
71131801|.8BEC          mov   ebp, esp
71131803|.83EC 08       sub   esp, 8
71131806|.8B45 08       mov   eax, dword ptr
71131809|.8B08          mov   ecx, dword ptr
7113180B|.8B11          mov   edx, dword ptr
7113180D|.8955 FC       mov   dword ptr , edx
71131810|.817D FC 05000>cmp   dword ptr , C0000005               ;STATUS_ACCESS_VIOLATION: 写入位置时发生访问冲突,程序企图读写一个不可访问的地址时引发的异常,也就是地址指针错误
71131817|.74 12         je      short 7113182B
71131819|.817D FC 90000>cmp   dword ptr , C0000090               ;STATUS_FLOAT_INVALID_OPERATION: 该异常表示一个未知的浮点数异常。
71131820|.74 09         je      short 7113182B
71131822|.817D FC 8E000>cmp   dword ptr , C000008E               ;STATUS_FLOAT_DIVIDE_BY_ZERO: 浮点数除法的除数是0时引发该异常。
71131829|.75 21         jnz   short 7113184C
7113182B|>6A 00         push    0                                           ; /Style = MB_OK|MB_APPLMODAL
7113182D|.68 208D1371   push    71138D20                                    ; |Title = "ATPTeam CrackMe zer0 Error Message"
71131832|.68 548B1371   push    71138B54                                    ; |Text = "Si tu es arriv?l?c'est que des octets sont chang閟 , ou que tu as entr?un tres mauvais code ;)",LF,"(Ou encore que SoftICE / FrogSICE sont charg閟 ...........) ",LF,"...Du coup tu as provoqu?une 'General Protection Fault' ",LF,"!!!"...
71131837|.A1 F8931371   mov   eax, dword ptr                    ; |
7113183C|.50            push    eax                                       ; |hOwner => NULL
7113183D|.FF15 3CA31371 call    dword ptr [<&USER32.MessageBoxA>]         ; \MessageBoxA
71131843|.C745 F8 01000>mov   dword ptr , 1
7113184A|.EB 07         jmp   short 71131853
7113184C|>C745 F8 00000>mov   dword ptr , 0
71131853|>8B45 F8       mov   eax, dword ptr
71131856|.8BE5          mov   esp, ebp
71131858|.5D            pop   ebp
71131859\.C2 0400       retn    4
其界面如下图:

如果上面图片没有显示出来(好象论坛中同一张图只能显示一次),请参见“三、定时器回调Proc”中的第一个图片。

十、总结
根据以上代码分析,我们不能简单的把NAG代码NOP掉,因为NAG中有对全局变量的初始化,还对代码进行了生成和回填,不回填的话原代码是错误的,会导致内存访问错误,还会导致 WinMain2()入口计算错误等,同时 int 68 也不能再使用,所以,本CrackMe有以下三个修改,才能完成NAG的破除,直接用16进制工具修改exe文件:
(1) 修改数据节变量静态初值:全局变量的静态初始值由0x0000001F 修改成 0xFFFFFFFF,如下:
修改文件,由:
00008a90h: 00 00 00 00 FF FF 00 00 D8 8A 13 71 1F 00 00 00
改为:
00008a90h: 00 00 00 00 FF FF 00 00 D8 8A 13 71 FF FF FF FF
(2) 修改指令节汇编代码:
由   90 90 90 90 90 B8 39 42 55 73 C6 00 C4:
711312F4|.90            nop                                    ;修改指令起点
711312F5|.90            nop
711312F6|.90            nop
711312F7|.90            nop
711312F8|.90            nop
711312F9|.B8 39425573   mov   eax, 73554239
711312FE|.C600 C4       mov   byte ptr , 0C4
修改成 A3 9C 93 13 71 90 EB 04 B4 43 CD 68 90:
711312F4|.A3 9C931371   mov   dword ptr , eax               ;修改指令
711312F9|.90            nop
711312FA      EB 04         jmp   short 71131300                        ;将 xor eax, eax 修改成 jmp 71131300
711312FC|.B4 43         mov   ah, 43
711312FE|.CD 68         int   68
71131300|.90            nop
在文件中是由:
000012f0h: A8 A2 13 71 90 90 90 90 90 B8 39 42 55 73 C6 00
00001300h: C4
修改成:
000012f0h: A8 A2 13 71 A3 9C 93 13 71 90 EB 04 B4 43 CD 68
00001300h: 90
(3) 将显示NAG的代码填充成NOP:
71131635   .6A 00                  push    0                                       ; /lParam = NULL
71131637   .68 0A101371            push    7113100A                              ; |DlgProc = Elraizer.7113100A
7113163C   .6A 00                  push    0                                       ; |hOwner = NULL
7113163E   .68 E9030000            push    3E9                                     ; |pTemplate = 3E9
71131643   .8B0D 18941371            mov   ecx, dword ptr                ; |Elraizer.71130000
71131649   .51                     push    ecx                                     ; |hInst => 71130000
7113164A   .FF15 50A31371            call    dword ptr [<&USER32.DialogBoxParamA>]   ; \DialogBoxParamA
以上代码全部填充成NOP指令。
在文件中由:
00001630h: 29 96 13 71 CD 6A 00 68 0A 10 13 71 6A 00 68 E9
00001640h: 03 00 00 8B 0D 18 94 13 71 51 FF 15 50 A3 13 71
改成如下:
00001630h: 29 96 13 71 CD 90 90 90 90 90 90 90 90 90 90 90
00001640h: 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
完成以上修改后,可以在Windows 10 中完美运行此 CrackMe 了。

运气好的话或在调试状态下,在 CrackMe关闭时,还可以看到最后 CreateWindow生成的窗口,SN输入正确时,该窗口会在屏幕左上角闪一下,然后消失,SN输入错误时则不会出现:

分析完毕!!!

kown 发表于 2019-5-11 19:09

奈何本人文化浅,一声卧槽送楼主。一脸懵逼的进来,现在准备一脸懵逼的出去。。

HackerWen 发表于 2019-5-11 21:06

大佬是哪里来的这么多CrackMe呀

solly 发表于 2019-5-12 02:46

HackerWen 发表于 2019-5-11 21:06
大佬是哪里来的这么多CrackMe呀

这里的呀,置顶贴中的:
https://www.52pojie.cn/thread-709699-1-1.html

娄不夜 发表于 2019-5-12 11:32

怎么判断汇编语言功能的啊,我只知道指令的意思

我还不够叼 发表于 2019-5-12 20:28

感谢分享!

solly 发表于 2019-5-12 21:30

娄不夜 发表于 2019-5-12 11:32
怎么判断汇编语言功能的啊,我只知道指令的意思

可以结合一些反汇编工具(如 WDASM,IDA等)来判断,还有就是联系上下文,API调用(DOS下是中断),以及一段代码组合在一起来判断,等等。。。。。。
页: [1]
查看完整版本: 160 个 CrackMe 之 55 -- Elraizer.1 详细分析和追码过程