赞
踩
这篇博客主要介绍如何使用 Apktool、dex2jar、jd-gui等工具反编译 Android APP、修改源码、重新打包并签名。
1. 工具安装
1.1. Apktool
Apktool 主要用于反编译和重编译 APK 文件。
Windows 和 Linux 安装方法参照:官方指南
macOS 安装方法如下:
右键保存启动脚本,命名为 apktool
下载最新版本的 Apktool,命名为 apktool.jar
将 apktool 和 apktool.jar 移动到 /usr/local/bin 目录下
使用 chmod +x apktool 和 chmod +x apktool.jar 添加运行权限
在命令行直接运行命令 apktool 即可,安装完成
1.2. dex2jar
dex2jar 主要用于将 dex 文件转为 jar 文件
从 GitHub 下载最新版本,并解压。运行解压后目录下的 d2j-dex2jar.sh 或 d2j-dex2jar.bat 即可。
1.3. jd-gui
jd-gui 主要用来可视化 jar 文件。
从 GitHub 下载最新版本,并解压。运行解压后目录下的 JD-GUI 即可。
macOS 下注意:
如果 jd-gui 无法打开,编辑 JD-GUI.app/Contents/MacOS/universalJavaApplicationStub.sh
1
2
3
4
5
6
7
8
9
10
11
12exec "$JAVACMD" \
-cp "${JVMClassPath}" \
-Xdock:icon="${ResourcesFolder}/${CFBundleIconFile}" \
-Xdock:name="${CFBundleName}" \
## ----- 添加这两行 -----
--add-opens java.base/jdk.internal.loader=ALL-UNNAMED \
--add-opens jdk.zipfs/jdk.nio.zipfs=ALL-UNNAMED \
## --------------------
${JVMOptions:+$JVMOptions }\
${JVMDefaultOptions:+$JVMDefaultOptions }\
${JVMMainClass}\
${JVMArguments:+ $JVMArguments}
2. 查看源码
以小米投屏神器 APP 为例。
运行 d2j-dex2jar.sh 将 APK 中的 dex 文件转为 jar 文件:
1
2$ ./d2j-dex2jar.sh mi.apk
dex2jar mi.apk -> ./mi-dex2jar.jar
打开 jd-gui,将 mi-dex2jar.jar 拖进去,即可看到反编译后的源码。
3. 修改源码
这里以增加 Log 为修改源码的例子。
jd-gui 只能查看源码,但是无法修改。要修改源码,只能修改 smali 文件,它类似于汇编,但是要简单很多。
首先得使用 apktool 进行反编译:
1apktool d mi.apk -o mi
这里将 mi.apk 进行反编译并将结果放到 mi 目录中。
首先找到需要添加 Log 的位置,建议在 jd-gui 中寻找,然后在 mi 目录中定位。
例如以下 class 和 smali 的对应关系
mi-dex2jar.jar!/com/xiaomi/mitv/phone/tvassistant/b/a.class
mi/smali/com/xiaomi/mitv/phone/tvassistant/b/a.smali
打开 a.class 和 a.smali,首先看 java:
1
2
3
4
5
6
7
8
9public void a(int paramInt)
{
String str1 = String.valueOf(System.currentTimeMillis());
String str2 = a(String.valueOf(paramInt), this.b, str1);
new c(this.d, String.format("http://%s:6095/general?action=setVolum&volum=%d&ts=%s&sign=%s", new Object[] { this.a, Integer.valueOf(paramInt), str1, str2 }), new c.a()
{
public void a(int paramAnonymousInt, String paramAnonymousString){}
}).d();
}
对应的 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.method public a(I)V
# a 是方法,p0 = this
# a 接收一个参数, p1 = paramInt'
# 定义了 8 个寄存器
.locals 8
.prologue
.line 55
# 获取系统当前时间
invoke-static {}, Ljava/lang/System;->currentTimeMillis()J
# 结果赋值给 v0
move-result-wide v0
# 调用 String.valueOf 方法,将 v0 转为字符串
invoke-static {v0, v1}, Ljava/lang/String;->valueOf(J)Ljava/lang/String;
# 结果赋值给 v0
move-result-object v0
# 到这一步
# String str1 = String.valueOf(System.currentTimeMillis());
# 执行完了
.line 57
# 调用 String.valueOf 方法 p1 转为字符串
invoke-static {p1}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
# 结果赋值给 v1
move-result-object v1
# 获取 this.b,也就是从 p1 中获取 b,并赋值给 v2
iget-object v2, p0, Lcom/xiaomi/mitv/phone/tvassistant/b/a;->b:Ljava/lang/String;
# 将 v1,v2,v0 作为参数调用方法 a,并传递 p0 作为 this
invoke-direct {p0, v1, v2, v0}, Lcom/xiaomi/mitv/phone/tvassistant/b/a;->a(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
# 将结果赋值给 v1
move-result-object v1
# 到这一步
# String str2 = a(String.valueOf(paramInt), this.b, str1);
# 执行完了
...
假设我们想要得知其中 this.b 的值,可以这样添加 Log:
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.method public a(I)V
# 额外定义 4 个寄存器用于存储 TAG
.locals 12
.prologue
.line 55
# 设置初始化TAG
const-string v8, "AndiedieHack.currentTimeMillis"
const-string v9, "AndiedieHack.param"
const-string v10, "AndiedieHack.b"
const-string v11, "AndiedieHack.result"
invoke-static {}, Ljava/lang/System;->currentTimeMillis()J
move-result-wide v0
invoke-static {v0, v1}, Ljava/lang/String;->valueOf(J)Ljava/lang/String;
move-result-object v0
# 第一个 Log,使用 v8 作为 TAG,输出 v0 的值,即系统当前时间
invoke-static {v8, v0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
.line 57
invoke-static {p1}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
move-result-object v1
# 第二个 Log,使用 v9 作为 TAG,输出 v1 的值
# v1 是 p1 的字符串形式,p1 是函数参数
invoke-static {v9, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
iget-object v2, p0, Lcom/xiaomi/mitv/phone/tvassistant/b/a;->b:Ljava/lang/String;
# 第三个 Log,使用 v10 作为 TAG,输出 v2 的值,即 this.b
invoke-static {v10, v2}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
invoke-direct {p0, v1, v2, v0}, Lcom/xiaomi/mitv/phone/tvassistant/b/a;->a(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
# 第四个 Log,使用 v11 作为 TAG,输出 v1 的值,即 str2
invoke-static {v11, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
到这一步,源码修改完成。接下来进行重新打包。
3. 重新打包与签名
将修改后的内容重新打包为 APK:
1apktool b mi -o unsigned.apk
打包之后的 unsigned.apk 是没有签名的,无法安装。
签名需要使用一个 keystore,可以直接使用 Android Studio 为我们生成的 debug 用 keystore。
位置在 用户目录/.android/debug.keystore,alias 是 androiddebugkey,密码是 android
签名命令:
1jarsigner -keystore debug.keystore -signedjar signed.apk unsigned.apk androiddebugkey
输入密码即可。
4. 测试
在手机上安装 signed.apk,注意如果之前已经安装了投屏神器,需要先卸载,因为两者的签名不一致。
运行 APP,调整音量,可以看到以下 Log:
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。