本程序在WinXP/SP2、ra2之1.006英文版(有中国超牛机器人的那个)运行通过且稳定无误。;------------------------------------------------------------------------ ;文件一:Ra2MDF.Asm 启动游戏 和 DLL注入 ;------------------------------------------------------------------------ .586 .model flat , stdcall option casemap :none include windows.incinclude user32.incincludelib user32.libinclude kernel32.incincludelib 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.incinclude user32.incincludelib user32.libinclude kernel32.incincludelib 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
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)