news 2026/3/29 14:06:34

学某通风控参数分析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/3/26 23:33:44

YOLOFuse打鼾与呼吸暂停检测:健康风险早期提示

YOLOFuse打鼾与呼吸暂停检测&#xff1a;健康风险早期提示 在深夜的卧室里&#xff0c;一个人正安静地躺着——看起来一切正常。但如果你能“看见”他的呼吸节律&#xff0c;或许会发现异常&#xff1a;胸腹长达十秒没有起伏&#xff0c;血氧可能正在悄然下降。这样的场景每天在…

作者头像 李华
网站建设 2026/3/27 21:02:34

YOLOFuse注意力缺陷评估:课堂专注度动态监测

YOLOFuse注意力缺陷评估&#xff1a;课堂专注度动态监测 在一间普通教室里&#xff0c;光线随着时间推移不断变化——早晨阳光斜射、午后拉上窗帘、傍晚灯光昏暗。传统基于可见光摄像头的智能监考系统在这种环境下常常“失明”&#xff1a;学生轮廓模糊、头部姿态误判、注意力评…

作者头像 李华
网站建设 2026/3/26 23:00:04

一文说清usb_burning_tool刷机工具的镜像定制原理

深入理解 usb_burning_tool 刷机机制&#xff1a;从烧录到镜像定制的全链路解析你有没有遇到过这样的场景&#xff1f;产线上一批新板子上电后无法启动&#xff0c;排查半天才发现是 eMMC 里的 bootloader 烧错了版本&#xff1b;或者同一个硬件平台要出多个区域版本&#xff0…

作者头像 李华
网站建设 2026/3/29 2:49:19

Elasticsearch数据库访问故障排查:面向日志系统的实用技巧

Elasticsearch 访问故障排查实战&#xff1a;从连不通到稳定写入的全链路指南你有没有遇到过这样的场景&#xff1f;凌晨三点&#xff0c;监控告警突然炸了——Kibana 看不到新日志&#xff0c;ELK 链路中断。第一反应就是&#xff1a;“Elasticsearch 到底能不能访问&#xff…

作者头像 李华
网站建设 2026/3/27 7:18:19

YOLOFuse临床试验受试者依从性分析:用药行为识别

YOLOFuse临床试验受试者依从性分析&#xff1a;用药行为识别 在一项为期三个月的居家精神类药物依从性研究中&#xff0c;研究人员发现超过37%的受试者在夜间熄灯后存在“自我报告服药但无实际动作”的偏差。传统依赖问卷或定时提醒的方式难以捕捉真实用药行为&#xff0c;尤其…

作者头像 李华
网站建设 2026/3/27 10:45:05

SpringBoot+Vue 学生选课系统管理平台源码【适合毕设/课设/学习】Java+MySQL

摘要 随着信息技术的快速发展&#xff0c;教育管理信息化已成为高校提升教学效率和管理水平的重要手段。传统的学生选课系统多采用手工操作或单机版管理&#xff0c;存在效率低、数据易丢失、信息不透明等问题。尤其是在高校扩招的背景下&#xff0c;学生人数激增&#xff0c;选…

作者头像 李华