首页
论坛
专栏
课程

[原创]源码简析之ArtMethod结构与涉及技术介绍

deff 2019-1-11 15:22 1312

源码简析之ArtMethod结构与涉及技术介绍

一、前言

       
不论是APK代码抽取加固还是热更新,亦或是Xposed/frida hook,都离不开ArtMethod这个结构体。

 

       但该结构体从andorid 4.4开始一直到现在的9.0,或多或少都会update,这导致之前版本的脱壳或者热更新都需要实时适配……想想,还是针对该结构体进行一个总结,总结过程当中会顺便介绍一些常见的框架使用到该结构体的部分。

 

主要做个人的一个总结,有些地方可能表述不清……

 

       目的:一方面是进行个人总结,加深印象,另一方面便于以后查询适配。

 

       适用于:了解hook原理基础

二、Method与ArtMethod

       谈到ArtMethod还是提下4.4之前Method。

 

       因为本次是针对ArtMethod做各版本总结,这里简单列出对比,方便过渡。

名称 版本 环境 执行方式 源码地址
Method <=4.4 Dalvik VM DEX字节码(insns) platform/art/runtime/art_method.h
artmethod >=4.4 Android Runtime DEX字节码或者本地机器指令 platform/art/runtime/art_method.h
 

       针对Method结构体,不得不谈谈xposed,还有第一代指令抽取加固方式。实际就是找到Method 中的 insns指令码字段(加固时是在dex文件,hook则直接获取函数指针得到该指令码字段),然后运行时替换。

 

       而ArtMethod,是ART下的产物。其结构教于Method发生了变化,当dex2oat的时候,还会有多种方法内联优化。而方法内联会改变原本的方法分布和调用流程,对加固、热修复、hook,实际上都有一定的影响。当然,这个是后话,就不再赘述。

三、ArtMethod结构

       先给出ArtMethod结构主要的字段。
英文是源码,中文是个人注释

 

版本:android-9.0.0_r30:

class ArtMethod {
 …………
protect:

 // Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".
  // The class we are a part of.
  //GCroot添加在N,实际是个指针,这个字段即是表明所属类,一般用喜闻乐见的uint32_t代替
  GcRoot<mirror::Class> declaring_class_;

  // Access flags; low 16 bits are defined by spec.
  // Getting and setting this flag needs to be atomic when concurrency is
  // possible, e.g. after this method's class is linked. Such as when setting
  // verifier flags and single-implementation flag.
  //可以理解为该函数的标志位,如函数为public,private,static,native等。
  //这个字段java2native,xposed hook的标志位。
  //值得一提的是,9.0以后的Hid API,对应函数GetHiddenApiAccessFlags,不是这个,但是看到这里,可以考虑如何修改hide标志位

  std::atomic<std::uint32_t> access_flags_;


  /* Dex file fields. The defining dex file is available via declaring_class_->dex_cache_ */
  // Offset to the CodeItem.
  //加固代码替换点。
  //里面指向code_item指针,code_item存储的实际是dex当中的字节码.其用处本来是适配dalvik解释器,即无法编译成机器码的,用解释器来执行。
 //
  uint32_t dex_code_item_offset_;



  // Index into method_ids of the dex file associated with this method.
  //主要作为寻址替换用
  uint32_t dex_method_index_;


  /* End of dex file fields. */
  // Entry within a dispatch table for this method. For static/direct methods the index is into
  // the declaringClass.directMethods, for virtual methods the vtable and for interface methods the
  // ifTable.
  uint16_t method_index_;



  // The hotness we measure for this method. Not atomic, as we allow
  // missing increments: if the method is hot, we will see it eventually.
  //方法被记录的频率热度,用在Android N+混合编译部分,根据该值来判断是否使用JIT编译。
  uint16_t hotness_count_;


  // Fake padding field gets inserted here.
  // Must be the last fields in the method.
   //没错,这个Fields9.0地址又改了,需适配,在下面详细介绍每个字段
  struct PtrSizedFields {
    // Depending on the method type, the data is
    //   - native method: pointer to the JNI function registered to this method
    //                    or a function to resolve the JNI function,
    //   - conflict method: ImtConflictTable,
    //   - abstract/interface method: the single-implementation if any,
    //   - proxy method: the original interface method or constructor,
    //   - other methods: the profiling data.


   //这个字段顾名思义,根据方法类型,会充当不同作用,是不是可以理解为以前几个指针的复合……
    void* data_;
    // Method dispatch from quick compiled code invokes this pointer which may cause bridging into
    // the interpreter.
    void* entry_point_from_quick_compiled_code_;
  } ptr_sized_fields_;

  …………
  }

       上面是最新的9.0的结构。以下重点介绍几个版本改动处,原因,以及适配点。

 

       前面的结构几乎没有变化,但是要开始谈论,还是得溯源到5.0……
5.0地址在runtime/mirror/art_method.h中

 

关键结构

class ArtMethod {
 …………
 protect:
  HeapReference<Class> declaring_class_;
  HeapReference<ObjectArray<ArtMethod>> dex_cache_resolved_methods_;
  HeapReference<ObjectArray<Class>> dex_cache_resolved_types_;
  uint32_t access_flags_;
  uint32_t dex_code_item_offset_;
  uint32_t dex_method_index_;
  uint32_t method_index_;
  struct PACKED(4) PtrSizedFields {
    void* entry_point_from_interpreter_;
    void* entry_point_from_jni_;
    void* entry_point_from_quick_compiled_code_;

#if defined(ART_USE_PORTABLE_COMPILER)
    void* entry_point_from_portable_compiled_code_;
#endif
  } ptr_sized_fields_;
  static GcRoot<Class> java_lang_reflect_ArtMethod_;

}
……

       这个时候,还存在entry_point_fromjni,entry_point_frominterpreter,关键在于这两个字段的被广泛用于hook(frida,xposed)、加固以及热更新(所以版本更新就得适配了……)

 

下面重点介绍下PtrSizedFields结构,其版本做过多个改变,实际上是进行了精简,不过还是需要适配……

5.0:
struct PACKED(4) PtrSizedFields {
    void* entry_point_from_interpreter_;
    void* entry_point_from_jni_;
    void* entry_point_from_quick_compiled_code_;

    #if defined(ART_USE_PORTABLE_COMPILER)
        void* entry_point_from_portable_compiled_code_;
    #endif
} ptr_sized_fields_;

6.0: 
struct PtrSizedFields {
    void* entry_point_from_interpreter_;
    void* entry_point_from_jni_;
    void* entry_point_from_quick_compiled_code_;
} ptr_sized_fields_;

7.0:
struct PtrSizedFields {
    ArtMethod** dex_cache_resolved_methods_;
    void* dex_cache_resolved_types_;
    void* entry_point_from_jni_;
    void* entry_point_from_quick_compiled_code_;
} ptr_sized_fields_;


8.0:
struct PtrSizedFields {
    ArtMethod** dex_cache_resolved_methods_;
    void* data_;
    void* entry_point_from_quick_compiled_code_;
 } ptr_sized_fields_;

9.0:
struct PtrSizedFields {
    void* data_;
    void* entry_point_from_quick_compiled_code_;
} ptr_sized_fields_;

简单谈一下几个关键字段的作用与各版本改变,可以对照之上的看:

uint32_t access_flags_:标志位,常见的 Hook方案,将待hook的java函数修改为native函数,就是修改该标志位。


uint32_t dex_code_item_offset_:最终可得到code_item地址,里面存储这方法指令。但需先获取dex头地址


entry_point_from_jni_:用于存储jni方法信息。非jni方法的entrypoint_from_jni_是无用的,所以很多hook框架将该字段作为原方法信息备份地址,如xposed,frida都是将原方法保存到entry_point_from_jni_。但android 7.0也就是N后,该字段被用于保存方法的profile,这个又和hotness_count_相关,及混合编译,有兴趣可以去看源码,所以这个字段不能作为原方法保存地址。而且在8.0以后,entry_point_from_jni_同样被移除了……


entry_point_from_interpreter_:通过interpreter方式调用方法 解释执行入口。android 7.0,entrypoint_from_interpreter_已经被移除,非jni方法的执行通过entrypoint_from_quick_compiled_code_进行分发。


entry_point_from_quick_compiled_code:
这里就是常见的ART Hook方法为,替换方法的入口点,替换后的入口点,会重新准备栈和寄存器,执行hook的方法。注意,这里指向的是汇编代码,运行的是已经预处理过的机器码。

        从上面就可以很清晰的看到一个7.0之前的hook流程:获取函数的入口,得到函数结构体,替换accessflags将方法native化,保存原函数信息在entry_point_fromjni,替换entry_point_from_quick_compiled_code。调用被hook的方法时,首先会跳转替换的方法,然后跳转到entry_point_fromjni所指向的原方法。

 

       当然,每个版本的hook方式不同,有些会采用机器码执行,有些会采用解释器执行,同样的加固原理也不同。

 

       这边可以发现两种指令抽取加固原理。

  • 替换dex_code_itemoffset对应的code_item,不改变原方法属性
  • 改变原方法属性,直接替换entry_point_from_quick_compiled_code。

       通过artmethod关键结构,可以发现,这都是可行的操作。

 

       但这里仅从原理角度分析可行,但实际环境适配与汇编代码编写,都是需要处理的,因为Android N的混合编译,垃圾回收修改,会导致的诸多问题,而7.0以后的适配,实际也就是针对原函数存放地址的适配,只能简略提一提,不多提。

 

       忽然想到,现在的vm加固,若采用ARM 虚拟化保护,需要制定汇编解释器,将原始函数转换成字节码,保存原始函数的寄存器以及堆栈信息,然后在运行当中,然后用自定义解释器解析,这一部分又是一块需要大量实验和适配的地方……当然只是闲扯一句。

 

以上,实际深入了解ArtMethod结构后,会对以往版本的hook、代码抽取加固技术等有一个更清晰的认识,通过源码理解字段的作用和功能,也可以帮助产品进行适配,不至于在版本更新时,无从下手。



[推荐]看雪企服平台,提供APP等级保护加固等安全服务!

本主题帖已收到 1 次赞赏,累计¥2.00
最新回复 (8)
junkboy 2019-1-11 15:28
2

0

支持
windxiang 2019-1-11 18:05
3

0

多谢。学习下
Editor 2019-1-11 18:38
4

0

赞!
koflfy 1 2019-1-11 21:12
5

0

mark
hhhaiai 2019-1-12 19:15
6

0

mark
roysue 3 2019-1-14 17:37
7

0

mark
niuzuoquan 5天前
8

0

mark
comewhere 1 2天前
9

0

mark
返回