美文网首页JS破解&&Android逆向
使用Xpatch破解App签名校验的两个方法

使用Xpatch破解App签名校验的两个方法

作者: Windy_816 | 来源:发表于2019-04-10 00:43 被阅读182次

问题来源

在使用Xpatch(https://github.com/WindySha/Xpatch
)对App进行二次打包时,经常会出现重打包后的App启动卡死或者无法获取到网络数据。这是因为这些App在启动时做了签名校验,检验App的签名是不是App自己原本的签名,不是,则阻止程序继续执行。

因此,破解App签名显得非常有必要。
本文主要就介绍使用Xpatch破解App签名校验的两种方法。

破解步骤

在破解某个App的签名之前,我们先需要获取这个App的原签名,然后Hook获取App签名的方法,使其返回的结果跟原App里返回结果一致,这样就能够破解App的签名校验。
因此,步骤可以总结为两步:

  1. 获取App原签名信息;
  2. 替换App获取签名方法的返回结果,使其跟原签名结果一致。

获取原签名

获取原签名其实非常简单,Android系统已经提供公开的API。

签名文件信息是在App安装的时候通过PMS解析出来,保存到应用的PackageInfo中,获取指定包名的PackageInfo信息,只需调用的PackageManagergetPackageInfo方法即可,代码如下:

public static String getPackageSignature(Context context, String packageName) {
        String sigature = null;
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
            if (info.signatures != null && info.signatures.length > 0) {
                sigature = info.signatures[0].toCharsString();
            }
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return sigature;
    }

获取指定一个包名的签名,我们只需写一个demo App,在demo的Application onCreate方法里(或者其他任意方法)调用上面方法即可,调用此方法时,context传demo的context,packageName传需要获取的App包名。返回结果后,将返回的字符串打印日志(或其他方式)记录下来,便于下面的Hook方法中使用。

使用Xposed Hook获取签名的方法

既然经过Xpatch重打包后的App可以加载任意Xposed插件(https://github.com/WindySha/Xpatch),那我们何不写一个Xposed插件Hook住上面获取App的签名的方法呢。

App获取签名的方法是PackageManagergetPackageInfo方法,而PackageManager是一个抽象类,其具体的实现类是android.app.ApplicationPackageManager.java。其实只用Hook这个类的getPackageInfo方法即可。
由于getPackageInfo方法返回的是一个packageInfo对象,签名字符只是这个对象一个成员变量,因此我们不需要完全替换此方法的返回结果,只需要替换返回结果里的签名字符信息即可。
故,选择在afterHookedMethod方法里执行替换签名字符的操作,具体实现代码如下:

 // App的原签名
    private final static String originalSignature =  "3082023b308201a4a00302010202044be8c388300d06092a86....";
    
    public static void hookSignature(ClassLoader classLoader, final String targetPackageName) {
       XposedHelpers.findAndHookMethod("android.app.ApplicationPackageManager", classLoader,
                "getPackageInfo", String.class, int.class,
                new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        try {
                            if (param.args[0] != null && param.args[0] instanceof String) {
                                    String packageName = (String) param.args[0];
                                    if (!packageName.equals(targetPackageName)) {
                                        return;
                                    }
                             }
                            int flag = (int) param.args[1];
                            PackageInfo packageInfo = (PackageInfo) param.getResult();

                            if (PackageManager.GET_SIGNATURES == flag) {
                                if (packageInfo.signatures != null && packageInfo.signatures.length > 0) {
                                    // 替换结果里的签名信息
                                    packageInfo.signatures[0] = new Signature(originalSignature);
                                }
                            } else if (Build.VERSION.SDK_INT >= 28 && PackageManager.GET_SIGNING_CERTIFICATES == flag) {
                                if (packageInfo.signingInfo != null) {
                                    Signature[] signaturesArray = packageInfo.signingInfo.getApkContentsSigners();
                                    if (signaturesArray != null && signaturesArray.length > 0) {
                                        signaturesArray[0] = new Signature(originalSignature);
                                    }
                                }
                            }
                            // 更改最终的返回结果
                            param.setResult(packageInfo);
                        } catch (Throwable e) {
                        }
                    }
                })
    }

