本文为看雪论坛精华文章
看雪论坛作者ID:abucs
一
动态替换需要 Hook 的指令片段为一段经过设计的跳板指令,即 trampoline ,目标为我们设计好的一段 shellCode
在内存中设计并生成一段 shellCode ,这是我们的可控 shellCode ,在该 shellCode 中需要实现 Hook 的功能函数(即打印/替换-参数/结果)
shellCode 的设计原则是保持 Hook 前后的栈平衡,并保护寄存器状态(即Hook结束后,保持与Hook开始前一致的栈布局与寄存器状态)
在 shellCode 中完成原函数的执行工作,被替换的掉的指令中若包含计算 PC-relative address ( 如 Branch 指令 ),需要对其正确解析执行
//## hookTest1: Hook 导出函数->Java_com_example_x64_JNI_aal
function hookTest1() {
var helloAddr = Module.findExportByName("libx64.so", "Java_com_example_x64_JNI_aal");
console.log(helloAddr);
if(helloAddr != null){
Interceptor.attach(helloAddr,{
onEnter: function(args){
console.log("hook1 on enter");
},
onLeave: function(retval){
console.log("hook1 on leave");
}
});
}
}
//## hookTest2: Hook 指定位置->0x000000000000BBA0
function hookTest2() {
var libutilityAddr = Module.findBaseAddress("libx64.so");
var getOriginalStringAddr = libutilityAddr.add(0x000000000000BBA0);
console.log(getOriginalStringAddr);
if(getOriginalStringAddr != null){
Interceptor.attach(getOriginalStringAddr,{
onEnter: function(args){
console.log("hook2 on enter");
},
onLeave: function(retval){
console.log("hook2 on leave");
}
});
}
}
首先 mmap 了一段匿名内存( 7face7c000-7face83000 rwxp ),在 0x7face7c600 位置放置了以下几条汇编指令构成第二段跳板。
> ldr x17, =0x7facec12e0
> ldr x16, =0x7face7c000
> br x16
其中 x17 寄存器装载了一个地址( 0x7facec12e0 ),这个地址内部保存着 0x7fac430b70 ,正是 Java_com_example_x64_JNI_aal 函数入口地址。
而 x16 寄存器装载了此番生成的 shellCode 的地址( 0x7face7c000 ),将该段内存 dump 下来,拖入 ida 进行分析:
二
Hook 导出函数:即在函数开头进行 Hook ,能够执行原函数,并提供 onEnter 以及 onLeave 两层代码注入点,达到类似 Frida 那种 "代码托管" 一样的效果
Hook 函数内指定地址:Hook 指定位置的汇编指令,仅提供 onEnter 一层代码注入点,因为考虑到在指定位置上 X30( LR ) 寄存器可能已经发生变化,此时用该寄存器做返回判断并不准确,故放弃 onLeave
在 onEnter 中提供入参的打印/修改操作 ( 本质是寄存器/堆栈内存打印/修改操作 )
在 onLeave 中提供返回值的打印/修改操作 ( 本质是寄存器/堆栈内存打印/修改操作 )
_trampoline_:
LDR X16, x64code0
BR X16
x64code0:
_jmp_addr_:
.dword 0x1111111111111111
//## Hook目标函数
extern "C" JNIEXPORT jstring JNICALL
Java_com_cs_inline_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* thisobj */,
jstring jstr) {
std::string hello = "Hello from C++: ";
hello.append(env->GetStringUTFChars(jstr, nullptr));
return env->NewStringUTF(hello.c_str());
}
//## 该函数内部完成了对Java_com_cs_inline_MainActivity_stringFromJNI函数的inlineHook
extern "C" JNIEXPORT void JNICALL
Java_com_cs_inline_MainActivity_inlineHook1(JNIEnv* env,
jobject /* thisobj */)
{
//## Hook target函数为:Java_com_cs_inline_MainActivity_stringFromJNI
u_long func_addr = (u_long)Java_com_cs_inline_MainActivity_stringFromJNI;
extern u_long _shellcode_start_, _the_func_addr_, _end_func_addr_, _ori_ins_set1_, _retback_addr_, _shellcode_end_, _trampoline_, _jmp_addr_, _shellcode_part2_;
//## 计算shellcode整体长度
u_long total_len = (u_long)&_shellcode_end_ - (u_long)&_shellcode_start_;
LOGD(ANDROID_LOG_DEBUG, "[+] ShellCode len: %d, target func: %p", total_len, func_addr);
//## 使用mmap分配匿名内存存放shellcode
u_long page_size = getpagesize();
u_long shellcode_mem_start = (u_long)mmap(0, page_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
memset((void *)shellcode_mem_start, 0, page_size);
memcpy((void *)shellcode_mem_start, (void *)&_shellcode_start_, total_len);
LOGD(ANDROID_LOG_DEBUG, "[+] shellcode_mem_start: %p", shellcode_mem_start);
//## 设置trampoline跳转的目标地址
*(u_long*)&_jmp_addr_ = shellcode_mem_start;
u_long mem_the_func_addr_ = (u_long)&_the_func_addr_ - (u_long)&_shellcode_start_ + shellcode_mem_start;
u_long mem_end_func_addr_ = (u_long)&_end_func_addr_ - (u_long)&_shellcode_start_ + shellcode_mem_start;
u_long mem_ori_ins_set1_ = (u_long)&_ori_ins_set1_ - (u_long)&_shellcode_start_ + shellcode_mem_start;
u_long mem_retback_addr_ = (u_long)&_retback_addr_ - (u_long)&_shellcode_start_ + shellcode_mem_start;
if(!off_shellcode_part2_)
off_shellcode_part2_ = (u_long)&_shellcode_part2_ - (u_long)&_shellcode_start_;
//## 设置onEnter及onLeave函数
*(u_long*)mem_the_func_addr_ = (u_long)on_enter_1;
*(u_long*)mem_end_func_addr_ = (u_long)on_leave_1;
//## 设置返回地址为距离Hook点0x10长度的指令地址,即偏移为trampoline的长度
*(u_long*)mem_retback_addr_ = (u_long)func_addr + 0x10;
//## 原指令保存,并未做任何解析,PC-relative address相关指令暂不支持
*(u_long*)mem_ori_ins_set1_ = *(u_long*)func_addr;
*(u_long*)(mem_ori_ins_set1_ + 8) = *(u_long*)(func_addr + 8);
//## 页权限修改并完成inlineHook
u_long entry_page_start = (u_long)(func_addr) & (~(page_size-1));
mprotect((u_long*)entry_page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC);
*(u_long*)func_addr = *(u_long*)&_trampoline_;
*(u_long*)(func_addr + 8) = *(u_long*)(((u_long)&_trampoline_) + 8);
//## 使用线程局部存储保存原始返回地址LR(X30)
u_long thread_local ori_lr = 0;
u_long off_shellcode_part2_ = 0;
void on_enter_1(u_long sp)
{
//## sp回到初始位置,取出返回地址LR
sp = sp + 0x60;
u_long lr = *(u_long*)(sp - 8);
u_long lr_ptr = sp - 8;
u_long pc = *(u_long*)(sp - 0x20);
pc -= 0x20;
//## 使用TLS保存LR
ori_lr = lr;
//## 一般来说8个参数顶天了
u_long arg1 = *(u_long*)(sp - 0x28);
u_long arg2 = *(u_long*)(sp - 0x30);
u_long arg3 = *(u_long*)(sp - 0x38);
u_long* arg3_ptr = (u_long*)(sp - 0x38);
u_long arg4 = *(u_long*)(sp - 0x40);
u_long arg5 = *(u_long*)(sp - 0x48);
u_long arg6 = *(u_long*)(sp - 0x50);
u_long arg7 = *(u_long*)(sp - 0x58);
u_long arg8 = *(u_long*)(sp - 0x60);
//## sp上还有参数的话照下面这么写
u_long arg9 = *(u_long*)(sp);
u_long arg10 = *(u_long*)(sp + 0x8);
//## 打印String参数
JNIEnv* env = reinterpret_cast<JNIEnv *>(arg1);
jstring jstr = reinterpret_cast<jstring>(arg3);
LOGD(ANDROID_LOG_INFO, "[+] arg3: %s", env->GetStringUTFChars(jstr, nullptr));
//## 替换String参数
jstring jstr_new = env->NewStringUTF("--This is on_enter_1 !");
*arg3_ptr = reinterpret_cast<u_long>(jstr_new);
//## 修改LR寄存器,保证原始函数执行完毕会回到on_leave_1函数
*(u_long*)lr_ptr = pc + off_shellcode_part2_;
LOGD(ANDROID_LOG_WARN, "[+] on_enter_1: %p", on_enter_1);
}
void on_leave_1(u_long sp)
{
//## sp回到初始位置
sp = sp + 0x10;
u_long x0 = *(u_long*)(sp - 8);
u_long* x0_ptr = (u_long*)(sp - 8);
u_long lr = *(u_long*)(sp - 0x10);
u_long* lr_ptr = (u_long*)(sp - 0x10);
//## do_something ...
LOGD(ANDROID_LOG_DEBUG, "[+] on_leave_1: %p", on_leave_1);
//## 取回LR并返回
*(u_long*)lr_ptr = ori_lr;
}
三
看雪ID:abucs
https://bbs.pediy.com/user-home-772122.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!