前言
之前在研究如何有效的对抗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 的注入流程是如何的,怎么做到注入的。
大致的讲下注入的步骤:
- 创建一个包含frida 代理的.so
- 使用ptrace劫持远程进程中的线程
- 使用代码填充引导程序
- 执行引导程序
- 开始新线程
- 打开FIFO到调试器进程
- 通过 FIFO 通知调试器
- 加载代理的.so文件,(主要是通过mmap方法加载,这是一个很关键的点)
- 恢复远程线程执行
- 执行代理入口点
- 注入成功。
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