0%

Android逆向基础

概述

Android是基于Linux内核的操作系统,大多是是基于ARM架构的,于是要熟悉一下ARM相关内容。

Android开发环境的安装

安装JDK、NDK、SDK,,并配置好环境变量。

模拟器的选择

可以使用SDK自带的模拟器(最推荐,虽然慢但是出问题的概率最小),也可使用其他模拟器,如雷电、逍遥等。由于是x86架构,所以在进行调试时要使用android_x86_server

adb的常见命令

  • adb devices

  • adb shell

  • adb -s 设备名 shell

  • adb install

  • adb push

  • adb pull

Apk文件

apk 文件其实是一个zip文件,只是后缀改变了而已。网上找了个结构图如下:

  • AndroidManifest.xml 文件:Android工程的基础配置属性文件,定义了整个应用的运行信息,包括:包名、权限、API 版本、组件等等信息

  • classes.dex 文件:Java代码编译得到的Dalvik VM能直接执行的文件,包含了应用的整个代码逻辑

  • lib 目录:so库存放位置,一般由NDK编译得到,常见于使用游戏弓|擎或JNI native调用的工程中,包含了应用需要的原生库文件,其主要是从c/c++代码编译而来

    • armeabi/armeabi-v7a 目录:so库文件分为不同的CPU架构
  • META-INF 目录:存放工程一些属性文件,例如Manifest.MF,如存储了应用的证书和签名信息文件

  • res 目录:资源目录,包含不能打包进 resources.arsc的一些资源文件,如图片、布局信息等

    • drawable 目录:图片和对应的xml资源
    • layout 目录:定义布局的xml资源
  • resources.arsc 文件:对res目录下的资源的一个索引文件,保存了原工程中strings.xmI等文件内容

  • asset(此网图例没有):也是资源目录,但与res有所区别(具体相同、不同如下)

    • 相同:两者目录下的文件在打包后会原封不动的保存在apk包中,不会被编译成二进制

    • 不同:

      • res/raw中的文件会被映射到R.java文件中,访问的时候直接使用资源ID即R.id.filename;assets文件夹下的文件不会被映射到R.java中,访问的时候需要AssetManager类
      • res/raw不可以有目录结构,而assets则可以有目录结构,也就是assets目录下可以再建立文件夹
      • assets文件夹是存放不进行编译加工的原生文件,即该文件夹里面的文件不会像xml,java文件被预编译,可以存放一些图片,html,js,css等文件

Dalvik

Dalvik是google专门为Android操作系统设计的一个虚拟机

虽然Android上的程序是使用java来开发的,但是Dalvik和标准的java虚拟机(JVM)还是两回事

  • Dalvik VM是基于寄存器的,而JVM是基于栈的
  • Dalvik有专属的文件执行格式dex(dalvik executable),而JVM则执行的是java字节码
  • Dalvik VM比JVM速度更快,占用空间更少

通过Dalvik的字节码我们不能直接看到原来的逻辑代码,这时需要借助如Apktooljadx等工具来帮助查看。但是,注意的是最终我们修改APK需要操作的文件是.smali文件,而不是将导出来的Java文件重新编译。

ART

ART虚拟机是Android4.4发布的,用来替换Dalvik虚拟机的,在Android 5.0时,默认采用ART虚拟机。

在ART中,系统在安装应用时会进行一次预编译,将字节码预先编译成机器码并存储在本地,而不是像Dalvik每次运行都需要通过即时编译器生成字节码,从而提高了运行的效率。但也因此,ART虚拟机因为需要存储字节码从而占用空间会比较大。

Smali

Smali是用于Dalvik的反汇编程序实现,汇编工具(将Smali代码汇编为dex文件)为smali.jar,与之对应的baksmali.jar则是反汇编程序。其语法是Jasmin/dedexer语法

Smali支持注解、调试信息、线路信息、行数信息等基本Java的基本特性

在Smali中,如果需要存储变量,必须先声明足够数量的寄存器,1个寄存器可以存储32位长度的类型,64位类型(Long/Double)用2个寄存器表示。本地寄存器用v开头数字结尾的符号来表示,参数寄存器则使用p开头数字结尾的符号来表示。特别注意的是,p0不一定是函数中的第一个参数,在非static函数中,p0代指“this”,p1表示函数的第一个参数,p2代表函数中的第二个参数;而在static函数中p0才对应第一个 参数,因为Java的static方法中没有this方法。

