app安卓逆向之smali代码log注入与原代码修改
1.背景分析
在安卓逆向的过程中常常会面临三个场景
-
想要了解某个方法是否有被调用
-
经过常规的代码分析后,想要知道App某些方法内部某些变量的值
-
App在Jave层的一些安全检测代码在我们的研究阶段需要屏蔽及修改
以上场景虽然使用Xposed、Frida、Java层代码动态调试效率会更高,但是目前市面上一些主流app存在对这些hook框架的检测,因此Smali代码注入与修改同样十分具有价值。
2.概述
本文将分为以下四个部分,完成某电商app的smali代码log注入与原代码修改
- Smali代码基础知识普及
- apktool的基本使用
- 构建个人Log类并且反编译Smali文件注入到某电商App中
- 重编译修改后的App项目,安装到模拟器中运行并使用注入的代码打印日志
相关软件下载
本教程中用到的所有软件都保存在该网盘中
https://pan.baidu.com/s/1XsMANMxzdxlrQAnEjIedxQ
密码:zzkk
3.开始
3.1 smali代码基础知识普及
首先,什么是smali代码?
Smali 实质上就是安卓的Dalvik虚拟机操作码, Java 字节码,一句 Java 代码会对应多句 Smali 代码
下面来看一个例子
Java源代码
package cn.com.zifeng.utils;
// 进行简单的加法运算
public class Calculate {
public static int add(int a,int b){
return a+b;
}
}
Smali代码解析
文件基本结构
.class
类名修饰符.super
父类的类名.source
源文件名.implements
实现的接口.annotation
注解.field
字段.method
方法
基本方法体
.method 描述符 方法名(参数类型)返回类型
方法代码...
.end method
寄存器(暂时存放数据的地方)
寄存器分为普通寄存器及参数寄存器,普通寄存器为v(0-n),参数寄存器p(0-N),在上面的例子中方法的一开始标明了*.locals 1*,此处用来声明普通寄存器的个数
ps:在非静态方法中,会默认申请一个参数寄存器p0,用来存储当前类的实例(this)
类型参照
Java | Smali |
---|---|
void | V |
boolean | Z (不同) |
byte | B |
short | S |
char | C |
int | I |
long | J (不同) |
float | F |
double | D |
例子:
I ==》 int
Ljava/lang/String ==》 String
[i ==> int[]
字段声明
声明语法:
.field 描述符 字段名:字段类型
例子:
Java:
public int number;
Smali:
.field public number:I;
方法调用
调用语法:
invoke-xxxxxx {参数列表}, 类名->方法名(参数类型)返回类型
指令类型:
指令名称 | 含义 |
---|---|
invoke-virtual | 调用虚方法 (涉及Java的多态,例如定义为: Object string = “123”; string.equals(“123”); 此处实际调用了String的equals方法,Smali中将会使用invoke-virtual调用) |
invoke-direct | 直接调用方法(直接调用无法被重写的方法,提高效率) |
invoke-static | 调用静态方法 |
invoke-super | 调用父类方法 |
invoke-interface | 调用接口方法 |
基础语法
语法 | 语义 |
---|---|
.field public isNull:z | 定义变量 |
.method | 定义方法 |
.end method | 方法结束 |
.param | 方法参数 |
.prologue | 方法开始 |
.line 12 | 此方法位于第12行 |
const/4 v0, 0x0 | 把0x0赋值给v0 |
return-void | 函数返回void |
new-instance | 创建实例 |
iput-object | 对象赋值 |
iget-object | 调用对象 |
常用跳转语法
语法 | 语义 |
---|---|
if-eq vA, vB, :cond_ | 如果vA等于vB则跳转到:cond_ |
if-ne vA, vB, :cond_ | 如果vA不等于vB则跳转到:cond_ |
if-lt vA, vB, :cond_ | 如果vA小于vB则跳转到:cond_ |
if-ge vA, vB, :cond_ | 如果vA大于等于vB则跳转到:cond_ |
if-gt vA, vB, :cond_ | 如果vA大于vB则跳转到:cond_ |
if-le vA, vB, :cond_ | 如果vA小于等于vB则跳转到:cond_ |
if-eqz vA, :cond_ | 如果vA等于0则跳转到:cond_ |
if-nez vA, :cond_ | 如果vA不等于0则跳转到:cond_ |
if-ltz vA, :cond_ | 如果vA小于0则跳转到:cond_ |
if-gez vA, :cond_ | 如果vA大于等于0则跳转到:cond_ |
if-gtz vA, :cond_ | 如果vA大于0则跳转到:cond_ |
if-lez vA, :cond_ | 如果vA小于等于0则跳转到:cond_ |
3.2 apktool的基本使用
介绍
apktool主要用于逆向apk文件。它可以将资源解码,并在修改后可以重新构建它们。它还可以执行一些自动化任务,例如构建apk。
主要功能
-
将资源解码成原来的形式(包括resources.arsc,class.dex等)
-
将解码的资源重新打包成apk/jar
-
组织和处理依赖于框架资源的APK
-
Smali调试
-
执行自动化任务
使用方法
这里不对apktool的全部功能做详细介绍,这里主要介绍在该教程中用到的功能,反编译/重编译
反编译
反编译前准备:
- 一个应用的apk文件
- apktool工具
具体步骤:
-
将该apk文件放置到apktool的相同目录下
-
运行cmd窗口切换至该目录
-
执行命令:
apktool -r d xxx.apk // 参数说明 -r表示不反编译资源文件,如果不加-r参数在重打包过程当中可能会出现资源找不到导致打包失败的问题 d表示反编译
-
得到apk同名文件夹
重编译
重编译前的准备:
- 安装jdk(重编译用到jdk中bin目录下的keytool以及jarsigner)
具体步骤:
-
运行cmd切换至apktool目录
-
重编译apk,执行命令
apktool b xxx xxx为上一步反编译生成的xxx.apk同名文件夹
-
执行完毕后在该同名文件夹下的dist目录会生成一个未签名的apk文件
-
cmd切换至该文件夹下的dist目录下
-
生成密钥文件,执行命令
E:\jdk1.8_271\bin\keytool.exe -genkeypair -alias app.keystore -keyalg RSA -validity 100 -keystore app.keystore // 参数说明 开头为keytool.exe 文件的全路径,该文件位于jdk中 -genkeypair 为生成密钥对 -alias 为处理条目别名 -keyalg 密钥算法名称 -validity 有效天数 -keystore 生成密钥库名称
-
输入相关信息(随便输入):
执行完毕后目录情况:
参数说明:
-
使用该keystore对apk进行签名,输入命令
E:\jdk1.8_271\bin\jarsigner.exe -verbose -keystore app.keystore -signedjar com.xunmeng.pinduoduo.apk com.xunmeng.pinduoduo.apk app.keystore // 参数说明 开头为jarsigner.exe文件的全路径,该文件位于jdk中 -verbose 签名时候输出详细信息 -keystore 指定密钥库 -signedjar 签名后生成的apk名称,同名即可
其他参数:
- 执行完毕后在同一目录下便生成了已签名的apk文件
3.3 构建个人Log类并且反编译成Smali文件注入到某电商App中
介绍
通过自定义log类注入到app的目录下,并且在app执行过程中完成调用,常用于输出app执行过程中寄存器的值或调用信息。
具体步骤
- 新建安卓工程,并且创建MyLog.java类,编写日志输出代码后,编译该工程生成apk文件,使用apktool反编译获取对应的MyLog.smali文件(当然可以直接使用网上现成的.smali文件,就不用自己创建安卓工程及编译了,此处会介绍如何自定义类完成功能定制)
- 目标app使用apktool反编译获得同名文件夹,该文件夹内部包含了其实现代码的smali文件夹
- 把第一步生成的MyLog.smali文件放置到目标app的smali文件夹中
- 修改MyLog.smali包名(因为注入到目标app后,其包名路径和原项目中有所不同)
- 在目标方法中调用MyLog完成日志输出
具体实现
-
这里首先给出一份MyLog.smali代码以及其对照的java代码,如果不想自定义可以直接把该MyLog.smali代码放置到目标app相应smali文件夹中
Java代码:
import android.util.Log; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.util.List; import java.util.Map; /** * @author: wzf * @date: 2021/03/017 17:11:14 * @version: 1.0.0 * @description: 该类用于注入smali,静态调试打印寄存器信息 */ public class MyLog { // 打印List类型 public static void logD(List list) { list.forEach(bean -> Log.d("crawler", "logList:" + toString(bean))); } // 打印Map类型 public static void logD(Map map) { map.entrySet().forEach(bean -> { Log.d("crawler", "logMap:" + toString(((Map.Entry) bean).getKey()) + "--->" + toString(((Map.Entry) bean).getValue())); }); } // 打印其他类型 public static void logD(Object object){ Log.d("crawler",object.getClass()+":"+toString(object)); } // 假设没有重写toStirng方法则通过该反射方法输出field public static String toString(Object obj) { try{ StringBuffer strBuf = new StringBuffer(); Class cla = obj.getClass(); /** * 对于基本数据类型和String直接返回 */ if (cla == Integer.class || cla == Short.class || cla == Byte.class || cla == Long.class || cla == Double.class || cla == Float.class || cla == Boolean.class || cla == String.class || cla == Character.class) { strBuf.append(obj); return strBuf.toString(); } /** * 对数组类型的处理 */ if (cla.isArray()) { strBuf.append("["); for (int i = 0; i < Array.getLength(obj); i++) { if (i > 0) strBuf.append(","); Object val = Array.get(obj, i); if (val != null && !val.equals("")) { strBuf.append(toString(val)); } } strBuf.append("]"); return strBuf.toString(); } //获取所有属性 Field[] fields = cla.getDeclaredFields(); //设置所有属性方法可访问 AccessibleObject.setAccessible(fields, true); strBuf.append("["); for (int i = 0; i < fields.length; i++) { Field fd = fields[i]; strBuf.append(fd.getName() + "="); try { if (!fd.getType().isPrimitive() && fd.getType() != String.class) { strBuf.append(toString(fd.get(obj))); } else { strBuf.append(fd.get(obj)); } } catch (Exception e) { e.printStackTrace(); } if (i != fields.length - 1) strBuf.append(","); } strBuf.append("]"); return strBuf.toString(); } catch (Exception e){ return "invoke错误,错误信息:"+e; } } }
Smali代码(有点长):
.class public LMyLog; .super Ljava/lang/Object; .source "MyLog.java" # direct methods .method public constructor <init>()V .locals 0 .line 16 invoke-direct {p0}, Ljava/lang/Object;-><init>()V return-void .end method .method static synthetic lambda$logD$0(Ljava/lang/Object;)V .locals 2 .param p0, "bean" # Ljava/lang/Object; .line 20 new-instance v0, Ljava/lang/StringBuilder; invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V const-string v1, "logList:" invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; invoke-static {p0}, LMyLog;->toString(Ljava/lang/Object;)Ljava/lang/String; move-result-object v1 invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; move-result-object v0 const-string v1, "crawler" invoke-static {v1, v0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I return-void .end method .method static synthetic lambda$logD$1(Ljava/lang/Object;)V .locals 2 .param p0, "bean" # Ljava/lang/Object; .line 26 new-instance v0, Ljava/lang/StringBuilder; invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V const-string v1, "logMap:" invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; move-object v1, p0 check-cast v1, Ljava/util/Map$Entry; invoke-interface {v1}, Ljava/util/Map$Entry;->getKey()Ljava/lang/Object; move-result-object v1 invoke-static {v1}, LMyLog;->toString(Ljava/lang/Object;)Ljava/lang/String; move-result-object v1 invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; const-string v1, "--->" invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; move-object v1, p0 check-cast v1, Ljava/util/Map$Entry; invoke-interface {v1}, Ljava/util/Map$Entry;->getValue()Ljava/lang/Object; move-result-object v1 invoke-static {v1}, LMyLog;->toString(Ljava/lang/Object;)Ljava/lang/String; move-result-object v1 invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; move-result-object v0 const-string v1, "crawler" invoke-static {v1, v0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I .line 27 return-void .end method .method public static logD(Ljava/lang/Object;)V .locals 2 .param p0, "object" # Ljava/lang/Object; .line 32 new-instance v0, Ljava/lang/StringBuilder; invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V invoke-virtual {p0}, Ljava/lang/Object;->getClass()Ljava/lang/Class; move-result-object v1 invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/Object;)Ljava/lang/StringBuilder; const-string v1, ":" invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; invoke-static {p0}, LMyLog;->toString(Ljava/lang/Object;)Ljava/lang/String; move-result-object v1 invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; move-result-object v0 const-string v1, "crawler" invoke-static {v1, v0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I .line 33 return-void .end method .method public static logD(Ljava/util/List;)V .locals 1 .param p0, "list" # Ljava/util/List; .line 20 sget-object v0, L-$$Lambda$MyLog$nM9EONxxK20HWpTl1L8s_b-BcWM;->INSTANCE:L-$$Lambda$MyLog$nM9EONxxK20HWpTl1L8s_b-BcWM; invoke-interface {p0, v0}, Ljava/util/List;->forEach(Ljava/util/function/Consumer;)V .line 21 return-void .end method .method public static logD(Ljava/util/Map;)V .locals 2 .param p0, "map" # Ljava/util/Map; .line 25 invoke-interface {p0}, Ljava/util/Map;->entrySet()Ljava/util/Set; move-result-object v0 sget-object v1, L-$$Lambda$MyLog$L3GR9fwRjR-BVLzaRcZHRwImXmo;->INSTANCE:L-$$Lambda$MyLog$L3GR9fwRjR-BVLzaRcZHRwImXmo; invoke-interface {v0, v1}, Ljava/util/Set;->forEach(Ljava/util/function/Consumer;)V .line 28 return-void .end method .method public static toString(Ljava/lang/Object;)Ljava/lang/String; .locals 10 .param p0, "obj" # Ljava/lang/Object; .line 38 new-instance v0, Ljava/lang/StringBuffer; invoke-direct {v0}, Ljava/lang/StringBuffer;-><init>()V .line 39 .local v0, "strBuf":Ljava/lang/StringBuffer; invoke-virtual {p0}, Ljava/lang/Object;->getClass()Ljava/lang/Class; move-result-object v1 .line 44 .local v1, "cla":Ljava/lang/Class; const-class v2, Ljava/lang/Integer; if-eq v1, v2, :cond_8 const-class v2, Ljava/lang/Short; if-eq v1, v2, :cond_8 const-class v2, Ljava/lang/Byte; if-eq v1, v2, :cond_8 const-class v2, Ljava/lang/Long; if-eq v1, v2, :cond_8 const-class v2, Ljava/lang/Double; if-eq v1, v2, :cond_8 const-class v2, Ljava/lang/Float; if-eq v1, v2, :cond_8 const-class v2, Ljava/lang/Boolean; if-eq v1, v2, :cond_8 const-class v2, Ljava/lang/String; if-eq v1, v2, :cond_8 const-class v2, Ljava/lang/Character; if-ne v1, v2, :cond_0 goto/16 :goto_4 .line 54 :cond_0 invoke-virtual {v1}, Ljava/lang/Class;->isArray()Z move-result v2 const-string v3, "," const-string v4, "]" const-string v5, "[" if-eqz v2, :cond_4 .line 55 invoke-virtual {v0, v5}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer; .line 56 const/4 v2, 0x0 .local v2, "i":I :goto_0 invoke-static {p0}, Ljava/lang/reflect/Array;->getLength(Ljava/lang/Object;)I move-result v5 if-ge v2, v5, :cond_3 .line 57 if-lez v2, :cond_1 invoke-virtual {v0, v3}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer; .line 58 :cond_1 invoke-static {p0, v2}, Ljava/lang/reflect/Array;->get(Ljava/lang/Object;I)Ljava/lang/Object; move-result-object v5 .line 60 .local v5, "val":Ljava/lang/Object; if-eqz v5, :cond_2 const-string v6, "" invoke-virtual {v5, v6}, Ljava/lang/Object;->equals(Ljava/lang/Object;)Z move-result v6 if-nez v6, :cond_2 .line 61 invoke-static {v5}, LMyLog;->toString(Ljava/lang/Object;)Ljava/lang/String; move-result-object v6 invoke-virtual {v0, v6}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer; .line 56 .end local v5 # "val":Ljava/lang/Object; :cond_2 add-int/lit8 v2, v2, 0x1 goto :goto_0 .line 64 .end local v2 # "i":I :cond_3 invoke-virtual {v0, v4}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer; .line 65 invoke-virtual {v0}, Ljava/lang/StringBuffer;->toString()Ljava/lang/String; move-result-object v2 return-object v2 .line 69 :cond_4 invoke-virtual {v1}, Ljava/lang/Class;->getDeclaredFields()[Ljava/lang/reflect/Field; move-result-object v2 .line 72 .local v2, "fields":[Ljava/lang/reflect/Field; const/4 v6, 0x1 invoke-static {v2, v6}, Ljava/lang/reflect/AccessibleObject;->setAccessible([Ljava/lang/reflect/AccessibleObject;Z)V .line 75 invoke-virtual {v0, v5}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer; .line 76 const/4 v5, 0x0 .local v5, "i":I :goto_1 array-length v7, v2 if-ge v5, v7, :cond_7 .line 77 aget-object v7, v2, v5 .line 78 .local v7, "fd":Ljava/lang/reflect/Field; new-instance v8, Ljava/lang/StringBuilder; invoke-direct {v8}, Ljava/lang/StringBuilder;-><init>()V invoke-virtual {v7}, Ljava/lang/reflect/Field;->getName()Ljava/lang/String; move-result-object v9 invoke-virtual {v8, v9}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; const-string v9, "=" invoke-virtual {v8, v9}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; invoke-virtual {v8}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; move-result-object v8 invoke-virtual {v0, v8}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer; .line 80 :try_start_0 invoke-virtual {v7}, Ljava/lang/reflect/Field;->getType()Ljava/lang/Class; move-result-object v8 invoke-virtual {v8}, Ljava/lang/Class;->isPrimitive()Z move-result v8 if-nez v8, :cond_5 invoke-virtual {v7}, Ljava/lang/reflect/Field;->getType()Ljava/lang/Class; move-result-object v8 const-class v9, Ljava/lang/String; if-eq v8, v9, :cond_5 .line 81 invoke-virtual {v7, p0}, Ljava/lang/reflect/Field;->get(Ljava/lang/Object;)Ljava/lang/Object; move-result-object v8 invoke-static {v8}, LMyLog;->toString(Ljava/lang/Object;)Ljava/lang/String; move-result-object v8 invoke-virtual {v0, v8}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer; goto :goto_2 .line 83 :cond_5 invoke-virtual {v7, p0}, Ljava/lang/reflect/Field;->get(Ljava/lang/Object;)Ljava/lang/Object; move-result-object v8 invoke-virtual {v0, v8}, Ljava/lang/StringBuffer;->append(Ljava/lang/Object;)Ljava/lang/StringBuffer; :try_end_0 .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0 .line 87 :goto_2 goto :goto_3 .line 85 :catch_0 move-exception v8 .line 86 .local v8, "e":Ljava/lang/Exception; invoke-virtual {v8}, Ljava/lang/Exception;->printStackTrace()V .line 88 .end local v8 # "e":Ljava/lang/Exception; :goto_3 array-length v8, v2 sub-int/2addr v8, v6 if-eq v5, v8, :cond_6 .line 89 invoke-virtual {v0, v3}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer; .line 76 .end local v7 # "fd":Ljava/lang/reflect/Field; :cond_6 add-int/lit8 v5, v5, 0x1 goto :goto_1 .line 92 .end local v5 # "i":I :cond_7 invoke-virtual {v0, v4}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer; .line 93 invoke-virtual {v0}, Ljava/lang/StringBuffer;->toString()Ljava/lang/String; move-result-object v3 return-object v3 .line 47 .end local v2 # "fields":[Ljava/lang/reflect/Field; :cond_8 :goto_4 invoke-virtual {v0, p0}, Ljava/lang/StringBuffer;->append(Ljava/lang/Object;)Ljava/lang/StringBuffer; .line 48 invoke-virtual {v0}, Ljava/lang/StringBuffer;->toString()Ljava/lang/String; move-result-object v2 return-object v2 .end method
-
Android Studio中新建安卓项目,创建完成后在java目录下新建类MyLog完成自定义类编写(记得在java目录下编写,不然后续需要修改smali文件中的包名)
-
编写完毕后选择Build=>Build APk打包
-
进入build目录下找到刚才生成的apk
-
将该apk复制到apktool目录下执行反编译操作
-
进入生成的app-debug目录下拿到对应的MyLog.smali文件
-
把目标apk拖入到apktool目录中执行反编译命令获取同名源码文件夹
-
使用Android Studio打开该项目,并且进入到我们想要输出打印的类,此处我们想要静态分析的类名为a,在smali文件夹当中(此处会有多个smali文件夹,需要找到我们所在的类在哪个smali文件夹中)
-
在目标位置注入打印代码
3.4 重编译修改后的App项目,安装到模拟器中运行并使用注入的代码打印日志
介绍
上一步我们完成了MyLog类的注入以及在目标App中调用我们的自定义类,接下来对项目进行重编译及签名,并且运行在模拟器上查看日志打印。
具体步骤
- 使用apktool来对修改后的smali代码进行重编译及签名
- 模拟器运行重签名后的app并且打印日志
具体实现
-
上一步MyLog代码,注入完毕后重编译生成新的apk
Jadx前后代码对比:
-
把生成的apk安装到模拟器下并且运行
-
AS中打开Terminal窗口,并且输入以下指令(adb需要提前连接好模拟器,如果没连接好请使用adb connect指令来连接)
adb logcat
-
启动目标app
-
能够在Terminal界面中看到我们插入的日志输出
4. 总结
以上完成了在安卓逆向当中的smali代码注入与app的重编译及签名操作,主要介绍了静态分析的基本流程及常用工具。在实际逆向的过程当中我们能够通过修改app的代码来帮助我们屏蔽检测以及自定义代码的注入,有时候如果仅仅想查看方法的调用情况使用动态分析效率可能更高,但是静态分析对于逆向工作的开展以及逆向思维的形成有着重要的意义,此外他也是动态分析的基础。