33.安卓逆向2-frida hook技术-逆向分析加密方式(一)(api-sign java层)

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!

内容参考于:图灵Python学院

工具下载:

链接:https://pan.baidu.com/s/1bb8NhJc9eTuLzQr39lF55Q?pwd=zy89

提取码:zy89

复制这段内容后打开百度网盘手机App,操作更方便哦

上一个内容:32.安卓逆向2-frida hook技术-分析请求中加密参数技巧

上一个内容里找到了一个加密参数 api_sign,本次来找它的加密方式

首先使用jadx反编译apk文件,然后搜索 api_sign,如下图红框是比较符合的

双击上图红框进入代码位置

如下图红框,api_sign的值来自于str,str的值来自于b.b方法

首先使用frida进行hook第一个b.b方法,也就是hook下图红框,按着CTRL使用鼠标左键点击b就能进入b方法了

来到b方法,然后鼠标放到下图的b上使用鼠标左键单机一下,然后右击选择复制为frida片段

然后如下图红框,可以发现,b方法就是加密方法,这就可以证明通过使用jadx搜索到的代码没问题

它的入参很长

然后b方法的入参和抓到的包作对比,发现它的第一个参数是请求form(请求参数),第二个参数是null(空的),第三个参数是请求地址

然后加密方法找到了,然后接下来就进一步分析,b方法最终调用了a方法,然后按着ctrl键鼠标左键单击a

然后就进入了a方法

代码分析结果

java 复制代码
/**
 * 生成API签名的工具方法,处理签名生成过程中的各种异常情况
 * 
 * @param context 应用上下文,用于获取系统资源和服务
 * @param treeMap 包含请求参数的TreeMap,按key自然排序保证签名一致性
 * @param str 额外的签名密钥或盐值,增强安全性
 * @return 生成的API签名,或错误情况下的错误信息
 */
private static String a(Context context, TreeMap<String, String> treeMap, String str) {
    try {
        // 检查并初始化全局上下文(单例模式)
        if (VCSPCommonsConfig.getContext() == null) {
            VCSPCommonsConfig.setContext(context);
        }
        
        // 调用安全服务生成API签名
        String apiSign = VCSPSecurityBasicService.apiSign(context, treeMap, str);
        
        // 处理签名为空的异常情况
        if (TextUtils.isEmpty(apiSign)) {
            // 获取设备/用户标识信息
            String c2 = c(context);
            // 返回包含错误信息的字符串,格式:"p: [标识], vcsp return empty sign :[签名值]"
            return "p: " + c2 + ", vcsp return empty sign :" + apiSign;
        }
        
        // 正常情况返回生成的签名 
        return apiSign;
        
    // 捕获并处理常规异常
    } catch (Exception e2) {
        // 打印异常堆栈信息用于调试
        e2.printStackTrace();
        // 获取设备/用户标识信息
        String c3 = c(context);
        // 返回包含异常信息的字符串,格式:"p: [标识], Exception:[异常信息]"
        return "p: " + c3 + ", Exception:" + e2.getMessage();
        
    // 捕获并处理严重错误(如虚拟机错误、内存溢出等)
    } catch (Throwable th) {
        // 打印错误堆栈信息
        th.printStackTrace();
        // 获取设备/用户标识信息
        String c4 = c(context);
        // 返回包含错误信息的字符串,格式:"p: [标识], Throwable:[错误信息]"
        return "p: " + c4 + ", Throwable:" + th.getMessage();
    }
}

然后进一步分析,然后就进入 apiSign 方法,如下图它又调用了 getMapParamsSign 方法,然后就需要进入getMapParamsSign方法

getMapParamsSign 方法

getMapParamsSign 方法分析

java 复制代码
/**
 * 生成请求参数的签名,支持基于用户令牌的动态密钥
 * 
 * 此方法会检查请求参数中是否包含用户令牌(USER_TOKEN),
 * 如果存在且有效,则使用该令牌对应的密钥进行签名,
 * 否则使用默认密钥或传入的静态密钥。
 * 
 * 签名过程通过TreeMap保证参数按字典序排列,确保与服务端签名逻辑一致。
 * 
 * @param context 应用上下文,用于获取系统资源
 * @param treeMap 请求参数的键值对集合,必须使用TreeMap保证有序性
 * @param str 可选的静态签名密钥,为空时自动获取用户令牌对应的密钥
 * @param z 是否使用HMAC-SHA256算法(true)或MD5算法(false)
 * @return 生成的签名结果,参数集合为空时返回null
 */