将上面代码整理成一个Xposed插件,即可实现破解指定App的签名校验。

使用动态代理Hook获取签名的接口

其实使用Xposed Hook的方法已经非常简单了,使用动态代码的方案一般是在没有Xposed框架支持的情况下使用,其通用性更强一些,但实现起来也更加复杂。这里介绍动态代码的方法主要用于拓展思路。

Java的动态代理技术在这里就不详细介绍,不了解原理的可以查阅其他相关资料学习,这里主要介绍运用动态代码实现PMS在本地的代理方法的拦截,从而实现签名的破解。

因为ApplicationPackageManagergetPackageInfo的最终实现是IPackageManager接口通过Binder跨进程调用PMS实现的,其实现逻辑如下:

@Override
    public PackageInfo getPackageInfo(String packageName, int flags)
            throws NameNotFoundException {
        return getPackageInfoAsUser(packageName, flags, mContext.getUserId());
    }

private final IPackageManager mPM;
@Override
    public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
            throws NameNotFoundException {
        try {
            PackageInfo pi = mPM.getPackageInfo(packageName, flags, userId);
            if (pi != null) {
                return pi;
            }
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        throw new NameNotFoundException(packageName);
    }

在动态代理之前,除了需要知道是代理哪个接口,还需要知道实现这个接口的对象。
android.app.ActivityThread.java中可以找到其实现对象:

static volatile IPackageManager sPackageManager;

public static IPackageManager getPackageManager() {
        if (sPackageManager != null) {
            return sPackageManager;
        }
        IBinder b = ServiceManager.getService("package");
        sPackageManager = IPackageManager.Stub.asInterface(b);
        return sPackageManager;
    }

通过反射调用ActivityThreadgetPackageManager方法即可获取到接口的实例。
动态代理HookgetPackageInfo实现代码为:

public static void hookPMS(Context context) {
        try {
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
            // 获取全局的ActivityThread对象的实现
            Object activityThreadObj = currentActivityThreadMethod.invoke(null);

            // 获取ActivityThread里的getPackageManager方法获取原始的sPackageManager对象
            Method getPackageManagerMethod = activityThreadClass.getDeclaredMethod("getPackageManager");
            getPackageManagerMethod.setAccessible(true);
            Object packageManagerObj = getPackageManagerMethod.invoke(activityThreadObj);

            // 准备好代理对象, 用来替换原始的对象
            Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");
            Object proxy = Proxy.newProxyInstance(
                    iPackageManagerInterface.getClassLoader(),
                    new Class<?>[]{iPackageManagerInterface},
                    new MyInvocationHandler(packageManagerObj));
            // 1. 替换掉ActivityThread里面的 sPackageManager 字段
            Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
            sPackageManagerField.setAccessible(true);
            sPackageManagerField.set(activityThreadObj, proxy);
            // 2. 替换 ApplicationPackageManager里面的 mPM对象
            PackageManager pm = context.getPackageManager();
            Field mPmField = pm.getClass().getDeclaredField("mPM");
            mPmField.setAccessible(true);
            mPmField.set(pm, proxy);
        } catch (Exception e) {
        }
    }

    private final static String originalSignature =  "3082023b308201a4a00302010202044be8c388300d06092a86....";

    static class MyInvocationHandler implements InvocationHandler {

        private Object pmBase;

        public MyInvocationHandler(Object base) {
            pmBase = base;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if ("getPackageInfo".equals(method.getName())) {
                if (args[0] != null && args[0] instanceof String) {
                    String packageName = (String) args[0];
                    if (!packageName.equals(currentPackageName)) {
                        return method.invoke(pmBase, args);
                    }
                }

                Integer flag = (Integer) args[1];
                if (PackageManager.GET_SIGNATURES == flag) {
                    PackageInfo packageInfo = (PackageInfo) method.invoke(pmBase, args);
                    if (packageInfo.signatures != null && packageInfo.signatures.length > 0) {
                        // 替换结果里的签名信息
                        packageInfo.signatures[0] = new Signature(originalSignature);
                    }
                    return packageInfo;
                } else if (Build.VERSION.SDK_INT >= 28 && PackageManager.GET_SIGNING_CERTIFICATES == flag) {
                    PackageInfo packageInfo = (PackageInfo) method.invoke(pmBase, args);
                    if (packageInfo.signingInfo != null) {
                        Signature[] signaturesArray = packageInfo.signingInfo.getApkContentsSigners();
                        if (signaturesArray != null && signaturesArray.length > 0) {
                            signaturesArray[0] = new Signature(originalSignature);
                        }
                    }
                    return packageInfo;
                }
            }
            return method.invoke(pmBase, args);
        }
    }

通过动态代理不仅可以HookPackageManger的方法,也可以Hook住所有system_server进程的服务在本地的代理,比如AMS,PMS,IMMS等等。而且,这种技术在插件化开发中运用非常广泛。著名的双开框架VirtualApp就大量运用到动态代理Hook系统服务的技术。如果想对此深入了解,建议阅读此文:http://weishu.me/2016/03/07/understand-plugin-framework-ams-pms-hook/,这篇文档讲解十分详细。

验证效果

为了验证上面的方案可行,我们需要找一个做了签名校验的App进行验证。假如没有现成的App用来验证,也可以自己写一个Demo,并将Demo App签名,然后破解验证。不过,这样做还是比较繁琐。

庆幸的是,笔者找到了一个做了签名校验的App,趣头条App

通过Xpatch重打包后的趣头条App,安装后,新闻列表界面无法显示数据。猜测可能是做了签名校验导致的问题。因此,我们使用上面的方法一(Xposed Hook方法)来破解签名,最终结果令人惊喜,趣头条新闻列表数据成功刷新出来!!这样,既验证了我们破解签名成功,也说明趣头条确实是做了签名校验。

下面贴上趣头条的原Apk签名信息字符,感兴趣的可以自己写个Xposed插件破解试试看。

private static final String QU_TOU_TIAO_PACKAGENAME = "com.jifen.qukan";

private static final String QU_TOU_TIAO_SIGNATURE = "3082021b30820184a0030201020204574beab6300d06092a864886f70d01010505003052310c300a06035504061303303231310b3009060355040813025348310b3009060355040713025348310b3009060355040a13025a48310b3009060355040b1302434e310e300c0603550403130551754b616e301e170d3136303533303037323433385a170d3431303532343037323433385a3052310c300a06035504061303303231310b3009060355040813025348310b3009060355040713025348310b3009060355040a13025a48310b3009060355040b1302434e310e300c0603550403130551754b616e30819f300d06092a864886f70d010101050003818d0030818902818100aa5bae49b771380e692444437b82b375cabdefb3f23307c29510653776b8e4115f776bea5eb6690285f97d4e6e8d0469e49f79ecba31e4b7fb85dd612ee6b27ef38502aa38d055ddad2aa7b52d19fb8d2aeeb59a830b91c341f1b467655e7313e9ff65feb6539bf1655f35a37e17faa94e506a08219df196730f45d9c1cd94d30203010001300d06092a864886f70d0101050500038181000e6cc9fb74aef11dd33d6603869a9db61b8dcedae77bc815433026693fe59fd4b75a3284170f8872737e55595c1fd40da3dfbe5ad8a4e96802f53637977f0eb6e9b0dc35161cbaed398b41ecd73c4009a1dae7bcb00b75c3f8d5792405bcc5e4602d9dff6a0dc4739240a3b42626f5efce4d7baea0fced2b13361cb4ded8ed0b"  

欢迎关注个人技术公众号:Android葵花宝典
或微信扫一扫关注


相关文章

网友评论

    本文标题:使用Xpatch破解App签名校验的两个方法

    本文链接:https://www.haomeiwen.com/subject/aqrxiqtx.html