通过Frida源码来做对抗

前言

之前在研究如何有效的对抗Frida的攻击,从github开源项目中获取的信息来看,还是无法做到100%达成的。诸如:https://github.com/qtfreet00/AntiFrida、https://github.com/darvincisec/DetectFrida、https://github.com/b-mueller/frida-detection-demo。

我都试过了,诸如

  • 检测maps表信息,匹配关键字;虽然maps表是防守方经常使用的一个文件,但是不够靠谱,当然也可以通过内联汇编的方式读取文件(防止被hook掉了读取相关的api),但是匹配关键字难免得不偿失。还有一些文件的检测,这些都没啥太大的作用,不做概述。
  • 通过Frida内存特征对maps中elf文件进行扫描匹配特征,这个方法不是百试百灵。
  • 通过frida-server的D-BUS通信来进行检测,方法是好的, 但是如果对所有的端口号都进行遍历检测的话,那么很影响原始APP的性能。

正文

那么有没有一种一劳永逸(类似)的方法呢?

自己接着去查找相关的文档,最好是frida作者写的那种文档了,竟然发现了frida作者大胡子竟然为frida做个一次演讲,主要讲的就是frida的注入实现。原ppt:https://frida.re/slides/osdc-2015-the-engineering-behind-the-reverse-engineering.pdf

其中很详细的说明了frida 的注入流程是如何的,怎么做到注入的。

大致的讲下注入的步骤:

  1. 创建一个包含frida 代理的.so
  2. 使用ptrace劫持远程进程中的线程
  3. 使用代码填充引导程序
  4. 执行引导程序
  5. 开始新线程
  6. 打开FIFO到调试器进程
  7. 通过 FIFO 通知调试器
  8. 加载代理的.so文件,(主要是通过mmap方法加载,这是一个很关键的点)
  9. 恢复远程线程执行
  10. 执行代理入口点
  11. 注入成功。

Pdf 的debuggee就是frida-server了,这些过程大多是frida-server来完成注入的。

所以直接让frida-server无效即可。

那我们来看frida-server的源码来找找有什么关键的点。

https://github.com/frida/frida-core/blob/master/src/linux/frida-helper-backend-glue.c 这个代码就是frida-server在android上的大致功能了。

其中有很多关于libc的源码:

1
2
3
4
5
6
static GumAddress frida_resolve_libc_function (pid_t pid, const gchar * function_name);

static GumAddress frida_find_libc_base (pid_t pid);

#if defined (HAVE_ANDROID) || defined (HAVE_UCLIBC)

其中的frida_find_libc_base 需要引起注意,这是比较关键 一个函数。他的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
frida_find_libc_base (pid_t pid)

{

#if defined (HAVE_UCLIBC)

return frida_find_library_base (pid, "libuClibc", NULL);

#else

return frida_find_library_base (pid, "libc", NULL);

#endif

}

其中的frida_find_library_base 又是关键 ,顺藤摸瓜,看看在干嘛

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
static GumAddress

frida_find_library_base (pid_t pid, const gchar * library_name, gchar ** library_path)

{

GumAddress result = 0;

gchar * maps_path;

FILE * fp;

const guint line_size = 1024 + PATH_MAX;

gchar * line, * path;



if (library_path != NULL)

*library_path = NULL;



maps_path = g_strdup_printf ("/proc/%d/maps", pid);



fp = fopen (maps_path, "r");

g_assert (fp != NULL);



g_free (maps_path);



line = g_malloc (line_size);

path = g_malloc (PATH_MAX);



while (result == 0 && fgets (line, line_size, fp) != NULL)

{

GumAddress start;

gint n;



n = sscanf (line, "%" G_GINT64_MODIFIER "x-%*x %*s %*x %*s %*s %s", &start, path);

if (n == 1)

continue;

g_assert (n == 2);



if (path[0] == '[')

continue;



if (strcmp (path, library_name) == 0)

{

result = start;

if (library_path != NULL)

*library_path = g_strdup (path);

}

#ifdef HAVE_ANDROID

else if (g_str_has_prefix (path, "/system_root") && strcmp (path + strlen ("/system_root"), library_name) == 0)

{

result = start;

if (library_path != NULL)

*library_path = g_strdup (path);

}

#endif

else

{

gchar * p = strrchr (path, '/');

if (p != NULL)

{

p++;



if (g_str_has_prefix (p, library_name) && strstr (p, ".so"))

{

gchar next_char = p[strlen (library_name)];

if (next_char == '-' || next_char == '.')

{

result = start;

if (library_path != NULL)

*library_path = g_strdup (path);

}

}

}

}

}



g_free (path);

g_free (line);



fclose (fp);



return result;

}

有了能够找到so的函数,那么也会有找到so中function的函数:

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
static GumAddress

frida_resolve_library_function (pid_t pid, const gchar * library_name, const gchar * function_name)

{

gchar * local_library_path, * remote_library_path, * canonical_library_name;

GumAddress local_base, remote_base, remote_address;

gpointer module, local_address;



local_base = frida_find_library_base (getpid (), library_name, &local_library_path);

g_assert (local_base != 0);



remote_base = frida_find_library_base (pid, local_library_path, &remote_library_path);

if (remote_base == 0)

return 0;



g_assert (g_strcmp0 (local_library_path, remote_library_path) == 0);



canonical_library_name = g_path_get_basename (local_library_path);



module = dlopen (canonical_library_name, RTLD_GLOBAL | RTLD_LAZY);

g_assert (module != NULL);



local_address = dlsym (module, function_name);

g_assert (local_address != NULL);



remote_address = remote_base + (GUM_ADDRESS (local_address) - local_base);



dlclose (module);



g_free (local_library_path);

g_free (remote_library_path);

g_free (canonical_library_name);



return remote_address;

}

然后frida会使用这个函数来使用libc中的函数(诸如mmap、open、close等),然后在通过这些函数来实现注入。

如何防御

已知frida会使用libc的函数来进行注入,那么我们能不能将自己的libc弄一下呢,让frida拿到的libc是假的或者是废的。

通过frida_find_library_base 实现可以知道,frida查找maps表中 so,只会找到第一个,比如libc他也只 找到第一个并没有很多 校验之类的,匹配到了返回。

这就是他不是很严谨的一个地方,我们可以在app启动的时候就在maps表中mmap一条只读的libc,这样frida拿到了我们的libc就无法继续使用从而崩溃。

具体的代码:https://github.com/TUGOhost/anti_Android/blob/main/anti/src/main/cpp/check/anti_frida.cpp