一个简单的桩实现类:

#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) 替换指令码:
b8
00 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得知一个任务下的所有线程数

 

标签: none

添加新评论