第一、摘要

今天不是周末,但是我已经放假了,所以就开始我们的技术探索之旅,今天我们来讲一下Android中最期待的技术,就是拦截Activity的启动,其实我在去年的时候,就像实现这个技术了,但是因为知识不充足,就放弃了,当时的需求很简单,就是实现应用锁,就是给每个应用打开的时候加上密码,不了解的同学


不过,当时也是实现了,只是使用了监听TopActivity的方式实现的,后台起一个Service来轮询监听。但是当时想到了性能上的问题,因为感觉后台启动一个Service来进行轮询操作会很消耗性能,但是最后发现,现在市场上的应用锁应用实现的原理都是这么做的,所以当时就是用这个技术来做了。但是关于其他方式来做的工作没有断续,现在有时间就来研究一下如何通过拦截Activity的启动方式来实现拦截。因为:监听比轮训机制高效。


第二、前提准备

不过实现了拦截Activity启动技术实现之后,我们还会发现这个技术在很多地方都会用到。关于注入技术需要关注的文章:


看完这篇文章,才能理解注入技术,才能看懂这篇文章。看完这篇文章之后,我们能看到三个文件:

poison; libproxybinder.so; DemoInject3.apk

关于这三个文件的作用就不多说了。看完文章就知道了。


第三、技术解析

涉及到源码类:

ContextImpl.java

ActivityThread.java

Instrumentation.java

ActivityManagerNative.java

那么这里我们现在需要拦截Activity的启动流程的话,那就要看看Activity的启动源代码:

先来看一下ContextImpl.java中的startActivity代码:

它内部是调用ActivityThread类的Instrumentation变量的execStartActivity方法:

它内部是调用ActivityManagerNativestartActivity方法的:

到这里我们可以看到了,这里使用了Binder机制来做的,我们知道

ActivityManager

IActivityManager

ActivityManagerNative

ActivityManagerService

ActivityManagerProxy

这几个类的关系:

关于他们之间的具体信息,这里就不做详细介绍了。


我们看到ActivityManagerNative.java中的代码之后,我们知道我们只需要拦截code为启动Activity的就可以了:START_ACTIVITY_TRANSACTION

当然我们还需要解析Parcel数据,才能得到启动Activity的信息:具体的Parcel数据格式我们在代码中也能看到:


data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
IApplicationThread app = ApplicationThreadNative.asInterface(b);
String callingPackage = data.readString();
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
IBinder resultTo = data.readStrongBinder();
String resultWho = data.readString();
int requestCode = data.readInt();
int startFlags = data.readInt();
ProfilerInfo profilerInfo = data.readInt() != 0
? ProfilerInfo.CREATOR.createFromParcel(data) : null;
Bundle options = data.readInt() != 0
? Bundle.CREATOR.createFromParcel(data) : null;
int result = startActivity(app, callingPackage, intent, resolvedType,
		resultTo, resultWho, requestCode, startFlags, profilerInfo, options);
reply.writeNoException();
reply.writeInt(result);


拦截的code和Parcel的数据结构我们都知道了。那么我们来看一下DemoInject3项目的核心代码:

EntryClass.java

@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {

	/**
	 *  data.enforceInterface(IActivityManager.descriptor);
	            IBinder b = data.readStrongBinder();
	            IApplicationThread app = ApplicationThreadNative.asInterface(b);
	            String callingPackage = data.readString();
	            Intent intent = Intent.CREATOR.createFromParcel(data);
	            String resolvedType = data.readString();
	            IBinder resultTo = data.readStrongBinder();
	            String resultWho = data.readString();
	            int requestCode = data.readInt();
	            int startFlags = data.readInt();
	            ProfilerInfo profilerInfo = data.readInt() != 0
	                    ? ProfilerInfo.CREATOR.createFromParcel(data) : null;
	            Bundle options = data.readInt() != 0
	                    ? Bundle.CREATOR.createFromParcel(data) : null;
	            int result = startActivity(app, callingPackage, intent, resolvedType,
	                    resultTo, resultWho, requestCode, startFlags, profilerInfo, options);
	            reply.writeNoException();
	            reply.writeInt(result);
	 */

	if(code == 3){
		try{
			Log.i("TTT", "code:"+code);
			data.enforceInterface("android.app.IActivityManager");
			IBinder b = data.readStrongBinder();
			Log.i("TTT", "binder:"+b.getInterfaceDescriptor());
			String callingPackage = data.readString();
			Log.i("TTT","pkg:"+callingPackage);
			Intent intent = Intent.CREATOR.createFromParcel(data);
			Log.i("TTT", "intent:"+intent);
			if(intent != null){
				Log.i("TTT", "data:"+intent.getData());
				Log.i("TTT", "type:"+intent.getType());
			}
			String resolvedType = data.readString();
			Log.i("TTT", "resolvedType:"+resolvedType);
		}catch(Exception e){
			Log.i("TTT", "error:"+Log.getStackTraceString(e));
		}
	}

	return mBinder.transact(code, data, reply, flags);
}


DemoInject3项目的下载地址:


第四、运行程序

我们得到上面的三个文件:

poison; libproxybinder.so; DemoInject3.apk

下载地址:


将poison和libproxybinder.so两个文件放到data/data目录下

adb push poison /data/data

adb push libproxybinder.so /data/data

将DemoInject3.apk存放到/data/local/tmp目录下

adb push DemoInject3.apk /data/local/tmp


运行:

先找到system_server的进程id,然后注入

然后到/data/data/目录下

运行poison


这时候我们使用  adb logcat -s TTT  查看log:

注入成功,那么这时候我们打开几个App看看log:

看到log之后,我们可以看到我们的拦截成功了,我们可以得到启动Activity的Intent内容和App的包名信息等。

多么激动的一天,拦截成功之后,我们什么事都可以干了。这个实现应用锁的效率会高很多的。但是这个也是有一个弊端,需要root。


第五、拦截系统安装界面,实现自己的安装界面

那么这个技术除了实现应用锁,还有其他什么用途呢?

下面我们来看一下360卫士的自定义安装界面的实现,先来看看他的效果:

我们看到他能够替换系统的安装界面,自己定义的安装界面。那么我们现在也是可以实现的。我们现在打开一个apk安装:

这里我们可以看到Intent中携带的信息,和type类型,pkg是安装来源的App的包名,Intent中有待安装的apk路径,有了路径之后,我们就可以得到这个apk的信息。那么这个安装界面的实现就不难了。

当然这个问题困扰了我很长时间,也是这个问题驱使我义无反顾的来解决这个问题。360的技术很不了的。一个360卫士的Apk我们学到的东西很多,而且可能学不完。后面我还有一篇文章专门来解读360手机卫士的技术点实现。


第六、总结

今天就介绍了如何通过注入技术来实现拦截Activity的启动,这个技术的实现,能够解决我们很多问题,当然这个注入技术是需要root的,所以这里只是理论介绍他可以来实现应用锁。但是我们在实际项目中,不会采用这个技术的,原因很简单,root是一个可以研究的技术,但是本人坚决反对使用这个技术来开发产品,因为这个技术是和Google开发者相违背的。这么做是不正确的。当然只是个人观点。不过后面我还会介绍一种技术来实现应用锁技术:辅助功能(AccessibilityService)。