news 2026/4/18 14:01:56

学某通风控参数分析Frida绕过(上)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
学某通风控参数分析Frida绕过(上)

过Frida检测

先hook一下dlopen,也就是android_dlopen_ext
为什么要Hook dlopen呢?
因为App的Frida检测代码一般都在so层实现,这些检测代码会在对应的so加载时初始化

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

function hook_dlopen() {

var android_dlopen_ext = Module.findExportByName(null,"android_dlopen_ext");

console.log("addr_android_dlopen_ext", android_dlopen_ext);

Interceptor.attach(android_dlopen_ext, {

onEnter: function (args) {

var pathptr = args[0];

if(pathptr != null && pathptr != undefined) {

var path = ptr(pathptr).readCString();

console.log("android_dlopen_ext:", path);

}

},

onLeave: function (retvel) {

}

})

}

function main() {

hook_dlopen()

}

setImmediate(main)

Frida进程会被杀死,同时手机也会卡死,而且也加载了特征so
这是为什么呢?

1

2

3

4

5

每隔几毫秒检查一次

发现了Frida的痕迹

执行反制措施:卡死界面 + 杀进程

我们的反制措施为Hook Clone函数

Clone函数为Linux创建线程的底层调用,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

function hook_clone() {

var clone = Module.findExportByName('libc.so','clone');

Interceptor.attach(clone, {

onEnter: function (args) {

console.log("═══ Clone Called ═══");

console.log("args[0] (wrapper):", args[0]);// __pthread_start

console.log("args[1] (stack) :", args[1]);

console.log("args[2] (flags) :", args[2]);

console.log("args[3] (tls) :", args[3]);//// 线程局部存储(TLS)

if(args[3] != 0) {

try{

// 读取真正的线程函数

var real_func = args[3].add(96).readPointer();

var module = Process.findModuleByAddress(real_func);

if(module) {

var offset = real_func.sub(module.base);

console.log(" 真正的线程函数:");

console.log(" SO名称:", module.name);

console.log(" 函数地址:", real_func);

console.log(" 偏移:", ptr(offset));

if(module.name.includes("DexHelper")) {

console.log(" 检测到目标so!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");

}

}

}catch(e) {

console.log("解析失败:", e);

}

}

}

});

}

setImmediate(hook_clone);

为什么要在var real_func = args[3].add(96).readPointer(); 读取?
我们需要了解一下 pthread_internal_t 结构体也就是pthread_t
这是 Android Bionic 库中用来管理线程的内部结构:
那么什么时候会创建这个结构体呢?肯定是线程被创建的时候,也就是pthread_create函数

Android创建线程分析

安卓平台上总共有三种线程:
1. Java 线程:Android 虚拟机线程,具有运行 Java 代码的 Runtime
2. Native 线程(只能执行 C/C++):纯粹的 Linux 线程
3. Native 线程(还能执行 Java):既能执行 C/C++ 代码,也能执行 Java 代码

Java线程创建流程

java层:Thread.start()

1

2

3

4

5

6

7

// /libcore/libart/src/main/java/java/lang/Thread.java

publicsynchronizedvoidstart() {

checkNotStarted();// 保证线程只启动一次

hasBeenStarted =true;

// 调用 native 方法创建线程

nativeCreate(this, stackSize, daemon);

}

nativeCreate为JNI方法,对应C++层的Thread_nativeCreate

JNI方法映射

1

2

3

4

5

6

// /art/runtime/native/java_lang_Thread.cc

// 宏定义

#define NATIVE_METHOD(className, functionName, signature) \

