一个简单的桩实现类:
#define JMPCODE_LENGTH 5 //x86 平坦内存模式下,绝对跳转指令长度
#define JMPCMD_LENGTH 1 //机械码0xe9长度
#define JMPCMD 0xe9 //对应汇编的jmp指令
// 一个简化的打桩类的实现
class XSimpleStub
{
public:
explicit XSimpleStub(void* pOrigFunc, void* pNewFunc, bool need_lock_other_thread = false);
~XSimpleStub();
private:
// 源函数地址
void * str_func_addr;
// 是否打桩成功
bool is_stub_succ;
// 是否打桩成功
bool need_lock_other_thread_;
// 源指令数据的备份
unsigned char str_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);
return TRUE;
}
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);
return TRUE;
}
static void __inner_memcpy(unsigned char* pDest, unsigned char* pSrc, unsigned int count)
{
while(count > 0) {
*pDest++ = *pSrc++;
count --;
}
}
XSimpleStub::XSimpleStub(void* pOrigFunc, void* pNewFunc, bool need_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)
知识兔