安卓逆向入门-基础知识

adb

常用命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
adb devices
adb install *.apk
adb install -r -t *.apk #升级/覆盖 测试模式
adb uninstall *.apk #卸载
adb push 本地路径 设备路径
adb pull 设备路径 本地路径 #若所在文件夹需要root 则先转移到/sdcard再拉取
adb logcat > log.txt
adb shell dumpsys package 包名 #获取指定应用详细信息 包名如com.example.myapp
adb shell dumpsys activity top #查看当前应用activity信息
adb shell dumpsys dbinfo 包名 #查看数据库信息、执行操作的查询语句等
adb shell dumpsys meminfo 包名/进程ID #查看内存信息
adb shell input text "xxx" #在屏幕选中输入框内输入文字 不能中文
adb shell pm list packages #列出所有安装的包
adb shell am start-activity -D -N com.example.network/.MainActivity #以Debug模式启动App
adb shell screencap -p 设备路径/screen.png #手机截屏
adb shell screenrecord 设备路径/screen.mp4 #手机录屏
adb forward tcp:本地端口 tcp:设备端口 #端口转发 发送到本地端口的数据将被转发到设备端口
adb tcpip 5555
adb connect 10.203.171.25:5555

其他常用:

1
2
3
4
5
6
7
8
9
10
11
12
adb shell pm list packages
adb shell pm list packages -s #系统应用
adb shell pm list packages -3 #用户自己安装的应用
adb shell pm list packages 'test'
adb shell am start -n com.xxx.xxx #启动指定服务
adb shell am broadcast -a android.NET.conn.CONNECTIVITY_CAHNGE #向系统发送一条指定广播
adb shell am force-stop com.xxx.xxx #强行停止指定应用进程
adb shell input keyevent <keycode> #模拟按键输入 3-HOME键 4-返回键 26-电源键 82-菜单键 187-切换应用 224-系统休眠
adb shell input swipe 500 300 100 300 #(500,300)滑向(100,300)
adb shell input tap 100 300 #单击
adb reboot bootloader
adb reboot recovery

APK文件

APK根目录下目录文件有:

名称 简介
META-INF 元数据,如签名、证书等
AndroidManifest.xml 全局配置文件
assets 资源文件夹,不编译不占APK内存
classes.dex 编译并打包后源码
lib 二进制共享库文件夹
res 资源文件夹,被映射到R.java中,访问资源R.id.filename
resources.arsc 编译res中的文件
kotlin Kotlin相关
original 反编译产生,保存一些文件备份
Unknown 反编译产生,暂时无法被处理的文件
Apktool.yml Apktool描述文件,记录反编译信息

res中可能的目录有:

名称 简介
anim 编译后动画xml文件
color 编译后选择器xml文件
layout 编译后布局xml文件
menu 编译后菜单xml文件
raw 不编译的资源文件,音视频等
xml 编译后自定义xml文件
drawable-* 按分辨率存放的图片

APK打包时首先处理Java文件、Java代码和Android代码,整体项目编译为class文件,再将class文件转换为Dex文件,再把资源文件、Dex文件和其他文件通过APKbuilder进行合并,打包为一个APK,完成签名后发布。

DEX即Dalvik Executable,Dalvik是Android系统的可执行文件,包含应用程序全部操作指令及运行时数据,结构为:

1
2
3
4
5
6
7
8
9
10
11
struct DexFile{
DexHeader Header; //文件头 有版本表示、文件各部分大小及偏移
DexStringId StringIds[stringIdsSize]; //字符串标识符列表
DexTypeId TypeIds[typeIdsSize]; //数据类型
DexFieldId FieldIds[fieldIdsSize]; //字段信息
DexMethodId MethodIds[methodIdsSize]; //方法信息
DexProtoId ProtoIds[protoIdsSize]; //原型信息
DexClassDef ClassDefs[classDefsSize]; //类信息
DexData Data; //数据区
DexLink LinkData; //静态链接数据区
};

实战

签名

有些工具自己去Android Studio的Android SDK目录找,没有的自己下载。

对APK解包:

1
apktool d ./crackme02.apk

对反汇编结果进行修改,再重新打包:

1
apktool b ./crackme02

接下来是签名:

1
2
3
4
5
6
apksigner verify ./crackme02.apk #先检查是否有签名
zipalign -v 4 ./crackme02.apk ./crackme02-aligned.apk #对齐 若失败则再来一次
keytool -genkey -v -keystore keystore1.ks -alias ks1 -keyalg RSA -keysize 2048 -validity 10000 #生成密钥库keystore1.ks 别名ks1 算法RSA 有效期10000天 接下来会询问密钥等问题
apksigner sign --ks keystore1.ks --ks-key-alias ks1 --out crackme02-signed.apk crackme02-aligned.apk #签名 且会询问上一步的密码
apksigner verify --verbose ./crackme02-signed.apk #验证是否签名
apksigner verify --print-certs ./crackme02-signed.apk #查看签名证书

对于apksigner其他参数:

1
2
--v1-signing-enabled 使用jar包签名方式
--v2-signing-enabled 使用全apk包签名方式

v1版本签名方案便利apk中左右条目,提取处包中文件消息摘要并写入MANIFEST.MF中,对后者二次摘要生成CERT.SF并用私钥对后者签名,仨文件一块打包保存到META-INF文件夹中。v2签名方案验证归档中所有字节,并在原apk块中增加一个新签名块用于存储签名、摘要、签名算法、证书链等属性信息。v2支持将apk分割成小块,分别计算小块摘要,再计算最终摘要。v1和v2可共存,有v2签名块则必须通过v2校验流程,否则降级v1签名降级流程。

对于Apktool的解包时,-r或--no-res在反编译时不处理resource.arsc和AndroidManifest.xml,适用于只需要Dex或资源文件解码错误,二次打包时资源文件将原封不动打回去。-s或--no-src指定反编译时Dex不被反编译为Smali,只解码resource.arsc和AndroidManifest.xml。

Dalvik字节码生成

例如:

1
2
3
4
5
6
7
8
9
10
//Hello.java
public class Hello{
public int foo(int a,int b){
return (a+b)*(a-b);
}
public static void main(String[] argc){
Hello hello=new Hello();
System.out.println(hello.foo(5,3));
}
}

编译为Java字节码:

1
2
3
4
javac Hello.java #编译为Hello.class文件
d8 --output . ./Hello.class #编译为classes.dex文件
javap ./Hello.class #反编译.dex文件为java源码
javap -c ./Hello.class #反编译.dex文件为java字节码

结果有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Compiled from "Hello.java"
public class Hello {
public Hello();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return

public int foo(int, int);
Code:
0: iload_1
1: iload_2
2: iadd
3: iload_1
4: iload_2
5: isub
6: imul
7: ireturn

public static void main(java.lang.String[]);
Code:
0: new #7 // class Hello
3: dup
4: invokespecial #9 // Method "<init>":()V
7: astore_1
8: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
11: aload_1
12: iconst_5
13: iconst_3
14: invokevirtual #16 // Method foo:(II)I
17: invokevirtual #20 // Method java/io/PrintStream.println:(I)V
20: return
}

查看Dalvik字节码:

1
dexdump -d ./classes.dex