{ #functionName, signature,reinterpret_cast<void*>(className ## _ ## functionName) }

// 方法注册

NATIVE_METHOD(Thread, nativeCreate,"(Ljava/lang/Thread;JZ)V"),

展开后,nativeCreate 映射到 Thread_nativeCreate 函数。

Thread_nativeCreate

1

2

3

4

5

6

7

// /art/runtime/native/java_lang_Thread.cc

staticvoidThread_nativeCreate(JNIEnv* env, jclass, jobject java_thread,

jlong stack_size, jboolean daemon) {

// 创建 Native 线程

Thread::CreateNativeThread(env, java_thread, stack_size, daemon == JNI_TRUE);

}

CreateNativeThread

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

// /art/runtime/thread.cc

voidThread::CreateNativeThread(JNIEnv* env, jobject java_peer,

size_tstack_size,boolis_daemon) {

Thread* self =static_cast<JNIEnvExt*>(env)->self;

Runtime* runtime = Runtime::Current();

// 1. 创建 ART 的 Thread 对象

Thread* child_thread =newThread(is_daemon);

// 2. 关联 Java 层的 Thread 对象(jpeer)

child_thread->tlsPtr_.jpeer = env->NewGlobalRef(java_peer);

// 3. 修正栈大小

stack_size = FixStackSize(stack_size);

// 4. 在 Java Thread 对象中设置 native peer 指针

env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer,

reinterpret_cast<jlong>(child_thread));

// 5. 创建 JNI 环境

std::unique_ptr<JNIEnvExt> child_jni_env_ext(

JNIEnvExt::Create(child_thread, Runtime::Current()->GetJavaVM()));

// 6. 设置线程属性并创建 pthread

pthread_t new_pthread;

pthread_attr_t attr;

pthread_attr_init(&attr);

child_thread->tlsPtr_.tmp_jni_env = child_jni_env_ext.get();

// 7. 调用 pthread_create 创建线程

intpthread_create_result = pthread_create(

&new_pthread,//返回线程句柄

&attr,

Thread::CreateCallback,// 线程入口函数

child_thread// 传递给线程的参数

);

if(pthread_create_result == 0) {

child_jni_env_ext.release();

return;

}

// 创建失败的处理...

}

- 创建 ART 虚拟机的 Thread 对象
- 关联 Java 和 Native 的 Thread 对象(双向引用)
- 创建 JNI 环境,使线程能够调用 Java 代码
- 调用 pthread_create 创建真正的操作系统线程

Thread::CreateCallback

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

// /art/runtime/thread.cc

void* Thread::CreateCallback(void* arg) {

Thread* self =reinterpret_cast<Thread*>(arg);

Runtime* runtime = Runtime::Current();

// 1. 附加到 ART 虚拟机

self->Init(runtime->GetThreadList(), runtime->GetJavaVM());

// 2. 初始化线程相关资源

self->InitCardTable();

self->InitTid();

self->InitAfterFork();

// 3. 调用 Java 层的 run() 方法

{

ScopedObjectAccess soa(self);

self->NotifyThreadBirth();

// 获取 Thread.run() 方法

ArtMethod* run_method =

WellKnownClasses::java_lang_Thread_run->GetArtMethod();

// 反射调用 run 方法

JValue result;

run_method->Invoke(self,

reinterpret_cast<uint32_t*>(&self->tlsPtr_.jpeer),

sizeof(void*),

&result,

"V");

}

// 4. 线程执行完毕,清理资源

self->NotifyThreadDeath();

returnnullptr;

}

- 线程启动后先初始化 ART虚拟机环境,通过反射调用 Java 层的 run() 方法执行完毕后进行资源清理

pthread_create分析

pthread_create在CreateNativeThread时被调用

1

2

3

4

5

6

intpthread_create_result = pthread_create(

&new_pthread,//返回线程句柄

&attr,

Thread::CreateCallback,// 线程入口函数

child_thread// 传递给线程的参数

);

