• 反编译 AndroidKiller 逆向 字节码 实践案例 [MD]


    博文地址

    我的GitHub 我的博客 我的微信 我的邮箱
    baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

    目录

    反编译 AndroidKiller 逆向 实践案例 MD

    PS:以下所有内容,包括测试用来逆向的APP均已脱敏

    参考我的其他博客内容:

    AndroidKiller 简介

    Android Killer 是一款可以对APK进行反编译的工具,它能够对反编译后的Smali文件进行修改,并将修改后的文件进行打包。

    AndroidKiller的基本功能:

    • 可以修改清单文件,比如修改包名
    • 可以修改(替换)任意资源文件,比如 string、drawable、color、layout 等,以及 assets、raw 等
    • 可以对整个工程中的字符或文件进行搜索(支持匹配编码、匹配文件类型、匹配范围)
    • 可以查看对应的 smali 、class 源码,以及反编译的 java 源码
    • 可以对修改后的工程重新打包

    插件升级

    软件中的Apktool可能会因为版本太低导致 apk 的反编译失败,此时需要到 Apktool 官网去下载最新版本的Apktool。

    下载完成后找到解压好的 AndroidKiller 目录下的binapktoolapktool目录将下载的最新版的 apktool 复制进去。

    然后修改 AndroidKiller 根目录下的binapktool下的apktool.batapktool.ini文件,将里面对应的文件名改为你下载的最新的 apktool 文件名。

    基本使用

    使用 AndroidKiller 对 Apk 进行反编译只需要将 Apk 文件拖入软件即可。

    反编译后我们可以对资源文件等进行简单的修改,修改后后点击左上角菜单栏中的Android -- 编译即可重新编译成APK。

    另外我们也可以点击 smali 目录,查看反编译的 smali 源码,并可以在某一 smali 源码文件中右键 -- 查看 -- 查看源码通过 Java Decompiler 查看反编译的 java 源码。

    实践案例

    修改清单文件

    经常会修改包名、应用图标、应用名称等基本信息,只需在这里找到相应的res并修改即可。

    另外还可以查看其使用的Application,因为一般一些初始化操作、全局常量的设置等都是在Application里面。

    <manifest package="包名" >
    <application android:icon="图标" android:label="名称" android:name="使用的Application">
    

    打印 debug 级别的日志

    或者说是开启debug模式,一般有如下几种处理思路

    方式一:直接代理 Log 类

    按如下方式便可以 hook 住系统 Log 的方法调用,我们可以在 Log.d 等方法被调用 前/后 做自己的逻辑,比如对日志进行过滤、把日志保存到文件中。

    public class XposedZygoteInit implements IXposedHookZygoteInit {
        @Override
        public void initZygote(StartupParam startupParam) throws Throwable {
    
            //hook住某个方法。参数:类名,类加载器,方法名,参数列表,Hook成功后的回调
            XposedHelpers.findAndHookMethod("android.util.Log", StartupParam.class.getClassLoader(), "d", String.class, String.class, new XC_MethodHook() {
    
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    //Log.i("bqt", "【代理系统的Log类,可以在这里把日志保存到文件中】" + param.args[0] + " ," + param.args[1]);
                }
            });
        }
    }
    

    方式二:通过修改字段值修改判断条件

    比如,反编译一款 APP 后发现其打印日志的部分逻辑如下:

    public class LogTool{
      public static boolean a = ;
      public static String b = "通用的tag";
      
      public static void a(String paramString) {
        if (a) {
          Log.d(b, paramString);
        }
      }
    

    在 release 包中,这个值静态字段 a 肯定是 false,我们只需将其改为 true 即可打印 debug 下才会打印的日志。

    hook 方式如下:

    public class XposedInit implements IXposedHookLoadPackage {
    
        @Override
        public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) throws Exception {
            if (lpparam.packageName.equals(应用的包名,进入if代表此应用启动了,可以开始hook了)) {
                Field field = XposedHelpers.findClass("完整路径.LogTool", lpparam.classLoader).getField("a");
                field.setAccessible(true);
                field.setBoolean(null, true); //静态字段的 the object whose field should be modified is null
            }
        }
    }
    

    PS:hook 系统类不能通过 IXposedHookLoadPackage ,而需要通过 IXposedHookZygoteInit,因为此应用启动前,系统类已经被加载过了,已经错过了 hook 的时机了。

    方式三:通过修改方法返回值修改判断条件

    比如,反编译一款 APP 后发现其打印日志的部分逻辑如下:

    public class LogTool {
        public static void d(String paramString1, String paramString2) {
            if (isLogAble(LogLevel.DEBUG)) {
                //...
            }
        }
    
        private static boolean isLogAble(LogLevel paramLogLevel) {
            if (logcatLevel.value == LogLevel.OFF.value) {
                return false;
            }
            if (logcatLevel.value == LogLevel.ALL.value) {
                return true;
            }
            return logcatLevel.compare(paramLogLevel) >= 0;
        }
    }
    

    类似上述案例,我们只需hook住isLogAble方法,并且将返回值直接返回true即可跳过日志级别判断。

    hook 方式如下:

    XposedHelpers.findAndHookMethod("类的完整路径", lpparam.classLoader, "isLogAble", "参数的完整路径", new XC_MethodHook() {
        @Override
        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
            Log.i("bqt", "【" + Arrays.toString(param.args) + "】");
            param.setResult(true);
        }
    });
    

    方式四:修改 BuildConfig.DEBUG 中的 DEBUG 常量值

    这种方式和上面那种方式类似,不过适用场景更广泛。

    APP编译时会自动为每一个module生成一个 BuildConfig 类,其中包含一些经常使用到的与环境相关的常量,例如:

    public final class BuildConfig {
      public static final boolean DEBUG = Boolean.parseBoolean("true"); //是否是调试模式
      public static final String APPLICATION_ID = "com.bqt.test"; //包名
      public static final String BUILD_TYPE = "debug";
      public static final String FLAVOR = "";
      public static final int VERSION_CODE = 1;
      public static final String VERSION_NAME = "1.0";
    }
    

    很多时候,我们判断是否应打印日志就是根据 BuildConfig.DEBUG 来确定的,所以我们只需要修改这个值就可以了。

    修改这个值的方式肯定不是通过Xposed框架,因为这些常量都是 final 类型的,动态框架肯定是无法修改的,一种可行的方式:

    如何hook混淆后指定类中的指定方法

    首先需要明白,混淆后的类名、方法名、成员变量名等都是固定的,反编译后你看到的名字是什么,运行时它的名字就是什么,不会再变的

    然后你还需要知道,他的名字虽然是固定的,但是往往你没办法直接hook,因为很多混淆后的名字都是非法字符,你即没法输入(IDE不识别),也没法通过编译(编译器不识别),所以需要采用特殊的方式来hook。

    以下是一种可供参考的案例:

    //从指定类中混淆后的方法名中获取匹配的方法
    String methodName = "";
    Method[] methods = XposedHelpers.findClass("o.ayc", lpparam.classLoader).getDeclaredMethods();
    for (Method method : methods) {
        method.setAccessible(true);
        //匹配返回值,这里只是简单字符串匹配,更精确的可以通过类型匹配
        if (method.getReturnType().toString().contains("HttpURLConnection")) {
            methodName = method.getName();
            break;
        }
    }
    

    通过上述方法,便可跳过IDE和编译器的名称规范的检查,在运行时便可匹配混淆后的类或方法(运行时并不会进行规范性检查)。

    完整案例

    基本步骤

    1、安装 XposedInstaller.apk,安装后启动此应用,安装 framework ,重启手机

    2、AS工程中添加依赖

    compileOnly 'de.robv.android.xposed:api:82:sources'//xposed依赖,注意这个版本号和framework版本号并不是一致的
    implementation files('libs/javassist.jar')//非必须
    

    3、application下添加三个meta-data

    <meta-data
        android:name="xposedmodule"
        android:value="true"/>
    <meta-data
        android:name="xposeddescription"
        android:value="这是对你使用xposed能完成功能的简要描述"/>
    <meta-data
        android:name="xposedminversion"
        android:value="89"/>
    

    4、编写Hook逻辑

    5、配置完整的Hook类类名

    新建assets文件夹,文件夹下新建xposed_init文件(文件名固定),在文件中填写hook逻辑所在类的fully qualified class name:

    com.bqt.test.temp.XposedInit
    com.bqt.test.temp.XposedZygoteInit
    

    6、有任何代码变动,都需重装APP后重启手机才能生效

    XposedInit

    public class XposedInit implements IXposedHookLoadPackage {
    
        @Override
        public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) throws Exception {
            if (lpparam.packageName.equals("应用1的包名,进入if代表此应用启动了,可以开始hook了")) {
    
                //开启日志打印
                XposedHelpers.findAndHookMethod("类的完整路径", lpparam.classLoader, "isLogAble", "参数的完整路径", new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        Log.i("bqt", "日志级别【" + Arrays.toString(param.args) + "】");
                        param.setResult(true);
                    }
                });
    
                //从指定类中混淆后的方法名中获取匹配的方法
                String methodName = "";
                Method[] methods = XposedHelpers.findClass("o.ayc", lpparam.classLoader).getDeclaredMethods();
                for (Method method : methods) {
                    method.setAccessible(true);
                    //匹配返回值,这里只是简单字符串匹配,更精确的可以通过类型匹配
                    if (method.getReturnType().toString().contains("HttpURLConnection")) {
                        methodName = method.getName();
                        break;
                    }
                }
    
                //hook住某个方法。参数:类名,类加载器,方法名,参数列表,Hook成功后的回调
                XposedHelpers.findAndHookMethod("o.ayc", lpparam.classLoader, methodName, String.class, new XC_MethodHook() {
    
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        Log.i("bqt", "1-【" + param.method.getName() + "】" + param.args[0]);
                    }
                });
    
                //只能hook具体的方法,不能hook接口的方法或抽象的方法。比如只能hook住某个Runnable的实现类的run方法,而不能hook住所有Runnable的run方法
                XposedHelpers.findAndHookMethod("o.ayf", lpparam.classLoader, "run", new XC_MethodHook() {
    
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        Log.i("bqt", "2-【" + param.thisObject.getClass().toString() + "】");
                    }
                });
    
            } else if (lpparam.packageName.equals("应用2的包名")) {
                //开启日志打印
                Field field = XposedHelpers.findClass("完整路径.LogTool", lpparam.classLoader).getField("a");
                field.setAccessible(true);
                field.setBoolean(null, true); //静态字段的 the object whose field should be modified is null
    
                //注意,基本类型变量的class文件不能使用其相应包装类型来标识,例如 boolean.class 不能使用 Boolean.class 来代替
                XposedHelpers.findAndHookMethod("包名.a", lpparam.classLoader, "a", long.class, String.class, boolean.class, new XC_MethodHook() {
    
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        Log.i("bqt", "3-【" + Arrays.toString(param.args) + "】");
                    }
                });
            }
        }
    }
    

    XposedZygoteInit

    public class XposedZygoteInit implements IXposedHookZygoteInit {
        @Override
        public void initZygote(StartupParam startupParam) throws Throwable {
    
            XposedHelpers.findAndHookMethod("android.util.Log", StartupParam.class.getClassLoader(), "d", String.class, String.class, new XC_MethodHook() {
    
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    //Log.i("bqt", "【代理系统的Log类,可以在这里把日志保存到文件中】" + param.args[0] + " ," + param.args[1]);
                }
            });
        }
    }
    

    2019-09-30

  • 相关阅读:
    GPU CUDA之——深入理解threadIdx
    需求分析、业务逻辑与数据结构
    软件建模的本质
    浅谈软件需求建模
    软件建模即程序设计
    软件开发从0到1与软件建模
    数据模型所描述的内容包括三个部分:数据结构、数据操作、数据约束。
    观察力与信息搜集能力
    人类为什么写书
    鲁宾斯坦说:"思维是在概括中完成的。"
  • 原文地址:https://www.cnblogs.com/baiqiantao/p/11611811.html
Copyright © 2020-2023  润新知