过签名校验(1) -- PM 去校验实践

查看 110|回复 9
作者:2016976438   
过签名校验(1) -- PM 去校验实践
看了正己 大佬的视频,肯定得实践实践。
签名校验比前面的教程额外的困难,所以记录一下。
我打算把我实践我过程写 3 部分 教程.
[ol]

  • PM 去校验 和 android 部分 原理

  • MT 的 IO 重定向 xhook openet 去校验

  • 实践 之前的七猫小说去签名校验
    [/ol]
    PackageManager
    首先从包管理器开始说起,包管理器用于检索与设备上当前安装的应用程序包相关的各种信息的类、其中就包括砸门的签名信息。这里我们hook掉 就完全去签名校验的功能
    虽然 正己 大佬说是五年前的技术,但了解其原理,可以深入安卓,并且我们可以从这个地方牵扯出很多东西,如 Application 和  ActivityThread 等等。 可以了解整个android 周期 为我们逆向提供很多思路、
    ​             如下所示: PackageManager  可以很轻松获取 当前 app 应用的各种信息。
    public class MainActivity extends AppCompatActivity {
        private byte[] signatureFromAPI() {
            try {
                PackageInfo info = getPackageManager().getPackageInfo(getPackageName(),                                                 PackageManager.GET_SIGNATURES);
                Log.i("签名数量:", info.signatures.length + "");
                return info.signatures[0].toByteArray();
            } catch (PackageManager.NameNotFoundException e) {
                throw new RuntimeException(e);
            }
       }
    }   
    ContextWrapper
    但是 getPackageManager()  是如何从我们当前 MainActivity 获取的呢?咋们看图下的结构图


    2023-01-02_215204.png (37.5 KB, 下载次数: 0)
    下载附件
    2023-1-5 22:58 上传

    学过面向对象的应该知道 ,我们当前使用的 MainActivity 实际就是 ContextWrapper 的子类
    砸门点进去 getPackageManager() 看看。
    public class ContextWrapper extends Context {
        //1. 包装的 Context
            Context mBase;
        //2. 赋值 Context 的方法
        protected void attachBaseContext(Context base) {
            if (mBase != null) {
                throw new IllegalStateException("Base context already set");
            }
            mBase = base;
        }
        @Override
        public PackageManager getPackageManager() {
            //3. 从 Context 获取包管理器
            return mBase.getPackageManager();
        }
    }   
    可以看到 ContextWrapper 包装了 Context,其中  getPackageManager()  就是从 Context 获取了。
    所以才能直接调用 getPackageManager() 方法
    Activity
    Context 是从哪里传过来的呢?  我觉得应该是从 创建 砸门的  MainActivity 后传过来的。
    接下来我就来分析 Activity 是如何创建的
    [ol]
  • 我们先在 onCreate 回调下断点。
    [/ol]


    2023-01-02_155248.png (67.93 KB, 下载次数: 0)
    下载附件
    2023-1-5 22:58 上传
  • 查看栈过程
    [/ol]


    2023-01-02_161350.png (78.74 KB, 下载次数: 0)
    下载附件
    2023-1-5 22:58 上传

    很明显我们得到了一个关键类 ActivityThread
    他从 ActivityThread.main 开始 。 然后最后一个方法 performLanunchActivity 触发我们的 onCreate 回调。咋们进去看看
    public final class ActivityThread{
        //这个大概就是用来基础组件的创建 和 基础组 函数调用的 (我猜的)
        Instrumentation mInstrumentation;
            private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
            //1.创建         ContextImpl
            ContextImpl appContext = createBaseContextForActivity(r);
            //2.通过 mInstrumentation 创建activity
            Activity activity = = mInstrumentation.newActivity(
                        cl, component.getClassName(), r.intent);
            //3. 通过  activity.attach 传入砸门的 context
            activity.attach(appContext, this, ....);
        }     
    }
    从这里您应该也能看懂了 实际 Context 就是 ContextImpl
    咋们继续
    ContextImpl
    class ContextImpl extends Context {
        @Override
        public PackageManager getPackageManager() {
            if (mPackageManager != null) {
                return mPackageManager;
            }
                    //1. 通过 ActivityThread 获取 IPackageManager
            final IPackageManager pm = ActivityThread.getPackageManager();
            if (pm != null) {
                // Doesn't matter if we make more than one instance.
                return (mPackageManager = new ApplicationPackageManager(this, pm));
            }
            return null;
        }
    }
    兜兜转转 又是从  ActivityThread.getPackageManager() 获取的 ,这恐怕就是面向对象吧...
    public final class ActivityThread
        //最终获取的地方
            static volatile IPackageManager sPackageManager;
    }
    到这里应该也能懂了,为什么 代{过}{滤}理这里的 ActivityThread.packageManager 能够完成去签名的功能了吧。
    下面是hook 的一段代码
    public class ServiceManagerWraper {
        public final static String ZJ = "ZJ595";
        public static void hookPMS(Context context, String signed, String appPkgName, int hashCode) {
            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");
                Object proxy = Proxy.newProxyInstance(
                        iPackageManagerInterface.getClassLoader(),
                        new Class[]{iPackageManagerInterface},
                        new PmsHookBinderInvocationHandler(sPackageManager, signed, appPkgName, 0));
                // 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);
            } catch (Exception e) {
                Log.d(ZJ, "hook pms error:" + Log.getStackTraceString(e));
            }
        }
        public static void hookPMS(Context context) {
            String Sign = "原包的签名信息";
            hookPMS(context, Sign, "com.zj.hookpms", 0);
        }
    }
    public class PmsHookBinderInvocationHandler implements InvocationHandler{
        //应用正确的签名信息
        private String SIGN;
         @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Log.i(SHARK, method.getName());
            //查看是否是getPackageInfo方法
            if("getPackageInfo".equals(method.getName())){
                String pkgName = (String)args[0];
                Integer flag = (Integer)args[1];
                //是否是获取我们需要hook apk的签名
                if(flag == PackageManager.GET_SIGNATURES && appPkgName.equals(pkgName)){
                    //将构造方法中传进来的新的签名覆盖掉原来的签名
                    Signature sign = new Signature(SIGN);
                    PackageInfo info = (PackageInfo) method.invoke(base, args);
                    info.signatures[0] = sign;
                    return info;
                }
            }
            return method.invoke(base, args);
        }   
    }   
    MT 的 PM 代{过}{滤}理
    看了下 mtgithub 代码 ,发现它是 直接 对 PackageInfo  内置代{过}{滤}理进行处理。
    这种方式比之前额外的简单。
    回顾获取签名的代码
    public class MainActivity extends Activity {
             private byte[] signatureFromAPI() {
            try {
                      //无论怎么样,我们获取签名只用从 PackageInfo 当中获取。
                PackageInfo info = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
                return info.signatures[0].toByteArray();
            } catch (PackageManager.NameNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
    }
    最终都是 PackageInfo 所以我们想办法只针对这一个即可
    我们进去看看 (其实到这里就很痛苦了,android 部分代码看不到 )(上google 才知道,android 还分什么服务器端
    客户端 咋也不懂 咋也不敢乱说。)
    刚刚说过 ContextImpl 会调用 ActivityThread.getPackageManager 获取包管理器。
    class ContextImpl extends Context {
       public PackageManager getPackageManager() {
            if (mPackageManager != null) {
                return mPackageManager;
            }
                    //1. 通过 ActivityThread 获取 包管理器
            final IPackageManager pm = ActivityThread.getPackageManager();
            if (pm != null) {
                // 2.包装成 ApplicationPackageManager 返回
                return (mPackageManager = new ApplicationPackageManager(this, pm));
            }
            return null;
        }
    }   
    public final class ActivityThread
       public static IPackageManager getPackageManager() {
            if (sPackageManager != null) {
                return sPackageManager;
            }
            //3. 获取 binder 与服务器通信
            final IBinder b = ServiceManager.getService("package");
                //3. 通过服务器 生成 IPackageManager.Stub.Proxy 代码获取 sPackageManager
            sPackageManager = IPackageManager.Stub.asInterface(b);
            return sPackageManager;
       }
    }   
    这个地方就特别迷糊了,IPackageManager 代{过}{滤}理的 源码看不了怎么办???
    但是 google 大佬能看到。
    以下就是谷歌出来的:
    public interface IPackageManager extends android.os.IInterface{
             public static android.content.pm.IPackageManager asInterface(android.os.IBinder obj){
            if ((obj==null)) {
                return null;
            }
            ....
            //1.不是当前进程,返回的是代{过}{滤}理类
            return new android.content.pm.IPackageManager.Stub.Proxy(obj);
        }
         //2.类Stub中定义的代{过}{滤}理类Proxy,Proxy中代{过}{滤}理方法很多,这里同样只贴出了getPackageInfo方法
        private static class Proxy implements android.content.pm.IPackageManager {
            private android.os.IBinder mRemote;
            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }
            ...
            @Override
            public android.content.pm.PackageInfo getPackageInfo(java.lang.String packageName, int flags, int userId) throws android.os.RemoteException{
                    .....
                if ((0!=_reply.readInt())) {
                    //3. 注意这里 IPackageManager.getPackageInfo() 实际是通过 PackageInfo.CREATOR
                    //获取的,砸门只要代{过}{滤}理这个地方 就OK了!
                    _result = android.content.pm.PackageInfo.CREATOR.createFromParcel(_reply);
                } else {
                    _result = null;
                }
               ....
                return _result;
            }
            ...
            }
    }
    下面就是 MT 开源中的:
    public class KillerApplication extends Application {
             static {
            String packageName = "com.example.killerapplication";
            //正确的签名信息
            String signature = "原始签名";
            killPmm(packageName,
                    signature);
        }
        private static void killPmm(String packageName, String signatureData) {
            try {
                //正确的签名
                Signature fakeSignature = new Signature(Base64.decode(signatureData,Base64.DEFAULT));
                //1. 获取原包装 , 在 IPackageManager.Stub.Proxy 中 实际 获取签名就是这个
                Parcelable.Creator originalCreator = PackageInfo.CREATOR;
                //2.咋们创建一个我们自己的
                Parcelable.Creator creator = new Parcelable.Creator() {
                    @Override
                    public PackageInfo createFromParcel(Parcel source) {
                        //3.从原包装创建 packageInfo
                        PackageInfo packageInfo = originalCreator.createFromParcel(source);
                        if (packageInfo.packageName.equals(packageName)) {
                            if (packageInfo.signatures != null && packageInfo.signatures.length > 0) {
                                //4.把当前改过的签名 修改成以前 正确的签名
                                packageInfo.signatures[0] = fakeSignature;
                            }
                        }
                        //4.对新api 的兼容
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                            if (packageInfo.signingInfo != null) {
                                Signature[] signaturesArray = packageInfo.signingInfo.getApkContentsSigners();
                                if (signaturesArray != null && signaturesArray.length > 0) {
                                    signaturesArray[0] = fakeSignature;
                                }
                            }
                        }
                        return packageInfo;
                    }
                    @Override
                    public PackageInfo[] newArray(int size) {
                        return originalCreator.newArray(size);
                    }
                };
                            try {
                    //5.将原来的 CREATOR 替换成我们的
                    findField(PackageInfo.class, "CREATOR").set(null, creator);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }               
                try {
                    Map mCreators = (Map) findField(Parcel.class, "mCreators").get(null);
                    Map sPairedCreators = (Map) findField(Parcel.class, "sPairedCreators").get(null);
                    //清除调用条件
                    mCreators.clear();
                    sPairedCreators.clear();
                } catch (Throwable ignored) {
                }
        }
    }
    如果要使用的话。参考下面的步骤
    [ol]
  • 构建我们的项目
    [/ol]


    2023-01-03_215530.png (39.38 KB, 下载次数: 0)
    下载附件
    2023-1-5 22:58 上传
  • 把里面 classesX.dex (具体哪一个自己用jadx 看一下) 拖到要破解的包里 (这个建议用MT  NP好像有点问题)
    [/ol]


    2023-01-03_215905.png (92.63 KB, 下载次数: 0)
    下载附件
    2023-1-5 22:58 上传



    2023-01-03_224248.png (197.91 KB, 下载次数: 0)
    下载附件
    2023-1-5 22:58 上传

  • AndroidManifest.xml 是否拥有自己的 Application
    两种情况
    [ol]
  • 如果没有 直接用我们的
  • 有的话我们就插入静态块 在调用我的。
    [/ol]


    2023-01-03_224451.png (302.78 KB, 下载次数: 0)
    下载附件
    2023-1-5 22:58 上传

    [/ol]
    很明显它是有的,我们找一下他的 MainApplication.smali
    添加如下 :


    2023-01-03_225944.png (103.52 KB, 下载次数: 0)
    下载附件
    2023-1-5 22:58 上传

    .method static ()V
        .registers 1
         .line 37
         new-instance        v0, Lcom/example/nativelib/KillerApplication; # type@0013
         invoke-direct       {v0}, Lcom/example/nativelib/KillerApplication;->()V # method@001c
         .line 38
         return-void         
    .end method


    2023-01-04_181338.png (62.05 KB, 下载次数: 0)
    下载附件
    2023-1-5 23:07 上传

    下载次数, 下载附件

  • lianquke   

    写的很好,“最终都是 PackageInfo 所以我们想办法只针对这一个即可”,这句话太绝对了,解析RSA转成signature,直接读v2签名字段都不走packageinfo
    debug_cat   


    2016976438 发表于 2023-1-6 22:47
    我好像有点困难 不知道怎么搜 才能定位到 生成代{过}{滤}理的地方。 只能看到个 IPackageMan ...
    需要有开发的知识
    aidl在编译的时候会产生对应的java文件,比如IPackageManager.aidl,会生成IPackageManager.java,服务端需要继承IPackageManager.Stub实现aidl中定义的所有协议。也就是aosp的代码中可以查找java文件,里面是否包含有字符串xxxxService extends IPackageManager.Stub这样的字符串。
    这就是开发的经验。
    怎么在源码中搜索自己需要的代码
    例如aosp 10代码中,我在本地源码中搜索find . -name "*.java" | xargs grep "extends ILocationManager.Stub" --color=auto
    可以得到这样的结果,你就知道了aosp中那个文件以及文件对应的具体路径了。


    QQ截图20230107093329.jpg (122.04 KB, 下载次数: 0)
    下载附件
    2023-1-7 09:37 上传

    在线源码可以这里取搜索:https://cs.android.com/ 或者http://androidxref.com/
    Poorwood   

    个人理解就是将“下面就是 MT 开源中的:” 这个地方的源码,编译为smali,然后塞进游戏的application里,就行了吧?
    debug_cat   

    【上google 才知道,android 还分什么服务器端
    客户端 咋也不懂 咋也不敢乱说】,这里在系统的角度来说,pms服务在系统只有一份,也就是开机的时候启动的服务,对于SDK和应用层来说,他们就是客户端,pms就是服务器,所有的app和pms通信,都是通过binder,所以可以说pms系统服务器是服务端,所有的调用方都是客户端。
    至于你在as中看到的SDK代码,是给开发者用的,至于pms是在系统层,你需要去看系统层代码。当然有很多在线浏览的,比如http://www.aospxref.com/,也可以自己下载repo,然后本地查看系统代码。
    2016976438
    OP
      

    学习了,写的都比我讲得好
    2016976438
    OP
      


    正己 发表于 2023-1-6 17:17
    学习了,写的都比我讲得好

    那也比不上启蒙老师
    过签名之前也看过几篇,越看越乱。
    还是跟着大佬视频容易容易上手
    2016976438
    OP
      


    莫问刀 发表于 2023-1-6 11:01
    【上google 才知道,android 还分什么服务器端
    客户端 咋也不懂 咋也不敢乱说】,这里在系统的角度来说, ...

       我好像有点困难 不知道怎么搜 才能定位到 生成代{过}{滤}理的地方。 只能看到个 IPackageManager.aidl
    2016976438
    OP
      


    lianquke 发表于 2023-1-6 08:52
    写的很好,“最终都是 PackageInfo 所以我们想办法只针对这一个即可”,这句话太绝对了,解析RSA ...

    确实说绝对了  受教了
    debug_cat   


    Poorwood 发表于 2023-1-6 09:51
    个人理解就是将“下面就是 MT 开源中的:” 这个地方的源码,编译为smali,然后塞进游戏的application里,就 ...

    其实 确实是你这一句话的事情 ...
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部