安卓接入QQ登录签名校验问题研究[基于8.4.10]

背景

一般我们安卓开发会有两套签名(debug和release),而调用QQ登录时,QQ会检测目标应用的签名信息,这样就会导致在调试时无法正常登录的问题

解决方案

通过Xposed注入QQ,在QQ检测签名信息时,强制返回一个release签名信息,以使在调试时仍能正常使用QQ登录功能

具体实现方式

参考bin大的https://github.com/L-JINBIN/ApkSignatureKiller,这是他在mt管理器里去除签名校验的代码,核心原理就是替换签名信息,不过他这个只能用于检测自身签名信息,而且还需要修改应用,英雌我们需要适配下,通过Xposed将该代码注入到QQ里,以下是我修改后的核心代码:


import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.text.TextUtils;

import com.mhook.dialog.tool.Debug;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

import de.robv.android.xposed.XC_MethodHook;
import louis.framework.util.ByteUtil;

/**
 * Created by ASUS on 2020/10/26.
 */

public class AppSignaturesHook extends XC_MethodHook implements InvocationHandler {
    
    
    public static final String TAG="AppSignaturesHook";
    private static final int GET_SIGNATURES = 0x00000040;
    private Object base;
    private Map<String,byte[][]> pkgSignMaps=new HashMap<>();
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        if ("getPackageInfo".equals(method.getName())) {
    
    
            String pkgName = (String) args[0];
            Integer flag = (Integer) args[1];
            if(pkgSignMaps.containsKey(pkgName)){
    
    
                byte[][] sign=pkgSignMaps.get(pkgName);
                if ((flag & GET_SIGNATURES) != 0&&sign!=null&&sign.length>0) {
    
    
                    PackageInfo info = (PackageInfo) method.invoke(base, args);
                    info.signatures = new Signature[sign.length];
                    for (int i = 0; i < info.signatures.length; i++) {
    
    
                        info.signatures[i] = new Signature(sign[i]);
                    }
                    Debug.LogI(TAG,"Replace "+pkgName+" sign success.");
                    return info;
                }
            }
        }
        return method.invoke(base, args);
    }
    private void hook(Context context) {
    
    
        try {
    
    
            // 获取全局的ActivityThread对象
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Method currentActivityThreadMethod =
                    activityThreadClass.getDeclaredMethod("currentActivityThread");
            Object currentActivityThread = currentActivityThreadMethod.invoke(null);

            // 获取ActivityThread里面原始的sPackageManager
            Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
            sPackageManagerField.setAccessible(true);
            Object sPackageManager = sPackageManagerField.get(currentActivityThread);

            // 准备好代理对象, 用来替换原始的对象
            Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");
            this.base = sPackageManager;

            Object proxy = Proxy.newProxyInstance(
                    iPackageManagerInterface.getClassLoader(),
                    new Class<?>[]{
    
    iPackageManagerInterface},
                    this);

            // 1. 替换掉ActivityThread里面的 sPackageManager 字段
            sPackageManagerField.set(currentActivityThread, proxy);

            // 2. 替换 ApplicationPackageManager里面的 mPM对象
            PackageManager pm = context.getPackageManager();
            Field mPmField = pm.getClass().getDeclaredField("mPM");
            mPmField.setAccessible(true);
            mPmField.set(pm, proxy);
           Debug.LogI(TAG,"PmsHook success.");
        } catch (Exception e) {
    
    
            Debug.LogE(TAG,"PmsHook failed.");
            e.printStackTrace();
        }
    }

    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
    
    
        super.beforeHookedMethod(param);
        switch (param.method.getName()){
    
    
            case "attach":
                attach(param);
                break;

        }
    }

    private static AppSignaturesHook instance;
    public static AppSignaturesHook getInstance(Map<String,String> hexSignMaps){
    
    
        if(instance==null){
    
    
            instance=new AppSignaturesHook(hexSignMaps);
        }
        return instance;
    }
    private AppSignaturesHook(Map<String,String> hexSignMaps) {
    
    
       //do init code
        pkgSignMaps.clear();
        for (String pkg: hexSignMaps.keySet()){
    
    
            if(TextUtils.isEmpty(pkg))continue;
            String hexSign=hexSignMaps.get(pkg);
            if(TextUtils.isEmpty(hexSign))continue;
            Debug.LogI(TAG,"Parse "+pkg+" hexSign:"+hexSign);
            try {
    
    
                pkgSignMaps.put(pkg,parseSign(hexSign));
            } catch (IOException e) {
    
    
                e.printStackTrace();
                Debug.LogI(TAG,"Parse "+pkg+" failed:"+e.getMessage());
            }
        }
    }

    private byte[][] parseSign(String data) throws IOException {
    
    
        DataInputStream is = new DataInputStream(new ByteArrayInputStream(ByteUtil.hexString2byte(data)));
        byte[][] sign = new byte[is.read() & 0xFF][];
        for (int i = 0; i < sign.length; i++) {
    
    
            sign[i] = new byte[is.readInt()];
            is.readFully(sign[i]);
        }
        return sign;
    }

    private void attach(MethodHookParam param) {
    
    
        Context context= (Context) param.args[0];
        hook(context);
    }
}

  • 调用
findAndHookMyMethod(Application.class, "attach", Context.class, AppSignaturesHook.getInstance(signMap));

成品

猜你喜欢

转载自blog.csdn.net/qq_26914291/article/details/109304871
今日推荐