
工具
环境
Jdk8u202
http://www.codebaoku.com/jdk/jdk-index.html
选择 windows 64位
安卓sdk
调试器
DDMS
后面看log用
要下载安卓sdk
逆向工具
爱盘工具下载
逆向IDE
Android Killer , JEB3
JEB3
Android Killer
最常使用
打log十分方便
坑下载替换好最新的Apktool
反编译工具
Apktool
jdgui (java)
转换工具
Jadx
Dex2jar
查壳工具
安卓开发者助手
pkid
so逆向工具
IDA
前置知识
Android 应用目录结构
这里使用MT管理器查看目录结构
lib 安卓依赖库目录
META-INF 里面有安装脚本 及配置
res是布局文件
manifest
每个安卓应用程序必须有一个AndroidManifest.xml文件,在app/manifests目录中。它在简单的Android系统的应用中提出了重要的信息,信息系统 [1] 必须具备之前,它可以运行任何应用程序的代码。除其他事项外,清单中执行下列操作:
它给应用程序的Java包命名,包的名称作为应用程序的唯一标识符。
它描述了应用程序的组件的活动、服务,广播接收机,内容提供商,应用程序组成。它命名的类,实现每个组件,并出版自己的能力(例如,可以处理哪些意图的消息)。这些声明让Android系统知道这些组件是什么和在什么条件下,他们可以推出。
它决定哪些进程将运行应用程序组件。
它决定了应用程序必须有权限才能访问受保护的API部分,并与其他应用程序进行交互。
它还决定了其他人与应用程序的组件交互所需要的权限。
它宣布了Android API的应用程序需要的最低水平。
它列出了库,应用程序必须与之配对。
classes.dex
虚拟机使用的字节码文件
其中包含 APK 的可执行代码,是分析 Android 软件时最常见的目标
反汇编安卓应用
使用Apktool 反编译安卓应用
命令
apktool d test.apk
NDK
安卓调用系统底层库桥梁
Smali
借用 https://www.jianshu.com/p/9931a1e77066
Smali是Android虚拟机的反汇编语言。
语法
Opcode name | 说明 | 案例 |
---|---|---|
nop | 无操作 | 0000 - nop |
move vx,vy | 将vy赋值给vx 256寄存器 | |
Move/from16 vx,vy | 将vy(int)赋值给vx 当vy是64k寄存器 | |
move/16 | ||
move-wide | ||
move-wide/from16 vx,vy | 将vy(long,double)赋值给vx 当vy是64k寄存器 | |
move-object vx,vy | 将vy(值对象)赋值给vx | |
move-object/from16 vx,vy | 将vy(值对象)赋值给vx 当vy是64k寄存器 | |
move-object/16 | ||
move-result vx | 将前一个调用方法的返回值(int)赋值给vx | |
move-result-wide vx | 将前一个调用方法的返回值(double,long)赋值给vx | |
move-result-object vx | 将前一个调用方法的返回值(值对象)赋值给vx | |
move-exception vx | 将方法执行抛出的异常对象地址赋值到 vx | |
return-void | 返回空 | |
return vx | 返回 int | |
return-wide vx | 返回 double/long | |
return-object vx | 返回对象地址 | |
const/4 vx,lit4 | 将长度4位的数值赋值给vx | Const/4 vx, 0x2 |
const/16 vx,lit16 | 将长度16位的数值赋值给vx | Const/16 vx,ox16 |
const vx, lit32 | 将int型数值赋值给vx | Const vx,ox231 |
const-wide/16 vx, lit16 | 将int型数值赋值给vx和vx+1,扩展成long型 | |
const-wide/32 vx, lit32 | 将32位数值赋值给vx和vx+1,扩展成long型 | |
const-wide vx, lit64 | 将64位数值赋值给vx和vx+1,扩展成long型 | |
const/high16 v0, lit16 | 将16位数值赋值到vo寄存器中最高的16位,初始化 | |
const-wide/high16 vx,lit16 | 将16位数值赋值到vx和vx+1中最高的16位,初始化 | |
const-string vx,string_id | 将string常量赋值给vx | |
const-string-jumbo | ||
const-class vx,type_id | 将类的类型的地址赋值给vx | |
monitor-enter vx | 加锁 | |
monitor-exit | 释放锁 | |
Check-cast vx type_id | 检查vx里的对象能不能强转成某个类的对象,否则抛异常 | |
instance-of vx,vy,type_id | 如果vy的对象是指定类的实例则设vx值为非零 | |
array-length vx,vy | 计算array vy的长度并赋值给vx | |
new-instance vx,type | 将指定的类实例化并将地址赋值给vx | |
new-array vx,vy,type_id | 实例化指定类型且长度为vy的array并将地址赋值给vx | |
filled-new-array {parameters},type_id | 实例化指定类型的空间并且将parameter的元素全部填充,但是这种方式不安全。 | |
filled-new-array-range {vx..vy},type_id | 实例化指定类型的空间并且将vx到vy(连续的)的元素全部填充,但是这种方式不安全。 | |
fill-array-data vx,array_data_offset | ||
throw vx | 将异常对象vx抛出 | |
goto target | 无条件跳转 short | |
goto/16 target | 无条件跳转 16位 | |
goto/32 target | 无条件跳转 32位 | |
packed-switch vx,table | 对应switch语句,vx代表case序号(连续) | |
sparse-switch vx,table | 对应switch语句,vx代表case序号(少量) | |
cmpg-float vx, vy, vz | If(vy>vz&&vy>0||vy==vz&&vz<0||vy<vz 赋值到vx | |
cmpl-double vx,vy,vz | 同上(vy和vy+1与vz和vz+1对比) | |
cmpg-double vx, vy, vz | 同上 | |
cmp-long vx, vy, vz | 同上 | |
if-eq vx,vy,target | vx==vy | |
if-ne vx,vy,target | vx!=vy | |
if-lt vx,vy,target | vx<vy | |
if-ge vx, vy,target | Vx>=vy | |
if-gt vx,vy,target | Vx>vy | |
if-le vx,vy,target | Vx<vy | |
if-eqz vx,target | Vx==0 | |
if-nez vx,target | Vx!=0 | |
if-ltz vx,target | Vx<0 | |
if-gez vx,target | Vx>=0 | |
if-gtz vx,target | Vx>0 | |
if-lez vx,target | Vx<=0 | |
aget vx,vy,vz | 将array vy的第vz个(int)值赋值给vx | |
aget-wide vx,vy,vz | 将array vy的第vz个(long,double)值赋值给vx | |
aget-object vx,vy,vz | 将array vy的第vz个(对象地址)值赋值给vx | |
aget-byte vx,vy,vz | ||
aget-char vx, vy,vz | ||
aget-short vx,vy,vz | ||
aput vx,vy,vz | 将vx值赋值给array vy的第vz个(int) | |
aput-wide vx,vy,vz | ||
aput-objectvx,vy,vz | ||
aput-booleanvx,vy,vz | ||
aput- byte,vy,vz | ||
aput-char vx,vy,vz | ||
iget vx, vy, field_id | 将vy对象的指定字段赋值给vx | iget v0, v1, Test2.i6:I |
iget-wide vx,vy,field_id | ||
iget-object vx,vy,field_id | ||
iget-boolean vx,vy,field_id | ||
iget-byte vx,vy,field_id | ||
iget-char vx,vy,field_id | ||
iget-short vx,vy,field_id | ||
iput vx,vy, field_id | 将vx 赋值给vy对象的指定字段 | iput v0,v2, Test2.i6:I |
iput-wide vx,vy, field_id | ||
iput-object vx,vy,field_id | ||
iput-boolean vx,vy, field_id | ||
iput-byte vx,vy,field_id | ||
iput-char vx,vy,field_id | ||
iput-short vx,vy,field_id | ||
sget vx,field_id | 读取指定静态字段到vx | sget v0, Test3.is1:I |
sget-wide vx, field_id | ||
sget-object vx,field_id | ||
sget-boolean vx,field_id | ||
sget-byte vx,field_id | ||
sget-char vx,field_id | ||
sget-short vx,field_id | ||
sput vx, field_id | 将vx赋值给指定的静态字段 | sput v0, Test2.i5:I |
sput-wide vx, field_id | ||
sput-object vx,field_id | ||
sput-boolean vx,field_id | ||
sput-byte vx,field_id | ||
sput-char vx,field_id | ||
sput-short vx,field_id | ||
invoke-virtual { parameters }, methodtocall | ||
invoke-super {parameter},methodtocall | ||
invoke-direct { parameters }, methodtocall | ||
invoke-static {parameters}, methodtocall | ||
invoke-interface {parameters},methodtocall | ||
invoke-virtual/range {vx..vy},methodtocall | ||
invoke-super/range | ||
invoke-direct/range {vx..vy},methodtocall | ||
invoke-static/range {vx..vy},methodtocall | ||
invoke-interface-range | ||
neg-int vx,vy | vx=-vy. | |
not-int vx,vy | ||
neg-long vx,vy | vx,vx+1=-(vy,vy+1) | |
not-long vx,vy | ||
neg-float vx,vy | ||
neg-double vx,vy | ||
int-to-long vx, vy | 强转vy到 vx,vx+1 | |
int-to-float vx, vy | ||
int-to-double vx, vy | ||
long-to-int vx,vy | 强转 vy,vy+1到vx | |
long-to-float vx, vy | ||
long-to-double vx, vy | ||
float-to-int vx, vy | ||
float-to-long vx,vy | ||
float-to-double vx, vy | ||
double-to-int vx, vy | ||
double-to-long vx, vy | ||
double-to-float vx, vy | ||
int-to-byte vx,vy | ||
int-to-char vx,vy | ||
int-to-short vx,vy | ||
add-int vx,vy,vz | Vz加上vy并赋值给vx (int) | |
sub-int vx,vy,vz | 减 | |
mul-int vx, vy, vz | 乘 | |
div-int vx,vy,vz | 除 | |
rem-int vx,vy,vz | 求余 | |
and-int vx, vy, vz | 与 | |
or-int vx, vy, vz | 或 | |
xor-int vx, vy, vz | 异或 | |
shl-int vx, v y, vz | 将vy左移vz位,并将结果存到vx | |
shr-int vx, vy, vz | 将vy右移vz位,并将结果存到vx | |
ushr-int vx, vy, vz | 无符号右移 | |
add-long vx, vy, vz | Vz加上vy并赋值给vx (long) | |
sub-long vx,vy,vz | ||
mul-long vx,vy,vz | ||
div-long vx, vy, vz | ||
rem-long vx,vy,vz | ||
and-long vx, vy, vz | ||
or-long vx, vy, vz | ||
xor-long vx, vy, vz | ||
shl-long vx, vy, vz | ||
shr-long vx,vy,vz | ||
ushr-long vx, vy, vz | ||
add-float vx,vy,vz | Vz加上vy并赋值给vx (float) | |
sub-float vx,vy,vz | ||
mul-float vx, vy, vz | ||
div- float vx, vy, vz | ||
rem-float vx,vy,vz | ||
add-double vx,vy,vz | Vz加上vy并赋值给vx (double) | |
sub-double vx,vy,vz | ||
mul-double vx, vy, vz | ||
div- double vx, vy, vz | ||
rem-double vx,vy,vz | ||
sub-int/2addr vx,vy | Vx加vy并将值赋给vx | |
add-int/2addr vx,vy | ||
mul-int/2addr vx,vy | ||
div-int/2addr vx,vy | ||
rem-int/2addr vx,vy | ||
and-int/2addr vx, vy | ||
or-int/2addr vx, vy | ||
xor-int/2addr vx, vy | ||
shl-int/2addr vx, vy | ||
shr-int/2addr vx, vy | ||
ushr-int/2addr vx, vy | ||
add-long/2addr vx,vy | Vx加vy并将值赋给vx | |
sub-long/2addr vx,vy | ||
mul-long/2addr vx,vy | ||
div-long/2addr vx,vy | ||
rem-long/2addr vx,vy | ||
and-long/2addr vx, vy | ||
or-long/2addr vx, vy | ||
xor-long/2addr vx, vy | ||
shl-long/2addr vx, vy | ||
shr-long/2addr vx, vy | ||
ushr-long/2addr vx, vy | ||
add-long/2addr vx,vy | ||
sub-long/2addr vx,vy | ||
mul-long/2addr vx,vy | ||
div-long/2addr vx,vy | ||
rem-long/2addr vx,vy | ||
add-float/2addr vx,vy | Vx加vy并将值赋给vx | |
sub- float /2addr vx,vy | ||
mul- float /2addr vx,vy | ||
div- float /2addr vx,vy | ||
rem- float /2addr vx,vy | ||
add-double/2addr vx,vy | Vx加vy并将值赋给vx | |
sub- double /2addr vx,vy | ||
mul- double /2addr vx,vy | ||
div- double /2addr vx,vy | ||
rem- double /2addr vx,vy | ||
add-int/lit16 vx,vy,lit16 | 将一个数字与vy相加并赋值给vx | add-int/lit16 v1, v0, ox234 |
sub-int/lit16 vx,vy,lit16 | ||
mul-int/lit16 vx,vy,lit16 | ||
div-int/lit16 vx,vy,lit16 | ||
rem-int/lit16 vx,vy,lit16 | ||
and-int/lit16 vx,vy,lit16 | ||
or-int/lit16 vx,vy,lit16 | ||
xor-int/lit16 vx,vy,lit16 | ||
add-int/lit8 vx,vy,lit8 | ||
sub-int/lit8 vx,vy,lit8 | ||
mul-int/lit8 vx,vy,lit8 | ||
div-int/lit8 vx,vy,lit8 | ||
rem-int/lit8 vx,vy,lit8 | ||
and-int/lit8 vx,vy,lit8 | ||
or-int/lit8 vx, vy, lit8 | ||
xor-int/lit8 vx, vy, lit8 | ||
shl-int/lit8 vx, vy, lit8 | ||
shr-int/lit8 vx, vy, lit8 | ||
ushr-int/lit8 vx, vy, lit8 | 将vy右移(无符号)指定位数赋值给vx | |
execute-inline {parameters},inline ID | 这个是一个不安全的结构 | execute-inline {v1, v0}, inline #0003 |
invoke-direct-empty | invoke-direct-empty {v0}, Ljava/lang/Object;.:()V | |
iget-quick vx,vy,offset | iget-quick v1, v2, [obj+0010] | |
iget-wide-quick vx,vy,offset | iget-wide-quick v4, v6, [obj+0130] | |
iget-object-quick vx,vy,offset | iget-object-quick v1, v3, [obj+000c] | |
iput-quick vx,vy,offset | iput-quick v1, v2, [obj+0010] | |
iput-wide-quick vx,vy,offset | iput-wide-quick v2, v5, [obj+0170] | |
iput-object-quick vx,vy,offset | iput-object-quick v1, v0, [obj+004c] | |
invoke-virtual-quick {parameters},vtable offset | invoke-virtual-quick {v15, v12}, vtable #00b8 | |
invoke-virtual-quick/range {parameter range},vtable offset | invoke-super-quick {v2, v3, v4, v5}, vtable #0081 | |
invoke-super-quick {parameters},vtable offset | invoke-super-quick {v2, v3, v4, v5}, vtable #0081 | |
invoke-super-quick/range {register range},vtable offset | invoke-super-quick/range {v0..v5}, vtable #001b |
函数示例
void foo() foo ()V boolean foo(int a, int b, int c) foo (III)Z String foo (Boolean isB, int[] arrA, int[] arrB, String strA, long c·*)*** foo (Z[I[ILjava/lang/String;J)Ljava/lang/String; 数据类型
Smali Java 备注 v void 只能用于返回值类型 Z boolean B byte S short C char I int J long F float D double Lpackage/name; 对象类型 L表示这是一个对象类型,package表示该对象所在的包,;表示对象名称的结束 [类型 数组 [I表示一个int型数据,[Ljava/lang/String 表示一个String的对象数组 语法关键词
关键词 说明 .class 定义java类名 .super 定义父类名 .source 定义Java源文件名 .filed 定义字段 .method 定义方法开始 .end method 定义方法结束 .annotation 定义注解开始 .end annotation 定义注解结束 .implements 定义接口指令 .local 指定了方法内局部变量的个数 .registers 指定方法内使用寄存器的总数 .prologue 表示方法中代码的开始处 .line 表示java源文件中指定行 .paramter 指定了方法的参数 .param 和.paramter含义一致,但是表达格式不同 寄存器
寄存器分为如下两类:
1、本地寄存器
用v开头数字结尾的符号来表示,v0, v1, v2,...
2、参数寄存器
用p开头数字结尾的符号来表示,p0,p1,p2,...
注意:
在非static方法中,p0代指this,p1为方法的第一个参数。
在static方法中,p0为方法的第一个参数。
代码示例
const/4 v0, 0x1 //把值0x1存到v0本地寄存器
iput-boolean v0,p0,Lcom/aaa;->IsRegisterd:Z //把v0中的值赋给com.aaa.IsRegistered,p0代表this,相当于this.Isregistered=true
```
4. 成员变量
成员变量定义格式为:
\.field public/private \[static\]\[final\] varName:<类型\>
获取指令
iget, sget, iget-boolean, sget-boolean, iget-object, sget-object
操作指令
iput, sput, iput-boolean, sput-boolean, iput-object, sput-object
array的操作是aget和aput
指令解析
sget-object v0,Lcom/aaa;->ID:Ljava/lang/String;
获取ID这个String类型的成员变量并放到v0这个寄存器中
iget-object v0,p0,Lcom/aaa;->view:Lcom/aaa/view;
iget-object比sget-object多一个参数p0,这个参数代表变量所在类的实例。这里p0就是this
Smali代码示例1:
```
const/4 v3, 0x0
sput-object v3, Lcom/aaa;->timer:Lcom/aaa/timer;
```
相当于java代码:this.timer = null;
Smali代码示例2:
```
.local v0, args:Landroid/os/Message;
const/4 v1, 0x12
iput v1,v0,Landroid/os/Message;->what:I
```
相当于java代码:args.what = 18;
其中args为Message的实例
5. 函数
函数定义格式为:
\.method public/private \[static\]\[final\] methodName\(\)<类型\>
.end method
Smali代码示例:
```
.method private ifRegistered()Z
.locals 2 // 本地寄存器的个数
.prologue
const/4 v0, 0x1 //v0赋值为1
if-eqz v0, :cond_0 //判断v0是否等于0,等于0则跳到cond_0执行
const/4 v1, 0x1 //符合条件分支
:goto_0 //标签
return v1 //返回v1的值
:cond_0 //标签
const/4 v1, 0x0 //cond_0分支
goto :goto_0 //跳到goto_0执行
.end method
```
函数分为两类:direct method和virtual method
direct method就是private方法,virtual method就是指其余的方法。
调用指令:
invoke-direct
invoke-virtual
invoke-static
invoke-super
invoke-interface
调用格式:
invoke-指令类型 {参数1, 参数2,...}, L类名;->方法名
如果不是是静态方法,参数1代表调用该方法的实例。
Smali代码示例:
```
const-string v0, "NDKLIB"
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
```
相当于java代码:System.loadLibrary("NDKLIB")
函数返回结果
Smali需要用指令move-result或move-result-object来保存函数返回的结果
Smali代码示例:
```
const-string v0, "Eric"
invoke-static {v0}, Lcmb/pbi;->t(Ljava/lang/String;)Ljava/lang/String;
move-result-object v2
```
表示将方法t返回的String对象保存到v2中。
6. IF 指令
z 字母 : zero 简写,表示 0
eq 或 e:equal 简写,表示等于
n : 表示 不
g 或 gt : 表示 大于
l 或 lt: 表示 小于
| 命令 | 含义 |
| :---: | :---: |
| if-eq vA, vB, :cond\_\*\* | 如果 vA 等于 vB 则跳转到 :cond\_\*\* |
| if-ne vA, vB, :cond\_\*\* | 如果 vA 不等于 vB 则跳转到 :cond\_\*\* |
| if-lt vA, vB, :cond\_\*\* | 如果 vA 小于 vB 则跳转到 :cond\_\*\* |
| if-ge vA, vB, :cond\_\*\* | 如果 vA 大于等于 vB 则跳转到 :cond\_\*\* |
| if-gt vA, vB, :cond\_\*\* | 如果 vA 大于 vB 则跳转到 :cond\_\*\* |
| if-le vA, vB, :cond\_\*\* | 如果 vA 小于等于 vB 则跳转到 :cond\_\*\* |
| if-eqz vA, :cond\_\*\* | 如果 vA 等于 0 则跳转到 :cond\_\*\* |
| if-nez vA, :cond\_\*\* | 如果 vA 不等于 0 则跳转到 :cond\_\*\* |
| if-ltz vA, :cond\_\*\* | 如果 vA 小于 0 则跳转到 :cond\_\*\* |
| if-gez vA, :cond\_\*\* | 如果 vA 大于等于 0 则跳转到 :cond\_\*\* |
| if-gtz vA, :cond\_\*\* | 如果 vA 大于 0 则跳转到 :cond\_\*\* |
| if-lez vA, :cond\_\*\* | 如果 vA 小于等于 0 则跳转到 :cond\_\*\* |
示例:
```
.method private test(I)V
.registers 3 # 定义寄存器个数
.param p1, "x" # I
.prologue
.line 13
if-nez p1, :cond_4 # 如果参数不等于 0,就跳转至 cond_4。否则就执行下面的语句
.line 14
const/4 p1, 0x2
.line 20
:goto_3 # 定义语句 goto_3,在满足条件时可以跳到该语句中
return-void
.line 15
:cond_4
const/4 v0, 0x1
if-ne p1, v0, :cond_9
.line 16
const/4 p1, 0x3
goto :goto_3
.line 18
:cond_9
const/4 p1, 0x4
goto :goto_3
.end method
```
上述代码对应的 java 文件为:
```
private void test(int x){
if(x == 0){
x = 2;
}else if(x == 1){
x = 3;
}else{
x = 4;
}
}
```
7. while
while 循环其实可以使用 if 与 goto 表示:当满足 if 条件时,goto 到指定的语句执行。执行完毕后再判断一 if 条件。
这也是 smali 实现 while 循环的思路:
```
.method private test(I)V
.registers 3
.param p1, "x" # I
.prologue
.line 13
:goto_0
const/16 v0, 0xa
if-ge p1, v0, :cond_7
.line 14
add-int/lit8 p1, p1, 0x1
goto :goto_0
.line 16
:cond_7
return-void
.end method
```
其对应的 java 代码为:
```
private void test(int x){
while(x < 10){
x++;
}
}
```
从中可以看出,while 循环转换成一次次 if 判断以及 goto 语句。
每一次 if 判断成功后,会执行 while 代码块中的语句。执行完毕后,通过 goto 跳转到 if 判断。直到 if 判断失败,也就是整个循环执行完毕。
8. for
同 while 循环一样, for 循环也会被转换为 if 判断与 goto 语句。
9. 操作符
练习靶场
这是我写的应用程序靶场
1-5 关
修改数值到1000
猜数字 插装log
插入log 跳过
Jni 1 破解so 了解IDA
Jni 2 破解so2 分析条件
开始解题
此讲主要是逆向基础不是安卓开发
题目内容
猜数字 猜对获取flag
1.反编译安卓应用
打开安卓killer 放入APK文件
开始反编译
2.寻找对应smali文件
寻找关键业务
每次点击都会生成新的数字
寻找点击事件
或者flag打印处
反编译看看
触发条件未知
但fresh num 是关键
寻找生成随机数的地方
发现生成随机数函数
插个log试试
log出现了
int型log
invoke-static {v1}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
move-result-object v0
invoke-static {v0}, Lcom/android/killer/Log;->LogStr(Ljava/lang/String;)V
要把int转为String
然后log出来
注意有无变量增减
变动要修改此处
拿到密码
获取flag
CTF真题
mmactf-2015-rock-paper-scissors-rps
赢够1000次获取flag
当前活动
Mainactivity
看看jdgui反编译代码
要赢1000次才有flag
分析此处
意思是v1==v2==1000 进入cond_0
意味着当进入当前分支时候v2==v1==1000
于是修改分支条件
if-eq v1,v2,cond_0
然后强制赋值1000 (0x3e8)
获取flag
flag如下
IDA 查看SO
可以看到函数返回值为7
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)