Frida反调试

一,常见检测

1.ThracePid检测

原理:当被调试器附加之后 不为0:安卓逆向反调试的手段有哪些-街头小贩

检测方法可以一直遍历 /proc/self/status Thracepid的值

ptrace占坑:
https://bbs.pediy.com/thread-268155.htm#msg_header_h2_16

(具体方法见ida动态调试部分有详细记录,这里我们就跳了)

2.进程名检测

遍历进程名是否包含frida-server

[翻译]多种特征检测 Frida-外文翻译-看雪安全社区|专业技术交流与安全研究论坛

3.端口检测

27042是frida的默认端口

./fs1 -l 0.0.0.0:6666

adb forward tcp:6666 tcp:6666

frida -H 127.0.0.1:6666 包名 -l hook.js

4.D-Bus协议通信

文章:https://bbs.pediy.com/thread-217482.htm
因为fridaserver 使用D-Bus 协议通信,我们为每个开放的端口发送D-Bus 的认证消息,哪个端口回复”REJEcT”.哪个就是fridaserver。

5.maps检测

/proc/self/maps 是一个特殊的文件,它包含了当前进程的内存映射信息。当你打开这个文件时,它会显示一个列表,其中包含了进程中每个内存区域的详细信息。

adb shell dumpsys window w lgrep V lgrep name=

先搜索一下进程

ps -e | grep 包名

cat proc/进程号/maps | grep frida

6.线程检测gmain,gdbus,gum-js-loop

sailfish:/proc/进程号/task

cat 31651/status|grep Name
Name:
gdbus
/proc/self/task/self/status

7.内存特征检测frida相关的字符串

8.自实现的比较函数

https://blog.csdn.net/u010559109/article/details/120846740Android逆向-过frida检测+so层算法逆向https://blog.csdn.net/weixin_43889136/article/details/127713563

二,过检测的几种方式

下面以Demo示例

adb shell dumpsys window | findstr mCurrentFocus

运行查看包名

我们将apk扔进jadx发现检测均为native函数,我们hook so层

借助脚本定位检测frida的so

1
2
3
4
5
6
7
8
9
10
11
12
13
function hook_dlopen() {
Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
{
onEnter: function (args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
console.log("load " + path);
}
}
}
);
}

偏移

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
function find_RegisterNatives(params) {
var symbols = Module.enumerateSymbolsSync("libart.so");
var addrRegisterNatives = null;
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];

//_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("RegisterNatives") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0) {
addrRegisterNatives = symbol.address;
console.log("RegisterNatives is at ", symbol.address, symbol.name);
hook_RegisterNatives(addrRegisterNatives)
}
}

}

function hook_RegisterNatives(addrRegisterNatives) {

if (addrRegisterNatives != null) {
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
console.log("[RegisterNatives] method_count:", args[3]);
var env = args[0];
var java_class = args[1];
var class_name = Java.vm.tryGetEnv().getClassName(java_class);
//console.log(class_name);

var methods_ptr = ptr(args[2]);

var method_count = parseInt(args[3]);
for (var i = 0; i < method_count; i++) {
var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));

var name = Memory.readCString(name_ptr);
var sig = Memory.readCString(sig_ptr);
var find_module = Process.findModuleByAddress(fnPtr_ptr);
console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, " fnOffset:", ptr(fnPtr_ptr).sub(find_module.base), " callee:", DebugSymbol.fromAddress(this.returnAddress));

}
}
});
}
}

setImmediate(find_RegisterNatives);

我们通过ida查询偏移

我们就找到了第一个检测点,直接修改过掉就可以

ThracePid反调试

android拦截native方法,Android native反调试方式及使用IDA绕过反调试-CSDN博客

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function bypass_fgets() {
var fgetsPtr = Module.findExportByName("libc.so", "fgets");
var fgets = new NativeFunction(fgetsPtr, 'pointer', ['pointer', 'int', 'pointer']);
Interceptor.replace(fgetsPtr, new NativeCallback(function (buffer, size, fp) {
var retval = fgets(buffer, size, fp);
var bufstr = buffer.readCString();
if (bufstr.indexOf("TracerPid:") > -1) {
Memory.writeUtf8String(buffer, "TracerPid:\t0");
}
//替换的值
if (bufstr.indexOf("Name:\tgmain") > -1 || bufstr.indexOf("Name:\tgdbus") > -1 || bufstr.indexOf("Name:\tpool-frida") > -1 || bufstr.indexOf("Name:\tgum-js-loop") > -1 || bufstr.indexOf("SigBlk:\tffffffe0fffbfaff") > -1) {
Memory.writeUtf8String(buffer, "TName:\t1");
}
return retval;
}, 'pointer', ['pointer', 'int', 'pointer']))
}

hook线程创建
定位检测点hook系统函数进行过掉需要分析常用于检测的系统函数

线程检测反调试

我们ida跳转到0x10b8

我们可以发现线程检测就是

我们通过返回假,来过掉检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function bypass_fgets() {
var fgetsPtr = Module.findExportByName("libc.so", "fgets");
var fgets = new NativeFunction(fgetsPtr, 'pointer', ['pointer', 'int', 'pointer']);
console.log(fgetsPtr,fgets);
Interceptor.replace(fgetsPtr, new NativeCallback(function (buffer, size, fp) {
var retval = fgets(buffer, size, fp);
var bufstr = buffer.readCString();
if (bufstr.indexOf("TracerPid:") > -1) {
Memory.writeUtf8String(buffer, "TracerPid:\t0");
}

if (bufstr.indexOf("Name:\tgmain") > -1 || bufstr.indexOf("Name:\tgdbus") > -1 || bufstr.indexOf("Name:\tpool-frida") > -1 || bufstr.indexOf("Name:\tgum-js-loop") > -1 || bufstr.indexOf("SigBlk:\tffffffe0fffbfaff") > -1) {
Memory.writeUtf8String(buffer, "TName:\t1");
}
return retval;
}, 'pointer', ['pointer', 'int', 'pointer']))
}

function main(){
bypass_fgets()
}

setImmediate(main)

成功过掉线程检测

除了hook fgets我们该可以hook strstr再或者hook fopen

过maps检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function hook_fopen() {
var open_addr = Module.findExportByName("libc.so", "fopen")
var io_map = Memory.allocUtf8String("/proc/13585/maps");
Interceptor.attach(open_addr, {
onEnter: function (args) {
if (args[0].readCString().indexOf("/maps") != -1) {
args[0] = io_map
}
this.pathname = args[0]
this.mode = args[1]
},
onLeave: function (retval) {
console.log("fopen pathname=" + this.pathname.readCString() + "---mode=" + this.mode)
}
})
}

过FD检测

readlink_百度百科

readlink()会将参数path的符号链接内容存储到参数buf所指的内存空间,返回的内容不是以\000作字符串结尾,但会将字符串的字符数返回,这使得添加\000变得简单。若参数bufsiz小于符号连接的内容长度,过长的内容会被截断,如果 readlink 第一个参数指向一个文件而不是符号链接时,readlink 设 置errno 为 EINVAL 并返回 -1。 readlink()函数组合了open()、read()和close()的所有操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function hook_readlink() {
var aaa, bbb, ccc;
Interceptor.attach(Module.findExportByName(null, "readlink"), {
onEnter: function (args) {
aaa = args[0];
bbb = args[1];
ccc = args[2];
},
onLeave: function (retval) {
// console.log('\nreadlink(' + 's1="' + aaa.readCString() + '"' + ', s2="' + bbb.readCString() + '"' + ', s3="' + ccc + '"' + ')');
if (bbb.readCString().indexOf("frida") !== -1 ||
bbb.readCString().indexOf("gum-js-loop") !== -1 ||
bbb.readCString().indexOf("gmain") !== -1 ||
bbb.readCString().indexOf("tmp") !== -1 ||
bbb.readCString().indexOf("linjector") !== -1) {
console.log('\nreadlink(' + 's1="' + aaa.readCString() + '"' + ', s2="' + bbb.readCString() + '"' + ', s3="' + ccc + '"' + ')');
bbb.writeUtf8String("/system/framework/boot.art")
console.log("replce with: "+bbb.readCString())
retval.replace(0x1A)
}
}
});
}

魔改frida

https://github.com/taisuii/rusda

[undetected-frida] https://github.com/zer0def/undetected-frida
[strongR-frida-android] https://github.com/hzzheyang/strongR-frida-android
[Florida] https://github.com/Ylarod/Florida
[rusda] https://github.com/taisuii/rusda

实战过检测

1.hook线程创建干掉检测线程案例

i加密的 贵旅

我们去hook创建,当线程加载的时候会挂掉,打印出相应的so以及偏移