结果有删减:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Processing './classes.dex'...
Opened './classes.dex', DEX version '035'
Class #0 -
Class descriptor : 'LHello;'
Access flags : 0x0001 (PUBLIC)
Superclass : 'Ljava/lang/Object;'
Interfaces -
Static fields -
Instance fields -
Direct methods -
#0 : (in LHello;)
name : '<init>'
type : '()V'
access : 0x10001 (PUBLIC CONSTRUCTOR)
code -
registers : 1
ins : 1
outs : 1
insns size : 4 16-bit code units
00016c: |[00016c] Hello.<init>:()V
00017c: 7010 0400 0000 |0000: invoke-direct {v0}, Ljava/lang/Object;.<init>:()V // method@0004
000182: 0e00 |0003: return-void
...
000184: |[000184] Hello.main:([Ljava/lang/String;)V
000194: 2203 0100 |0000: new-instance v3, LHello; // type@0001
000198: 7010 0000 0300 |0002: invoke-direct {v3}, LHello;.<init>:()V // method@0000
00019e: 6200 0000 |0005: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0000
0001a2: 1251 |0007: const/4 v1, #int 5 // #5
0001a4: 1232 |0008: const/4 v2, #int 3 // #3
0001a6: 6e30 0100 1302 |0009: invoke-virtual {v3, v1, v2}, LHello;.foo:(II)I // method@0001
0001ac: 0a03 |000c: move-result v3
0001ae: 6e20 0300 3000 |000d: invoke-virtual {v0, v3}, Ljava/io/PrintStream;.println:(I)V // method@0003
0001b4: 0e00 |0010: return-void
...
000150: |[000150] Hello.foo:(II)I
000160: 9000 0203 |0000: add-int v0, v2, v3
000164: b132 |0002: sub-int/2addr v2, v3
000166: 9200 0002 |0003: mul-int v0, v0, v2
00016a: 0f00 |0005: return v0
...

还有用BakSmali进行反汇编:

1
baksmali d ./classes.dex #输出在out文件夹中

结果有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
.class public LHello;
.super Ljava/lang/Object;
.source "Hello.java"

# direct methods
.method public constructor <init>()V
.registers 1

.line 1
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method

.method public static main([Ljava/lang/String;)V
.registers 4

.line 6
new-instance p0, LHello;
invoke-direct {p0}, LHello;-><init>()V

.line 7
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const/4 v1, 0x5
const/4 v2, 0x3
invoke-virtual {p0, v1, v2}, LHello;->foo(II)I
move-result p0
invoke-virtual {v0, p0}, Ljava/io/PrintStream;->println(I)V

.line 8
return-void
.end method


# virtual methods
.method public foo(II)I
.registers 4

.line 3
add-int v0, p1, p2
sub-int/2addr p1, p2
mul-int v0, v0, p1
return v0
.end method

除了BakSmali还有Dedexer:

1
ddx -d . ./classes.dex #输出Hello.ddx 用记事本打开就行

结果有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
.class public Hello
.super java/lang/Object
.source Hello.java


.method public <init>()V
.limit registers 1
; this: v0 (LHello;)
.line 1
invoke-direct {v0},java/lang/Object/<init> ; <init>()V
return-void
.end method

.method public static main([Ljava/lang/String;)V
.limit registers 4
; parameter[0] : v3 ([Ljava/lang/String;)
.line 6
new-instance v3,Hello
invoke-direct {v3},Hello/<init> ; <init>()V
.line 7
sget-object v0,java/lang/System.out Ljava/io/PrintStream;
const/4 v1,5
const/4 v2,3
invoke-virtual {v3,v1,v2},Hello/foo ; foo(II)I
move-result v3
invoke-virtual {v0,v3},java/io/PrintStream/println ; println(I)V
.line 8
return-void
.end method

.method public foo(II)I
.limit registers 4
; this: v1 (LHello;)
; parameter[0] : v2 (I)
; parameter[1] : v3 (I)
.line 3
add-int v0,v2,v3
sub-int/2addr v2,v3
mul-int v0,v0,v2
return v0
.end method

再编写HelloWorld.smali:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
.class public LHelloWorld;
.super Ljava/lang/Object;
.method public static main([Ljava/lang/String;)V
.registers 4
.prologue
#空指令
nop
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;
#方法调用指令
invoke-direct {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
#比较指令
cmpl-float v0, v2, v2
#字段操作指令
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v1, "Hello World" #构造字符串
#方法调用指令
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
#返回指令
:goto_0
return-void
.end method

将HelloWorld.smali编译为out.dex:

1
smali assemble ./HelloWorld.smali

NDK开发入门

不需要Android Studio啥的,直接新建MainActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package test.example;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import android.widget.LinearLayout;
import android.widget.Button;
public class MainActivity extends Activity{
static{
System.loadLibrary("jni_test");
}
public native String stringFromJNI();
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
LinearLayout lla=new LinearLayout(this);
Button b=new Button(this);
b.setText("click");
lla.addView(b);
this.setContentView(lla);
final Activity _this=this;
b.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
Toast.makeText(_this,stringFromJNI(),Toast.LENGTH_LONG).show();
}
});
}
}

编译:

1
2
javac --class-path=D:\AndroidSDK\platforms\android-34\android.jar -d . ./MainActivity.java #生成class文件 生成test文件夹
javac -h ./test/example/jni -classpath D:\AndroidSDK\platforms\android-34\android.jar -d . ./MainActivity.java #生成h头文件

此时在./test/example/jni目录下生成头文件test_example_MainActivity.h,并在该目录下新建jni.c文件:

1
2
3
4
5
6
#include <string.h>
#include <jni.h>
#include "test_example_MainActivity.h"
JNIEXPORT jstring JNICALL Java_test_example_MainActivity_stringFromJNI(JNIEnv* env,jobject _this){
return (*env)->NewStringUTF(env,"return from c");
}

同目录下再新建Android.mk文件,声明如何编译动态链接库:

1
2
3
4
5
LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:=jni_test
LOCAL_SRC_FILES:=jni.c
include $(BUILD_SHARED_LIBRARY)

在同目录下编译:

1
2
cd test/example/jni
ndk-build

此时在./test/example/libs目录会生成各种架构的动态链接库。

当某个.so文件在运行时发生异常,adb logcat将会记录下来。通过查看日志找到.so发生错误的PC寄存器值和.so文件offset后,可用addr2line来定位对应c语言行数:

1
llvm-addr2line -C -f -e *.so XXXXXXXXXXXXXXXX #后面参数为pc寄存器+动态链接库offset

可用readelf查看ELF格式文件信息,能看好多信息,这里举例文件头信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
> llvm-readelf -h ./libjni_test.so
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian #2补码 小端序
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: AArch64
Version: 0x1
Entry point address: 0x0 #入口点地址
Start of program headers: 64 (bytes into file) #程序头起点
Start of section headers: 2624 (bytes into file) #section段头部起点
Flags: 0x0
Size of this header: 64 (bytes) #头部大小
Size of program headers: 56 (bytes) #程序头部大小
Number of program headers: 8 #程序头部数量
Size of section headers: 64 (bytes) #section段头部大小
Number of section headers: 23 #section段头部数量
Section header string table index: 22 #section段头部字符表索引

Dalvik汇编/Smali字节码

入门

Smali/bakSmali是Android的Java VM实现dalvik使用的Dex格式的汇编/反汇编程序。对应Java基础类型有:

Java Smali
void V
boolean Z
byte B
short S
char C
int I
long J
float F
double D

语法关键词有:

关键词 含义
.class 定义Java类名
.super 定义父类名
.source 定义Java源文件名
.field 定义字段
.method 定义方法
.end method 定义方法结束
.annotation 定义注释
.end annotation 定义注释结束
.implements 定义接口指令
.local 方法内局部变量个数
.registers 方法内使用寄存器的总数
.prologue 方法中代码开始处
.line Java源文件中指定行
.parameter 方法参数

Smali字段表示方法如Ljava/lang/String。表述参数类型时若有多个,如int,int,String可表示为IILjava/lang/String。对于数组String[]、int[][]可表示为[Ljava/lang/String[[I。多维数组维数最大为255个。

常见参数语法约定有:

参数 描述
vX 寄存器
#+X 常量数字
+X 相对指令的地址偏移
kind@X 常量池索引值

上述“kind”表示常量池类型,可以是字符串常量池索引string、类型常量池索引type、字段常量池索引field、方法常量池索引meth。

例如方法格式如下:

1
2
// methd(I[[IILjava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
String method(int,int[][],int,String,Object[]){}

BakSmali版在.method指令前可能有# virtual method表示虚方法,# direct methods表示直接方法。BakSmali字段以.field开头,# instance field表示实例字段,# static field表示静态字段。

实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# BakSmali版
# 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 method

; Dedexer版
.method public foo(II)I
.limit registers 5
; this: v2 (LHello;)
; parameter[0] : v3 (I)
; parameter[1] : v4 (I)
.line 3
add-int v0,v3,v4
sub-int v1,v3,v4
mul-int/2addr v0,v1
return v0
.end method

常用指令

Dalvik虚拟机中每个寄存器都32位的,64位常规类型添加-wide后缀。特殊类型字节码根据具体类型添加后缀,可以是-boolean-byte-char-short-int-long-float-double-object-string-class-void。宽度值中每个大写字母表示4位。如:

1
move-wide/from16 vAA,vBBBB

move位基础字节码。wide位名称后缀。from16位字节码后缀,标识源位一个16位寄存器引用变量。vAA和vBBBB分别为目的寄存器和源寄存器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
nop #空操作指令 值00

move vA,vB #将vB寄存器值赋给vA寄存器
move/from16 vAA,vBBBB
move/16 vAAAA,vBBBB
move-wide vA,vB
move-wide/from16 vAA,vBBBB
move-object vA,vB
move-object/from16 vAA,vBBBB
move-object vA,vB
move-object/from16 vAA,vBBBB
move-object/16 vAAAA,vBBBB
move-result vAA #将上个invoke类型指令操作的单字节非对象结果赋给vAA寄存器
move-result-wide vAA
move-result-object vAA
move-exception vAA #保存一个运行时发生的异常到vAA寄存器 必须异常发生时的异常处理器的一条指令 否则无效

return-void
return vAA #返回一个32位非对象类型
return-wide vAA
return-object vAA

const/4 vA,#+B #将数值B符号扩展为32位后赋值给寄存器vA
const/16 vAA,#+BBBB
const vAA,#+BBBBBBBB
const/high16 vAA,#+BBBB0000
const-wide/16 vAA,#+BBBB
const-wide/32 vAA,#+BBBBBBBB
const-wide vAA,#+BBBBBBBBBBBBBBBB
const-wide/high16 vAA,#+BBBB000000000000
const-string vAA,string@BBBB #通过字符串索引构造一个字符串并赋给寄存器vAA
const-string/jumbo vAA,string@BBBBBBBB
const-class vAA,type@BBBB
const-class/jumbo vAAAA,type@BBBBBBBB

monitor-enter vAA #为指定对象获取锁
monitor-exit vAA #释放指定对象的锁

check-cast vAA,type@BBBB #将vAA中对象引用转换成指定类型 失败抛出ClassCastException异常 若B为基本类型且A为非基本类型则运行始终失败
instance-of vA,vB,type@CCCC #判断vB对象引用是否可转换为指定类型 可以则vA赋值1 否则0
new-instance vAA,type@BBBB #构造指定类型对象新实例 对象引用赋值给vAA type不能是数组类
check-cast/jumbo vAAAA,type@BBBBBBBB
instance-of/jumbo vAAAA,vBBBB,type@CCCCCCCC
new-instance/jumbo vAAAA,type@BBBBBBBB

array-length vA,vB #获取vB数组长度并赋值给vA
new-array vA,vB,type@CCCC #构造type类型与vB大小的数组 赋值给vA
filled-new-array {vC,vD,vE,vF,vG},type@BBBB
filled-new-array/range {vCCCC..vNNNN},type@BBBB
fill-array-data vAA,+BBBBBBBB #用指定的B数据表填充基础类型数组vAA
new-array/jumbo vAAAA,vBBBB,type@CCCCCCCC
fileed-new-array/jumbo {vCCCC..vNNNN},type@BBBBBBBB

throw vAA #抛出vAA指定类型的异常

goto +AA #无条件跳转到指定偏移处 AA不能为0
goto/16 +AAAA
goto/32 +AAAAAAAA
packed-switch vAA,+BBBBBBBB #vAA为switch分支中要判断的值 B指向一个packed-switch-payload格式偏移表
sparse-switch vAA,+BBBBBBBB #同上 B指向sparse-switch-payload格式偏移表

if-eq vA,vB,+CCCC #若vA==vB则跳转C偏移处
if-ne vA,vB,+CCCC #!=
if-lt vA,vB,+CCCC #<
if-ge vA,vB,+CCCC #>=
if-gt vA,vB,+CCCC #>
if-le vA,vB,+CCCC #<=
if-eqz vA,vB,+CCCC #vAA==0
if-nez vA,vB,+CCCC #vAA!=0
if-ltz vA,vB,+CCCC #vAA<0
if-gez vA,vB,+CCCC #vAA>=0
if-gtz vA,vB,+CCCC #vAA>0
if-lez vA,vB,+CCCC #vAA<=0

cmpl-float vAA,vBB,vCC #vBB>vCC则vA为-1 相等0 小于1
cmpg-float vAA,vBB,vCC #vBB>vCC则vA为1 相等0 小于-1
cmpl-double vAA,vBB,vCC
cmpg-double vAA,vBB,vCC
cmp-long vAA,vBB,vCC #vBB>vCC则vA为1 相等0 小于-1

i* vA,vB,field@CCCC #普通字段
s* vAA,field@BBBB #静态字段

invoke-virtual {vC,vD,vE,vF,vG},meth@BBBB #调用实例虚方法
invoke-super {vC,vD,vE,vF,vG},meth@BBBB #调用实例父方法
invoke-direct {vC,vD,vE,vF,vG},meth@BBBB #调用实例直接方法
invoke-static {vC,vD,vE,vF,vG},meth@BBBB #调用实例静态方法
invoke-interface {vC,vD,vE,vF,vG},meth@BBBB #调用实例接口方法
invoke-*/range {vCCCC..vNNNN},meth@BBBB
invoke-*/jumbo {vCCCC..vNNNN},meth@BBBBBBBB

neg-int vA,vB #vB转换类型到vA 求补
not-int vA,vB #求反
#其他:neg-long not-long neg-float neg-double int-to-long int-to-float int-to-double long-to-int long-to-float long-to-double float-to-int float-to-long float-to-double double-to-int double-to-long double-to-float int-to-byte int-to-char int-to-short

add-* vAA,vBB,vCC #vAA=vBB+vCC
#其他:sub-* mul-* div-* rem-* and-* or-* xor-* shl-* shr-* ushr-* 后面类型可以是-int -long -float -double
*/2addr vA,vB #vA=vA运算vB
*/lit116 vA,vB,#+CCCC #vA=vB运算C
*/lit8 vAA,vBB,#+CC #vAA=vBB运算C

文件格式

smali文件头格式为:

1
2
3
.class <访问权限> [修饰关键字] <类名>
.super <父类名>
.source <源文件名>

访问权限可以是public、private、protected之一,修饰关键字可以是synthetic等。例如MainActivity.smali前3行为:

1
2
3
.class public Lcom/droider/crackme0502/MainActivity;
.super Landroid/app/Activity;
.source "MainActivity.java"

两种实例声明格式为:

1
2
3
4
5
#static fields
.field <访问权限> static [修饰关键字] <字段名>:<字段类型>

#instance fields
.field <访问权限> [修饰关键字] <字段名>:<字段类型>

例如:

1
2
# instance fields
.field private btnAnno:Landroid/widget/Button;

方法声明格式为:

1
2
3
4
5
6
7
8
#direct methods
.method <访问权限> [修饰关键字] <方法原型>
<.locals>
[.parameter]
[.prologue]
[.line]
<代码体>
.end method

虚方法声明则开头改为“virtual methods”。.locals指定局部变量个数,每一个.parameter表明使用一个参数,有几个参数则有几条.parameter.prologue指定代码开始处。

接口格式为:

1
2
#interfaces
.implements <接口名>

注解格式如下。

1
2
3
4
#annotations
.annotation [注解属性] <注解类名>
[注解字段=值]
.end annotation

注解的作用范围可以是类、方法或字段。若作用范围是类,该指令直接定义在smali文件中。若是方法或字段,该指令包含在方法或字段定义中,如下:

1
2
3
4
5
6
# instance fields
.field public sayWhat:Ljava/lang/String;
.annotation runtime Lcom/droider/anno/MyAnnoField;
info = "Hello my friend"
.end annotation
.end field

转为Java代码为:

1
2
@com.droider.anno MyAnnoField(info="Hello my friend")
public String sayWhat;

例如下面的类,反编译后生成Outer.smali和Outer$Inner.smali,内部类文件名格式为“[外部类]$[内部类].smali”。

1
2
3
class Outer{
class Inner{}
}

有时遇到实例字段定义如下,synthetic属性表明是被编译器合成的、虚构的,而不是代码作者声明的。

1
2
# instance fields
.field final synthetic this$0:Lcom/droider/crackme0502/MainActivity;

当内部类层数过多时,有自动保留的指向所在外部类的引用:

1
2
3
4
5
6
7
8
public class Outer { //this$0
public class FirstInner { //this$1
public class SecondInner { //this$2
public class ThirdInner {
}
}
}
}

对于一个非静态方法而言,若有两条.parameter语句,则实际上使用了p0~p2共3个参数寄存器。此时隐含使用p0寄存器当作类的this引用。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# direct methods
.method public constructor <init>(Lcom/droider/crackme0502/MainActivity;Ljava/lang/String;)V
.registers 3
.param p2, "sn" # Ljava/lang/String;

.prologue
.line 83
iput-object p1, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->this$0:Lcom/droider/crackme0502/MainActivity;
invoke-direct {p0}, Ljava/lang/Object;-><init>()V

.line 84
iput-object p2, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String;

.line 85
return-void
.end method

监听器一般用匿名内部类形式实现,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# virtual methods
.method public onCreate(Landroid/os/Bundle;)V
.registers 4
.param p1, "savedInstanceState" # Landroid/os/Bundle;

.prologue
...
.line 32
iget-object v0, p0, Lcom/droider/crackme0502/MainActivity;->btnAnno:Landroid/widget/Button;
new-instance v1, Lcom/droider/crackme0502/MainActivity$1;
invoke-direct {v1, p0}, Lcom/droider/crackme0502/MainActivity$1;-><init>(Lcom/droider/crackme0502/MainActivity;)V
invoke-virtual {v0, v1}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V

.line 40
iget-object v0, p0, Lcom/droider/crackme0502/MainActivity;->btnCheckSN:Landroid/widget/Button;
new-instance v1, Lcom/droider/crackme0502/MainActivity$2;
invoke-direct {v1, p0}, Lcom/droider/crackme0502/MainActivity$2;-><init>(Lcom/droider/crackme0502/MainActivity;)V
invoke-virtual {v0, v1}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V

.line 50
return-void
.end method

有MemberClass注解如下,该注解为一个系统注解,为父类提供一个MemberClass子类成员列表,即内部类列表。

1
2
3
4
5
6
# annotations
.annotation system Ldalvik/annotation/MemberClasses;
value = {
Lcom/droider/crackme0502/MainActivity$SNChecker;
}
.end annotation

有EnclosingMethod注解如下,说明整个MainActivity$1匿名类的作用范围,注解value表明它位于MainActivity的onCreate中。

1
2
3
4
# annotations
.annotation system Ldalvik/annotation/EnclosingMethod;
value = Lcom/droider/crackme0502/MainActivity;->onCreate(Landroid/os/Bundle;)V
.end annotation

例如还有EnclosingClass注解,value标识MainActivity$SNChecker作用于MainActivity类。

1
2
3
4
5
6
7
8
9
# annotations
.annotation system Ldalvik/annotation/EnclosingClass;
value = Lcom/droider/crackme0502/MainActivity;
.end annotation

.annotation system Ldalvik/annotation/InnerClass;
accessFlags = 0x1
name = "SNChecker"
.end annotation

EnclosingClass后跟着InnerClass注解,其中accessFlags访问标志位一个枚举值,name位内部类名。

1
2
3
4
5
enum {
kDexVisibilityBuild=0x00,
kDexVisibilityRuntime=0x01, //Runtime属性
kDexVisibilitySystem=0x02,
};

若注解类在生命是提供了默认值,则程序中会用到AnnotationDefault注解:

1
2
3
4
5
6
# annotations
.annotation system Ldalvik/annotation/AnnotationDefault;
value = .subannotation Lcom/droider/anno/MyAnnoClass;
value = "MyAnnoClass"
.end subannotation
.end annotation

Signature注解用于验证方法签名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.method public onItemClick(Landroid/widget/AdapterView;Landroid/view/View;IJ)V
.locals 6
.parameter
.parameter "v"
.parameter "position"
.parameter "id"
.annotation system Ldalvik/annotation/Signature;
value={
"(",
"Landroid/widget/AdapterView",
"<*>;",
"Landroid/view/View;",
"IJ)V"
}
.end annotation
...
.end method

若方法声明中使用throws关键字抛出异常,则生成相应Throws注解:

1
2
3
4
5
6
7
8
9
10
.method public final get()Ljava/lang/Object;
.locals 1
.annotation system Ldalvik/annotation/Throws;
value={
Ljava/lang/InterruptedException;,
Ljava/util/concurrent/ExecutionException;
}
.end annotation
...
.end method

其对应Java代码为:

1
2
3
public final Object get() throws InterruptedException, ExecutionException {
//...
}

此外Android SDK还自动生成一些类,有R、BuildConfig等类。R类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.droider.crackme0502;

/* renamed from: com.droider.crackme0502.R */
/* loaded from: classes.dex */
public final class C0039R {

/* renamed from: com.droider.crackme0502.R$attr */
/* loaded from: classes.dex */
public static final class attr { //属性
}

/* renamed from: com.droider.crackme0502.R$dimen */
/* loaded from: classes.dex */
public static final class dimen { //尺寸
public static final int padding_large = 2130968578;
public static final int padding_medium = 2130968577;
public static final int padding_small = 2130968576;
}

/* renamed from: com.droider.crackme0502.R$drawable */
/* loaded from: classes.dex */
public static final class drawable { //图片
public static final int ic_action_search = 2130837504;
public static final int ic_launcher = 2130837505;
}

/* renamed from: com.droider.crackme0502.R$id */
/* loaded from: classes.dex */
public static final class C0040id { //id标识
public static final int btn_annotation = 2131230720;
public static final int btn_checksn = 2131230722;
public static final int edt_sn = 2131230721;
public static final int menu_settings = 2131230723;
}

/* renamed from: com.droider.crackme0502.R$layout */
/* loaded from: classes.dex */
public static final class layout { //布局
public static final int activity_main = 2130903040;
}

/* renamed from: com.droider.crackme0502.R$menu */
/* loaded from: classes.dex */
public static final class menu { //菜单
public static final int activity_main = 2131165184;
}

/* renamed from: com.droider.crackme0502.R$string */
/* loaded from: classes.dex */
public static final class string { //字符串
public static final int app_name = 2131034112;
public static final int hello_world = 2131034113;
public static final int menu_settings = 2131034114;
public static final int title_activity_main = 2131034115;
}

/* renamed from: com.droider.crackme0502.R$style */
/* loaded from: classes.dex */
public static final class style { //样式
public static final int AppTheme = 2131099648;
}
}

BuildConfig类有:

1
2
3
4
5
6
package com.droider.crackme0502;

/* loaded from: classes.dex */
public final class BuildConfig {
public static final boolean DEBUG = true; //调试版本发布
}

反编译

例如迭代器for方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
.method private iterator()V
.registers 8

.prologue
.line 34
const-string v4, "activity"
invoke-virtual {p0, v4}, Lcom/droider/circulate/MainActivity;->getSystemService(Ljava/lang/String;)Ljava/lang/Object;
move-result-object v0
check-cast v0, Landroid/app/ActivityManager;

.line 35
.local v0, "activityManager":Landroid/app/ActivityManager;
invoke-virtual {v0}, Landroid/app/ActivityManager;->getRunningAppProcesses()Ljava/util/List;
move-result-object v2

.line 36
.local v2, "psInfos":Ljava/util/List;, "Ljava/util/List<Landroid/app/ActivityManager$RunningAppProcessInfo;>;"
new-instance v3, Ljava/lang/StringBuilder;
invoke-direct {v3}, Ljava/lang/StringBuilder;-><init>()V

.line 37
.local v3, "sb":Ljava/lang/StringBuilder;
invoke-interface {v2}, Ljava/util/List;->iterator()Ljava/util/Iterator;
move-result-object v4
:goto_15
invoke-interface {v4}, Ljava/util/Iterator;->hasNext()Z
move-result v5
if-nez v5, :cond_28

.line 40
invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v4
const/4 v5, 0x0
invoke-static {p0, v4, v5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v4
invoke-virtual {v4}, Landroid/widget/Toast;->show()V

.line 41
return-void

.line 37
:cond_28
invoke-interface {v4}, Ljava/util/Iterator;->next()Ljava/lang/Object;
move-result-object v1
check-cast v1, Landroid/app/ActivityManager$RunningAppProcessInfo;

.line 38
.local v1, "info":Landroid/app/ActivityManager$RunningAppProcessInfo;
new-instance v5, Ljava/lang/StringBuilder;
iget-object v6, v1, Landroid/app/ActivityManager$RunningAppProcessInfo;->processName:Ljava/lang/String;
invoke-static {v6}, Ljava/lang/String;->valueOf(Ljava/lang/Object;)Ljava/lang/String;
move-result-object v6
invoke-direct {v5, v6}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
const/16 v6, 0xa
invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(C)Ljava/lang/StringBuilder;
move-result-object v5
invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v5
invoke-virtual {v3, v5}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
goto :goto_15
.end method

反编译结果为:

1
2
3
4
5
6
7
8
9
private void iterator() {
ActivityManager activityManager = (ActivityManager) getSystemService("activity");
List<ActivityManager.RunningAppProcessInfo> psInfos = activityManager.getRunningAppProcesses();
StringBuilder sb = new StringBuilder();
for (ActivityManager.RunningAppProcessInfo info : psInfos) {
sb.append(String.valueOf(info.processName) + '\n');
}
Toast.makeText(this, sb.toString(), 0).show();
}

例如传统for方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
.method private forCirculate()V
.registers 9

.prologue
.line 47
invoke-virtual {p0}, Lcom/droider/circulate/MainActivity;->getApplicationContext()Landroid/content/Context;
move-result-object v6
invoke-virtual {v6}, Landroid/content/Context;->getPackageManager()Landroid/content/pm/PackageManager;
move-result-object v3

.line 49
.local v3, "pm":Landroid/content/pm/PackageManager;
const/16 v6, 0x2000

.line 48
invoke-virtual {v3, v6}, Landroid/content/pm/PackageManager;->getInstalledApplications(I)Ljava/util/List;
move-result-object v0

.line 50
.local v0, "appInfos":Ljava/util/List;, "Ljava/util/List<Landroid/content/pm/ApplicationInfo;>;"
invoke-interface {v0}, Ljava/util/List;->size()I
move-result v5

.line 51
.local v5, "size":I
new-instance v4, Ljava/lang/StringBuilder;
invoke-direct {v4}, Ljava/lang/StringBuilder;-><init>()V

.line 52
.local v4, "sb":Ljava/lang/StringBuilder;
const/4 v1, 0x0
.local v1, "i":I
:goto_18
if-lt v1, v5, :cond_27

.line 56
invoke-virtual {v4}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v6
const/4 v7, 0x0
invoke-static {p0, v6, v7}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v6
invoke-virtual {v6}, Landroid/widget/Toast;->show()V

.line 57
return-void

.line 53
:cond_27
invoke-interface {v0, v1}, Ljava/util/List;->get(I)Ljava/lang/Object;
move-result-object v2
check-cast v2, Landroid/content/pm/ApplicationInfo;

.line 54
.local v2, "info":Landroid/content/pm/ApplicationInfo;
new-instance v6, Ljava/lang/StringBuilder;
iget-object v7, v2, Landroid/content/pm/ApplicationInfo;->packageName:Ljava/lang/String;
invoke-static {v7}, Ljava/lang/String;->valueOf(Ljava/lang/Object;)Ljava/lang/String;
move-result-object v7
invoke-direct {v6, v7}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
const/16 v7, 0xa
invoke-virtual {v6, v7}, Ljava/lang/StringBuilder;->append(C)Ljava/lang/StringBuilder;
move-result-object v6
invoke-virtual {v6}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v6
invoke-virtual {v4, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

.line 52
add-int/lit8 v1, v1, 0x1
goto :goto_18
.end method

反编译结果为:

1
2
3
4
5
6
7
8
9
10
11
private void forCirculate() {
PackageManager pm = getApplicationContext().getPackageManager();
List<ApplicationInfo> appInfos = pm.getInstalledApplications(8192);
int size = appInfos.size();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < size; i++) {
ApplicationInfo info = appInfos.get(i);
sb.append(String.valueOf(info.packageName) + '\n');
}
Toast.makeText(this, sb.toString(), 0).show();
}

switch语句有两种不同的Smali表现形式,分别为packedSwitch和sparseSwitch。例如packedSwitch形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
.method private packedSwitch(I)Ljava/lang/String;
.registers 3
.param p1, "i" # I

.prologue
.line 21
const/4 v0, 0x0

.line 22
.local v0, "str":Ljava/lang/String;
packed-switch p1, :pswitch_data_14

.line 36
const-string v0, "she is a person"

.line 39
:goto_6
return-object v0

.line 24
:pswitch_7
const-string v0, "she is a baby"

.line 25
goto :goto_6

.line 27
:pswitch_a
const-string v0, "she is a girl"

.line 28
goto :goto_6

.line 30
:pswitch_d
const-string v0, "she is a woman"

.line 31
goto :goto_6

.line 33
:pswitch_10
const-string v0, "she is an obasan"

.line 34
goto :goto_6

.line 22
nop

:pswitch_data_14
.packed-switch 0x0
:pswitch_7
:pswitch_a
:pswitch_d
:pswitch_10
.end packed-switch
.end method

packed-switch指令前面讲过,结构格式如下,用二进制编辑器可分析.dex文件,这里不展开。

1
2
3
4
5
6
struct packed-switch-payload {
ushort ident; //固定0x0100
ushort size; //case数目
int first_key; //初始case值
int[] targets; //每个case相对switch指令处偏移
};

反汇编结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private String packedSwitch(int i) {
switch (i) {
case 0:
return "she is a baby";
case 1:
return "she is a girl";
case 2:
return "she is a woman";
case 3:
return "she is an obasan";
default:
return "she is a person";
}
}

sparseSwitch形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
.method private sparseSwitch(I)Ljava/lang/String;
.registers 3
.param p1, "age" # I

.prologue
.line 43
const/4 v0, 0x0

.line 44
.local v0, "str":Ljava/lang/String;
sparse-switch p1, :sswitch_data_14

.line 58
const-string v0, "he is a person"

.line 61
:goto_6
return-object v0

.line 46
:sswitch_7
const-string v0, "he is a baby"

.line 47
goto :goto_6

.line 49
:sswitch_a
const-string v0, "he is a student"

.line 50
goto :goto_6

.line 52
:sswitch_d
const-string v0, "he is a father"

.line 53
goto :goto_6

.line 55
:sswitch_10
const-string v0, "he is a grandpa"

.line 56
goto :goto_6

.line 44
nop

:sswitch_data_14
.sparse-switch
0x5 -> :sswitch_7
0xf -> :sswitch_a
0x23 -> :sswitch_d
0x41 -> :sswitch_10
.end sparse-switch
.end method

sparse-switch指令上面也讲过,格式为:

1
2
3
4
5
6
struct sparse-switch-payload{
ushort ident; //固定0x0200
ushort size; //case数目
int[] keys; //每个case值 从低到高
int[] targets; //每个case相对switch指令处偏移
};

对于try/catch语句有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
.method private tryCatch(ILjava/lang/String;)V
.registers 13
.param p1, "drumsticks" # I
.param p2, "peple" # Ljava/lang/String;

.prologue
const/4 v9, 0x0

.line 19
:try_start_1
invoke-static {p2}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I
:try_end_4
.catch Ljava/lang/NumberFormatException; {:try_start_1 .. :try_end_4} :catch_45
move-result v1

.line 21
.local v1, "i":I
:try_start_5
div-int v2, p1, v1

.line 22
.local v2, "m":I
mul-int v5, v2, v1
sub-int v3, p1, v5

.line 23
.local v3, "n":I
const-string v5, "共有%d只鸡腿,%d个人平分,每人可分得%d只,还剩下%d只"
const/4 v6, 0x4
new-array v6, v6, [Ljava/lang/Object;
const/4 v7, 0x0

.line 24
invoke-static {p1}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
move-result-object v8
aput-object v8, v6, v7
const/4 v7, 0x1
invoke-static {v1}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
move-result-object v8
aput-object v8, v6, v7
const/4 v7, 0x2
invoke-static {v2}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
move-result-object v8
aput-object v8, v6, v7
const/4 v7, 0x3
invoke-static {v3}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
move-result-object v8
aput-object v8, v6, v7

.line 23
invoke-static {v5, v6}, Ljava/lang/String;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
move-result-object v4

.line 25
.local v4, "str":Ljava/lang/String;
const/4 v5, 0x0
invoke-static {p0, v4, v5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v5
invoke-virtual {v5}, Landroid/widget/Toast;->show()V
:try_end_38
.catch Ljava/lang/ArithmeticException; {:try_start_5 .. :try_end_38} :catch_39
.catch Ljava/lang/NumberFormatException; {:try_start_5 .. :try_end_38} :catch_45

.line 33
.end local v1 # "i":I
.end local v2 # "m":I
.end local v3 # "n":I
.end local v4 # "str":Ljava/lang/String;
:goto_38
return-void

.line 26
.restart local v1 # "i":I
:catch_39
move-exception v0

.line 27
.local v0, "e":Ljava/lang/ArithmeticException;
:try_start_3a
const-string v5, "人数不能为0"
const/4 v6, 0x0
invoke-static {p0, v5, v6}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v5
invoke-virtual {v5}, Landroid/widget/Toast;->show()V
:try_end_44
.catch Ljava/lang/NumberFormatException; {:try_start_3a .. :try_end_44} :catch_45
goto :goto_38

.line 29
.end local v0 # "e":Ljava/lang/ArithmeticException;
.end local v1 # "i":I
:catch_45
move-exception v0

.line 30
.local v0, "e":Ljava/lang/NumberFormatException;
const-string v5, "无效的数值字符串"
invoke-static {p0, v5, v9}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v5
invoke-virtual {v5}, Landroid/widget/Toast;->show()V
goto :goto_38
.end method

反汇编结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void tryCatch(int drumsticks, String peple) {
try {
int i = Integer.parseInt(peple);
try {
int m = drumsticks / i;
int n = drumsticks - (m * i);
String str = String.format("共有%d只鸡腿,%d个人平分,每人可分得%d只,还剩下%d只", Integer.valueOf(drumsticks), Integer.valueOf(i), Integer.valueOf(m), Integer.valueOf(n));
Toast.makeText(this, str, 0).show();
} catch (ArithmeticException e) {
Toast.makeText(this, "人数不能为0", 0).show();
}
} catch (NumberFormatException e2) {
Toast.makeText(this, "无效的数值字符串", 0).show();
}
}

ARM入门

概述

ARM有31个通用寄存器,6个状态寄存器。其中通用寄存器分为16种,分别为R0到R15,状态寄存器分为CPSR和SPSR两种。

通用寄存器:

寄存器 描述
R0~R3 传入函数参数,传出函数返回值
R4~R11 函数局部变量
R12 内部调用暂时寄存器,被调用函数返回前不必恢复
R13 栈指针寄存器SP,存放堆栈栈顶地址
R14 链接寄存器LR,存放子程序返回地址
R15 程序计数器PC,保存当前正在执行的指令在内存中的地址

状态寄存器:

寄存器 描述
CPSR一个 状态寄存器。保存程序当前状态
SPSR五个 备份程序状态寄存器,异常返回后回复异常发生时的工作状态

汇编基础

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
.text:000082E0                 ; int __fastcall main(int argc, const char **argv, const char **envp)
.text:000082E0 EXPORT main
.text:000082E0 main ; CODE XREF: j_main↓j
.text:000082E0
.text:000082E0 var_C = -0xC
.text:000082E0 var_8 = -8
.text:000082E0
.text:000082E0 000 00 48 2D E9 PUSH {R11,LR} ; Push registers
.text:000082E4 008 04 B0 8D E2 ADD R11, SP, #4 ; Rd = Op1 + Op2
.text:000082E8 008 08 D0 4D E2 SUB SP, SP, #8 ; Rd = Op1 - Op2
.text:000082EC 010 08 00 0B E5 STR R0, [R11,#var_8] ; Store to Memory
.text:000082F0 010 0C 10 0B E5 STR R1, [R11,#var_C] ; Store to Memory
.text:000082F4 010 18 30 9F E5 LDR R3, =(aHelloArm - 0x8300) ; Load from Memory
.text:000082F8 010 03 30 8F E0 ADD R3, PC, R3 ; Rd = Op1 + Op2
.text:000082FC 010 03 00 A0 E1 MOV R0, R3 ; s
.text:00008300 010 ED FF FF EB BL puts ; Branch with Link
.text:00008304 010 00 30 A0 E3 MOV R3, #0 ; Rd = Op2
.text:00008308 010 03 00 A0 E1 MOV R0, R3 ; Rd = Op2
.text:0000830C 010 04 D0 4B E2 SUB SP, R11, #4 ; Rd = Op1 - Op2
.text:00008310 008 00 88 BD E8 POP {R11,PC} ; Pop registers
.text:00008310 ; End of function main
.text:00008310
.text:00008310 ; ---------------------------------------------------------------------------
.text:00008314 50 00 00 00 off_8314 DCD aHelloArm - 0x8300 ; DATA XREF: main+14↑r
.text:00008314 ; "Hello ARM!"

ARM处理器有ARM和Thumb两种状态,处理器可在这两种之间随意切换。处于ARM状态时32位字对齐,Thumb状态16位对齐。Thumb状态下FP、IP、SP、LP和PC对应ARM状态下R11、R12、R13、R14和R15。IDA动调时没法区分这两种,得手动切换。

指令条件码cond:

条件码 标志 含义
EQ Z\=1 相等
NE Z\=0 不相等
CS/HS C\=1 无符号大于等于
CC/LO C\=0 无符号小于
MI N\=1 负数
PL N\=0 正数或0
VS V\=1 溢出
VC V\=0 无溢出
HI C\=1,Z\=0 无符号大于
LS C\=0,Z\=1 无符号小于等于
GE N\=V 符号大于等于
LT N!\=V 符号小于
GT Z\=0,N\=V 符号大于
LE Z\=1,N!\=V 符号小于等于
AL 任何 无条件

后缀S表示影响CPSR的值,操作数据大小type有:

type 含义
B 无符号字节
SB 有符号字节
H 无符号半字
SH 有符号半字

递增含义addr_mode:

addr_mode 含义
IA 执行后增加
IB 执行前增加
DA 执行后减少
DB 执行前减少
FD 满递减堆栈
FA 满递增堆栈
ED 空递减堆栈
EA 空递增堆栈

常用汇编:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
B{cond} label @cond满足则跳到label继续执行
BL{cond} label @先将下一条指令地址拷贝到R14即LR中 再跳转
BX{cond} Rm @cond满足 若Rm[0]为1则跳转且CPSR置位T标志 切换至Thumb状态 否则CPSR标志T复位 切换到Thumb状态
BLX{cond} Rm

LDR{type}{cond} Rd,label
LDRD{cond} Rd,Rd2,label
STR{type}{cond} Rd,label
STRD{cond} Rd,Rd2,label
LDM{addr_mode}{cond} Rn{!} reglist
STM{addr_mode}{cond} Rn{!} reglist
PUSH{cond} reglist
POP{cond} reglist
SWP{B}{cond} Rd,Rm,[Rn]

MOV{cond}{S} Rd,operand2
MVN{cond}{S} Rd,operand2 @按位取反后MOV
ADD{cond}{S} Rd,Rn,operand2
ADC{cond}{S} Rd,Rn,operand2
SUB{cond}{S} Rd,Rn,operand2
RSB{cond}{S} Rd,Rn,operand2 @Rd=operand2-Rn
SBC{cond}{S} Rd,Rn,operand2
RSC{cond}{S} Rd,Rn,operand2
MUL{cond}{S} Rd,Rm,Rn @Rd=Rm*Rn
@其他MLA UMULL UMLAL SMULL SMLAL SMLAD SMLSD SDIV UDIV ASR ADD ORR EOR BIC LSL LSR ROR RRX CMP CMN TST TEQ

SWI{cond},immed_24 @软中断
NOP
MRS Rd,psr
MSR Rd,psr_fields,operand2

Android原生逆向

常见函数结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
;函数头
STMFD SP1,{FP,LR} ;栈帧指针FP和
ADD R11,SP,#0 ;设置栈帧起始位置
SUB SP,SP,#16 ;为栈中程序变量分配存储空间

;函数体
MOV R3,#5
STR R3,[SP]
MOV R0,#1
MOV R1,#2
MOV R2,#3
MOV R3,#4
BL SUM

;函数尾
MOV R0,R0
MOV R0,R3
SUB SP,SP,#4
LDMFD SP1,{FP,PC}

例如展示了两种for循环的调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
.text:000085FC                 ; int __fastcall main(int argc, const char **argv, const char **envp)
.text:000085FC main ; CODE XREF: j_main↑j
.text:000085FC ; __unwind {
.text:000085FC 000 10 40 2D E9 PUSH {R4,LR} ; Push registers
.text:00008600 008 05 00 A0 E3 MOV R0, #5 ; Rd = Op2
.text:00008604 008 E1 FF FF EB BL sub_8590 ; Branch with Link
.text:00008608 008 00 10 A0 E1 MOV R1, R0 ; Rd = Op2
.text:0000860C 008 24 00 9F E5 LDR R0, =(aFor1D - 0x8618) ; Load from Memory
.text:00008610 008 00 00 8F E0 ADD R0, PC, R0 ; Rd = Op1 + Op2
.text:00008614 008 BC FF FF EB BL printf ; Branch with Link
.text:00008618 008 05 00 A0 E3 MOV R0, #5 ; Rd = Op2
.text:0000861C 008 E6 FF FF EB BL sub_85BC ; Branch with Link
.text:00008620 008 00 10 A0 E1 MOV R1, R0 ; Rd = Op2
.text:00008624 008 10 00 9F E5 LDR R0, =(aFor2D - 0x8630) ; Load from Memory
.text:00008628 008 00 00 8F E0 ADD R0, PC, R0 ; Rd = Op1 + Op2
.text:0000862C 008 B6 FF FF EB BL printf ; Branch with Link
.text:00008630 008 00 00 A0 E3 MOV R0, #0 ; Rd = Op2
.text:00008634 008 10 80 BD E8 POP {R4,PC} ; Pop registers
.text:00008634 ; End of function main
.text:00008634
.text:00008634 ; ---------------------------------------------------------------------------
.text:00008638 18 15 00 00 off_8638 DCD aFor1D - 0x8618 ; DATA XREF: main+10↑r
.text:00008638 ; "for1:%d\n"
.text:0000863C 10 15 00 00 off_863C DCD aFor2D - 0x8630 ; DATA XREF: main+28↑r
.text:0000863C ; } // starts at 85FC ; "for2:%d\n"

源码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
int nums[5] = {1, 2, 3, 4, 5};
int for1(int n){ //普通for循环
int i = 0;
int s = 0;
for (i = 0; i < n; i++){
s += i * 2;
}
return s;
}
int for2(int n){ //访问全局数组
int i = 0;
int s = 0;
for (i = 0; i < n; i++){
s += i * i + nums[n-1];
}
return s;
}
int main(int argc, char* argv[]){
printf("for1:%d\n", for1(5));
printf("for2:%d\n", for2(5));
return 0;
}

这里看第二种for:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.text:000085BC     ; int __fastcall sub_85BC(int)
.text:000085BC sub_85BC ; CODE XREF: main+20↓p
.text:000085BC ; __unwind {
.text:000085BC 000 SUBS R1, R0, #0 ; Rd = Op1 - Op2
.text:000085C0 000 MOVLE R0, #0 ; Rd = Op2
.text:000085C4 000 BXLE LR ; Branch to/from Thumb mode
.text:000085C8 000 LDR R3, =(__data_start_ptr - 0x85DC) ; Load from Memory
.text:000085CC 000 SUB R2, R1, #1 ; Rd = Op1 - Op2
.text:000085D0 000 MOV R0, #0 ; Rd = Op2
.text:000085D4 000 LDR R3, [PC,R3] ; __data_start
.text:000085D8 000 LDR R12, [R3,R2,LSL#2] ; Load from Memory
.text:000085DC 000 MOV R3, R0 ; Rd = Op2
.text:000085E0
.text:000085E0 loc_85E0 ; CODE XREF: sub_85BC+34↓j
.text:000085E0 000 MLA R2, R3, R3, R12 ; Multiply-Accumulate
.text:000085E4 000 ADD R3, R3, #1 ; Rd = Op1 + Op2
.text:000085E8 000 CMP R3, R1 ; Set cond. codes on Op1 - Op2
.text:000085EC 000 ADD R0, R0, R2 ; Rd = Op1 + Op2
.text:000085F0 000 BNE loc_85E0 ; Branch
.text:000085F4 000 BX LR ; Branch to/from Thumb mode
.text:000085F4 ; End of function sub_85BC
.text:000085F4
.text:000085F4 ; ---------------------------------------------------------------------------
.text:000085F8 off_85F8 DCD __data_start_ptr - 0x85DC
.text:000085F8 ; DATA XREF: sub_85BC+C↑r
.text:000085F8 ; } // starts at 85BC

再如if语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
void if1(int n){ //if else语句
if(n < 10){
printf("the number less than 10\n");
} else {
printf("the number greater than or equal to 10\n");
}
}
void if2(int n){ //多重if else语句
if(n < 16){
printf("he is a boy\n");
} else if(n < 30){
printf("he is a young man\n");
} else if(n < 45){
printf("he is a strong man\n");
} else{
printf("he is an old man\n");
}
}
int main(int argc, char* argv[]){
if1(5);
if2(35);
return 0;
}

if1为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.text:000085C8                 sub_85C8                                ; CODE XREF: main+8↓p
.text:000085C8 ; __unwind {
.text:000085C8 000 09 00 50 E3 CMP R0, #9 ; Set cond. codes on Op1 - Op2
.text:000085CC 000 02 00 00 DA BLE loc_85DC ; Branch
.text:000085D0 000 10 00 9F E5 LDR R0, =(aTheNumberGreat - 0x85DC) ; Load from Memory
.text:000085D4 000 00 00 8F E0 ADD R0, PC, R0 ; Rd = Op1 + Op2
.text:000085D8 000 C9 FF FF EA B puts ; Branch
.text:000085DC ; ---------------------------------------------------------------------------
.text:000085DC
.text:000085DC loc_85DC ; CODE XREF: sub_85C8+4↑j
.text:000085DC 000 08 00 9F E5 LDR R0, =(aTheNumberLessT - 0x85E8) ; Load from Memory
.text:000085E0 000 00 00 8F E0 ADD R0, PC, R0 ; Rd = Op1 + Op2
.text:000085E4 000 C6 FF FF EA B puts ; Branch
.text:000085E4 ; End of function sub_85C8
.text:000085E4
.text:000085E4 ; ---------------------------------------------------------------------------
.text:000085E8 94 15 00 00 off_85E8 DCD aTheNumberGreat - 0x85DC
.text:000085E8 ; DATA XREF: sub_85C8+8↑r
.text:000085E8 ; "the number greater than or equal to 10"
.text:000085EC 70 15 00 00 off_85EC DCD aTheNumberLessT - 0x85E8
.text:000085EC ; DATA XREF: sub_85C8:loc_85DC↑r
.text:000085EC ; } // starts at 85C8 ; "the number less than 10"

if2为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
.text:00008570                 sub_8570                                ; CODE XREF: main+10↓p
.text:00008570 ; __unwind {
.text:00008570 000 0F 00 50 E3 CMP R0, #0xF ; Set cond. codes on Op1 - Op2
.text:00008574 000 09 00 00 DA BLE loc_85A0 ; Branch
.text:00008578 000 1D 00 50 E3 CMP R0, #0x1D ; Set cond. codes on Op1 - Op2
.text:0000857C 000 0A 00 00 DA BLE loc_85AC ; Branch
.text:00008580 000 2C 00 50 E3 CMP R0, #0x2C ; ',' ; Set cond. codes on Op1 - Op2
.text:00008584 000 02 00 00 DA BLE loc_8594 ; Branch
.text:00008588 000 28 00 9F E5 LDR R0, =(aHeIsAnOldMan - 0x8594) ; Load from Memory
.text:0000858C 000 00 00 8F E0 ADD R0, PC, R0 ; Rd = Op1 + Op2
.text:00008590 000 DB FF FF EA B puts ; Branch
.text:00008594 ; ---------------------------------------------------------------------------
.text:00008594
.text:00008594 loc_8594 ; CODE XREF: sub_8570+14↑j
.text:00008594 000 20 00 9F E5 LDR R0, =(aHeIsAStrongMan - 0x85A0) ; Load from Memory
.text:00008598 000 00 00 8F E0 ADD R0, PC, R0 ; Rd = Op1 + Op2
.text:0000859C 000 D8 FF FF EA B puts ; Branch
.text:000085A0 ; ---------------------------------------------------------------------------
.text:000085A0
.text:000085A0 loc_85A0 ; CODE XREF: sub_8570+4↑j
.text:000085A0 000 18 00 9F E5 LDR R0, =(aHeIsABoy - 0x85AC) ; Load from Memory
.text:000085A4 000 00 00 8F E0 ADD R0, PC, R0 ; Rd = Op1 + Op2
.text:000085A8 000 D5 FF FF EA B puts ; Branch
.text:000085AC ; ---------------------------------------------------------------------------
.text:000085AC
.text:000085AC loc_85AC ; CODE XREF: sub_8570+C↑j
.text:000085AC 000 10 00 9F E5 LDR R0, =(aHeIsAYoungMan - 0x85B8) ; Load from Memory
.text:000085B0 000 00 00 8F E0 ADD R0, PC, R0 ; Rd = Op1 + Op2
.text:000085B4 000 D2 FF FF EA B puts ; Branch
.text:000085B4 ; End of function sub_8570
.text:000085B4
.text:000085B4 ; ---------------------------------------------------------------------------
.text:000085B8 AC 15 00 00 off_85B8 DCD aHeIsAnOldMan - 0x8594
.text:000085B8 ; DATA XREF: sub_8570+18↑r
.text:000085B8 ; "he is an old man"
.text:000085BC 88 15 00 00 off_85BC DCD aHeIsAStrongMan - 0x85A0
.text:000085BC ; DATA XREF: sub_8570:loc_8594↑r
.text:000085BC ; "he is a strong man"
.text:000085C0 54 15 00 00 off_85C0 DCD aHeIsABoy - 0x85AC ; DATA XREF: sub_8570:loc_85A0↑r
.text:000085C0 ; "he is a boy"
.text:000085C4 58 15 00 00 off_85C4 DCD aHeIsAYoungMan - 0x85B8
.text:000085C4 ; DATA XREF: sub_8570:loc_85AC↑r
.text:000085C4 ; } // starts at 8570 ; "he is a young man"

再例如while循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
int dowhile(int n){
int i = 1;
int s = 0;
do{
s += i;
}while(i++ < n);
return s;
}
int whiledo(int n){
int i = 1;
int s = 0;
while(i <= n){
s += i++;
}
return s;
}
int main(int argc, char* argv[]){
printf("dowhile:%d\n", dowhile(100));
printf("while:%d\n", whiledo(100));
return 0;
}

dowhile有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
.text:00008570                 ; int __fastcall sub_8570(int)
.text:00008570 sub_8570 ; CODE XREF: main+8↓p
.text:00008570 ; __unwind {
.text:00008570 000 00 20 A0 E3 MOV R2, #0 ; Rd = Op2
.text:00008574 000 01 30 A0 E3 MOV R3, #1 ; Rd = Op2
.text:00008578
.text:00008578 loc_8578 ; CODE XREF: sub_8570+18↓j
.text:00008578 000 03 20 82 E0 ADD R2, R2, R3 ; Rd = Op1 + Op2
.text:0000857C 000 01 30 83 E2 ADD R3, R3, #1 ; Rd = Op1 + Op2
.text:00008580 000 01 10 43 E2 SUB R1, R3, #1 ; Rd = Op1 - Op2
.text:00008584 000 01 00 50 E1 CMP R0, R1 ; Set cond. codes on Op1 - Op2
.text:00008588 000 FA FF FF CA BGT loc_8578 ; Branch
.text:0000858C 000 02 00 A0 E1 MOV R0, R2 ; Rd = Op2
.text:00008590 000 1E FF 2F E1 BX LR ; Branch to/from Thumb mode
.text:00008590 ; } // starts at 8570
.text:00008590 ; End of function sub_8570
.text:00008590
.text:00008594
.text:00008594 ; =============== S U B R O U T I N E =======================================
.text:00008594
.text:00008594
.text:00008594 ; int __fastcall sub_8594(_DWORD)
.text:00008594 sub_8594 ; CODE XREF: main+20↓p
.text:00008594 ; __unwind {
.text:00008594 000 00 20 50 E2 SUBS R2, R0, #0 ; Rd = Op1 - Op2
.text:00008598 000 00 00 A0 D3 MOVLE R0, #0 ; Rd = Op2
.text:0000859C 000 1E FF 2F D1 BXLE LR ; Branch to/from Thumb mode
.text:000085A0 000 00 00 A0 E3 MOV R0, #0 ; Rd = Op2
.text:000085A4 000 01 30 A0 E3 MOV R3, #1 ; Rd = Op2
.text:000085A8
.text:000085A8 loc_85A8 ; CODE XREF: sub_8594+20↓j
.text:000085A8 000 03 00 80 E0 ADD R0, R0, R3 ; Rd = Op1 + Op2
.text:000085AC 000 01 30 83 E2 ADD R3, R3, #1 ; Rd = Op1 + Op2
.text:000085B0 000 03 00 52 E1 CMP R2, R3 ; Set cond. codes on Op1 - Op2
.text:000085B4 000 FB FF FF AA BGE loc_85A8 ; Branch
.text:000085B8 000 1E FF 2F E1 BX LR ; Branch to/from Thumb mode
.text:000085B8 ; } // starts at 8594
.text:000085B8 ; End of function sub_8594

switch语句如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
int switch1(int a, int b, int i){
switch (i){
case 1:
return a + b;
break;
case 2:
return a - b;
break;
case 3:
return a * b;
break;
case 4:
return a / b;
break;
default:
return a + b;
break;
}
}
int main(int argc, char* argv[]){
printf("switch1:%d\n", switch1(3, 5, 3));
return 0;
}

结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
.text:000085A0                 ; int __fastcall sub_85A0(int, int, int)
.text:000085A0 sub_85A0 ; CODE XREF: main+10↓p
.text:000085A0 ; __unwind {
.text:000085A0 000 01 20 42 E2 SUB R2, R2, #1 ; Rd = Op1 - Op2
.text:000085A4 000 10 40 2D E9 PUSH {R4,LR} ; Push registers
.text:000085A8 008 00 30 A0 E1 MOV R3, R0 ; Rd = Op2
.text:000085AC 008 03 00 52 E3 CMP R2, #3 ; switch 4 cases
.text:000085B0 008 02 F1 8F 90 ADDLS PC, PC, R2,LSL#2 ; switch jump switch语句的标志
.text:000085B4 ; ---------------------------------------------------------------------------
.text:000085B4
.text:000085B4 loc_85B4 ; CODE XREF: sub_85A0+10↑j
.text:000085B4 008 0B 00 00 EA B def_85B0 ; jumptable 000085B0 default case
.text:000085B8 ; ---------------------------------------------------------------------------
.text:000085B8
.text:000085B8 loc_85B8 ; CODE XREF: sub_85A0+10↑j
.text:000085B8 008 08 00 00 EA B loc_85E0 ; jumptable 000085B0 case 0
.text:000085BC ; ---------------------------------------------------------------------------
.text:000085BC
.text:000085BC loc_85BC ; CODE XREF: sub_85A0+10↑j
.text:000085BC 008 05 00 00 EA B loc_85D8 ; jumptable 000085B0 case 1
.text:000085C0 ; ---------------------------------------------------------------------------
.text:000085C0
.text:000085C0 loc_85C0 ; CODE XREF: sub_85A0+10↑j
.text:000085C0 008 02 00 00 EA B loc_85D0 ; jumptable 000085B0 case 2
.text:000085C4 ; ---------------------------------------------------------------------------
.text:000085C4
.text:000085C4 loc_85C4 ; CODE XREF: sub_85A0+10↑j
.text:000085C4 008 FF FF FF EA B loc_85C8 ; jumptable 000085B0 case 3
.text:000085C8 ; ---------------------------------------------------------------------------
.text:000085C8
.text:000085C8 loc_85C8 ; CODE XREF: sub_85A0+10↑j
.text:000085C8 ; sub_85A0:loc_85C4↑j
.text:000085C8 008 14 00 00 EB BL sub_8620 ; jumptable 000085B0 case 3
.text:000085CC 008 10 80 BD E8 POP {R4,PC} ; Pop registers
.text:000085D0 ; ---------------------------------------------------------------------------
.text:000085D0
.text:000085D0 loc_85D0 ; CODE XREF: sub_85A0+10↑j
.text:000085D0 ; sub_85A0:loc_85C0↑j
.text:000085D0 008 93 01 00 E0 MUL R0, R3, R1 ; jumptable 000085B0 case 2
.text:000085D4 008 10 80 BD E8 POP {R4,PC} ; Pop registers
.text:000085D8 ; ---------------------------------------------------------------------------
.text:000085D8
.text:000085D8 loc_85D8 ; CODE XREF: sub_85A0+10↑j
.text:000085D8 ; sub_85A0:loc_85BC↑j
.text:000085D8 008 00 00 61 E0 RSB R0, R1, R0 ; jumptable 000085B0 case 1
.text:000085DC 008 10 80 BD E8 POP {R4,PC} ; Pop registers
.text:000085E0 ; ---------------------------------------------------------------------------
.text:000085E0
.text:000085E0 loc_85E0 ; CODE XREF: sub_85A0+10↑j
.text:000085E0 ; sub_85A0:loc_85B8↑j
.text:000085E0 008 00 00 81 E0 ADD R0, R1, R0 ; jumptable 000085B0 case 0
.text:000085E4 008 10 80 BD E8 POP {R4,PC} ; Pop registers
.text:000085E8 ; ---------------------------------------------------------------------------
.text:000085E8
.text:000085E8 def_85B0 ; CODE XREF: sub_85A0+10↑j
.text:000085E8 ; sub_85A0:loc_85B4↑j
.text:000085E8 008 00 00 81 E0 ADD R0, R1, R0 ; jumptable 000085B0 default case
.text:000085EC 008 10 80 BD E8 POP {R4,PC} ; Pop registers
.text:000085EC ; } // starts at 85A0
.text:000085EC ; End of function sub_85A0

再例如C++的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdio.h>
class aclass{
private:
int m;
char c;
public:
aclass(int i, char ch) {
printf("Constructor called.\n");
this->m = i;
this->c = ch;
}
~aclass() {//定义析构函数
printf("Destructor called.\n");
}
int getM() const {
return m;
}
void setM(int m) {
this->m = m;
}
char getC() const{
return c;
}
void setC(char c) {
this->c = c;
}
int add(int a, int b) {
printf("%d\n", a+b);
}
};
int main(int argc, char* argv[]){
aclass *a = new aclass(3, 'c');
a->setM(5);
a->setC('a');
a->add(2, 8);
printf("%d\n", a->getM());
delete a;
return 0;
}

结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
.text:00008600                 ; int __fastcall main(int argc, const char **argv, const char **envp)
.text:00008600 main ; CODE XREF: j_main↑j
.text:00008600 ; __unwind {
.text:00008600 000 70 40 2D E9 PUSH {R4-R6,LR} ; Push registers
.text:00008604 010 08 00 A0 E3 MOV R0, #8 ; unsigned int
.text:00008608 010 EC FF FF EB BL _Znwj ; Branch with Link new操作符
.text:0000860C 010 00 40 A0 E1 MOV R4, R0 ; Rd = Op2
.text:00008610 010 50 00 9F E5 LDR R0, =(aConstructorCal - 0x8620) ; Load from Memory
.text:00008614 010 50 50 9F E5 LDR R5, =(aD - 0x862C) ; Load from Memory
.text:00008618 010 00 00 8F E0 ADD R0, PC, R0 ; Rd = Op1 + Op2
.text:0000861C 010 D5 FF FF EB BL puts ; Branch with Link
.text:00008620 010 05 30 A0 E3 MOV R3, #5 ; Rd = Op2
.text:00008624 010 05 50 8F E0 ADD R5, PC, R5 ; Rd = Op1 + Op2
.text:00008628 010 00 30 84 E5 STR R3, [R4] ; Store to Memory
.text:0000862C 010 61 30 A0 E3 MOV R3, #0x61 ; 'a' ; Rd = Op2
.text:00008630 010 04 30 C4 E5 STRB R3, [R4,#4] ; Store to Memory
.text:00008634 010 0A 10 A0 E3 MOV R1, #0xA ; Rd = Op2
.text:00008638 010 05 00 A0 E1 MOV R0, R5 ; format
.text:0000863C 010 C4 FF FF EB BL printf ; Branch with Link
.text:00008640 010 00 10 94 E5 LDR R1, [R4] ; Load from Memory
.text:00008644 010 05 00 A0 E1 MOV R0, R5 ; format
.text:00008648 010 C1 FF FF EB BL printf ; Branch with Link
.text:0000864C 010 1C 00 9F E5 LDR R0, =(aDestructorCall - 0x8658) ; Load from Memory
.text:00008650 010 00 00 8F E0 ADD R0, PC, R0 ; Rd = Op1 + Op2
.text:00008654 010 C7 FF FF EB BL puts ; Branch with Link
.text:00008658 010 04 00 A0 E1 MOV R0, R4 ; void *
.text:0000865C 010 C8 FF FF EB BL _ZdlPv ; Branch with Link
.text:00008660 010 00 00 A0 E3 MOV R0, #0 ; Rd = Op2
.text:00008664 010 70 80 BD E8 POP {R4-R6,PC} ; Pop registers
.text:00008664 ; End of function main
.text:00008664
.text:00008664 ; ---------------------------------------------------------------------------
.text:00008668 48 15 00 00 off_8668 DCD aConstructorCal - 0x8620
.text:00008668 ; DATA XREF: main+10↑r
.text:00008668 ; "Constructor called."
.text:0000866C 54 15 00 00 off_866C DCD aD - 0x862C ; DATA XREF: main+14↑r
.text:0000866C ; "%d\n"
.text:00008670 30 15 00 00 off_8670 DCD aDestructorCall - 0x8658
.text:00008670 ; DATA XREF: main+4C↑r
.text:00008670 ; } // starts at 8600 ; "Destructor called."

静态链接STL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
using namespace std;
class aclass{
private:
int m;
char c;
public:
aclass(int i, char ch) {
cout<<"Constructor called."<<endl;
this->m = i;
this->c = ch;
}
~aclass() {//定义析构函数
cout<<"Destructor called."<<endl;
}
int getM() const {
return m;
}
void setM(int m) {
this->m = m;
}
char getC() const{
return c;
}
void setC(char c) {
this->c = c;
}
int add(int a, int b) {
cout<<a+b;
}
};
int main(int argc, char* argv[]){
aclass *a = new aclass(3, 'c');
a->setM(5);
a->setC('a');
a->add(2, 8);
//printf("%d\n", a->getM());
cout<<a->getM()<<endl;
delete a;
return 0;
}

结果如下,C++符号是自己手补的,一般IDA只能恢复动态链接符号,静态链接不行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
.text:0000998C                 ; int __fastcall main(int argc, const char **argv, const char **envp)
.text:0000998C main ; CODE XREF: j_main↑j
.text:0000998C ; __unwind {
.text:0000998C 000 F0 47 2D E9 PUSH {R4-R10,LR} ; Push registers
.text:00009990 020 08 00 A0 E3 MOV R0, #8 ; unsigned int
.text:00009994 020 C0 93 00 EB BL _Znwj ; Branch with Link
.text:00009998 020 7C 41 9F E5 LDR R4, =(_GLOBAL_OFFSET_TABLE_ - 0x99AC) ; Load from Memory
.text:0000999C 020 7C 61 9F E5 LDR R6, =(off_3D990 - 0x3D6D4) ; Load from Memory
.text:000099A0 020 7C 11 9F E5 LDR R1, =(aConstructorCal - 0x99C0) ; Load from Memory
.text:000099A4 020 04 40 8F E0 ADD R4, PC, R4 ; _GLOBAL_OFFSET_TABLE_
.text:000099A8 020 06 50 94 E7 LDR R5, [R4,R6] ; dword_3DDAC
.text:000099AC 020 00 70 A0 E1 MOV R7, R0 ; Rd = Op2 aclass *a
.text:000099B0 020 13 20 A0 E3 MOV R2, #0x13 ; Rd = Op2 需要输出的字符个数
.text:000099B4 020 05 00 A0 E1 MOV R0, R5 ; Rd = Op2
.text:000099B8 020 01 10 8F E0 ADD R1, PC, R1 ; Rd = Op1 + Op2
.text:000099BC 020 17 44 00 EB BL ZSt16__ostream_insertIcSt11char_traitsIcEERSt13 ; Branch with Link cout
.text:000099C0 020 00 30 95 E5 LDR R3, [R5] ; Load from Memory
.text:000099C4 020 0C 30 13 E5 LDR R3, [R3,#-0xC] ; Load from Memory
.text:000099C8 020 03 30 85 E0 ADD R3, R5, R3 ; Rd = Op1 + Op2
.text:000099CC 020 7C 80 93 E5 LDR R8, [R3,#0x7C] ; Load from Memory
.text:000099D0 020 00 00 58 E3 CMP R8, #0 ; Set cond. codes on Op1 - Op2 判断返回的cout是否为空
.text:000099D4 020 4F 00 00 0A BEQ throw_badcast ; Branch
.text:000099D8 020 1C 30 D8 E5 LDRB R3, [R8,#0x1C] ; Load from Memory
.text:000099DC 020 00 00 53 E3 CMP R3, #0 ; Set cond. codes on Op1 - Op2
.text:000099E0 020 27 10 D8 15 LDRBNE R1, [R8,#0x27] ; Load from Memory
.text:000099E4 020 07 00 00 1A BNE loc_9A08 ; Branch
.text:000099E8 020 08 00 A0 E1 MOV R0, R8 ; Rd = Op2
.text:000099EC 020 D2 00 00 EB BL _ZNKSt5ctypeIcE13_M_widen_initEv ; Branch with Link
.text:000099F0 020 0A 10 A0 E3 MOV R1, #0xA ; Rd = Op2
.text:000099F4 020 08 00 A0 E1 MOV R0, R8 ; Rd = Op2
.text:000099F8 020 00 30 98 E5 LDR R3, [R8] ; Load from Memory
.text:000099FC 020 0F E0 A0 E1 MOV LR, PC ; Rd = Op2
.text:00009A00 020 18 F0 93 E5 LDR PC, [R3,#0x18] ; Indirect Jump
.text:00009A04 020 00 10 A0 E1 MOV R1, R0 ; Rd = Op2
.text:00009A08
.text:00009A08 loc_9A08 ; CODE XREF: main+58↑j
.text:00009A08 020 06 80 94 E7 LDR R8, [R4,R6] ; dword_3DDAC
.text:00009A0C 020 08 00 A0 E1 MOV R0, R8 ; Rd = Op2 以下这两行为cout<<endl;
.text:00009A10 020 17 43 00 EB BL _ZNSo3putEc ; Branch with Link std::ostream::put(char)
.text:00009A14 020 A2 42 00 EB BL _ZNSo5flushEv ; Branch with Link std::ostream:flush(void)
.text:00009A18 020 05 30 A0 E3 MOV R3, #5 ; Rd = Op2 5
.text:00009A1C 020 00 30 87 E5 STR R3, [R7] ; Store to Memory a->setM(5)
.text:00009A20 020 61 30 A0 E3 MOV R3, #0x61 ; 'a' ; Rd = Op2 'a'
.text:00009A24 020 04 30 C7 E5 STRB R3, [R7,#4] ; Store to Memory a->setC('a')
.text:00009A28 020 0A 10 A0 E3 MOV R1, #0xA ; Rd = Op2 a+b=10
.text:00009A2C 020 08 00 A0 E1 MOV R0, R8 ; Rd = Op2 返回的cout对象
.text:00009A30 020 F9 43 00 EB BL _ZNSolsEi ; Branch with Link cout<<a+b
.text:00009A34 020 08 00 A0 E1 MOV R0, R8 ; Rd = Op2
.text:00009A38 020 00 10 97 E5 LDR R1, [R7] ; Load from Memory R7为aclass类首地址
.text:00009A3C 020 F6 43 00 EB BL _ZNSolsEi ; Branch with Link cout<<a->getM()
.text:00009A40 020 00 30 90 E5 LDR R3, [R0] ; Load from Memory
.text:00009A44 020 00 80 A0 E1 MOV R8, R0 ; Rd = Op2
.text:00009A48 020 0C 30 13 E5 LDR R3, [R3,#-0xC] ; Load from Memory
.text:00009A4C 020 03 30 80 E0 ADD R3, R0, R3 ; Rd = Op1 + Op2
.text:00009A50 020 7C A0 93 E5 LDR R10, [R3,#0x7C] ; Load from Memory
.text:00009A54 020 00 00 5A E3 CMP R10, #0 ; Set cond. codes on Op1 - Op2
.text:00009A58 020 2E 00 00 0A BEQ throw_badcast ; Branch
.text:00009A5C 020 1C 30 DA E5 LDRB R3, [R10,#0x1C] ; Load from Memory
.text:00009A60 020 00 00 53 E3 CMP R3, #0 ; Set cond. codes on Op1 - Op2
.text:00009A64 020 27 10 DA 15 LDRBNE R1, [R10,#0x27] ; Load from Memory
.text:00009A68 020 07 00 00 1A BNE loc_9A8C ; Branch 下面两行为cout<<endl;
.text:00009A6C 020 0A 00 A0 E1 MOV R0, R10 ; Rd = Op2
.text:00009A70 020 B1 00 00 EB BL _ZNKSt5ctypeIcE13_M_widen_initEv ; Branch with Link
.text:00009A74 020 0A 10 A0 E3 MOV R1, #0xA ; Rd = Op2
.text:00009A78 020 0A 00 A0 E1 MOV R0, R10 ; Rd = Op2
.text:00009A7C 020 00 30 9A E5 LDR R3, [R10] ; Load from Memory
.text:00009A80 020 0F E0 A0 E1 MOV LR, PC ; Rd = Op2
.text:00009A84 020 18 F0 93 E5 LDR PC, [R3,#0x18] ; Indirect Jump
.text:00009A88 020 00 10 A0 E1 MOV R1, R0 ; Rd = Op2
.text:00009A8C
.text:00009A8C loc_9A8C ; CODE XREF: main+DC↑j
.text:00009A8C 020 08 00 A0 E1 MOV R0, R8 ; Rd = Op2
.text:00009A90 020 F7 42 00 EB BL _ZNSo3putEc ; Branch with Link
.text:00009A94 020 82 42 00 EB BL _ZNSo5flushEv ; Branch with Link
.text:00009A98 020 06 80 94 E7 LDR R8, [R4,R6] ; dword_3DDAC
.text:00009A9C 020 84 10 9F E5 LDR R1, =(aDestructorCall - 0x9AB0) ; Load from Memory
.text:00009AA0 020 12 20 A0 E3 MOV R2, #0x12 ; Rd = Op2
.text:00009AA4 020 08 00 A0 E1 MOV R0, R8 ; Rd = Op2
.text:00009AA8 020 01 10 8F E0 ADD R1, PC, R1 ; Rd = Op1 + Op2
.text:00009AAC 020 DB 43 00 EB BL ZSt16__ostream_insertIcSt11char_traitsIcEERSt13 ; Branch with Link
.text:00009AB0 020 00 30 98 E5 LDR R3, [R8] ; Load from Memory
.text:00009AB4 020 0C 30 13 E5 LDR R3, [R3,#-0xC] ; Load from Memory
.text:00009AB8 020 03 50 85 E0 ADD R5, R5, R3 ; Rd = Op1 + Op2
.text:00009ABC 020 7C 50 95 E5 LDR R5, [R5,#0x7C] ; Load from Memory
.text:00009AC0 020 00 00 55 E3 CMP R5, #0 ; Set cond. codes on Op1 - Op2
.text:00009AC4 020 13 00 00 0A BEQ throw_badcast ; Branch
.text:00009AC8 020 1C 30 D5 E5 LDRB R3, [R5,#0x1C] ; Load from Memory
.text:00009ACC 020 00 00 53 E3 CMP R3, #0 ; Set cond. codes on Op1 - Op2
.text:00009AD0 020 27 10 D5 15 LDRBNE R1, [R5,#0x27] ; Load from Memory
.text:00009AD4 020 06 00 00 0A BEQ loc_9AF4 ; Branch
.text:00009AD8
.text:00009AD8 loc_9AD8 ; CODE XREF: main+188↓j
.text:00009AD8 020 06 00 94 E7 LDR R0, [R4,R6] ; dword_3DDAC
.text:00009ADC 020 E4 42 00 EB BL _ZNSo3putEc ; Branch with Link
.text:00009AE0 020 6F 42 00 EB BL _ZNSo5flushEv ; Branch with Link
.text:00009AE4 020 07 00 A0 E1 MOV R0, R7 ; void * R7为aclass类首地址
.text:00009AE8 020 81 8A 00 EB BL _ZdlPv ; Branch with Link 删除a指针
.text:00009AEC 020 00 00 A0 E3 MOV R0, #0 ; Rd = Op2
.text:00009AF0 020 F0 87 BD E8 POP {R4-R10,PC} ; Pop registers
.text:00009AF4 ; ---------------------------------------------------------------------------
.text:00009AF4
.text:00009AF4 loc_9AF4 ; CODE XREF: main+148↑j
.text:00009AF4 020 05 00 A0 E1 MOV R0, R5 ; Rd = Op2
.text:00009AF8 020 8F 00 00 EB BL _ZNKSt5ctypeIcE13_M_widen_initEv ; Branch with Link
.text:00009AFC 020 0A 10 A0 E3 MOV R1, #0xA ; Rd = Op2
.text:00009B00 020 05 00 A0 E1 MOV R0, R5 ; Rd = Op2
.text:00009B04 020 00 30 95 E5 LDR R3, [R5] ; Load from Memory
.text:00009B08 020 0F E0 A0 E1 MOV LR, PC ; Rd = Op2
.text:00009B0C 020 18 F0 93 E5 LDR PC, [R3,#0x18] ; Indirect Jump
.text:00009B10 020 00 10 A0 E1 MOV R1, R0 ; Rd = Op2
.text:00009B14 020 EF FF FF EA B loc_9AD8 ; Branch
.text:00009B18 ; ---------------------------------------------------------------------------
.text:00009B18
.text:00009B18 throw_badcast ; CODE XREF: main+48↑j
.text:00009B18 ; main+CC↑j ...
.text:00009B18 020 F4 00 00 EB BL sub_9EF0 ; Branch with Link
.text:00009B18 ; End of function main
.text:00009B18
.text:00009B18 ; ---------------------------------------------------------------------------
.text:00009B1C 28 3D 03 00 off_9B1C DCD _GLOBAL_OFFSET_TABLE_ - 0x99AC
.text:00009B1C ; DATA XREF: main+C↑r
.text:00009B20 BC 02 00 00 off_9B20 DCD off_3D990 - 0x3D6D4 ; DATA XREF: main+10↑r
.text:00009B24 70 D0 02 00 off_9B24 DCD aConstructorCal - 0x99C0
.text:00009B24 ; DATA XREF: main+14↑r
.text:00009B24 ; "Constructor called."
.text:00009B28 98 CF 02 00 off_9B28 DCD aDestructorCall - 0x9AB0
.text:00009B28 ; DATA XREF: main+110↑r
.text:00009B28 ; } // starts at 998C ; "Destructor called."

Android NDK的jni.h中声明了所有可用的JNI接口函数,其中有JNINativeInterface和JNIInvokeInterface两个结构体。JNINativeInterface为JNI本地接口,实质是个接口函数指针表,每一项都为JNI接口函数指针,所有原生代码可调用这些接口函数。JNIInvokeInterface为JNI调用接口,用于访问全局JNI接口,用于原生多线程程序开发。可以在IDA导入Android SDK目录下的\\ndk\\28.0.12433566\\toolchains\\llvm\\prebuilt\\windows-x86_64\\sysroot\\usr\\include\\jni.h文件,但需要先将开头包含的其他库语句删掉。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
#include <string.h>
#include <jni.h>
#include <android/log.h>
#include <pthread.h>
#include "jniMethods.h"
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, "com.droider.jnimethods", __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "com.droider.jnimethods", __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "com.droider.jnimethods", __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, "com.droider.jnimethods", __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "com.droider.jnimethods", __VA_ARGS__)
JNIEnv *g_env;
JavaVM *g_vm; //下面多线程程序用到
pthread_mutex_t thread_mutex;
jclass native_class;
#ifndef NELEM //计算结构元素个数
#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
#endif
JNIEXPORT jstring nativeMethod(JNIEnv* env, jclass clazz){
const char * chs = "你好!NativeMethod";
return (*env)->NewStringUTF(env, chs);
}
JNIEXPORT void Java_com_droider_jnimethods_TestJniMethods_test(JNIEnv* env, jobject object) {
int version = (*env)->GetVersion(env);
LOGV("GetVersion() --> jni version:%2x", version);
jclass build_class = (*env)->FindClass(env, "android/os/Build"); //加载一个本地类
jfieldID brand_id = (*env)->GetStaticFieldID(env,build_class, "MODEL", "Ljava/lang/String;");//获取类的静态字段ID
jstring brand_obj = (jstring)(*env)->GetStaticObjectField(env,build_class, brand_id);//获取类的静态字段的值ֵ
const char *nativeString = (*env)->GetStringUTFChars(env, brand_obj, 0); //通过jstring生成char*
LOGV("GetStringUTFChars() --> MODEL:%s", nativeString);
LOGV("GetStaticFieldID() --> %s", nativeString);
LOGV("ReleaseStringUTFChars() --> %s", nativeString);
(*env)->ReleaseStringUTFChars(env, brand_obj, nativeString); //释放GetStringUTFChars()生成的char*
jclass test_class = (*env)->FindClass(env, "com/droider/jnimethods/TestClass");
if((*env)->ExceptionCheck(env)){
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
LOGE("ExceptionCheck()");
LOGE("ExceptionDescribe()");
LOGE("ExceptionClear()");
}
jmethodID constructor = (*env)->GetMethodID(env, test_class, "<init>", "()V"); //获取构造函数
jobject obj = (*env)->NewObject(env, test_class, constructor); //创建一个对象
jthrowable throwable = (*env)->ExceptionOccurred(env);
if (throwable){ //有异常发生,还可以使用ExceptionCheck()函数来判断
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
LOGE("ExceptionOccurred()");
}
if (obj){
(*env)->MonitorEnter(env, obj); //同步操作
jfieldID stringfieldID = (*env)->GetFieldID(env,test_class, "aStringField", "Ljava/lang/String;");//获取String类型的字段
jstring stringfieldValue = (jstring)(*env)->GetObjectField(env,obj, stringfieldID);//获取String字段的值ֵ
const char *stringValue = (*env)->GetStringUTFChars(env,stringfieldValue, 0);
LOGV("GetObjectField() --> aStringField:%s", stringValue);
const char *myValue = "def"; //设置字段的值
(*env)->SetObjectField(env, obj, stringfieldID, (*env)->NewStringUTF(env, myValue));
LOGV("SetObjectField() --> aStringField:def");
(*env)->ReleaseStringUTFChars(env, stringfieldValue, stringValue);
jfieldID intfieldID = (*env)->GetFieldID(env,test_class, "aIntField", "I");//获取int类型的字段
jint fieldValue = (*env)->GetIntField(env, obj, intfieldID); //获取int字段的值ֵ
LOGV("GetIntField() --> aField:%d", fieldValue);
(*env)->SetIntField(env, obj, intfieldID, (jint)123); // 设置int字段的值ֵ
fieldValue = (*env)->GetIntField(env, obj, intfieldID);
LOGV("SetIntField() --> aField:%d", fieldValue);
jclass parent_class = (*env)->GetSuperclass(env, test_class);
LOGV("GetSuperclass() --> ");
if(JNI_OK == (*env)->EnsureLocalCapacity(env, 5)){ //检测是否还可以创建5个局部引用
LOGV("EnsureLocalCapacity() --> ensure 5 locals");
jmethodID obj2_voidmethod = (*env)->GetMethodID(env,test_class, "aVoidMethod", "()V");//获取一个void方法
(*env)->CallVoidMethod(env, obj, obj2_voidmethod);
LOGV("CallVoidMethod()");
jmethodID obj2_Staticmethod = (*env)->GetStaticMethodID(env,test_class, "aStaticMethod", "(Ljava/lang/String;)V");//获取static方法
LOGV("GetStaticMethodID()");
const char *fromJni = "this string from jni";
jstring jstr_static = (*env)->NewStringUTF(env, fromJni);
(*env)->CallStaticVoidMethod(env, test_class, obj2_Staticmethod, jstr_static); //调用一个静态方法
jmethodID obj2_chinesemethod = (*env)->GetMethodID(env,test_class, "getChineseString", "()Ljava/lang/String;");//传递中文字符串
jstring obj2_chinesejstring = (jstring)(*env)->CallObjectMethod(env, obj, obj2_chinesemethod);
jsize chinese_size = (*env)->GetStringLength(env, obj2_chinesejstring); //获取UTF-16字符串长度
LOGV("GetStringLength() --> %d", chinese_size);
jchar buff_jchar[4] = {0};
(*env)->GetStringRegion(env, obj2_chinesejstring, 0, 3, buff_jchar);
LOGV("GetStringRegion() -->");
const jchar * obj2_chinesechars = (*env)->GetStringChars(env, obj2_chinesejstring, NULL);
jstring new_chinesestring = (*env)->NewString(env, obj2_chinesechars, chinese_size);
(*env)->CallStaticVoidMethod(env, test_class, obj2_Staticmethod, new_chinesestring); //调用一个静态方法
(*env)->ReleaseStringChars(env, obj2_chinesejstring, obj2_chinesechars); //释放GetStringChars获取的jchar*
LOGV("CallStaticVoidMethod()");
jmethodID obj2_sqrtmethod = (*env)->GetStaticMethodID(env,test_class, "sqrt", "(I)I");//获取一个static方法
jint int_sqrt = (*env)->CallStaticIntMethod(env,test_class, obj2_sqrtmethod, (jint)5);//计算5的平方
LOGV("CallStaticIntMethod() -->5 sqrt is:%d", int_sqrt);
}
if(JNI_TRUE == (*env)->IsAssignableFrom(env, test_class, parent_class)){
LOGV("IsAssignableFrom() --> yes");
} else {
jclass newExceptionClazz = (*env)->FindClass(env,"java/lang/RuntimeException"); //实例化一个异常
if(newExceptionClazz != NULL)
(*env)->ThrowNew(env, newExceptionClazz,"这里永远不会被执行的!");
LOGE("ThrowNew()");
}
jclass obj_clazz = (*env)->GetObjectClass(env, obj); //获取对象的类
if(JNI_TRUE == (*env)->IsInstanceOf(env, obj, obj_clazz)){
LOGV("IsInstanceOf() --> Yes");
} else {
(*env)->FatalError(env, "fatal error!"); //抛出致命错误
LOGE("FatalError()");
}
(*env)->PushLocalFrame(env, 2); //申请局部引用空间,增加局部引用的管理
jobject obj_localref = (*env)->NewLocalRef(env, obj); //创建一个局部引用
jobject obj_globalref = (*env)->NewGlobalRef(env, obj); //创建一个全局引用
LOGV("PushLocalFrame()");
LOGV("NewLocalRef()");
LOGV("NewGlobalRef()");
if (JNI_TRUE == (*env)->IsSameObject(env, obj_localref, obj_globalref)){
LOGV("IsSameObject() --> Yes");
}
(*env)->DeleteLocalRef(env, obj_localref); //删除一个局部引用
(*env)->DeleteGlobalRef(env, obj_globalref); //删除一个全局引用
(*env)->PopLocalFrame(env, NULL);
LOGV("DeleteLocalRef()");
LOGV("DeleteGlobalRef()");
LOGV("PopLocalFrame()");
(*env)->MonitorExit(env, obj);
}
jclass sub_class = (*env)->FindClass(env,"com/droider/jnimethods/TestSubClass"); //查询子类
jobject sub_obj = (*env)->AllocObject(env, sub_class);
jmethodID sub_methodID = (*env)->GetMethodID(env,sub_class, "aVoidMethod", "()V");
(*env)->CallNonvirtualVoidMethod(env,sub_obj, sub_class, sub_methodID); //调用子类的方法
(*env)->CallNonvirtualVoidMethod(env,sub_obj, test_class, sub_methodID); //根据调用类的不同调用父类的方法
jfieldID sub_fieldID = (*env)->GetStaticFieldID(env,sub_class, "subFloatField", "F");//获取子类静态字段
(*env)->SetStaticFloatField(env, sub_class, sub_fieldID, (jfloat)33.88f);
LOGV("SetStaticFloatField() --> %.2f",(*env)->GetStaticFloatField(env, sub_class, sub_fieldID));
jclass class_string = (*env)->FindClass(env, "java/lang/String");
jarray string_array = (*env)->NewObjectArray(env, 3, class_string, 0); //创建String数组
LOGV("NewObjectArray()");
jsize array_size = (*env)->GetArrayLength(env, string_array);
LOGV("GetArrayLength() --> %d", array_size);
jstring array_string1 = (*env)->NewStringUTF(env, "one");
char buff_char[4] = {0};
(*env)->GetStringUTFRegion(env, array_string1, 0, 3, buff_char);
LOGV("GetStringUTFRegion() --> %s", buff_char);
(*env)->SetObjectArrayElement(env, string_array, 0, array_string1); //设置数组元素的值
LOGV("SetObjectArrayElement() --> one");
array_string1 = (jstring)(*env)->GetObjectArrayElement(env, string_array, 0); //获取数组元素的值
const char* array_elemchars = (*env)->GetStringUTFChars(env, array_string1, NULL);
LOGV("GetObjectArrayElement() --> %s", array_elemchars);
(*env)->ReleaseStringUTFChars(env, array_string1, array_elemchars);
jintArray int_array = (*env)->NewIntArray(env, 5); //创建int数组
LOGV("NewIntArray() --> %d", (*env)->GetArrayLength(env, int_array)); //获取数组长度
const jint ints[] = {11, 12, 13, 14, 15};
(*env)->SetIntArrayRegion(env, int_array, 0, 5, ints); //设置数组一个范围的值ֵ
LOGV("SetIntArrayRegion() --> %d,%d,%d,%d,%d",ints[0], ints[1], ints[2], ints[3], ints[4]);
jint ints2[2] = {0, 0};
(*env)->GetIntArrayRegion(env, int_array, 1, 2, ints2); //获取数组一个范围的值
LOGV("GetIntArrayRegion() --> %d,%d", ints2[0], ints2[1]);
jint* array_ints = (*env)->GetIntArrayElements(env, int_array, NULL); //获取指向所有元素的指针
LOGV("GetIntArrayElements() --> %d,%d,%d,%d,%d",array_ints[0], array_ints[1], array_ints[2], array_ints[3], array_ints[4]);
(*env)->ReleaseIntArrayElements(env, int_array, array_ints, 0); //释放指向所有元素的指针
LOGV("ReleaseIntArrayElements()");
}
void *thread_func(void *arg){
JNIEnv *env;
pthread_mutex_lock(&thread_mutex);
if (JNI_OK != (*g_vm)->AttachCurrentThread(g_vm, &env, NULL)){
LOGE("AttachCurrentThread() failed");
return NULL;
}
LOGV("AttachCurrentThread() --> thread:%d", (jint)arg);
(*g_vm)->DetachCurrentThread(g_vm);
LOGV("DetachCurrentThread() --> thread:%d", (jint)arg);
pthread_mutex_unlock(&thread_mutex);
pthread_exit(0);
return NULL;
}
JNIEXPORT void newJniThreads(JNIEnv* env, jobject obj, jint nums){
(*env)->GetJavaVM(env, &g_vm); //保存全局JavaVM
LOGV("GetJavaVM()");
pthread_t* pt = (pthread_t*)malloc(sizeof(pthread_t)* nums);
pthread_mutex_init(&thread_mutex, NULL);
int i;
for (i = 0 ; i < nums; i++){
pthread_create(&pt[i], NULL, &thread_func, (void*)i); //创建线程
}
free(pt);
}
JNIEXPORT jobject allocNativeBuffer(JNIEnv* env, jobject obj, jlong size){
void* buffer = malloc(size);
jobject directBuffer = (*env)->NewDirectByteBuffer(env, buffer, size);
LOGV("NewDirectByteBuffer() --> %d", (int)size);
return directBuffer;
}
JNIEXPORT void freeNativeBuffer(JNIEnv* env, jobject obj, jobject bufferRef){
void *buffer = (*env)->GetDirectBufferAddress(env, bufferRef);
strcpy(buffer, "123");
LOGV("GetDirectBufferAddress() --> %s", buffer);
free(buffer);
}
static JNINativeMethod methods[] = {
{"nativeMethod", "()Ljava/lang/String;", (void*)nativeMethod},
{"newJniThreads", "(I)V", (void*)newJniThreads},
{"allocNativeBuffer", "(J)Ljava/lang/Object;", (void*)allocNativeBuffer},
{"freeNativeBuffer", "(Ljava/lang/Object;)V", (void*)freeNativeBuffer}
};
jint JNI_OnLoad(JavaVM* vm, void* reserved){
if(JNI_OK != (*vm)->GetEnv(vm, (void**)&g_env, JNI_VERSION_1_6)){ //加载指定版本的JNI
return -1;
}
LOGV("JNI_OnLoad()");
native_class = (*g_env)->FindClass(g_env, "com/droider/jnimethods/TestJniMethods");
if (JNI_OK ==(*g_env)->RegisterNatives(g_env,native_class, methods, NELEM(methods))){//注册未声明的本地方法
LOGV("RegisterNatives() --> nativeMethod() ok");
} else {
LOGE("RegisterNatives() --> nativeMethod() failed");
return -1;
}
return JNI_VERSION_1_6;
}
jstring stoJstring( JNIEnv* env, const char* pat ){
jclass strClass = (*env)->FindClass(env, "java/lang/String");
jmethodID ctorID = (*env)->GetMethodID(env,strClass, "<init>", "([BLjava/lang/String;)V");
jbyteArray bytes = (*env)->NewByteArray(env, strlen(pat));
(*env)->SetByteArrayRegion(env, bytes, 0, strlen(pat), pat);
jstring encoding = (*env)->NewStringUTF(env, "GB2312");
return (jstring)(*env)->NewObject(env,strClass, ctorID, bytes, encoding);
}
void JNI_OnUnLoad(JavaVM* vm, void* reserved){
LOGV("JNI_OnUnLoad()");
(*g_env)->UnregisterNatives(g_env, native_class);
LOGV("UnregisterNatives()");
}
int main(int argc, char *argv[]){
return 0;
}

动态调试

可修改Smali,用android.util.Log类输出调试信息,如:

1
Log.v("com.droider.jnimethods","jni test a void subclass method, this run in java");

除了v还有d、i、w、e方法,分别为VERBOSE、DEBUG、INFO、WARN、ERROR类型信息。第一个字符串为Tag标记,第二个为调试信息。命令行查看输出为:

1
adb logcat -s com.droider.jnimethods:V

此时添加的反汇编代码为:

1
2
const-string v3,"SN"
invoke-static {v3,v0},Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I

也可以用异常来进行栈跟踪:

1
new Exception("print trace").printStackTrace();

对应反汇编代码为:

1
2
3
4
new-instance v0,Ljava/lang/Exception;
const-string v1,"print trace"
invoke-direct {v0,v1},Ljava/lang/Exception;-><init>(Ljava/lang/String;)V
invoke-virtual {v0},Ljava/lang/Exception;->printStackTrace()V

栈跟踪信息的Tag为System.err,WARN级别,运行时需要:

1
adb logcat -s System.err:V *:W

当调试Android原生程序时,需要:

1
2
3
4
5
6
7
8
9
10
11
adb push .\debugnativeapp /data/local/tmp
adb push D:\IDA9RC1\dbgsrv\android_server /data/local/tmp
adb shell
su
cd /data/local/tmp
chmod 755 ./android_server
chmod 755 ./debugnativeapp
exit
exit
adb forward tcp:23946 tcp:23946
adb shell /data/local/tmp/android_server

然后设置IDA远程参数,Application为/data/local/tmp/debugnativeapp,Directory为/data/local/tmp,Hostname为localhost。

当要调试动态链接库时,先开启端口转发与服务端,IDA再用Attach方法附加一个进程。可以在Debug options中设置各种初始化断点。但Android 5.0以上强制开启PIE,得找对应链接库所在的段,再计算函数相对偏移量去找。

IDA还可以Dump Android应用内存,即.so文件。IDA动调附加并设置断点后,在Modules窗口或下面命令查看.so文件起始地址和大小:

1
cat /proc/pid/maps

在IDA的Execute script中输入:

1
2
3
4
5
6
7
8
9
10
auto file,fname,i,address,size,x;
address=0x710D827000; //起始地址
size=0xB25B000; //大小
fname="D:\dump.so";
file=fopen(fname,"wb");
for(i=0;i<size;i++,address++){
x=DbgByte(address);
fputc(x,file);
}
fclose(file);

还可以用JDB搭配Android Studio进行动调。下载https://bitbucket.org/JesusFreke/smali/downloads/下的smalidea\-0\.05\.zip插件并安装到Android Stdio中,把用Apktool反编译的Smali源码导入Android Studio并Mark Directory as->Resources Root,标记为资源根目录。然后Run/Debug Configurations下新建一个Remote调试配置,填写localhost和5005,然后在Android Device Monitor中查看pid并设置端口转发:

1
2
adb shell ps | grep com.example
adb forward tcp:5005 jdwp:29688

Android入门

简介

Android系统启动时,第一个启动的是init进程,后者根据读取/init.rc中配置创建并启动App_process进程,也就是Zygote孵化器进程。Zygote启动后创建SystemServer进程,开始创建应用程序进程。Zygote是Android中所有应用的父进程,它开启Socket接口来监听请求,执行一个Android应用程序时,SystemServer通过Binder IPC发送命令给Zygote,后者通过自身的Dalvik虚拟机实例来启动应用程序。

SystermServer是Android核心进程之一,提供大部分Android基础服务,如AMS、WMS、PMS等。AMS为调度器,负责系统组件和应用程序的启动、切换、调度等工作。

Android应用启动时,手机屏幕是一个Activity,也叫做Launcher。Android应用一般通过触发来产生启动条件,Launcher进程接收事件并通知AMS,后者收到消息后,差u女鬼剑Taks启动指定Activity,此时AMS与Zygote通信,去Fork一个实例来执行应用。

Zygote提供三种创建进程的方法:fork()创建一个Zygote进程,forkAndSpecialize()创建一个非Zygote进程,forkSystemServer()创建一个系统服务进程。Zygote进程可再fork()处其他进程,而非Zygote进程不能fork其他进程,系统服务进程再终止后它的子进程也必需终止。

进程fork成功后,执行的工作交给Dalvik虚拟机,后者通过loadClassFromDex()完成类的装载,每个类成功解析后会拥有一个ClassObject类型的数据结构存储再运行时环境中。虚拟机用gDvm.loadedClasses全局哈希表来 存储与查询所有装载进来的类。随后字节码验证器用dvmVerifyCodeFlow()对装入的代码进行校验,接着虚拟机用FindClass()查找并装载main方法类,随后用dvmInterpret()初始化解释器并执行字节码流。

对于Android源码的下载,这里下载Android 4.1,Linux内核3.4。下载需要安装repo,这里略,安装后更新.bashrc为:

1
export REPO_URL='https://mirrors.tuna.tsinghua.edu.cn/git/git-repo'

然后初始化并同步:

1
2
repo init -u https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/platform/manifest -b android-4.1.2_r1
repo sync

完全拉下来得好几十个GB,这里也建议直接在线阅读https://cs.android.com/android/platform/superproject/+/android-4.1.2_r2.1:。

关键代码定位

一个Android程序由一个或多个Activity以及其他组件组成,每个Activity都是相同级别的。每个Android程序有且只有一个主Activity,隐藏程序除外。

例如找到AndroidManifest.xml:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.droider.crackme0502">
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15"/>
<application android:theme="@style/AppTheme" android:label="@string/app_name" android:icon="@drawable/ic_launcher" android:name="com.droider.crackme0502.MyApp" android:debuggable="true">
<activity android:label="@string/title_activity_main" android:name="com.droider.crackme0502.MainActivity"> <!--标题、类-->
<intent-filter> <!--启动意图-->
<action android:name="android.intent.action.MAIN"/> <!--该Activity为主Activity-->
<category android:name="android.intent.category.LAUNCHER"/> <!--该Activity可通过LAUNCHER启动-->
</intent-filter>
</activity>
</application>
</manifest>

找到该主Activity后去找它的OnCreate方法即可。

有时可能出现Application类,用于程序组件间传递全局变量、在Activity启动前做初始化工作等。使用Application类时需要在程序中添加一个类继承自android.app.Application,并重写它的OnCreate。Application类启动比其他类要早,某些商业软件将授权验证代码转移到该类中。

破解基础

方法createPackageContext创建其他程序的Context,通过该Context可访问其他软件包资源,甚至执行其他软件包代码。该方法可能抛出java.lang.SecurityException安全异常,因为一个软件不能创建其他程序Context,除非两个软件拥有相同的用户ID与签名。用户ID在AndroidManifest.xml的manifest标签中指定,格式为android\:sharedUserId=\”xxx.xxx.xxx\”。当两个程序指定相同用户ID时,这两个程序运行在同一个进程空间,他们之间资源可相互访问,签名相同还可相互执行软件包之间代码。

抓包可用Shell自带的tcpdump,记录:

1
tcpdump -p -vv -s 0 -w /sdcard/Download/capture.pcap

用Ctrl+C结束,用adb拉下来,再用Wireshark分析。

有时Dalvik层定义的原生函数无法在Native层找到相同的函数名,可能是在JNI_OnLoad方法中注册其他函数与Java层方法关联了,找到__data_start即可。

APK可能在AndroidManifest.xml中Application标签加入android:debuggable="false",但实测没啥用。判断该值是否被修改过:

1
2
3
4
if((getApplicationInfo().flags&=ApplicationInfo.FLAG_DEBUGGABLE)!=0){
Log.e("com.droider.antidebug","程序被修改为可调试状态");
android.os.Process.killProcess(android.os.Process.myPid());
}

还有种方法检测调试器是否已经连接:

1
android.os.Debug.isDebuggerConnected()

可通过以下命令查看Android属性值,其中一些差异如下,不过现在模拟器都隐藏地很好了:

属性值 模拟器 正常手机
ro.product.model sdk 手机型号
ro.build.tags test-keys release-keys
ro.kernel.qemu 1 没有该值

例如检测ro.kernel.qemu属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.droider.checkqemu;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.util.Log;
import android.view.Menu;
import android.widget.TextView;
public class MainActivity extends Activity {
private TextView text_info;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setTitle("模拟器检测演示程序");
text_info = (TextView) findViewById(R.id.textView1);
if (isRunningInEmualtor()) { //检测模拟器
text_info.setTextColor(Color.RED);
text_info.setText("程序运行在模拟器中!");
} else {
text_info.setTextColor(Color.GREEN);
text_info.setText("程序运行在真实设备中!");
}
}
boolean isRunningInEmualtor() {
boolean qemuKernel = false;
Process process = null;
DataOutputStream os = null;
try{
process = Runtime.getRuntime().exec("getprop ro.kernel.qemu");
os = new DataOutputStream(process.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(),"GBK"));
os.writeBytes("exit\n");
os.flush();
process.waitFor();
qemuKernel = (Integer.valueOf(in.readLine()) == 1);
Log.d("com.droider.checkqemu", "检测到模拟器:" + qemuKernel);
} catch (Exception e){
qemuKernel = false;
Log.d("com.droider.checkqemu", "run failed" + e.getMessage());
} finally {
try{
if (os != null) {
os.close();
}
process.destroy();
} catch (Exception e) {}
Log.d("com.droider.checkqemu", "run finally");
}
return qemuKernel;
}
public static String getProp(Context context, String property) {
try {
ClassLoader cl = context.getClassLoader();
Class SystemProperties = cl.loadClass("android.os.SystemProperties");
Method method = SystemProperties.getMethod("get", String.class);
Object[] params = new Object[1];
params[0] = new String(property);
return (String)method.invoke(SystemProperties, params);
} catch (Exception e) {
return null;
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}

用PackageManager进行签名校验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.droider.checksignature;
import android.os.Bundle;
import android.app.Activity;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.graphics.Color;
import android.view.Menu;
import android.widget.TextView;
public class MainActivity extends Activity {
private TextView text_info;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setTitle("签名检查演示程序");
text_info = (TextView) findViewById(R.id.textView1);
int sig = getSignature("com.droider.checksignature");
if (sig != 2071749217) {
text_info.setTextColor(Color.RED);
text_info.setText("检测到程序签名不一致,该程序被重新打包过!");
} else {
text_info.setTextColor(Color.GREEN);
text_info.setText("该程序没有被重新打包过!");
}
}
public int getSignature(String packageName) {
PackageManager pm = this.getPackageManager();
PackageInfo pi = null;
int sig = 0;
try {
pi = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
Signature[] s = pi.signatures;
sig = s[0].hashCode();
} catch (Exception e1) {
sig = 0;
e1.printStackTrace();
}
return sig;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}

CRC校验classes.dex文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.droider.checkcrc;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import android.os.Bundle;
import android.app.Activity;
import android.graphics.Color;
import android.util.Log;
import android.view.Menu;
import android.widget.TextView;
public class MainActivity extends Activity {
private TextView text_info;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setTitle("校验保护演示程序");
text_info = (TextView) findViewById(R.id.textView1);
if (checkCRC()) { //对比classes.dex的校验和
text_info.setTextColor(Color.GREEN);
text_info.setText("程序正常!");
} else {
text_info.setTextColor(Color.RED);
text_info.setText("程序被修改过!");
}
}
private boolean checkCRC() {
boolean beModified = false;
long crc = Long.parseLong(getString(R.string.crc));
ZipFile zf;
try {
zf = new ZipFile(getApplicationContext().getPackageCodePath());
ZipEntry ze = zf.getEntry("classes.dex");
Log.d("com.droider.checkcrc", String.valueOf(ze.getCrc()));
if (ze.getCrc() == crc) {
beModified = true;
}
} catch (IOException e) {
e.printStackTrace();
beModified = false;
}
return beModified;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}