原文地址:https://www.zhuoyue360.com/crack/108.html
前言
随着逆向的攻防强度不断的提升,目前主流的移动安全厂商的加固服务基本上都已包含了常见Hook框架的反调试,我们最常见的hook工具如下:
- frida
- xposed
为了更好的提升自己相关的经验,我们可以拿这类demo来进行原理的学习.
分析
demo应用在未启动frida的情况下,点击点我按钮发现. 亮了一个frida-agent路径.我们可以反编译该应用去看看它如何检测的.
1. frida-agent路径
根据它的代码可以看到,该检测案例是通过判断下面两个条件,只要满足以下两个其中之一,则判定frida存在.
/data/local/tmp/re.frida.server/frida-agent-64.so
路径的内容是否存在/data/local/tmp/re.frida.server/frida-agent.so
路径的文件是否存在
public static boolean a() {
boolean z = new File("/data/local/tmp/re.frida.server/frida-agent-64.so").exists() || new File("/data/local/tmp/re.frida.server/frida-agent.so").exists();
if (z) {
Log.e("Ming-CheckFrida", "/data/local/tmp/re.frida.server/frida-agent-64.so中发现Frida特征");
}
return z;
}
看了下,我们的确存在这些应用.我们删除一下看看.
把re.frida.server
目录删除就可以了. 当然,这种方法并不好. 我们可以尝试Hook一下 File
,把入参包含frida-agent
的fileName给他一个不存在的值,即可绕过该检测. 可以看到已经成功的绕过了frida-agent
检测项.
let _File = Java.use("java.io.File");
_File.$init.overload("java.lang.String").implementation = function(fileName){
var f = '';
if (fileName.indexOf("frida-agent") > -1){
f = "/sdcard/fuck_frida";
}else{
f= fileName
}
console.log("fileName ->" + f,f.indexOf("frida-agent"))
return this.$init(f);
}
2.TCP检测
根据下面的代码片段,有点基础的读者应该不难看出.它是在检测端口69a2
,69a2
端口是十六进制的表示,那么其十进制的表示是27042
端口.
this.t = (Switch) findViewById(R.id.switchTcp);
this.t.setChecked(false);
String a2 = CheckFrida.a("/proc/net/tcp6");
String a3 = CheckFrida.a("/proc/net/tcp");
if (a2 == null || ClockFaceView.VALUE_PLACEHOLDER.equals(a2)) {
z2 = false;
} else {
z2 = false;
for (String str : a2.split("\n")) {
if (str.toLowerCase().contains(":69a2")) {
Log.e("Ming-CheckFrida", "tcp文件中发现Frida特征");
z2 = true;
}
}
}
if (a3 != null && !ClockFaceView.VALUE_PLACEHOLDER.equals(a3)) {
for (String str2 : a3.split("\n")) {
if (str2.toLowerCase().contains(":69a2")) {
Log.e("Ming-CheckFrida", "tcp文件中发现Frida特征");
z2 = true;
}
}
}
public static String a(String str) {
try {
FileInputStream fileInputStream = new FileInputStream(str);
byte[] bArr = new byte[RecyclerView.c0.FLAG_ADAPTER_FULLUPDATE];
String str2 = ClockFaceView.VALUE_PLACEHOLDER;
for (int read = fileInputStream.read(bArr); read > 0; read = fileInputStream.read(bArr)) {
str2 = str2 + new String(bArr, 0, read);
}
fileInputStream.close();
return str2;
} catch (IOException e) {
e.printStackTrace();
return ClockFaceView.VALUE_PLACEHOLDER;
}
}
那么我们该如何做呢? 看到上面的方法a
可以看出,它是以读文件
的方式进行检测的. 此时我们应该以自定义端口
的方式进行frida的启动及Hook.
frida-server启动命令:
./frida-server-16.0.2-android-arm64 -l 0.0.0.0:7777
frida hook 自定义端口命令
frida -H 192.168.123.85:7777 -F -l .\hook_frida_check.js
再来看看效果,Wow!! 看来这不只是解决了TCP检测
项,我们还顺手的解决了Native-端口
检测. 其原理都是一样的.就是看看27042
端口是否打开,咱们从本质上去解决就好啦!
3.map文件
根据下面代码,我们可以看到.它是通过读取文件/proc/self/maps
的内容是否包含frida
字眼进行检测的.
this.t = (Switch) findViewById(R.id.switchJMap);
this.t.setChecked(false);
if (CheckFrida.a("/proc/self/maps").contains("frida")) {
Log.e("Ming-CheckFrida", "/proc/self/maps发现Frida特征");
z = true;
} else {
z = false;
}
我们其实可以复用我们上面的脚本,当文件名称为maps
的时候,我们传入虚拟的maps
文件.
通过执行cat /proc/self/maps >> /sdcard/maps
命令maps内容到/sdcard/maps
中. 来看看效果吧~
let _File = Java.use("java.io.File");
_File.$init.overload("java.lang.String").implementation = function(fileName){
var f = '';
if (fileName.indexOf("frida-agent") > -1){
f = "/sdcard/fuck_frida";
}else if(fileName.indexOf("/proc/self/maps") > -1){
f = "/sdcard/maps";
}else{
f= fileName
}
return this.$init(f);
}
4. Native-map文件
打开IDA,拖入so文件进去看看. 发现是动态注册
且JNI_onload
被混淆了.用下registerNative
我们需要的是mCheckFridaNMap
,其偏移是0xb654
[RegisterNatives] method_count: 0x6
[RegisterNatives] java_class: com.example.test.util.CheckFrida name: mCheckFridaPort sig: ()Z fnPtr: 0x7dd0acd540 fnOffset: 0x7dd0acd540 libCheckFrida.so!0x9540 callee: 0x7dd0acd020 libCheckFrida.so!0x9020
[RegisterNatives] java_class: com.example.test.util.CheckFrida name: mCheckFridaBus sig: ()Z fnPtr: 0x7dd0acda70 fnOffset: 0x7dd0acda70 libCheckFrida.so!0x9a70 callee: 0x7dd0acd020 libCheckFrida.so!0x9020
[RegisterNatives] java_class: com.example.test.util.CheckFrida name: mCheckFridaNMap sig: ()Z fnPtr: 0x7dd0acf654 fnOffset: 0x7dd0acf654 libCheckFrida.so!0xb654 callee: 0x7dd0acd020 libCheckFrida.so!0x9020
[RegisterNatives] java_class: com.example.test.util.CheckFrida name: mCheckFridaMem sig: ()Z fnPtr: 0x7dd0ad0998 fnOffset: 0x7dd0ad0998 libCheckFrida.so!0xc998 callee: 0x7dd0acd020 libCheckFrida.so!0x9020
[RegisterNatives] java_class: com.example.test.util.CheckFrida name: mCheckFridaMem2 sig: ()Ljava/lang/String; fnPtr: 0x7dd0ad010c fnOffset: 0x7dd0ad010c libCheckFrida.so!0xc10c callee: 0x7dd0acd020 libCheckFrida.so!0x9020
[RegisterNatives] java_class: com.example.test.util.CheckFrida name: mCheckFridaP sig: ()Ljava/lang/String; fnPtr: 0x7dd0ad0f20 fnOffset: 0x7dd0ad0f20 libCheckFrida.so!0xcf20 callee: 0x7dd0acd020 libCheckFrida.so!0x9020
跳到目标函数发现,依然是进行了混淆的. 我们来分析分析.
查看下F5的伪代码.看到我认为是关键的.
- fopen打开文件,给v1
- 把stream的值设置为v1
// FILE *fopen(const char *filename, const char *mode);
v1 = fopen((const char *)&xmmword_F0A0, &byte_F0B0);
v2 = -686024801;
stream = v1;
我们分别查看一下v1
和stream
的交叉应用(选中按下x
键)
v1的交叉引用:
可以发现,v1
只有一处使用了,就是把steam
的值设置为v1
,那么接下来,我们只需要聚焦steam
即可.
steam的交叉引用
- fclose : 关闭流
- fgets : 从指定的流中读取数据,每次读取一行。其原型为:char *fgets(char *str, int n, FILE *stream);
我们只需要看看fgets
上下文. 先到215行,结果为v41
跟踪v41
根据交叉引用,去看看121行的内容.
再看看haystack的引用, 发现了一个很关键的strstr
- strstr : 返回值为char * 类型( 返回指向 str1 中第一次出现的 str2 的指针);如果 str2 不是 str1 的一部分,则返回空指针。
此时可以hook一下,strstr
函数和fopen
函数
fopen hook:
function hook_native(){
var add = Module.findExportByName(null,"fopen");
console.log("fopenaddr", add);
Interceptor.attach(add,{
onEnter:function(args){
console.log("[+]fopen("+args[0].readCString()+")")
},
onLeave:function(){
console.log("[-]fopen")
}
})
var strstr = Module.findExportByName(null,"strstr");
console.log("strstr addr", add);
Interceptor.attach(strstr,{
onEnter:function(args){
console.log("[+]strstr("+args[0].readCString()+","+args[1].readCString()+")")
},
onLeave:function(){
console.log("[-]strstr")
}
})
}
function call_maps(){
Java.perform(function(){
let CheckFrida = Java.use("com.example.test.util.CheckFrida");
CheckFrida.mCheckFridaNMap();
})
}
function hook(){
hook_native();
call_maps()
}
setImmediate(hook_native)
hook 日志:
通过fopen方法可以发现,它读的是/proc/self/maps
,通过strstr
可以发现它是在比较是否包含frida
字符串.
call_maps()
[+]fopen(/proc/self/maps)
[-]fopen
[+]strstr(12c00000-12c80000 rw-p 00000000 00:00 0 [anon:dalvik-main space (region space)]
,frida)
[-]strstr
[+]strstr(12c80000-12e00000 ---p 00000000 00:00 0 [anon:dalvik-main space (region space)]
,frida)
[-]strstr
[+]strstr(12e00000-13100000 ---p 00000000 00:00 0 [anon:dalvik-main space (region space)]
,frida)
[-]strstr
[+]strstr(13100000-13200000 rw-p 00000000 00:00 0 [anon:dalvik-main space (region space)]
,frida)
[-]strstr
[+]strstr(13200000-14100000 ---p 00000000 00:00 0 [anon:dalvik-main space (region space)]
,frida)
[-]strstr
[+]strstr(14100000-14140000 rw-p 00000000 00:00 0 [anon:dalvik-main space (region space)]
,frida)
[-]strstr
[+]strstr(14140000-14180000 ---p 00000000 00:00 0 [anon:dalvik-main space (region space)]
,frida)
[-]strstr
[+]strstr(14180000-141c0000 rw-p 00000000 00:00 0 [anon:dalvik-main space (region space)]
,frida)
[-]strstr
[+]strstr(141c0000-14200000 ---p 00000000 00:00 0 [anon:dalvik-main space (region space)]
,frida)
[-]strstr
[+]strstr(14200000-14240000 rw-p 00000000 00:00 0 [anon:dalvik-main space (region space)]
,frida)
[-]strstr
[+]strstr(14240000-16480000 ---p 00000000 00:00 0 [anon:dalvik-main space (region space)]
,frida)
[-]strstr
[+]strstr(16480000-164c0000 rw-p 00000000 00:00 0 [anon:dalvik-main space (region space)]
,frida)
[-]strstr
[+]strstr(164c0000-16500000 ---p 00000000 00:00 0 [anon:dalvik-main space (region space)]
,frida)
[-]strstr
[+]strstr(16500000-32c00000 rw-p 00000000 00:00 0 [anon:dalvik-main space (region space)]
,frida)
[-]strstr
[+]strstr(7032d000-705b4000 rw-p 00000000 103:11 1544 /system/framework/arm64/boot.art
,frida)
[-]strstr
[+]strstr(705b4000-706a3000 rw-p 00000000 103:11 1523 /system/framework/arm64/boot-core-libart.art
,frida)
[-]strstr
[+]strstr(706a3000-706d9000 rw-p 00000000 103:11 1535 /system/framework/arm64/boot-okhttp.art
,frida)
[-]strstr
[+]strstr(706d9000-7071a000 rw-p 00000000 103:11 1520 /system/framework/arm64/boot-bouncycastle.art
,frida)
[-]strstr
[+]strstr(7071a000-7072a000 rw-p 00000000 103:11 1517 /system/framework/arm64/boot-apache-xml.art
,frida)
[-]strstr
[+]strstr(7072a000-70fe6000 rw-p 00000000 103:11 1529 /system/framework/arm64/boot-framework.art
,frida)
[-]strstr
[+]strstr(70fe6000-71019000 rw-p 00000000 103:11 1526 /system/framework/arm64/boot-ext.art
,frida)
[-]strstr
[+]strstr(71019000-71110000 rw-p 00000000 103:11 1538 /system/framework/arm64/boot-telephony-common.art
,frida)
[-]strstr
[+]strstr(71110000-7111e000 rw-p 00000000 103:11 1541 /system/framework/arm64/boot-voip-common.art
,frida)
[-]strstr
[+]strstr(7111e000-71133000 rw-p 00000000 103:11 1532 /system/framework/arm64/boot-ims-common.art
,frida)
[-]strstr
[+]strstr(71133000-71136000 rw-p 00000000 103:11 1514 /system/framework/arm64/boot-android.test.base.art
那么我们依然使用上面maps文件中的方法 - 伪造maps文件