首页
论坛
课程
招聘
[原创]Frida-syscall-interceptor
2021-6-15 11:18 16309

[原创]Frida-syscall-interceptor

2021-6-15 11:18
16309

一、目标

现在很多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个字节,我们先看看修改之前的情况。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 我们定位的锚是 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);

跑一下结果

1
2
3
4
5
6
7
8
9
10
11
12
==== 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。 听起来很牛的样子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 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();

再跑一下,

1
2
3
4
5
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 的原理,那就想怎么玩就怎么玩。


[注意] 欢迎加入看雪团队!base上海,招聘CTF安全工程师,将兴趣和工作融合在一起!看雪20年安全圈的口碑,助你快速成长!

上传的附件:
收藏
点赞12
打赏
分享
最新回复 (6)
雪    币: 790
活跃值: 活跃值 (1053)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小希希 活跃值 2021-6-15 13:52
2
0
感谢分享
雪    币: 133
活跃值: 活跃值 (342)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
koflfy 活跃值 1 2021-6-16 12:52
3
0
mark
雪    币: 1702
活跃值: 活跃值 (1110)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
TUGOhost 活跃值 2021-6-16 17:09
4
0
mark
雪    币: 376
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
cheny76 活跃值 2021-6-16 19:57
5
0
mark
雪    币: 396
活跃值: 活跃值 (92)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
SniperOB 活跃值 2021-6-24 13:47
6
0
mark 感谢大佬分享
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
王四五 活跃值 2021-11-15 15:27
7
0
大佬请教下 按照你的代码, 为什么我一调用 app程序就崩溃了呢。
下面是我的代码 
class SyscallCallback {
    constructor(frida, native) {
        this.frida = frida;
        this.native = native;
    }
}

let callbacks = [];
function hookSyscall(syscallAddress, callback) {
    console.log(Process.arch)
    if (Process.arch == 'arm') {
        const address = syscallAddress.sub(8);
        const instructions = address.readByteArray(20);
        if (instructions == null) {
            throw new Error(`Unable to read instructions at address ${address}.`);
        }
        console.log(" ==== old instructions ==== " + address);
        console.log(instructions);
        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);
    }
    else {
        const address = syscallAddress.sub(12);
        const instructions = address.readByteArray(12);
        if (instructions == null) {
            throw new Error(`Unable to read instructions at address ${address}.`);
        }
        console.log(" ==== old instructions ==== " + address);
        console.log(instructions);
        Memory.patchCode(address, 16, function (code) {
            let writer = null;
            writer = new Arm64Writer(code, { pc: address });
            writer.putBranchAddress(createCallback(callback, instructions, address.add(16), syscallAddress));
            writer.flush();
        });
        console.log(" ==== new instructions ==== " + address);
        const instructionsNew = address.readByteArray(12);
        console.log(instructionsNew);
    }
}


