WINDOWS⿊客基础(3):注⼊代码
有使⽤过外挂的朋友应该知道,我们在玩游戏的时候,有很多辅助功能给你使⽤,⽐如吃药,使⽤物品等功能,这个时候我们就是使⽤注⼊代码的技术,简单的来将就是我们让另外⼀个进程去执⾏我们想让它执⾏的代码,这中间的关键函数是CreateRemoteThread
HANDLE WINAPI CreateRemoteThread(
_In_ HANDLE hProcess,
_In_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ SIZE_T dwStackSize,
_In_ LPTHREAD_START_ROUTINE lpStartAddress,
_In_ LPVOID lpParameter,
_In_ DWORD dwCreationFlags,
_Out_ LPDWORD lpThreadId
);
CreateRemoteThread的参数跟CreateThread的参数差不多,多出来的hProcess是我们要对其操作的进程HANDLE,在早期的WINDOWS 版本CreateThread确实是使⽤CreateRemoteThread实现的,就是把hProcess传⼊我们⾃⼰的进程HANDLE
CreateRemoteThread的功能就是在指定的进程创建⼀个线程,这个线程运⾏我们指定的函数,看起来很简单,但是有⼀个问题,就是虚拟内存导致的问题
⼤家都知道,在WINDOWS下是使⽤虚拟内存来进⾏数据管理的,每个进程都有⾃⼰独⽴的地址空间,假设进程A准备向进程B注⼊⼀段代码,他要让进程B执⾏他进程空间的函数InjectionCode(),这个函数在进程A的地址空间地址为0X3000
现在我们开始进⾏代码注⼊,利⽤CreateRemoteThread我们告诉B进程,请执⾏虚拟内存地址为0X3000的代码,这个时候B进程该⼲什么呢??B进程收到这个命令后,他很听话地创建了线程,然后乖乖得CALL了0X3000的内容,请注意,现在B进程CALL的是它⾃⼰内存空间内0X3000的代码⽽不是A进程的,那么现在B进程的0X3000是什么内容??没⼈知道,运⽓好的话说不定真的有段代码给你执⾏,运⽓不好你⾃⼰也不知道会发⽣什么事情,这就跟你进错了学⽣公寓⼀样,同样号码的房间,运⽓好是校花的房间,运⽓不好就是如花的房间
那么我们怎么才能让进程去执⾏我们对应的代码呢??我们只要在B进程内开辟⼀块内存,然后把我们的代码或者数据复制进去,再执⾏对应的代码就可以了,我们需要⽤到这⼏个函数:
LPVOID WINAPI VirtualAllocEx(
_In_ HANDLE hProcess,
_In_opt_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD flAllocationType,
_In_ DWORD flProtect
);
BOOL WINAPI WriteProcessMemory(
_In_ HANDLE hProcess,
_In_ LPVOID lpBaseAddress,
_In_ LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_ SIZE_T *lpNumberOfBytesWritten
);
这两个函数跟我们平常⽤的函数都差不多,只是多了个进程的选项,⼤概步骤如下图:
现在我们将实际操作⼀下:
下⾯是我们要注⼊的程序,在这之前,我们最好把基地址固定掉,这样我们不会每次重新运⾏程序的时候函数的地址都会改变,在VS2008中,项⽬属性->链接器->⾼级,把随机基址和固定基址选择默认值
void PrintMsg(const char *msg)
{
printf("ThreadI D:%d Msg:%s\n",GetCurrentThreadId(),msg);
}
int main(void)
{
printf("%d\n",GetCurrentThreadId());
printf("Print Msg Function Address:%X\n",PrintMsg);
system("pause");
}
假设我们的PrintMsg的地址是0x401000,现在我们需要往这个进程⾥⾯注⼊⼀段代码,让她可以⾃动调⽤PrintMsg这个函数
1static const char *msg = "INJECTION CODE SUCESS\n";
2static const unsigned int PARAM_SIZE = 100;
3static const unsigned int EXE_SIZE = 500;
4
5
6void InjectionCode(const char *msg)
7 {
8 __asm
9 {
10 push eax
11 push msg
12 mov eax,0x401000
13 call eax //因为在我们要注⼊的进程中,PrintMsg位于0x401000这个位置
14 pop eax
waitforsingleobject函数15 pop eax
16 }
17 }
18int main(void)
19 {
20 HANDLE hProcess = OpenProcessByProcessNmae(""); //这个函数在上⼀章
21
22if (hProcess == INVALID_HANDLE_VALUE)
23 {
24 printf("error open process %d\n",GetLastError());
25return1;
26 }
27 //⼀定要把函数的代码和msg写⼊要注⼊的进程,否则会发⽣位置错误(⼀般是崩溃)
28 LPVOID RemoteExe = VirtualAllocEx(hProcess,NULL,EXE_SIZE,MEM_COMMIT,PAGE_EXECUTE);
29 LPVOID RemoteParam = VirtualAllocEx(hProcess,NULL,PARAM_SIZE,MEM_COMMIT,PAGE_READWRITE);
30
31 SIZE_T WriteCount = 0;
32int ret = 0;
33 ret = WriteProcessMemory(hProcess,RemoteParam,msg,PARAM_SIZE,&WriteCount);
34 ret = WriteProcessMemory(hProcess,RemoteExe,InjectionCode,0x13,&WriteCount);
35
36 HANDLE hThread = CreateRemoteThread(hProcess,NULL,0,(LPTHREAD_START_ROUTINE)R
emoteExe,RemoteParam,0,NULL);
37 WaitForSingleObject(hThread,INFINITE);
38 }
运⾏上⾯的程序,我们就可以在另外⼀个进程中创建⼀个线程,并且这个线程将会输出该线程的ID以及我们要输出的消息
上⾯的程序还有⼏个要注意的:
1.资源竞争
由于是创建线程执⾏相应代码所以肯定会有资源竞争的问题,以后要写代码⼀定要注意,在本例中我忽略了这个问题
2. 关于代码的长度问题
在本例中,我们的代码长度是0X13,但是要知道,汇编代码的长度随便懂⼀下就可能更改,可能因为⼀个指令,也可能因为⼀个参数,所以我们需要时刻注意这点,关于代码长度怎么测量,我是看了反汇编
的代码后计算的,这个⽅法⽐较准确,也可以⼤概估计下,只要能把代码复制完整就可以,超出也没关系,只要不超出申请的内存⼤⼩就可以
3.记得备份我们使⽤的寄存器
这个⼗分重要,⼀旦你更改了寄存器,如果没有后⾯没有恢复,可能会导致⼀系列错误,特别是ESP,EBP等重要的寄存器
3.注⼊代码多次调⽤系统DLL中的函数
<<WINDOWS核⼼编程>>⾥⾯说,系统的DLL都会加载到⼀个固定的地址,⽐如VirtualAllocEx,⼀般我们在A进程和B进程的时候,call或者jmp的地址都是⼀样的,所以⼀般我们如果调⽤的是系统函数,⼀般我们不需要担⼼,但是,昨天我想到了⼀个问题,⽐如我们进程A要命令进程B调⽤CreateToolhelp32Snapshot这个系统API,现在我们假设CreateToolhelp32Snapshot这个API在单独的TLHELP32.DLL⾥⾯(实际上这个在KERNEL32.DLL⾥⾯,所有进程都会加载这个DLL,所以不需要担⼼下⾯的问题,这个只是举例),操作系统在加载DLL的时候,会统⼀把这个API的地址映射到虚拟内存的0XFF40100的地址,按照我们原来的想法进程B会⾃⼰跑去call 0XFF40100这个地址。但问题在于,如果我们的进程根本就没有加载TLHELP32.DLL这个DLL,那么进程call 0XFF40100会怎么样??这个就要看你这个地⽅是什么代码了,有⼈说操作系统会帮你加载这个DLL,但我觉得是错的,因为操作系统
要帮你加载的DLL都在PE头⾥⾯的导⼊表⾥⾯,要嘛就是我们要显⽰地去加载,否则操作系统不会知道我们的API在哪个DLL⾥⾯
4.注⼊代码多次调⽤我们⾃⼰编写的函数
⽐如我们有IntejectionCode,⾥⾯调⽤了IntejectionCode1,这个时候我们需要把IntejectionCode1也写⼊对⽅进程⾥⾯,不能只写⼊IntejectionCode,并且,我们需要更改IntejectionCode⾥⾯call IntejectionCode1跳转指令,让其跳转到正确的位置。总之,别⼈的地盘别⼈做主,对⽅进程想把代码放哪⾥就放哪⾥,我们⽆法管理(实际上virtualAllocEx是可以指定位置的,但是⼀般我们都尽量让操作系统去指定),我们只能⼊乡随俗,⼈家让我们去哪⾥调⽤我们就要去哪⾥调⽤,不然很容易导致进程崩溃
5.关于代码的基地址
在本例中PrintMsg的基地址是固定的,是我们⼈为去固定基地址的,我们在开发的时候很少⼈会去把基地址固定掉,所以在进程运⾏的时候,PrintMsg这个函数的地址是会改变的,当然我们也可以算出来这段代码在运⾏的时候会放在哪⾥,因为PrintMsg这段代码以⼆进制放在EXE⽂件的时候,也有⼀个⽂件偏移量,当操作系统把EXE⽂件加载进内存后,会根据基地址和⽂件偏移量,来算出PrintMsg在虚拟内存中的位置,所以我们只要能拿到进程运⾏时候的基地址,并且把EXE反汇编查看这段代码的⽂件偏移量,也能算出每次运⾏的时候PrintMsg的地址,虽然很⿇烦,特别是反汇编代码的那部分,但也没办
法,这个在后⾯我会讲
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论