public static String getMapParamsSign(Context context, TreeMap<String, String> treeMap, String str, boolean z) {
    // 初始化签名密钥
    String str2 = null;
    
    // 验证参数集合有效性
    if (treeMap != null) {
        // 标记是否存在有效的用户令牌
        boolean z2 = false;
        
        // 获取参数集合的键值对集合
        Set<Map.Entry<String, String>> entrySet = treeMap.entrySet();
        
        // 遍历参数集合查找用户令牌
        if (entrySet != null) {
            Iterator<Map.Entry<String, String>> it = entrySet.iterator();
            
            while (true) {
                // 检查迭代器状态
                if (it == null || !it.hasNext()) {
                    break;
                }
                
                // 获取当前参数项
                Map.Entry<String, String> next = it.next();
                
                // 验证参数项有效性并检查是否为用户令牌
                if (next != null && next.getKey() != null 
                    && ApiConfig.USER_TOKEN.equals(next.getKey()) 
                    && !TextUtils.isEmpty(next.getValue())) {
                    
                    // 标记找到有效用户令牌
                    z2 = true;
                    break;
                }
            }
        }
        
        // 如果存在有效用户令牌,则使用对应的密钥
        if (z2) {
            // 优先使用传入的密钥,为空则从配置获取令牌对应密钥
            if (TextUtils.isEmpty(str)) {
                str = VCSPCommonsConfig.getTokenSecret();
            }
            str2 = str;
        }
        
        // 调用底层签名算法生成签名
        // 参数:上下文、参数字典、签名密钥、算法类型
        return getSignHash(context, treeMap, str2, z);
    }
    
    // 参数集合为空时返回null
    return null;
}

然后最终调用了 getSignHash 方法进行加密,所以要进入 getSignHash 方法中分析,如下图它调用了gs方法,然后进入gs方法

gs方法

gs方法说明,它最终使用的反射

java 复制代码
/**
 * 生成请求参数的哈希签名(自定义API接口)
 * 
 * 该方法通过反射机制调用SDK内部的签名生成模块,
 * 支持根据传入参数选择不同的签名算法。
 * 
 * 调用流程:
 * 1. 校验输入参数有效性
 * 2. 通过反射获取并调用底层签名方法
 * 3. 捕获可能的异常并返回错误信息
 * 
 * @param context 应用上下文对象(Android API:android.content.Context)
 *                用于获取应用资源和环境信息,需确保非空
 * @param map 请求参数集合(Java API:java.util.Map)
 *            包含需要参与签名计算的所有参数键值对
 * @param str 签名密钥(自定义参数)
 *            用于生成哈希值的安全密钥,不能为空
 * @param z 算法选择标识(自定义参数)
 *            控制底层使用的签名算法类型(具体映射由SDK内部决定)
 * @return 签名结果字符串(格式由SDK内部定义)
 *         异常情况下返回以"error!"开头的错误信息
 */
public static String getSignHash(Context context, Map<String, String> map, String str, boolean z) {
    try {
        // 调用内部方法执行实际签名逻辑
        // getApplicationContext() - Android API方法,获取应用级上下文
        return gs(context.getApplicationContext(), map, str, z);
    } catch (Throwable th) {
        // 记录错误日志(自定义API:VCSPMyLog.error)
        // 用于内部错误追踪,需确保日志系统已正确初始化
        VCSPMyLog.error(clazz, th);
        return "error! params invalid";
    }
}

/**
 * 内部方法:通过反射调用SDK核心签名方法(私有API)
 * 
 * 该方法使用Java反射机制调用内部SDK类的签名方法,
 * 实现应用代码与底层签名库的解耦。
 * 
 * 技术实现细节:
 * 1. 使用双重检查锁定确保单例初始化
 * 2. 缓存Method对象提高反射调用性能
 * 3. 统一处理反射调用过程中的异常
 * 
 * @param context 应用上下文(Android API)
 * @param map 请求参数(Java API)
 * @param str 签名密钥
 * @param z 算法类型标识
 * @return 签名字符串,异常时返回错误信息
 */
