Skip to content

Instantly share code, notes, and snippets.

@jimmy947788
Last active January 4, 2026 14:17
Show Gist options
  • Select an option

  • Save jimmy947788/f47c766e9f83bf9b1dc9f08417f81aa5 to your computer and use it in GitHub Desktop.

Select an option

Save jimmy947788/f47c766e9f83bf9b1dc9f08417f81aa5 to your computer and use it in GitHub Desktop.
PineHook框架重打包注入Apk

[TOC]

PineHook框架重打包注入Apk

最后更新: 2025-01-04
测试版本: DBS App 25.12.3
状态: ✅ 重打包成功,应用可正常启动


概述

本文档详细说明如何使用 PineHook 框架重打包 Android APK,并注入自定义 Hook 代码(HookEntry.java)。

成功案例

  • DBS App 25.12.3: 重打包成功,应用可正常启动
  • ✅ Hook 成功安装并运行
  • ✅ 成功拦截多个安全检测点

关键要点

  1. DEX 64K 限制: 大型 APK 必须使用 smali_classesX 目录,不能使用主 smali/ 目录
  2. Hook 初始化: 在 Application.onCreate() 方法中注入 HookEntry.init() 调用
  3. 验证步骤: 重打包后必须验证 DEX 文件完整性

从AAR提取Pine库

1. 获取Pine AAR

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 ./

2. 解压AAR

unzip pine-core.aar -d pine_aar_content

3. 提取SO文件

# 复制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/

4. 将classes.jar转换为smali

- 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

5. 注入到目标APK

⚠️ 重要:DEX 64K 限制处理

对于大型 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

6.对齐签名

zipalign -p -f -v 4 target_patched_unsigned.apk aligned.apk

Smali注入

1.查看AndroidManifest.xml中的Application

aapt dump badging <你的APK文件路径.apk>
launchable-activity: name='com.igg.andr.Launcher' 

2.编写Hook代码

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);
        }

    }
}

⚠️ 重要:DEX 64K 限制处理

对于大型 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/

3.注入Hook

3.1 查找 Application 类

# 方法 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 Application

DBS App 示例:

  • Application 类: com.dbs.sg.iwealth.IWApplication
  • 文件位置: decompiled/smali_classes2/com/dbs/sg/iwealth/IWApplication.smali

3.2 注入 HookEntry.init() 调用

在 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

关键注意事项:

  1. .locals 数量: 必须增加 1(因为使用了新的寄存器 v4

    • 如果原来是 .locals 4,改为 .locals 5
    • 如果原来是 .locals 5,改为 .locals 6
  2. 寄存器选择: 使用未使用的寄存器(通常是 v4 或更高)

    • 检查方法中已使用的寄存器(v0, v1, v2, v3 等)
    • 选择第一个未使用的寄存器
  3. 注入位置: 必须在 onCreate() 方法的最开始(在 .locals 之后,任何其他代码之前)

3.3 验证注入

# 检查注入是否成功
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

4.编译Apk

4.1 重新打包

# 进入反编译目录
cd decompiled

# 重新打包
apktool b . -o ../base-repacked-unsigned.apk

# 检查输出
ls -lh ../base-repacked-unsigned.apk

预期输出:

  • APK 文件大小应该接近原始 APK(通常略大 1-5MB)
  • 不应该有错误信息(警告可以忽略)

4.2 对齐 APK

zipalign -p -f -v 4 \
    base-repacked-unsigned.apk \
    base-repacked-aligned.apk

验证对齐:

zipalign -c -v 4 base-repacked-aligned.apk
# 应该显示: Verification successful

4.3 签名 APK

方法 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

⚠️ 重要:验证 DEX 文件

重打包后,务必验证 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 目录)

常见错误处理:

  1. Unsigned short value out of range: 65XXX

    • 原因: DEX 64K 限制(方法引用索引超过 65,536)
    • 解决: 使用方法 B(将代码放到新的 smali_classesX 目录)
    • 验证: 检查 classes.dex 文件头是否正常
  2. 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,说明文件损坏
  3. ClassLoaderContext classpath size mismatch

    • 原因: DEX 文件数量不匹配(通常是重复的 smali_classes 目录)
    • 解决: 检查并清理重复文件
      # 查找重复的 HookEntry
      find decompiled -name "HookEntry.smali" -type f
      # 只保留最新的,删除旧的
  4. VerifyError: Verifier rejected class

    • 原因: Smali 代码验证失败,通常是寄存器使用错误
    • 常见错误:
      • .locals 数量不足(注入代码需要额外寄存器)
      • 使用了已占用的寄存器
    • 解决:
      • 检查 .locals 数量是否正确(原值 +1)
      • 确保使用的寄存器(如 v4)未被占用
      • 验证注入代码的语法正确性
  5. ClassNotFoundException: Didn't find class "com.hook.HookEntry"

    • 原因: HookEntry.smali 未正确复制到 APK
    • 解决:
      • 检查 smali_classesX/com/hook/HookEntry.smali 是否存在
      • 确保所有内部类(HookEntry$*.smali)都已复制
      • 验证文件权限和路径正确
  6. Hook installed successfully 但应用仍显示安全警告

    • 原因: Hook 安装成功,但可能有其他检测方法未覆盖
    • 排查:
      • 检查日志中是否有其他检测调用
      • 确认所有检测点都已 Hook
      • 检查是否有 native 层检测

PineHook+ 重打包成功報告

日期: 2025-12-17
App 版本: 25.12.3
解決方案: 將注入代碼移動到新的 smali_classes 目錄


執行摘要

重打包成功! 通過將 PineHook+ 類文件移動到 smali_classes19 而不是主 smali/ 目錄,成功避免了 DEX 64K 限制問題。


問題與解決方案

原始問題

  • 錯誤: Unsigned short value out of range: 65707
  • 原因: classes.dex 已有 65,510 個方法(99.96% 使用率),注入 PineHook+ 後超過 65,536 限制

解決方案

根據 apktool 錯誤解決方案

Unsigned short value out of range: 65536
Simply move your codes to smali_classes(X) or create new one.

實施步驟:

  1. 創建新的 smali_classes19 目錄
  2. 將 PineHook+ 類文件從 smali/ 移動到 smali_classes19/
  3. 重新打包成功

執行步驟

步驟 1-4: 準備工作(已完成)

✅ 反編譯 APK
✅ 提取 PineHook+ 類文件
✅ 注入類文件到 smali/
✅ 複製 Hook 文件夾和 Native 庫
✅ 修改 AndroidManifest.xml
✅ 配置 config.json

步驟 5: 移動類文件到新的 DEX(關鍵步驟)

cd apks/com.dbs.sg.dbsmbanking-25.12.3/decompiled

# 創建新的 smali_classes19
mkdir -p smali_classes19

# 移動 PineHook+ 類文件
mv smali/top smali_classes19/
mv smali/com/pinehook smali_classes19/com/
mv smali/dev/rikka smali_classes19/dev/ 2>/dev/null || true

步驟 6: 重新打包

apktool b . -o ../base-repacked-unsigned-v2.apk -f

結果: ✅ 成功!

I: Smaling smali_classes19 folder into classes19.dex...
I: Building apk file...
I: Built apk into: ../base-repacked-unsigned-v2.apk

步驟 7: 對齊 APK

zipalign -v 4 base-repacked-unsigned-v2.apk base-repacked-aligned-v2.apk

輸出文件

  • 未簽名 APK: base-repacked-unsigned-v2.apk
  • 已對齊 APK: base-repacked-aligned-v2.apk
  • 下一步: 簽名 APK

關鍵發現

DEX 文件結構

  • 原始: 18 個 DEX 文件(classes.dex 到 classes18.dex)
  • 重打包後: 19 個 DEX 文件(新增 classes19.dex 包含 PineHook+ 代碼)

為什麼這樣可以解決問題

  1. 主 DEX 不超過限制: classes.dex 保持 65,510 個方法,未超過 65,536
  2. 新 DEX 存放注入代碼: PineHook+ 的類文件在 classes19.dex
  3. Multidex 支持: Android 支持多個 DEX 文件,可以正常運行

驗證清單

  • 反編譯成功
  • PineHook+ 類文件提取成功
  • 類文件移動到 smali_classes19 成功
  • Hook 文件夾和 Native 庫已複製
  • AndroidManifest.xml 已修改(添加 Loader provider)
  • config.json 已配置
  • 重新打包成功
  • APK 對齊成功
  • APK 簽名(待完成)
  • 安裝測試(待完成)

下一步

1. 簽名 APK

cd /home/jimmy9478/Projects/reverse-dbs/apks/com.dbs.sg.dbsmbanking-25.12.3

apksigner sign --ks <keystore> \
    --ks-key-alias <alias> \
    base-repacked-aligned-v2.apk \
    -out base-repacked-signed-v2.apk

2. 安裝測試

adb install -r base-repacked-signed-v2.apk

3. 驗證 Hook 是否生效

# 查看 logcat
adb logcat | grep -i "pinehook\|loader"

# 檢查應用是否正常運行
adb shell am start -n com.dbs.sg.dbsmbanking/com.dbs.sg.digibank.ui.demo.SplashActivity

相關文檔


參考資料


最後更新: 2025-12-17
狀態: ✅ 重打包成功

@jimmy947788

Copy link
Copy Markdown
Author

first

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment