专栏名称: 看雪学苑
致力于移动与安全研究的开发者社区,看雪学院(kanxue.com)官方微信公众帐号。
目录
相关文章推荐
上海普陀  ·  聚智赋能!2025第四届新耀东方网络安全博览 ... ·  5 小时前  
上海普陀  ·  聚智赋能!2025第四届新耀东方网络安全博览 ... ·  5 小时前  
计算机与网络安全  ·  2025 ... ·  22 小时前  
看雪学苑  ·  欢迎投递简历~ ·  2 天前  
计算机与网络安全  ·  渗透测试工程师(高级)证书:终身有效(无需维 ... ·  2 天前  
中科院之声  ·  我国科学家成功合成新核素镤-210 ·  2 天前  
中科院之声  ·  我国科学家成功合成新核素镤-210 ·  2 天前  
51好读  ›  专栏  ›  看雪学苑

Stalker 源码浅入浅出

看雪学苑  · 公众号  · 互联网安全  · 2025-06-02 17:59

正文

请到「今天看啥」查看全文



这个变量配合着 _GumExecBlock中的

gint recycle_count;


每次执行到某个基本块,stalker就会先去对比原始代码和快照,上文也有提到,如果没变化给recycle_count加一,一直加到trust_threshold为止,到了 trust_threshold,就说明这个基本块已经稳定了,不会发生变化,此后如果再次执行到这个基本块,就啥也不做了直接跳转到插好桩后的基本块,这个信任阈值是用户指定的。


之后是_GumSlab,这个是stalker内存分配用的,为了避免每次都使用malloc或者mmap,比较影响性能,所以自己实现了内存分配机制,先预先申请一大块内存,之后需要用内存,就从这个块里获取,看一眼就知道大概意思和作用了,stalker里保存着很多。


_GumSlab


他们是以链表的形式保存的,里面保存着stalker插装好的基本块,slob还有快慢两种不同用处的内存块,把执行频率高的代码集中放入快路径,以提供cache命中率,不过这是琐碎的细节优化问题,借用著名外交官耿爽大使的一句话,不必理会。


struct _GumSlab
{

  guint8 * data;      // 数据区起始位置

  guint offset;       // 当前已使用的偏移量

  guint size;         // 可用数据区大小

  guint memory_size;  // 整个内存块大小(包括头部)

  GumSlab * next;     // 链表指向下一个slab


接下来是_GumStalkerIterator,主要负责配合transform函数进行具体的翻译的,后文会提到。


struct _GumStalkerIterator
{
  GumExecCtx * exec_context;
  GumExecBlock * exec_block;
  GumGeneratorContext * generator_context;
  GumInstruction instruction;
  GumVirtualizationRequirements requirements;
};


最后是_GumGeneratorContext,他是翻译过程中用于生成指令的主要数据结构,而更加具体的功能是利用frida gum提供的能力,比如GumArm64Relocator用于将指令复制到新内存,会自动处理pc相关指令,GumArm64Writer用于生成指令。


struct _GumGeneratorContext
{
  GumInstruction * instruction;
  GumArm64Relocator * relocator;
  GumArm64Writer * code_writer;
  GumArm64Writer * slow_writer;
  gpointer continuation_real_address;
  GumPrologType opened_prolog;
};


流程

stalker的入口有两个api,gum_stalker_follow_me和 gum_stalker_follow,以 gum_stalker_follow为例说明,后文给出的代码只给出重要的部分,省略一部分平凡的。

1.gum_stalker_follow (GumStalker * self,
                    GumThreadId thread_id,
                    GumStalkerTransformer* transformer,
                    GumEventSink* sink)
    gumInfectContext ctx;
    ctx.stalker = self;
    ctx.transformer = transformer;
    ctx.sink = sink;

gum_process_modify_thread (thread_id, gum_stalker_infect, &ctx,

        GUM_MODIFY_THREAD_FLAGS_NONE);

2.gum_process_modify_thread (GumThreadId thread_id,
                           GumModifyThreadFunc func,
                           gpointer user_data,
                           GumModifyThreadFlags flags)
                           
GumModifyThreadContext ctx;

  ctx.func = func//gum_stalker_infect

  ctx.user_data = user_data; //gum_stalker_follow的ctx,gumInfectContext
  return gum_linux_modify_thread (thread_id, GUM_REGS_GENERAL_PURPOSE,
      gum_do_modify_thread, &ctx, NULL);

3. gum_linux_modify_thread (GumThreadId thread_id,
                         GumLinuxRegsType regs_type,
                         GumLinuxModifyThreadFunc func,
                         gpointer user_data,
                         GError** error)

  ctx.thread_id = thread_id;
  ctx.regs_type = regs_type;
  ctx.func = func//gum_do_modify_thread
  ctx.user_data = user_data;  //gum_process_modify_thread的ctx,GumModifyThreadContext

     child = gum_libc_clone (
      gum_linux_do_modify_thread,
      stack + gum_query_page_size (),
      CLONE_VM|CLONE_SETTLS,
      &ctx,
      NULL,
      desc,
      NULL);
    prctl (PR_SET_PTRACER, child);
      if (thread_id == gum_process_get_current_thread_id ())
  {
    success =GPOINTER_TO_UINT (g_thread_join (g_thread_new (
            "gum-modify-thread-worker",
            gum_linux_handle_modify_thread_comms,
            &ctx)));
  }
  else
  {
    success =GPOINTER_TO_UINT (gum_linux_handle_modify_thread_comms (&ctx));
  }

4.gum_linux_handle_modify_thread_comms (gpointer data)
GumLinuxModifyThreadContext* ctx = data;
//func是 gum_do_modify_thread
ctx->func (ctx->thread_id, &ctx->regs_data, ctx->user_data);

5.gum_do_modify_thread (GumThreadId thread_id,
                      GumRegs* regs,
                      gpointer user_data)
GumModifyThreadContext* ctx = user_data;
// func 是 gum_stalker_infect
// user_data是这三个的ctx
//    gumInfectContext ctx;
    //    ctx.stalker = self;
    //    ctx.transformer = transformer;
    //    ctx.sink = sink;
ctx->func (thread_id, &cpu_context, ctx->user_data);

6.gum_stalker_infect (GumThreadId thread_id,
                    GumCpuContext* cpu_context,
                    gpointer user_data)


其中最后一个参数是事件相关的,stalker支持一些事件以及回调,比如执行基本块事件,执行指令事件,函数调用事件等等,通过事件机制让用户在这些重要的时间点执行一些操作。


gum_stalker_follow做了很多事情,可分为两个阶段,第一阶段是为第二阶段调用gum_stalker_infect做好各种准备,也就是上面贴出来的代码;太细的就不说了,只说一些最重要的事情和大概的轮廓,stalker是通过不断地拉取基本块,翻译基本块,执行基本块,这样子工作的,那么如何拉取基本块呢,特别首次如何拉取基本块,就是 gum_stalker_follow做的事情了。


如果是 gum_stalker_follow_me进入的,那么拉取基本块就比较简单,因为此时正在调用 gum_stalker_follow_me函数,只要获取的lr寄存器,就可可以作为首个基本块的入口了; gum_stalker_follow跟踪的并不是本线程,而是跨线程,所以有一些技术在里面,stalker主要是通过clone系统调用,生成一个新线程,然后这个新线程通过ptrace系统调用,去attach要跟踪的目标线程。


由此就可以拿到目标线程的寄存器上下文,通过pc寄存器即可拿到首个基本块的入口地址了,之所以采用clone而不是fork,是有两个原因的,一是因为stalker期望与主进程共享内存,fork出来的线程,是一个新进程,其内存是独立的,第二个原因是ptrace系统调用不允许attach同一个线程组的线程,使用clone可以比较精细的配置新线程,让它属于不同线程组,以满足这个要求。


注意,此时出现了三个线程,执行 gum_stalker_follow的线程,成为主线程,clone出来的线程,称为clone线程,还有将要跟踪的目标线程,主线程和clone线程通过socket通信,主线程通过clone线程去ptrace目标线程,拿到目标线程的寄存器上下文,然后执行一定的逻辑,这些逻辑会修改寄存器上下文,最后让clone线程把被修改后的寄存器上下文写回目标线程,主要的修改就是获取了目标线程的pc,通过pc拉取基本块,翻译基本块,之后把pc改为指向翻译好的基本块,这样就完成了跟踪。


上面说到 gum_stalker_follow分为两阶段,一阶段主要是配置和clone新线程,二阶段就是在clone的线程中执行gum_stalker_infect。


1






请到「今天看啥」查看全文