IOS逆向(二)-绕过某租车app越狱和代理检测

因为日常使用shadowrocket,所以每次打开某租车app都会提示检测到手机开了代理,并且在越狱设备上打开还会直接闪退。如何绕过app的代理和越狱检测呢?

难度

★★☆☆☆

工具环境

  • 越狱IOS 14.4
  • frida-ios-dump
  • frida
  • frida-trace
  • IDA 7.7

IDA逆向分析

通过砸壳得到app的maco文件,直接扔进ida里面分析。既然app做了越狱检测,那么先直接搜索 jail 相关的函数名。

确实存在越狱检测的函数,通过检查一些特定的文件和目录是否存在来判断设备是否已经越狱。”Cydia.app”是一个在越狱设备上常见的应用商店,而”/bin/bash”和”/usr/sbin/sshd”则表示设备已经获得了全面的文件系统访问权限。

检测的过程是通过NSFileManager的fileExistsAtPath:方法来实现的。如果检测到任何一个文件或目录存在,函数就会返回1,表示设备已经越狱。如果所有的文件和目录都不存在,函数就会返回0,表示设备没有越狱。

frida-trace hook越狱函数

看到这里大家可能会想到,那我直接用frida来hook越狱检测函数 +[_priv_NBSProbe isJailBreak] 不就可以了?

一开始我也是这样想的,所以直接用frida-trace进行hook。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
frida-trace -U -f com.szzc.szzc -m "+[_priv_NBSProbe isJailBreak]"
frida-trace -U -f com.szzc.szzc -m "+[_priv_NBSProbe isJailBreak]"
frida-trace -U -f  com.szzc.szzc -m "+[_priv_NBSProbe isJailBreak]"

修改hook代码如下,替换函数返回值为0绕过检测。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{
onEnter(log, args, state) {
log(`进入 +[_priv_NBSProbe isJailBreak] 检测函数`);
log(args[0]);
},
onLeave(log, retval, state) {
log('退出 +[_priv_NBSProbe isJailBreak] 检测函数');
retval.replace(0);
}
}
{ onEnter(log, args, state) { log(`进入 +[_priv_NBSProbe isJailBreak] 检测函数`); log(args[0]); }, onLeave(log, retval, state) { log('退出 +[_priv_NBSProbe isJailBreak] 检测函数'); retval.replace(0); } }
{
  onEnter(log, args, state) {
    log(`进入 +[_priv_NBSProbe isJailBreak] 检测函数`);
    log(args[0]);
  },
  onLeave(log, retval, state) {
    log('退出 +[_priv_NBSProbe isJailBreak] 检测函数');
    retval.replace(0);
  }
}

程序在进入该函数之前,已经退出了。说明是app还有其他的检测函数在该函数之前先执行并退出了。

定位程序退出的堆栈

如何定位程序退出时的堆栈?可以通过frida-trace来hook系统的exit或abort函数。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
frida-trace -U -i "exit" -i "abort" -f com.szzc.szzc
frida-trace -U -i "exit" -i "abort" -f com.szzc.szzc
frida-trace -U -i "exit" -i "abort" -f com.szzc.szzc

