过签名校验(1) -- PM 去校验实践
看了正己 大佬的视频,肯定得实践实践。
签名校验比前面的教程额外的困难,所以记录一下。
我打算把我实践我过程写 3 部分 教程.
-
PM 去校验 和 android 部分 原理
-
MT 的 IO 重定向 xhook openet 去校验
-
实践 之前的七猫小说去签名校验
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 是如何创建的
- 我们先在 onCreate 回调下断点。
- 查看栈过程
很明显我们得到了一个关键类 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 代{过}{滤}理
看了下 mt的 github 代码 ,发现它是 直接 对 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) {
}
}
}
如果要使用的话。参考下面的步骤
- 构建我们的项目
- 把里面 classesX.dex (具体哪一个自己用jadx 看一下) 拖到要破解的包里 (这个建议用MT NP好像有点问题)
-
看AndroidManifest.xml 是否拥有自己的 Application
两种情况
- 如果没有 直接用我们的
- 有的话我们就插入静态块 在调用我的。
很明显它是有的,我们找一下他的 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