这篇文章来学习一下有关安卓重打包中的一些基本操作
一些知识
apk文件
apk文件其实就是一个包含资源和已编译Java代码的zip文件,跟jar包类似,里面有一些结构需要知道
-
classes.dex: Dalvik 字节码,存放 Java/Kotlin 代码编译后的逻辑。 -
AndroidManifest.xml: 应用的配置文件(权限、组件声明)。 -
META-INF/: 存放签名信息(CERT.RSA,CERT.SF,MANIFEST.MF),这是重打包必须破坏并重建的部分。 -
resources.arsc&res/: 资源文件。
Smali汇编语言
当反编译classes.dex字节码的时候,通常得到的就是Smali代码,就是类似汇编各种指令
签名机制
apk的签名机制是用来确保应用安全性的重要环节,它不仅保障了应用来源的真实性,还防止了APK文件在传输过程中被篡改。Android系统要求每个应用程序都必须经过数字签名,这是一种使用密钥对(包含公钥和私钥)对应用程序进行加密和验证的方式。
安卓签名的演进分为v1-v4
v1签名(基于JAR的签名)
Android 7.0 之前唯一的机制,基于 Java 的 JAR 签名标准
它会对APK (ZIP) 中的每个文件进行 Hash 计算,并将结果保存在 META-INF/MANIFEST.MF 中。在安装时,系统会解压apk,逐个文件校验Hash。
缺陷:
-
速度慢: 安装时需要解压校验所有文件。
-
完整性保护不全: 它只保护了ZIP里面的单个文件内容,但没有保护 ZIP 文件的元数据(如 ZIP Comment)。攻击者可以在不解压的情况下,向 ZIP 的空闲区或注释区插入数据(例如多渠道打包工具原理),而不破坏 V1 签名。
V2 签名 (Full APK Signature)
在Android 7.0 (Nougat)引入
它不再校验单个文件,而是对整个 APK 二进制文件进行 Hash 校验。在 ZIP 文件的结构中(Central Directory 之前)插入了一个独立的 APK Signing Block。
一旦你修改了 APK 中的任何一个字节(哪怕是重新压缩一下),V2 签名就会立即失效。
V3 签名 (密钥轮替方案)
在Android 9.0 (Pie)引入
以前如果你的签名私钥泄露了,或者公司换了主体,你必须发布一个新的 App(包名不变但签名变了,用户无法覆盖安装)。V3 允许你在签名块中包含“旧密钥”和“新密钥”的证明链,实现平滑过渡。
V4 签名
在Android 11引入
主要是为了配合 ADB 的增量安装(Incremental Install),主要用于开发调试和应用商店的大文件流式安装。
签名方案对比
常用工具或者网站
Apktool:https://apktool.org/(反编译和回编译工具)
免费开源的apk网站:https://f-droid.org/
apksigner:https://developer.android.com/tools/apksigner(签名工具)
jarsigner:签名工具,jdk自带
重打包测试
这里拿F-Droid上的一个开源app来做测试:https://github.com/siyuan-note/siyuan-android
先进行解码
java -jar .\apktool_2.12.1.jar d .\apk\siyuan-3.4.2.apk

解码完成后的目录结构如下:

接下来我们去修改一下增加一些代码进行测试,常用的方式是”Smali嫁接法“,就是先用java写好恶意逻辑,编译成apk,反编译拿到Smali,然后复制到前面的目录当中,最后在主程序插桩调用它。
这里用Android Studio创建一个空项目,然后写一个弹窗代码
package com.clown.myevil;
import android.content.Context;
import android.util.Log;
import android.widget.Toast;
public class Payload {
// 静态方法,接收一个 Context 参数用于弹窗
public static void start(Context context) {
Log.e("HACK_TAG", "恶意代码开始运行...");
Toast.makeText(context, "嫁接法注入成功!", Toast.LENGTH_LONG).show();
}
}
然后我们Build一下这个apk

然后去项目的build目录下找到apk

