安卓那档事01

认识APK

apk,全称Android Package,相当于一个压缩文件,zip后缀和rar后缀都可以解压。

对于一个常见的apk,解压后可以看到:

某游戏安装包解压
目录/文件 注释
assets 里面存放的是apk的静态资源文件,比如视频,音频,图片等
lib 里面存放了不同框架下使用的so文件,文件是由c或cpp编译的动态链接库。
armeabi-v7a基本通用所有android设备,arm64-v8a只适用于64位的android设备,x86常见用于android模拟器
META-INF 保存了签名信息的文件,一般主要用于验证apk完整性
res 存放资源文件,包括图片,字符串等等。apk的样子也一般由其中的layout文件设计。
AndroidManifest.xml APK的应用清单信息,它描述了应用的名字,版本,权限,引用的库文件等等信息
classes.dex classes.dex是java源码编译后生成的java字节码文件,APK运行的主要逻辑
resources.arsc resources.arsc是编译后的二进制资源文件,它是一个映射表,映射着资源和id,通过R文件中的id就可以找到对应的资源

像上图的kotlin文件夹,说明这个apk全部功能或者部分功能是由kotlin开发的。

双开APK

双开:简单来说,就是手机同时运行两个或多个相同的应用,例如同时运行两个微信

原理 解释
修改包名 让手机系统认为这是2个APP,这样的话就能生成2个数据存储路径,此时的多开就等于你打开了两个互不干扰的APP
修改Framework 对于有系统修改权限的厂商,可以修改Framework来实现双开的目的,例如:小米自带多开
通过虚拟化技术实现 虚拟Framework层、虚拟文件系统、模拟Android对组件的管理、虚拟应用进程管理 等一整套虚拟技术,将APK复制一份到虚拟空间中运行,例如:平行空间
以插件机制运行 利用反射替换,动态代{过}{滤}理,hook了系统的大部分与system—server进程通讯的函数,以此作为“欺上瞒下”的目的,欺骗系统“以为”只有一个apk在运行,瞒过插件让其“认为”自己已经安装。例如:VirtualApp

汉化APK

汉化:使用专门的工具对外文版的软件资源进行读取、翻译、修改、回写等一系列处理,使软件的菜单、对话框、提示等用户界面显示为中文,而程序的内核和功能保持不变,这个过程即为软件汉化

基本上字符串都是在arsc里,建议一键汉化,然后再润色。 少量没汉化到的字符串可以使用工具去定位去逐个汉化

逆向汉化流程

初识AndroidManifest.xml

AndroidManifest.xml文件是整个应用程序的信息描述文件,定义了应用程序中包含的Activity,Service,Content provider和BroadcastReceiver组件信息。

每个应用程序在根目录下必须包含一个AndroidManifest.xml文件,且文件名不能修改。它描述了package中暴露的组件,他们各自的实现类,各种能被处理的数据和启动位置。

属性 定义
versionCode 版本号,主要用来更新,例如:12
versionName 版本名,给用户看的,例如:1.2
package 包名,例如:com.zj.52pj.demo
uses-permission android:name="" 应用权限,例如:android.permission.INTERNET 代表网络权限
image-20230928224041944
android:label="@string/app_name" 应用名称
android:icon="@mipmap/ic_launcher" 应用图标路径
android:debuggable="true" 应用是否开启debug权限

了解JVM、Dalvik、ART

  • JVM是JAVA虚拟机,运行JAVA字节码程序
  • Dalvik是Google专门为Android设计的一个虚拟机,Dalvik有专属的文件执行格式dex(Dalvik executable)
  • Art(Android Runtime)相当于Dalvik的升级版,本质与Dalvik无异

smali

smali是Dalvik的寄存器语言,smali代码是dex反编译而来的。

关键字

名称 注释
.class 类名
.super 父类名,继承的上级类名名称
.source 源名
.field 变量
.method 方法名
.register 寄存器
.end method 方法名的结束
public 公有
protected 半公开,只有同一家人才能用
private 私有,只能自己使用
.parameter 方法参数
.prologue 方法开始
.line xxx 位于第xxx行

数据类型对应

smali类型 java类型 注释
V void 无返回值
Z boolean 布尔值类型,返回0或1
B byte 字节类型,返回字节
S short 短整数类型,返回数字
C char 字符类型,返回字符
I int 整数类型,返回数字
J long (64位 需要2个寄存器存储) 长整数类型,返回数字
F float 单浮点类型,返回数字
D double (64位 需要2个寄存器存储) 双浮点类型,返回数字
string String 文本类型,返回字符串
Lxxx/xxx/xxx object 对象类型,返回对象

常用指令

关键字 注释
const 重写整数属性,真假属性内容,只能是数字类型
const-string 重写字符串内容
const-wide 重写长整数类型,多用于修改到期时间。
return 返回指令
if-eq 全称equal(a=b),比较寄存器ab内容,相同则跳
if-ne 全称not equal(a!=b),ab内容不相同则跳
if-eqz 全称equal zero(a=0),z即是0的标记,a等于0则跳
if-nez 全称not equal zero(a!=0),a不等于0则跳
if-ge 全称greater equal(a>=b),a大于或等于则跳
if-le 全称little equal(a<=b),a小于或等于则跳
goto 强制跳到指定位置
switch 分支跳转,一般会有多个分支线,并根据指令跳转到适当位置
iget 获取寄存器数据
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
// 说明了方法是私有的,静态的,不可变的 还有对应的参数的类型,最后的z是boolean的返回值的代表
.method private static final onCreate$lambda-2(Lkotlin/jvm/internal/Ref$IntRef;Lcom/zj/wuaipojie/ui/ChallengeSecond;Landroid/widget/ImageView;Landroid/widget/ImageView;Landroid/widget/ImageView;Landroid/view/View;)Z
.registers 7 // 寄存器格式,若在修改代码使用了更多的寄存器,这里的寄存器数量要大于函数中总的使用数量

.line 33 // 代码所在的行数.
00390374: 5211 ee97 0000: iget v1, v1, Lkotlin/jvm/internal/Ref$IntRef;->element:I # field@97ee
// 读取v1(第一个参数,参考寄存器知识)中element的值赋值给v1.
00390378: 1216 0002: const/4 v6, 0x1
// 赋值0x1给v6寄存器
0039037a: 1300 0a00 0003: const/16 v0, 0xa
0039037e: 3501 1000 0005: if-ge v1, v0, :cond_0015
// 判断v1是否大于或等于v0的值,若是,则跳转到 cond_0015 .
.line 34
00390382: 0721 0007: move-object v1, v2
// mov
00390384: 1f01 6300 0008: check-cast v1, Landroid/content/Context; # type@0063
// 检查Context对象引用.
00390388: 1a00 09ed 000a: const-string v0, "请先获取10个硬币哦" # string@ed09
弹窗文本信息,把""里的字符串数据赋值给v0
0039038c: 1f00 e514 000c: check-cast v0, Ljava/lang/CharSequence; # type@14e5