function createCallback(callback, instructions, retAddress, syscallAddress) {
    // Create custom instructions.
    let frida = Memory.alloc(Process.pageSize);
    Memory.patchCode(frida, Process.pageSize, function (code) {
        let writer = null;
        if (Process.arch == 'arm') {
            /*
            * Created by fenfei
            * http://91fans.com.cn/
            * public accounts: fenfei330
            * wx:fenfei331  mail:fenfei331@126.com
           */
            writer = new ArmWriter(code, { pc: frida });
            // Restore argument instructions.            
            writer.putBytes(instructions);

            // FE 5F 2D E9 STMFD  SP!, {R1-R12,LR} 寄存器入栈 不存 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条指令
            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);

            // FE 9F BD E8 LDMFD  SP!, {R1-R12,PC}    寄存器出栈 不存 r0
            // FF 5F BD E8 LDMFD  SP!, {R0-R12,LR}    寄存器出栈 
            writer.putInstruction(0xE8BD5FFF);
        }
        else {
            writer = new Arm64Writer(code, { pc: frida });
            // Restore argument instructions.
            writer.putBytes(instructions);
            // Push all registers except x0.
            writer.putPushRegReg('x15', 'x1');
            writer.putPushRegReg('x2', 'x3');
            writer.putPushRegReg('x4', 'x5');
            writer.putPushRegReg('x6', 'x7');
            writer.putPushRegReg('x8', 'x9');
            writer.putPushRegReg('x10', 'x11');
            writer.putPushRegReg('x12', 'x13');
            writer.putPushRegReg('x14', 'x15');
            writer.putPushRegReg('x16', 'x17');
            writer.putPushRegReg('x18', 'x19');
            writer.putPushRegReg('x20', 'x21');
            writer.putPushRegReg('x22', 'x23');
            writer.putPushRegReg('x24', 'x25');
            writer.putPushRegReg('x26', 'x27');
            writer.putPushRegReg('x28', 'x29');
            writer.putInstruction(0xd53b420f); // 保存状态寄存器
            writer.putPushRegReg('x30', 'x15');
            // Call native.
            writer.putLdrRegAddress('x16', callback);
            writer.putBlrReg('x16');
            // Pop all registers, except x0, so x0 from native call gets used.
            writer.putPopRegReg('x30', 'x15');
            writer.putInstruction(0xd51b420f); // 还原状态寄存器
            writer.putPopRegReg('x28', 'x29');
            writer.putPopRegReg('x26', 'x27');
            writer.putPopRegReg('x24', 'x25');
            writer.putPopRegReg('x22', 'x23');
            writer.putPopRegReg('x20', 'x21');
            writer.putPopRegReg('x18', 'x19');
            writer.putPopRegReg('x16', 'x17');
            writer.putPopRegReg('x14', 'x15');
            writer.putPopRegReg('x12', 'x13');
            writer.putPopRegReg('x10', 'x11');
            writer.putPopRegReg('x8', 'x9');
            writer.putPopRegReg('x6', 'x7');
            writer.putPopRegReg('x4', 'x5');
            writer.putPopRegReg('x2', 'x3');
            writer.putPopRegReg('x15', 'x1');
        }
        // Call syscall.
        // writer.putInstruction(0xd4000001);
        writer.putBranchAddress(retAddress);
        writer.flush();
    });
    console.log("==== frida ====");
    console.log(frida);

    console.log("==== retAddress ====");
    console.log(retAddress);
    // Store callback so it doesn't get garbage collected.
    callbacks.push(new SyscallCallback(frida, callback));
    // Return pointer to the instructions.
    return callbacks[callbacks.length - 1].frida;
}


Java.perform(function () {
    console.log("Full call stack: ");
       var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
    Interceptor.attach(android_dlopen_ext, {
        onEnter: function (args) {
            this.call_hook = false;
            var so_name = ptr(args[0]).readCString();
            if (so_name.indexOf("libnative-lib.so") >= 0) {
                console.log("android_dlopen_ext:", ptr(args[0]).readCString());
                this.call_hook = true;
            }

        }, onLeave: function (retval) {
            if (this.call_hook) {
                inline_hook();
            }
        }
    });


});

function inline_hook() {
    var libnative_lib_addr = Module.findBaseAddress("libnative-lib.so");
    console.log("libnative_lib_addr:", libnative_lib_addr);
    if (libnative_lib_addr){
        var address = libnative_lib_addr.add(0x81F8); 
        // var address = libnative_lib_addr.add(0x47D66); 
        // 0x47FB4  0x42634 0x43B3A 0x4256E 0x42862 0x47ECC 0x44CBE 0x446BE 0x440B2 
        console.log(address);
               hookSyscall(address, new NativeCallback(function (dirfd, pathname, mode, flags) {
            let path = pathname.readCString();
            console.log(`Called faccessat hook11`);
            console.log('- X0: ' + dirfd);
            console.log('- X1: ' + path);
            console.log('- X2: ' + mode);
            console.log('- X3: ' + flags);

            return 0;
        }, 'int', ['int', 'pointer', 'int', 'int']));
        

    }

}

游客
登录 | 注册 方可回帖
返回