在已运行进程注入间谍DLL
主体代码(文字说明放在代码的注释中):
void inject(DWORD threadid,BYTE*remotebuff, HMODULE hMod, DWORD
entrypoint,HANDLE processhandle,HANDLE eventhandle);
void loadandinject(DWORD procid)
{
BYTE array[256];
char buff[1024];
DWORD byteswritten,dw,threadid;
//按照Jeffrey Richter提出的方法在目标进程加载spydll.dll
HANDLE processhandle=OpenProcess(PROCESS_ALL_ACCESS,0,procid);
BYTE* writebuff=(BYTE*)VirtualAllocEx(processhandle,0,4096,MEM_RESERVE,PAGE_EXECUTE_READWRITE);
writebuff=(BYTE*)VirtualAllocEx(processhandle,writebuff,4096,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
DWORD funcptr=(DWORD)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
strcpy(buff,"spydll.dll");
WriteProcessMemory(processhandle,writebuff,buff,256,&byteswritten);
CreateRemoteThread(processhandle,0,0,(LPTHREAD_START_ROUTINE)funcptr,writebuff,0,&threadid);
//在目标进程中找到spydll.dll的模块句柄
HANDLE snap= CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,procid);
MODULEENTRY32 mod;
mod.dwSize=sizeof(MODULEENTRY32);
Module32First(snap,&mod);
HMODULE hMod=0;
while(Module32Next(snap,&mod))
{
if(!strcmp(mod.szModule,"spydll.dll"))
{
hMod=mod.hModule;
break;
}
}
CloseHandle(snap);
//获取spydll.dll在目标进程中的入口点
ReadProcessMemory(processhandle,(void*)hMod,buff,1024,&dw);
IMAGE_DOS_HEADER * dosheader=(IMAGE_DOS_HEADER *)buff;
IMAGE_OPTIONAL_HEADER * opthdr =(IMAGE_OPTIONAL_HEADER *)((BYTE*)buff+dosheader->e_lfanew+24);
DWORD entry=(DWORD)hMod+opthdr->AddressOfEntryPoint;
//创建一个自我复位的Event,其初始状态为没有信号状态
HANDLE eventhandle=CreateEvent(0,0,0,"spyevent");
//让目标进程已经运行的线程调用挂钩DLL文件的入口点函数
snap= CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,0);
THREADENTRY32 th;
th.dwSize=sizeof(THREADENTRY32);
Thread32First(snap,&th);
while(Thread32Next(snap,&th))
{
if(th.th32OwnerProcessID==procid)
inject(th.th32ThreadID,writebuff,hMod,entry,processhandle,eventhandle);
}
CloseHandle(eventhandle);
}
inject和前面篇幅的函数一样,也是机器码写的。但这里的情况更加复杂一些,因为必须改变目标线程的执行体上下文,让它调用我们硬编码以调用spydll.dll的入口点,以下是它的汇编伪码:
push eax
push ebx
push ecx
push edx
pushf
push 0
push value_of_DLL_THREAD_ATTACH
push hMod
call dword ptr[_imp_Dllentrypoint]
push eventnameptr
push 0
push value_of_EVENT_ALL_ACCESS
call dword ptr[_imp_OpenEvent]
push eax
push eax
call dword ptr[_imp_SetEvent]
call dword ptr[_imp_CloseHandle]
popf
pop edx
pop ecx
pop ebx
pop eax
jmp dword ptr[retaddressptr]
inject具体代码如下:
void inject(DWORD threadid,BYTE* remotebuff,HMODULE hMod,DWORD entrypoint,HANDLE processhandle,HANDLE eventhandle)
{
DWORD arg1=(DWORD)hMod,arg2=DLL_THREAD_ATTACH,arg3=0;
typedef HANDLE (__stdcall*func)(DWORD,BOOL,DWORD);
func OpenThread=(func)GetProcAddress(GetModuleHandle("KERNEL32.dll"),"OpenThread");
HANDLE threadhandle=OpenThread(THREAD_SUSPEND_RESUME| THREAD_GET_CONTEXT|THREAD_SET_CONTEXT,0,threadid);
SuspendThread(threadhandle);
CONTEXT Context;
Context.ContextFlags=CONTEXT_CONTROL;
GetThreadContext(threadhandle,&Context);
DWORD retaddress= Context.Eip;
//we are going to do the tough job of filling the array with the machine codes
BYTE array[256];
//copy all necessary data into the array
DWORD *openeventptr=(DWORD *)&array[100];
openeventptr[0]=(DWORD )&OpenEvent;
openeventptr=(DWORD *)&remotebuff[100];
DWORD*seteventptr=(DWORD *)&array[104];
seteventptr[0]=(DWORD )&SetEvent;
seteventptr=(DWORD *)&remotebuff[104];
DWORD* closehandleptr=(DWORD *)&array[108];
closehandleptr[0]=(DWORD )&CloseHandle;
closehandleptr=(DWORD *)&remotebuff[108];
DWORD* entrypointptr=(DWORD *)&array[112];
entrypointptr[0]=entrypoint;
entrypointptr=(DWORD *)&remotebuff[112];
DWORD* retaddressptr=(DWORD *)&array[116];
retaddressptr[0]=retaddress;
retaddressptr=(DWORD *)&remotebuff[116];
strcpy((char*)&array[120],"spyevent");
char*eventnameptr=(char*)&remotebuff[120];
//now we are filling the array with actual machine instructions
//push registers and flags
array[0]=0x50; //push eax
array[1]=0x53; //push ebx
array[2]=0x51; //push ecx
array[3]=0x52; //push edx
array[4]=0x9C; //pushf
//push entrypoint arguments
array[5]=0x68; //push 0
memmove(&array[6],&arg3,4);
array[10]=0x68; //push value_of_DLL_THREAD_ATTACH
memmove(&array[11],&arg2,4);
array[15]=0x68; //push hMod
memmove(&array[16],&arg1,4);
//call entrypoint
array[20]=0xFF; //call dword ptr[_imp_Dllentrypoint]
array[21]=0x15;
memmove(&array[22],&entrypointptr,4);
//push OpenEvent arguments
array[26]=0x68; //push eventnameptr
memmove(&array[27],&eventnameptr,4);
array[31]=0x68; //push 0
int a=0;
memmove(&array[32],&a,4);
array[36]=0x68; //push value_of_EVENT_ALL_ACCESS
a=EVENT_ALL_ACCESS;
memmove(&array[37],&a,4);
//call OpenEvent
array[41]=0xFF; //call dword ptr[_imp_OpenEvent]
array[42]=0x15;
memmove(&array[43],&openeventptr,4);
// push eax
array[47]=0x50;
// push eax
array[48]=0x50;
//call SetEvent
array[49]=0xFF;
array[50]=0x15;
memmove(&array[51],&seteventptr,4);
//call CloseHandle
array[55]=0xFF;
array[56]=0x15;
memmove(&array[57],&closehandleptr,4);
//restore registers and flags
array[61]=0x9D; //popf
array[62]=0x5A; //pop edx
array[63]=0x59; //pop ecx
array[64]=0x5B; //pop ebx
array[65]=0x58; //pop eax
//jmp dword ptr[retaddressptr]
array[66]=0xFF;
array[67]=0x25;
memmove(&array[68],&retaddressptr,4);
// we have finished filling the array, thanks God
DWORD byteswritten;
WriteProcessMemory(processhandle,(void *)remotebuff,(void*)array,256,&byteswritten);
Context.Eip=(DWORD)&remotebuff[0];
SetThreadContext(threadhandle,&Context);
ResumeThread(threadhandle);
//在改变目标线程的执行体上下文后,inject函数为等待目标线程置位同步的Event,所以直到目标线程已经恢复到原来的执行体上下文前,Inject函数都不能对其他的线程进行更改操作
//此外这里使用的同步技术还可以用于检测线程死锁
WaitForSingleObject(eventhandle,INFINITE);
CloseHandle(threadhandle);
}
注意:如果我们需要把spydll.dll注入到我们自己创建的进程,最好的办法是在spydll.dll刚被加载的时候改写目标函数的地址(此时的fdwReason=DLL_PROCESS_ATTACH),因为这个时候我们的进程还只有一个线程(主线程)在运行。但如果要把spydll.dll注入到已经运行的进程,就需要在每一个目标线程中调用spydll.dll的入口函数,以分配函数替换需要的存贮空间,否则就会导致调用Prolog()函数时因为没有分配存贮空间而出错。另外一方面,我们要Hook的目标函数的地址将被我们的spydll.dll输出,所以我们可以通过spydll.dll的输出函数表获取目标函数在注入进程中的地址。
由于对已运行进程的注入很复杂,除了实验用途以外,建议使用自己创建进程的方式来注入。