Tech

通过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 的注入流程是如何的,怎么做到注入的。

大致的讲下注入的步骤:

  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的源码:

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