我们吧其中不要的偏移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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
function hook_pthread_create() {
var pthread_create_addr = Module.findExportByName(null, "pthread_create");
const __android_log_print_ptr = Module.findExportByName(null, '__android_log_print')
const cm = new CModule(`
#include <gum/guminterceptor.h>
extern void onMessageStr (const gchar * message);
extern void onMessagePtr (void * message);
extern void onMessageInt (int a);
extern int __android_log_print(int prio, const char* tag, const char* fmt, ...);
void hello(){
}
void onEnter (GumInvocationContext * ic)
{
// void* arg2,arg3;
if((((int)gum_invocation_context_get_nth_argument(ic, 2))&0xfff)==0x599){
onMessageStr("replace success");
gum_invocation_context_replace_nth_argument(ic,2,(gpointer)hello);
}
// arg2 = gum_invocation_context_get_nth_argument (ic, 2);
onMessagePtr(gum_invocation_context_get_nth_argument (ic, 2));
// arg0 = (int)gum_invocation_context_get_nth_argument (ic, 2);
// gum_invocation_context_replace_nth_argument(ic,2,(gpointer)100);
// arg1 = (int)gum_invocation_context_get_nth_argument (ic, 3);
// log ("function add arg0=%d arg1=%d ", arg0,arg1);
// __android_log_print(3,"isDebug","arg0=%d,arg1=%d",arg0,arg1);
}
void
onLeave (GumInvocationContext * ic)
{
int result;
result = (int) gum_invocation_context_get_return_value (ic);
onMessageInt(result);
// gum_invocation_context_replace_return_value (ic,(gpointer)100);
}`, {
__android_log_print: __android_log_print_ptr,
onMessageStr: new NativeCallback(messagePtr => {
const message = messagePtr.readUtf8String();
console.log('onMessageStr:', message);
}, 'void', ['pointer']),
onMessagePtr: new NativeCallback(messagePtr => {
console.log('onMessagePtr:', messagePtr ,hexdump(messagePtr));
}, 'void', ['pointer']),
onMessageInt: new NativeCallback(messageInt => {
console.log('onMessageInt:', messageInt);
}, 'void', ['int']),
});
Interceptor.attach(pthread_create_addr, cm);
}

function replace_thread() {
var pthread_create_addr = Module.findExportByName(null, "pthread_create");
var pthread_create = new NativeFunction(pthread_create_addr, "int", ["pointer", "pointer", "pointer", "pointer"]);
Interceptor.replace(pthread_create_addr, new NativeCallback((parg0, parg1, parg2, parg3) => {
var so_name = Process.findModuleByAddress(parg2).name;
var so_base = Module.getBaseAddress(so_name);
var offset = (parg2 - so_base);
var PC = 0;
console.log("normal find thread func offset", so_name, parg2,offset, offset.toString(16));
// i加密
if(
(so_name.indexOf("libexec.so")>-1 && offset===197069)||
(so_name.indexOf("libexec.so")>-1 && offset===196137)
){

}else if((so_name.indexOf("libDexHelper.so")>-1 && offset===684452)||
(so_name.indexOf("libDexHelper.so")>-1 && offset===724380)){

}
else{
PC = pthread_create(parg0, parg1, parg2, parg3);
}
return PC;
}, "int", ["pointer", "pointer", "pointer", "pointer"]));
}

setImmediate(replace_thread)

排除后就找到相关文件。

梆梆 步道悦跑

同理hook排除得到libDexHelper.so线程干掉即可

2.定位检测点 hook系统函数进行过掉

需要分析 常用于检测的系统函数:strstr,strcmp.open,read.fread,

豆瓣

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
function hook_strstr() {
// char *strstr(const char *haystack, const char *needle) 在字符串 haystack 中查找第一次出现字符串 needle 的位置,不包含终止符
var strstr = Module.findExportByName(null, "strstr");
if (null !== strstr) {
Interceptor.attach(strstr, {
onEnter: function (args) {
this.frida = Boolean(0);

this.haystack = args[0];
this.needle = args[1];

if (this.haystack.readCString() !== null && this.needle.readCString() !== null) {
if (this.haystack.readCString().indexOf("frida") !== -1 ||
this.needle.readCString().indexOf("frida") !== -1 ||
this.haystack.readCString().indexOf("gum-js-loop") !== -1 ||
this.needle.readCString().indexOf("gum-js-loop") !== -1 ||
this.haystack.readCString().indexOf("gmain") !== -1 ||
this.needle.readCString().indexOf("gmain") !== -1 ||
this.haystack.readCString().indexOf("linjector") !== -1 ||
this.needle.readCString().indexOf("linjector") !== -1) {
this.frida = Boolean(1);
}
console.log("strstr haystack: " + this.haystack.readCString());
console.log("strstr needle: " + this.needle.readCString());
}
},
onLeave: function (retval) {
if (this.frida) {
retval.replace(ptr("0x0"));
}

}
})
console.log("anti anti-frida");
}

console.log("hook strstr over")
}

function main(){
hook_strstr();
}

setImmediate(main)

3.魔改frida将frida的特征都给改完仿照葫芦娃加上自己的

我们直接使用魔改好的frida就可以绕过大部分检测,魔改新老版本差别较大我们后续有空单独探讨。


Frida反调试
https://cc-nx.github.io/2026/01/16/frida反调试/
作者
CC
发布于
2026年1月16日
许可协议