Android Hook技术实践
大家好,又见面了,我是你们的朋友全栈君。
一、hook简介
hook俗称钩子,主要作用是替换系统内存中的对象,在上层调用此对象的方法的时候可以修改传入参数或者返回值,达到欺骗上层的目的,就像小红帽故事里的大灰狼,通过扮演小红帽的外婆的角色来达到欺骗小红帽的目的。其实hook就是一种中间人劫持的思想,如图所示:
在安卓中实现hook主要通过两种方式:
1.反射技术和代理实现,当然代理不管是动态还是静态的都是可以实现的,但是只能hook自己应用内存中的对象;
2.在root的情况下,Xposed通过替换/system/bin/app_process程序控制zygote进程,使得app_process在启动过程中会加载XposedBridge.jar这个jar包,从而完成对Zygote进程及其创建的Dalvik虚拟机的劫持,可以达到hook整个系统中所有进程内存里面的对象的目的;
方式2虽然很强大、很逆天,但是也有限制就是必须的root,所以本文主要讲方式1的实现。
二、hookAms实践:
插件技术中很重要的一项就是宿主启动插件APK中的Activity,因为插件都是后面业务迭代加进来的,所以Activity不可能提前注册在宿主Activity的清单文件中的,所以正常的情况下是不可能启动插件里的Activity的,因为启动Activity的过程是需要在清单文件中寻找是否注册,若没有,则直接crash。
所以想实现跳过系统检查,做法就是先在宿主里注册一个ProxyActivity,在启动插件的Activity的时候,把我们这个真实意图Intent替换为可以通过检查的启动ProxyActivity的代理意图Intent,然后让真实意图作为Extra添加进代理意图里,在通过检查后再取出来替换回来,而这样的功能,是通过hook实现的。
我们知道Activity的启动过程是通过AIDL Binder的方式跟AMS进行一系列的交互,最终通过反射newInstance创建出来的,由于AMS处于系统进程中,所以我们是没法从它里面寻找hook点的。所以这里所说的hookAms,其实是hook位于我们自己的应用这边的与AMS交互的AIDL的接口IActivityManeger,那怎么才能找到这个对象并且进行替换呢,需要看FrameWork源码:
2376 private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
2377 protected IActivityManager More ...create() {
2378 IBinder b = ServiceManager.getService("activity");
2379 if (false) {
2380 Log.v("ActivityManager", "default service binder = " + b);
2381 }
2382 IActivityManager am = asInterface(b);
2383 if (false) {
2384 Log.v("ActivityManager", "default service = " + am);
2385 }
2386 return am;
2387 }
2388 };
26public abstract class More ...Singleton<T> {
27 private T mInstance;
28
29 protected abstract T More ...create();
30
31 public final T More ...get() {
32 synchronized (this) {
33 if (mInstance == null) {
34 mInstance = create();
35 }
36 return mInstance;
37 }
38 }
39}
gDefault是在 android.app.ActivityManagerNative里面的一个静态内部类实现了Singleton的抽象方法create()并且返回mInstance即IActivityManeger
基于此,我们便可以通过反射和动态代理来下钩子了:
public static void hookAms(Context context){ try { //获取ActivityManagerNative里面的gDefault静态对象即是内存的唯一对象 Class<?> forName = Class.forName("android.app.ActivityManagerNative"); Field defauleField = forName.getDeclaredField("gDefault"); defauleField.setAccessible(true); Object defauleValue = defauleField.get(null);//静态对象传null即可 //获取gDefault里面的iActivityManagerObject对象 Class<?> forName2 = Class.forName("android.util.Singleton"); Field instanceField = forName2.getDeclaredField("mInstance"); instanceField.setAccessible(true); Object iActivityManagerObject = instanceField.get(defauleValue); //传入真实对象生成代理对象proxyy Object proxyy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{Class.forName("android.app.IActivityManager")}, new AmsInvocationHandler(context,iActivityManagerObject)); //真实对象替换为代理对象proxyy instanceField.set(defauleValue,proxyy); } catch (Exception e) { e.printStackTrace(); }}
public class AmsInvocationHandler implements InvocationHandler { private Context context; private Object iActivityManagerObject; public AmsInvocationHandler(Context context,Object iActivityManagerObject) { this.context = context; this.iActivityManagerObject = iActivityManagerObject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("startActivity".contains(method.getName())){ Intent intent = null; int index = 0; for(int i = 0;i<args.length;i++){ if(args[i] instanceof Intent){ intent = (Intent)args[i]; index = i; break; } } Intent proxyIntent = new Intent(context,ProxyActivity.class); proxyIntent.putExtra("oldIntent",intent); args[index] = proxyIntent; } return method.invoke(iActivityManagerObject,args); }}
如此便完成了意图的替换,接下来就是在通过检查后、启动Activity前把意图替换回来:
通过源码我们知道ActivityThread里维护着一个系统Handler:mH,mH主要负责发送和处理各种系统服务的msg,包括四大组件的创建启动
由于mH的代码量比较大,我这里就只贴出handlerMessage的launchActivity部分
case LAUNCH_ACTIVITY: {
1273 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
1274 final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
1275
1276 r.packageInfo = getPackageInfoNoCheck(
1277 r.activityInfo.applicationInfo, r.compatInfo);
1278 handleLaunchActivity(r, null);
1279 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
1280 } break;
可以看到msg携带的obj是一个叫做ActivityClientRecord的对象,而这个对象里面其实携带了Intent,基于此,我们便可以在mH的handlerMessage之前将msg截取修改里面的Intent参数就可以了,由于Looper从消息队列中取出msg,然后在获取他的target即是handler的时候是调用handler的dispatchMessage的,所以我们可以先看看此方法
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); }}
可以看到方法里会先检查有没有mCallback,如果有则直接调用它的handlerMesage,所以这里我们可以直接hook它的mCallback
public static void hookSystemHandler(){
try {
// 获取全局的ActivityThread对象
Class<?> forName = Class.forName("android.app.ActivityThread");
Field currentActivityThreadField = forName.getDeclaredField("sCurrentActivityThread");
currentActivityThreadField.setAccessible(true);
Object currentActivityThread = currentActivityThreadField.get(null);
// 获取ActivityThread里面的mH对象
Field handlerField = forName.getDeclaredField("mH");
handlerField.setAccessible(true);
Handler handlerObject = (Handler)handlerField.get(currentActivityThread);
// 获取mH里面的mCallback对象,并且替换为我们自己的ActivityThreadHandlerCallBack
Field callBackFieid = Handler.class.getDeclaredField("mCallback");
callBackFieid.setAccessible(true);
callBackFieid.set(handlerObject,new ActivityThreadHandlerCallBack(handlerObject));
}catch (Exception e){
}
}
public class ActivityThreadHandlerCallBack implements Handler.Callback{
Handler handler;
public ActivityThreadHandlerCallBack(Handler handler) {
this.handler = handler;
}
@Override
public boolean handleMessage(Message msg) {
if(msg.what == 100){
launchActiity(msg);
}
handler.handleMessage(msg);
return true;
}
private void launchActiity(Message msg){
Object obj = msg.obj;
try {
Field intentField = obj.getClass().getDeclaredField("intent");
intentField .setAccessible(true);
Intent proxyIntent = (Intent)intentField.get(obj);
Intent realIntent = proxyIntent.getParcelableExtra("oldIntent");
if(realIntent != null){
proxyIntent.setComponent(realIntent.getComponent());
}
}catch (Exception e){
}
}
}
至此,我们通过hook技术完成了启动不在清单注册也能启动Activity这个功能。
三、hookPms实践
类似于AMS,ActivityThread也是通过AIDL接口IPackageManager来与系统的PackageManagerService进行交互的,所以我们只要hook我们应用内的IPackageManager就可以达到修改PMS的一些服务目的,这里我们通过hookPms来修改应用内签名获取的方法,这样做的意义在于当我们进行反编译的时候,并且遇到反编译的APP有签名校验的时候,可以用事先获取到的真实签名数据来替换APP内获取的签名,从而达到破解签名校验的目的。当然,实际开发的时候也是可以用到的,我们在做安卓终端开发的时候,用到了百度地图SDK,所以首先得去他们网站上注册一个应用,填写包名和签名sha1值,但是我们的应用是需要在多个平台上运行的,而不同的平台使用的系统签名不一样,所以不能做到创建一个应用通用全部平台,所以这里我就是通过hookPms的方式欺骗了百度地图,让底层返回那个百度后台创建过的应用的签名,这样百度SDK会判定你的包名、APPKEY和签名信息都是同一个应用且正确的,从而可以正常的使用。通过hook技术,我们避免了在百度后台创建多个应用,而且打包的时候根据每个平台来修改它meta-data的key值也是很麻烦的。
public static void hookPms(Context context, String signed, 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);
// 准备好代理对象, 用来替换原始的对象
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[] {
Class.forName("android.content.pm.IPackageManager") },
new PmsHookBinderInvocationHandler(sPackageManager, signed, hashCode));
// 替换掉ActivityThread里面的 sPackageManager 字段
sPackageManagerField.set(currentActivityThread, proxy);
// 替换 ApplicationPackageManager里面的 mPM对象
PackageManager pm = context.getPackageManager();
Field mPmField = pm.getClass().getDeclaredField("mPM");
mPmField.setAccessible(true);
mPmField.set(pm, proxy);
}catch (Exception e){
}
}
public class PmsHookBinderInvocationHandler implements InvocationHandler{
private Object base;
//应用正确的签名信息
private String SIGN;
private int hashCode = 0;
public PmsHookBinderInvocationHandler(Object base, String sign, int hashCode) {
try {
this.base = base;
this.SIGN = sign;
this.hashCode = hashCode;
} catch (Exception e) {
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("getPackageInfo".equals(method.getName())){
Integer flag = (Integer)args[1];
if(flag == PackageManager.GET_SIGNATURES){
Signature sign = new Signature(SIGN);
if(hashCode != 0){
try{
Class<?> clazz = sign.getClass();
Field mHaveHashCodeF = clazz.getDeclaredField("mHaveHashCode");
mHaveHashCodeF.setAccessible(true);
mHaveHashCodeF.set(sign, true);
Field mHashCodeF = clazz.getDeclaredField("mHashCode");
mHashCodeF.setAccessible(true);
mHashCodeF.set(sign, hashCode);
}catch(Exception e){
}
}
PackageInfo info = (PackageInfo) method.invoke(base, args);
info.signatures[0] = sign;
return info;
}
}
return method.invoke(base, args);
}
}
最后再附上获取签名信息的方法:
private void getSignature() {
try {
PackageInfo packageInfo = getPackageManager().getPackageInfo(
getPackageName(), PackageManager.GET_SIGNATURES);
if (packageInfo.signatures != null) {
Log.d("", "sig:"+packageInfo.signatures[0].toCharsString()+
"hashcode:"+packageInfo.signatures[0].hashCode());
}
} catch (Exception e2) {
}
}
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/141290.html原文链接:https://javaforall.cn
相关文章
- Android加密篇 AES
- 代码加密 android,Android 开发怎样做代码加密或混淆「建议收藏」
- android移动点餐系统内容和要求,基于Android云计算的移动点餐系统
- android开发笔记之 Android代码混淆打包
- android onresume方法,Android onActivityResult()和onResume()的执行顺序
- strictmode android,(十三)Android 性能优化 StrictMode
- android vlc 中文字幕,解决Android版vlc中文乱码问题
- android定时器取消,Android定时器崩溃取消
- 【Android布局】在程序中设置android gravity 和 android layout Gravity属性
- Android触摸事件_android设置按钮点击事件
- Android 编译_android线程
- android进程间通信的方式_Android进程注入
- 【Jetpack】DataBinding 架构组件 ( 数据绑定技术简介 | Android 中的 DataBinding 数据绑定 | 启动数据绑定 | 定义数据类 | 布局文件转换 )
- 【Android 逆向】Android 进程注入工具开发 ( 注入代码分析 | 远程调用 目标进程中 libc.so 动态库中的 mmap 函数 二 | 准备参数 | 远程调用 mmap 函数 )
- Android系统的应用开发与运行环境构建详解手机开发
- Android LruCache技术原理详解手机开发
- 深入理解Android插件化技术详解手机开发
- Android 中静默安装实现详解编程语言
- Android系统与Linux之间的联系(android和linux)
- Connecting Android to Oracle: The Ultimate Guide for Seamless Integration.(android连接oracle)
- android分辨率适配的方法
- Android三种GSM手机定位技术分析
- android耳机左右声道接反具体修正方法