frida源码初探

# PREFACE:被拷打到 frida 和 xpose 的原理啥的,啥啥不会,爬来赶紧学学

# 总览

首先是一整个 frida 项目可以分为这些 subprojects:

感觉网上许多文章都着重在看 frida-core 和 frida-gum,那么就先看看这两部分

# frida-gum

分析过程中感觉直接干看还是有点困难,这里先整理一些 frida 所实现的功能,对照着功能看进去

# 一、前置原理

# 1.1 inline hook

​ 我们可以使用 Frida 的 API 在程序运行时拦截函数的调用,并且在拦截到函数调用时,通过修改函数的参数或者返回值来改变程序的行为。

​ 这里的核心原理是:动态替换需要 Hook 的指令片段为一段经过设计的跳板指令,即 trampoline ,目标为我们设计好的一段 shellCode,这里可以看 [原创] Frida inlineHook 原理分析及简单设计一款 AArch64 inlineHook 工具 - Android 安全 - 看雪 - 安全社区 | 安全招聘 | kanxue.com 这篇文章对于 Frida 生成的 shellcode 进行了详细的研究,暂时没有进行复现,先纸面学习,大概需要注意的点是:

  • frida 寻找前后 128MB 的地址获取一个空闲地址,将 hook 的点的前 16 个字节(或 4 字节)替换掉为 LDR 和 BR 指令,并且利用 x16 寄存器作为 trampoline 跳板
  • 第一段跳板跳到 shellcode 后 mmap 一段匿名内存,保存当前寄存器状态与用于写入第二段跳板指令
  • 然后是 onenter 和 onleave 的编写,注入 shellcode 实现需求的逻辑即可(这里是简易的核心原理,后面代码会复杂很多)
# 1.2 inline hook 检测

恰好学习的时候搜到这篇文章,一块记录了

​ 既然 frida 会生成 trampoline 和 shellcode,那么有个很简单的方法:开一个线程,检查函数开头有没有被替换过,并且有没有被替换成类似 BR 到 shellcode 的格式

# 二、源码

readme 描述:

Cross-platform instrumentation and introspection library written in C.

This library is consumed by frida-core through its JavaScript bindings, GumJS.

然后文档有大概给出不同部分的功能(词汇量稍微有点不够咳咳)不过直接看着稍微有点没头绪,先看看别人的分析,这篇写的不错)

偷看一下别人的阅读思路:

image-20240428202138661

首先有个 meson.build 构建文件,其中可以先看 gumtest.c 这一测试运行器(根据 testcase 递归看进代码)

runner_sources = [
  'gumtest.c',
  'testutil.c',
  'stubs' / 'dummyclasses.c',
  'stubs' / 'fakebacktracer.c',
  'stubs' / 'fakeeventsink.c',
  'stalkerdummychannel.c',
  'lowlevelhelpers.c',
]
# gumtest.c

前面一大段代码根据宏来判断,是用于根据平台信息做初始化的

标记一个核心对象先:

struct _GumInterceptor
{
#ifndef GUM_DIET
  GObject parent;
#else
  GumObject parent;
#endif
  GRecMutex mutex;
  GHashTable * function_by_address;
  GumInterceptorBackend * backend;
  GumCodeAllocator allocator;
  volatile guint selected_thread_id;
  GumInterceptorTransaction current_transaction;
};

拦截器初始化

GumInterceptor *
gum_interceptor_obtain (void)
{
  GumInterceptor * interceptor;
  g_mutex_lock (&_gum_interceptor_lock);
#ifndef GUM_DIET
  if (_the_interceptor != NULL)
  {
    interceptor = GUM_INTERCEPTOR (g_object_ref (_the_interceptor));
  }
  else
  {
    _the_interceptor = g_object_new (GUM_TYPE_INTERCEPTOR, NULL);
    g_object_weak_ref (G_OBJECT (_the_interceptor),
        the_interceptor_weak_notify, NULL);
    interceptor = _the_interceptor;
  }
#else
  if (_the_interceptor != NULL)
  {
    interceptor = gum_object_ref (_the_interceptor);
  }
  else
  {
    _the_interceptor = g_new0 (GumInterceptor, 1);
    _the_interceptor->parent.ref_count = 1;
    _the_interceptor->parent.finalize = gum_interceptor_finalize;
    gum_interceptor_init (_the_interceptor);
    interceptor = _the_interceptor;
  }
#endif
  g_mutex_unlock (&_gum_interceptor_lock);
  return interceptor;
}

