本程序在WinXP/SP2、ra2之1.006英文版(有中国超牛机器人的那个)运行通过且稳定无误。
;------------------------------------------------------------------------
;文件一:Ra2MDF.Asm 启动游戏 和 DLL注入
;------------------------------------------------------------------------
.586
.model flat, stdcall
option casemap :none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
.code
;------------------------------------------------------------------------
;把本程序拷入游戏文件夹,运行本程序,游戏被启动,本程序在后台运行,无界面
;本程序运行后启动“ra2.exe”,再由“ra2.exe”启动游戏程序,之后“ra2.exe”
;无用了,停掉它以节约内存。
;------------------------------------------------------------------------
_StartGame proc
LOCAL StartInfo:STARTUPINFO
LOCAL PI:PROCESS_INFORMATION
invoke RtlZeroMemory,addr StartInfo,sizeof STARTUPINFO
mov StartInfo.cb,sizeof STARTUPINFO
mov ecx,GAME_NAME
xor edx,edx
invoke CreateProcess,edx,ecx,edx,edx,edx,edx,edx,edx,addr StartInfo,addr PI ;启动游戏
invoke Sleep,15000 ;等10秒,游戏程序应该启动了吧?
invoke TerminateProcess,PI.hProcess,0h ;没用了,停掉它
invoke CloseHandle,PI.hProcess ;释放内存
ret
GAME_NAME:
db "ra2.exe",0
_StartGame endp
start:
;------------------------------------------------------------------------
;
;
;------------------------------------------------------------------------
Main proc uses edi esi
LOCAL @dwProcessID,@lpLoadLibrary,@lpDllName,@szDllFullPath[MAX_PATH]
invoke _StartGame ;启动游戏
lea edi,@szDllFullPath
invoke GetCurrentDirectory,MAX_PATH,edi
call @F
db '\Ra2MD.dll',0
@@: push edi
call lstrcat ;获取dll的全路径文件名
call @F
db 'Kernel32.dll',0
@@: call GetModuleHandle
call @F
db 'LoadLibraryA',0
@@: push eax
call GetProcAddress ;获取LoadLibrary函数地址
mov @lpLoadLibrary,eax
invoke GetForegroundWindow ;取得游戏窗口的句柄
lea esi,@dwProcessID
invoke GetWindowThreadProcessId,eax,esi ;获取进程ID
invoke OpenProcess,PROCESS_CREATE_THREAD or PROCESS_VM_OPERATION or \
PROCESS_VM_WRITE,FALSE,@dwProcessID ;打开进程
.if eax
mov esi,eax
;********************************************************************
; 在进程中分配空间并将DLL文件名拷贝过去,然后创建一个LoadLibrary线程
;********************************************************************
invoke VirtualAllocEx,eax,NULL,MAX_PATH,MEM_COMMIT,PAGE_READWRITE;
.if eax
mov @lpDllName,eax
invoke WriteProcessMemory,esi,eax,edi,MAX_PATH,NULL
invoke CreateRemoteThread,esi,NULL,0h,@lpLoadLibrary,@lpDllName,0h,NULL ;完成DLL注入
invoke CloseHandle,eax
.endif
invoke CloseHandle,esi
.endif
invoke ExitProcess,NULL
Main endp
end start
;-------------------------------------------------------------------------------------
;文件二:Ra2DLL.Asm 用来被嵌入到其它进程执行的dll,完成热键、搜索、修改等全部功能
;作为游戏进程的一部分,可以直接访问游戏内存,不用ReadProcessMemory和WriteProcessMemory
;这样搜索速度相对要快一些。本例中所说的游戏版本与前面的相同。
;-------------------------------------------------------------------------------------
.586
.model flat, stdcall
option casemap :none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
ADD_DATA1 equ 008373cch ;00883c84h ;其他版本要改这个地址值
;ADD_DATA2 equ 008373d0h ;00883c88h ;这个不用
ADD_DATA3_START equ 01000000h ;搜索范围开始地址,可根据需要改动,但要跳过前两个地址
ADD_DATA3_END equ 0f600000h ;搜索范围结束地址,可根据需要改动
MEMSIZE equ 10000h ;每次读取数据块的大小,不宜太小
.code
_GetDataAddr proc uses esi edi num,hmem
LOCAL N,ListMemSize,pListMem,ReadSize
LOCAL mbi:MEMORY_BASIC_INFORMATION
invoke GlobalLock,hmem ;锁定保存搜索结果的内存
mov pListMem,eax
invoke GlobalSize,hmem;保存搜索结果的内存大小
mov ListMemSize,eax
mov edi,ADD_DATA1 ;保存金钱数,以便后面比较搜索,原理见前文
mov edi,[edi]
mov ecx,ADD_DATA3_START ;设置要搜索的内存地址范围开始处
.repeat ;循环搜索游戏内存
@@: invoke VirtualQuery,ecx,addr mbi,sizeof MEMORY_BASIC_INFORMATION ;返回页面虚拟信息
.if mbi.State == MEM_COMMIT && mbi.Protect == PAGE_READWRITE ;已提交且为可读写的区域,加速搜索
invoke IsBadWritePtr,mbi.BaseAddress,mbi.RegionSize
.if !eax
mov esi,mbi.BaseAddress
xor ecx,ecx
.repeat
.if edi==[esi+ecx] ;数值相等,找到了?
mov eax,num ;地址num中记录了搜索结果的个数
inc dword ptr[eax] ;搜索的结果个数加一
mov eax,dword ptr[eax]
shl eax,2h ;保存结果所需的内存大小
.if eax>ListMemSize ;如果搜索到的结果较多,内存用完,要重新分配内存
push eax
push ecx
add eax,1000h ;追加4K内存
invoke GlobalReAlloc,hmem,eax,GMEM_MOVEABLE ;重新分配内存,原来的数据被复制过来
invoke GlobalLock,eax
mov pListMem,eax ;保存搜索结果的内存首地址
invoke GlobalSize,hmem
mov ListMemSize,eax ;保存搜索结果的内存大小
pop ecx
pop eax
.endif
add eax,pListMem ;相当于pListMem[num]
mov edx,esi
add edx,ecx ;首地址+偏移地址=实际地址
mov [eax-4h],edx ;搜索的结果保存起来,pListMem[num-1]=实际地址
.endif
add ecx,4h ;金钱数为DWORD型数值,考虑到内存对齐,这里是不用担心漏掉的
.until ecx>=mbi.RegionSize
.endif
.endif
mov ecx,mbi.BaseAddress
add ecx,mbi.RegionSize ;下一区段首地址
.until ecx>=ADD_DATA3_END ;下一区段在搜索范围之外了吗?是则完成第一遍搜索
ret
_GetDataAddr endp
;------------------------------------------------------------------------
;第二、三……遍的搜索,在第一次的结果中找,速度极快
;第二次按下“*”键便来到这里,一般只要两遍就可锁定。算法:有用地址向前移
;结果个数由num返回,如果num==1就算找到正确的金钱地址了
;------------------------------------------------------------------------
_FindAddrInList proc uses edi esi num,hmem
LOCAL N
invoke GlobalLock,hmem
mov edi,eax ;前次搜索结果保存的内存首地址
xor esi,esi ;指针,指向第一个结果
mov N,esi ;本次搜索到的个数初始化为0
.repeat ;逐个比较
mov edx,[edi+esi*4h] ;相当于edx=hmem[esi]
mov eax,ADD_DATA1
mov eax,[eax]
.if eax==[edx] ;等于金钱数吗?等则记录下来
push [edi+esi*4h]
mov eax,N
pop [edi+eax*4h] ;相当于hmem[N]=hmem[esi],即把搜索到的结果向hmem内存前面移
inc N ;搜索到的个数加一
.endif
mov eax,num
inc esi ;指针指向下一个结果
.until esi>=[eax] ;每个都比较过了吗?是则完成这次搜索
mov edx,N ;这次搜索到的结果个数
mov [eax],edx ;修改原来的个数
shl edx,2h ;个数×4=内存大小
invoke GlobalReAlloc,hmem,edx,GMEM_MOVEABLE ;释放多余的内存
ret
_FindAddrInList endp
;------------------------------------------------------------------------
;找到了正确的地址,可以锁定金钱数值了:),每五秒锁定一次,按“-”键停锁
;游戏中钱数看起来有增有减,象未锁定一样,别人不容易发现你作弊
;------------------------------------------------------------------------
_WriteProcessData proc uses edi esi hmem
mov esi,5h ;要锁定的金钱数,别太多,多了是会招贼来偷的:)
invoke GlobalLock,hmem
mov edi,[eax] ;游戏中真正的保存金钱数的地址
.repeat
.if esi>=5h ;每五循环锁定一次
xor esi,esi ;循环次数清0
mov dword ptr[edi],2000
.endif
invoke Sleep,1000 ;定时一秒
inc esi ;循环次数加一
invoke GetAsyncKeyState,VK_SUBTRACT ;按了“-”键吗?
.until eax;如果按了“-”键则结束循环
ret
_WriteProcessData endp
;------------------------------
;新线程,检查按键,响应用户请求
;------------------------------
ThreadProc Proc pParam:LPVOID
Local @dwNum,@hMem
invoke GlobalAlloc,GMEM_MOVEABLE,1000h ;预留内存空间,搜索时用来保存结果
mov @hMem,eax ;搜索结果个数的不确定性需要我们用GlobalReAlloc来重新分配内存大小
mov @dwNum,0h ;搜索结果个数初始化为0
.while TRUE
invoke GetAsyncKeyState,VK_SUBTRACT ;按了“-”键吗?
.if eax
mov @dwNum,0h ;搜索结果个数置0,表示从未搜索过
invoke SetCursorPos,9h,9h ;把鼠标置于屏幕左上角,提醒游戏者,第一遍搜索时特别有用
.endif
invoke GetAsyncKeyState,VK_MULTIPLY ;按了“*”键吗?
.if eax ;按了热键“*”
.if @dwNum==0h ;搜索结果个数为0则从未搜索过,进行第一遍搜索
invoke _GetDataAddr,addr @dwNum,@hMem
.elseif @dwNum>1h ;搜索结果个数非0则至少已搜过一遍且未找到正确地址
invoke _FindAddrInList,addr @dwNum,@hMem ;再搜它一遍或几遍
.endif
.if @dwNum==1h ;搜索结果为1,说明找到正确地址了
invoke _WriteProcessData,@hMem ;去锁定它!
.endif
invoke MessageBeep,MB_OK ;发声提示游戏者,按键收到,该做的本程序都做过了
invoke SetCursorPos,9h,9h ;把鼠标置于屏幕左上角,提醒游戏者,第一遍搜索时特别有用
.endif
invoke Sleep,1000
.endw
invoke GlobalUnlock,@hMem
invoke GlobalFree,@hMem
ret
ThreadProc endP
DllEntry Proc _hInstance,_dwReason,_dwReserved
LOCAL @dwThreadID
.if _dwReason == DLL_PROCESS_ATTACH
invoke CreateThread,NULL,0h,offset ThreadProc,NULL,NULL,addr @dwThreadID ;创建一个新线程
invoke CloseHandle,eax
.endif
mov eax,TRUE
ret
DllEntry Endp
End DllEntry
- 标 题:游戏修改的常用方法之二Dll注入(asm源码详注)
- 作 者:王仁军
- 时 间:2008-06-15 20:29
- 链 接:http://bbs.pediy.com/showthread.php?t=66625