admin 发表于 2010-5-18 01:20:28

C++实现远程注入调用游戏CALL

1.引申
   计算机发展历程从单道技术道多道技术,到后来的分时技术,为了解决资源的高效分配和CPU利用率的提高,引入进程的概念可以说是一个操作系统的一个里程碑。这里不多引申,主要是想说明,系统里各个进程的运行是相互独立的,每个进程独享自己的进程空间。在单CPU上,微观上各个进程串行运作,但是宏观上表现为各个进程是并行处理。进程只是 一个“容器”,它复杂向系统申请资源,真正的执行操作的是线程。这里仅仅是简单的说说...
   在进程加载的时候,代码被映射到进程的地址空间中,然后利用各种分页分段技术,在运行期将代码映射到具体的物理内存上,方可执行。Windows下进程的地址空间在逻辑上是相互独立的,而在物理上却是相互重叠的,所谓的重叠是指在一块内存区域可能被多个进程同时使用(共享内存便是这个原理)。
2.注入的原理
   上面说了进程的地址空间是相互独立的,那么怎么使本进程的代码(比如一个CALL函数)在目标进程里运行呢,那就需要注入了。将代码利用一定的技术手段加载进目标进程空间,然后触发使之执行,这便是代码注入的雏形。当然,后来的技术做了很多“变种”,以至于这种技术被病毒技术得到了很好的利用。
      明白了上面的原理,就可以自己进行我们下面的这个实例了。
      我们调用完美游戏的CALL,必须要吧代码写进完美游戏进程地址空间,有关于这一系列的操作,Windows提供了相应的API,我也把它封装了成函数,中间过程的一些重要的细节等会解剖。
3.远程注入
    这是一种比较普遍的做法,但是也有着明显的缺点,频繁的注入比较容易产生内碎片的大量累积。当然,有改进的做法,比如固定申请的地址,一次申请,多次利用,这里不在多说。
    这里仅作为技术探讨,列出主要的实现步骤。分二层实现,稍作了封装
    我们以带参数的选怪CALL为例:
voidCallSelectMonster (long MonsterSn)
{

DWORD dwAddr = 0x59B8B0;
_asm{
pushad
mov edi , MonsterSn
push edi
mov eax , dword ptr
mov eax , dword ptr
mov ecx , dword ptr
add ecx , 0xec
call dwAddr
popad
}
}
      这里CALL存在问题,等会解决
   
    1>. 第一层调用:选怪函数,里面封装了调用注入函数的操作。
void CSkill::SelectMonster(DWORD ProcessId, long MonsterSn)
{
long * m_pGuaiId = & MonsterSn ;

//调用注入函数
InjectRemoteFunc(ProcessId,CallSelectMonster,m_pGuaiId,sizeof(*m_pGuaiId));
}
   
      2>.封装注入代码并调用执行的操作。

//封装远程注入的函数
//参数 1. 进程ID
//参数 2. 被注入函数指针<函数名>
//参数 3. 参数
//参数 4. 参数长度

BOOL InjectRemoteFunc(DWORD dwProcId,LPVOID mFunc, LPVOID pRemoteParam, DWORD ParamSize)
{
HANDLE hProcess;
LPVOID ThreadAdd;
LPVOID ParamAdd = NULL;
HANDLE hThread= NULL;
DWORD lpNumberOfBytes;
BOOL BO;
ThreadAdd = mFunc;
hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwProcId);//打开被注入的进程
ThreadAdd = VirtualAllocEx(hProcess,NULL,4096,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
BO = WriteProcessMemory(hProcess,ThreadAdd,mFunc,4096, &lpNumberOfBytes); //写入函数地址
if(ParamSize!=0)
{
ParamAdd = VirtualAllocEx(hProcess,NULL,ParamSize, MEM_COMMIT, PAGE_READWRITE);
BO = WriteProcessMemory(hProcess, ParamAdd, pRemoteParam, ParamSize, &lpNumberOfBytes); //写入参数地址
}
hThread = CreateRemoteThread(hProcess, NULL,0,(LPTHREAD_START_ROUTINE)ThreadAdd, ParamAdd, 0,&lpNumberOfBytes); //创建远程线程
WaitForSingleObject(hThread, INFINITE);//等待线程结束
VirtualFreeEx(hProcess, ThreadAdd, 4096, MEM_RELEASE);
if(ParamSize!=0)
{
VirtualFreeEx(hProcess, ParamAdd, ParamSize, MEM_RELEASE); //释放申请的地址
}
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}

4.分析
    会C++的朋友可以马上要它运行起来,哪怕是控制台下,只要获得了窗口句柄,赋值一个比较合适的怪物ID,做个循环加,都可以实现选怪了。其他附加的代码我不贴出来了,前面说了,这是写给有一定基础的辅助编程的朋友学习讨论的。
    我在前面留了一个小的bug,这也是我主要写这篇教程的原因之一。在处理这个问题的时候,我也郁闷了好一阵,但最后还是解决了。如果现在能看出来bug的朋友,并知道为什么会有这样bug的朋友,可以不用看了,呵呵。
    1>为了说明这个问题,我想引入msdn里的一些说明:
    首先,创建远程线程的函数原型:
HANDLE CreateRemoteThread(
HANDLE hProcess,                        // handle to process
LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD
SIZE_T dwStackSize,                      // initial stack size
LPTHREAD_START_ROUTINE lpStartAddress,    // thread function
LPVOID lpParameter,                      // thread argument
DWORD dwCreationFlags,                  // creation option
LPDWORD lpThreadId                        // thread identifier
);   
    看第四个参数,也就是回调函数(由用户编写,但调用权限由操作系统主宰的函数) ,当调用了这个创建远程线程的函数,只要这个倒数第二个参数设置为0,也就是dwCreationFlags=0,那么回调函数将会立即执行。
    现在看看回调函数的原型:
    DWORD WINAPI ThreadProc(
    LPVOID lpParameter// thread data
    );
发现该函数只接收一个LPVOID类型的参数,那么如果这个参数不是指针行不行呢,当然可行了,呵呵,现在明白了吧,前面的带参数CALL相当于这个“回调函数”,只是样子变了一下而已,这个思想很重要    那么现在改下CALL原型,变成这样就OK了
voidCallSelectMonster (long * MonsterSn)
{
long sn = *MonsterSn;
DWORD dwAddr = 0x59B8B0;
_asm{
pushad
mov edi , sn
push edi
mov eax , dword ptr
mov eax , dword ptr
mov ecx , dword ptr
add ecx , 0xec
call dwAddr
popad
}
}
      到这里,都明白了吧,前面CALL的原型就是错误的,也就是不匹配,当调用创建远程线程时,参数传递就出现问题了。
      作为引申,观察另外一个函数的原型:
    HMODULE LoadLibrary(
    LPCTSTR lpFileName// file name of module
    );

    比较一下

    DWORD WINAPI ThreadProc(
    LPVOID lpParameter// thread data
    );
   
    原型一样,那么可以用LoadLibrary来作这个“回调函数”,又一个变形的回调函数,那么就可以轻松的将一个DLL注入的目标进程了,并且在DLL初始化的时候做很多事情。
5.又一种注入DLL的方式,全局DLL注入
   说下实现的步骤和思想。这种方式可以用 hook + DLL来实现:
   1>.安装一个钩子,将钩子回调函数写在DLL里,在回调函数里new PerfectDialog() ,创建一个窗口,方便操作。
   2>.安装这个钩子,消息触发回调函数,那么指定的进程就会加载这个DLL了,这个进程当然就是你所要操作的进程。这个时候,DLL里代码操作的数据就和目标进程空间里的数据通用了。貌似很爽,调用CALL就好像调用自己进程空间的CALL一样,不用在进程注入了。但是,也会引发一些其他的问题,应该就是对于异常的考虑。比如,用指针读相关数据,指针+偏移,在读相关数据,这些操作都是要进行异常判断。这里不在用ReadProcessMemoryWriteProcessMemory函数,那么,这些操作你就是考虑自己封装,理论上就像是模拟一个自己进程空间的读写函数。呵呵,不是很难,但是也需要一些技巧。
页: [1]
查看完整版本: C++实现远程注入调用游戏CALL