attach 上函数的方法:

传入地址,调用 gum_interceptor_resolve ,这里的 gum_interceptor_instrument 后面也会看

GumAttachReturn
gum_interceptor_attach (GumInterceptor * self,
                        gpointer function_address,
                        GumInvocationListener * listener,
                        gpointer listener_function_data)
{
  GumAttachReturn result = GUM_ATTACH_OK;
  GumFunctionContext * function_ctx;
  GumInstrumentationError error;
  gum_interceptor_ignore_current_thread (self);
  GUM_INTERCEPTOR_LOCK (self);
  gum_interceptor_transaction_begin (&self->current_transaction);
  self->current_transaction.is_dirty = TRUE;
  function_address = gum_interceptor_resolve (self, function_address);
  function_ctx = gum_interceptor_instrument (self, GUM_INTERCEPTOR_TYPE_DEFAULT,
      function_address, &error);
  if (function_ctx == NULL)
    goto instrumentation_error;
  if (gum_function_context_has_listener (function_ctx, listener))
    goto already_attached;
  gum_function_context_add_listener (function_ctx, listener,
      listener_function_data);
  goto beach;
instrumentation_error:
  {
    switch (error)
    {
      case GUM_INSTRUMENTATION_ERROR_WRONG_SIGNATURE:
        result = GUM_ATTACH_WRONG_SIGNATURE;
        break;
      case GUM_INSTRUMENTATION_ERROR_POLICY_VIOLATION:
        result = GUM_ATTACH_POLICY_VIOLATION;
        break;
      case GUM_INSTRUMENTATION_ERROR_WRONG_TYPE:
        result = GUM_ATTACH_WRONG_TYPE;
        break;
      default:
        g_assert_not_reached ();
    }
    goto beach;
  }
already_attached:
  {
    result = GUM_ATTACH_ALREADY_ATTACHED;
    goto beach;
  }
beach:
  {
    gum_interceptor_transaction_end (&self->current_transaction);
    GUM_INTERCEPTOR_UNLOCK (self);
    gum_interceptor_unignore_current_thread (self);
    return result;
  }
}

跟进看这里的实现

static gpointer
gum_interceptor_resolve (GumInterceptor * self,
                         gpointer address)
{
  address = gum_strip_code_pointer (address);
  if (!gum_interceptor_has (self, address))
  {
    const gsize max_redirect_size = 16;
    gpointer target;
    gum_ensure_code_readable (address, max_redirect_size);
    /* Avoid following grafted branches. */
    if (gum_process_get_code_signing_policy () == GUM_CODE_SIGNING_REQUIRED)
      return address;
    target = _gum_interceptor_backend_resolve_redirect (self->backend,
        address);
    if (target != NULL)
      return gum_interceptor_resolve (self, target);
  }
  return address;
}

可以进到:这部分是比较核心的生成跳转 shellcode 的部分,或许也可以从这部分下手来考虑对抗?(TODO+1)

