Android逆向基础

本文最后更新于:2021年7月22日 下午

第一个Android程序

image-20210701223501900

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//关键代码

btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String username_hash = md5(edt_username.getText().toString());
String password = edt_password.getText().toString();
//d1ea8c82fe40032b5f5db149e9d6d103
if(!edt_username.getText().toString().isEmpty() && !password.isEmpty() && password.equals(username_hash)){
Toast.makeText(MainActivity.this, "登陆成功!", Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(MainActivity.this, "登陆失败!", Toast.LENGTH_SHORT).show();
}
}
});
}

编译并且在模拟器中运行,

image-20210718184146583

接下来我们破解这个程序,使用Android_killer打开程序,

image-20210718185017630

在如图中所示的文件中,找到了判断登录是否成功的代码点,发现cond_0是登录失败,所以我们通过修改smali代码使得无论输入什么都显示密码正确。

image-20210718185144735

将eqz改为nez,进行重编译,并且安装apk。会发现账号密码随便输入,显示登陆成功。

Dalvik可执行格式与字节码规范

Dalvik虚拟机的特点:

image-20210718185954440

Dalvik虚拟机与java虚拟机的区别:

  • 运行的字节码不同。 java虚拟机运行的是java字节码,Dalvik虚拟机运行的是Dalvik字节码。

  • Dalvik可执行文件的体积更小。

    image-20210718190401113

  • 虚拟机架构不同。 java基于栈架构,Dalvik虚拟机是基于寄存器架构的。

编译java源文件:

image-20210721114351771

使用d8命令生成dex文件。dx已经被弃用,使用d8代替。

d8 --output dex Hello.class

使用dexdump查看dalvik字节码:

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
s1lenc3@chenmodeMacBook-Pro:base/dex $ dexdump -d classes.dex 
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
catches : (none)
positions :
0x0000 line=1
locals :
0x0000 - 0x0004 reg=0 this LHello;

