[编程工具]masm32(9.00)+RadAsm2.2.0.9
[调试工具]OD1.10
[调试平台]WinXP/SP2
先说明一下,我编写这个内存注册机只是为了学习一点程序的调试原理,它的实用性不说也罢,因为现的软件保护谁都知道,想这么容易取得注册码,门都没有,这也许正是刘健英放弃KeyMake的原因吧。我不想,也没有能力去步他的后尘,只是好奇,玩玩而已。
内存注册机实际是个超小的调试器,以调试的方式打开目标软件进程,在特定的地址设置 int 3 断点,程序运行到断点处被挂起,这个时候就可以用 ReadProcessMemory 或 GetThreadContext 函数取得目标软件特定内存或特定寄存器中的内容,显示出来就好了。只是前面提到的地址、寄存器等只有你跟踪了目标软件的注册算法后才会得到。根据注册算法的不同,注册机中的某些代码也可能要调整,所以我的代码对不会编程的人来说就是1000%的垃圾。
有些软件加了壳,不过我终于想出办法来对付它了,有壳我照样可以断下来,取到我想要的数据。杀毒软件对我的注册机也不感兴趣,大概是太臭了吧~O~。
内存注册机.Asm文件:
.386
.model flat, stdcall ;32 bit memory model
option casemap :none ;case sensitive
include 内存注册机.inc
.code
start:
invoke InitCommonControls
invoke GetRegKey
invoke ExitProcess,0
;########################################################################
GetRegKey proc uses edi esi
LOCAL @ThreadConText:CONTEXT
LOCAL @StartInfo:STARTUPINFO
LOCAL @DBGEvent:DEBUG_EVENT
LOCAL @TmpCode,@BakCode
LOCAL @FullPath[512]:byte
invoke GetCommandLine
lea esi,@FullPath
invoke lstrcpy,esi,eax
mov edi,esi
xor edx,edx
xor ecx,ecx
cld
.while TRUE ;找最后一个“\”
lods byte ptr[esi]
inc ecx
.break .if !al
.continue .if al!='\'
mov edx,ecx ;记下最后一个“\”的位置
.endw
add edi,edx
invoke lstrcpy,edi,addr szEXEName ;生成绝对路径,否则由CreateProcess创建的进程可能会出问题
invoke GetStartupInfo,addr @StartInfo
invoke CreateProcess,NULL,addr @FullPath,NULL,NULL,FALSE,DEBUG_PROCESS+DEBUG_ONLY_THIS_PROCESS,NULL,NULL,addr @StartInfo,addr PI
.if !eax ;创建debugee进程失败的话提示、结束
invoke MessageBox,NULL,addr szMsgErr,NULL,MB_ICONERROR
jmp ENDPROC
.endif
.while TRUE
invoke WaitForDebugEvent,addr @DBGEvent,INFINITE ;等待调试事件的发生
.break .if @DBGEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT ;你退出了我也就退出吧!
.if TimesBreak ;debugee执行第一条指令之前我们不必做无用功
.if TimesBreak<BREAKFLAG ;已经取过注册码了吗?
mov eax,@BakCode
.if al!=byte ptr[OldCode] ;如果debugee加了壳,而且还没正确设置int 3断点
invoke ReadProcessMemory,PI.hProcess,BreakPoint,addr @TmpCode,sizeof @TmpCode,NULL
mov eax,@TmpCode
.if al==byte ptr[OldCode] ;壳已经解码完毕,可以设断点了
mov @BakCode,eax ;记住,我们在此已经役过断点了,用它作标志,还要用它恢复现场
invoke WriteProcessMemory,PI.hProcess,BreakPoint,addr BreakCode,sizeof BreakCode,NULL ;设断点
.endif
.endif
.endif
.endif
.if @DBGEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
.if @DBGEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
.if TimesBreak ;已经不止一次发生int 3中断的话
invoke CloseHandle,PI.hThread ;释放旧hThread的内存
invoke OpenThread,THREAD_GET_CONTEXT or THREAD_QUERY_INFORMATION,FALSE,@DBGEvent.dwThreadId ;由ThreadID得到hThread 想依此来解决多线程问题,会不会有问题,请高人指点
mov PI.hThread,eax ;保存备用
mov @ThreadConText.ContextFlags,CONTEXT_FULL ;访问所有的寄存器
invoke GetThreadContext,PI.hThread,addr @ThreadConText ;取debugee的数据
dec @ThreadConText.regEip ;EIP已经指向int 3中断的下一条代码了,让它重新指向int 3
mov ecx,@ThreadConText.regEip ;取debugee的EIP
.if ecx==BreakPoint ;是我们要断下的地方吗?是就取出数据,并恢复现场
invoke WriteProcessMemory,PI.hProcess,ecx,addr @BakCode,sizeof @BakCode,NULL ;恢复debugee原来的代码
invoke SetThreadContext,PI.hThread,addr @ThreadConText ;修改debugee的EIP,让其继续运行
invoke GetModuleHandle,NULL
lea edx,@ThreadConText ;把此结构的地址传给窗口,以便于显示
invoke DialogBoxParam,eax,IDD_DLGMAIN,NULL,addr DlgProc,edx ;弹出窗口,输出注册码
mov TimesBreak,BREAKFLAG ;设置标志,已经取过注册码了
.endif
.else ;如果debugee未加壳,就此两句可以设好断点,若加了壳就不起作用了,因为壳会修改掉我们的修改
invoke ReadProcessMemory,PI.hProcess,BreakPoint,addr @BakCode,sizeof @BakCode,NULL
invoke WriteProcessMemory,PI.hProcess,BreakPoint,addr BreakCode,sizeof BreakCode,NULL
.endif
inc TimesBreak
.endif
mov eax,DBG_CONTINUE
jmp @F
.endif
mov eax,DBG_EXCEPTION_NOT_HANDLED
@@:
invoke ContinueDebugEvent,@DBGEvent.dwProcessId,@DBGEvent.dwThreadId,eax
.endw
invoke CloseHandle,PI.hProcess
invoke CloseHandle,PI.hThread
ENDPROC:
ret
GetRegKey endp
DlgProc proc uses ebx hWin:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
mov eax,uMsg
.if eax==WM_INITDIALOG
mov ebx,lParam
assume ebx:ptr CONTEXT ;ebx作为CONTEXT类型指针使用
;要显示什么数据,怎么显示,那你就去分析debugee的注册算法吧,我这里只是示例
;invoke ReadProcessMemory,PI.hProcess,RegKeyAddr,addr Buffer,sizeof Buffer,NULL ;从特定内存地址中取注册码
invoke wsprintf,addr Buffer,addr szFmt,[ebx].regEax ;从特定寄存器中取注册码
invoke SetDlgItemText,hWin,IDC_REGKEY,addr Buffer
assume ebx:nothing
.elseif eax==WM_CLOSE
invoke EndDialog,hWin,0
.else
mov eax,FALSE
jmp @F
.endif
mov eax,TRUE
@@:
ret
DlgProc endp
end start
内存注册机.Inc文件:
include windows.inc
include kernel32.inc
include user32.inc
include Comctl32.inc
include shell32.inc
includelib kernel32.lib
includelib user32.lib
includelib Comctl32.lib
includelib shell32.lib
DlgProc PROTO :HWND,:UINT,:WPARAM,:LPARAM
GetRegKey PROTO
.const
IDD_DLGMAIN equ 101
IDC_REGKEY equ 1003
BREAKFLAG equ 100000h
.data
szFmt db '%1X',0
szMsgErr db '本注册机必须与下面的软件放在同一目录内:',0dh ;中间不要有其它变量定义
szEXEName db '例子.exe',0 ;换成要注册的软件可执行文件名
BreakPoint dd 0401131h ;可以改成要设断点的地址
OldCode db 050h ;BreakPoint处的第一字节内容,帮我们判断壳是否操作完毕,完毕才能设断点
;RegKeyAddr dd 40305ch ;注册码在debugee中的存放地址
BreakCode db 0CCh,90h,90h,90h ;最好别改动
TimesBreak dd 0 ;最好别改动
;#########################################################################
.data?
PI PROCESS_INFORMATION<>
Buffer db 256dup(?)
************************************************************************************************************
我这个贴子只说了内存注册机的一般原理,未做成通用模板,因此需要会编程的使用者视具体情况添加或改动代码,比如多线程、多次中断在同一地址等。后面有人跟贴提到这两个问题。对于多线程问题,我想了个对策,代码见上文,调试了没问题。对于第二个问题,最好去读一下我转贴的《Win32调试API教程》之第三部分。我的大致思路是:
1.第一次正确发生int3中断时,设为单步模式,为的是后面有机会再写入断点。也可以设硬件断点,那是另一套思路了。
2.在单步事件到来时,重写入断点代码,暂不单步了
.if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
(前面的判断代码此处略)
mov @ThreadConText.ContextFlags, CONTEXT_CONTROL
invoke GetThreadContext, PI.hThread, addr @ThreadConText
or @ThreadConText.regFlag,100h ;设为单步模式,为的是后面有机会再写入断点
invoke WriteProcessMemory,PI.hProcess,ecx,addr @BakCode,sizeof @BakCode,NULL ;恢复debugee原来的代码
invoke SetThreadContext,PI.hThread,addr @ThreadConText ;修改debugee的EIP,让其继续运行
inc TimesBreak
(后面的代码此处略)
.elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP ;int3中断后紧接着单步一次
invoke WriteProcessMemory,PI.hProcess,BreakPoint,addr BreakCode,sizeof BreakCode,NULL ;再次写入中断代码,对付同一地址多次中断。
马上要放假了,比较忙,没有时间详细设计和调试,只能这样了,算是抛砖引玉吧。假期我要好好休息一下,不会来这里了(要扔砖头的尽管扔,反正我不知道^O^)。希望收假后能看到更好的解决方法。
********************************************************************************************************************
附件中是asm源码和例子程序