Hook技术实现免注册和登录验证的插件化框架
一、功能介绍
此插件化框架是Droidplugin的简化版,也是核心部分。主要实现两个功能:
- 启动无注册的Activity(没有在AndroidManifest.xml注册过的)
- 自动登录验证
其中,自动登录验证的过程:是对必须要登录才能查看的界面,在进入界面前,先验证是否登录。若已登录,则直接进入;否则,跳转到登录界面,待登录成功后,再自动跳转到目标界面。
涉及的技术,也主要有两点:
- 占坑,或称代理Activity,用于欺骗AMS,绕过它的检查
- Hook,通过动态/静态代理实现业务功能
二、占坑
这里的无注册Activity,都是简单的Standard启动模式。真正的插件化,需根据实际项目需求来占坑,如:四大组件,其中Activity又需要不同的进程及四种启动模式。
这里,只要一个默认的Standard启动模式即可:
AndroidManifest.xml
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
...
// 占坑
<activity android:name=".hooklogin.ProxyActivity" />
</application>
三、Hook
Hook,翻译过来是钩子的意思,原理就是要去系统内部,把自己的钩子函数挂入,从而加工或修改原函数的功能。
hook,步骤有三:
- 寻找hook点:寻找类中的静态变量,或单例,这样能保证一旦对象创建好了,基本不会变化了
- 选择代理方式:动态代理或静态代理。对象的类型是接口,可以使用动态代理,如下面的IActivityManager接口;若内部有回调函数,可以直接使用静态代理,如下面的H继承了Handler,而Handler里有Callback回调函数。
- 替换成自己修改的对象
一个Activity启动另一个Activity,最简单的方式就是startActivity(intent),由它一步一步去寻找hook点
1 寻找hook点
这里只展示最重要的代码
Activity.java
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options) {
...
Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
...
}
Instrumentation.java
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {
...
int result = ActivityManager.getService().startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
...
}
ActivityManager.java
/**
* @hide
*/
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
ActivityManager.getService():是一个静态方法,内不返回的值是一个静态变量,这就是hook点
这里通过匿名内部类Singleton产生了静态变量IActivityManagerSingleton。create()内部通过IBinder客户端来产生IActivityManager单例对象。
Singleton是代表单例模式,一个抽象类,通过泛型T来确定单例的类型
有些SDK里找不到源码,可参考这里
Singleton.java
public abstract class Singleton<T> {
private T mInstance;
protected abstract T create();
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
Singleton里的泛型IActivityManager.java
public interface IActivityManager extends IInterface {
public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags,
ProfilerInfo profilerInfo, Bundle options) throws RemoteException;
}
我们就可以在接口IActivityManager里的startActivity()方法里做些事情,如:
判断要跳转的目标Activity,是否是未注册的Activity。如果是没注册的,则替换为ProxyActivity
2 动态代理
这里的IActivityManager是接口,肯定没有回调,只能选用动态代理,并替换掉原对象
具体代码实现:
HookUtils.java
public class HookUtils {
private Context mContext;
public HookUtils(Context context) {
this.mContext = context;
}
/**
* Hook startActivity,主要用来瞒天过海,通过ProxyActivity来欺骗AMS(否则会报未注册的错误)
*/
public HookUtils hookStartActivity() {
try {
/*
通过ActivityManager里的getService()方法获取IActivityManager
通过反射获取静态成员IActivityManagerSingleton,并使用代理对象替换掉
*/
// 1. 获取Singleton的值:ActivityManager类中,反射获取静态成员变量IActivityManagerSingleton的值
Field fieldIActivityManagerSingleton;
Object singletonObj;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 6.0+(sdk26) 不一样
Class<?> activityManagerCls = ActivityManager.class;
fieldIActivityManagerSingleton = activityManagerCls.getDeclaredField("IActivityManagerSingleton");
fieldIActivityManagerSingleton.setAccessible(true);
// 静态变量,对象直接传null即可
singletonObj = fieldIActivityManagerSingleton.get(null);
} else {
Class<?> activityManagerNativeCls = Class.forName("android.app.ActivityManagerNative");
fieldIActivityManagerSingleton = activityManagerNativeCls.getDeclaredField("gDefault");
fieldIActivityManagerSingleton.setAccessible(true);
singletonObj = fieldIActivityManagerSingleton.get(null);
}
// 2. 获取IActivityManager实例对象:Singleton<IActivityManager>接口中通过字段mInstance获取
Class<?> singletonCls = Class.forName("android.util.Singleton");
Field mInstanceField = singletonCls.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
Object iActivityManagerOld = mInstanceField.get(singletonObj);
// 3. 创建IActivityManager的动态代理
Class<?> iActivityManagerCls = Class.forName("android.app.IActivityManager");
InvocationHandler proxyHandler = new StartActivityProxyHandler(iActivityManagerOld);
Object iActivityManagerProxy = Proxy.newProxyInstance(
// 类加载器ClassLoader
Thread.currentThread().getContextClassLoader(),
// 被代理类实现的接口集合
new Class[]{iActivityManagerCls},
// 集中的处理类,主要的逻辑处理
proxyHandler);
// 4. 使用动态代理,把IActivityManagerSingleton成员变量中的IActivityManager,替换成动态代理对象
mInstanceField.set(singletonObj, iActivityManagerProxy);
} catch (Exception e) {
e.printStackTrace();
}
return this;
}
private class StartActivityProxyHandler implements InvocationHandler {
private Object iActivityManagerObject;
StartActivityProxyHandler(Object iActivityManagerObject) {
this.iActivityManagerObject = iActivityManagerObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
logD("InvocationHandler: invoke method name=%s", name);
Object resultVal;
if ("startActivity".equals(name)) {
// 1. 执行前调用
logD("InvocationHandler: before invoke...");
// 2. 业务逻辑处理
// 判断是否为未注册的Activity
Intent orgIntent = null;
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
orgIntent = (Intent) args[i];
index = i;
break;
}
}
ComponentName component = orgIntent.getComponent();
String className = component.getClassName();
// 如果是这些未注册的Activity,全部使用代理
if (LoginActivity.class.getName().equals(className)
|| FirstActivity.class.getName().equals(className)
|| SecondActivity.class.getName().equals(className)
|| ThirdActivity.class.getName().equals(className)){
// 把意图定向到代理界面,从而绕过AMS的检查
// InvocationHandler: className=com.zjun.demo.hooklogin.LoginActivity
logD("InvocationHandler: className=%s", LoginActivity.class.getName());
// 第一个是应用包名,第二个是包含全路径的类名
ComponentName componentName = new ComponentName("com.zjun.demo", ProxyActivity.class.getName());
// 或使用 上下文 + 类
// ComponentName componentName = new ComponentName(mContext, LoginActivity.class);
Intent newIntent = new Intent();
newIntent.setComponent(componentName);
// 把原意图,隐藏到新意图中
newIntent.putExtra("originalIntent", orgIntent);
args[index] = newIntent;
}
// 3. 调用原方法执行
resultVal = method.invoke(iActivityManagerObject, args);
// 4. 执行后调用
logD("InvocationHandler: after invoke...");
} else {
resultVal = method.invoke(iActivityManagerObject, args);
}
return resultVal;
}
}
}
四、Hook启动Activity
在startActivity()执行后,也就是经过了一系列的AMS的检查后,AndroidThread内部会通过发送消息给Handler,让Handler开始真正地启动Activity。
ActivityThread.java
// 这是一个App的入口函数,启动了一个ActivityThread
public static void main(String[] args) {
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
Security.addProvider(new AndroidKeyStoreProvider());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
private static ActivityThread sCurrentActivityThread;
private void attach(boolean system) {
sCurrentActivityThread = this;
...
}
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
PersistableBundle persistentState, List<ResultInfo> pendingResults,
List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
ProfilerInfo profilerInfo) {
...
ActivityClientRecord r = new ActivityClientRecord();
r.intent = intent;
...
sendMessage(H.LAUNCH_ACTIVITY, r);
}
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
...
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
...
}
}
handleLaunchActivity()是真正要启动Activity的函数,所以我们要赶在调用它之前做业务逻辑处理,Handler中有个分发事件的函数,内部有回调函数,供我们做业务处理
Handler.java
// 分发事件
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
final Callback mCallback;
public interface Callback {
public boolean handleMessage(Message msg);
}
因此,只要我们设置mCallback回调接口,就可以在内部函数handleMessage(Message msg)中处理业务,也就是修改msg.obj的数据。最后返回false,让Handler调用handleLaunchActivity()
最后的代码实现:
HookUtils.java
public class HookUtils {
...
/**
* hook LaunchActivity
*/
public void hookLaunchActivity() {
try {
// 获取ActivityThread实例
Class<?> activityThreadCls = Class.forName("android.app.ActivityThread");
Field sCurrentActivityThreadField = activityThreadCls.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThreadField.setAccessible(true);
Object mCurrentActivityThread = sCurrentActivityThreadField.get(null);
// 获取ActivityThread里的mH对象
Field mHField = activityThreadCls.getDeclaredField("mH");
mHField.setAccessible(true);
Handler mH = (Handler) mHField.get(mCurrentActivityThread);
// 使用静态代理,替换接口mCallback
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
mCallbackField.set(mH, new HandlerCallback());
} catch (Exception e) {
e.printStackTrace();
}
}
private class HandlerCallback implements Handler.Callback{
HandlerCallback() {
}
@Override
public boolean handleMessage(Message msg) {
// LAUNCH_ACTIVITY ==100 即将要加载一个activity了
if (msg.what == 100) {
handleLaunchActivity(msg);
}
// 不消费掉事件,最后还是要交还给mH自己去打开activity
return false;
}
/**
* 业务逻辑的处理
*/
private void handleLaunchActivity(Message msg) {
try {
// 获取Intent(msg.obj属于类ActivityClientRecord)
Field intentField = msg.obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent intent = (Intent) intentField.get(msg.obj);
// 获取要Intent要跳转的Activity名称
String className = intent.getComponent().getClassName();
logD("handleLaunchActivity: className=%s", className);
// 非代理Activity,无需处理
if (!ProxyActivity.class.getName().equals(className)) {
return;
}
// 获取原意图
Intent realIntent = intent.getParcelableExtra("originalIntent");
String realClassName = realIntent.getComponent().getClassName();
logD("handleLaunchActivity: realClassName=%s", realClassName);
// 还原真实intent: ComponentName和Bundle(可能携带数据)
intent.setComponent(realIntent.getComponent());
intent.putExtras(realIntent.getExtras());
// 对要登录的界面进行校验
// FirstActivity.class.getName():com.zjun.demo.hooklogin.SecondActivity
if (FirstActivity.class.getName().equals(realClassName)
// || SecondActivity.class.getName().equals(realClassName) // 不需要登录
|| ThirdActivity.class.getName().equals(realClassName)) {
// 判断是否登录
SharedPreferences sp = mContext.getSharedPreferences("hookLogin", Context.MODE_PRIVATE);
boolean isLogin = sp.getBoolean("isLogin", false);
if (!isLogin) {
// 未登录:跳到LoginActivity,并把要实际要跳转的intent,封装到内部
ComponentName loginComponent = new ComponentName(mContext, LoginActivity.class);
intent.setComponent(loginComponent);
intent.putExtra("extraRealIntent", realClassName);
logD("handleLaunchActivity: did't login, should redirect to LoginActivity firstly");
}
}
// 兼容AppCompatActivity报错问题:android.content.pm.PackageManager$NameNotFoundException
Class<?> activityThreadCls = Class.forName("android.app.ActivityThread");
Field field = activityThreadCls.getDeclaredField("sCurrentActivityThread");
field.setAccessible(true);
Object sCurrentActivityThread = field.get(null);
// 我自己执行一次那么就会创建PackageManager,系统再获取的时候就是下面的iPackageManager
Method getPackageManager = activityThreadCls.getDeclaredMethod("getPackageManager");
Object iPackageManager = getPackageManager.invoke(sCurrentActivityThread);
PackageManagerHandler handler = new PackageManagerHandler(iPackageManager);
Class<?> iPackageManagerIntercept = Class.forName("android.content.pm.IPackageManager");
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[]{iPackageManagerIntercept}, handler);
// 获取 sPackageManager 属性
Field iPackageManagerField = activityThreadCls.getDeclaredField("sPackageManager");
iPackageManagerField.setAccessible(true);
iPackageManagerField.set(sCurrentActivityThread, proxy);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private class PackageManagerHandler implements InvocationHandler{
private Object iPackageManager;
PackageManagerHandler(Object iPackageManager) {
this.iPackageManager = iPackageManager;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().startsWith("getActivityInfo")) {
ComponentName componentName = new ComponentName(mContext, ProxyActivity.class);
args[0] = componentName;
}
return method.invoke(iPackageManager, args);
}
}
private static void logD(String format, Object... args) {
Log.d("HookUtils", "zjun@" + String.format(format, args));
}
}
五、调用执行
因为是hook系统内部函数,所以应该在Application启动时就调用:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
new HookUtils(getApplicationContext()).hookStartActivity().hookLaunchActivity();
}
}