吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4674|回复: 17
上一主题 下一主题
收起左侧

[Android 原创] 过签名校验(1) -- PM 去校验实践

  [复制链接]
跳转到指定楼层
楼主
2016976438 发表于 2023-1-5 23:08 回帖奖励
本帖最后由 2016976438 于 2023-1-5 23:26 编辑

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

看了正己 大佬的视频,肯定得实践实践。

签名校验比前面的教程额外的困难,所以记录一下。

我打算把我实践我过程写 3 部分 教程.

  1. PM 去校验 和 android 部分 原理

  2. MT 的 IO 重定向 xhook openet 去校验

  3. 实践 之前的七猫小说去签名校验

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 获取的呢?咋们看图下的结构图

学过面向对象的应该知道 ,我们当前使用的 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 是如何创建的

  1. 我们先在 onCreate 回调下断点。

  1. 查看栈过程

很明显我们得到了一个关键类 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<PackageInfo> originalCreator = PackageInfo.CREATOR;
            //2.咋们创建一个我们自己的
            Parcelable.Creator<PackageInfo> creator = new Parcelable.Creator<PackageInfo>() {
                @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) {
            }

    }
}

如果要使用的话。参考下面的步骤

  1. 构建我们的项目

  1. 把里面 classesX.dex (具体哪一个自己用jadx 看一下) 拖到要破解的包里 (这个建议用MT  NP好像有点问题)

  1. AndroidManifest.xml 是否拥有自己的 Application

    两种情况

    1. 如果没有 直接用我们的
    2. 有的话我们就插入静态块 在调用我的。

很明显它是有的,我们找一下他的 MainApplication.smali

添加如下 :

.method static <clinit>()V
    .registers 1

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

.end method

免费评分

参与人数 8威望 +3 吾爱币 +98 热心值 +7 收起 理由
junjia215 + 1 + 1 谢谢@Thanks!
allspark + 1 + 1 用心讨论,共获提升!
Li1y + 1 + 1 用心讨论,共获提升!
正己 + 4 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
qtfreet00 + 3 + 88 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
debug_cat + 1 + 1 热心回复!
叶无道 + 1 + 1 热心回复!
xiaoyaotan + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
怜渠客 发表于 2023-1-6 08:52
写的很好,“最终都是 PackageInfo 所以我们想办法只针对这一个即可”,这句话太绝对了,解析RSA转成signature,直接读v2签名字段都不走packageinfo
推荐
debug_cat 发表于 2023-1-7 09:39
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中那个文件以及文件对应的具体路径了。

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

4#
Poorwood 发表于 2023-1-6 09:51
个人理解就是将“下面就是 MT 开源中的:” 这个地方的源码,编译为smali,然后塞进游戏的application里,就行了吧?
5#
debug_cat 发表于 2023-1-6 11:01
【上google 才知道,android 还分什么服务器端

客户端 咋也不懂 咋也不敢乱说】,这里在系统的角度来说,pms服务在系统只有一份,也就是开机的时候启动的服务,对于SDK和应用层来说,他们就是客户端,pms就是服务器,所有的app和pms通信,都是通过binder,所以可以说pms系统服务器是服务端,所有的调用方都是客户端。
至于你在as中看到的SDK代码,是给开发者用的,至于pms是在系统层,你需要去看系统层代码。当然有很多在线浏览的,比如http://www.aospxref.com/,也可以自己下载repo,然后本地查看系统代码。
6#
正己 发表于 2023-1-6 17:17
学习了,写的都比我讲得好
7#
 楼主| 2016976438 发表于 2023-1-6 22:42 |楼主
正己 发表于 2023-1-6 17:17
学习了,写的都比我讲得好

那也比不上启蒙老师
过签名之前也看过几篇,越看越乱。
还是跟着大佬视频容易容易上手
8#
 楼主| 2016976438 发表于 2023-1-6 22:47 |楼主
莫问刀 发表于 2023-1-6 11:01
【上google 才知道,android 还分什么服务器端

客户端 咋也不懂 咋也不敢乱说】,这里在系统的角度来说, ...

   我好像有点困难 不知道怎么搜 才能定位到 生成代{过}{滤}理的地方。 只能看到个 IPackageManager.aidl
9#
 楼主| 2016976438 发表于 2023-1-6 22:48 |楼主
lianquke 发表于 2023-1-6 08:52
写的很好,“最终都是 PackageInfo 所以我们想办法只针对这一个即可”,这句话太绝对了,解析RSA ...

确实说绝对了  受教了
10#
 楼主| 2016976438 发表于 2023-1-6 22:48 |楼主
Poorwood 发表于 2023-1-6 09:51
个人理解就是将“下面就是 MT 开源中的:” 这个地方的源码,编译为smali,然后塞进游戏的application里,就 ...

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

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-1-8 05:17

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表