app安卓逆向之smali代码log注入与原代码修改

app安卓逆向之smali代码log注入与原代码修改

    • 1.背景分析
    • 2.概述
    • 3.开始
      • 3.1 smali代码基础知识普及
      • 3.2 apktool的基本使用
      • 3.3 构建个人Log类并且反编译成Smali文件注入到某电商App中
      • 3.4 重编译修改后的App项目,安装到模拟器中运行并使用注入的代码打印日志
    • 4. 总结

1.背景分析

在安卓逆向的过程中常常会面临三个场景

  1. 想要了解某个方法是否有被调用

  2. 经过常规的代码分析后,想要知道App某些方法内部某些变量的值

  3. App在Jave层的一些安全检测代码在我们的研究阶段需要屏蔽及修改

以上场景虽然使用Xposed、Frida、Java层代码动态调试效率会更高,但是目前市面上一些主流app存在对这些hook框架的检测,因此Smali代码注入与修改同样十分具有价值。


2.概述

本文将分为以下四个部分,完成某电商app的smali代码log注入与原代码修改

  1. Smali代码基础知识普及
  2. apktool的基本使用
  3. 构建个人Log类并且反编译Smali文件注入到某电商App中
  4. 重编译修改后的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代码解析

app安卓逆向之smali代码log注入与原代码修改_第1张图片

文件基本结构

  • .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表示反编译
    

    app安卓逆向之smali代码log注入与原代码修改_第2张图片

  • 得到apk同名文件夹
    app安卓逆向之smali代码log注入与原代码修改_第3张图片

重编译

重编译前的准备:

  • 安装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 生成密钥库名称
    
  • 输入相关信息(随便输入):
    app安卓逆向之smali代码log注入与原代码修改_第4张图片
    执行完毕后目录情况:
    app安卓逆向之smali代码log注入与原代码修改_第5张图片
    参数说明:
    app安卓逆向之smali代码log注入与原代码修改_第6张图片

  • 使用该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名称,同名即可
    

    其他参数:

app安卓逆向之smali代码log注入与原代码修改_第7张图片

  • 执行完毕后在同一目录下便生成了已签名的apk文件

3.3 构建个人Log类并且反编译成Smali文件注入到某电商App中

介绍

通过自定义log类注入到app的目录下,并且在app执行过程中完成调用,常用于输出app执行过程中寄存器的值或调用信息。

具体步骤

  1. 新建安卓工程,并且创建MyLog.java类,编写日志输出代码后,编译该工程生成apk文件,使用apktool反编译获取对应的MyLog.smali文件(当然可以直接使用网上现成的.smali文件,就不用自己创建安卓工程及编译了,此处会介绍如何自定义类完成功能定制)
  2. 目标app使用apktool反编译获得同名文件夹,该文件夹内部包含了其实现代码的smali文件夹
  3. 把第一步生成的MyLog.smali文件放置到目标app的smali文件夹中
  4. 修改MyLog.smali包名(因为注入到目标app后,其包名路径和原项目中有所不同)
  5. 在目标方法中调用MyLog完成日志输出

具体实现

  1. 这里首先给出一份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
    
    
    
  2. Android Studio中新建安卓项目,创建完成后在java目录下新建类MyLog完成自定义类编写(记得在java目录下编写,不然后续需要修改smali文件中的包名)

    app安卓逆向之smali代码log注入与原代码修改_第8张图片
    app安卓逆向之smali代码log注入与原代码修改_第9张图片

  3. 编写完毕后选择Build=>Build APk打包

    app安卓逆向之smali代码log注入与原代码修改_第10张图片

  4. 进入build目录下找到刚才生成的apk

    app安卓逆向之smali代码log注入与原代码修改_第11张图片

  5. 将该apk复制到apktool目录下执行反编译操作

    app安卓逆向之smali代码log注入与原代码修改_第12张图片

  6. 进入生成的app-debug目录下拿到对应的MyLog.smali文件

    app安卓逆向之smali代码log注入与原代码修改_第13张图片

  7. 把目标apk拖入到apktool目录中执行反编译命令获取同名源码文件夹
    app安卓逆向之smali代码log注入与原代码修改_第14张图片

  8. 使用Android Studio打开该项目,并且进入到我们想要输出打印的类,此处我们想要静态分析的类名为a,在smali文件夹当中(此处会有多个smali文件夹,需要找到我们所在的类在哪个smali文件夹中)

    app安卓逆向之smali代码log注入与原代码修改_第15张图片

  9. 在目标位置注入打印代码

    app安卓逆向之smali代码log注入与原代码修改_第16张图片


3.4 重编译修改后的App项目,安装到模拟器中运行并使用注入的代码打印日志

介绍

上一步我们完成了MyLog类的注入以及在目标App中调用我们的自定义类,接下来对项目进行重编译及签名,并且运行在模拟器上查看日志打印。

具体步骤

  1. 使用apktool来对修改后的smali代码进行重编译及签名
  2. 模拟器运行重签名后的app并且打印日志

具体实现

  1. 上一步MyLog代码,注入完毕后重编译生成新的apk

    在这里插入图片描述

    Jadx前后代码对比:

app安卓逆向之smali代码log注入与原代码修改_第17张图片
app安卓逆向之smali代码log注入与原代码修改_第18张图片

  1. 把生成的apk安装到模拟器下并且运行

    app安卓逆向之smali代码log注入与原代码修改_第19张图片

  2. AS中打开Terminal窗口,并且输入以下指令(adb需要提前连接好模拟器,如果没连接好请使用adb connect指令来连接)

    adb logcat
    

    app安卓逆向之smali代码log注入与原代码修改_第20张图片

  3. 启动目标app

  4. 能够在Terminal界面中看到我们插入的日志输出

    在这里插入图片描述


4. 总结

以上完成了在安卓逆向当中的smali代码注入与app的重编译及签名操作,主要介绍了静态分析的基本流程及常用工具。在实际逆向的过程当中我们能够通过修改app的代码来帮助我们屏蔽检测以及自定义代码的注入,有时候如果仅仅想查看方法的调用情况使用动态分析效率可能更高,但是静态分析对于逆向工作的开展以及逆向思维的形成有着重要的意义,此外他也是动态分析的基础。

你可能感兴趣的:(数据采集,安卓逆向,smali,反编译,android,安卓,apk)