该工具目前仅支持安卓5.0、6.0 版本脱壳使用:
1.hook ClassLoader的loadClass方法
2.反射获取Class的getDex(),获取dex
3.反射获取Dex的getBytes(),保存dex文件
修复脱壳后dex文件,修改dex头文件Magic Value(dex文件头、035文件格式版本)
支持安卓8.0及以上版本脱壳使用,通过edxposed进程注入,hook libart.so中openCommon(),不同版本签名方法不同,需要用ida查看,使用时替换hook方法名。
- 跳过7.0后的私有api限制,获取libart.so内存段的加载位置
static __attribute__((__aligned__(PAGE_SIZE))) uint8_t __insns[PAGE_SIZE]; STUBS.generic_stub = __insns; //修改内存区读写属性 mprotect(__insns, sizeof(__insns), PROT_READ | PROT_WRITE | PROT_EXEC); /*对dlopen在内存段上替换,打到hook目底,获取so加载真实位置 destin-- 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。 source-- 指向要复制的数据源,类型强制转换为 void* 指针。 n-- 要被复制的字节数。*/ memcpy(__insns, "\xFD\x7B\xBF\xA9\xFD\x03\x00\x91\xFE\x03\x02\xAA\x60\x00\x1F\xD6", 16); memcpy(quick_on_stack_back, "\xFD\x7B\xC1\xA8\xC0\x03\x5F\xD6", 8);
- 通过动态符号表,查询hook方法的内存偏移
#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || defined(__arm__)
return STUBS.quick_on_stack_replace(handle, symbol,
quick_on_stack_back, dlsym);
- hook签名方法的内存地址
//HOOk地址不可空
dump((void*)(open_addr),openMethodCallBack, NULL,"openCommon");
读写寄存器、堆栈。接收读写寄存器和堆栈,实现dump的目的,原函数继续运行,在不影响原函数运行情况下实现hook,避免无法多次调用同一个被hook函数的调用异常。
- 备份原函数代码段
//arm模式下为24
pInfo->backUpLength = 24;
memcpy(pInfo->szbyBackupOpcodes, pInfo->pBeHookAddr, pInfo->backUpLength);
- malloc shellcode汇编代码到内存段
//malloc一段新的stub代码
long pagesize = sysconf(_SC_PAGE_SIZE);
void *pNewShellCode = NULL;// = malloc(sShellCodeLength);
int code = posix_memalign(&pNewShellCode, pagesize, pagesize);
LE("pNewShellCode=%p", pNewShellCode);
if(code || pNewShellCode == NULL)
{
LOGI("shell code malloc fail.");
break;
}
memcpy(pNewShellCode, p_shellcode_start_s, sShellCodeLength);
- 填充shellcode执行之后的回调地址
BYTE szLdrPCOpcodes[24] = {0xe1, 0x03, 0x3f, 0xa9, 0x40, 0x00, 0x00, 0x58, 0x00, 0x00, 0x1f, 0xd6};
//将目的地址拷贝到跳转指令缓存位置
memcpy(szLdrPCOpcodes + 12, &pJumpAddress, 8);
szLdrPCOpcodes[20] = 0xE0;
szLdrPCOpcodes[21] = 0x83;
szLdrPCOpcodes[22] = 0x5F;
szLdrPCOpcodes[23] = 0xF8;
LOGI("LIVE4.3.4");
//将构造好的跳转指令刷进去
memcpy(pCurAddress, szLdrPCOpcodes, 24);
- 最后构建跳转,在方法被调用时,跳转到shellcode代码段
//修改原位置的页属性,保证可写
if(ChangePageProperty(pInfo->pBeHookAddr, 24) == false)
{
LOGI("change page property error.");
break;
}
LOGI("LIVE4.3");
//填充跳转指令
if(BuildArmJumpCode(pInfo->pBeHookAddr, pInfo->pStubShellCodeAddr, pInfo) == false)
- hook回调,param1-param7从x0-x6取,更多参数从栈中取
void openMethodCallBack(my_pt_regs *regs, HK_INFO *pInfo) //参数regs就是指向栈上的一个数据结构,由第二部分的mov r0, sp所传递。
{
const char *name = "null";
if (pInfo) {
if (pInfo->methodName) {
name = pInfo->methodName;
} else {
char buf[20];
sprintf(buf, "%p", pInfo->pBeHookAddr);
name = buf;
}
}
LE("tid=%d, onPreCallBack:%s, x0=0x%llx, x1=0x%llx",gettid(), name,
regs->uregs[0], regs->uregs[1]);
- shellcode核心段,拉伸栈空间,保存真实sp、状态寄存器、LR。再次移动栈帧,将x0-x29寄存器按照低地址向高地址的方向从下向上依次存入,回调hook方法传入sp和结构指针作为参数。恢复寄存器,恢复栈平衡,跳转原函数继续执行。
_dump_start: //用于读写寄存器/栈,需要自己解析参数,不能读写返回值,不能阻止原函数(被hook函数)的执行
//从行为上来我觉得更偏向dump,所以起名为dump。
sub sp, sp, #0x20; //sp=sp-0x20,拉伸栈空间,跳板在栈上存储了x0、x1,但是未改变sp的值
mrs x0, NZCV //将系统的NZCV标志位状态寄存器移植到通用寄存器
str x0, [sp, #0x10]; //覆盖跳板存储的x1,存储状态寄存器
str x30, [sp]; //存储x30
add x30, sp, #0x20 //sp+0x20,sp初始地址
str x30, [sp, #0x8]; //存储真实的sp
ldr x0, [sp, #0x18]; //取出跳板存储的x0
save_x0_x29://保存寄存器x0-x29
sub sp, sp, #0xf0; //分配栈空间,向下递减
stp X0, X1, [SP]; //存储x0-x29
stp X2, X3, [SP,#0x10]
...
call_onPreCallBack://调用onPreCallBack函数,第一个参数是sp,第二个参数是STR_HK_INFO结构体指针
mov x0, sp; //x0作为第一个参数,那么操作x0=sp,即操作栈读写保存的寄存器
ldr x1, _hk_info;
ldr x3, [x1]; //onPreCallBack
bl get_lr_pc; //返回地址保存到lr,lr为下条指令
add lr, lr, #8; //lr为blr x3的地址
str lr, [sp, #0x108]; //lr当作pc,覆盖栈上的x0
blr x3 //绝对跳转X3,返回地址保存到LR
restore_regs://恢复寄存器
ldr x0, [sp, #0x100]; //取出状态寄存器
msr NZCV, x0
...
- 将libshelling.so复制到/data/local/tmp目录下
- chmod 777 libshelling.so 赋值读写执行权限
- setenforce 0 修改linux防火墙
- 在XposedManager中选中模块
- 脱壳成功,dex路径/data/data/{packagename}/dump