news 2026/6/15 20:28:01

学某通风控参数分析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/6/15 13:52:58

YOLOFuse游乐园设施安全监控:游客违规行为识别

YOLOFuse游乐园设施安全监控&#xff1a;游客违规行为识别 在大型游乐园的运营现场&#xff0c;一个看似平静的夜晚却暗藏风险——昏暗的灯光下&#xff0c;一名游客翻越护栏试图进入维修区域&#xff0c;而传统摄像头因光线不足未能及时捕捉这一危险动作。直到安保人员巡检时才…

作者头像 李华
网站建设 2026/6/15 13:53:28

【2025最新】基于SpringBoot+Vue的新冠物资管理系统管理系统源码+MyBatis+MySQL

摘要 新冠疫情的爆发对全球公共卫生系统提出了严峻挑战&#xff0c;物资管理成为疫情防控的关键环节。传统物资管理方式依赖人工操作&#xff0c;效率低下且易出错&#xff0c;难以应对突发公共卫生事件的大规模物资调配需求。为提升物资管理的智能化水平&#xff0c;开发一套高…

作者头像 李华
网站建设 2026/6/15 11:19:47

传输门与双向开关设计:逻辑门扩展应用实战

传输门与双向开关设计&#xff1a;从晶体管到系统级互连的实战解析在数字电路的世界里&#xff0c;我们习惯于将“逻辑门”视为布尔运算的基本积木——与、或、非&#xff0c;构成了组合逻辑的基石。但当你深入芯片内部&#xff0c;真正决定数据如何流动的&#xff0c;往往不是…

作者头像 李华
网站建设 2026/6/15 14:20:22

零基础入门:Multisim安装与实验平台搭建

从零开始搭建电路实验台&#xff1a;Multisim 安装与仿真入门实战你是否曾因为手头没有示波器、信号源&#xff0c;就只能对着课本上的RC电路干瞪眼&#xff1f;你是否在做模电作业时&#xff0c;想验证一个放大电路却苦于焊接失败、元件烧毁&#xff1f;别担心——现在&#x…

作者头像 李华
网站建设 2026/6/15 12:22:41

YOLOFuse输电线路覆冰监测:形变+温度联合判断

YOLOFuse输电线路覆冰监测&#xff1a;形变温度联合判断 在高海拔、寒冷地区的电网运维现场&#xff0c;一场突如其来的冻雨往往意味着巨大挑战。导线表面逐渐堆积的冰层不仅悄然增加机械负荷&#xff0c;更可能引发断线、倒塔甚至大面积停电。传统依赖气象站数据或人工巡检的方…

作者头像 李华
网站建设 2026/6/15 11:20:00

工业环境下USB转485驱动安装与调试指南

工业现场实战&#xff1a;USB转485驱动安装与通信调试全解析 在工控一线&#xff0c;你是否遇到过这样的场景&#xff1f;——新上位机接不上老设备&#xff0c;PLC数据读不出来&#xff0c;现场排查一圈才发现是 USB转485模块没被识别 。重启、换线、重装驱动……半小时过去…

作者头像 李华