Hook技术实现免注册和登录验证的插件化框架

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,步骤有三:

  1. 寻找hook点:寻找类中的静态变量,或单例,这样能保证一旦对象创建好了,基本不会变化了
  2. 选择代理方式:动态代理或静态代理。对象的类型是接口,可以使用动态代理,如下面的IActivityManager接口;若内部有回调函数,可以直接使用静态代理,如下面的H继承了Handler,而Handler里有Callback回调函数。
  3. 替换成自己修改的对象

一个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();
    }
}
阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页