private static String gs(Context context, Map<String, String> map, String str, boolean z) {
    try {
        // 检查反射所需的类和实例是否已初始化(自定义逻辑)
        if (clazz == null || object == null) {
            // 同步块保证单例初始化的线程安全(Java并发API:synchronized)
            synchronized (lock) {
                if (clazz == null || object == null) {
                    // 初始化反射所需的类和实例(自定义API)
                    initInstance();
                }
            }
        }
        
        // 缓存Method对象避免重复获取(Java反射API优化)
        if (gsMethod == null) {
            // 获取目标方法引用(Java反射API:Class.getMethod)
            gsMethod = clazz.getMethod("gs", 
                    Context.class,      // Android API类型参数
                    Map.class,          // Java API类型参数
                    String.class, 
                    Boolean.TYPE);
        }
        
        // 反射调用实际的签名方法(Java反射API:Method.invoke)
        return (String) gsMethod.invoke(object, 
                context,    // 应用上下文
                map,        // 请求参数Map
                str,        // 签名密钥
                Boolean.valueOf(z)); // 算法类型
    } 
    // 捕获反射调用过程中的受检异常(Java异常处理API)
    catch (Exception e) {
        // 打印异常堆栈(Java标准API)
        e.printStackTrace();
        return "Exception gs: " + e.getMessage();
    } 
    // 捕获严重错误(Java异常处理API)
    catch (Throwable th) {
        th.printStackTrace();
        return "Throwable gs: " + th.getMessage();
    }
}

然后就需要去看 initInstance 方法,看看它初始化的什么类型,如下图

initInstance 方法说明

java 复制代码
/**
 * 初始化反射所需的类和实例对象(私有API方法)
 * 
 * 该方法使用Java反射机制加载指定类并创建实例,
 * 用于后续通过反射调用其方法。
 * 
 * 技术实现:
 * 1. 使用全限定类名动态加载类(Class.forName)
 * 2. 通过无参构造函数创建实例(Class.newInstance)
 * 3. 采用延迟初始化策略,确保只初始化一次
 * 
 * 注意事项:
 * - 需确保"com.vip.vcsp.KeyInfo"类在运行时存在
 * - 该类必须提供公共无参构造函数
 * - 初始化失败会导致后续反射调用出错
 * 
 * @throws ClassNotFoundException 如果指定类不存在
 * @throws IllegalAccessException 如果无权限访问类或构造函数
 * @throws InstantiationException 如果类无法实例化(如抽象类)
 */
private static void initInstance() {
    // 检查是否已初始化,避免重复操作(延迟初始化)
    if (clazz == null || object == null) {
        try {
            // 动态加载指定类(Java反射API:Class.forName)
            // 需确保类路径正确且类在运行时可见
            Class<?> cls = Class.forName("com.vip.xxx.KeyInfo");
            
            // 缓存Class对象供后续反射调用
            clazz = cls;
            
            // 创建类的实例(Java反射API:Class.newInstance)
            // 等价于调用无参构造函数new KeyInfo()
            object = cls.newInstance();
            
        } catch (ClassNotFoundException e) {
            // 处理类未找到异常(Java异常处理API)
            e.printStackTrace();
            // 可能原因:类路径错误、依赖库缺失
        } catch (InstantiationException e) {
            // 处理实例化异常(如类为抽象类、接口等)
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // 处理权限异常(如构造函数为私有)
            e.printStackTrace();
        } catch (Exception e) {
            // 处理其他异常
            e.printStackTrace();
        }
    }
}

然后如下图,使用全局搜索

然后双击上图中找到的keyInfo,然后进入到 KeyInfo 中,查看它的gs方法

gs方法说明

java 复制代码
/**
 * 生成请求参数的哈希签名(公共API方法)
 * 
 * 该方法提供双重调用机制,优先尝试直接调用gsNav方法,
 * 失败时会加载本地库后再次尝试,以支持不同环境下的签名生成。
 * 
 * 调用逻辑:
 * 1. 首次尝试调用gsNav方法
 * 2. 若失败(捕获Throwable),加载本地库
 * 3. 再次尝试调用gsNav方法
 * 
 * @param context 应用上下文(Android API:android.content.Context)
 * @param map 请求参数(Java API:java.util.Map)
 * @param str 签名密钥
 * @param z 算法类型标识
 * @return 签名字符串,异常时返回错误信息
 */
public static String gs(Context context, Map<String, String> map, String str, boolean z) {
    try {
        try {
            // 首次尝试调用签名方法(自定义API:gsNav)
            return gsNav(context, map, str, z);
        } catch (Throwable th) {
            // 捕获首次调用可能的异常,返回错误信息
            // 错误信息格式:"KI gs: [异常信息]"
            return "KI gs: " + th.getMessage();
        }
    } catch (Throwable unused) {
        // 加载本地库(Android NDK API:SoLoader.load)
        // 用于加载C/C++编译的本地动态库
        SoLoader.load(context, LibName);
        
        // 再次尝试调用签名方法
        return gsNav(context, map, str, z);
    }
}

然后gsNav是一个native方法,所以接下来需要去看so文件了

so文件名,如下图红框keyinfo

SO文件下一节看