pthread_create` 会先得到一个`pthread_internal_t`结构体

pthread_create会先得到一个pthread_internal_t结构体

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

// 1. 应用层调用

pthread_tthread;

pthread_create(&thread, NULL, my_function, my_arg);

// 2. pthread_create 内部实现

intpthread_create(pthread_t* thread_out,

constpthread_attr_t* attr,

void* (*start_routine)(void*),

void* arg) {

// 分配并初始化 pthread_internal_t

pthread_internal_t*thread=

reinterpret_cast<pthread_internal_t*>(

calloc(sizeof(pthread_internal_t), 1));

// 设置关键字段

thread->start_routine = start_routine;

thread->start_routine_arg = arg;

// 分配线程栈

thread->stack_base = mmap(...);

thread->stack_size = stack_size;

// 调用 clone 系统调用

intflags = CLONE_VM | CLONE_FS | CLONE_FILES |

CLONE_SIGHAND | CLONE_THREAD |

CLONE_SYSVSEM | CLONE_SETTLS |

CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID;

// 关键:thread 作为 TLS 参数传递给 clone

inttid = clone(__pthread_start,// 包装函数

thread->stack_top(),// 栈顶

flags,// 克隆标志

thread,// TLS (args[3])

&(thread->tid));// parent_tidptr

// 将 pthread_internal_t 加入全局链表

__pthread_internal_add(thread);

// 返回线程句柄

*thread_out =reinterpret_cast<pthread_t>(thread);

return0;

}

// 3. __pthread_start 包装函数

staticint__pthread_start(void* arg) {

pthread_internal_t*thread=

reinterpret_cast<pthread_internal_t*>(arg);

// 设置线程 ID

thread->tid = gettid();

// 调用真正的线程函数

void* result =thread->start_routine(thread->start_routine_arg);

// 线程退出

pthread_exit(result);

return0;

}

这个结构体为核心数据结构,包含了线程的所有信息
pthread_create是pthread库中的函数,通过syscall再调用到clone来请求内核创建线程

Linux进程管理

Linux创建进程采用fork()和exec()

- fork: 采用复制当前进程的方式来创建子进程,此时子进程与父进程的区别仅在于pid, ppid以及资源统计量(比如挂起的信号)
- exec:读取可执行文件并载入地址空间执行;一般称之为exec函数族,有一系列exec开头的函数,比如execl, execve等

fork过程复制资源包括代码段,数据段,堆,栈。fork调用者所在进程便是父进程,新创建的进程便是子进程;在fork调用结束,从内核返回两次,一次继续执行父进程,一次进入执行子进程。

进程创建
- Linux进程创建: 通过fork()系统调用创建进程
- Linux用户级线程创建:通过pthread库中的pthread_create()创建线程
- Linux内核线程创建: 通过kthread_create()

Linux线程,也并非”轻量级进程”,在Linux看来线程是一种进程间共享资源的方式,线程可看做是跟其他进程共享资源的进程。

fork, vfork, clone根据不同参数调用do_fork

- pthread_create: flags参数为 CLONE_VM, CLONE_FS, CLONE_FILES, CLONE_SIGHAND
- fork: flags参数为 SIGCHLD
- vfork: flags参数为 CLONE_VFORK, CLONE_VM, SIGCHLD

Fork流程图

进程/线程创建的方法fork(),pthread_create(),最终在linux都是调用do_fork方法。 当然还有vfork其实也是一样的, 通过系统调用到sys_vfork,然后再调用do_fork方法,该方法 现在很少使用,所以下图省略该方法。

fork执行流程:

1. 用户空间调用fork()方法;
2. 经过syscall陷入内核空间, 内核根据系统调用号找到相应的sys_fork系统调用;
3. sys_fork()过程会在调用do_fork(), 该方法参数有一个flags很重要, 代表的是父子进程之间需要共享的资源; 对于进程创建flags=SIGCHLD, 即当子进程退出时向父进程发送SIGCHLD信号;
4. do_fork(),会进行一些check过程,之后便是进入核心方法copy_process.

flags参数

进程与线程最大的区别在于资源是否共享,线程间共享的资源主要包括内存地址空间,文件系统,已打开文件,信号等信息, 如下图蓝色部分的flags便是线程创建过程所必需的参数。

fork采用Copy on Write机制,父子进程共用同一块内存,只有当父进程或者子进程执行写操作时会拷贝一份新内存。 另外,创建进程也是有可能失败,比如进程个数达到系统上限(32768)或者系统可用内存不足。

在安卓源码对应内容如上图所示

而现在我们需要去分析pthread_internal_t* 结构体中,在哪里存储的线程函数

adb pull /system/lib64/libc.so ./libc64.so

搜索pthread_create
不要忘记了

1

2

3

4

5

6

intpthread_create_result = pthread_create(

&new_pthread,//返回线程句柄

&attr,

Thread::CreateCallback,// 线程入口函数

child_thread// 传递给线程的参数

);

我们向下追踪
发现a3的值赋值给了v54

所以偏移为0x60的地方为咱们线程函数的基址

1

2

3

4

5

6

7

8

9

10

11

12

13

14

structpthread_internal_t {

void* next;// 0x00 - 链表指针

void* prev;// 0x08 - 链表指针

pid_t tid;// 0x10 - 线程 ID

pid_t cached_pid;// 0x14 - 缓存的进程 ID

// ... 省略一些字段 ...

pthread_mutex_t startup_mutex;// 0x88 - 启动互斥锁

boolstartup_flag;// 0x8C - 启动标志

void* mmap_base;// 0x90 (144) - mmap 分配的基地址

size_tmmap_size;// 0x98 (152) - mmap 分配的大小

void* (*start_routine)(void*);// 0x60 (96) - 线程入口函数(更正!)

void* start_routine_arg;// 0x68 (104) - 传递给线程函数的参数

// ... 其他字段 ...

};// 总大小:704 字节 (0x2C0)

我们再进入clone函数

这个函数只是clone函数的包装器,真正的clone为

如果返回值没问题,就调用__start_thread

在这个函数,会初始化tid,以及调用线程函数,线程函数执行后,就退出线程
因此我们通过hook clone即可拦截线程!

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

console.log("启动反调试绕过...");

var anti_debug_offsets = [

0x4c574,

0x56c10,

0x54584,

0x5c3c4

];

function waitForModule() {

var module = Process.findModuleByName("libDexHelper.so");

if(module) {

console.log("找到 libDexHelper.so 基址:", module.base);

hookAntidebugFunctions(module.base);

}else{

console.log("等待 libDexHelper.so 加载...");

setTimeout(waitForModule, 100);

}

}

function hookAntidebugFunctions(base) {

console.log("开始Hook反调试函数");

var dummy_func =newNativeCallback(function(arg) {

return0;

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

anti_debug_offsets.forEach(function(offset, index) {

var func_addr = base.add(offset);

var hook_num = index + 1;

console.log("Hook 函数 #"+ hook_num +" 偏移:"+ ptr(offset) +" 地址:"+ func_addr);

try{

Interceptor.replace(func_addr, dummy_func);

console.log("replace 替换成功");

}catch(e1) {

console.log("replace 失败,尝试 attach");

try{

Interceptor.attach(func_addr, {

onEnter: function(args) {

console.log("函数 #"+ hook_num +" 被调用");

for(var i = 0; i < 8; i++) {

try{

args[i] = ptr(0);

}catch(e) {}

}

},

onLeave: function(retval) {

retval.replace(0);

console.log("返回值已改为0");

}

});

console.log("attach 拦截成功");

}catch(e2) {

console.log("attach 也失败:", e2.message);

}

}

});

console.log("所有函数Hook完成");

}

setTimeout(waitForModule, 500);

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 8:09:31

深入理解UDS 31服务:ECU编程前的必备知识

深入理解UDS 31服务&#xff1a;ECU编程前的“发令枪”为何如此关键&#xff1f;你有没有遇到过这样的情况——在给ECU刷写新固件时&#xff0c;一切准备就绪&#xff0c;却突然收到一条NRC 0x22&#xff08;条件不满足&#xff09;的错误响应&#xff1f;或者更糟&#xff0c;…

作者头像 李华
网站建设 2026/4/18 7:53:58

【2025最新】基于SpringBoot+Vue的学生信息管理系统管理系统源码+MyBatis+MySQL

摘要 随着教育信息化的快速发展&#xff0c;传统的学生信息管理方式逐渐暴露出效率低下、数据冗余、安全性不足等问题。高校和中小学校迫切需要一套高效、稳定且易于维护的学生信息管理系统&#xff0c;以实现学生数据的数字化、标准化管理。该系统需涵盖学生基本信息、课程成绩…

作者头像 李华
网站建设 2026/4/18 13:34:22

YOLOFuse地铁调度员状态分析:紧急情况下响应速度测评

YOLOFuse地铁调度员状态分析&#xff1a;紧急情况下响应速度测评 在城市轨道交通系统中&#xff0c;一次突发火灾或设备故障的应急响应效率&#xff0c;往往取决于最初几十秒内调度员能否准确识别异常并启动预案。然而&#xff0c;在烟雾弥漫、照明中断的极端环境下&#xff0c…

作者头像 李华
网站建设 2026/4/18 3:49:25

Proteus安装过程中的权限问题解决:教师部署建议

教学场景下Proteus安装权限问题的实战解析&#xff1a;从踩坑到高效部署在高校电子信息类课程中&#xff0c;Proteus几乎是每个嵌入式系统、单片机原理实验课绕不开的名字。它不仅能仿真模拟电路和数字逻辑&#xff0c;还支持多种MCU&#xff08;如8051、AVR、PIC、ARM Cortex-…

作者头像 李华
网站建设 2026/4/18 3:50:02

从“写论文”到“适配期刊”:当AI成为你投稿前的“隐形同行评审”

在科研工作者的日常中&#xff0c;投稿被拒往往不是因为研究质量不足&#xff0c;而是因为“错配”——语言风格与期刊调性不符、结构安排偏离惯例、讨论深度未达预期&#xff0c;甚至格式细节触碰红线。一篇在技术上扎实的论文&#xff0c;若未能在表达层面精准对接目标期刊的…

作者头像 李华
网站建设 2026/4/18 3:50:44

YOLOFuseICU重症监护:病人微小动作与呼吸监测

YOLOFuseICU重症监护&#xff1a;病人微小动作与呼吸监测 在重症监护室&#xff08;ICU&#xff09;中&#xff0c;哪怕是最轻微的生理变化——一次不规则的胸腹起伏、一个无意识的肢体抽动——都可能是病情恶化的前兆。然而&#xff0c;传统生命体征监测依赖接触式传感器&…

作者头像 李华