好友
阅读权限10
听众
最后登录1970-1-1
|
在下猫猫
发表于 2024-7-17 23:11
免责声明:本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关. 本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请联系作者立即删除.
一、实现功能分析
1、我们要实现通过鼠标位置判断当前位置下是否有雷,首先肯定就是需要分析雷区是存放在哪儿的,一般像扫雷这种游戏会将其数据存放在一个全局变量的二维数组中。那么如何找到这个二维数组呢,因为扫雷游戏需要对雷区进行各种设置,所以肯定会有访问这个数组的代码,只要我们找到这个数组代码我们就可以知道这个数组是存放在内存的什么位置了,而扫雷游戏肯定是需要绘制各种图片的,并且因为游戏较小很可能会使用GDI来进行绘制,而一般游戏要绘制都会使用双缓冲机制,因为不使用双缓冲很可能会导致闪屏,而要使用双缓冲就需要使用到Bitblt这个API,我们可以通过对这个API下断点,这样就可以通过栈回溯找到绘制的代码,而绘制的代码很有可能就访问了雷区数组,我们就可以通过他访问雷区数组的代码找到雷区数组到底在哪儿了。
2、我们通过栈回溯的方式找到调用bitblt函数的地方,发现他一共使用了两个数组,我们可以跟进这两个数组中进行查看。
3、查看上面第一个数组的地址我们可以看到,里面存放的数据很像扫雷的数组,所以我们就需要获取这个数组地址,并且在他最上面有几个数据是对应的列和行,所以我们可以通过获取上面的数据来获得雷区有多少列多少行。
4、接着我们就可以对写插件了,我们可以通过远程线程注入代码,这样就可以在对方的进程里使用我们刚才找到的数组地址1005340获得雷区的数据,并判断下面是否是雷就可以了。要远程线程注入代码,我们需要先调用FindWindow来获取扫雷的窗口句柄,接着通过GetWindowThreadProcessId来获取进行id,有了进程id后我们就可以使用OpenProcess打开这个进程了,接着我们可以用VirtualAllocEx在对方进程申请一块内存,我们可以在这块内存中写我们需要注入的代码,接着就可以调用WriteProcessMemory往对方进程写入代码了,最后我们创建一个远程线程来进行代码就可以了。
;打开进程
invoke FindWindow,offset class_name,NULL
.if eax == NULL
MOV EAX,FALSE
JMP SAFE_EXIT
.endif
mov @hWnd,eax
invoke GetWindowThreadProcessId,@hWnd,addr @dwPid
.if eax == FALSE
JMP SAFE_EXIT
.endif
invoke OpenProcess,PROCESS_ALL_ACCESS,FALSE,@dwPid
.if eax == FALSE
JMP SAFE_EXIT
.endif
mov @hProcess,eax
;申请内存
invoke VirtualAllocEx,@hProcess, NULL, 1000h, MEM_COMMIT, PAGE_EXECUTE_READWRITE
.if eax == NULL
MOV EAX,FALSE
JMP SAFE_EXIT
.endif
mov @lpBuf, eax
;写入代码
invoke WriteProcessMemory,@hProcess,@lpBuf,offset inject_code,inject_end - inject_code,ADDR @dwBytes
.if eax == FALSE
JMP SAFE_EXIT
.endif
;创建远程线程
invoke CreateRemoteThread,@hProcess, NULL, 0, @lpBuf, NULL, NULL, NULL
.if eax == NULL
JMP SAFE_EXIT
.endif
mov @hThread, eax
5、接着我们就可以写我们要注入的代码了,我的想法是在鼠标停在有雷的格子上时,我们将标题修改为扫雪,这样就可以达到获取格子下是否有雷的效果了。但现在又出现了一个问题,因为我们要修改标题就需要使用setwindowtext这个API,但是现在我们的代码在对方的进程,而代码却是在我们的程序写的,这就会出现问题,因为在我们调用API时,编译器会在数据区定义一个变量,这时候我们call调用时实际上时call的这个变量内存地址的数据,当软件运行时操作系统就会找到这个变量,并将API真正的地址存入进去,但在对方进程是没有这个变量的,所以就会导致无法调用到API,所以我们可以在我们的程序里获取API得地址,让对方进程调用API时就call我们获取到的地址,只要他的模块基址是同一个就不会出现问题。那么要怎么才能让我们注入的代码,调用我们获取到的函数地址呢,这里我们可以代码从定位一下,我们可以在需要修改地址的地方写一个标号,拿到这个标号的地址,根据其二进制进行偏移,偏移到地址处将我们的地址填上去就可以了。而为了方便后面写代码方便我们可以把获取模块基址API和获取函数地址API的地址重定位,拿到这两个函数后,我们就可以直接用这个两个函数进行操作了,不用一直进行代码重定位。但代码段是没有可写属性的,所以在修改之前我们需要先修改器内存属性为可读可写。
;修改内存属性
invoke VirtualProtect,offset inject_code, 1000h, PAGE_EXECUTE_READWRITE, addr @dwBytes
;获取api地址
invoke GetModuleHandle,offset msg_kernel32
mov @hModule, eax
;获取getmodehandle函数地址并修改注入代码的变量
invoke GetProcAddress, @hModule, offset msg_getmodulehandle
sub eax, @hModule
add eax, @hModule ;远程进程的模块基址
mov ebx, offset GET_MODUL_HANDLE
mov [ebx], eax
;获取getproceaddress函数地址并修改注入代码的变量
invoke GetProcAddress, @hModule, offset msg_Getprocaddress
sub eax, @hModule
add eax, @hModule ;远程进程的模块基址
mov ebx, offset GET_PROC_ADDRESS
mov [ebx], eax
6、但现在我们还面临一个使用字符串的问题,我们在对方代码定义的常量字符串,如果直接通过标号进行使用肯定会发生错误的,因为标号存的是在我们程序的地址,而数据在对方程序里就和我们程序里所在的数据位置不一样了,所以我们无法通过标号来使用字符串,这里我们可以通过定义一个标号,接着在标号的上一行call这个标号,因为call这条指令会将下一行汇编存到栈中,而我们在下一行汇编使用pop 这时候就拿到了标号的eip了(因为我们无法直接拿eip寄存器的值,所以需要使用这个方式)。接着我们用标号减去我们eip,因为标号是在我们程序的地址,而eip是在对方程序的地址,而他们都是在同一个位置的,只是在不同程序,所以通过减法运算我们就得到了对方代码位置和我们代码位置的偏移,而字符串肯定也是偏移了这么多,所以我们用字符串的标号+上我们算出来的偏移就是字符串在对方进程中的位置了。
label1:
pop ebx
;对方进程标号的地址减去我们标号的地址,等于对方代码和我方代码的偏移
sub ebx,label1
;代码的偏移 + 字符串的绝对地址 = 对方进程字符串的地址
lea edx, [ebx+offset STR_USER_32] ;获取user32字符串
。
7、接着就只需要获取模块、findwindow、setwindowtext、SetWindowLongW等函数地址,我们就可以在对方进程中进行调用了。通过SetWindowLongW拦截对方的回调函数,判断鼠标移动的消息,就可以获取鼠标所在的行和列,接着判断鼠标所在的位置是否是雷就可以了。
lea edx, [ebx+offset STR_USER_32] ;获取user32字符串
push edx
call [ebx + offset GET_MODUL_HANDLE];调用函数获取user32模块
mov [ebx + offset hModule_User32],eax;保存user32句柄
lea edx, [ebx+offset STR_FIND_WINDOWA] ;获取findwindow字符串
push edx
push [ebx + offset hModule_User32]
call dword ptr[ebx + offset GET_PROC_ADDRESS];调用函数获取findwindwindow函数地址
mov [ebx+offset FIND_WINDOWA],eax;保存函数地址
lea edx,[ebx + offset STR_SET_WINDOW_TEXTA];获取setwindowtext字符串
push edx
push [ebx + offset hModule_User32]
call dword ptr[ebx + offset GET_PROC_ADDRESS];调用函数获取setwindowtext函数地址
mov [ebx+offset SET_WINDOW_TEXTA],eax;保存函数地址
lea edx,[ebx + offset STR_SET_WINDOW_LONGW];获取SetWindowLongW字符串
push edx
push [ebx + offset hModule_User32]
call dword ptr[ebx + offset GET_PROC_ADDRESS];调用函数获取SetWindowLongW函数地址
mov [ebx+offset SET_WINDOW_LONGW],eax;保存函数地址
push 0
lea edx,[ebx + offset title2];获取扫雷字符串
push edx
call dword ptr[ebx + offset FIND_WINDOWA] ;获取扫雷hwnd
mov [ebx+offset WinmineHwnd],eax;保存扫雷hwnd
lea eax,[ebx+offset winmine_Callbackproc];获取回调函数的地址
push eax
push -4h;设置参数为修改回调函数
push [ebx+offset WinmineHwnd];传入扫雷句柄
call dword ptr[ebx + offset SET_WINDOW_LONGW];调用函数修改扫雷的回调函数
mov [ebx+offset g_OldProc],eax;返回值为旧回调的地址
loop1:
jmp loop1;无限循环不退出线程
retn 4
winmine_Callbackproc proc stdcall ,hWin:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
call @F
@@:
pop ebx
sub ebx, @B
cmp uMsg,WM_MOUSEMOVE ;判断是否是鼠标移动的消息
jne exit1;如果不是鼠标移动的消息就调用旧回调进行处理
mov ecx,lParam;将鼠标当前的坐标当做参数来获取在扫雷的列
mov edx,ecx
push edx
call GetCol
mov esi,eax;将返回值存起来
shr edx,10h;将高16位放到低16位
push edx
call GetLine1;获取鼠标所在行
mov edi,eax
push esi
push edi
call IsMine;判断是否是雷
cmp eax,1;比较eax是否为1,如果等于1就说明是雷就跳转到设置标题位扫雪,否则就设置标题为扫雷
je is_mine
lea edx,dword ptr[ebx + offset title2]
push edx
lea edx,dword ptr[ebx + offset WinmineHwnd]
mov ecx,[edx]
push ecx
call dword ptr[ebx + offset SET_WINDOW_TEXTA] ;SetWindowText("扫雷")
jmp exit1
is_mine:
lea edx,dword ptr[ebx + offset title1]
push edx
lea edx,dword ptr[ebx + offset WinmineHwnd]
mov ecx,[edx]
push ecx
call dword ptr[ebx + offset SET_WINDOW_TEXTA] ;SetWindowText("扫雪")
exit1:
push lParam
push wParam
push uMsg
push hWin
call [ebx+offset g_OldProc]
ret
winmine_Callbackproc endp
|
免费评分
-
查看全部评分
|