新手上路 
  
 
- 资源币
 - 19 
 
	- 积分
 - 16
 
- 贡献
 - 0 
 
- 在线时间
 - 2 小时
 
- 注册时间
 - 2020-2-20
 
- 最后登录
 - 2020-5-2
 
 
 
 
 | 
 
零基础安卓逆向学习之旅(六-下) 
 
解释Dalvik字节码  
 
下载,配置smali反编译器  
 
1.下载工具baksmali的相关文件 
 
 访问以下链接: 
 
https://bitbucket.org/JesusFreke/smali/downloads 
 
 
 
 
 
 
下载baksmali[version].jar文件的最新版本及baksmali脚本,并保存在同一目录下。 
 
2.配置baksmali工具 
 
    将下载下来的baksmali的jar文件改名为baksmali.jar 
 
    mv baksmali-[version-number].jar baksmali.jar 
 
    确认baksmali脚本拥有可执行权限 
 
    chmod +x 700 baksmali 
 
    运用baksmali反编译.dex文件 
 
    baksmali[Dex filename].dex 
 
    并在生成的out目录下查看反编译后的.smali文件 
 
 
 
 
 
解释字节码  
 
首先是开头几行代码 
 
.class public LExample; 
 
.super Ljava/lang/Object; 
 
.source "Example.java" 
 
这是被编译后类的元数据,给出了类名,父类以及源文件,由于所有的java类都是继承自java.lang.Object的,所以即使在Example.java的代码中,我们没有显式声明继承这个类,编译后也会显示出来。  
 
接着是Example.java的构造函数的smali代码 
 
#direct methods 
 
.method public constructor <init>()V 
 
    .registers 1 
 
    .prologue 
 
    .line 1 
 
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V  
 
    return-void 
 
.end method  
 
.method public constructor <init>()V  //这个方法的声明 
 
即这个方法名为init,返回一个void类型,访问标志位是public。  
 
.registers1 
 
这个方法只使用一个寄存器,一个方法在运行前都需要确定所需寄存器的数量。  
 
.prologue 
 
声明接下来是一个prologue方法,这是每个java方法都有的,它保证这个方法是以继承形式被调用的,这就是在下边一行调用了另一个名为init的方法,这是java.lang.Object的init方法。  
 
invoke-direct{p0}, Ljava/lang/Object;-><init>()V 
 
invoke-direct指令需要两个参数:p0寄存器和被调用方法的指针。 
 
invoke-direct是用来调用一个非静态直接方法(non-static direct method),一个不可重写的,private实例方法或构造方法。 
 
上边代码即是调用java.lang.Object类的构造方法这个非静态直接方法。  
 
return-void   返回一个void类型,并退出当前函数。 
 
.endmethod  标志着这个方法的结束。 
 
    接下来则是main方法的smali代码: 
 