gpointer
gum_arm64_reader_try_get_relative_jump_target (gconstpointer address)
{
  gpointer result = NULL;
  csh capstone;
  cs_insn * insn;
  const uint8_t * code;
  size_t size;
  uint64_t pc;
  const cs_arm64_op * ops;
  cs_arch_register_arm64 ();
  cs_open (CS_ARCH_ARM64, GUM_DEFAULT_CS_ENDIAN, &capstone);
  cs_option (capstone, CS_OPT_DETAIL, CS_OPT_ON);
  insn = cs_malloc (capstone);
  code = address;
  size = 16;
  pc = GPOINTER_TO_SIZE (address);
#define GUM_DISASM_NEXT() \
    if (!cs_disasm_iter (capstone, &code, &size, &pc, insn)) \
      goto beach; \
    ops = insn->detail->arm64.operands
#define GUM_CHECK_ID(i) \
    if (insn->id != G_PASTE (ARM64_INS_, i)) \
      goto beach
#define GUM_CHECK_OP_TYPE(n, t) \
    if (ops[n].type != G_PASTE (ARM64_OP_, t)) \
      goto beach
#define GUM_CHECK_OP_REG(n, r) \
    if (ops[n].reg != G_PASTE (ARM64_REG_, r)) \
      goto beach
#define GUM_CHECK_OP_MEM(n, b, i, d) \
    if (ops[n].mem.base != G_PASTE (ARM64_REG_, b)) \
      goto beach; \
    if (ops[n].mem.index != G_PASTE (ARM64_REG_, i)) \
      goto beach; \
    if (ops[n].mem.disp != d) \
      goto beach
  GUM_DISASM_NEXT ();
  switch (insn->id)
  {
    case ARM64_INS_B:
      result = GSIZE_TO_POINTER (ops[0].imm);
      break;
#ifdef HAVE_DARWIN
    case ARM64_INS_ADRP:
    {
      GumAddress target;
      GUM_CHECK_OP_REG (0, X17);
      target = ops[1].imm;
      GUM_DISASM_NEXT ();
      GUM_CHECK_ID (ADD);
      GUM_CHECK_OP_REG (0, X17);
      GUM_CHECK_OP_REG (1, X17);
      GUM_CHECK_OP_TYPE (2, IMM);
      target += ops[2].imm;
      GUM_DISASM_NEXT ();
      GUM_CHECK_ID (LDR);
      GUM_CHECK_OP_REG (0, X16);
      GUM_CHECK_OP_TYPE (1, MEM);
      GUM_CHECK_OP_MEM (1, X17, INVALID, 0);
      GUM_DISASM_NEXT ();
      GUM_CHECK_ID (BRAA);
      GUM_CHECK_OP_REG (0, X16);
      GUM_CHECK_OP_REG (1, X17);
      result = *((gpointer *) GSIZE_TO_POINTER (target));
      break;
    }
#endif
    default:
      break;
  }
beach:
  cs_free (insn, 1);
  cs_close (&capstone);
  return result;
}

创建拦截器后端以及分配器初始化

gum_interceptor_init (GumInterceptor * self)
{
  g_rec_mutex_init (&self->mutex);
  self->function_by_address = g_hash_table_new_full (NULL, NULL, NULL,
      (GDestroyNotify) gum_function_context_destroy);
  gum_code_allocator_init (&self->allocator, GUM_INTERCEPTOR_CODE_SLICE_SIZE);
  gum_interceptor_transaction_init (&self->current_transaction, self);
}

看到刚才 marked 的 gum_interceptor_instrument ,结合前面的代码,frida 似乎会将函数建哈希表用于寻址,并且进行了拦截器后端的初始化 _gum_interceptor_backend_create 以及生成跳板代码