我们需要app-debug.apk,这里封装的是主程序,还有另一个带test的是测试用的,李米娜封装的是测试代码
然后同样反编译我们这个apk
java -jar apktool_2.12.1.jar d .\apk\app-debug.apk
然后在app-debug\smali_classes3\com\clown\myevil这里找到我们的Payload.smali代码,smali代码内容如下
.class public Lcom/clown/myevil/Payload;
.super Ljava/lang/Object;
.source "Payload.java"
# direct methods
.method public constructor <init>()V
.locals 0
.line 7
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
.method public static start(Landroid/content/Context;)V
.locals 2
.param p0, "context" # Landroid/content/Context;
.line 10
const-string v0, "HACK_TAG"
const-string v1, "\u6076\u610f\u4ee3\u7801\u5f00\u59cb\u8fd0\u884c..."
invoke-static {v0, v1}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
.line 11
const-string v0, "\u5ac1\u63a5\u6cd5\u6ce8\u5165\u6210\u529f\uff01"
const/4 v1, 0x1
invoke-static {p0, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v0
invoke-virtual {v0}, Landroid/widget/Toast;->show()V
.line 12
return-void
.end method
但是观察我们前面反编译apk的smali文件夹

发现全部都被混淆过了,所以我们要看看怎么找到他的主程序
我们可以在AndroidManifest.xml这个文件里找到主activity
<activity
android:theme="@style/Theme.SiYuan.Boot"
android:name="org.b3log.siyuan.BootActivity"
android:exported="true"
android:configChanges="screenSize|orientation"
android:hardwareAccelerated="true"
android:autoRemoveFromRecents="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="siyuan"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
这里就可以看出主activity为org.b3log.siyuan.BootActivity

也可以比较简单找到,因为有些包是混淆不了的,比如这个入口点的类,系统(如 Dalvik/ART 虚拟机、包管理器)是通过反射或者硬编码字符串来查找和实例化这些组件的。如果这些类名被混淆了,应用将崩溃。
找到对应的smali代码之后,我们定位到onCreate方法下面,进行修改
原代码片段
.method public final onCreate(Landroid/os/Bundle;)V
.locals 3
.line 1
const-string v0, "boot"
.line 2
.line 3
const-string v1, "Create boot activity"
.line 4
.line 5
invoke-static {v0, v1}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I
.line 6
.line 7
.line 8
invoke-super {p0, p1}, Landroidx/fragment/app/x;->onCreate(Landroid/os/Bundle;)V
.line 9
.line 10
.line 11
const p1, 0x7f0b001c
修改后的代码片段
.method public final onCreate(Landroid/os/Bundle;)V
.locals 3
.line 1
const-string v0, "boot"
.line 2
.line 3
const-string v1, "Create boot activity"
.line 4
.line 5
invoke-static {v0, v1}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I
.line 6
.line 7
.line 8
invoke-super {p0, p1}, Landroidx/fragment/app/x;->onCreate(Landroid/os/Bundle;)V
invoke-static {p0}, Lcom/clown/myevil/Payload;->start(Landroid/content/Context;)V
.line 9
.line 10
.line 11
const p1, 0x7f0b001c
p0 是当前 Activity 的实例,也就是 Context
然后将我们的Payload.smali放在对应的目录下面,即siyuan-3.4.2/smali/com/clown/myevil目录下
然后回编译该apk
java -jar .\apktool_2.12.1.jar b .\apk\siyuan-3.4.2\ -o .\apk\attack.apk
然后进行签名,不签名的话无法安装,这里用apksigner来进行签名,直接在Android Studio中依次点击菜单栏的 Build -> Generate Signed Bundle / APK...,然后选择APK生成签名即可(信息自己看着填)
或者用jdk自带的keytool来生成,前面的方法如果JDK版本和Android Build Tools 之间的版本出现兼容问题可能会报错
keytool -genkeypair -v -keystore "./research-key.jks" -alias research_key -keyalg RSA -keysize 2048 -validity 1000 -storepass 123456 -keypass 123456 -storetype JKS
然后执行下面的命令进行签名
apksigner sign --ks .\research-key.jks --ks-key-alias research_key --ks-pass pass:123456 --key-pass pass:123456 --out final_attack.apk .\attack.apk
签名完后在模拟器安装启动

此时就能看到我们的弹窗了,成功将该应用进行重打包