参考
https://github.com/coolxv/cpp-stub //https://stackoverflow.com/questions/2152958/reading-memory-protection //https://github.com/18446744073709551615/reDroid/blob/master/jni/reDroid/re_mprot.c
详解
inline hook
这里的inline hook,从实现上看,就是对内存加载的函数地址内内容做实时修改,来实现,桩函数调用。
stub 类
方法: set
template<typename T,typename S> ,T 目标函数,要被替换的函数地址,S stub函数,源函数地址,用来做替换的函数地址
void set(T addr, S addr_stub)
{
这个方法是一个模板方法,可以根据传进来的函数定义,让编译器生成各式各样的set函数。 主要实现: 创建(new)新的func_stub 对象,然后将这个对象放到map变量m_result 中;以目标函数地址作为key; 在64bit环境下,设置 装函数对象far_jmp开关,将源函数地址内的特定长度的内容放到桩函数对象的成员:code_buf int old_protect = set_mprotect(pstub);
然后将原函数的固定长度的指令,备份,然后将stub函数指令跳转,覆盖原函数
if (pstub->far_jmp)
{
*(unsigned char*)fn = 0x49;
*((unsigned char*)fn + 1) = 0xbb; movabs, 将stub函数地址放到eax,
*(unsigned long long *)((unsigned char *)fn + 2) = (unsigned long long)fn_stub;
*(unsigned char *)((unsigned char *)fn + 10) = 0x41; push rax 怎么实现的 跳转? 是通过,push 将stub函数的地址放到栈里
*(unsigned char *)((unsigned char *)fn + 11) = 0x53;
*(unsigned char *)((unsigned char *)fn + 12) = 0xc3; ret ,ret时,将 栈中的stub函数pop 出来,继续处理stub函数。
}
else
{
*(unsigned char *)fn = (unsigned char)0xE9;
*(unsigned int *)((unsigned char *)fn + 1) = (unsigned char *)fn_stub - (unsigned char *)fn - CODESIZE_MIN;
}
restore_mprotect 将内存保护模式恢复。
析构函数
会将之前放置map里的所有stub过的函数恢复到之前的状态。
addrof
将 目标函数类型地址,转换成void,其实也不叫转换,就是一值两用,其他函数可以使用void 类型。
template<typename T>
void* addrof(T src)
{
union
{
T _s;
void* _d;
}ut;
ut._s = src;
return ut._d;
}
distanceof 函数(64bit)
的作用,是判断两个函数是否在同一个范围 以地址向右移32位,就是判断高32位是否同时大于0,如果同时为0,或者同时大于0,就是在同一范围,如果两个值不是同时的话,就说明两个函数的地址差别比较大属于远距离函数。比较绕。
#ifdef __x86_64__
bool distanceof(void* addr, void* addr_stub)
{
unsigned long long addr_tmp = (unsigned long long)addr;
unsigned long long addr_stub_tmp = (unsigned long long)addr_stub;
unsigned int int_addr_tmp = (unsigned int)(addr_tmp >> 32);
unsigned int int_addr_stub_tmp = (unsigned int)(addr_stub_tmp >> 32);
if ((int_addr_tmp > 0 && int_addr_stub_tmp > 0) || (int_addr_tmp == 0 && int_addr_stub_tmp == 0))
{
return false;
}
else
{
return true;
}
}
#endif
常量
#ifdef x86_64 #define CODESIZE 13U // 长指令,长度 #define CODESIZE_MIN 5U //短指令 #define CODESIZE_MAX CODESIZE #else
set_mprotect
设置内存的写权限。首先看一下函数地址是否跨两个页面,怎么判断是否跨两个页面,通过函数get_page_count 来计算。
int set_mprotect(const struct func_stub *pstub)
{
int page_count = get_page_count(pstub);
if (page_count != 1) 跨页,还不能操作! 那就意味着,没有可能跨页,为什么?编译器做了对齐了?
{
std::string what_err("stub cross page!");
throw std::logic_error(what_err);
}
unsigned int mprot = read_mprotection(pstub->fn);
int prot = 0;
if ((mprot & MPROT_W) == 0)
{
prot = mprot_std(mprot);
if (-1 == mprotect(pageof(pstub->fn), m_pagesize * page_count, prot | PROT_WRITE))
{
std::string what_err("stub set mprotect to w+r+x faild");
throw std::logic_error(what_err);
}
}
return prot;
}
get_page_count
根据要修改的内容,判断开始修改的首地址和未地址,是否跨页;
char *code_end = code_start + code_size - 1;
void *page1 = pageof(code_start); 查看 起始位置的页标号
void *page2 = pageof(code_end); 查看 末尾位置的页标号
int count = (page1 == page2 ? 1 : 2); 看看两个标号是否相同,不同就是两个页,其他是一个页
void *pageof(const void* p)
{
return (void *)((unsigned long)p & ~(m_pagesize - 1));
}
read_mprotection
根据运行时文件:/proc/self/maps,判断地址所在的位置,然后查看相应的权限;
|