1.检测原理
1.1 进程名检测
当你的手机上启动了frida-server,在当前app的进程中就有相应的痕迹。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public boolean checkRunningProcesses() { boolean returnValue = false; List<RunningServiceInfo> list = manager.getRunningServices(300); if(list != null){ String tempName; for(int i=0;i<list.size();++i){ tempName = list.get(i).process; if(tempName.contains("frida-server")) { returnValue = true; } } } return returnValue; }
|
如何绕过?
修改下文件名的名称即可,例如:frida-server-16.0.1-android-arm64
改成 fdd
1.2 端口监测
frida默认使用的端口是 27042,有app就会去监测是否开放了此端口。
1 2 3 4 5 6 7 8 9 10 11
| boolean is_frida_server_listening() { struct sockaddr_in sa; memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_port = htons(27042); inet_aton("127.0.0.1", &(sa.sin_addr)); int sock = socket(AF_INET , SOCK_STREAM , 0); if (connect(sock , (struct sockaddr*)&sa , sizeof sa) != -1) { } }
|
如何绕过?
让frida-server在运行时,更换下端口即可。【注意:建议使用 frida-server-14.2.18 】
在手段端运行
在电脑上
1 2 3 4 5 6
| 端口转发 adb forward tcp:8877 tcp:8877 脚本运行: frida -H 127.0.0.1:8877 -l jd.js -f frida -H 127.0.0.1:8877 -l jd.js -f com.che168.autotradercloud --no-pause
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import frida import sys
str_host = "127.0.0.1:8877" manager = frida.get_device_manager() rdev = manager.add_remote_device(str_host) session = rdev.attach("com.che168.autotradercloud")
scr = """ Java.perform(function () { console.log("加载"); }); """ script = session.create_script(scr)
def on_message(message, data): print(message, data)
script.on("message", on_message) script.load() sys.stdin.read()
|
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
| import frida import sys
str_host = "127.0.0.1:8877" manager = frida.get_device_manager() rdev = manager.add_remote_device(str_host)
pid = rdev.spawn(["com.che168.autotradercloud"]) session = rdev.attach(pid)
scr = """ Java.perform(function () {
}); """ script = session.create_script(scr)
def on_message(message, data): print(message, data)
script.on("message", on_message) script.load() rdev.resume(pid) sys.stdin.read()
|
1.3 D-Bus协议通信
frida-server使用D-Bus网络协议通信,有些app就会遍历内部所有端口,向端口发送消息,如果端口回复了REJECT
则表示此端口就是 frida-server,也证明了你在使用frida进行调试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| for(i = 0 ; i <= 65535 ; i++) { sock = socket(AF_INET , SOCK_STREAM , 0); sa.sin_port = htons(i); if (connect(sock , (struct sockaddr*)&sa , sizeof sa) != -1) { __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "FRIDA DETECTION [1]: Open Port: %d", i); memset(res, 0 , 7); send(sock, "\x00", 1, NULL); send(sock, "AUTH\r\n", 6, NULL); usleep(100); if (ret = recv(sock, res, 6, MSG_DONTWAIT) != -1) { if (strcmp(res, "REJECT") == 0) { } } } close(sock); }
|
如何绕过呢?
可以Hook C语言中用于 strstr 和 strcmp 方法,如果出现 REJECT
则返回
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
| function replace_str() { var pt_strstr = Module.findExportByName("libc.so", 'strstr'); var pt_strcmp = Module.findExportByName("libc.so", 'strcmp');
Interceptor.attach(pt_strstr, { onEnter: function (args) { var str1 = args[0].readCString(); var str2 = args[1].readCString(); if (str2.indexOf("REJECT") !== -1) { this.hook = true; } }, onLeave: function (retval) { if (this.hook) { retval.replace(0); } } });
Interceptor.attach(pt_strcmp, { onEnter: function (args) { var str1 = args[0].readCString(); var str2 = args[1].readCString(); if (str2.indexOf("REJECT") !== -1) { this.hook = true; } }, onLeave: function (retval) { if (this.hook) { retval.replace(0); } } })
}
replace_str();
|
1.4 maps文件
当APP在手机上运行时,在 /proc/进程ID/...
目录下会创建很多跟app运行相关的文件。
先启动起来APP,然后查看他的进程ID:
1 2 3 4 5 6 7 8 9 10 11
| import frida import requests
rdev = frida.get_remote_device()
front_app = rdev.get_frontmost_application() print(front_app)
|
然后就可以通过adb进入目录查看生成相应的文件:
1 2
| ps -A|grep com.che168.autotradercloud cd /proc/24606/
|
这个maps文件中存储的是APP运行时加载的依赖:
1 2
| cat maps cat maps |grep native-lib
|
当启动frida后,在maps文件中就会存在 frida-agent-64.so
、frida-agent-32.so
文件。
所以,在一些APP中就会去读取maps文件的内容,看看是否有frida来监测是否有被frida调试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| char line[512]; FILE* fp; fp = fopen("/proc/self/maps", "r"); if (fp) { while (fgets(line, 512, fp)) { if (strstr(line, "frida")) { } } fclose(fp); } else { } }
|
如何绕过呢?
Hook C语言中的 strstr strcmp 来进行判断 frida-agent-64.so
或 re.frida.server
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
| function replace_str_maps() { var pt_strstr = Module.findExportByName("libc.so", 'strstr'); var pt_strcmp = Module.findExportByName("libc.so", 'strcmp');
Interceptor.attach(pt_strstr, { onEnter: function (args) { var str1 = args[0].readCString(); var str2 = args[1].readCString(); if (str2.indexOf("REJECT") !== -1 || str2.indexOf("frida") !== -1) { this.hook = true; } }, onLeave: function (retval) { if (this.hook) { retval.replace(0); } } });
Interceptor.attach(pt_strcmp, { onEnter: function (args) { var str1 = args[0].readCString(); var str2 = args[1].readCString(); if (str2.indexOf("REJECT") !== -1 || str2.indexOf("frida") !== -1) { this.hook = true; } }, onLeave: function (retval) { if (this.hook) { retval.replace(0); } } })
}
replace_str();
|
1.5 task目录
可以在运行frida和未运行frida时,进行对比,看看frida的启动创建了哪些线程。
- 未运行frida
- 运行frida
对上述的线程ID进行比较,发现由于frida的运行新增了的线程ID:
1
| {'26163', '26164', '26166', '26167', '26165'}
|
在某些app中就会去读取 /proc/stask/线程ID/status
文件,如果是运行frida产生的,则进行反调试。
例如:gmain/gdbus/gum-js-loop/pool-frida
等
如何绕过?
Hook C语言中的 strstr strcmp 来进行判断 gmain
、gdbus
等字符串。
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 51 52 53
| function replace_str() { var pt_strstr = Module.findExportByName("libc.so", 'strstr'); var pt_strcmp = Module.findExportByName("libc.so", 'strcmp');
Interceptor.attach(pt_strstr, { onEnter: function (args) { var str1 = args[0].readCString();
var str2 = args[1].readCString(); if (str2.indexOf("tmp") !== -1 || str2.indexOf("frida") !== -1 || str2.indexOf("gum-js-loop") !== -1 || str2.indexOf("gmain") !== -1 || str2.indexOf("gdbus") !== -1 || str2.indexOf("pool-frida") !== -1|| str2.indexOf("linjector") !== -1) { this.hook = true; } }, onLeave: function (retval) { if (this.hook) { retval.replace(0); } } });
Interceptor.attach(pt_strcmp, { onEnter: function (args) { var str1 = args[0].readCString(); var str2 = args[1].readCString(); if (str2.indexOf("tmp") !== -1 || str2.indexOf("frida") !== -1 || str2.indexOf("gum-js-loop") !== -1 || str2.indexOf("gmain") !== -1 || str2.indexOf("gdbus") !== -1 || str2.indexOf("pool-frida") !== -1|| str2.indexOf("linjector") !== -1) { this.hook = true; } }, onLeave: function (retval) { if (this.hook) { retval.replace(0); } } })
}
replace_str();
|
1.6 fd目录
记录app都打开的了哪些文件。
1.7 frida-server启动
当frida-server启动时,在 /data/local/tmp/
目录下会生成一个文件夹并且会生成一下带有frida的特征。
如何绕过?
使用将 hluda-server
,在运行frida之后就不会出现frida等字眼了。
当使用了 hluda-server
后,由于 /data/local/tmp/
目录下文件夹和文件的名称改变了,所以在使用了hluda-server
后相当于
解决了:
- fd目录问题,
/data/local/tmp/re.frida.server/linjector
- maps文件问题,
/data/local/tmp/re.frida.server/frida-agent-64.so
未解决:
- task目录问题,Hook:
gmain
、gdbus
等
- D-Bus协议问题,Hook:
REJECT
- 端口问题,切换端口
- 进程名问题,更换文件名
2.实战案例
2.1 frida版本
在逆向时,请务必使用 hluda-server-14.2.18
+ python3.8
来对常见frida的特征进行隐藏。
注意:其他高版本不好使。
1 2
| pip install frida==14.2.18 pip install frida-tools==9.2.5
|
安装过程中如果需要用到下载egg相关文件,请点击:
https://pypi.doubanio.com/simple/frida/
2.2 第一类:识货、得物、唯品会等
代表APP有:识货、得物、唯品会、豆瓣、豆瓣、雷速体育
1.加载so
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
| var dlopen = Module.findExportByName(null, "dlopen"); var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
Interceptor.attach(dlopen, { onEnter: function (args) { var path_ptr = args[0]; var path = ptr(path_ptr).readCString(); console.log("[dlopen:]", path); }, onLeave: function (retval) {
} });
Interceptor.attach(android_dlopen_ext, { onEnter: function (args) { var path_ptr = args[0]; var path = ptr(path_ptr).readCString(); console.log("[dlopen_ext:]", path); }, onLeave: function (retval) {
} });
|
2.删除so【搞定】
先尝试删除可能的so文件,看看app是否可正常运行。
有些app的监测是在一个单独的so文件中,但删除后业务功能不受任何影响。
识货、得物、唯品会,删除后可以绕过。
豆瓣、雷速体育,删除后无法正常运行(闪退或白屏)
3.尝试Hook关键词【可选】
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 51 52 53 54 55 56 57 58
| function replace_str() { var pt_strstr = Module.findExportByName("libc.so", 'strstr'); var pt_strcmp = Module.findExportByName("libc.so", 'strcmp');
Interceptor.attach(pt_strstr, { onEnter: function (args) { var str1 = args[0].readCString(); var str2 = args[1].readCString(); if ( str2.indexOf("REJECT") !== -1 || str2.indexOf("tmp") !== -1 || str2.indexOf("frida") !== -1 || str2.indexOf("gum-js-loop") !== -1 || str2.indexOf("gmain") !== -1 || str2.indexOf("linjector") !== -1 ) { console.log("strstr-->", str1, str2); this.hook = true; } }, onLeave: function (retval) { if (this.hook) { retval.replace(0); } } });
Interceptor.attach(pt_strcmp, { onEnter: function (args) { var str1 = args[0].readCString(); var str2 = args[1].readCString(); if ( str2.indexOf("REJECT") !== -1 || str2.indexOf("tmp") !== -1 || str2.indexOf("frida") !== -1 || str2.indexOf("gum-js-loop") !== -1 || str2.indexOf("gmain") !== -1 || str2.indexOf("linjector") !== -1 ) { this.hook = true; } }, onLeave: function (retval) { if (this.hook) { retval.replace(0); } } })
}
replace_str();
|
2.3 第二类:e充电
代表APP有:e充电(含ROOT监测)
1.加载so
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
| var dlopen = Module.findExportByName(null, "dlopen"); var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
Interceptor.attach(dlopen, { onEnter: function (args) { var path_ptr = args[0]; var path = ptr(path_ptr).readCString(); console.log("[dlopen:]", path); }, onLeave: function (retval) {
} });
Interceptor.attach(android_dlopen_ext, { onEnter: function (args) { var path_ptr = args[0]; var path = ptr(path_ptr).readCString(); console.log("[dlopen_ext:]", path); }, onLeave: function (retval) {
} });
|
2.删除so【报错】
删除so文件后,无法运行。
3.尝试Hook关键词【搞定】
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 51 52 53
| function replace_str() { var pt_strstr = Module.findExportByName("libc.so", 'strstr'); var pt_strcmp = Module.findExportByName("libc.so", 'strcmp');
Interceptor.attach(pt_strstr, { onEnter: function (args) { var str1 = args[0].readCString(); var str2 = args[1].readCString(); if ( str2.indexOf("REJECT") !== -1 || str2.indexOf("tmp") !== -1 || str2.indexOf("frida") !== -1 || str2.indexOf("gum-js-loop") !== -1 || str2.indexOf("gmain") !== -1 || str2.indexOf("linjector") !== -1 ) { console.log("strstr-->", str1, str2); this.hook = true; } }, onLeave: function (retval) { if (this.hook) { retval.replace(0); } } });
Interceptor.attach(pt_strcmp, { onEnter: function (args) { var str1 = args[0].readCString(); var str2 = args[1].readCString(); if ( str2.indexOf("REJECT") !== -1 || str2.indexOf("tmp") !== -1 || str2.indexOf("frida") !== -1 || str2.indexOf("gum-js-loop") !== -1 || str2.indexOf("gmain") !== -1 || str2.indexOf("linjector") !== -1 ) { this.hook = true; } }, onLeave: function (retval) { if (this.hook) { retval.replace(0); } } })
}
replace_str();
|
2.4 第三类:安吉星、ibox、贵旅优品
代表APP有:安吉星(含ROOT检测)、ibox(含ROOT检测)、贵旅优品(含ROOT检测)
1.加载so
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
| var dlopen = Module.findExportByName(null, "dlopen"); var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
Interceptor.attach(dlopen, { onEnter: function (args) { var path_ptr = args[0]; var path = ptr(path_ptr).readCString(); console.log("[dlopen:]", path); }, onLeave: function (retval) {
} });
Interceptor.attach(android_dlopen_ext, { onEnter: function (args) { var path_ptr = args[0]; var path = ptr(path_ptr).readCString(); console.log("[dlopen_ext:]", path); }, onLeave: function (retval) {
} });
|
2.pthread_create【搞定】
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
| function hook_pthread_create() { var pt_create_func = Module.findExportByName("libc.so", 'pthread_create');
Interceptor.attach(pt_create_func, { onEnter: function (args) { var so_name = Process.findModuleByAddress(args[2]).name; if (so_name.indexOf("libDexHelper.so") != -1) { try { Interceptor.replace(args[2], new NativeCallback(function () { console.log('replace success'); return null; }, 'void', ["void"])); } catch (e) {
}
} }, onLeave: function (retval) { } }) }
hook_pthread_create();
|
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
| function hook_pthread_create() { var pt_create_func = Module.findExportByName("libc.so", 'pthread_create');
Interceptor.attach(pt_create_func, { onEnter: function (args) { var so_name = Process.findModuleByAddress(args[2]).name; if (so_name.indexOf("libDexHelper.so") != -1) { try { Interceptor.replace(args[2], new NativeCallback(function () { console.log('replace success'); return null; }, 'void', ["void"])); } catch (e) {
}
} }, onLeave: function (retval) { } }) }
hook_pthread_create();
|
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
| function hook_pthread_create() { var pt_create_func = Module.findExportByName("libc.so", 'pthread_create');
Interceptor.attach(pt_create_func, { onEnter: function (args) { var so_name = Process.findModuleByAddress(args[2]).name;
if (so_name.indexOf("libexec.so") != -1) { try { Interceptor.replace(args[2], new NativeCallback(function () { console.log('replace success'); return null; }, 'void', ["void"])); } catch (e) {
}
} }, onLeave: function (retval) { } }) }
hook_pthread_create();
|