关于安全:Fridasyscallinterceptor

8次阅读

共计 3363 个字符,预计需要花费 9 分钟才能阅读完成。

一、指标

当初很多 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  0123456789ABCDEF
00000000  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  0123456789ABCDEF
00000000  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, R10
writer.putInstruction(0xE8BD0400);
writer.putInstruction(0xE129F00A);

// FF 5F BD E8 LDMFD  SP!, {R0-R12,LR}    寄存器出栈 
writer.putInstruction(0xE8BD5FFF);

// 我回来了 0x93762204
writer.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 的原理,那就想怎么玩就怎么玩。

每每剖开本人写过的代码,外面都应有血流进去。

正文完
 0