2016976438 发表于 2023-1-5 23:08

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

本帖最后由 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 应用的各种信息。

```java
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.toByteArray();
      } catch (PackageManager.NameNotFoundException e) {
            throw new RuntimeException(e);
      }
   }
}   
```

# ContextWrapper

但是 getPackageManager()是如何从我们当前 MainActivity 获取的呢?咋们看图下的结构图



学过面向对象的应该知道 ,我们当前使用的 MainActivity 实际就是 ContextWrapper 的子类

砸门点进去 getPackageManager() 看看。

```java
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** 回调下断点。



2. 查看栈过程





很明显我们得到了一个关键类 **ActivityThread**。

他从 **ActivityThread.main** 开始 。 然后最后一个方法 **performLanunchActivity** 触发我们的 **onCreate** 回调。咋们进去看看

```java
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

```java
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()** 获取的 ,这恐怕就是面向对象吧...

```java
public final class ActivityThread
    //最终获取的地方
      static volatile IPackageManager sPackageManager;
}
```



到这里应该也能懂了,为什么 代{过}{滤}理这里的 ActivityThread.packageManager 能够完成去签名的功能了吧。

下面是hook 的一段代码

```java
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;
            Integer flag = (Integer)args;
            //是否是获取我们需要hook apk的签名
            if(flag == PackageManager.GET_SIGNATURES && appPkgName.equals(pkgName)){
                //将构造方法中传进来的新的签名覆盖掉原来的签名
                Signature sign = new Signature(SIGN);
                PackageInfo info = (PackageInfo) method.invoke(base, args);
                info.signatures = sign;
                return info;
            }
      }
      return method.invoke(base, args);
    }   
}   
```

# MT 的 PM 代{过}{滤}理

看了下 **mt**的 **github** 代码 ,发现它是 直接 对 **PackageInfo**内置代{过}{滤}理进行处理。

这种方式比之前额外的简单。

回顾获取签名的代码

```java
public class MainActivity extends Activity {
         private byte[] signatureFromAPI() {
      try {
                  //无论怎么样,我们获取签名只用从 PackageInfo 当中获取。
            PackageInfo info = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
            
            return info.signatures.toByteArray();
      } catch (PackageManager.NameNotFoundException e) {
            throw new RuntimeException(e);
      }
    }
}
```

最终都是 **PackageInfo** 所以我们想办法只针对这一个即可

我们进去看看 (其实到这里就很痛苦了,android 部分代码看不到 )(上google 才知道,android 还分什么服务器端

客户端 咋也不懂 咋也不敢乱说。)

刚刚说过 **ContextImpl** 会调用 **ActivityThread.getPackageManager** 获取包管理器。

```java
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 大佬能看到。

以下就是谷歌出来的:

```java
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 开源中的:

```java
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 = 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 = 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. 构建我们的项目


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











3. 看**AndroidManifest.xml** 是否拥有自己的 **Application**

   两种情况

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

   



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

添加如下 :



```assembly
.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
```

怜渠客 发表于 2023-1-6 08:52

写的很好{:1_921:},“最终都是 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/

Poorwood 发表于 2023-1-6 09:51

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

debug_cat 发表于 2023-1-6 11:01

【上google 才知道,android 还分什么服务器端

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

正己 发表于 2023-1-6 17:17

学习了,写的都比我讲得好{:301_997:}

2016976438 发表于 2023-1-6 22:42

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

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

2016976438 发表于 2023-1-6 22:47

莫问刀 发表于 2023-1-6 11:01
【上google 才知道,android 还分什么服务器端

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

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

2016976438 发表于 2023-1-6 22:48

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

确实说绝对了受教了

2016976438 发表于 2023-1-6 22:48

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

其实 确实是你这一句话的事情 ...
页: [1] 2
查看完整版本: 过签名校验(1) -- PM 去校验实践