上篇讲了利用LOADER通过原程序算法模块计算明码的例子,那如果是暗码呢,也就是说注册码是通过机器码运算后得到的结果(暂时称它为暗码),再经过一系列变换而来的那种。其实原理也一样。我们只要得到暗码,找出软件中利用它推出注册码的那段过程,然后将暗码逆运算后就可以得到注册码。
为了实践这一个过程,我用W32ASM写了个CRACKME,并写出相应的算法注册机以供验证。

附件点击本地下载

先分析这个CRACKME:

//机器码运算
004013B4  |.  E8 01080000   call    <jmp.&user32.GetDlgItemTextA>   
004013B9  |.  8D05 98334000 lea     eaxdword ptr [403398]          ;  压入机器码
004013BF  |?  68 98334000   push    00403398                         ;  EAX中放着机器码
....
00401B90  |.  50            push    eax                              ;  EAX中就是暗码
00401B91  |?  68 7E324000   push    0040327E                         ;  转字符串   
00401B96  |?  68 48334000   push    00403348                        
00401B9B  |?  E8 08000000   call    <jmp.&user32.wsprintfA>
00401BA0  |.  83C4 0C       add     esp, 0C

//注册码变形
0040131E  |.  E8 97080000   call    <jmp.&user32.GetDlgItemTextA>    ;  取注册码
00401323  |.  6A 08         push    8
00401325  |.  68 A8324000   push    004032A8
0040132A  |.  E8 B4FEFFFF   call    004011E3                         ;  转16进制
0040132F  |.  8BF0          mov     esieax
00401331  |.  81C6 A679F3FF add     esi, FFF379A6                    ;  加FFF379A6h
00401337  |.  81F6 DDAEEC04 xor     esi, 4ECAEDD                     ;  异或4ECAEDDh
0040133D  |.  81EE C78AA900 sub     esi, 0A98AC7                     ;  减0A98AC7h
00401343  |.  56            push    esi                              ;  结果
00401344  |.  68 7E324000   push    0040327E                         ;  转字符串
00401349  |.  68 F8324000   push    004032F8                         
0040134E  |.  E8 55080000   call    <jmp.&user32.wsprintfA>          

//对比结果
004010E8  |.  68 48334000   push    00403348                         ;  机器码运算后的字符串
004010ED  |.  68 F8324000   push    004032F8                         ;  注册码运算后的字符串
004010F2  |.  E8 F30A0000   call    <jmp.&kernel32.lstrcmpA>         ;  比较
004010F7  |.  0BC0          or      eaxeax
004010F9  |.  75 19         jnz     short 00401114

这个CRACKME虽然简单了点,但是讲的是一个原理。程序先将注册码运算后得到一个字符串,再将机器码运算后得到一个字符串,对比两个字符串,只要相等就注册成功了。
我们可以看出,004013BF中EAX指向的是机器码,00401B90中的EAX指向的就是暗码,得到暗码后我们只要根据注册码变形的那个逆运算就可以推出真正的注册码。
这次我们改用打开文件的方式,也加入了对话框窗口。详细代码如下:

.586
.model flatstdcall  
option casemap :none  
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
include kernel32.inc
includelib user32.lib
includelib kernel32.lib
include comdlg32.inc
includelib comdlg32.lib 
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
IDD_DLG1                equ 1000
IDC_NAME                equ 1001
IDC_CODE                equ 1002
IDC_OK                  equ 1005
IDC_ABOUT               equ 1006
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
BREAK_POINT1   equ 004013BFh ;第一个断点
BREAK_POINT2   equ 00401B90h ;第二个断点
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DlgProc proto :HWND,:UINT,:WPARAM,:LPARAM
GetKey  proto 
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
   .data 
   MsgboxText    db '    -=Author: langxang=-',0dh
              db ' -=Email:langxang@126.com=-',0
   MsgboxCaption db '关于',0
   MsgBoxText1    db '请输入远程机器码!并打开crackme.exe程序。',0
   MsgBoxCaption1 db '提示',0
   AppName db "crackme",0 
   int3    db 0cch
   oldcode db 2 dup(?)
   value   db 8 dup(?)
   BUFFER  db 8 dup(?)
   oldbyte1 db 68h
   oldbyte2 db 50h
   szFormat db "%X",0
   ofn OPENFILENAME <> 
   FilterString db "Executable Files",0,"*.exe",0 
                db "All Files",0,"*.*",0,0 
   
   ProcessInfo db "File Handle: %lx ",0dh,0Ah 
               db "Process Handle: %lx",0Dh,0Ah 
               db "Thread  Handle: %lx",0Dh,0Ah 
               db "Image   Base: %lx",0Dh,0Ah 
               db "Start   Address: %lx",0 
   .data? 
   buffer db 512 dup(?) 
   startinfo STARTUPINFO <> 
   pi PROCESS_INFORMATION <> 
   DBEvent DEBUG_EVENT <> 
   context CONTEXT <>
   uExitCode     dd ?
   hInstance     HINSTANCE ?
   hDlg          HINSTANCE ?  
   UserID        db 80 dup (?)
   Serial        db 80 dup (?)

   .code 
   start:
  invoke GetModuleHandle,NULL
       mov hInstance,eax
  invoke DialogBoxParam,hInstance,IDD_DLG1,NULL,addr DlgProc,NULL
  invoke ExitProcess,0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DlgProc proc hWin:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
        mov eax,uMsg
  .if     eax==WM_CLOSE
       invoke EndDialog,hWin,0
  .elseif eax==WM_INITDIALOG
        push hWin
        pop  hDlg       
  .elseif eax==WM_COMMAND
          mov eax,wParam
             .if eax==IDC_OK                
                invoke GetDlgItemText,hDlg,IDC_NAME,addr UserID,Sizeof UserID                                           
                   .if eax==0
                     invoke MessageBox,hDlg,addr MsgBoxText1,addr MsgBoxCaption1,MB_OK
                   .elseif  
                     invoke GetKey
                     invoke SetDlgItemText,hDlg,IDC_CODE,addr value
                   .endif
             .elseif eax==IDC_ABOUT
                invoke MessageBox,hDlg,addr MsgboxText,addr MsgboxCaption,MB_OK
             .endif            
  .else
    mov eax,FALSE
    ret
  .endif
  mov eax,TRUE
  ret
