Win32线程劫持-Suspend-Inject-Resume
所谓线程劫持,就是利用目标进程已有的线程执行自己的代码,而不用自己再在目标进程中创建新的线程。其核心就是Suspend(选择目标进程中的一个非等待状态的线程)-Inject(将EIP指向我们已经写入目标进程虚拟地址空间中的代码的起始地址)-Resume(恢复该线程的执行,这样一来目标线程将执行我们的代码),这种方法最关键的一点就是在目标线程执行完我们的代码之后,我们需要确保其返回原来执行的位置并且其通用寄存器以及其它的状态寄存器的状态不变。
核心函数如下
VOID suspendInjectResume(HANDLE hHandle, LPVOID loadLibAddr, LPVOID dllPathAddr) {
/*
This is a mixture from the following sites:
http://syprog.blogspot.com/2012/05/createremotethread-bypass-windows.html
http://www.kdsbest.com/?p=159
*/
HANDLE hSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 );
HANDLE hSnapshot2 = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 );
HANDLE thread = NULL;
THREADENTRY32 te;
THREADENTRY32 te2;
CONTEXT ctx;
DWORD firstThread = 0;
HANDLE targetThread = NULL;
LPVOID scAddr;
int i;
unsigned char sc[] = {
// Push all flags
0x9C,
// Push all register
0x60,
// Push 3,4,5,6 (dllPathAddr)
0x68, 0xAA, 0xAA, 0xAA, 0xAA,
// Mov eax, 8,9,10, 11 (loadLibAddr)
0xB8, 0xBB, 0xBB, 0xBB, 0xBB,
// Call eax
0xFF, 0xD0,
// Pop all register
0x61,
// Pop all flags
0x9D,
// Ret
0xC3
};
te.dwSize = sizeof(THREADENTRY32);
te2.dwSize = sizeof(THREADENTRY32);
ctx.ContextFlags = CONTEXT_FULL;
sc[3] = ((unsigned int) dllPathAddr & 0xFF);
sc[4] = (((unsigned int) dllPathAddr >> 8 )& 0xFF);
sc[5] = (((unsigned int) dllPathAddr >> 16 )& 0xFF);
sc[6] = (((unsigned int) dllPathAddr >> 24 )& 0xFF);
sc[8] = ((unsigned int) loadLibAddr & 0xFF);
sc[9] = (((unsigned int) loadLibAddr >> 8 )& 0xFF);
sc[10] = (((unsigned int) loadLibAddr >> 16 )& 0xFF);
sc[11] = (((unsigned int) loadLibAddr >> 24 )& 0xFF);
// Suspend Threads
if(Thread32First(hSnapshot, &te)) {
do {
if(te.th32OwnerProcessID == GetProcessId(hHandle)) {
if ( firstThread == 0 )
firstThread = te.th32ThreadID;
thread = OpenThread(THREAD_ALL_ACCESS | THREAD_GET_CONTEXT, FALSE, te.th32ThreadID);
if(thread != NULL) {
printf("\t[+] Suspending Thread 0x%08x\n", te.th32ThreadID);
SuspendThread(thread);
CloseHandle(thread);
} else {
printf("\t[+] Could not open thread!\n");
}
}
} while(Thread32Next(hSnapshot, &te));
} else {
printf("\t[+] Could not Thread32First! [%d]\n", GetLastError());
CloseHandle(hSnapshot);
exit(-1);
}
CloseHandle(hSnapshot);
printf("\t[+] Our Launcher Code:\n\t");
for (i=0; i<17; i++)
printf("%02x ",sc[i]);
printf("\n");
// Get/Save EIP, Inject
printf("\t[+] Targeting Thread 0x%08x\n",firstThread);
targetThread = OpenThread(THREAD_ALL_ACCESS, FALSE, firstThread);
if (GetThreadContext(targetThread, &ctx) == 0)
printf("[!] GetThreadContext Failed!\n");
printf("\t[+] Current Registers: \n\t\tEIP[0x%08x] ESP[0x%08x]\n", ctx.Eip, ctx.Esp);
printf("\t[+] Saving EIP for our return\n");
ctx.Esp -= sizeof(unsigned int);
WriteProcessMemory(hHandle, (LPVOID)ctx.Esp, (LPCVOID)&ctx.Eip, sizeof(unsigned int), NULL);
printf("\t\tEIP[0x%08x] ESP[0x%08x] EBP[0x%08x]\n", ctx.Eip, ctx.Esp, ctx.Ebp);
scAddr = VirtualAllocEx(hHandle, NULL, 17, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
printf("\t[+] Allocating 17 bytes for our Launcher Code [0x%08x][%d]\n", scAddr, GetLastError());
printf ("\t[+] Writing Launcher Code into targetThread [%d]\n", WriteProcessMemory(hHandle, scAddr, (LPCVOID)sc, 17, NULL));
printf("\t[+] Setting EIP to LauncherCode\n");
ctx.Eip = (DWORD)scAddr;
printf("\t\tEIP[0x%08x] ESP[0x%08x]\n", ctx.Eip, ctx.Esp);
if (SetThreadContext(targetThread, &ctx) == 0)
printf("[!] SetThreadContext Failed!\n");
// Resume Threads
hSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 );
te.dwSize = sizeof(THREADENTRY32);
if(Thread32First(hSnapshot2, &te2)) {
do {
if(te2.th32OwnerProcessID == GetProcessId(hHandle)) {
thread = OpenThread(THREAD_ALL_ACCESS | THREAD_GET_CONTEXT, FALSE, te2.th32ThreadID);
if(thread != NULL) {
printf("\t[+] Resuming Thread 0x%08x\n", te2.th32ThreadID);
ResumeThread(thread);
if (te2.th32ThreadID == firstThread)
WaitForSingleObject(thread, 5000);
CloseHandle(thread);
} else {
printf("\t[+] Could not open thread!\n");
}
}
} while(Thread32Next(hSnapshot2, &te2));
} else {
printf("\t[+] Could not Thread32First! [%d]\n", GetLastError());
CloseHandle(hSnapshot2);
exit(-1);
}
CloseHandle(hSnapshot2);
}
之前写的一个比较笨的实现方法
上面的代码是网上的代码,而我之前也写过一个实现,刚刚找到了就贴在这里。
查找目标进程的代码不需要过多的解析了
int _tmain(int argc,char** argv)
{
CToolhelp ProceeHelp(TH32CS_SNAPPROCESS);
PROCESSENTRY32 pe = {0};
pe.dwSize = sizeof(pe);
if(ProceeHelp.ProcessFirst(&pe))
{
while(ProceeHelp.ProcessNext(&pe))
{
if(!stricmp(pe.szExeFile,"notepad.exe"))
{
CHAR* szDll = "\\SimpleDll.dll";
CHAR szFullPath[MAX_PATH] = {0};
GetCurrentDirectory(MAX_PATH,szFullPath);
strcat(szFullPath,szDll);
if(Inject(pe.th32ProcessID,szFullPath))
{
ShowMessage(_T("注入成功"));
}
else
{
ShowMessage(_T("注入失败"));
}
break;
}
}
}
return 0;
}
- 核心的汇编代码
#define MY_EIP 0x12345670
#define MY_ST1 0x12345671
#define MY_FUN 0x12345673
#define MY_END 0x12345674
// 告诉编译器,函数代码的汇编语言为自己所写的,不需要编译器添加任何汇编代码,即生成纯汇编
// 需要在开始的时候保存上下文标志(push)并在结束的时候回复上下文(pop) 并在结尾添加ret 命令
void __declspec(naked) __stdcall ASM_RemoteFunc()
{
__asm
{
call Next;
Next:
pushfd;
pushad;
push MY_ST1
mov eax,MY_FUN
call eax
popad;
popfd;
mov DWORD PTR [esp],MY_EIP
ret;
push MY_END
}
}
PVOID Find_Ptr(PVOID BeginPoint,ULONG_PTR Flags)
{
PVOID ret_ptr = NULL;
__asm
{
mov eax,BeginPoint
jmp comp
diff: inc eax
comp: mov ebx,[eax]
cmp ebx,Flags
jnz diff
mov ret_ptr,eax
}
return ret_ptr;
}
后面的操作就不详细说了,就是得到LoadLibrary函数的地址,得到DLL path 的地址以及得到原来的EIP 的地址,得到了之后应该修正里面的偏移值,最后根据MY_END的值得到指令的大小等等的类似的操作。