安卓修改大师Smali插桩实战
—— Log.e打印各类变量值完整教程
一、Log插桩技术概述
Log插桩(Log Injection)是指在反编译APK后,在对应的Smali代码中添加日志输出指令,将程序中的关键变量值以Log日志的形式输出到logcat中,从而帮助逆向分析人员定位关键逻辑、追踪数据流、破解验证机制的技术手段。
在安卓逆向工程中,Log插桩是五种核心定位技巧之一:
- 信息反馈法:通过搜索资源ID字符串进行定位
- 特征函数法:搜索关键API函数调用
- 顺序查看法:分析程序执行流程
- 代码注入法(Log插桩):插入Log日志查看关键值
- 栈跟踪法:动态调试分析函数调用栈
1.1 Log插桩的核心原理
Log插桩的本质是向Smali代码中插入invoke-static指令,调用android.util.Log类的静态方法。Log.e是其中日志级别最高的方法,用于输出错误级别的日志信息,其方法签名为:
public static int e(String tag, String msg)
在Smali中调用Log.e的完整指令序列为:
const-string vX, "MyTag"
invoke-static {vX, vY}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
1.2 寄存器使用的核心规则
⚠️ 最重要的注意事项:不要使用被插桩方法中已有的寄存器!
Log插桩时需要额外添加寄存器,不要使用原方法中已经占用的默认寄存器,否则会覆盖原有数据导致程序崩溃或逻辑错误。
正确的做法是:
- 找到原方法中
.locals声明的寄存器数量N
- 将
.locals N修改为.locals N+1以上(根据需要使用额外寄存器数量)
- 使用原方法中未使用的最高编号寄存器(如vN、vN+1等)
💡 示例:如果原方法声明.locals 4(使用了v0-v3),则Log插桩时应使用v4作为新寄存器。将.locals 4改为.locals 5即可。
二、Log.e打印字符串类型变量
字符串类型是Log插桩中最简单、最直接的类型,因为Log.e的第二个参数本身就是String类型,无需进行类型转换。
2.1 基本字符串变量输出
假设在目标方法中寄存器v0存储了一个字符串变量,需要将其输出到日志:
const-string v3, "MyDebug"
invoke-static {v3, v0}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
2.2 字符串拼接后输出
如果需要在日志中添加描述性文字,可以使用String.concat方法拼接字符串:
const-string v3, "MyDebug"
const-string v4, "返回值 = "
invoke-static {v4, v0}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;
move-result-object v4
invoke-static {v3, v4}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
三、Log.e打印整数类型变量
整数类型(int)不能直接传给Log.e的String参数,需要先通过String.valueOf(int)或Integer.toString(int)转换为字符串。
3.1 使用String.valueOf转换
const-string v2, "MyDebug"
invoke-static {v0}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
move-result-object v3
invoke-static {v2, v3}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
3.2 使用Integer.toString转换
另一种同样有效的方式是使用Integer.toString(int)方法:
const-string v2, "MyDebug"
invoke-static {v0}, Ljava/lang/Integer;->toString(I)Ljava/lang/String;
move-result-object v3
invoke-static {v2, v3}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
3.3 带描述文字的整数输出
const-string v2, "MyDebug"
const-string v3, "用户金币数 = "
invoke-static {v0}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
move-result-object v4
invoke-static {v3, v4}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;
move-result-object v5
invoke-static {v2, v5}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
四、Log.e打印浮点数类型变量
浮点数类型(float)同样需要先转换为String才能传递给Log.e。转换方式与整数类似,但调用的是String.valueOf(float)或Float.toString(float)。
4.1 float变量输出
const-string v2, "MyDebug"
invoke-static {v0}, Ljava/lang/String;->valueOf(F)Ljava/lang/String;
move-result-object v3
invoke-static {v2, v3}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
4.2 使用Float.toString转换
const-string v2, "MyDebug"
invoke-static {v0}, Ljava/lang/Float;->toString(F)Ljava/lang/String;
move-result-object v3
invoke-static {v2, v3}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
五、Log.e打印双精度浮点数类型变量
双精度浮点数(double)在Smali中占用两个连续寄存器,这是与float、int类型最大的区别。在调用String.valueOf(double)时需要传入double值所在的两个寄存器。
5.1 double变量输出
const-string v3, "MyDebug"
invoke-static {v0, v1}, Ljava/lang/String;->valueOf(D)Ljava/lang/String;
move-result-object v4
invoke-static {v3, v4}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
5.2 使用Double.toString转换
const-string v3, "MyDebug"
invoke-static {v0, v1}, Ljava/lang/Double;->toString(D)Ljava/lang/String;
move-result-object v4
invoke-static {v3, v4}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
⚠️ double类型注意事项:
- double和long类型在Smali中都占用两个连续寄存器,如v0和v1
- 在调用
String.valueOf(D)或Double.toString(D)时,必须传入两个寄存器作为参数
- 传入的参数需要以
{v0, v1}的格式,而不是{v0}
六、Log.e打印对象类型变量
对象类型变量(如StringBuilder、JSONObject、ArrayList等)需要先调用toString()方法将其转换为字符串,再输出到日志。
6.1 对象类型变量输出
const-string v2, "MyDebug"
invoke-virtual {v0}, Ljava/lang/Object;->toString()Ljava/lang/String;
move-result-object v3
invoke-static {v2, v3}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
6.2 带描述文字的对象输出
const-string v2, "MyDebug"
const-string v3, "JSON数据 = "
invoke-virtual {v0}, Ljava/lang/Object;->toString()Ljava/lang/String;
move-result-object v4
invoke-static {v3, v4}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;
move-result-object v5
invoke-static {v2, v5}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
6.3 特殊场景:对象可能为null
如果对象可能为null,直接调用toString()会抛出空指针异常,导致程序崩溃。此时需要先进行null判断:
const-string v2, "MyDebug"
if-nez v0, :not_null
const-string v3, "对象为null"
invoke-static {v2, v3}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
goto :end_log
:not_null
invoke-virtual {v0}, Ljava/lang/Object;->toString()Ljava/lang/String;
move-result-object v3
invoke-static {v2, v3}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
:end_log
七、大文本量字符串的日志输出方案
当需要输出的字符串较大(如网络请求返回的JSON数据、长文本内容、HTML页面源码等)时,Logcat单条日志存在长度限制(约4KB)。超出部分会被截断,导致无法查看完整信息。以下是几种解决方案。
7.1 分段截断输出法
将大字符串按固定长度分段,逐段输出到日志。这是最常用的解决方案,无需额外工具类。
const/16 v3, 0x0
const/16 v4, 0x7D0
invoke-virtual {v0, v3, v4}, Ljava/lang/String;->substring(II)Ljava/lang/String;
move-result-object v5
const-string v6, "MyDebug_Part1"
invoke-static {v6, v5}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
const/16 v3, 0x7D0
const/16 v4, 0xFA0
invoke-virtual {v0, v3, v4}, Ljava/lang/String;->substring(II)Ljava/lang/String;
move-result-object v5
const-string v6, "MyDebug_Part2"
invoke-static {v6, v5}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
7.2 使用自定义日志辅助类
更好的方案是创建一个自定义的日志辅助类,专门用于处理大文本输出。在Java中编写好该类,然后通过安卓修改大师将生成的Smali文件复制到目标项目中。
步骤1:编写Java工具类
public class BigLogUtil {
private static final int MAX_LOG_LENGTH = 3000;
public static void logBigString(String tag, String content) {
if (content == null) {
Log.e(tag, "null");
return;
}
int len = content.length();
int start = 0;
int end = MAX_LOG_LENGTH;
int part = 0;
while (start < len) {
if (end > len) end = len;
Log.e(tag + "_part" + part, content.substring(start, end));
start = end;
end += MAX_LOG_LENGTH;
part++;
}
}
}
步骤2:在目标Smali中调用
const-string v2, "MyDebug"
invoke-static {v2, v0}, Lcom/example/utils/BigLogUtil;->logBigString(Ljava/lang/String;Ljava/lang/String;)V
7.3 写入文件方案
对于极大量文本(如完整的HTML页面、几十KB的JSON数据),日志输出效率较低,可以考虑将文本写入文件后通过ADB pull命令拉取到本地查看。
自定义工具类方法:
public static void writeToFile(String fileName, String content) {
try {
FileOutputStream fos = new FileOutputStream(
Environment.getExternalStorageDirectory() + "/" + fileName);
fos.write(content.getBytes());
fos.close();
Log.e("MyDebug", "文件已写入: " + fileName);
} catch (Exception e) {
Log.e("MyDebug", "写入文件失败: " + e.getMessage());
}
}
在Smali中调用后,使用ADB命令拉取文件:
adb pull /sdcard/output_data.txt ./
八、完整实战案例:综合多类型变量输出
8.1 案例场景
假设目标APK的某个方法中,需要同时输出一个int类型的用户ID、一个String类型的用户名、一个double类型的余额,以及一个对象类型的JSON数据。我们通过安卓修改大师进行插桩。
8.2 完整插桩代码
假设原始方法声明为.locals 3,寄存器分配为:v0=int用户ID,v1=String用户名,v2=double余额(占用v2,v3),v4=JSONObject对象。
由于原始方法已使用v0-v4,我们需将.locals 3改为.locals 9,使用v5-v8作为临时寄存器。
const-string v5, "MyDebug"
const-string v6, "用户ID = "
invoke-static {v0}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
move-result-object v7
invoke-static {v6, v7}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;
move-result-object v8
invoke-static {v5, v8}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
const-string v6, "用户名 = "
invoke-static {v6, v1}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;
move-result-object v8
invoke-static {v5, v8}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
const-string v6, "余额 = "
invoke-static {v2, v3}, Ljava/lang/String;->valueOf(D)Ljava/lang/String;
move-result-object v7
invoke-static {v6, v7}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;
move-result-object v8
invoke-static {v5, v8}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
const-string v6, "JSON数据 = "
invoke-virtual {v4}, Ljava/lang/Object;->toString()Ljava/lang/String;
move-result-object v7
invoke-static {v6, v7}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;
move-result-object v8
invoke-static {v5, v8}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
8.3 查看日志输出
插桩完成后,通过安卓修改大师重新打包签名,安装到设备上运行,在Android Studio的Logcat或命令行中使用以下命令查看日志:
adb logcat -s MyDebug
adb logcat MyDebug:D *:S
预期输出:
E/MyDebug: 用户ID = 10086
E/MyDebug: 用户名 = JohnDoe
E/MyDebug: 余额 = 9999.99
E/MyDebug: JSON数据 = {"status":0,"msg":"success","data":{...}}
⭐ @逆向新手小张 ★★★★★
“之前用命令行改Smali经常寄存器报错,分不清locals和registers。安卓修改大师可视化编辑,改完自动校验寄存器数量,跟着多类型变量案例一次跑通Log插桩,效率比命令行高很多。”
—— 来自官网用户评论
九、使用安卓修改大师进行Log插桩的优势
安卓修改大师作为专业的APK反编译与定制工具,在Log插桩方面提供了以下核心优势:
9.1 可视化编辑与自动校验
安卓修改大师将Smali代码编辑集成在图形化界面中,提供语法高亮、行号显示、查找替换等功能,大大降低了Smali的学习和操作门槛。在修改.locals寄存器数量时,软件会自动校验并提示,避免因寄存器数量不足导致的编译错误。
9.2 一键反编译与打包
软件支持一键反编译目标APK,自动生成完整的Smali代码树,无需手动配置JDK、apktool、ADB等环境。修改完成后,内置的打包签名模块可一键完成编译、对齐、签名流程,省去90%的手动配置步骤。
9.3 内置ADB调试模块
安卓修改大师内置了完整的ADB调试功能,包括一键安装/卸载APK、实时查看Logcat日志、管理设备文件等。插桩完成后,可以直接在软件中查看日志输出,无需额外打开命令行工具。
9.4 深度调试打包支持
对于复杂的调试需求,安卓修改大师支持生成调试安装包,自动携带JDWP调试标记和8700调试端口,可配合Android Studio和Smalidea插件进行Smali代码的动态调试。
⭐ @汉化博主阿泽 ★★★★★
“汉化海外游戏经常需要定位字符串来源,用安卓修改大师的Log插桩功能,快速输出变量值,比对不同条件下的结果,逆向效率翻倍。不用手动改AndroidManifest调试属性,节省大量试错时间。”
—— 来自官网用户评论
十、常见问题与解决方案
| 问题现象 | 可能原因 | 解决方案 |
| 程序崩溃闪退 | 寄存器数量未增加,导致覆盖了原方法使用的寄存器 | 检查.locals数值,确保增加了足够数量的寄存器 |
| 日志无输出 | 插桩代码被放置在了不可达路径 | 确认插桩位置在方法的可执行路径内,而非条件分支的死代码区域 |
| 输出为null | 变量值本身就是null,未做空值判断 | 增加if-nez/if-eqz判断,避免直接调用toString()或valueOf() |
| 日志被截断 | 字符串长度超过Logcat单条限制(约4KB) | 使用分段输出法或自定义日志工具类 |
| double类型输出异常 | 传入的寄存器数量不正确 | double类型需要传入两个连续寄存器,如{v0, v1} |
| 编译报错:Invalid register | 寄存器编号超过v15范围 | 使用invoke-static/range指令或拆分方法 |
十一、总结与实践建议
通过本文的系统讲解,你已经掌握了使用安卓修改大师在Smali代码中进行Log插桩的完整技术栈,包括:
- 字符串类型:直接传入Log.e,或拼接描述文字后输出
- 整数类型:通过
String.valueOf(int)或Integer.toString(int)转换后输出
- 浮点数类型:通过
String.valueOf(float)或Float.toString(float)转换后输出
- 双精度浮点数:注意占用两个连续寄存器,传入
{v0, v1}格式参数
- 对象类型:调用
toString()方法转换后输出,注意null判断
- 大文本字符串:分段截断、自定义日志工具类、写入文件三种方案
11.1 学习路径建议
- 从简单开始:先练习字符串类型的Log插桩,熟悉基本指令格式
- 逐步进阶:依次尝试int、float、double、对象类型的输出
- 实战应用:选择一款简单的APK,在关键方法中插入多种类型的日志
- 熟练掌握:将Log插桩与条件跳转修改、返回值篡改等技术结合使用
11.2 工具优势总结
安卓修改大师作为专业的APK修改工具,其Log插桩功能的核心优势在于:
- 无需配置环境:不用安装JDK、Android SDK、baksmali命令行
- 全图形化操作:拖拽APK即可一键反编译得到完整Smali源码
- 可视化代码编辑:语法高亮、行号显示、查找替换
- 自动校验寄存器:修改.locals后自动提示,避免编译错误
- 一站式流程:从反编译到打包签名,全部在同一软件内完成
⭐ @资深逆向工程师老周 ★★★★★
“对比过数十款PC端APK修改工具,安卓修改大师对Smali语法支持最完善,区分locals/registers提示清晰。多类型变量插桩的全套方案都有现成操作案例,官网持续更新适配新版Android系统dex格式,兼容性拉满。”
—— 来自官网用户评论
安卓修改大师
官方网站:www.apkeditor.cn
最新版本 v11.14.00.00 | 更新日期:2026-05-28 | 大小:12.45 MB
开发公司:上海空宇软件科技有限公司
*本文内容综合整理自安卓修改大师官网(www.apkeditor.cn)及相关技术文档,旨在帮助逆向爱好者掌握Smali Log插桩技术。所有操作请严格遵守相关法律法规,严禁将反编译后的代码用于商业用途。*