基本类型:

  • V :void,只能用于返回类型
  • Z :boolean
  • B :byte
  • S :short
  • C :char
  • I :int
  • F :float
  • J :long (64 bits)
  • D :double (64 bits)
  • [xxx(I/D…) : array
  • Lxxx/yyy(package/objectName); : object

对于最后一个,例如String对象在smali中为:Ljava/lang/String;,其中java/lang对应java.lang包,String就是定义在该包中的一个对象

Android启动流程

网上总结的很全,见这儿

Dalvik指令集

详见官方文档博客

  • 基本:
指令 说明
.field private isFlag:z 定义变量
.method 方法
.parameter 方法参数
.prologue 方法开始
.line 123 此方法位于第123行
invoke-super 调用父函数
const/high16 v0,0x7fo3 把0x7fo3赋值给v0
invoke-direct 调用函数
return-void 函数返回void
.end method 函数结束
new-instance 创建实例
iput-object 对象赋值
iget-object 调用对象
invoke-static 调用静态函数
  • 移位操作:
指令 说明
move v1,v2 将v2中的值移入到v1寄存器中(4位,支持int型)
move/from16 v1,v2 将16位的v2寄存器中的值移入到8位的v1寄存器中
move/16 v1,v2 将16位的v2寄存器中的值移入到16位的v1寄存器中
move-wide v1,v2 将寄存器对(一组,用于支持双字型)v2中的值移入到v1寄存器对中(4位,猜测支持float、double型)
move-wide/from16 v1,v2 将16位的v2寄存器对(一组)中的值移入到8位的v1寄存器中
move-wide/16 v1,v2 将16位的v2寄存器对(一组)中的值移入到16位的v1寄存器中
move-object v1,v2 将v2中的对象指针移入到v1寄存器中
move-object/from16 v1,v2 将16位的v2寄存器中的对象指针移入到v1(8位)寄存器中
move-object/16 v1,v2 将16位的v2寄存器中的对象指针移入到v1(16位)寄存器中
move-result v1 将这个指令的上一条指令计算结果,移入到v1寄存器中(需要配合invoke-static、invoke-virtual等指令使用)
move-result-object v1 将上条计算结果的对象指针移入v1寄存器
move-result-wide v1 将上条计算结果(双字)的对象指针移入v1寄存器
move-exception v1 将异常移入v1寄存器,用于捕获try-catch语句中的异常
  • 返回操作:
指令 说明
return-void 返回void,即直接返回
return v1 返回v1寄存器中的值
return-object v1 返回v1寄存器中的对象指针
return-wide v1 返回双字型结果给v1寄存器
  • 常量操作:
指令 说明
const(/4、/16、/hight16) v1 xxx 将常量xxx赋值给v1寄存器,/后的类型,需要根据xxx的长度选择
const-wide(/16、/32、/hight16) v1 xxx 将双字型常量xxx赋值给v1寄存器,/后的类型,需要根据xxx的长度选择
const-string(/jumbo) v1 “aaa” 将字符串常量”aaa”赋给v1寄存器,过长时需要加上jumbo
const-class v1 La/b/TargetClass 将Class常量a.b.TargetClass赋值给v1,等价于a.b.TargetClass.class
  • 调用操作:
指令 说明
invoke-virtual 用于调用一般的,非private、非static、非final、非构造函数的方法,它的第一个参数往往会传p0,也就是this指针
invoke-super 用于调用父类中的方法,其他和invoke-virtual保持一致
invoke-direct 用于调用private修饰的方法,或者构造方法
invoke-static 用于调用静态方法,比如一些工具类
invoke-interface 用于调用interface中的方法
  • 判断操作:
指令 说明
if-eq v1,v2 判断两个寄存器中的值是否相等
if-ne v1,v2 判断两个寄存器中的值是否不相等
if-lt v1,v2 判断v1寄存器中的值是否小于v2寄存器中的值(lt == less than)
if-ge v1,v2 判断v1寄存器中的值是否大于或等于v2寄存器中的值(ge == great than or equals)
if-gt v1,v2 判断v1寄存器中的值是否大于v2寄存器中的值(gt == great than)
if-le v1,v2 判断v1寄存器中的值是否小于或等于v2寄存器中的值(le == less than or equals)
  • 属性操作:
指令 说明
iget 取值,用于操作int这种的值类型
iget-wide 取值,用于操作wide型字段
iget-object 取值,用于操作对象引用
iget-boolean 取值,用于操作布尔类型
iget-byte 取值,用于操作字节类型
iget-char 取值,用于操作字符类型
iget-short 取值,用于操作short类型
iput 赋值,用于操作int这种的值类型
iput-wide 赋值,用于操作wide型字段
iput-object 赋值,用于操作对象引用
iput-boolean 赋值,用于操作布尔类型
iput-byte 赋值,用于操作字节类型
iput-char 赋值,用于操作字符类型
iput-short 赋值,用于操作short类型
  • 其他指令:
指令 说明
add-int/lit8 v1, v2, 0x1 给v2寄存器+1,并存入v1寄存器(注意:lit8是对要加的常量的长度限制,如果不写,则为4位,还可选择lit16,即16位)
add-int/2addr v1, v2 将v1、v2寄存器中的值相加,并赋值给v1寄存器
float-to-int v1, v2 将v2寄存器中的float类型值转换为int类型,并赋值给v1寄存器

问题汇总

  • adb连接雷电模拟器时offline问题:用Android SDK中的adb.exe替换掉雷电模拟器自带的adb.exe即可解决

  • Android远程调试:与Linux远程调试类似,但需要进行用adb forward tcp:23946 tcp:23946端口转发

  • 运行adb devices时出现未知设备offline:运行adb kill-server adb start-server adb remount