.method public static main([Ljava/lang/String;)V 
 
    .registers 4  
 
    .prologue 
 
    .line 3 
 
    sget-object v0,Ljava/lang/System;->out java/io/PrintStream;  
 
    const-string v1, "Hello World!\n"  
 
    const/4 v2, 0x0  
 
    new-array v2, v2, [Ljava/lang/Object;  
 
    invoke-virtual {v0, v1, v2},Ljava/io/PrintStream;->printf(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;  
 
    .line 4 
 
    return-void 
 
.end method  
 
第一行:.method public static main([Ljava/lang/String;)V 
 
这个方法接收一个java.lang.String类型的数组,返回void类型,且这方法是静态方法,访问属性为public。  
 
sget-object操作:sget-object v0, Ljava/lang/System; 
 
->out java/io/PrintStream; 
 
官网描述 erform the identified object static field operation withthe identified static field, loading or storing into the value register. 
 
执行获取对象静态成员的操作,找静态成员,将其加载/存储到存放值的寄存器中 
 
sget-object接收两个参数: 
 
一个寄存器,用于存储操作的结果 
 
一个是将被存储到上边的寄存器的对象的引用 
 
即这行代码是获取一个对象实例,并将其存放到寄存器中,v0是这方法的栈帧里的第一个寄存器。  
 
const-string指令:const-string v1, "Hello World!\n" 
 
获取一个字符串并将其存储在第一参数指定的寄存器中。  
 
const/4v2, 0x0 
 
将常数0放入第三个寄存器v2中。  
 
new-arrayv2, v2, [Ljava/lang/Object; 
 
new-array指令是构造一个指定类型和元素个数的数组,并将它存放在左起第一个寄存器中,这指令执行后,v2寄存器中存放的应是一个元素个数为0,java.lang.Object类型的数组.上一行const/4v2, 0x0先将0放入v2的原因。  
 
最后是非常常见的指令 : 
 
invoke-virtual{v0, v1, v2},Ljava/io/PrintStream;->printf(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;  
 
invoke-virtual指令的官网定义"invoke-virtual is used to invoke a normal virtualmethod (a method that is not private, static, or final, and is also not a constructor)." 
 
invoke-virtual用来调用一个普通的虚方法(方法属性不为private,static或final,且不是构造方法) 
 
其参数格式: 
 
invoke-kind{vC, vD, vE, vF, vG}, meth@BBBB 
 
其中,vC,vD,vE,vF,vG是用来向被调用的方法传递参数的寄存器,方法的具体代码由参数meth@BBBB指定,每个B表示4bit,即这个指令接收一个16位的方法引用 
 
所以实例中的代码,是调用一个名为java.io.PrintStream.printf方法,接收参数:java.lang.String对象和java.lang.Object类型的数组,最后返回java.io.PrintStream类型的对象 。 
 
将DEX反编译为java  
 
1.下载相关工具 
 
Dex2Jarhttps://sourceforge.net/projects/dex2jar/?source=typ_redirect下载最新.zip包 
 
JD-GUI:访问http://jd.benow.ca/  下载最新.jar。  
 
2.运用Dex2Jar将.dex文件转成.jar文件 
 
    [path-of-Dex2Jar]/dex2jar.shExample.dex   //生成Example_dex2jar.jar文件 
 
 
 
 
 
 
3.在JD-GUI上打开上边生成的.jar文件,查看java源代码 
 
java-jar jd-gui-[version]  //打开JD-GUI    点击File->Open。 
 
 
 
 
选择上边的.jar文件打开,查看java源代码。 
 
 
 
反编译app的原生库  
 
1.获取Android原生库文件(.so)。 
 
Sieve – A password manager app, showcasing some common Android vulnerabilities at 
 
https://www.mwrinfosecurity.com/system/assets/380/original/sieve.apk 
 
下载实例apk文件,将文件改名为sieve.zip,并解压为sieve,在sieve/lib/armeabi/*.so路径下便可找到Android原生库文件。  
 
2.确认工具objdump在AndroidNDK中路径 
 
[android-ndk-path]/toolchains/arm-linux-androideabi-[version]/prebuilt/[arch]/bin/在该目录下找到objdump工具:./arm-linux-androideabi-objdump。 
 
 
 
 
 
 
 
3.反编译原生库 
 
arm-linux-androideabi-objdump–D [native library].so 
 
 
 
 
 
使用GDB server调试Android进程  
 
1.将Android NDK下的gdbserver文件拷贝到设备上  
 
这需要一部已经root的Android设备或一台Android模拟器。 
 
emulator-avd [avd name]  //启动AVD 
 
adbshell  //进入设备shell 
 
重新挂载系统目录,以读-写权限mount系统目录/system  
 
mount  //查看当前各个块设备的挂载信息,确定/system的挂载信息(mount | grep system) 
 
 
 
 
mount -o rw,remount [device] /system  //重新挂载/system,改为读-写 
 
 
 
 
 
gdbserver位于[NDK-path]/prebuit/android-arm64/gdbserver/gdbserver 
 
pushgdbserver /system/bin/.   //将gdbserver拷贝到设备上 
 
 
 
 
2.运用gdbserver抓取进程 
 
ps  //查看设备正在运行的进程信息 
 
 
 
 
 
选取com.android.email进程作为实验进程,其PID为1009 
 
 
 
gdbserver:[tcp-port number] –attach [PID]  //抓取进程,连接TCP端口 
 
gdbserver:31337 –attach 1009  
 
3.在本地主机上运行gdb进行调试  
 
adb forward tcp:[device port-number] tcp:[local port-number]  //端口交互  
 
gdb位于[NDK-path]/prebuilt/[system]/bin/gdb 
 
运行gdb 
 
 
 
 
进入gdb后,运行下边命令,调试目标进程 
 
target remote :[PID]  //[PID]为本机TCP交互端口 
 
 
 
 
 
现在便可与运行在Android设备上的进程中的内存段和寄存器进行交互了。。。。。。 
 
 
 
 
 
 |   
 
 
 
 |