Last edited by: FANGG3
论坛关于反反调试的帖子很多,但是总觉得少了些什么。周末整理了一下自己之前的笔记,这里从我自己的角度,一步一步的抽丝剥茧,懂得原理才能走的更远,愿自己不要再做脚本boy。文中如有暇疵遗漏,欢迎各位斧正。
ptrace占坑
原理:a进程同一时间只能被b进程ptrace调试,如果c进程对a进程ptrace调试,则会失败。
对于frida,它的进程注入依赖于ptrace,详细可以看这篇。
运行App后,通过ps -A |grep byd
可以看到有两个进程:
1
2
|
u0_a222
29976
15576
6848868
360644
Sys_epoLl_wait
780e128208
S com. byd. aeri.caranywhere
u0_a222
30008
29976
6106376
256560
do_wait
780e129928
S com. byd. aeri. caranywhere
|
pid分别为29976、30008。其中29976为主进程,其父进程为15576(zygote)。30008为子进程,父进程为29976。
继续查看/proc/29976/status,如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
Name: eri.caranywhere
State: S (sleeping)
Tgid:
29976
Pid:
29976
#本进程pid
PPid:
15576
#父进程pid
TracerPid:
30008
#调试进程pid
..........
Name: eri.caranywhere
State: S (sleeping)
Tgid:
30008
Pid:
30008
PPid:
29976
TracerPid:
0
#0没有被调试
|
尝试附加进程:
1
2
3
4
5
6
7
8
9
|
❯ frida
-
U
29976
Failed to attach: unable to access process with pid
29976
due to system restrictions;
try
`sudo sysctl kernel.yama.ptrace_scope
=
0
`,
or
run Frida as root
# 子进程
# 附加后过一会,app会闪退
❯ frida
-
U
30008
[Redmi Note
7
::PID::
30008
]
-
> Process terminated
|
a. 得出结论,主进程被子进程Ptrace占坑调试,导致frida附加不上。
b. 附加子进程时,过段时间闪退,猜测存在frida检测。
如何实现ptrace占坑
首先,从ptrace函数出发,其主要用法如下:
1
2
3
4
5
6
7
|
#include <sys/ptrace.h>
long
ptrace(enum __ptrace_request request, pid_t pid, void
*
addr, void
*
data);
ptrace有四个参数:
1
). enum __ptrace_request request:指示了ptrace要执行的命令。
2
). pid_t pid: 指示ptrace要跟踪的进程。
3
). void
*
addr: 指示要监控的内存地址。
4
). void
*
data: 存放读取出的或者要写入的数据。
|
enum __ptrace_request
PTRACE_TRACEME :表示本进程将被其父进程跟踪,此时剩下的pid、addr、data参数都没有实际意义可以全部为0
这个选项只能用在被调试的进程中,也是被调试的进程唯一能用的request选项,其他的都只能用父进程调试器使用
PTRACE_ATTACH:attach到一个指定的进程,使其成为当前进程跟踪的子进程,而子进程的行为等同于它进行了一次PTRACE_TRACEME操作,可想而知,gdb的attach命令使用这个参数选项实现的
由此可拓展出反调试思路:
PTRACE_TRACEME 父进程占坑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
void ptraceCheck()
{
/
/
当前线程被非父线程调试,返回
-
1
;否则返回
0
/
/
成功时,tracerPid为父进程的Pid
int
ck
=
ptrace(PTRACE_TRACEME,
0
,
0
,
0
);
if
(ck
=
=
-
1
)
{
LOGA(
"进程正在被调试\n"
);
return
;
}
else
{
LOGB(
"ptrace的返回值为:%d\n"
,ck);
return
;
}
}
|
检测当前tracerPid
循环读取/proc/self/status中tracerPid的值
子线程附加父线程(也就是本App的反ptrace调试方法)
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
|
void anti_ptrace(void)
{
pid_t child;
/
/
创建子进程
child
=
fork();
if
(child)
{
/
/
返回在父进程中
wait(NULL);
}
else
{
/
/
获取父进程的pid
pid_t parent
=
getppid();
/
/
ptrace附加父进程
if
(ptrace(PTRACE_ATTACH, parent,
0
,
0
) <
0
)
{
while
(
1
);
sleep(
1
);
}
/
/
释放附加的进程
ptrace(PTRACE_DETACH, parent,
0
,
0
);
/
/
结束当前进程
exit(
0
);
}
}
|
反ptrace占坑
假设,app的占坑逻辑在某个So的_init中,app加载该So时,已完成了占坑查找。使用frida attach注入app时,必然会失败。在这个前提下,我们有3种方法反占坑:
在尽可能早的时机完成注入操作,例如frida spawn 注入的时机是root启动app线程后,app的So加载前。
可以发现,方法3并没有更改So的逻辑。如果调试进程Tracer修改了被调试进程Tracee的内存等,方法1和方法2可能会导致App运行异常。
1
2
|
> 总结:使用frida spawn 即可
>
|
了解pthread_create函数
pthread_create,作用是创建一个线程,可以指定新线程的运行函数地址,以及传递参数。
1
2
3
4
5
6
7
8
9
10
11
|
int
pthread_create(pthread_t
*
restrict tidp,const pthread_attr_t
*
restrict_attr,void
*
(
*
start_rtn)(void
*
),void
*
restrict arg);
(
1
)tidp:事先创建好的pthread_t类型的参数。成功时tidp指向的内存单元被设置为新创建线程的线程
ID
。
(
2
)restrict_attr:用于定制各种不同的线程属性,通常直接设为NULL。
*
*
(
3
)start_rtn:新创建线程从此函数开始运行。无参数是arg设为NULL即可。
*
*
(
4
)arg:start_rtn函数的参数。无参数时设为NULL即可。有参数时输入参数的地址。当多于一个参数时应当使用结构体传入。(以下举例)
5
、返回值:成功返回
0
,否则返回错误码。
|
从开发的角度看frida检测
在我的理解中,作为开发而言,大概率不会在主线程进行检测,否则有可能会使程序阻塞卡顿。所以,创建一个线程,进行检测会比较好。由于frida attach后,经过了几秒后App才被kill,可以推断出使用了定时器,循环检测frida。
接下来验证我的猜测:
直接启动目标app,通过ps -T -p $(主进程pid)
,查看进程中的线程,我们得到以下结果:
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
77
78
79
80
81
82
83
84
85
86
87
88
|
lavender:
/
proc
/
4027
/
task
# ps -Twwww -p 4027
USER PID TID PPID VSZ RSS WCHAN ADDR S CMD
u0_a222
4027
4027
15576
7587780
598880
SyS_epoll_wait
780e128208
S eri.caranywhere
u0_a222
4027
4031
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S Jit thread pool
u0_a222
4027
4036
15576
7587780
598880
do_sigtimedwait
780e128488
S Signal Catcher
u0_a222
4027
4044
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S HeapTaskDaemon
u0_a222
4027
4045
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S ReferenceQueueD
u0_a222
4027
4046
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S FinalizerDaemon
u0_a222
4027
4047
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S FinalizerWatchd
u0_a222
4027
4054
15576
7587780
598880
binder_ioctl
780e128348
S Binder:
4027_1
u0_a222
4027
4055
15576
7587780
598880
binder_ioctl
780e128348
S Binder:
4027_2
u0_a222
4027
4056
15576
7587780
598880
binder_ioctl
780e128348
S Binder:
4027_3
u0_a222
4027
4057
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S Profile Saver
u0_a222
4027
4059
15576
7587780
598880
hrtimer_nanosleep
780e128f68
S eri.caranywhere
u0_a222
4027
4063
15576
7587780
598880
hrtimer_nanosleep
780e128f68
S eri.caranywhere
u0_a222
4027
4064
15576
7587780
598880
poll_schedule_timeout
780e1283a8
S eri.caranywhere
u0_a222
4027
4065
15576
7587780
598880
hrtimer_nanosleep
780e128f68
S eri.caranywhere
u0_a222
4027
4068
15576
7587780
598880
pipe_wait
780b6ac008
S eri.caranywhere
u0_a222
4027
4070
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S mRecordExecutor
u0_a222
4027
4075
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S pool
-
1
-
thread
-
1
u0_a222
4027
4076
15576
7587780
598880
SyS_epoll_wait
780e128208
S default
u0_a222
4027
4078
15576
7587780
598880
binder_ioctl
780e128348
S Binder:
4027_4
u0_a222
4027
4079
15576
7587780
598880
SyS_epoll_wait
780e128208
S ConnectivityThr
u0_a222
4027
4081
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S looper_logic
u0_a222
4027
4083
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S pool
-
3
-
thread
-
1
u0_a222
4027
4084
15576
7587780
598880
unix_stream_read_generic
780e1291a8
S TcmReceiver
u0_a222
4027
4086
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S Gtc
-
ScheduleQue
u0_a222
4027
4093
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S .
1
, thread No.
1
u0_a222
4027
4097
15576
7587780
598880
SyS_epoll_wait
780e128208
S RenderThread
u0_a222
4027
4102
15576
7587780
598880
SyS_epoll_wait
780e128208
S Binder:intercep
u0_a222
4027
4103
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S ThreadTask
#1
u0_a222
4027
4104
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S pool
-
1
-
thread
-
2
u0_a222
4027
4107
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S BuglyThread
-
1
u0_a222
4027
4108
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S BuglyThread
-
2
u0_a222
4027
4109
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S Bugly
-
ThreadMon
u0_a222
4027
4110
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S BuglyThread
-
3
u0_a222
4027
4111
15576
7587780
598880
wait_woken
780b6ac008
S FileObserver
u0_a222
4027
4114
15576
7587780
598880
SyS_epoll_wait
780e128208
S queued
-
work
-
loo
u0_a222
4027
4115
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S RxSchedulerPurg
u0_a222
4027
4116
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S RxCachedWorkerP
u0_a222
4027
4123
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S AsyncTask
#1
u0_a222
4027
4125
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S pool
-
5
-
thread
-
1
u0_a222
4027
4126
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S Okio Watchdog
u0_a222
4027
4138
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S .
1
, thread No.
2
u0_a222
4027
4151
15576
7587780
598880
SyS_epoll_wait
780e128208
S Gtc HandlerThre
u0_a222
4027
4161
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S pool
-
8
-
thread
-
1
u0_a222
4027
4171
15576
7587780
598880
SyS_epoll_wait
780e128208
S magnifier pixel
u0_a222
4027
4179
15576
7587780
598880
binder_ioctl
780e128348
S Binder:
4027_5
u0_a222
4027
4193
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S pool
-
5
-
thread
-
2
u0_a222
4027
4211
15576
7587780
598880
SyS_epoll_wait
780e128208
S GBD
-
Thread
u0_a222
4027
4213
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S pool
-
10
-
thread
-
u0_a222
4027
4214
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S pool
-
10
-
thread
-
u0_a222
4027
4215
15576
7587780
598880
SyS_epoll_wait
780e128208
S GWS
-
Thread
u0_a222
4027
4216
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S gws
-
thread
u0_a222
4027
4218
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S pool
-
10
-
thread
-
u0_a222
4027
4219
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S pool
-
5
-
thread
-
3
u0_a222
4027
4226
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S ZX
-
Api
-
Thread
u0_a222
4027
4228
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S ZX
-
Api
-
Thread
u0_a222
4027
4231
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S pool
-
3
-
thread
-
2
u0_a222
4027
4248
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S pool
-
10
-
thread
-
u0_a222
4027
4569
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S pool
-
3
-
thread
-
3
u0_a222
4027
4572
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S pool
-
3
-
thread
-
4
u0_a222
4027
4785
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S pool
-
3
-
thread
-
5
u0_a222
4027
4788
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S pool
-
3
-
thread
-
6
u0_a222
4027
4999
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S pool
-
3
-
thread
-
7
u0_a222
4027
5006
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S pool
-
3
-
thread
-
8
u0_a222
4027
5466
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S pool
-
3
-
thread
-
9
u0_a222
4027
7602
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S OkHttp Connecti
u0_a222
4027
8067
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S glide
-
source
-
th
u0_a222
4027
8073
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S glide
-
disk
-
cach
u0_a222
4027
8075
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S glide
-
source
-
th
u0_a222
4027
8081
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S glide
-
source
-
th
u0_a222
4027
8082
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S glide
-
source
-
th
u0_a222
4027
8107
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S hwuiTask0
u0_a222
4027
8108
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S hwuiTask1
u0_a222
4027
8109
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S glide
-
active
-
re
u0_a222
4027
8120
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S RxCachedThreadS
u0_a222
4027
8121
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S RxCachedThreadS
u0_a222
4027
8132
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S ThreadTask
#2
u0_a222
4027
8189
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S z TaskRunner
u0_a222
4027
8191
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S ThreadTask
#3
u0_a222
4027
8192
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S ThreadTask
#4
u0_a222
4027
8236
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S Okio Watchdog
u0_a222
4027
8259
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S RxCachedThreadS
u0_a222
4027
8260
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S RxCachedThreadS
u0_a222
4027
8265
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S RxCachedThreadS
u0_a222
4027
8269
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S pool
-
13
-
thread
-
u0_a222
4027
8357
15576
7587780
598880
futex_wait_queue_me
780e0d7440
S process reaper
|
这样看起来似乎很复杂,我们知道linux中在没有指定线程名称时,线程的名称与进程的名称一致。
我们过滤一下包名,得到下面结果:
1
2
3
4
5
6
7
8
|
lavender:
/
proc
/
4027
/
task
# ps -Twwww -p 4027 |grep caranywhere
USER PID TID PPID VSZ RSS WCHAN ADDR S CMD
u0_a222
4027
4027
15576
7587780
598880
SyS_epoll_wait
780e128208
S eri.caranywhere
u0_a222
4027
4059
15576
7587780
598880
hrtimer_nanosleep
780e128f68
S eri.caranywhere
u0_a222
4027
4063
15576
7587780
598880
hrtimer_nanosleep
780e128f68
S eri.caranywhere
u0_a222
4027
4064
15576
7587780
598880
poll_schedule_timeout
780e1283a8
S eri.caranywhere
u0_a222
4027
4065
15576
7587780
598880
hrtimer_nanosleep
780e128f68
S eri.caranywhere
u0_a222
4027
4068
15576
7587780
598880
[pipe_wait](https:
/
/
www.notion.so
/
ec172c6f481d41559e523101ca95e05f?pvs
=
21
)
780b6ac008
S eri.caranywhere
|
这样就可以区分出,哪些是App通过pthrea_create自己创建的线程了。
继续往下分析,通过WCHAN观察线程状态,其中hrtimer_nanosleep
尤其显眼,说明该线程正在调用msleep之类的函数,也就验证了我之前的假设。
绕过frida检测
方法1: patch pthread_create 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
|
let pthread_create
=
Module.findExportByName(null,
"pthread_create"
)
let org_pthread_create
=
new NativeFunction(pthread_create,
"int"
, [
"pointer"
,
"pointer"
,
"pointer"
,
"pointer"
])
let my_pthread_create
=
new NativeCallback(function (a, b, c, d) {
let m
=
Process.getModuleByName(
"libDexHelper.so"
);
let base
=
m.base
console.log(Process.getModuleByAddress(c).name)
if
(Process.getModuleByAddress(c).name
=
=
m.name) {
console.log(
"pthread_create"
)
return
0
;
}
return
org_pthread_create(a, b, c, d)
},
"int"
, [
"pointer"
,
"pointer"
,
"pointer"
,
"pointer"
])
Interceptor.replace(pthread_create, my_pthread_create)
|
方法2: patch 所有调用pthread_create 函数的caller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
Interceptor.attach(pthread_create, {
onEnter: function (args) {
this.isHook
=
false
let m
=
Process.getModuleByName(
"libDexHelper.so"
);
let base
=
m.base
console.log(Process.getModuleByAddress(args[
2
]).name)
if
(Process.getModuleByAddress(args[
2
]).name
=
=
m.name) {
/
/
start_rtn地址 start_rtn偏移 caller地址
console.log(args[
2
], args[
2
].sub(base), this.context.lr.sub(m.base))
}
let addr
=
args[
2
].sub(base)
}
})
|
例如:获取caller偏移地址为 0xa765c
1
2
3
|
000a7654
841840f9
ldr x4, [x4,
#0x30]
000a7658
80003fd6
blr x4
000a765c
a0feff35 cbnz w0,
0xa7630
|
000a7658为函数pthread_create调用,成功时返回值0放在寄存器X0中,000a765c中W0则是取寄存器X0的低32位。
1
|
patch `
000a7658
80003fd6
blr x4` → `
000a7658
80003fd6
ldr x0
#0`
|
frida实现如下:
1
2
3
4
5
6
|
Memory.patchCode(ptr(addr),
4
, code
=
> {
console.log(
"bypass "
+
addr)
const cw
=
new Arm64Writer(code, {pc: ptr(addr)});
cw.putLdrRegU64(
"x0"
,
0
)
cw.flush();
});
|
验证结果:
frida可以正常spawn,不闪退。
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
|
frida
-
U
-
f com.byd.aeri.caranywhere
-
l _fcagent.js
-
-
no
-
pause
/
_ | Frida
15.2
.
2
-
A world
-
class
dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/
_
/
|_|
help
-
> Displays the
help
system
. . . .
object
?
-
> Display information about
'object'
. . . . exit
/
quit
-
> Exit
. . . .
. . . . More info at https:
/
/
frida.re
/
docs
/
home
/
. . . .
. . . . Connected to Redmi Note
7
(
id
=
bdb4a60)
Spawning `com.byd.aeri.caranywhere`...
[INFO][PID:
3779
][
3802
][JAVA]: available
0x71c451f0ac
Spawned `com.byd.aeri.caranywhere`. Resuming main thread!
[Pixel::com.byd.aeri.caranywhere ]
-
>
/
data
/
app
/
com.byd.aeri.caranywhere
-
qNtioYbkJ7fcACwYaIxNeg
=
=
/
oat
/
arm64
/
base.odex
/
data
/
app
/
com.byd.aeri.caranywhere
-
qNtioYbkJ7fcACwYaIxNeg
=
=
/
lib
/
arm64
/
libDexHelper.so
JNI_OnLoad
libDexHelper.so
pthread_create
libDexHelper.so
pthread_create
libDexHelper.so
pthread_create
libDexHelper.so
pthread_create
libDexHelper.so
pthread_create
/
data
/
app
/
com.byd.aeri.caranywhere
-
qNtioYbkJ7fcACwYaIxNeg
=
=
/
lib
/
arm64
/
libencrypt.so
|
查看线程状态,frida检测线程未创建,不影响执行流程。
1
2
3
|
1
|lavender:
/
proc
/
28458
# ps -Tp 3779 |grep car
u0_a222
3779
3779
17096
7013804
377392
SyS_epoll_wait
71c5591208
S eri.caranywhere
u0_a222
3779
3804
17096
7013804
377392
poll_schedule_timeout
71c5591388
S eri.caranywhere
|
虚拟框架加载So脱壳
So分析
checkCode算法分析
参考:
Linux逆向之调试&反调试
浅谈Android反调试 之 PTRACE_TRACEME
解决Android加固多进程ptrace反调试的思路整理
pthread_create函数详解(向线程函数传递参数)
[原创]绕过bilibili frida反调试
timerslack 与 sleep()/usleep()
更多【某车联网APPx梆反调试分析】相关视频教程:www.yxfzedu.com