static GumFunctionContext *
gum_interceptor_instrument (GumInterceptor * self,
                            GumInterceptorType type,
                            gpointer function_address,
                            GumInstrumentationError * error)
{
  GumFunctionContext * ctx;
  *error = GUM_INSTRUMENTATION_ERROR_NONE;
  // 要 hook 的函数,封装成了 GumFunctionContext,此时
  // 根据 地址,得到与之对应的 GunFunctionContext 对象
  ctx = (GumFunctionContext *) g_hash_table_lookup (self->function_by_address,
      function_address);
  if (ctx != NULL)
  {
    if (ctx->type != type)
    {
      *error = GUM_INSTRUMENTATION_ERROR_WRONG_TYPE;
      return NULL;
    }
    return ctx;
  }
  if (self->backend == NULL)
  {
    self->backend =
        _gum_interceptor_backend_create (&self->mutex, &self->allocator);
  }
  ctx = gum_function_context_new (self, function_address, type);
  if (gum_process_get_code_signing_policy () == GUM_CODE_SIGNING_REQUIRED)
  {
      // 创建跳板
    if (!_gum_interceptor_backend_claim_grafted_trampoline (self->backend, ctx))
      goto policy_violation;
  }
  else
  {
    if (!_gum_interceptor_backend_create_trampoline (self->backend, ctx))
      goto wrong_signature;
  }
  // 设置完成后, 添加到哈希表
  // hash_table, key, value
  //hook 函数地址,GumFunctionContext 对象对应, 方便查找
  g_hash_table_insert (self->function_by_address, function_address, ctx);
  
    // 当前 transaction 添加到 任务中, 设置回调 函数 gum_interceptor_activate 拦截器激活函数
  gum_interceptor_transaction_schedule_update (&self->current_transaction, ctx,
      gum_interceptor_activate);
  return ctx;
policy_violation:
  {
    *error = GUM_INSTRUMENTATION_ERROR_POLICY_VIOLATION;
    goto propagate_error;
  }
wrong_signature:
  {
    *error = GUM_INSTRUMENTATION_ERROR_WRONG_SIGNATURE;
    goto propagate_error;
  }
propagate_error:
  {
    gum_function_context_finalize (ctx);
    return NULL;
  }
}

先偷一个跳板代码:

00C30200  mov         al,byte ptr ds:[FF00C121h]  
00C30205  xor         eax,0C30200h  
00C3020A  jmp         00C30000   // 跳到上面的 enter_thunk
00C3020F  push        dword ptr ds:[0C30200h]  
00C30215  jmp         00C30100  // 跳到 leave_thunk
// 原函数修复的指令,7个字节
00C3021A  push        ebp  
00C3021B  mov         ebp,esp  
00C3021D  cmp         dword ptr [ebp+8],0  
00C30221  jmp         gum_test_target_function+7h (0D6FB97h) // 跳回原函数,因为写跳转用了7字节,所以+7

拦截器后端的初始化:这里初始化的 writerrelocator 分别用于指令写和指令恢复。

_gum_interceptor_backend_create (GRecMutex * mutex,
                                 GumCodeAllocator * allocator)
{
  GumInterceptorBackend * backend;
  backend = g_slice_new (GumInterceptorBackend);
  backend->allocator = allocator;
  gum_arm_writer_init (&backend->arm_writer, NULL);
  backend->arm_writer.cpu_features = gum_query_cpu_features ();
  gum_arm_relocator_init (&backend->arm_relocator, NULL, &backend->arm_writer);
  gum_thumb_writer_init (&backend->thumb_writer, NULL);
  gum_thumb_relocator_init (&backend->thumb_relocator, NULL,
      &backend->thumb_writer);
  gum_interceptor_backend_create_thunks (backend);
  return backend;
}

看到 thunks 的初始化,这里是关键函数,用来调度执行,对应进入 hook 和离开 hook

static void
gum_interceptor_backend_create_thunks (GumInterceptorBackend * self)
{
  GumArmWriter * aw = &self->arm_writer;
  GumThumbWriter * tw = &self->thumb_writer;
  self->arm_thunks = gum_code_allocator_alloc_slice (self->allocator);
  gum_arm_writer_reset (aw, self->arm_thunks->data);
  self->enter_thunk_arm = gum_arm_writer_cur (aw);
  gum_emit_arm_enter_thunk (aw);
  self->leave_thunk_arm = gum_arm_writer_cur (aw);
  gum_emit_arm_leave_thunk (aw);
  gum_arm_writer_flush (aw);
  g_assert (gum_arm_writer_offset (aw) <= self->arm_thunks->size);
  self->thumb_thunks = gum_code_allocator_alloc_slice (self->allocator);
  gum_thumb_writer_reset (tw, self->thumb_thunks->data);
  self->enter_thunk_thumb = gum_thumb_writer_cur (tw) + 1;
  gum_emit_thumb_enter_thunk (tw);
  self->leave_thunk_thumb = gum_thumb_writer_cur (tw) + 1;
  gum_emit_thumb_leave_thunk (tw);
  gum_thumb_writer_flush (tw);
  g_assert (gum_thumb_writer_offset (tw) <= self->thumb_thunks->size);
}