#1 : (in LHello;)
name : 'main'
type : '([Ljava/lang/String;)V'
access : 0x0009 (PUBLIC STATIC)
code -
registers : 4
ins : 1
outs : 3
insns size : 17 16-bit code units
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
catches : (none)
positions :
0x0000 line=7
0x0005 line=8
0x0010 line=9
locals :
0x0000 - 0x0011 reg=3 (null) [Ljava/lang/String;

Virtual methods -
#0 : (in LHello;)
name : 'foo'
type : '(II)I'
access : 0x0001 (PUBLIC)
code -
registers : 4
ins : 3
outs : 0
insns size : 6 16-bit code units
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
catches : (none)
positions :
0x0000 line=3
locals :
0x0000 - 0x0006 reg=1 this LHello;
0x0000 - 0x0006 reg=2 (null) I
0x0000 - 0x0006 reg=3 (null) I

source_file_idx : 12 (hello.java)

虚拟机的执行流程

Android系统由Linux内核、函数库、Android运行时、应用程序框架及应用程序组成。Dalvik虚拟机属于Android运行时环境。

Android系统结构

1
2
Android系统启动并加载内核后,立即执行init进程,完成初始化后再读区init.rc文件并启动重要外部程序Zygote。
Zygote是Android系统中所有进程的孵化器进程。

image-20210721132932393

1
2
3
4
即时编译(JIT)又称动态编译,一种在运行时将字节码翻译为机器码的技术。
有两种编译方式:
method方式:以函数或方法为单位进行编译。
trace方式:以trace为单位进行编译,优先编译“热路径”。

Dalvik语言基础

1
2
3
4
5
dexdump反汇编工具寄存器采用“v”命名法,baksmali反汇编工具寄存器采用“p”和“v”命名法。如果是共同使用的话,v寄存器一般作为局部变量寄存器。
Dalvik寄存器都是32位的,对于64位类型,可以使用两个相邻的寄存器来表示。
寄存器范围是v0-v65535
Dalvik虚拟机为每个进程维护一个调用栈,调用栈的作用之一就是“虚拟”寄存器。
Dalvik字节码只有基本类型和引用类型,对象和数组属于引用类型。字节码描述符如下表。

image-20210721140245657

1
2
3
4
5
6
7
8
9
10
Ljava/lang/String;相当于java.lang.String
[[I相当于int[][]
[Ljava/lang/String表示jva中的字符串数组
方法:
Lpackage/name/ObjectName;->MethodName(III)Z
method(I[[IILjava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
java形式为:String method(int, int[][],int, String, Object[])
字段:
Lpackage/name/ObjectName;->FieldName:Ljava/lang/String;
类型;->字段名:字段类型

Dalvik指令集

image-20210721142447916

获取字段时,普通字段指令前缀为i,静态字段的指令前缀为s。

1
2
3
使用adb运行dex文件。
java -jar smali.jar ass ../base/HelloWorld.smali
adb shell dalvikvm -cp /data/local/tmp/out.dex HelloWorld

常见Android文件格式

1
2
jar包可以使用jd-gui进行反编译
aar包包含资源文件

apk是一个压缩文件,后缀改为zip可以解压出来。

AndroidMainfest.xml

一些配置清单,名字、版本、权限、引用的库文件等等。找一个apk看看这个文件,就什么都懂了。

META-INF目录

存放签名信息,android程序生成release版本的apk需要签名,加密信息应该就保存的这里了。

res目录

存放各种资源文件。最终被映射到Android工程中的R文件中,对各种资源会生成对应ID。逆向android主要是找strings.xml,便于定位关键字符串。一般在values文件夹中,layout文件夹是存放的应用界面布局。

lib目录

存放依赖的native库文件,.so文件是C/C++写的。可以根据特定目录确定是什么架构的。.so文件在逆向中应用挺多。

assets目录

这个文件夹中的文件会原封不动的打包到apk中。

resources.arsc

编译后的二进制资源文件的索引。应该就是程序运行,然后从这个文件根据对应ID找到对应的res资源文件。

classes.dex文件

java字节码文件,这个文件很重要,可以用jeb等转换成java源码。说到这就不得不提一下几个文件的转换了。

img

baksmali.jar和smali.jar有时候java自带的老出问题,我是从网上下载的。https://bitbucket.org/JesusFreke/smali/downloads/

当然敲命令有点费事,可以去吾爱的爱盘下载android逆向工具包。

apk打包流程

将编译模块和依赖项打包成dex文件、将apk packager打包成apk和签名。

image-20210722155918309

apk安装流程

1
2
3
请求安装apk时会启动packageinstaller.apk,并且接收通过Intent传递过来的APK信息。
进入PackageInstallActivity中的onCreate方法,主要PackageUtil中的getPackageInfo方法和initiateInstall方法最为重要。
getPackageInfo->initiateInstall->startInstallConfirm->onClick->instalPackage->installPackageWithVerification->processPendingInstall->installlPackageLI->scanPackageLI->mInstaller.install()->

Activity 的主要方法

onClick

CrackMe类型的题目,重点关注这个方法。

init和onCreate

Activity创建时的初始化函数

BuildConfig和R

编译时自动添加的资源类,不常用

Native层与so

Android原生代码,Linux中的执行代码,C/C++写的,底层是ARM汇编,很重要。

JNI_ONLOAD

这个函数是Dalvik虚拟机加载库时的初始化函数。

动态调试Smali代码演示

使用adb install xxxxxx.apk

安装apk。

拖入apk到jeb中,Ctrl+B下断点。

使用adb shell am start -D -n com.droider.crackme0201/.MainActivity 让程序处于等待调试状态

然后点 调试器->开始

附加。断点设在点击事件后。

然后正常执行app,就会在指定断点处断下来。

可以查看局部变量。

img

在VM 终端中可以执行指令:

img

img

还有很多用法,我也不会用。

判断跳转:

![img](D:/有道笔记/数据/qq20118A67A04314627D0DF4F3B9EFF13F/7d45d1529d4c41eead3e0d46a2c8f067/clipboard.png)

用APKIDE修改 if-nez 为 if-eqz。

APK破解成功。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!