Andoroid 逆向分析语法简析

主流的DEX文件反汇编工具

目前反编译DEX文件的工具有BakSmail与Dedexer。两个反编译效果都不粗,虽然反编译出来的语句语法有点区别,但是相似度还是比较高的,

Dalvik虚拟机与Java虚拟机的区别

  • Java程序经过编译,生成java字节码保存在class文件中,jvm通过解码class文件运行。
  • Dalvik运行的是有java字节码转换过来的dalvik字节码,并将其打包到一个DEX(Delvik Executable)可执行文件中,Dalvik通过解释DEX文件来执行这些字节码。
  • jvm基于栈架构,dalvik基于寄存器架构
  • AndoridSDK中有个dx工具负责将java字节码转换为dalvik字节码,dx工具对java类文件重新排列,消除所有冗余信息,比如讲java类文件中常量池进行分解,消除冗余,重组成新的常量池,所有类文件共享它。

Dalvik 65536

  • 因为Dalvik是基于寄存器架构,代码中大量使用到了寄存器,同时用到的寄存器是32位,64位类型用两个相邻的寄存器表示,而Dalvik最多能支持2的16次方即65535个寄存器,采用v0作为起始值,范围v0-v65536,在Android系统中,一个Dex文件中存储方法id用的是short类型数据,所以导致你的dex中方法不能超过65k。

Dalvik字节码类型描述符

