Android 应用逆向与 Hook 技术进阶实战

目录

Android 应用逆向与 Hook 技术进阶实战

一、课程目标

二、变量 Hook 详解

(一)静态变量与实例变量区分

(二)静态变量 Hook 步骤

(三)实例变量 Hook 步骤

三、构造函数 Hook 攻略

(一)构造函数识别

(二)Hook 构造函数方法

四、方法主动调用技巧

(一)静态方法与实例方法区分及调用原则

(二)静态方法调用示例

(三)实例方法调用示例

五、内部类 Hook 指南

(一)内部类定义与识别

(二)Hook 内部类方法步骤

六、反射机制深度剖析

(一)反射在 Android 应用中的关键地位

(二)反射基本操作步骤

七、便利工具与实用技巧

(一)遍历所有类及方法

(二)字符串赋值定位技巧

(三)点击事件监听方法

(四)布局改写实战

八、免 Root 注入实现(以 l patch 工具为例)

九、快速 Hook 工具(以 simple hook 为例)

十、案例实战:应用 Hook 技术破解关卡验证

(一)第三关破解

(二)第四关破解


在 Android 应用开发与安全研究领域,深入理解应用的内部机制以及掌握有效的调试和修改手段至关重要。本节课程将围绕 Android 应用的 Hook 技术展开,详细介绍变量 Hook、构造函数 Hook、方法主动调用、内部类处理、反射机制运用等核心内容,并通过实际案例演示如何利用这些技术解决实际问题,同时还将涉及免 Root 注入和快速 Hook 工具的使用。

一、课程目标

  • 全面掌握 export 采用的各类 API 及其应用场景。
  • 学会借助 list patch 工具实现无 Root 环境下的注入操作。
  • 熟练运用 import hook 技术,并掌握 import pk 等相关工具的使用。

二、变量 Hook 详解

(一)静态变量与实例变量区分

在 Android 应用的代码世界里,变量分为静态变量和实例变量。静态变量通常带有 “static” 关键字修饰,例如在代码中出现 “date” 这样的变量声明,即为静态变量。而像 “Adx” 这种没有 “static” 修饰的变量,则属于实例变量。

(二)静态变量 Hook 步骤

  1. 精准定位变量所在类的字节码。这需要仔细分析应用的代码结构,找到目标变量所属的类。例如,若变量位于 “vip 包下 finder class” 中,首先要复制该类的报名,假设类名为 “demo”,则需准确获取其完整路径信息。
  2. 依据变量类型进行针对性操作。若变量类型为常见的基本数据类型(如 int、char 等)或引用类型(如 String 等),需按照特定的 API 调用方式进行处理。以 String 类型变量为例,需传入类的字节码、变量名以及要修改的值。以下是示例代码:

// 假设已获取到类加载器 classLoader 和目标类的字节码 targetClass
// 变量名为 variableName,要修改的值为 newValue
Object value = // 根据具体情况获取或创建要设置的值对象,对于 String 类型可直接赋值
// 使用相应的 API 进行静态变量值的修改,此处以常见的修改静态变量值的方式为例,实际可能因使用的工具或框架有所不同
// 假设存在 setStaticValue 方法用于设置静态变量值
setStaticValue(targetClass, variableName, value); 

(三)实例变量 Hook 步骤

  1. 同样先获取变量所在类的字节码,这是后续操作的基础。
  2. 由于实例变量通常在构造函数中初始化,所以需要 Hook 所有相关的构造函数。在构造函数执行完毕且对象初始化完成后,获取当前输出的对象。
  3. 按照变量类型,利用合适的 API 进行值的修改。假设变量为 int 类型,示例代码如下:

// 假设已获取到类加载器 classLoader 和目标类的字节码 targetClass
// 变量名为 variableName,要修改的值为 newValue
Object instance = // 通过构造函数创建或获取目标类的实例对象,此处省略具体创建过程
// 使用相应的 API 进行实例变量值的修改,假设存在 setInstanceValue 方法用于设置实例变量值
setInstanceValue(instance, variableName, newValue); 

三、构造函数 Hook 攻略

(一)构造函数识别

在一个类中,构造函数的名称与类名相同。例如,对于名为 “demo” 的类,其构造函数可能有多种形式,如无参构造函数 “demo ()” 和带参构造函数 “demo (String param)” 等。

(二)Hook 构造函数方法

  1. 借助特定的 API(如 JADX 提供的相关方法)进行操作。首先传入类名及类的字节码,然后设置回调函数 “before” 和 “after”,这与普通方法的 Hook 过程在原理上相似,但使用的 API 有所不同。例如:

// 假设已获取到类加载器 classLoader 和目标类的字节码 targetClass
// 定义回调函数 before 和 after
BeforeCallback before = (params) -> {
    // 在构造函数执行前的操作,可修改传入参数等
    // 例如,若要修改传入的 String 参数为固定值 "modified"
    if (params[0] instanceof String) {
        params[0] = "modified";
    }
};
AfterCallback after = (result) -> {
    // 在构造函数执行后的操作,可对返回结果进行处理等
};
// 使用相应的 API 进行构造函数的 Hook,假设存在 hookConstructor 方法用于 Hook 构造函数
hookConstructor(targetClass, classLoader, before, after);

  1. 若要修改带参构造函数的传入参数,可在 “before” 回调函数中进行操作。通过获取参数类型并进行强制转换,将其修改为目标值。

四、方法主动调用技巧

(一)静态方法与实例方法区分及调用原则

静态方法带有 “static” 修饰符,调用时无需实例化对象,可直接通过类名进行调用。而实例方法则需要先创建类的实例对象,再通过该对象调用方法。

(二)静态方法调用示例

  1. 首先查找目标方法所在类的字节码,这需要对应用的代码结构有清晰的了解。
  2. 调用特定的 API(如 “costatic method”),传入字节码、方法名以及相应的参数(若有)。以下是示例代码框架:

// 假设已获取到类加载器 classLoader 和目标类的字节码 targetClass
// 方法名为 methodName,参数列表为 args(若无参数则为空数组)
// 使用相应的 API 进行静态方法的调用,假设存在 callStaticMethod 方法用于调用静态方法
Object result = callStaticMethod(targetClass, methodName, args);

(三)实例方法调用示例

  1. 先获取类的字节码,并实例化该类的对象。例如:

// 假设已获取到类加载器 classLoader 和目标类的字节码 targetClass
Object instance = // 通过合适的方式创建目标类的实例对象,如使用默认构造函数或带参构造函数

  1. 利用类似 “call mesa” 的 API,传入实例对象、方法名和参数(若有)进行调用。示例代码如下:

// 假设方法名为 methodName,参数列表为 args(若无参数则为空数组)
// 使用相应的 API 进行实例方法的调用,假设存在 callInstanceMethod 方法用于调用实例方法
Object result = callInstanceMethod(instance, methodName, args);

五、内部类 Hook 指南

(一)内部类定义与识别

内部类是定义在其他类内部的类,其标识符包含外部类名和内部类名,通过 “符号连接。例如,在类中定义了内部类,则其完整类名表示为B”。

(二)Hook 内部类方法步骤

  1. 找到内部类的字节码,这需要根据内部类的完整类名进行查找,确保准确无误。
  2. 使用特定的 API(如 “hook final in hook masa”)进行 Hook 操作,传入类加载器、内部类的完整类名、方法名及参数等信息。示例代码如下:

// 假设已获取到类加载器 classLoader
// 外部类名为 outerClassName,内部类名为 innerClassName,方法名为 methodName,参数列表为 args(若无参数则为空数组)
String innerClassNameWithDollar = outerClassName + "$" + innerClassName;
// 使用相应的 API 进行内部类方法的 Hook,假设存在 hookInnerClassMethod 方法用于 Hook 内部类方法
hookInnerClassMethod(classLoader, innerClassNameWithDollar, methodName, args);

六、反射机制深度剖析

(一)反射在 Android 应用中的关键地位

反射机制是 Android 开发中强大的工具之一,许多重要的 API 功能都是基于反射实现的。它允许在运行时动态地获取类的信息、调用方法和访问成员变量,为应用的灵活开发和调试提供了极大的便利。

(二)反射基本操作步骤

  1. 通过 “class.forName ()” 方法查找目标类,这是反射操作的起点。例如:

// 假设目标类名为 targetClassName
Class targetClass = Class.forName(targetClassName);

  1. 利用 “getDeclaredMethod ()” 方法获取目标方法,若方法为私有方法,则需将 “accessible” 属性设置为 true 以获取访问权限。示例代码如下:

// 假设要获取的方法名为 methodName,参数类型列表为 parameterTypes
Method method = targetClass.getDeclaredMethod(methodName, parameterTypes);
if (method.isAccessible() == false) {
    method.setAccessible(true);
}

  1. 可通过 “invoke ()” 方法实现主动调用,或者使用 “set ()” 方法修改变量的值。例如:

// 假设已获取到目标对象 instance(若为静态方法则可为 null)
// 调用方法并获取返回值,参数列表为 args(若无参数则为空数组)
Object result = method.invoke(instance, args);

// 假设要修改的变量名为 variableName,新值为 newValue
Field field = targetClass.getDeclaredField(variableName);
if (field.isAccessible() == false) {
    field.setAccessible(true);
}
field.set(instance, newValue);

七、便利工具与实用技巧

(一)遍历所有类及方法

  1. 利用 “class load” 的 “load class” 方法,并传入特定的字符串参数进行类的加载操作。
  2. 在获取加载结果后,通过 “get name” 方法获取类名,并进行筛选,排除安卓基础类及一些不需要关注的类。
  3. 对筛选后的类,获取其所有方法名,并通过循环遍历输出。同时,可根据实际需求排除抽象、native 和接口方法。以下是示例代码框架:

// 假设已获取到类加载器 classLoader
for (String className : getClassNamesToProcess(classLoader)) {
    Class clazz = classLoader.loadClass(className);
    Method[] methods = clazz.getDeclaredMethods();
    for (Method method : methods) {
        if (!isAbstractOrNativeOrInterfaceMethod(method)) {
            System.out.println(method.getName());
        }
    }
}

private static boolean isAbstractOrNativeOrInterfaceMethod(Method method) {
    return Modifier.isAbstract(method.getModifiers()) || Modifier.isNative(method.getModifiers()) || method.isBridge();
}

private static List getClassNamesToProcess(ClassLoader classLoader) {
    List classNames = new ArrayList<>();
    // 此处可根据实际情况添加获取类名的逻辑,例如从特定的目录或配置文件中读取类名
    return classNames;
}

(二)字符串赋值定位技巧

  1. Hook 安卓基础的 “set text” 方法,这是定位字符串赋值操作的关键。
  2. 在 Hook 成功后,通过打印相关对象和分析堆栈调用信息,定位到具体的字符串赋值代码位置。例如:

// 假设使用某种 Hook 框架进行 set text 方法的 Hook,以下是示例的 Hook 回调函数
HookCallback setTextHookCallback = (params) -> {
    Object textObject = params[0]; // 获取设置的文本对象
    if (textObject instanceof String) {
        String text = (String) textObject;
        System.out.println("Set text: " + text);
        // 可在此处进一步分析堆栈信息,获取调用 set text 方法的位置
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        for (StackTraceElement element : stackTrace) {
            System.out.println(element.getClassName() + "." + element.getMethodName() + "(" + element.getLineNumber() + ")");
        }
    }
};
// 假设存在 hookSetText 方法用于 Hook set text 方法
hookSetText(setTextHookCallback);

(三)点击事件监听方法

  1. 找到 “view” 类中的相关方法,这是捕获点击事件的核心。
  2. 在方法执行过程中,获取所有变量信息,并通过特定的 API 获取类名进行输出,从而实现点击事件的监听和定位。示例代码如下:

// 假设使用某种方式 Hook view 类的点击事件处理方法,以下是示例的 Hook 回调函数
HookCallback viewClickHookCallback = (params) -> {
    View view = (View) params[0];
    List variables = getVariablesInView(view); // 自定义方法获取 view 中的变量信息
    for (VariableInfo variable : variables) {
        System.out.println("Variable in view: " + variable.getName() + " = " + variable.getValue());
    }
    System.out.println("Clicked view class: " + view.getClass().getName());
};
// 假设存在 hookViewClick 方法用于 Hook view 类的点击事件处理方法
hookViewClick(viewClickHookCallback);

(四)布局改写实战

  1. 首先 Hook 包含布局绑定操作的方法,如 “on queen” 方法。这需要准确识别该方法在应用代码中的位置。
  2. 通过 “call method” 等 API 传入对象、方法名和控件 ID 等信息,实现对控件的操作,如隐藏布局或图片等。示例代码如下:

// 假设已获取到类加载器 classLoader 和目标类的字节码 targetClass
// 假设要隐藏的控件 ID 为 viewId,通过某种方式获取到该 ID
Object instance = // 获取目标类的实例对象
// 使用相应的 API 调用隐藏布局或图片的方法,假设存在 hideView 方法用于隐藏控件
hideView(instance, "hideLayoutMethod", viewId);

八、免 Root 注入实现(以 l patch 工具为例)

  1. 确保系统版本符合要求,l patch 工具最低支持安卓 9 及以上版本。在项目中进行如下操作:
  2. 点击 “build” 选项,选择 “app” 进行项目编译。若首次操作,需新建一个签名信息文件,选择合适路径并输入相关信息(如文件名、密码等),生成 “.jks” 文件。在后续操作中,选择 “release” 包进行构建。
  3. 构建完成后,在生成的文件夹中找到 “app” 文件,将其安装到模拟器或设备上。同时,安装 l patch 工具。
  4. 打开 l patch 工具,选择要处理的应用,选择 “便携模式”,该模式可将模块内置到原 app 中,实现无 Root 注入。在工具中勾选相关模块,并根据需求选择可调试、破解版本号、破解签名校验等选项,然后点击 “嵌入模块”。
  5. 手动安装生成的修复后 app,启动应用查看效果,如布局隐藏等功能是否生效。

九、快速 Hook 工具(以 simple hook 为例)

  1. 安装 simple hook 工具,可从指定位置获取安装包并进行安装。
  2. 在框架中找到 “hook” 选项,启动模块并勾选相关配置。打开应用后,若有未开启的配置项,可进行勾选以方便后续操作。
  3. 在底部添加配置时,参考 “key ham app” 上的详细编写规则。例如,要 Hook 一个普通方法,先在应用中找到该方法,复制其方法签名,在 simple hook 工具中进行粘贴,工具会自动填写相关信息。可记录方法的参数值和返回值,保存配置。若需要修改参数值或返回值,可在配置中进行相应修改后保存,并重启应用查看效果。

十、案例实战:应用 Hook 技术破解关卡验证

(一)第三关破解

  1. 分析关卡代码可知,该关主要通过检测点击次数来触发逻辑。当点击次数达到 999 次时,会显示特定信息并给出解密后的字符串。
  2. 利用 export 技术进行破解有两种方法:
    • Hook 点击次数检测方法:首先注释掉原代码中相关部分,修改报名以方便操作。然后通过前面所学的 Hook 方法,找到 “check” 方法并 Hook 其返回值,将其设置为 999,从而触发后续逻辑。示例代码如下:

// 假设已获取到类加载器 classLoader 和目标类的字节码 targetClass
// 使用相应的 API 进行 check 方法的 Hook,假设存在 hookCheckMethod 方法用于 Hook check 方法
hookCheckMethod(targetClass, classLoader, (params) -> {
    return 999;
});

  • 主动调用解密方法:先找到 “decree” 方法所在类,获取类字节码并实例化对象。根据方法参数要求,传入加密后的数据和偏移量(该关为凯撒加密),然后调用该方法获取解密结果,并通过吐司或日志输出。示例代码如下:

// 假设已获取到类加载器 classLoader 和目标类的字节码 targetClass
// 假设加密后的数据为 encryptedData,偏移量为 offset
Object instance = // 创建目标类的实例对象
// 使用相应的 API 调用 decree 方法,假设存在 callDecreeMethod 方法用于调用 decree 方法
Object result = callDecreeMethod(instance, "decree", encryptedData, offset);
// 输出解密结果,假设存在 toast 方法用于显示提示信息
toast("Decrypted result: " + result.toString());

(二)第四关破解

  1. 分析代码发现,该关根据设备 UID 和输入的 flag 进行验证,关键在于 “suffer” 方法的返回值判断。
  2. 通过 Hook “suffer” 方法的返回值,将其修改为正确的值,使验证通过。示例代码如下:

// 假设已获取到类加载器 classLoader 和目标类的字节码 targetClass
// 使用相应的 API 进行 suffer 方法的 Hook,假设存在 hookS

(二)第四关破解 分析代码发现,该关根据设备 UID 和输入的 flag 进行验证,关键在于 “suffer” 方法的返回值判断。 通过 Hook “suffer” 方法的返回值,将其修改为正确的值,使验证通过。示例代码如下: java // 假设已获取到类加载器 classLoader 和目标类的字节码 targetClass // 使用相应的 API 进行 suffer 方法的 Hook,假 从这里继续

// 假设已获取到类加载器 classLoader 和目标类的字节码 targetClass
// 使用相应的 API 进行 suffer 方法的 Hook,假设存在 hookSufferMethod 方法用于 Hook suffer 方法
hookSufferMethod(targetClass, classLoader, (params) -> {
    // 根据具体的验证逻辑,确定正确的返回值
    // 这里假设正确的返回值为 "success"
    return "success"; 
});

在上述代码中,首先需要确定 hookSufferMethod 这个 API 的具体实现。它应该能够接收目标类的字节码和类加载器,以及一个回调函数。在回调函数中,根据对原 suffer 方法的分析,修改其返回值为能使验证通过的值。例如,如果原方法在验证成功时返回 true,那么这里就返回 true;如果是根据特定的字符串比较结果返回不同的值,就需要返回符合成功验证条件的字符串,如上述示例中的 "success"。这样,在游戏运行时,当调用到 suffer 方法时,就会返回修改后的正确值,从而绕过验证,实现第四关的破解。

同时,在实际操作中,获取类加载器和目标类的字节码可能需要参考之前课程中介绍的方法,如通过分析应用的代码结构、查找相关类的定义位置等方式来确定。并且,要确保在合适的时机执行这段 Hook 代码,通常可以在应用启动的早期或者在相关功能被调用之前进行设置,以保证能够有效地修改 suffer 方法的返回值,成功破解第四关的验证机制。

你可能感兴趣的:(java,android)