Android_Note( 一 )
安卓逆向基础
APK结构
APK 是 Android Package 的缩写,是 Android 平台上的应用程序包文件格式。APK 文件是一个 ZIP 压缩格式的文件,其中包含了应用程序的代码、资源文件和 META-INF 目录等文件。APK 文件的结构如下:
文件 | 注释 |
---|---|
assets目录 | 存放APK的静态资源文件,比如视频,音频,图片等 |
lib 目录 | armeabi-v7a基本通用所有android设备,arm64-v8a只适用于64位的android设备,x86常见用于android模拟器,其目录下的.so文件是c或c++编译的动态链接库文件 |
META-INF目录 | 保存应用的签名信息,签名信息可以验证APK文件的完整性,相当于APK的身份证(验证文件是否又被修改) |
res目录 | res目录存放资源文件,包括图片,字符串等等,APK的脸蛋由他的layout文件设计 |
AndroidMainfest.xml文件 | APK的应用清单信息,它描述了应用的名字,版本,权限,引用的库文件等等信息 |
classes.dex文件 | classes.dex是java源码编译后生成的java字节码文件,APK运行的主要逻辑 |
resources.arsc文件 | resources.arsc是编译后的二进制资源文件,它是一个映射表,映射着资源和id,通过R文件中的id就可以找到对应的资源 |
初识AndroidManifest.xml
AndroidManifest.xml文件是整个应用程序的信息描述文件,它描述了应用程序的名称、版本、图标、权限、组件等信息。AndroidManifest.xml文件是一个 XML 格式的文件,它的根元素是 manifest 元素,manifest 元素的几个常见属性如下:
- package:应用程序的包名,每个应用程序都有一个唯一的包名。如QQ的包名是com.tencent.mobileqq。
- versionCode:应用程序的版本号,用于区分不同版本的应用程序。
- versionName:应用程序的版本名称,用于显示给用户。
- uses-permission android:name=“”:声明应用程序需要的权限。例如,如果应用程序需要访问网络,则需要声明 android.permission.INTERNET 权限。
- application:应用程序的信息,包括应用程序的名称、图标、主题、启动 Activity 等信息。
- activity:应用程序的 Activity 组件,每个 Activity 都需要在 AndroidManifest.xml 文件中进行声明。
- android:lable=“@string/app_name”:Activity 的名称,用于显示在应用程序的标题栏中。
- android:icon=“@drawable/ic_launcher”:Activity 的图标,用于显示在应用程序的标题栏中。
- intent-filter:Activity 的过滤器,用于指定 Activity 的启动方式。例如,通过指定 MAIN 和 LAUNCHER,可以将 Activity 设置为应用程序的启动 Activity。
- android:debuggable=“true”:是否允许调试应用程序。
每个应用程序在根目录下必须包含一个AndroidManifest.xml文件,且文件名不能修改。它描述了package中暴露的组件,他们各自的实现类,各种能被处理的数据和启动位置。
简单Android逆向举例——双开
双开软件是一种可以在同一台手机上安装多个同一应用的软件,比如可以在同一台手机上安装两个微信,两个QQ等。双开有多种实现方式:
- 多开分身:通过修改应用程序的包名和签名,实现多个应用程序的安装。
- 修改Framework:对于有系统修改权限的厂商,可以修改 Framework 来实现双开的目的,例如:小米自带多开
- Xposed框架:通过 Xposed 框架实现双开,通过 Hook 应用程序的启动逻辑,实现多个应用程序的安装。
- 虚拟化技术:通过虚拟化技术实现双开,虚拟 Framework 层、虚拟文件系统、模拟 Android 对组件的管理、虚拟应用进程管理等一整套虚拟技术,将 APK 复制一份到虚拟空间中运行。
- 以插件机制运行:利用反射替换,动态代(过)(滤)理,hook了系统的大部分与system—server进程通讯的函数,以此作为“欺上瞒下”的目的,欺骗系统“以为”只有一个apk在运行,瞒过插件让其“认为”自己已经安装。例如:VirtualApp
由于其他几种方式比较复杂,涉及到系统层面的修改,这里主要介绍第一种方式:多开分身。多开分身的原理是通过修改应用程序的包名和签名,实现多个应用程序的安装。这里用 NP管理器简单演示,具体步骤如下:
先在菜单栏选择安装包提取,然后选择要提取的应用,
提取成功后,点击定位,定位到安装包位置
点击安装包,能看到安装包的一些基本信息,我们点击功能
选择 APK 共存,然后会在当前目录下生成一个新的 APK 文件,这个 APK 文件就是多开分身的 APK 文件。
点击生成的安装包,可以看到生成的 APK 文件的包名和签名状态已经修改了,这样就实现了多开分身。这时候就可以点击安装了。
Dalvik虚拟机和smali语言
Dalvik 虚拟机是 Android 平台上的虚拟机,它是专门为 Android 平台设计的虚拟机,用于执行 Android 应用程序的字节码。它有专属的字节码格式 dex,Dalvik 虚拟机的字节码文件是以 .dex 为后缀的文件。Dalvik 虚拟机的字节码文件是通过将 Java 字节码文件转换为 Dalvik 字节码文件生成的,这个过程叫做 dex 编译。
smali 是一种基于 Dalvik 字节码的汇编语言,它是 Dalvik 字节码的文本表示形式,用于描述 Dalvik 字节码的指令。smali 语言的语法和 Java 语言的语法有很大的不同,smali 语言的指令是 Dalvik 字节码的指令,它是一种基于寄存器的指令集,每个指令都是一个操作码和操作数的组合。smali 语言的指令和 Java 语言的指令是一一对应的,通过 smali 语言可以很方便地查看和修改 Dalvik 字节码。我们在反编译 Android 应用程序时,无法直接修改 Java 源码,但可以通过修改 smali 代码来修改 Dalvik 字节码,从而实现对应用程序的修改。
这里仅对 smali 语言做简单介绍,更多语法内容遇到了再自行查询。
这里分享一个吾爱大佬写的smali语法查询:smali语法查询
首先是关键字,smali 语言的关键字和 Java 语言的关键字有很大的不同,smali 语言的关键字主要包括 .class、.super、.source、.field、.method、.register、.end method、public、protected、private、.parameter、.prologue、.line xxx 等,这些关键字用于描述类、方法、变量等信息。
名称 | 注释 |
---|---|
.class | 类名 |
.super | 父类名,继承的上级类名名称 |
.source | 源名 |
.field | 变量 |
.method | 方法名 |
.register | 寄存器 |
.end method | 方法名的结束 |
public | 公有 |
protected | 半公开,只有同一家人才能用 |
private | 私有,只能自己使用 |
.parameter | 方法参数 |
.prologue | 方法开始 |
.line xxx | 位于第xxx行 |
然后是数据类型,smali 语言的数据类型和 Java 语言的数据类型有很大的不同,smali 语言的数据类型主要包括 V、Z、B、S、C、I、J、F、D、string、Lxxx/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 | 对象类型,返回对象 |
最后是指令集,smali 语言的指令集和 Dalvik 字节码的指令集是一一对应的,通过 smali 语言可以很方便地查看和修改 Dalvik 字节码。smali 语言的指令集主要包括常量指令、加载指令、存储指令、算术指令、逻辑指令、控制指令、异常指令等,这些指令用于描述 Dalvik 字节码的操作。
指令 | 注释 |
---|---|
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 | 获取寄存器数据 |
smali语法实例——VIP会员绕过
接下来就通过一个简单的实例来学习 smali 语法。实例安装包
首先先看看功能,点击获取硬币,硬币数就会增加,单击一键三连提示请长按完成一键三连,长按一键三连就提示请先充值大会员哦!
根据关键词大会员,我们在jadx-gui中搜索大会员,找到相关代码
找到对应 Java 代码,我们可以看到在点击一键三连时,会调用一个名为isVip的方法,如果不是大会员则会提示请先充值大会员。我们可以通过修改 smali 代码来绕过这个检测。
找到对应的 smali 代码,
分析如下:
1 | //一个私有、静态、不可变的方法 方法名lambda-2 |
可以看到,isvip方法默认返回值为0,即不是大会员,我们可以通过修改这个方法的返回值来绕过大会员检测。我们可以将返回值改为1,即是大会员,这样就可以绕过大会员检测了。
其次我们需要先获取10个硬币,才能点亮一键三连,我们可以通过修改 onCreate$lambda-2 方法中的 if-ge p0, v0, :cond_15 这一行代码,将 v0 的值改为 0,这样就可以绕过硬币检测了。但是这个不是很必要,因为我们可以直接修改硬币的数量,这里只是为了演示 smali 语法。
接下来就是修改环节,用 NP管理器提取安装包后查看 classes.dex 文件
选择 DEX编辑PLUS,搜索 isvip,找到对应的方法,将返回值改为1
定位到 isvip 方法的 smali 代码,将返回值改为 1
修改完成后保存退出,NP管理器会自动重新打包签名,点击安装即可
重新安装后就可以点亮一键三连了