freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

安卓逆向入门篇
2022-05-14 01:06:19
所属地 广东省

工具

环境

Jdk8u202

http://www.codebaoku.com/jdk/jdk-index.html
image

选择 windows 64位

安卓sdk

image

调试器

DDMS
image

image

后面看log用

要下载安卓sdk

逆向工具

爱盘工具下载
image

逆向IDE

Android Killer , JEB3

JEB3
image
Android Killer
image
最常使用

打log十分方便

坑下载替换好最新的Apktool

反编译工具

Apktool
image

jdgui (java)
image

转换工具

Jadx

Dex2jar

查壳工具

安卓开发者助手

pkid
image

so逆向工具

IDA

前置知识

Android 应用目录结构

这里使用MT管理器查看目录结构
image

  1. lib 安卓依赖库目录

image

  1. META-INF 里面有安装脚本 及配置

image

  1. res是布局文件

image

  1. manifest

image

每个安卓应用程序必须有一个AndroidManifest.xml文件,在app/manifests目录中。它在简单的Android系统的应用中提出了重要的信息,信息系统 [1] 必须具备之前,它可以运行任何应用程序的代码。除其他事项外,清单中执行下列操作:

它给应用程序的Java包命名,包的名称作为应用程序的唯一标识符。

它描述了应用程序的组件的活动、服务,广播接收机,内容提供商,应用程序组成。它命名的类,实现每个组件,并出版自己的能力(例如,可以处理哪些意图的消息)。这些声明让Android系统知道这些组件是什么和在什么条件下,他们可以推出。

它决定哪些进程将运行应用程序组件。

它决定了应用程序必须有权限才能访问受保护的API部分,并与其他应用程序进行交互。

它还决定了其他人与应用程序的组件交互所需要的权限。

它宣布了Android API的应用程序需要的最低水平。

它列出了库,应用程序必须与之配对。

  1. classes.dex
    虚拟机使用的字节码文件
    image
    其中包含 APK 的可执行代码,是分析 Android 软件时最常见的目标

反汇编安卓应用

使用Apktool 反编译安卓应用

命令

apktool d test.apk

image

NDK

安卓调用系统底层库桥梁

Smali

借用 https://www.jianshu.com/p/9931a1e77066

Smali是Android虚拟机的反汇编语言。

  1. 语法

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位的数值赋值给vxConst/4 vx, 0x2
const/16 vx,lit16将长度16位的数值赋值给vxConst/16 vx,ox16
const vx, lit32将int型数值赋值给vxConst 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, vzIf(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,targetvx==vy
if-ne vx,vy,targetvx!=vy
if-lt vx,vy,targetvx<vy
if-ge vx, vy,targetVx>=vy
if-gt vx,vy,targetVx>vy
if-le vx,vy,targetVx<vy
if-eqz vx,targetVx==0
if-nez vx,targetVx!=0
if-ltz vx,targetVx<0
if-gez vx,targetVx>=0
if-gtz vx,targetVx>0
if-lez vx,targetVx<=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对象的指定字段赋值给vxiget 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读取指定静态字段到vxsget 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,vyvx=-vy.
not-int vx,vy

neg-long vx,vyvx,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,vzVz加上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, vzVz加上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,vzVz加上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,vzVz加上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,vyVx加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,vyVx加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,vyVx加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,vyVx加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相加并赋值给vxadd-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
  1. 函数示例

    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;
    1. 数据类型

    SmaliJava备注
    vvoid只能用于返回值类型
    Zboolean
    Bbyte
    Sshort
    Cchar
    Iint
    Jlong
    Ffloat
    Ddouble
    Lpackage/name;对象类型L表示这是一个对象类型,package表示该对象所在的包,;表示对象名称的结束
    [类型数组[I表示一个int型数据,[Ljava/lang/String 表示一个String的对象数组
    1. 语法关键词

    关键词说明
    .class定义java类名
    .super定义父类名
    .source定义Java源文件名
    .filed定义字段
    .method定义方法开始
    .end method定义方法结束
    .annotation定义注解开始
    .end annotation定义注解结束
    .implements定义接口指令
    .local指定了方法内局部变量的个数
    .registers指定方法内使用寄存器的总数
    .prologue表示方法中代码的开始处
    .line表示java源文件中指定行
    .paramter指定了方法的参数
    .param和.paramter含义一致,但是表达格式不同
    1. 寄存器
      寄存器分为如下两类:
      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 关
image

  1. 修改数值到1000

  2. 猜数字 插装log

  3. 插入log 跳过

  4. Jni 1 破解so 了解IDA

  5. Jni 2 破解so2 分析条件

开始解题

此讲主要是逆向基础不是安卓开发

题目内容

猜数字 猜对获取flag

image

1.反编译安卓应用

打开安卓killer 放入APK文件

开始反编译

image
2.寻找对应smali文件
image

寻找关键业务

每次点击都会生成新的数字

寻找点击事件

或者flag打印处

反编译看看

image
image
触发条件未知

但fresh num 是关键

image

寻找生成随机数的地方
image
image

发现生成随机数函数

image

插个log试试
image

log出现了

image

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出来

注意有无变量增减

变动要修改此处

image

image

拿到密码

image

获取flag

image

CTF真题

mmactf-2015-rock-paper-scissors-rps

image

image

赢够1000次获取flag

当前活动

Mainactivity

image

看看jdgui反编译代码

image

要赢1000次才有flag

image

分析此处

image
image

意思是v1==v2==1000 进入cond_0

意味着当进入当前分支时候v2==v1==1000

于是修改分支条件

if-eq v1,v2,cond_0

然后强制赋值1000 (0x3e8)

获取flag

image

flag如下

image
IDA 查看SO

image

可以看到函数返回值为7

image

# 逆向 # Android # 逆向技术 # 逆向调试
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录