Windows下对函数打桩,及Linux类似技术
一个简单的桩实现类:
#define JMPCODE_LENGTH 5 //x86 平坦内存模式下,绝对跳转指令长度 #define JMPCMD_LENGTH 1 //机械码0xe9长度 #define JMPCMD 0xe9 //对应汇编的jmp指令 //一个简化的打桩类的实现 classXSimpleStub
{public:explicit XSimpleStub(void* pOrigFunc, void* pNewFunc, bool need_lock_other_thread = false);~XSimpleStub();private://源函数地址 void *str_func_addr;//是否打桩成功 boolis_stub_succ;//是否打桩成功 boolneed_lock_other_thread_;//源指令数据的备份 unsigned charstr_instruct_back[JMPCODE_LENGTH];
};
函数就只有两个函数体,分别如下
#include <tlhelp32.h>BOOL LockOtherThread()
{
DWORD dwCurrPid=GetCurrentProcessId();
DWORD dwCurrTid=GetCurrentThreadId();
HANDLE hThread=NULL;
HANDLE hThreadSnap=NULL;
THREADENTRY32 te32= { 0};
te32.dwSize= sizeof(THREADENTRY32);//遍历线程 if (Thread32First(hThreadSnap, &te32))
{do{if (te32.th32OwnerProcessID ==dwCurrPid) {if (te32.th32ThreadID !=dwCurrTid){//获取句柄 hThread =OpenThread(THREAD_SUSPEND_RESUME, FALSE, te32.th32ThreadID);if (NULL !=hThread){
SuspendThread(hThread);
}
CloseHandle(hThread);
}
}
}while (Thread32Next(hThreadSnap, &te32));
}
CloseHandle(hThreadSnap);returnTRUE;
}
BOOL UnlockOtherThread()
{
DWORD dwCurrPid=GetCurrentProcessId();
DWORD dwCurrTid=GetCurrentThreadId();
HANDLE hThread=NULL;
HANDLE hThreadSnap=NULL;
THREADENTRY32 te32= { 0};
te32.dwSize= sizeof(THREADENTRY32);//遍历线程 if (Thread32First(hThreadSnap, &te32))
{do{if (te32.th32OwnerProcessID ==dwCurrPid) {if (te32.th32ThreadID !=dwCurrTid){//获取句柄 hThread =OpenThread(THREAD_SUSPEND_RESUME, FALSE, te32.th32ThreadID);if (NULL !=hThread){
ResumeThread(hThread);
}
CloseHandle(hThread);
}
}
}while (Thread32Next(hThreadSnap, &te32));
}
CloseHandle(hThreadSnap);returnTRUE;
}static void __inner_memcpy(unsigned char* pDest, unsigned char* pSrc, unsigned intcount)
{while(count > 0) {*pDest++ = *pSrc++;
count--;
}
}
XSimpleStub::XSimpleStub(void* pOrigFunc, void* pNewFunc, boolneed_lock_other_thread):
str_func_addr(pOrigFunc), is_stub_succ(false), need_lock_other_thread_(need_lock_other_thread)
{//源地址、目标地址需要进行一次判定 if (nullptr != pOrigFunc && nullptr !=pNewFunc)
{
DWORD ProtectVar;//保护属性变量 MEMORY_BASIC_INFORMATION MemInfo; //内存分页属性信息//取得对应内存的原始属性 if (0 != VirtualQuery(pOrigFunc, &MemInfo, sizeof(MEMORY_BASIC_INFORMATION)))
{//如果需要锁住所有其他线程,则先执行锁定动作 if(need_lock_other_thread) {
LockOtherThread();
}//修改页面为可写 if(VirtualProtect(MemInfo.BaseAddress, MemInfo.RegionSize, PAGE_READWRITE, &MemInfo.Protect))
{//备份原数据,防止自身需要使用memcpy,不能使用类似接口 __inner_memcpy((unsigned char*)str_instruct_back, (unsigned char*)pOrigFunc, JMPCODE_LENGTH);//修改目标地址指令为 jmp pDestFunc *(unsigned char*)pOrigFunc = JMPCMD; //拦截API,在函数代码段前面注入jmp xxx *(DWORD*)((unsigned char*)pOrigFunc + JMPCMD_LENGTH) = (DWORD)pNewFunc - (DWORD)pOrigFunc -JMPCODE_LENGTH;//改回原属性 VirtualProtect(MemInfo.BaseAddress, MemInfo.RegionSize, MemInfo.Protect, &ProtectVar);//修改后,还需要刷新cache FlushInstructionCache(GetCurrentProcess(), pOrigFunc, JMPCODE_LENGTH);
is_stub_succ= true;
}//如果需要锁住所有其他线程,则先执行锁定动作 if(need_lock_other_thread) {
UnlockOtherThread();
}
}
}
}
XSimpleStub::~XSimpleStub()
{if(is_stub_succ)
{
DWORD TempProtectVar;//临时保护属性变量 MEMORY_BASIC_INFORMATION MemInfo; //内存分页属性信息 if (0 != VirtualQuery(str_func_addr, &MemInfo, sizeof(MEMORY_BASIC_INFORMATION)))
{//如果需要锁住所有其他线程,则先执行锁定动作 if(need_lock_other_thread_) {
LockOtherThread();
}//修改页面为可写 if(VirtualProtect(MemInfo.BaseAddress,MemInfo.RegionSize, PAGE_READWRITE,&MemInfo.Protect))
{//恢复代码段 __inner_memcpy((unsigned char*)str_func_addr, (unsigned char*)str_instruct_back, JMPCODE_LENGTH);//改回原属性 VirtualProtect(MemInfo.BaseAddress,MemInfo.RegionSize, MemInfo.Protect,&TempProtectVar);//修改后,还需要刷新cache FlushInstructionCache(GetCurrentProcess(), str_func_addr, JMPCODE_LENGTH);
}//如果需要锁住所有其他线程,则先执行锁定动作 if(need_lock_other_thread_) {
UnlockOtherThread();
}
}
}
}
Linux下一样有类似技术,可以参考IBM的一个文档:
https://www.ibm.com/developerworks/cn/linux/l-knldebug/index.html
这其中的差别在跳转指针的设计上,Linux上,是使用了7个字节,并不需要计算原函数、新函数的地址距离
整个替换流程的实现分为如下几个步骤:
(1) 替换指令码:
b800 00 00 00 /*movl $0, $eax;这里的$0将被具体替换函数的地址所取代*/
ff e0 /*jmp*$eax ;跳转函数*/
将上述7个指令码存放在一个字符数组中:
replace_code[7]
(2) 用替换函数的地址覆盖第一条指令中的后面8个0,并保留原来的指令码:
memcpy (orig_code, func,7);/* 保留原函数的指令码 */ *((long*)&replace_code[1])= (long) replace_func;/* 赋替换函数的地址 */ memcpy (func, replace_code, 7); /* 用新的指令码替换原函数指令码 */
(3) 恢复过程用保留的指令码覆盖原函数代码:
memcpy (func, orig_code,7)
Linux下,实现代码页属性修改使用函数接口:mprotect
而相应的Linux下线程挂起、恢复使用如下接口:
#include <signal.h>
pthread_kill(ThreadID, SIGSTOP); // suspend
pthread_kill(ThreadID, SIGCONT); // resume
通过查看/proc/pid/task得知一个任务下的所有线程数