DlgProc endp

GetKey proc  
       pushad
;********************************************************************
; 打开文件
;******************************************************************** 
   mov ofn.lStructSize,sizeof ofn 
   mov ofn.lpstrFilter, offset FilterString 
   mov ofn.lpstrFile, offset buffer 
   mov ofn.nMaxFile,512 
   mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY 
   invoke GetOpenFileNameADDR ofn   
;********************************************************************
; 创建进程
;******************************************************************** 
   .if eax==TRUE     
    invoke GetStartupInfo,addr startinfo 
    invoke CreateProcessaddr buffer, NULL, NULL, NULL, FALSE,\
                      DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS, NULL, NULL,\
                      addr startinfo, addr pi 
;********************************************************************
; 调试进程
;********************************************************************
.while TRUE 
       invoke WaitForDebugEventaddr DBEvent, INFINITE 
       .break .if DBEvent.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT
;********************************************************************
; 如果进程开始,则将两个断点地址的代码改为INT3中断
;********************************************************************
       .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
          invoke  WriteProcessMemory,pi.hProcess,BREAK_POINT1,addr int3,1,NULL       ;插入INT3
          invoke  WriteProcessMemory,pi.hProcess,BREAK_POINT2,addr int3,1,NULL       ;插入INT3
;********************************************************************
; 中断触发的异常事件,并进入安全模式读写状态
;********************************************************************                             
       .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
           .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT 
             mov context.ContextFlags, CONTEXT_FULL 
             invoke GetThreadContext, pi.hThread, addr context 
;********************************************************************
; 如果在机器码处中断,则恢复原来的代码,并写入新的机器码
;********************************************************************              
             .if     context.regEip == BREAK_POINT1+1
              dec     context.regEip
              invoke  WriteProcessMemory,pi.hProcess,BREAK_POINT1,addr oldbyte1,1,NULL    ;恢复代码
              mov eax,context.regEax                                                      ;机器码在EAX中
              invoke  WriteProcessMemory,pi.hProcess,eax,addr UserID,0ch,NULL             ;读入22位机器码
              invoke  SetThreadContext,pi.hThread, addr context                           ;设置生效
              ;invoke ReadProcessMemory,pi.hProcess,context.regEax,addr BUFFER,0ch,NULL   ;用于检测修改的机器码是否已经生效
              ;invoke  MessageBox,0, addr BUFFER,addr AppName, MB_OK+MB_ICONINFORMATION       
;********************************************************************
; 如果在注册码处中断,则恢复原来的代码,并读出注册码
;********************************************************************                                                     
            .elseif  context.regEip == BREAK_POINT2+1
              dec     context.regEip
              invoke WriteProcessMemory,pi.hProcess,BREAK_POINT2,addr oldbyte2,1,NULL     ;恢复代码             
;********************************************************************
; 逆推注册码
;********************************************************************   
              MOV EAX,context.regEax                                                      ;暗码在EAX中
              ADD EAX,0A98AC7h
              XOR EAX,4ECAEDDh
              SUB EAX,0FFF379A6h 
              invoke wsprintf,addr value,addr szFormat,eax                 
              invoke SetDlgItemText,hDlg,IDC_CODE,addr value                              ;输出到注册码框
              invoke SetThreadContext,pi.hThread, addr context
              invoke TerminateProcess,pi.hProcess,uExitCode                               ;强行退出 
              .break            
            .endif           
          .endif 
       .endif 
       invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,DBG_CONTINUE 
.endw 
;********************************************************************
; 结束线程
;******************************************************************** 
          invoke  CloseHandle,pi.hProcess
          invoke  CloseHandle,pi.hThread
.endif       
    popad
    ret 
GetKey endp
end start   

按照这个原理,比较简单的程序一般都可以处理。但是如果防INT3或者防内存补丁的程序一般要先处理这些ANTI。另外对于有些压缩壳加壳的软件,可以采用启动后迅速挂起或者等待几秒挂起的方法。对于那些ANTI很厉害的壳程序,这个是没有用的。
如果你编程技术了得,可以模拟刘前辈的KEYMAKE写个注册机生成器。