gum_emit_arm_enter_thunk:

static void
gum_emit_arm_enter_thunk (GumArmWriter * aw)
{
  // 用于保存返回地址以及栈、寄存器情况
  gum_emit_arm_prolog (aw);
  // 构造自身函数栈以及寄存器
  gum_arm_writer_put_add_reg_reg_imm (aw, ARM_REG_R1, ARM_REG_SP,
      GUM_FRAME_OFFSET_CPU_CONTEXT);
  gum_arm_writer_put_sub_reg_reg_imm (aw, ARM_REG_R2, ARM_REG_R4, 4);
  gum_arm_writer_put_add_reg_reg_imm (aw, ARM_REG_R3, ARM_REG_SP,
      GUM_FRAME_OFFSET_NEXT_HOP);
  // 调用
  gum_arm_writer_put_call_address_with_arguments (aw,
      GUM_ADDRESS (_gum_function_context_begin_invocation), 4,
      GUM_ARG_REGISTER, ARM_REG_R6,
      GUM_ARG_REGISTER, ARM_REG_R1,
      GUM_ARG_REGISTER, ARM_REG_R2,
      GUM_ARG_REGISTER, ARM_REG_R3);
  gum_emit_arm_epilog (aw);
}

gum_emit_arm_leave_thunk:

static void
gum_emit_arm_leave_thunk (GumArmWriter * aw)
{
  gum_emit_arm_prolog (aw);
  gum_arm_writer_put_add_reg_reg_imm (aw, ARM_REG_R1, ARM_REG_SP,
      GUM_FRAME_OFFSET_CPU_CONTEXT);
  gum_arm_writer_put_add_reg_reg_imm (aw, ARM_REG_R2, ARM_REG_SP,
      GUM_FRAME_OFFSET_NEXT_HOP);
  gum_arm_writer_put_call_address_with_arguments (aw,
      GUM_ADDRESS (_gum_function_context_end_invocation), 3,
      GUM_ARG_REGISTER, ARM_REG_R6,
      GUM_ARG_REGISTER, ARM_REG_R1,
      GUM_ARG_REGISTER, ARM_REG_R2);
  gum_emit_arm_epilog (aw);
}

拦截器激活:

static void
gum_interceptor_activate (GumInterceptor * self,
                          GumFunctionContext * ctx,
                          gpointer prologue)
{
  if (ctx->destroyed)
    return;
  g_assert (!ctx->activated);
  ctx->activated = TRUE;
  _gum_interceptor_backend_activate_trampoline (self->backend, ctx,
      prologue);
}

核心 bl 处:

void _gum_interceptor_backend_activate_trampoline (GumInterceptorBackend * self,
                                              GumFunctionContext * ctx,
                                              gpointer prologue)
{
  GumAddress function_address;
  GumArmFunctionContextData * data = GUM_FCDATA (ctx);
  function_address = GUM_ADDRESS (
      _gum_interceptor_backend_get_function_address (ctx));
  if (FUNCTION_CONTEXT_ADDRESS_IS_THUMB (ctx))
  {
    GumThumbWriter * tw = &self->thumb_writer;
	// 设置 base、pc
    gum_thumb_writer_reset (tw, prologue);
    tw->pc = function_address;
    if (ctx->trampoline_deflector != NULL)
    {
      if (data->redirect_code_size == GUM_INTERCEPTOR_THUMB_LINK_REDIRECT_SIZE)
      {
        gum_emit_thumb_push_cpu_context_high_part (tw);
        gum_thumb_writer_put_bl_imm (tw, // 写 bl,进行跳板操作
            GUM_ADDRESS (ctx->trampoline_deflector->trampoline));
      }
      else
      {
        g_assert (data->redirect_code_size ==
            GUM_INTERCEPTOR_THUMB_TINY_REDIRECT_SIZE);
        gum_thumb_writer_put_b_imm (tw,
            GUM_ADDRESS (ctx->trampoline_deflector->trampoline));
      }
    }
    else if (ctx->type == GUM_INTERCEPTOR_TYPE_FAST)
    {
      gum_thumb_writer_put_ldr_reg_address (tw, ARM_REG_PC,
          GUM_ADDRESS (ctx->replacement_function));
    }
    else
    {
      gum_thumb_writer_put_ldr_reg_address (tw, ARM_REG_PC,
          GUM_ADDRESS (ctx->on_enter_trampoline));
    }
    gum_thumb_writer_flush (tw);
    g_assert (gum_thumb_writer_offset (tw) <= data->redirect_code_size);
  }
  else
  {
    GumArmWriter * aw = &self->arm_writer;
    gum_arm_writer_reset (aw, prologue);
    aw->pc = function_address;
    if (ctx->trampoline_deflector != NULL)
    {
      g_assert (data->redirect_code_size ==
          GUM_INTERCEPTOR_ARM_TINY_REDIRECT_SIZE);
      gum_arm_writer_put_b_imm (aw,
          GUM_ADDRESS (ctx->trampoline_deflector->trampoline));
    }
    else if (ctx->type == GUM_INTERCEPTOR_TYPE_FAST)
    {
      gum_arm_writer_put_ldr_reg_address (aw, ARM_REG_PC,
          GUM_ADDRESS (ctx->replacement_function));
    }
    else
    {
      gum_arm_writer_put_ldr_reg_address (aw, ARM_REG_PC,
          GUM_ADDRESS (ctx->on_enter_trampoline));
    }
    gum_arm_writer_flush (aw);
    g_assert (gum_arm_writer_offset (aw) == data->redirect_code_size);
  }
}

# 三、执行流程

偷一个(上面我们走的是 arm 的流程,函数名略有不同,但流程是一样的)

image-20240503113613151

# 先这样~有机会再来调试

# frida-core

# 一、前置知识

# 1、vala 语言

​ 介绍:Projects/Vala - GNOME Wiki!:Vala is a programming language using modern high level abstractions without imposing additional runtime requirements and without using a different ABI compared to applications and libraries written in C. Vala uses the GObject type system and has additional code generation routines that make targeting the GNOME stack simple. Vala has many other uses where native binaries are required.

​ vala 语言是 GNOME 中使用的一个高级语言,和传统高级语言不同的是 vala 代码会被编译器先编译成 C 代码,然后再编译成二进制文件,因此也可以认为 vala 语言是 C 的一个语法糖拓展。其使用使用 glib 的 GObject 类型系统来构造类和接口以实现面向对象,其语法有点类似于 C# ,支持许多现代语言的高级特性,包括但不限于接口、属性、内存管理、异常、lambda、信号等等。Vala 既可以通过 API 文件访问已有的 C 库文件,也可以从 C 中很容易调用 Vala 的方法。

直接看也能大概看懂

vala 官方文档

# 2、进程问题

​ 无论在 Android 还是 iOS 或者其他平台,进程和进程直接相互是透明的,就算对 Frida 来说目标进程并非透明的,但是对目标进程来说,至少 Frida 也该是透明的。但是现实情况显然是,二者往往需要相互之间识别并交互。

# 二、源码分析

vala 插件点不进去啊急了,看不动先鸽了 ××× 等 selinux 看看再回来