【标题】: 劫持正在运行进程的EIP注入代码的方法
【作者】: 火血狼(QQ:65845743)
【工具】: VC++2005, WINXP, WIN7
【声明】: 1.禁止用来做破坏;2.转载请告知作者.
-----------------------------------------------------------------------------
【灵感来源】
近日,在读<<Windows内核编程>>的时候,偶然发现,一个函数GetThreadContext,该函数可以使用户级的代码访问并操作指定线程的上下文:CONTEXT,通过这个CONTEXT里的一个字段EIP,我们可以得到CPU寄存器的当前值。当时就想,如果通过这个EIP允许修改,不就可以控制程序流程了吗?查了查资料,果然可以被另外用户态的进程修改,于是做了如下实验,实验目标:劫持EIP,执行自己代码,然后恢复EIP。
【第一步】修改另外进程的EIP寄存器
SuspendThread(hThread);//这里先让线程挂起,避免EIP乱跑
CONTEXT context;
context.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(hThread, &context);
DWORD dwEIP = context.Eip;
context.ContextFlags = CONTEXT_CONTROL;
//
context.Eip = 0x000000; //这里随便设一个EIP值,导致目标进程崩溃
SetThreadContext(hThread, &context);
ResumeThread(hThread);
通过上面的代码实验,得出结论,EIP的设置是不受限制的。(其中hThread为目标进程的主线程句柄,至于如何得到,很多地方有例子,这里不再普及基础知识)
【第二步】构建合法的EIP值,引导目标进程EIP进入指定代码
在进行这一步的时候我遇到了以下几个问题:1.目标进程只能访问自身的虚拟内存地址;2.如何向内存中放入指定代码。
要解决第一个问题,就要用到
PVOID pCodeRemote = VirtualAllocEx(hProcess, NULL, (size_t)dwCodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE));
这个代码将在目标进程的虚拟内存里申请一块儿大小为dwCodeSize的鲜活内存空间,并把内存起始指针返回。(并且页权限为可执行,可读写)
解决第二个问题,要用到汇编啦
写这样一个函数void __declspec(naked) __stdcall ASM_RemoteFunc(){
_asm{ int 3 }
}
然后把这个函数Copy到刚才的内存中,用到代码
WriteProcessMemory(hProcess, pCodeRemote, (PVOID)ASM_RemoteFunc, (size_t)dwCodeSize, NULL)
到这里,又有疑问了,怎么确定dwCodeSize呢?嗯,可以在函数末尾加个特殊值,然后查找到这个值,就可以确定函数的末尾地址了,嘿嘿,来试试
(naked修饰这里也不解释,请读者自行查资料)
void __declspec(naked) __stdcall ASM_RemoteFunc(){
_asm{ int 3; push 0x12345679 }
}
这样写搜索代码
void* find_ptr(void* mem, DWORD dwv)
{
void* ret_ptr;
__asm
{
mov eax, mem
jmp comp
diff: inc eax
comp: mov ebx, [eax]
cmp ebx, dwv
jnz diff
mov ret_ptr, eax
}
return ret_ptr;
}
最后,函数大小可以通过下面代码来计算:
DWORD dwCodeStart = (DWORD)ASM_RemoteFunc; PVOID ptrCodeLocal = (PVOID)dwCodeStart; DWORD dwCodeEnd = (DWORD)find_ptr(ptrCodeLocal, PLACE_HOLDER_END) + 4; DWORD dwCodeSize = dwCodeEnd - dwCodeStart;
好了,第二部问题解决了,实验一下,果然,目标进程产生中断异常,说明执行了指定代码,但是最终程序还是会崩溃。如何能让程序不崩溃呢?
【第三步】寄存器和堆栈恢复
先分析一下程序为啥崩溃:因为我们改变EIP的时候,其代码有可能处于任何位置,执行完我们的代码后,并没有恢复原来的EIP指针,也没有保护好各个寄存器的值,目标进程会出现不可预计的现象。
如何恢复EIP呢,写过shellcode的人都知道,ret可以做到这一点,于是我们先push当前的EIP,然后,再结束的时候ret,就会返回到原来的地方执行EIP啦,于是这样写:
void __declspec(naked) __stdcall ASM_RemoteFunc(){
_asm{
push 0x12345670
ret
push 0x12345679
}
}
呵呵,有人奇怪了,为啥用0x12345670而不用真正的EIP呢,因为这会儿我们无法得到,运行的时候才有。那怎么办呢?不用急,我们用找函数大小的方法找到0x12345670的地址,然后把目标进程的当前EIP,写入,不就行啦。
void * placeHolderEIP = find_ptr(ptrCodeLocal, 0x12345670);
memcpy((void *)placeHolderEIP, &dwEIP, 4);
运行,安静的通过,哈哈。
下面保护寄存器,并且调用一些有意思的代码:
#define PLACE_HOLDER_EIP 0x12345670
#define PLACE_HOLDER_ST1 0x12345671
#define PLACE_HOLDER_ST2 0x12345672
#define PLACE_HOLDER_FUN 0x12345678
#define PLACE_HOLDER_END 0x12345679
void __declspec(naked) __stdcall ASM_RemoteFunc(){
_asm{
push PLACE_HOLDER_EIP;
pushfd;
pushad;
push MB_OK | MB_ICONINFORMATION
push PLACE_HOLDER_ST1;
push PLACE_HOLDER_ST2;
push NULL
mov eax, PLACE_HOLDER_FUN;
call eax;
popad;
popfd;
ret;
push PLACE_HOLDER_END
}
}
按照同样的内存查找的方法,把指定地方放入自己的值:
HMODULE hModule = 0;
if (!(hModule = LoadLibrary(_T("User32.dll")))) return false;
DWORD funRemote = 0;
if (!(funRemote = (DWORD)GetProcAddress(hModule, "MessageBoxA"))) return false;
PVOID strRemote1 = NULL;
if (!(strRemote1 = VirtualAllocEx(hProcess, NULL, (size_t)(strlen(strPam1) + 1), MEM_COMMIT, PAGE_READWRITE))) return false;
PVOID strRemote2 = NULL;
if (!(strRemote2 = VirtualAllocEx(hProcess, NULL, (size_t)(strlen(strPam2) + 1), MEM_COMMIT, PAGE_READWRITE))) return false;
PVOID pCodeRemote = NULL;
if (!(pCodeRemote = VirtualAllocEx(hProcess, NULL, (size_t)dwCodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE))) return false;
void * placeHolderEIP = find_ptr(ptrCodeLocal, PLACE_HOLDER_EIP);
void * placeHolderST1 = find_ptr(ptrCodeLocal, PLACE_HOLDER_ST1);
void * placeHolderST2 = find_ptr(ptrCodeLocal, PLACE_HOLDER_ST2);
void * placeHolderFUN = find_ptr(ptrCodeLocal, PLACE_HOLDER_FUN);
memcpy((void *)placeHolderEIP, &dwEIP, 4);
memcpy((void *)placeHolderST1, &strRemote1, 4);
memcpy((void *)placeHolderST2, &strRemote2, 4);
memcpy((void *)placeHolderFUN, &funRemote, 4);
最后,把自己的函数复制到目标进程
if (!WriteProcessMemory(hProcess, strRemote1, (LPCVOID)strPam1, strlen(strPam1), NULL)) return false;
if (!WriteProcessMemory(hProcess, strRemote2, (LPCVOID)strPam2, strlen(strPam2), NULL)) return false;
if (!WriteProcessMemory(hProcess, pCodeRemote, ptrCodeLocal, (size_t)dwCodeSize, NULL)) return false;
然后,ResumeThread(hThread),弹出messagebox,标题是strRemote1,内容为strRemote2指定的字符串值。
点ok后,目标进程安全无恙。大功告成!!
【结论】 EIP就是一切!!
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)