之前有分析过kstools以及XX管理器的去签名校验功能,XX管理器 普通版以及kstools的去签名校验使用动态代理PackageManagerService接口生成的对象替换系统原PMS服务, 增强版Hook了Native层标准函数库里的open函数以及对Java层的各种IO类进行了继承重写,然后使用反射替换系统的IO。重写与构造虚假Context绕过一般签名校验的思路一样。
Android Hook技术目前非常火热,典型产品功能有App插件化,热修复等。在Android逆向破解方面就是注入了,不妨看看这篇:
上面提到的去签名校验以及替换系统PMS服务都源自于这篇这篇帖子,有详细介绍,下面也会简单带过一下。
Hook系统服务(修改系统API函数的执行逻辑),有全局Hook和单进程Hook,全局Hook需要Root权限,典型代表开源框架Xposed,Frida 。单个进程Hook,代表开源框架Whale,爱奇艺的XHook。单个进程Hook就是自己Hook自己啦。前面提到的替换PMS服务对象就是。那么有人说不要Root权限能不能实现全局Hook呢?可以,你搞定SELinux就行。
我们知道JDK原生动态代理只能代理接口,因为代理接口后经过动态字节码技术生成的代理class是继承Proxy类的。那为什么代理类一定要继承Proxy类呢?这个问题我查看了许多答案,看的也不是特别明白。如大家有兴趣,可以去搜索一下。下面是一个动态字节码class命名:
public final class $Proxy0 extends Proxy implements MyInterface
$Proxy0有多少个代理类,0-N
MyInterface即我们需要代理的接口
其实我们知道Java平台还有一个动态代理框架:cglib,
它不仅仅能够代理接口,还可以代理非接口的类,并且还能够为类的非final方法提供代理,为JDK的动态代理提供了很好的补充。那为什么我们为什么不在Android平台上使用它呢?
cglib如此强大,但是,它有一个很致命的缺点是:cglib底层是采用著名的ASM字节码生成框架,使用字节码技术生成代理类,也就是通过操作字节码来生成的新的.class文件,而我们在android中加载的是优化后的.dex文件,也就是说我们需要可以动态生成.dex文件代理类,因此cglib在android中是不能使用的。不过,网上已经有人根据dexmaker框架(dex代码生成工具)来仿照cglib库动态生成.dex文件,实现了类似于cglib的AOP的功能,不过还有bug尚待修复,对高版本的Android支持不太友好。
好了,我们回到正题上来,Android
IPackageManager接口正是我们需要代理的接口,因为里面有获取apk包名,签名信息等一些列方法,而且通常我们也会通过这个接口来获取Apk的一些信息。通过代理这个接口,我们可以修改这些函数的返回值,达到我们想要的目的。代理又分为静态代理和动态代理,静态代理就是本文一开始提到的继承重写,这两种代理的优缺点这里不做作阐述。我们现在一般都是使用动态代理来实现一些功能,比如日志功能。现在Java平台非常火热的Spring框架的AOP切面编程就是采用的JDK原生动态代理,原生代理是非侵入式的。通过动态代理掉 IPackageManager 这个接口生成代理对象后,我们将代理对象替换掉C urrentActivityThread对象里的sPackageManager字段和 PackageManager 对象里的 mPM字段里的原始IPM对象,这样只要进程内调用了 IPackageManager 这个接口内的任意方法,都会被转发到代理对象的invoke方法里面去,在里面我们就可以执行自定义的逻辑了。
另外,如果一个类不是接口如何实现Hook呢?我们可以先写一个类继承它,然后重写里面的方法,再使用反射替换就行了。 hook 点必须满足以下的条件:需要 hook 的方法,所属的对象必须是静态的,因为我们是通过反射来获取对象的,我们获取的是系统的对象,所以不能够 new 一个新的对象,必须用系统创建的那个对象,所以只有静态的才能保证和系统的对象一致。
简单介绍了一下Hook,那么我们如何防止被Hook?从而拿到Apk的真实签名信息呢?下面介绍两种方案,一种是恢复被Hook的服务对象,另一种则是我们通过从 /proc/self/maps中解析 SO 符号表得到dlopen和dlsym函数的地址,再通过这两个函数去到系统的/system/lib目录下的libc.so或者libart.so去寻找open函数的真实地址,最后读取Apk的META-INF目录下的CERT.RSA/DSA文件,然后解析出公钥信息。
1.还原被Hook的服务对象
我们分析Android系统源码:
android.app.ActivityThread发现类里面有这样一个方法:
如果sPackageManager字段为空就会调用IPackageManager内部Stub类的asInterface方法初始化IPackageManager对象,那么Hook PMS就是将 sPackageManager 字段替换了,到这里我们就很容易得出一种还原PMS服务的方法:先将 sPackageManager 字段置空,然后反射调用getPackageManager方法,这样得到的IPM对象就是原始的服务对象,再 利用反射将PMS对象替换成原始的对象就好了。
但是,我们这里不采用这种方式,因为跟进asInterface这个方法我们不难发现: 这里的queryLocalInterface方法也是一个Hook点。
为什么说 queryLocalInterface方法也是一个Hook点呢?这里我以Hook queryLocalInterface方法的思路来阐述:
这个方法的意思就是:先查看本进程是否存在这个Binder对象,如果有那么直接就是本进程调用了;
如果不存在那么创建一个代理对象,让代理对象委托驱动完成跨进程调用。
观察这个方法,前面的那个if语句判空返回肯定动不了手脚;最后一句调用构造函数然后直接返回我们也是无从下手,
要修改asInterface方法的返回值,我们唯一能做的就是从这一句下手:
1
2
3
4
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
IBinder b = ServiceManager.getService(
"service_name"
);
我们修改这个getService方法的返回值,让这个方法返回一个我们伪造过的IBinder对象;这样,
我们可以在自己伪造的IBinder对象的queryLocalInterface方法作处理,进而使得asInterface方法返回
在queryLocalInterface方法里面处理过的值,最终实现hook系统服务的目的。
在跟踪这个getService方法之前我们思考一下,由于系统服务是一系列的远程Service,它们的本体,
也就是Binder本地对象一般都存在于某个单独的进程,在这个进程之外的其他进程存在的都是这些Binder本地对象的代理。
因此在我们的进程里面,存在的也只是这个Binder代理对象,我们也只能对这些Binder代理对象下手。然后,
这个getService是一个静态方法,如果此方法什么都不做,拿到Binder代理对象之后直接返回;
那么我们就无能为力了:我们没有办法拦截一个静态方法,也没有办法获取到这个静态方法里面的局部变量(即我们希望修改的那个Binder代理对象)。
接下来就可以看这个getService的代码了:
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2020-2-24 21:20
被LivedForward编辑
,原因: