Smali - Decompiling/[Modifying]/Compiling
Tip
学习并实践 AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
学习并实践 GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
学习并实践 Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
浏览用于评估路线的 完整 HackTricks Training 目录(ARTA/GRTA/AzRTA)以及 Linux Hacking Expert (LHE)。
支持 HackTricks
- 查看 订阅方案!
- 加入 💬 Discord 群组、telegram 群组,关注 X/Twitter 上的 @hacktricks_live,或查看 LinkedIn 页面 和 YouTube 频道。
- 通过向 HackTricks 和 HackTricks Cloud github 仓库提交 PR,分享 hacking 技巧。
有时修改应用程序代码以获取隐藏信息(例如高度混淆的密码或 flags)是有意义的。此时,反编译 apk、修改代码并重新编译可能会很有用。
Opcodes reference: http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html
快速方法
使用 Visual Studio Code 和 APKLab 扩展,你可以 automatically decompile、修改、recompile、签名并安装该应用,而无需执行任何命令。
另一个极大简化此任务的脚本是 https://github.com/ax/apk.sh
Split APKs / App Bundles
现代目标通常以 split APKs(base.apk + split_config.*.apk)的形式发布,而不是单个单体 APK。如果你只修改 base.apk,资源或本机库可能会不同步,导致安装失败。
Quick triage from a device:
adb shell pm path com.example.app
adb pull /data/app/.../base.apk
adb pull /data/app/.../split_config.arm64_v8a.apk
adb pull /data/app/.../split_config.en.apk
如果目标是拆分包,要么重建整个套件,要么使用可以 先合并 APKs 的工具。 apk.sh 在这里很有用,因为它可以将拆分的 APK 合并成单个可打补丁的 APK 并修复公共资源标识符。
对于面向 Frida/Objection 的重新打包工作流,也可查看 Android Anti-Instrumentation & SSL Pinning Bypass.
反编译 APK
使用 APKTool 你可以访问 smali code and resources:
apktool d APP.apk
如果 apktool 出现任何错误,尝试安装 最新版本
一些有趣且你应该查看的文件包括:
- res/values/strings.xml(以及 res/values/* 中的所有 xml)
- AndroidManifest.xml
- 任何扩展名为 .sqlite 或 .db 的文件
如果 apktool 在解码应用时有问题,请查看 https://ibotpeaches.github.io/Apktool/documentation/#framework-files 或尝试使用参数 -r(不要解码资源)。这样,如果问题出在资源而不是源代码,你就不会遇到该问题(不过资源也不会被反编译)。
Change smali code
你可以更改****指令、更改某些变量的值或添加新的指令。我使用 VS Code 修改 Smali 代码,安装 smalise extension 后编辑器会告诉你是否有任何指令不正确。
一些示例可以在这里找到:
或者你可以查看下面的一些 Smali 修改说明。
Recompile the APK
修改代码后你可以重新编译代码使用:
apktool b . #In the folder generated when you decompiled the application
它会在 dist 文件夹 内 编译 新的 APK。
如果 apktool 出现 错误,试试 installing the latest version
为新的 APK 签名
然后,你需要 生成一个密钥(系统会要求你输入一个密码和一些可以随机填写的信息):
keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias <your-alias>
最后,签名新的 APK:
jarsigner -keystore key.jks path/to/dist/* <your-alias>
jarsigner 仍然适用于一些快速测试,但对于现代 Android 构建,apksigner 更为推荐,因为它能够处理较新的 APK 签名方案。
优化新应用
zipalign 是一个归档对齐工具,为 Android 应用 (APK) 文件提供重要的优化。 More information here.
zipalign [-f] [-v] <alignment> infile.apk outfile.apk
zipalign -v 4 infile.apk
如果 APK 包含捆绑的本机库(lib/*.so),Android 现在建议使用 -P 16,以便 .so 文件对 16 KiB 和 4 KiB 页面大小设备都进行对齐:
zipalign -P 16 -f -v 4 infile.apk outfile.apk
为新的 APK 签名(又一次?)
如果你更喜欢使用apksigner而不是 jarsigner,你应该为 apk 签名,在应用zipaling 的优化之后。BUT NOTICE THAT YOU ONLY HAVE TO 只对应用签名一次 WITH jarsigner (before zipalign) OR WITH aspsigner (after zipaling).
apksigner sign --ks key.jks ./dist/mycompiled.apk
更实用的现代流程是:
apktool b . -o dist/app-unsigned.apk
zipalign -P 16 -f -v 4 dist/app-unsigned.apk dist/app-aligned.apk
apksigner sign --ks key.jks --out dist/app-signed.apk dist/app-aligned.apk
apksigner verify --verbose --print-certs dist/app-signed.apk
重要说明:
- 如果你在用
apksigner签名之后修改了 APK,签名会被作废,你必须重新签名。 apksigner verify --print-certs可用于确认重建后的 APK 是否可安装,并检查目标在运行时会暴露的证书。
修改 Smali
对于下面的 Hello World Java 代码:
public static void printHelloWorld() {
System.out.println("Hello World")
}
Smali 代码如下:
.method public static printHelloWorld()V
.registers 2
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v1, "Hello World"
invoke-virtual {v0,v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
return-void
.end method
Smali 指令集可在 here.
轻微更改
在函数内部修改变量的初始值
有些变量在函数开始处使用操作码 const 定义,你可以修改它们的值,或者定义新的变量:
#Number
const v9, 0xf4240
const/4 v8, 0x1
#Strings
const-string v5, "wins"
基本操作
#Math
add-int/lit8 v0, v2, 0x1 #v2 + 0x1 and save it in v0
mul-int v0,v2,0x2 #v2*0x2 and save in v0
#Move the value of one object into another
move v1,v2
#Condtions
if-ge #Greater or equals
if-le #Less or equals
if-eq #Equals
#Get/Save attributes of an object
iget v0, p0, Lcom/google/ctf/shallweplayagame/GameActivity;->o:I #Save this.o inside v0
iput v0, p0, Lcom/google/ctf/shallweplayagame/GameActivity;->o:I #Save v0 inside this.o
#goto
:goto_6 #Declare this where you want to start a loop
if-ne v0, v9, :goto_6 #If not equals, go to: :goto_6
goto :goto_6 #Always go to: :goto_6
更大变更
Smali 中通常会导致重建失败的陷阱
- 当你只需要在现有方法体内使用临时寄存器时,优先增加
.locals。参数寄存器 (p0,p1…) 映射到方法的 最高 寄存器,因此盲目改用.registers经常会破坏参数布局。 move-result,move-result-wide, 和move-result-object必须紧跟在 对应的invoke-*之后。在它们之间插入 logging 或任何其他 opcode 会使方法无效。long和double值是 wide 值,会占用一对寄存器。如果你在后面重用这些寄存器,记住v10也占用v11。- 如果你需要传递大量寄存器,或编号很高的寄存器,使用带
/range的变体,例如invoke-virtual/range。
Logging
#Log win: <number>
iget v5, p0, Lcom/google/ctf/shallweplayagame/GameActivity;->o:I #Get this.o inside v5
invoke-static {v5}, Ljava/lang/String;->valueOf(I)Ljava/lang/String; #Transform number to String
move-result-object v1 #Move to v1
const-string v5, "wins" #Save "win" inside v5
invoke-static {v5, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I #Logging "Wins: <num>"
建议:
- 如果你打算在函数内使用已声明的变量(declared v0,v1,v2…),请将这些行放在 .local
和变量声明 (const v0, 0x1) 之间 - 如果你想把日志代码插入函数代码的中间:
- 将已声明变量的数量增加 2:例如从 .locals 10 到 .locals 12
- 新变量应为已声明变量之后的编号(在此例中应为 v10 和 v11,记住编号从 v0 开始)。
- 修改日志函数的代码,使用 v10 和 v11 来替换 v5 和 v1。
Patching common anti-tamper checks
当应用被重新打包时,首先可能出问题的之一是应用内的 签名 / 安装来源 / 完整性 检查。 在 JADX 或 smali 树中好的搜索字符串包括:
GET_SIGNATURESGET_SIGNING_CERTIFICATESapkContentsSignersMessageDigestSHA-256Base64getInstallerPackageNamecom.android.vending
现代应用通常会调用 PackageManager.getPackageInfo(..., GET_SIGNING_CERTIFICATES),使用 MessageDigest 对签名字节进行哈希,并将结果与硬编码常量比较。 在实践中,通常更容易修补 final boolean / branch,而不是重写所有的签名处理代码。
Example patterns:
# Force a boolean result to "valid"
const/4 v0, 0x1
# Or invert the branch that sends execution to the tamper handler
if-eqz v0, :tamper_detected # original
if-nez v0, :tamper_detected # patched
如果验证代码很嘈杂,寻找在错误对话框 / finish() / System.exit() / telemetry 调用之前的最后一次比较,并在那里打补丁,而不是修改整个例程。
Toasting
记得在函数开头将 .locals 的数量增加 3。
这段代码准备插入函数的中间(必要时更改 变量的数量)。它会取得 this.o 的值,将其转换为 String,然后用该值显示一个toast。
const/4 v10, 0x1
const/4 v11, 0x1
const/4 v12, 0x1
iget v10, p0, Lcom/google/ctf/shallweplayagame/GameActivity;->o:I
invoke-static {v10}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
move-result-object v11
invoke-static {p0, v11, v12}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v12
invoke-virtual {v12}, Landroid/widget/Toast;->show()V
在启动时加载本地库 (System.loadLibrary)
有时你需要预加载本地库,以便在其他 JNI 库之前完成初始化(例如,为了启用进程本地的 telemetry/logging)。你可以在静态初始化器或在 Application.onCreate() 的早期注入对 System.loadLibrary() 的调用。下面是用于静态类初始化器 (
.class public Lcom/example/App;
.super Landroid/app/Application;
.method static constructor <clinit>()V
.registers 1
const-string v0, "sotap" # library name without lib...so prefix
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
return-void
.end method
或者,将相同的两条指令放在 Application.onCreate() 的开头,以确保库尽可能早地加载:
.method public onCreate()V
.locals 1
const-string v0, "sotap"
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
invoke-super {p0}, Landroid/app/Application;->onCreate()V
return-void
.end method
注意:
- 确保库的正确 ABI 变体存在于 lib/
/(例如 arm64-v8a/armeabi-v7a)以避免 UnsatisfiedLinkError。 - 尽早加载(class static initializer)可保证 native logger 能观察到随后的 JNI 活动。
Smali 静态分析 / 基于规则的检测
使用 apktool 反编译后,你可以逐行扫描 Smali,使用 regex 规则快速发现反分析逻辑(root/emulator checks)和可能的硬编码秘密。这是一种快速筛查技术:将命中视为线索,必须在周围的 Smali 或重建的 Java/Kotlin 中验证。
关键点:
- Library filtering:在常见第三方命名空间下抑制或标记发现,以便你专注于应用自有的代码路径。
- Context hints:要求可疑字符串出现在消费它们的 API 附近(在同一方法内或在 N 行之内)。
- Confidence:使用简单的级别(高/中)对线索排序并减少误报。
默认应抑制的示例库前缀:
Landroidx/
Lkotlin/
Lkotlinx/
Lcom/google/
Lcom/squareup/
Lokhttp3/
Lokio/
Lretrofit2/
示例检测规则 (regex + 上下文启发式):
{
"category": "root_check",
"regex_patterns": [
"(?i)invoke-static .*Runtime;->getRuntime\\(\\).*->exec\\(.*\\"(su|magisk|busybox)\\"",
"(?i)const-string [vp0-9, ]+\\"(/system/xbin/su|/system/bin/su|/sbin/su)\\""
],
"context_hint": "Only report when the same method also calls File;->exists/canExecute or Runtime;->exec."
}
在实践中效果良好的额外启发式方法:
- 根包/路径检查:要求在附近存在针对像
com.topjohnwu.magisk或/data/local/tmp这样的字符串的PackageManager;->getPackageInfo或File;->exists调用。 - 模拟器检查:将可疑字面量(例如
ro.kernel.qemu、generic、goldfish)与附近的Build.*getters 和字符串比较(->equals、->contains、->startsWith)配对。 - 硬编码的敏感信息:仅当附近的
.field或move-result标识符包含像password、token、api_key这样的关键字时,才标记const-string。明确忽略仅与 UI 相关的标记,如AutofillType、InputType、EditorInfo。
像 PulseAPK Core 这样的规则驱动扫描器实现了该模型,以便在 Smali 中快速发现反分析逻辑和潜在的敏感信息。
参考资料
- PulseAPK Core
- PulseAPK Smali Detection Rules
- SoTap:轻量级的应用内 JNI (.so) 行为记录器 – github.com/RezaArbabBot/SoTap
- Android Developers: apksigner and zipalign
- apk.sh: github.com/ax/apk.sh
Tip
学习并实践 AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
学习并实践 GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
学习并实践 Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
浏览用于评估路线的 完整 HackTricks Training 目录(ARTA/GRTA/AzRTA)以及 Linux Hacking Expert (LHE)。
支持 HackTricks
- 查看 订阅方案!
- 加入 💬 Discord 群组、telegram 群组,关注 X/Twitter 上的 @hacktricks_live,或查看 LinkedIn 页面 和 YouTube 频道。
- 通过向 HackTricks 和 HackTricks Cloud github 仓库提交 PR,分享 hacking 技巧。


