彻底弄懂dalvik字节码【三】0x01:0x02:0x03:0x04:0x05:

【一】【二】中从代码的角度分析了dalvik字节码解释执行的过程,这篇文章以一个例子来实际分析一下。

我们以这篇文章中提到的crackme为例,下载链接参见那篇文章。我们只分析dalvik字节码,因此忽略so。

0x01:

使用Jeb打开crackme.apk,找到MainActivity的onCreate方法,其smali内容是:

.method protected onCreate(Bundle)V
          .registers 5
          .param p1, "savedInstanceState"
          .prologue
00000000  invoke-super            AppCompatActivity->onCreate(Bundle)V, p0, p1
00000006  const                   v2, 0x7F040019
0000000C  invoke-virtual          MainActivity->setContentView(I)V, p0, v2
00000012  const                   v2, 0x7F0C0050
00000018  invoke-virtual          MainActivity->findViewById(I)View, p0, v2
0000001E  move-result-object      v1
00000020  check-cast              v1, EditText
          .local v1, txt:Landroid/widget/EditText;
00000024  const                   v2, 0x7F0C0051
0000002A  invoke-virtual          MainActivity->findViewById(I)View, p0, v2
00000030  move-result-object      v0
00000032  check-cast              v0, Button
          .local v0, btn:Landroid/widget/Button;
00000036  sget-boolean            v2, MainActivity->$assertionsDisabled:Z
0000003A  if-nez                  v2, :4E
:3E
0000003E  if-nez                  v0, :4E
:42
00000042  new-instance            v2, AssertionError
00000046  invoke-direct           AssertionError-><init>()V, v2
0000004C  throw                   v2
:4E
0000004E  new-instance            v2, MainActivity$1
00000052  invoke-direct           MainActivity$1-><init>(MainActivity, EditText)V, v2, p0, v1
00000058  invoke-virtual          Button->setOnClickListener(View$OnClickListener)V, v0, v2
0000005E  return-void
.end method

smali 对于Android,可以理解为汇编对于C。smali中定义了一套完整的dalvik操作码(类似于汇编的指令集),构成了dalvik虚拟机最核心的部分。

字节码是二进制的,这些二进制通过一定的方式可以被解释成为smali指令。我们来看看这个过程。

0x02:

使用010editor打开crackme.apk中的classes.dex,应用dex模板,结果如下:

dex文件格式参见这篇文章

我们分析的目标是MainActivity的onCreate方法,直接找到它:

因为onCreate是重写的父类方法,所以在virtual_methods中,我们看到这个方法需要5个寄存器,2个参数,3个内部方法调用参数,48条指令。字节码在insns中。

0x03:

下面进入对字节码的分析,在【二】中分析方法执行时, dvmInterpretPortable的最后一个语句:

FINISH(0); /* fetch and execute first instruction */

即为定位到insns的起始处,并取2个字节的内容放置到inst变量中,之后取出inst的低字节数值作为handlerTable数组的索引号,然后跳转到对应符号去执行。

代码上的跟踪比较繁琐,好在google有相关的文档:【Dalvik bytecode】【Dalvik Executable instruction formats】,我们根据文档来分析。

在这个例子中,第一个“两字节”是:6F 20, 其低字节是6F,代表着handlerTable数组的索引号,查询文档:

表示操作符是invoke-super

查看OP_INVOKE_SUPER.cpp,里面有对此指令的执行过程:

HANDLE_OPCODE(OP_INVOKE_SUPER /*vB, {vD, vE, vF, vG, vA}, meth@CCCC*/)
    GOTO_invoke(invokeSuper, false);
OP_END

其中:

# define HANDLE_OPCODE(_op) op_##_op:
OP_INVOKE_SUPER                 = 0x6f,
#define GOTO_invoke(_target, _methodCallRange)                              \
    do {                                                                    \
        methodCallRange = _methodCallRange;                                 \
        goto _target;                                                       \
    } while(false)

翻译一下就是:

op_0x6f:
  do{
      methodCallRange = false;
      goto invokeSuper;
  }while(false)

invokeSuper在gotoTargets.cpp中定义:

GOTO_TARGET(invokeSuper, bool methodCallRange)
    {
        Method* baseMethod;
        u2 thisReg;

        EXPORT_PC();

        vsrc1 = INST_AA(inst);      /* AA (count) or BA (count + arg 5) */
        ref = FETCH(1);             /* method ref */
        vdst = FETCH(2);            /* 4 regs -or- first reg */

        if (methodCallRange) {
            ILOGV("|invoke-super-range args=%d @0x%04x {regs=v%d-v%d}",
                vsrc1, ref, vdst, vdst+vsrc1-1);
            thisReg = vdst;
        } else {
            ILOGV("|invoke-super args=%d @0x%04x {regs=0x%04x %x}",
                vsrc1 >> 4, ref, vdst, vsrc1 & 0x0f);
            thisReg = vdst & 0x0f;
        }
    
      ... //此处省略后后面的内容

其中:

#define GOTO_TARGET(_target, ...) _target:

#define GOTO_TARGET_END

翻译一下就是:

invokeSuper:
    {
        Method* baseMethod;
        u2 thisReg;

        EXPORT_PC();

        vsrc1 = INST_AA(inst);      /* AA (count) or BA (count + arg 5) */
        ref = FETCH(1);             /* method ref */
        vdst = FETCH(2);            /* 4 regs -or- first reg */

        if (methodCallRange) {
            ILOGV("|invoke-super-range args=%d @0x%04x {regs=v%d-v%d}",
                vsrc1, ref, vdst, vdst+vsrc1-1);
            thisReg = vdst;
        } else {
            ILOGV("|invoke-super args=%d @0x%04x {regs=0x%04x %x}",
                vsrc1 >> 4, ref, vdst, vsrc1 & 0x0f);
            thisReg = vdst & 0x0f;
        }
    
      ... //此处省略后后面的内容

这样就找到了invokeSuper真正执行的地方了,可以看到,依然是解析字节码和获取新字节码解析的过程。代码跟踪同样比较复杂,我们直接看文档:

从图中可以看出6f的格式是35c(后面会用到),语法格式为:

invoke-*kind*{vC, vD, vE, vF, vG}, meth@BBBB

其中的A、B、C的解释在后面一列有说明,光看这个还不能看懂,看一下35c:

这个文档我一开始也看得不是太懂,简单的还能对应起来,复杂的(现在这个例子)就搞不清楚了,文档只能理解个大概,最后我选择看代码。这里直接说我看代码看明白的:

首先 6F 20 中的 6F表示操作码,20又分两个4位来解释,2表示寄存器的数量,0代表啥还没有看明白:(,随后的两个字节 47 2A 表示的是method id,47 2A 表示数值是0x2A47,即10823,如图:

所以我们大概明白了:

invoke-super            AppCompatActivity->onCreate(Bundle)V

随后的两个字节 43 00 也是需要被解析的,是用来确定寄存器标号的,至于怎么映射为 p0 p1,我也没有看懂(代码在gotoTargets.cpp的GOTO_TARGET(invokeSuper, bool methodCallRange)中,欢迎有兴趣的同学继续研究并加微信交流)。

0x04:

至此,我们分析完第一条指令的字节码解释过程了。在源码中,你可以看到,它不光是解释成smali这么简单,它真正的去寻找父类的onCreate方法,构造函数堆栈并进行调用。 pc指针随着执行过程不断往后移动,当方法返回后,继续去下一个“两字节”进行解释执行。这个例子中的下一个“两字节"是14 02,其中14为操作码,如图:

可以看到是const vAA, #+BBBBBBBB,和我们在Jeb中看到的const v2, 0x7F040019能够对上。后续的过程和前面完全一致,只是处理的是不同的操作码而已。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏JavaQ

深入理解Spring系列之八:常用的扩展接口

Spring不仅提供了一个进行快速开发的基础框架,而且还提供了很多可扩展的接口,用于满足一些额外的开发需求,本篇将对常用的可扩展接口进行归纳总结。 1.Ini...

382100
来自专栏程序猿DD

Spring框架中的设计模式(五)

通过以前的4篇文章,我们看到Spring采用了大量的关于创建和结构方面的设计模式。本文将描述属于行为方面的两种设计模式:命令和访问者。 前传: Spring框架...

50070
来自专栏微服务生态

简单比较init-method,afterPropertiesSet和BeanPostProcessor

1、init-method方法,初始化bean的时候执行,可以针对某个具体的bean进行配置。init-method需要在applicationContext....

12620
来自专栏向治洪

[置顶] 浅谈我为什么选择用Retrofit作为我的网络请求框架

比较AsyncTask、Volley、Retrofit三者的请求时间 使用 单次请求 7个请求 25个请求 AsyncTask 94...

23550
来自专栏Java与Android技术栈

基于Kotlin的委托机制实现一个对Extra、SharedPreferences操作的库

本文介绍的库,github地址:https://github.com/fengzhizi715/SAF-Object-Delegate

15530
来自专栏Android开发与分享

【Android】DataBinding库(MVVM设计模式)

49470
来自专栏技术小黑屋

如何在Android中避免创建不必要的对象

在编程开发中,内存的占用是我们经常要面对的现实,通常的内存调优的方向就是尽量减少内存的占用。这其中避免创建不必要的对象是一项重要的方面。

7720
来自专栏向治洪

android自定义属性

1、引言 对于自定义属性,大家肯定都不陌生,遵循以下几步,就可以实现: 自定义一个CustomView(extends View )类 编写values/a...

212100
来自专栏逍遥剑客的游戏开发

OGRE中用到的设计模式

23570
来自专栏移动开发之家

通用RecylerAdapter,内置XRecyclerView,兼容上下拉与动画,高复用,一个Adapter通用所有页面,支持空页面,懒人专属

这是欢迎各位践踏的Github:https://github.com/CarGuo

10950

扫码关注云+社区

领取腾讯云代金券