语法 含义
V Void,只用于返回值类型
Z boolean
B byte
S short
C char
I int
J long
F float
D double
L java类类型
[ 数组类型

Dalvik方法描述

  • 方法格式:Lpackage/name/ObjectName;->MethodName()Z
  • 方法代码以.method开始,以end.method结束,根据方法的不同,在方法指令前面可能有出现#号加以注释,如#virtual method代表一个虚方法,#direct method表示这个是直接方法。

寄存器表示方法(V P 命名法)

  • V P 命名法,是Dalvik字节码中两种不同的寄存器表示方法。
  • v命名,采用小写字母”v”开头表示函数中永达偶的局部变量与参数,所有的寄存器命名从v0开始,依次递增。
  • p命名,对函数局部变量寄存器命名无影响,命名规则为:函数中引入的参数命名从p0开始,依次递增。
  • 假设一个函数使用到M个寄存器,并且函数由N个参数,根据Dalvik虚拟机参数传递的规定:参数使用最后的N个寄存器,局部变量使用从v0开始的前M-N个寄存器。举个栗子

    # virtual methods
    .method public foo(II)I
      .register 5
      .parameter
      .parameter
      .prologue
      .line 3
      add-int v0, p1, p2
      sub-int v1, p1, p2
      mul-int/2addr v0, v1
      return v0
    .end mehtod
    
  • foo()函数使用了5个寄存器,2个显式的整形参数,方法本身是类的非静态方法,函数被调用时会传入一个隐式的类对象引用,因此实际传入的参数是3个,根据传参规则,局部变量将使用前2个寄存器,参数会使用后三个寄存器。

Dalvik指令集

  • Dalvik大多数指令用寄存器作为目的操作数或源操作数,一种A/B/C/D/E/F/G/H代表一个4位的数值,用老标识0~15的数值或者v0~v15的寄存器,而AA/BB/CC/DD/EE/FF/GG/HH代表一个8位的数值,用来标识0~255的数值或者v0~v255的寄存器,AAAA/BBBB……代表一个16位的数值0~65533或者v0~v65533的寄存器。

方法调用指令集

  • 方法调用指令负责调用类实例的方法,基础指令为invoke,方法调用指令有”invoke-kind{vC,vD,vE,vF,vG},meth@bbbb”与”invoke-kind/range{vCCCC..vNNNN},meth@BBBB”两类,两类指令在作用上一样,只是后者在设置参数寄存器时使用了range来指定寄存器的范围。根据方法类型的不同,一共有5条方法调用指令。

  • “invoke-virtual”或”invoke-virtual/range” :调用实例的虚方法 “invoke-super”或”invoke-super/range” :调用实例的父类方法 “invoke-direct”或”invoke-direct/range” :调用实例的直接方法 “invoke-static”或”invoke-static/range” :调用实例的静态方法 “invoke-interface”或”invoke-interface/range” :调用实例的接口方法

  • 在Android4.0版本后,Dalvik指令集增加了 “invoke-kind/jumbo{vCCCC..vNNNN},meth@BBBBBBBB” 这种类型的指令,作用一样,只是使用jumbo字节码后缀,并且寄存器与指令的索引范围更大。

  • 方法调用指令的返回值类型必须使用move-result*指令来获取,如:

invoke-state {}, Landroid/os/Parcel:->obtain{}Landroid/os/Parcel;
move-result-object v0

空操作指令

  • 空操作指令助记符位nop,它的值位00,通常此指令被用来作为对齐代码使用,没实际操作。

    数据操作指令

  • 数据操作指令为move,根据字节码的大小与类型不同,后面会跟上不同的后缀 move vA,vB 将vB寄存器的值赋给vA集群器,源寄存器与目的寄存器都位4位 move wide vA vB 为4位寄存器赋值 move/from16 vAA,vBBBB 将vBBBB寄存器的值赋给vAA寄存器,源寄存器为16位,目的寄存器为8位 move-object vA,vB 为对象赋值,源寄存器和目的寄存器都为4位 move-exception vAA 保存一个运行时发生的异常到vAA寄存器

    返回指令

  • 返回指令指的是函数结尾时运行的最后一条指令,基础字节码位return,共有四条返回指令 return-void 表示函数由一个void方法返回 return vAA 表示函数返回一个32位非对象类型的值,返回值为8位寄存器vAA return wide vAA表示函数返回一个64位非对象类型的值,返回值为8位寄存器vAA return-object vAA表示函数返回一个对象类型的值,返回值为8位急促器vAA

    数据定义指令

  • 数据定义指令用来定义常量、字符串、类等数据,基础字节码位const const/4 vA,#+B 将数值符号扩展为32位后赋值给寄存器vA const/16 vAA, #+BBBB 将数值符号扩展为32位后赋值vAA const-string vAA, string@BBBB通过字符串索引获取一个字符串并赋给寄存器vAA const-class vAA, type@BBBB 通过类型索引获取一个类引用并赋值给寄存器vAA

    跳转指令

  • 跳转指令用于从当前地址跳转到指定的偏移处,有三种跳转指令无条件跳转(goto)、分支跳转(switch)、条件跳转(if)。 goto +AA 无条件跳转到指定偏移处,偏移量AA不能为0。

  • if-test- vA,vB,+CCCC条件跳转指令,比较vA寄存器和vB寄存器的值,如果比较结果满足则跳转到CCCC指定的偏移处,CCCC不能为0,if-test类型指令有以下几条 if-eq 如果vA等于vB则跳转,对应java语法为if(vA == vB). if-ne 如果vA不等于vB则跳转,对应java语法为if(vA != vB) if-lt 如果vA小于vB则跳转……. if-le 如果小于等于 if-ge 如果大于等于 if-gt 如果大于

  • if=testz -vAA,+vBBBB条件跳转指令,拿vAA寄存器与0比较,如果比较结果满足或值为0就跳转到BBBB指定的偏移处

实例参考

// 空指令
nop
nop
nop
//数据定义指令
const/16 v0, 0x8
const/4 v1, 0x5
const/4 v2, 0x3
//数据操作指令
move v1 , v2
//数组操作指令
new-array v0, v0, [I
array-length v1, v0
//实例操作指令
new-instance v1, Ljava/lang/StringBuilder;-><init>{}V
//跳转指令
if-nez v0, :cond_0
goto : goto_0
:cond_0
//数据转换指令
int-to-float v2, v2
//数据运算指令
add-float v2, v2, v2
//比较指令
compl-float v0, v2, v2
//字段操作指令
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrantStream;
//构造字符串
const-string v1, "Hello World"
//方法调用指令
invoke-virtual{v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
//返回指令
:goto_0
return-void