警告:逆向开发相关知识请在法律规定范围内使用,请勿使用相关技术进行违法犯罪行为。
逆向开发
反编译 & 反混淆
反编译 是指将已经编译成机器码或字节码的程序文件,还原成接近源代码的形式的过程。
反编译工具
-
Apktool:是一款用于对第三方、封闭、二进制 Android 应用进行逆向工程的工具。它可以将资源解码为接近原始形式,并在进行一些修改后重建它们;它可以逐步调试 smali 代码。由于具有类似项目的文件结构和一些重复任务(例如构建 apk 等)的自动化,它还可以更轻松地使用应用。
-
dex2jar:此工具可以把 dex 字节码转换为 Java 字节码,当然,也可以将 dex 反汇编为 smali 文件并从 smali 文件组装 dex,使得你可以使用标准的Java反编译器(如 jd-gui)来查看代码。
-
jd-gui:这是一个独立的图形界面程序,用来浏览Java类文件中的源代码。当与 dex2jar 一起使用时,它可以显示反编译后的 Java 代码。
-
jadx:这是一个用于分析、反编译、解密Dalvik字节码和Java字节码、smali 调试器的强大工具,支持将.dex或.jar文件反编译为接近于原始源代码的格式。
-
fernflower:它是 IntelliJ IDEA 中内置的反编译器,也可以作为命令行工具使用,能够生成高质量的 Java 源代码。
反编译
使用 Apktool
进行反编译,使用的指令为:
- 解码:
apktool d apk所在路径/apk名称
打开 Windows命令行控制台 输入指令按回车即可对 apk 文件进行反编译。
完成后,会在本地生成一个文件夹,apk的资源就都在里边了。
反混淆
反混淆是逆向工程的重要手段,可以帮助开发者理解、调试和研究已发布的应用程序。
混淆相关的知识点可以参考本人以往博客文章 👉 【Android】App攻防之代码混淆
PS:混淆是提高 Android 应用安全性和减少 APK 大小的有效手段,但需要谨慎配置,以避免混淆后产生新的问题。
混淆后的 apk 可以通过一系列工具进行反混淆的操作,例如:
- JEB:是一款功能强大的反编译器,广泛用于逆向工程、安全分析和软件开发领域。JEB支持多种格式的文件和语言,提供了丰富的功能来帮助用户分析和理解二进制代码。
这些工具可以实现反混淆的同时,但是呢部分功能也是需要收费的,也很不巧,反混淆功能就在他们的收费列表之中…
这就难顶了,我以为大家都是为爱发电…
修改预置资源文件
执行上述反编译的操作后,生成的文件内容如下:
当我们使用 Android Studio 打包项目时,源代码(通常是 Java 或 Kotlin )首先会被编译成 Java 字节码(.class文件)。然后,这些.class文件会被进一步转换为 Dalvik 字节码(专为Android平台设计的一种中间代码形式),并打包到 classes.dex 文件中。如下图的 classes.dex 文件和 classes2.dex 文件。
扯那么多,那 Dalvik 字节码 和 smali 代码又是什么关系?
smali 是一种类似于汇编语言的文本格式,用于表示 Dalvik 字节码。
这也就解释了为什么反编译后生成的类它变成了 .smali 文件扩展名的文件。
如果你真的需要仅仅通过一个 .apk 文件就把人家的代码完全理解,那么,smali
将是你必须要跨过去的坎。
但是!!!如果,你只是简单的学习一下人家的部分代码是如何编写的,可以利用某一些工具去简化自己的学习成本,例如:
jadx-gui
:上面我们有提到过,这个可以将一些smali
代码转回 Java 代码,使用这个工具,可以最快的获取 apk 文件里面的信息。smali2java
:smali2java
是一个专门用于将smali
代码转换为 Java 代码的工具。它可以直接处理 smali 文件并生成相应的 Java 源代码。
由于 smali
代码是一种低级别的汇编语言,学习起来需要一定的时间,这里就不修改 smali
文件,而是修改打包时没有被进行加密处理的 AndroidManifest.xml
资源文件 。
修改后点击保存,再给 apk 重签名 + 二次打包就能得到一个修改后的 apk 了。
抓包
Proxyman 是一款高性能的抓包应用程序,使开发人员能够查看来自应用程序和域的 HTTP/HTTPS
请求,包括 iOS
设备、iOS 模拟器
和 Android
设备。
前期准备
-
下载
Proxyman
点击下载 👉 Proxyman
-
安装完成后打开
Proxyman
,根据指引安装证书到本地电脑 -
找一台
Root
过的Android
设备,让设备网络与电脑网络处于同一局域网,并能使用电脑ping
通Android
设备的 IP 地址。 -
进入
Android
设备的网络设置,为以太网
或WIFI
配置代理信息。配置的代理信息如下:Server:192.1.68.1.5
Port:9090
Authentication:No配置代理需要确保
Android
设备上的VPN
被关闭,以免代理冲突。 -
在
Android
设备上打开浏览器,访问http://proxy.man/ssl
网址下载 CA 证书。 -
打开设置,根据 安全 → 加密与凭证 → 安装证书 → CA 证书 的操作步骤安装刚刚下载的 CA 证书。
-
如果你的
Android
系统版本高于 10 ,需要在你的Android
项目上配置信任 CA 证书,即在res/xml/network_security_config.xml
加上如下配置:<?xml version="1.0" encoding="utf-8"?> <network-security-config><debug-overrides><trust-anchors><!-- Trust user added CAs while debuggable only --><!-- 信任用户添加的CA证书 --><certificates src="user" /><!-- 信任系统预装的CA证书 --><certificates src="system" /></trust-anchors></debug-overrides><!-- 允许明文流量(即HTTP请求)--><base-config cleartextTrafficPermitted="true"><!-- 配置指定信任的证书来源。--><trust-anchors><!-- 表示信任系统预装的CA证书和用户添加的CA证书 --><certificates src="system" /><!-- 信任系统预装的CA证书和用户添加的CA证书 --><certificates src="user" /></trust-anchors></base-config> </network-security-config>
将配置文件的引用添加到
AndroidManifest.xml
:<manifest xmlns:android="http://schemas.android.com/apk/res/android"><applicationandroid:networkSecurityConfig="@xml/network_security_config"></application> </manifest>
-
打开APP
具体详情可参考 👉 Android Device & Emulator
注意事项:
1、http
的网络请求在任何的 Android
版本上都可以进行抓包,https
的网络请求在Android 10及以上的Android
版本抓包,必须需要在 Android
项目加上一些配置才能抓包。
2、Android 10 及以上的 Android
版本需要在 Android
系统上配置 CA 证书。
3、使用抓包工具进行抓包的电脑要求要和 Android
端的网络处于同一局域网,且使用电脑能够 ping
通。
二次打包
二次打包是指在原始 APK 文件的基础上进行一些修改,然后重新编译生成一个新的 APK 文件。
打包使用的 apktool
指令为:
apktool b apk文件夹所在路径/apk文件夹
至此完成二次打包的操作。
重签名
由于 APK 的签名机制是与打包过程直接相关的,当你对 APK 进行二次打包时,APK 的内容(如 classes.dex、资源文件等)可能已经发生变化,这会导致原有的签名失效。
重签名需要生成一个签名文件,可以选择使用 Android Studio
生成,或者使用 keytool
命令生成。
以下为 keytool 生成签名的指令:
keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000
以上各个参数的含义如下:
genkey
:这个参数表示keytool要生成一个新的密钥对和一个自签名的证书。alias android_keystore
:这个参数指定了生成的密钥对和证书的别名(alias),此处别名为android_keystore
,这个别名在密钥库中用于唯一标识这个特定的密钥对和证书。keyalg RSA
:这个参数指定了用于生成密钥对的算法,即RSA算法。RSA是一种广泛使用的非对称加密算法,它使用一对密钥:一个公钥用于加密数据,另一个私钥用于解密数据。validity 10000
:这个参数设置了证书的有效期,以天为单位,此处证书的有效期是 10000 天。keystore android.keystore
:这个参数指定了密钥库(keystore)的文件名,即android.keystore。密钥库是一个用于存储密钥对和证书的数据库文件。
确认信息点击回车,就会提示你输入一系列的信息。
最后面输入字母 Y
确认输入的信息,并生成一个签名。
打包完成后还需要进行签名,麻烦,反编译一个 apk 一堆事…
到这里,逆向开发的入门操作就完成啦,快试试将 apk 安装到你的设备上运行试一试吧!
如何预防 App 被逆向开发?
代码混淆
代码混淆是一种软件保护技术,用于增加逆向工程的难度,使源代码或字节码更难以被理解和修改。在移动应用开发中,特别是 Android 应用,代码混淆是非常重要的一步,可以有效防止应用被反编译和破解。
在 Android 中,除了可以使用 AS 提供的 ProGuard
和 R8
进行混淆,还可以借助一些第三方库实现混淆。例如:
- DexGuard:
ProGuard
的商业版本,提供了更强大的混淆和保护功能。它不仅包括ProGuard
的所有功能,还增加了更多的安全特性。 - AndResGuard:一个专门针对
Android
应用资源文件进行混淆和压缩的工具。它可以帮助你减小 APK 的体积,同时提高应用的安全性。
关于 ProGuard
代码混淆的语法可参考 👉 【Android】App攻防之代码混淆
应用加固
应用加固的方式有很多,比如说 360 加固助手、百度应用加固、网易易盾等,360 加固助手每个月都是有免费加固次数使用,本文使用的是 360 加固助手。
点击下载 360 加固助手
使用 360 加固助手加固后的 app 再使用反编译软件打开,app 的源码、资源等信息已经提取不到了。
防止动态调试
防止动态调试通常指的是防止应用程序在调试模式下运行,或者检测到调试器时采取某些措施禁止调试。
防止动态调试可以在关键逻辑处加入检测代码,如果检测到调试器,则采取相应的措施,如退出应用或显示警告信息。
fun checkForDebugger() {if (isAppInDebugMode()) {// 采取相应措施Toast.makeText(this,"该应用禁止动态调试",Toast.LENGTH_LONG).show()// 可以选择退出应用System.exit(0)}
}fun isAppInDebugMode(): Boolean {return BuildConfig.DEBUG || Debug.isDebuggerConnected()
}
PS:防止动态调试建议和代码混淆、应用加固一起使用,否则形同虚设。
Root 检测
Root 检测是指在 Android 设备上检查是否已经获取了 root 权限的过程。Root 权限允许用户对设备进行底层操作,包括修改系统文件、访问受保护的数据等。
Root 检测通常用于确定设备的安全状态,防止被不怀好意的用户破解,apk 若是被安装到一台拥有 Root 权限的 Android 设备上,那么将可能被不怀好意的用户使用以下技术对 apk 进行破解等操作。
Hook
:指在程序运行时动态修改或插入代码,以改变程序原有的行为。
那么,如何进行 Root 检测呢?
- 检查已知的Root管理应用
许多Root管理应用(如SuperSU、Magisk等)会在设备上留下特定的文件或目录。可以通过检查这些文件或目录的存在来判断设备是否已被Root。public boolean checkForRootManagementApps() {String[] paths = {"/system/app/Superuser.apk","/system/bin/su","/system/xbin/su","/sbin/su","/system/sd/xbin/su","/system/bin/failsafe/su","/data/local/xbin/su","/data/local/bin/su","/system/sbin/su","/usr/bin/su","/usr/sbin/su","/system/extras/su","/system/usr/we-need-root/su"};for (String path : paths) {if (new File(path).exists()) {return true;}}return false; }
- 检查系统属性
某些Root工具会修改系统属性,可以通过检查这些属性来判断设备是否已被Root。public boolean checkForDangerousProps() {String[] props = {"ro.debuggable","ro.secure","ro.build.tags"};for (String prop : props) {try {String value = SystemProperties.get(prop);if ("1".equals(value) || "test-keys".equals(value)) {return true;}} catch (Exception e) {// Ignore}}return false; }
- 检查是否存在su命令
可以通过尝试执行su命令来判断设备是否已被Root。public boolean checkForSuBinary() {Process process = null;try {process = Runtime.getRuntime().exec(new String[]{"/system/xbin/which", "su"});BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));if (in.readLine() != null) {return true;}return false;} catch (Throwable t) {return false;} finally {if (process != null) {process.destroy();}} }
二次打包检测
二次打包检测是指检测应用是否被非法重新打包或篡改。通常有以下几种方式验证应用是否被进行了二次打包。
-
签名验证
通过验证应用的签名是否与预期的签名一致,可以判断应用是否被重新打包。public boolean isSignatureValid(Context context) {try {PackageManager pm = context.getPackageManager();PackageInfo packageInfo = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);for (Signature signature : packageInfo.signatures) {MessageDigest md = MessageDigest.getInstance("SHA");md.update(signature.toByteArray());String currentSignature = Base64.encodeToString(md.digest(), Base64.DEFAULT);// 预期的签名值String expectedSignature = "YOUR_EXPECTED_SIGNATURE_HERE";if (currentSignature.equals(expectedSignature)) {return true;}}} catch (PackageManager.NameNotFoundException | NoSuchAlgorithmException e) {e.printStackTrace();}return false; }
-
应用完整性校验
通过校验应用的文件完整性,可以判断应用是否被篡改。可以使用MD5或SHA-256等哈希算法来生成文件的校验值,并与预存的校验值进行比较。public boolean isAppIntegrityValid(Context context) {try {ApplicationInfo appInfo = context.getApplicationInfo();String apkPath = appInfo.sourceDir;// 计算当前APK的MD5值String currentMd5 = getMD5(apkPath);// 预期的MD5值String expectedMd5 = "YOUR_EXPECTED_MD5_HERE";return currentMd5.equals(expectedMd5);} catch (Exception e) {e.printStackTrace();}return false; }private String getMD5(String filePath) throws Exception {FileInputStream fis = new FileInputStream(filePath);MessageDigest md = MessageDigest.getInstance("MD5");byte[] dataBytes = new byte[1024];int nread = 0;while ((nread = fis.read(dataBytes)) != -1) {md.update(dataBytes, 0, nread);}fis.close();byte[] mdbytes = md.digest();// convert the byte to hex formatStringBuilder sb = new StringBuilder();for (byte b : mdbytes) {sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));}return sb.toString(); }
报错集合
-
执行
apktool b reverse_development-release
指令进行二次打包时,提示C:\Users\binjx\Desktop\反编译资源\reverse_development-release\res: error: failed to open directory: 系统找不到指定的 文件
。解决方案:将文件所在路径包含的中文名称改为英文名称。
编写文章期间产生的一些疑问
- 使用Java、kotlin、compose进行编码的类文件反编译有什么区别吗?
- apk 经过反编译后得到的代码是否可以在 Android Studio 上运行?
可以的。但是过程较为复杂,非专业人员需要耗费的时间过多,期间可能会遇到一些问题,例如:缺少部分资源文件、代码注释丢失、混淆后的代码难以理解等问题。
本文暂时欠缺部分逆向开发知识点如下:
- 动态调试
- Xposed
参考文章
1、当 App 有了系统权限,真的可以为所欲为?
2、Androidmanifest文件加固和对抗
3、Android 逆向入门保姆级教程
4、Android 逆向之 Xposed 开发
5、Android逆向之旅—反编译利器Apktool使用教程(Apktool的安装使用)
6、动态调试 & Log插桩