并添加exit的js代码如下:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{
onEnter(log, args, state) {
log(`exit(status=${args[0]})`);
log('exit() called from:\n' +
Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n') + '\n');
},
onLeave(log, retval, state) {
}
}
{ onEnter(log, args, state) { log(`exit(status=${args[0]})`); log('exit() called from:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join('\n') + '\n'); }, onLeave(log, retval, state) { } }
{
  onEnter(log, args, state) {
    log(`exit(status=${args[0]})`);
    log('exit() called from:\n' +
    Thread.backtrace(this.context, Backtracer.ACCURATE)
    .map(DebugSymbol.fromAddress).join('\n') + '\n');
  },
  onLeave(log, retval, state) {
  }
}

frida打印程序退出时的堆栈,可以看到是在0x275d4b8的偏移地址执行退出的。

IDA分析程序退出的堆栈

这里解释一下frida打印的三个地址的含义。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
0x1047994b8 WCCApp!0x275d4b8 (0x10275d4b8)
0x1047994b8 WCCApp!0x275d4b8 (0x10275d4b8)
0x1047994b8 WCCApp!0x275d4b8 (0x10275d4b8)
  • 0x1047994b8 是程序在内存中实际的内存地址,因为存在地址空间布局随机化(ASLR),运行看到的地址都是相对于程序的基址的偏移,这个基址在每次运行时都会改变。
  • 0x275d4b8 是该函数相对于基址的偏移地址
  • 0x10275d4b8 是该函数在IDA中的地址,因为IDA的默认基址为0x100000000,0x10275d4b8 = 0x100000000 + 0x275d4b8

在IDA中来到0x10275d4b8的地址。

发现没有并不是判断异常的代码逻辑,来到堆栈的上一层0x100007464

这里有几十个判断逻辑,可以看到越狱检测的和代理检测都有,应该是所有的检测都放到了一起,依次检测判断。从字符串可以推断是app集成了爱加密的sdk。

frida 踩坑

那么是否直接hook 0x7464 地址就行呢?

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
frida -U -l wcc.js -f com.szzc.szzc
frida -U -l wcc.js -f com.szzc.szzc
frida -U -l wcc.js -f com.szzc.szzc

wcc.js 代码如下:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
var baseAddr = Module.findBaseAddress('WCCApp');
var offsetAddr = 0x7464 // 0x100007464
var targetAddr = baseAddr.add(offsetAddr);
console.log("WCCApp 基址: " + baseAddr);
console.log("目标函数地址: " + targetAddr);
Interceptor.attach(targetAddr, {
onEnter: function(args) {
console.log("函数hook成功!");
console.log(' called from:\n' +
Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n') + '\n');
this.skip = true;
},
onLeave: function(retval) {
console.log("Function execution finished.");
}
});
var baseAddr = Module.findBaseAddress('WCCApp'); var offsetAddr = 0x7464 // 0x100007464 var targetAddr = baseAddr.add(offsetAddr); console.log("WCCApp 基址: " + baseAddr); console.log("目标函数地址: " + targetAddr); Interceptor.attach(targetAddr, { onEnter: function(args) { console.log("函数hook成功!"); console.log(' called from:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join('\n') + '\n'); this.skip = true; }, onLeave: function(retval) { console.log("Function execution finished."); } });
var baseAddr = Module.findBaseAddress('WCCApp');
var offsetAddr = 0x7464 // 0x100007464
var targetAddr = baseAddr.add(offsetAddr); 

console.log("WCCApp 基址: " + baseAddr);
console.log("目标函数地址: " + targetAddr);

Interceptor.attach(targetAddr, {
    onEnter: function(args) {
        console.log("函数hook成功!");
        console.log(' called from:\n' +
        Thread.backtrace(this.context, Backtracer.ACCURATE)
        .map(DebugSymbol.fromAddress).join('\n') + '\n');
        this.skip = true;
    },
    onLeave: function(retval) {
        console.log("Function execution finished.");
    }
});

这里frida执行没有打印出堆栈信息是为什么呢?

frida hook函数需要对函数名的偏移地址然后在基址上添加,而不是函数内部的偏移地址。

为什么用frida给的偏移地址hook不成功?

查看函数的交叉应用。

这里我推测是上层函数中调用里面用了异步方法导致的。

交叉应用定位外层函数调用

再往上查找一层,IDA能识别出这是一个objc的方法。

绕过越狱检测

到这一步思路就很清晰了,直接用frida 来hook这个函数然后把把sub_100007008替换成一个空函数就行。重新写一个frida脚本为wcc_jail.js

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
var baseAddr = Module.findBaseAddress('WCCApp');
console.log("WCCApp base address: " + baseAddr);
//0x7008是sub_100007008越狱检测函数的偏移地址
var targetFunctionAddr = baseAddr.add(0x7008);
console.log("Target function address: " + targetFunctionAddr);
const targetFunction = new NativeFunction(targetFunctionAddr, 'void', []);
// 把sub_100007008替换成一个空函数
Interceptor.replace(targetFunctionAddr, new NativeCallback(function () {
console.log("Skip the execution of sub_100007008");
}, 'void', []));
//0x8A24是-[RootViewController viewDidLoad]的偏移地址
const targetFunctionAddrRootVC = baseAddr.add(0x8A24);
Interceptor.attach(targetFunctionAddrRootVC, {
onEnter: function (args) {
console.log('Entering -RootViewController viewDidLoad!');
},
onLeave: function (retval) {
console.log('Leaving -RootViewController viewDidLoad!');
}
});
var baseAddr = Module.findBaseAddress('WCCApp'); console.log("WCCApp base address: " + baseAddr); //0x7008是sub_100007008越狱检测函数的偏移地址 var targetFunctionAddr = baseAddr.add(0x7008); console.log("Target function address: " + targetFunctionAddr); const targetFunction = new NativeFunction(targetFunctionAddr, 'void', []); // 把sub_100007008替换成一个空函数 Interceptor.replace(targetFunctionAddr, new NativeCallback(function () { console.log("Skip the execution of sub_100007008"); }, 'void', [])); //0x8A24是-[RootViewController viewDidLoad]的偏移地址 const targetFunctionAddrRootVC = baseAddr.add(0x8A24); Interceptor.attach(targetFunctionAddrRootVC, { onEnter: function (args) { console.log('Entering -RootViewController viewDidLoad!'); }, onLeave: function (retval) { console.log('Leaving -RootViewController viewDidLoad!'); } });
var baseAddr = Module.findBaseAddress('WCCApp');
console.log("WCCApp base address: " + baseAddr);
//0x7008是sub_100007008越狱检测函数的偏移地址
var targetFunctionAddr = baseAddr.add(0x7008); 
console.log("Target function address: " + targetFunctionAddr);

const targetFunction = new NativeFunction(targetFunctionAddr, 'void', []);

// 把sub_100007008替换成一个空函数
Interceptor.replace(targetFunctionAddr, new NativeCallback(function () {
    console.log("Skip the execution of sub_100007008");
}, 'void', []));
//0x8A24是-[RootViewController viewDidLoad]的偏移地址
const targetFunctionAddrRootVC = baseAddr.add(0x8A24); 

Interceptor.attach(targetFunctionAddrRootVC, {
    onEnter: function (args) {
        console.log('Entering -RootViewController viewDidLoad!');
    },
    onLeave: function (retval) {
        console.log('Leaving -RootViewController viewDidLoad!');
    }
});

执行命令如下:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
frida -U -l wcc_jail.js -f com.szzc.szzc
frida -U -l wcc_jail.js -f com.szzc.szzc
frida -U -l wcc_jail.js -f com.szzc.szzc

效果演示

赞赏

微信赞赏支付宝赞赏