上篇讲了利用LOADER通过原程序算法模块计算明码的例子,那如果是暗码呢,也就是说注册码是通过机器码运算后得到的结果(暂时称它为暗码),再经过一系列变换而来的那种。其实原理也一样。我们只要得到暗码,找出软件中利用它推出注册码的那段过程,然后将暗码逆运算后就可以得到注册码。
为了实践这一个过程,我用W32ASM写了个CRACKME,并写出相应的算法注册机以供验证。
先分析这个CRACKME:
//机器码运算
004013B4 |. E8 01080000 call <jmp.&user32.GetDlgItemTextA>
004013B9 |. 8D05 98334000 lea eax , dword 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 esi , eax
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 eax , eax
004010F9 |. 75 19 jnz short 00401114
这个CRACKME虽然简单了点,但是讲的是一个原理。程序先将注册码运算后得到一个字符串,再将机器码运算后得到一个字符串,对比两个字符串,只要相等就注册成功了。
我们可以看出,004013BF中EAX指向的是机器码,00401B90中的EAX指向的就是暗码,得到暗码后我们只要根据注册码变形的那个逆运算就可以推出真正的注册码。
这次我们改用打开文件的方式,也加入了对话框窗口。详细代码如下:.586 .model flat , stdcall option casemap :none ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> include windows.incinclude user32.incinclude kernel32.incincludelib user32.libincludelib kernel32.libinclude comdlg32.incincludelib 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 GetOpenFileName , ADDR ofn ;******************************************************************** ; 创建进程 ;********************************************************************
.if eax ==TRUE
invoke GetStartupInfo ,addr startinfo
invoke CreateProcess , addr buffer, NULL, NULL, NULL, FALSE ,\
DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS, NULL, NULL,\
addr startinfo, addr pi ;******************************************************************** ; 调试进程 ;******************************************************************** .while TRUE
invoke WaitForDebugEvent , addr 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写个注册机生成器:
[招生]系统0day安全班,企业级设备固件漏洞挖掘,Linux平台漏洞挖掘!
上传的附件: