一、指标
当初很多App不讲武德了,为了避免 openat 、read、kill 等等底层函数被hook,罗唆就间接通过syscall的形式来做零碎调用,导致无奈hook。
应答这种状况有两种计划:
- 刷机重写零碎调用表来拦挡内核调用
- inline Hook SWI/SVC指令
咱们明天采纳第二种办法,用frida来实现
- 内联汇编SWI/SVC做零碎调用, syscall
- frida inline hook
- hook syscall
- frida ArmWriter
- frida typescript project
二、步骤
inline Hook 原理
. 备份SWI/SVC局部的指令,重写成为跳转指令
. 跳转到咱们新的代码空间,把之前备份的指令执行一下。而后执行咱们本人的逻辑。 (打印参数之类的)
. 跳回原程序空间,持续往下跑
重写成为跳转指令
这次hook应用 frida ArmWriter 来实现,用 putBranchAddress 函数写个跳转指令,须要花20个字节,咱们先看看批改之前的状况。
// 咱们定位的锚是 svc指令, 0x9374C1F8 它后面还有8个字节的指令这里一并替换const address = syscallAddress.sub(8);// 备份这20个字节,马上它们就要被替换了const instructions = address.readByteArray(20);if (instructions == null) { throw new Error(`Unable to read instructions at address ${address}.`);}// 把旧的20个字节打印进去console.log(" ==== old instructions ==== " + address);console.log(instructions);// 开始替换成跳转指令,跳转的地址是 createCallback 外面创立的新的代码空间地址。Memory.patchCode(address, 20, function (code) { let writer = null; writer = new ArmWriter(code, { pc: address }); writer.putBranchAddress(createCallback(callback, instructions, address.add(20), syscallAddress)); writer.flush();});// 把新的指令打进去比照下console.log(" ==== new instructions ==== " + address);const instructionsNew = address.readByteArray(20);console.log(instructionsNew);
跑一下后果
==== old instructions ==== 0x937621f0 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF00000000 07 c0 a0 e1 42 71 00 e3 00 00 00 ef 0c 70 a0 e1 ....Bq.......p..00000010 01 0a 70 e3 ..p. ==== new Code Addr ====0xa9b83000 ==== retAddress ====0x93762204 ==== new instructions ==== 0x937621f0 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF00000000 01 80 2d e9 04 00 9f e5 04 00 8d e5 01 80 bd e8 ..-.............00000010 00 30 b8 a9 .0..
指令是批改胜利了,然而批改的对不对呢? 这时候须要祭出 IDA,Attach一下咱们的demo。比照一下。
新的代码空间地址是 0xa9b83000。 从咱们批改的指令来看,它会跳转到 0xa9b83000, 木有问题。
而后返回的地址是 0x93762204, 恰好也是要回来的地址。
执行备份指令,和咱们本人的逻辑
执行备份指令比较简单,然而咱们本人的逻辑可不能用Arm汇编来写,frida曾经帮咱们想好了,能够创立一个 NativeCallback, 执行备份指令之后,间接能够跳转到 firida的 NativeCallback。 听起来很牛的样子。
// Hook 逻辑,这里只打印 参数hookSyscall(address, new NativeCallback(function (dirfd, pathname, mode, flags) { let path = pathname.readCString(); log('Called openat hook'); log('- R0: ' + dirfd); log('- R1: ' + path); log('- R2: ' + mode); log('- R3: ' + flags); return 0;}, 'int', ['int', 'pointer', 'int', 'int']));......// 创立一个新的代码空间,放咱们本人的代码let frida = Memory.alloc(Process.pageSize);// 开始写程序了writer = new ArmWriter(code, { pc: frida });// 执行备份的指令writer.putBytes(instructions);// 寄存器入栈,这里把r0也入栈了// FF 5F 2D E9 STMFD SP!, {R0-R12,LR} 寄存器入栈 writer.putInstruction(0xE92D5FFF);// 00 A0 0F E1 MRS R10, CPSR// 00 04 2D E9 STMFD SP!, {R10} // 状态寄存器入栈writer.putInstruction(0xE10FA000);writer.putInstruction(0xE92D0400);// instructions.size = 20 + 5条指令// 批改lr寄存器,保障执行咱们本人的逻辑之后还能回来持续向下执行。writer.putLdrRegAddress("lr",frida.add(20 + 5*4));writer.putBImm(callback);// 00 04 BD E8 LDMFD SP!, {R10} // 状态寄存器出栈 // 0A F0 29 E1 MSR CPSR_cf, R10writer.putInstruction(0xE8BD0400);writer.putInstruction(0xE129F00A);// FF 5F BD E8 LDMFD SP!, {R0-R12,LR} 寄存器出栈 writer.putInstruction(0xE8BD5FFF);// 我回来了 0x93762204writer.putBranchAddress(retAddress);writer.flush();
再跑一下,
Called openat hook- R0: 86- R1: /proc/self/maps - R2: 0- R3: 0
三、总结
本文来自https://github.com/AeonLucid/frida-syscall-interceptor ,(对,就是AndroidNativeEmu的作者。 Orz) ,作者实现了 arm64上面的hook,咱们把arm32的hook补上了,所以调试的时候须要在 arm32的手机下来调试。
frida 脚本采纳 typescript project, 调试和编译脚本的时候须要参照 https://github.com/oleavr/frida-agent-example 。
frida-syscall-interceptor和frida-agent-example在同级目录。
生成js文件时,会提醒ArmWriter的putBranchAddress函数找不到,其实这个函数是存在的,只是库文件没有更新, 手工在 declare class ArmWriter 外面减少一下 putBranchAddress 的申明。
咱们的实现只是把参数和后果打印进去了,在咱们本人的 NativeCallback 中并不能不便的去批改这些入参和返回值。这个就给大家留作业了。 参照 https://github.com/zhuotong/Android\_InlineHook 的原理,那就想怎么玩就怎么玩。
每每剖开本人写过的代码,外面都应有血流进去。