00390390: 7130 ae11 0106 000e: invoke-static {v1, v0, v6}, Landroid/widget/Toast;->makeText(Landroid/content/Context;, Ljava/lang/CharSequence;, I)Landroid/widget/Toast; # method@11ae
// 以{v1, v0, v6}为参数,调用静态方法
00390396: 0c01 0011: move-result-object v1
// 结果传回给v1
00390398: 6e10 af11 0100 0012: invoke-virtual {v1}, Landroid/widget/Toast;->show()V # method@11af
//调用方法.
.line 36
cond_0015:
0039039e: 6e10 c39c 0200 0015: invoke-virtual {v2}, Lcom/zj/wuaipojie/ui/ChallengeSecond;->isvip()Z # method@9cc3
003903a4: 0a01 0018: move-result v1
003903a6: 3801 2a00 0019: if-eqz v1, :cond_0043
.line 37
003903aa: 1f02 6300 001b: check-cast v2, Landroid/content/Context; # type@0063
003903ae: 1a01 00ed 001d: const-string v1, "当前已经是大会员了哦!" # string@ed00
003903b2: 1f01 e514 001f: check-cast v1, Ljava/lang/CharSequence; # type@14e5
003903b6: 7130 ae11 1206 0021: invoke-static {v2, v1, v6}, Landroid/widget/Toast;->makeText(Landroid/content/Context;, Ljava/lang/CharSequence;, I)Landroid/widget/Toast; # method@11ae
003903bc: 0c01 0024: move-result-object v1
003903be: 6e10 af11 0100 0025: invoke-virtual {v1}, Landroid/widget/Toast;->show()V # method@11af
003903c4: 1401 1800 0d7f 0028: const v1, 0x7f0d0018
.line 38
003903ca: 6e20 e10f 1300 002b: invoke-virtual {v3, v1}, Landroid/widget/ImageView;->setImageResource(I)V # method@0fe1
003903d0: 1401 0800 0d7f 002e: const v1, 0x7f0d0008
.line 39
003903d6: 6e20 e10f 1400 0031: invoke-virtual {v4, v1}, Landroid/widget/ImageView;->setImageResource(I)V # method@0fe1
003903dc: 1401 0a00 0d7f 0034: const v1, 0x7f0d000a
.line 40
003903e2: 6e20 e10f 1500 0037: invoke-virtual {v5, v1}, Landroid/widget/ImageView;->setImageResource(I)V # method@0fe1
.line 41
003903e8: 6201 7d95 003a: sget-object v1, Lcom/zj/wuaipojie/util/SPUtils;->INSTANCE:Lcom/zj/wuaipojie/util/SPUtils; # field@957d
003903ec: 1223 003c: const/4 v3, 0x2
003903ee: 1a04 3db1 003d: const-string v4, "level" # string@b13d
003903f2: 6e40 499d 2134 003f: invoke-virtual {v1, v2, v4, v3}, Lcom/zj/wuaipojie/util/SPUtils;->saveInt(Landroid/content/Context;, Ljava/lang/String;, I)V # method@9d49
003903f8: 280e 0042: goto :goto_0050
.line 44
cond_0043:
003903fa: 1f02 6300 0043: check-cast v2, Landroid/content/Context; # type@0063
003903fe: 1a01 07ed 0045: const-string v1, "请先充值大会员哦!" # string@ed07
00390402: 1f01 e514 0047: check-cast v1, Ljava/lang/CharSequence; # type@14e5
00390406: 7130 ae11 1206 0049: invoke-static {v2, v1, v6}, Landroid/widget/Toast;->makeText(Landroid/content/Context;, Ljava/lang/CharSequence;, I)Landroid/widget/Toast; # method@11ae
0039040c: 0c01 004c: move-result-object v1
0039040e: 6e10 af11 0100 004d: invoke-virtual {v1}, Landroid/widget/Toast;->show()V # method@11af
goto_0050:
00390414: 0f06 0050: return v6

.end method

简单跟着吾爱作者分析了一下。

寄存器

在smali里的所有操作都必须经过寄存器来进行:本地寄存器用v开头数字结尾的符号来表示,如v0、 v1、v2。 参数寄存器则使用p开头数字结尾的符号来表示,如p0、p1、p2。特别注意的是,p0不一定是函数中的第一个参数,在非static函数中,p0代指“this",p1表示函数的第一个 参数,p2代表函数中的第二个参数。而在static函数中p0才对应第一个参数(因为Java的static方法中没有this方法)


学习链接:

https://www.52pojie.cn/thread-1695796-1-1.html

https://www.52pojie.cn/thread-1701353-1-1.html