[TOC]
最后更新: 2025-01-04
测试版本: DBS App 25.12.3
状态: ✅ 重打包成功,应用可正常启动
本文档详细说明如何使用 PineHook 框架重打包 Android APK,并注入自定义 Hook 代码(HookEntry.java)。
- ✅ DBS App 25.12.3: 重打包成功,应用可正常启动
- ✅ Hook 成功安装并运行
- ✅ 成功拦截多个安全检测点
- DEX 64K 限制: 大型 APK 必须使用
smali_classesX目录,不能使用主smali/目录 - Hook 初始化: 在
Application.onCreate()方法中注入HookEntry.init()调用 - 验证步骤: 重打包后必须验证 DEX 文件完整性
wget https://repo1.maven.org/maven2/top/canyie/pine/core/0.3.0/core-0.3.0.aar -O pine-core.aar
方法B:从本地项目获取
cp /home/cvm/CodexCode/pine/prebuilt/pine-core.aar ./
unzip pine-core.aar -d pine_aar_content
# 复制so文件
cp pine_aar_content/jni/arm64-v8a/libpine.so output/lib/arm64-v8a/
cp pine_aar_content/jni/armeabi-v7a/libpine.so output/lib/armeabi-v7a/
- dex2jar (d2j) - jar转dex
- baksmali - dex转smali
wget https://github.com/pxb1988/dex2jar/releases/download/v2.4/dex-tools-v2.4.zip
unzip dex-tools-v2.4.zip
export PATH=$PATH:$(pwd)/dex-tools-v2.4
wget https://github.com/baksmali/smali/releases/download/3.0.9/baksmali-3.0.9-fat-release.jar -O baksmali-3.0.9.jar
转换流程:
# 步骤1: jar → dex
d2j-jar2dex.sh pine_aar_content/classes.jar -o pine_classes.dex
# 步骤2: dex → smali
java -jar baksmali-3.0.9.jar d pine_classes.dex -o output/smali
对于大型 APK(如 DBS App),如果主 smali/ 目录的方法引用已接近 65,536 限制,必须将 Pine Hook 放到新的 smali_classesX 目录,而不是主 smali/ 目录。
方法 A:标准方法(适用于小型 APK)
# 复制so文件
cp -r output/lib/* target_src/lib/
# 复制smali文件到主 smali 目录
cp -r output/smali/top target_src/smali/
方法 B:避免 DEX 64K 限制(适用于大型 APK)
# 复制so文件
cp -r output/lib/* target_src/lib/
# 找到最新的 smali_classes 编号
LATEST=$(find target_src -type d -name "smali_classes*" | sed 's/.*smali_classes//' | sort -V | tail -1)
NEXT=$((LATEST + 1))
# 复制smali文件到新的 smali_classes 目录
mkdir -p target_src/smali_classes${NEXT}
cp -r output/smali/top target_src/smali_classes${NEXT}/
如何判断需要使用哪种方法?
- 如果重打包时出现
Unsigned short value out of range: 65XXX错误,必须使用方法 B - 如果重打包成功但生成的
classes.dex文件头损坏(全是 0),必须使用方法 B - 如果应用崩溃并显示
Invalid or truncated dex file,必须使用方法 B
zipalign -p -f -v 4 target_patched_unsigned.apk aligned.apk
aapt dump badging <你的APK文件路径.apk>
launchable-activity: name='com.igg.andr.Launcher'
package com.hook;
import android.util.Log;
import top.canyie.pine.Pine;
import top.canyie.pine.PineConfig;
import top.canyie.pine.callback.MethodHook;
import java.lang.reflect.Method;
public class HookEntry {
private static boolean initialized = false;
public static void init(ClassLoader classLoader) {
if (initialized) return;
initialized = true;
Log.d("PineHook", "Initializing hooks...");
PineConfig.debug = true;
PineConfig.debuggable = true;
try {
Class<?> targetClass = classLoader.loadClass("OO0o00.OOOO0");
Method targetMethod = targetClass.getDeclaredMethod("O0Oo", long.class, java.lang.String[].class);
Pine.hook(targetMethod, new MethodHook() {
@Override
public void beforeCall(Pine.CallFrame callFrame) {
Log.d("PineHook", "beforeCall: " + callFrame.args[0]);
// 修改参数
// callFrame.args[0] = "modified";
}
@Override
public void afterCall(Pine.CallFrame callFrame) {
Log.d("PineHook", "afterCall result: " + callFrame.getResult());
// 修改返回值
// callFrame.setResult("hooked result");
}
});
Log.d("PineHook", "Hook installed successfully");
} catch (Exception e) {
Log.e("PineHook", "Hook failed", e);
}
}
}
对于大型 APK,HookEntry 也必须放到新的 smali_classesX 目录,而不是主 smali/ 目录。
方法 A:标准方法(适用于小型 APK)
#编译Apk
# apktool d <编译好的APK路径>
# 取出com\hook类放到需要重打包的com\hook即可
# 即:cp -r temp_decompiled/smali/com/hook target_decompiled/smali/
方法 B:避免 DEX 64K 限制(适用于大型 APK)
#编译Apk
# apktool d <编译好的APK路径>
# 找到最新的 smali_classes 编号
LATEST=$(find target_decompiled -type d -name "smali_classes*" | sed 's/.*smali_classes//' | sort -V | tail -1)
NEXT=$((LATEST + 1))
# 复制到新的 smali_classes 目录
mkdir -p target_decompiled/smali_classes${NEXT}/com/hook
cp -r temp_decompiled/smali/com/hook/* target_decompiled/smali_classes${NEXT}/com/hook/
# 方法 1: 使用 aapt 查看 AndroidManifest.xml
aapt dump badging <APK路径> | grep "application-label"
# 方法 2: 在反编译目录中搜索
find decompiled -name "*Application.smali" -type f
# 方法 3: 查看 AndroidManifest.xml
grep -r "android:name" decompiled/AndroidManifest.xml | grep ApplicationDBS App 示例:
- Application 类:
com.dbs.sg.iwealth.IWApplication - 文件位置:
decompiled/smali_classes2/com/dbs/sg/iwealth/IWApplication.smali
在 Application 类的 onCreate() 方法开始处注入以下代码:
.method public onCreate()V
.locals 5 # ⚠️ 重要:必须增加 .locals 数量(原值 +1)
# ========== Hook 初始化 ==========
invoke-virtual {p0}, Landroid/app/Application;->getClassLoader()Ljava/lang/ClassLoader;
move-result-object v4 # 使用新的寄存器(v4 或更高)
invoke-static {v4}, Lcom/hook/HookEntry;->init(Ljava/lang/ClassLoader;)V
# ========== Hook 初始化结束 ==========
# 原有代码继续...
.line 1
invoke-virtual {p0}, Lcom/dbs/sg/iwealth/IWApplication;->N()Z
# ...
.end method关键注意事项:
-
.locals数量: 必须增加 1(因为使用了新的寄存器v4)- 如果原来是
.locals 4,改为.locals 5 - 如果原来是
.locals 5,改为.locals 6
- 如果原来是
-
寄存器选择: 使用未使用的寄存器(通常是
v4或更高)- 检查方法中已使用的寄存器(
v0,v1,v2,v3等) - 选择第一个未使用的寄存器
- 检查方法中已使用的寄存器(
-
注入位置: 必须在
onCreate()方法的最开始(在.locals之后,任何其他代码之前)
# 检查注入是否成功
grep -A 5 "HookEntry.init" decompiled/smali_classes*/com/dbs/sg/iwealth/IWApplication.smali
# 应该看到类似输出:
# invoke-virtual {p0}, Landroid/app/Application;->getClassLoader()Ljava/lang/ClassLoader;
# move-result-object v4
# invoke-static {v4}, Lcom/hook/HookEntry;->init(Ljava/lang/ClassLoader;)V# 进入反编译目录
cd decompiled
# 重新打包
apktool b . -o ../base-repacked-unsigned.apk
# 检查输出
ls -lh ../base-repacked-unsigned.apk预期输出:
- APK 文件大小应该接近原始 APK(通常略大 1-5MB)
- 不应该有错误信息(警告可以忽略)
zipalign -p -f -v 4 \
base-repacked-unsigned.apk \
base-repacked-aligned.apk验证对齐:
zipalign -c -v 4 base-repacked-aligned.apk
# 应该显示: Verification successful方法 1: 使用 apksigner(推荐)
# 创建密钥库(如果不存在)
keytool -genkey -v -keystore my-release-key.jks \
-keyalg RSA -keysize 2048 -validity 10000 \
-alias my-key-alias \
-storepass android \
-keypass android \
-dname "CN=Android Debug, O=Android, C=US"
# 签名
apksigner sign \
--ks my-release-key.jks \
--ks-key-alias my-key-alias \
--ks-pass pass:android \
--key-pass pass:android \
--out base-repacked-signed.apk \
base-repacked-aligned.apk
# 验证签名
apksigner verify base-repacked-signed.apk方法 2: 使用 jarsigner(备选)
jarsigner -verbose \
-sigalg SHA256withRSA \
-digestalg SHA-256 \
-keystore my-release-key.jks \
-storepass android \
-keypass android \
base-repacked-aligned.apk \
my-key-alias重打包后,务必验证 classes.dex 文件是否正常:
# 检查 classes.dex 文件头(应该是 'dex\n035\0')
unzip -p target_signed.apk classes.dex | head -c 100 | hexdump -C | head -5
# 如果文件头全是 0,说明 DEX 文件损坏(通常是 DEX 64K 限制导致)
# 必须使用方法 B(将代码放到新的 smali_classes 目录)常见错误处理:
-
Unsigned short value out of range: 65XXX- 原因: DEX 64K 限制(方法引用索引超过 65,536)
- 解决: 使用方法 B(将代码放到新的
smali_classesX目录) - 验证: 检查
classes.dex文件头是否正常
-
Invalid or truncated dex file- 原因:
classes.dex文件损坏(通常是 DEX 64K 限制导致) - 症状: 文件头全是 0,文件大小异常减小
- 解决: 使用方法 B(将代码放到新的
smali_classesX目录) - 验证:
unzip -p target_signed.apk classes.dex | head -c 100 | hexdump -C # 应该看到: 64 65 78 0a 30 33 35 00 (dex.035.) # 如果全是 0,说明文件损坏
- 原因:
-
ClassLoaderContext classpath size mismatch- 原因: DEX 文件数量不匹配(通常是重复的
smali_classes目录) - 解决: 检查并清理重复文件
# 查找重复的 HookEntry find decompiled -name "HookEntry.smali" -type f # 只保留最新的,删除旧的
- 原因: DEX 文件数量不匹配(通常是重复的
-
VerifyError: Verifier rejected class- 原因: Smali 代码验证失败,通常是寄存器使用错误
- 常见错误:
.locals数量不足(注入代码需要额外寄存器)- 使用了已占用的寄存器
- 解决:
- 检查
.locals数量是否正确(原值 +1) - 确保使用的寄存器(如
v4)未被占用 - 验证注入代码的语法正确性
- 检查
-
ClassNotFoundException: Didn't find class "com.hook.HookEntry"- 原因: HookEntry.smali 未正确复制到 APK
- 解决:
- 检查
smali_classesX/com/hook/HookEntry.smali是否存在 - 确保所有内部类(
HookEntry$*.smali)都已复制 - 验证文件权限和路径正确
- 检查
-
Hook installed successfully但应用仍显示安全警告- 原因: Hook 安装成功,但可能有其他检测方法未覆盖
- 排查:
- 检查日志中是否有其他检测调用
- 确认所有检测点都已 Hook
- 检查是否有 native 层检测
first