通过Frida源码来做对抗
之前在研究如何有效的对抗Frida的攻击,从github开源项目中获取的信息来看,还是无法做到100%达成的。诸如:https://github.com/qtfreet00/AntiFrida、https://github.com/darv
前言
之前在研究如何有效的对抗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的源码:
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 需要引起注意,这是比较关键 一个函数。他的实现如下:
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 又是关键 ,顺藤摸瓜,看看在干嘛
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的函数:
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