TA的每日心情 | 开心 2024-12-9 18:45 |
---|
签到天数: 124 天 [LV.7]常住居民III
|
欢迎您注册加入!这里有您将更精采!
您需要 登录 才可以下载或查看,没有账号?注册
x
1.引申
计算机发展历程从单道技术道多道技术,到后来的分时技术,为了解决资源的高效分配和CPU利用率的提高,引入进程的概念可以说是一个操作系统的一个里程碑。这里不多引申,主要是想说明,系统里各个进程的运行是相互独立的,每个进程独享自己的进程空间。在单CPU上,微观上各个进程串行运作,但是宏观上表现为各个进程是并行处理。进程只是 一个“容器”,它复杂向系统申请资源,真正的执行操作的是线程。这里仅仅是简单的说说...
在进程加载的时候,代码被映射到进程的地址空间中,然后利用各种分页分段技术,在运行期将代码映射到具体的物理内存上,方可执行。Windows下进程的地址空间在逻辑上是相互独立的,而在物理上却是相互重叠的,所谓的重叠是指在一块内存区域可能被多个进程同时使用(共享内存便是这个原理)。
2.注入的原理
上面说了进程的地址空间是相互独立的,那么怎么使本进程的代码(比如一个CALL函数)在目标进程里运行呢,那就需要注入了。将代码利用一定的技术手段加载进目标进程空间,然后触发使之执行,这便是代码注入的雏形。当然,后来的技术做了很多“变种”,以至于这种技术被病毒技术得到了很好的利用。
明白了上面的原理,就可以自己进行我们下面的这个实例了。
我们调用完美游戏的CALL,必须要吧代码写进完美游戏进程地址空间,有关于这一系列的操作,Windows提供了相应的API,我也把它封装了成函数,中间过程的一些重要的细节等会解剖。
3.远程注入
这是一种比较普遍的做法,但是也有着明显的缺点,频繁的注入比较容易产生内碎片的大量累积。当然,有改进的做法,比如固定申请的地址,一次申请,多次利用,这里不在多说。
这里仅作为技术探讨,列出主要的实现步骤。分二层实现,稍作了封装
我们以带参数的选怪CALL为例:
- void CallSelectMonster (long MonsterSn)
- {
- DWORD dwAddr = 0x59B8B0;
- _asm{
- pushad
- mov edi , MonsterSn
- push edi
- mov eax , dword ptr [0x950954]
- mov eax , dword ptr [eax]
- mov ecx , dword ptr [eax + 0x20]
- 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了
- void CallSelectMonster (long * MonsterSn)
- {
- long sn = *MonsterSn;
- DWORD dwAddr = 0x59B8B0;
- _asm{
- pushad
- mov edi , sn
- push edi
- mov eax , dword ptr [0x950954]
- mov eax , dword ptr [eax]
- mov ecx , dword ptr [eax + 0x20]
- 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函数,那么,这些操作你就是考虑自己封装,理论上就像是模拟一个自己进程空间的读写函数。呵呵,不是很难,但是也需要一些技巧。 |
|