FastHook——如何使用FastHook免root hook微信

4,139 阅读4分钟

一、概述

本文介绍如何通过FastHook + VirtualApp实现免root hook。由于VirtualApp已经不更新了,所以本文只作为一个教程,并不主要解决一些兼容和稳定性问题。

项目地址:VirtualFastHook:github.com/turing-tech…

二、实现原理

要实现应用hook,可以简单的分为下面三个步骤: 1. 识别Hook插件 2. 保存Hook插件信息 3. 获取Hook插件信息进行Hook

2.1 识别Hook插件

规定Hook插件须在AndroidManifest.xml里定义三个meta-data

<meta-data
        android:name="fasthook.hook.plugin"
        android:value="true"/>
        
<meta-data
        android:name="fasthook.hook.process"
        android:value="XXX"/>
        
<meta-data
        android:name="fasthook.hook.info"
        android:value="XXX"/>

1. fasthook.hook.plugin:表示这是一个Hook插件

2. fasthook.hook.process:表示要Hook的进程

3. fasthook.hook.info:表示Hook信息类名

可以在VirtualApp解析Apk时加上判断是否为Hook插件的代码,例如在AppRepository.java

private List<AppInfo> convertPackageInfoToAppData(Context context, List<PackageInfo> pkgList, boolean fastOpen) {
        PackageManager pm = context.getPackageManager();
        List<AppInfo> list = new ArrayList<>(pkgList.size());
        String hostPkg = VirtualCore.get().getHostPkg();
        for (PackageInfo pkg : pkgList) {
            // ignore the host package
            if (hostPkg.equals(pkg.packageName)) {
                continue;
            }
            // ignore the System package
            if (isSystemApplication(pkg)) {
                continue;
            }
            boolean isHookPlugin = false;
            //start 判断是否是Hook插件
            ApplicationInfo ai = null;
            try {
                ai = context.getPackageManager().getApplicationInfo(pkg.packageName,PackageManager.GET_META_DATA);
                if(ai.metaData != null) {
                    boolean enable = ai.metaData.getBoolean("fasthook.hook.plugin", false);
                    if(enable) {
                        String hookProcess = ai.metaData.getString("fasthook.hook.process","");
                        String hookInfo = ai.metaData.getString("fasthook.hook.info","");
                        if(!hookProcess.isEmpty() || !hookInfo.isEmpty()) {
                            isHookPlugin = true;
                        }
                    }
                }
            }catch (Exception e) {
                e.printStackTrace();
            }
            //end 判断是否是Hook插件
            String path = ai.publicSourceDir != null ? ai.publicSourceDir : ai.sourceDir;
            if (path == null) {
                continue;
            }
            AppInfo info = new AppInfo();
            info.packageName = pkg.packageName;
            info.fastOpen = fastOpen;
            info.path = path;
            info.icon = ai.loadIcon(pm);
            info.name = ai.loadLabel(pm);
            info.isHook = isHookPlugin;
            InstalledAppInfo installedAppInfo = VirtualCore.get().getInstalledAppInfo(pkg.packageName, 0);
            if (installedAppInfo != null) {
                info.cloneCount = installedAppInfo.getInstalledUsers().length;
            }
            list.add(info);
        }
        return list;
    }

2.2 如何获取Hook插件

在apk安装时,可以把Hook插件保存起来,例如在VAppManagerService.java

public synchronized InstallResult installPackage(String path, int flags, boolean notify) {
        //无关代码
        boolean isHook = (flags & InstallStrategy.IS_HOOK) != 0;
        //无关代码
        if (res.isUpdate) {
            FileUtils.deleteDir(libDir);
            VEnvironment.getOdexFile(pkg.packageName).delete();
            if(isHook) {
                VActivityManagerService.get().killAllApps();
            }
            else {
                VActivityManagerService.get().killAppByPkg(pkg.packageName, VUserHandle.USER_ALL);
            }
        }
        //无关代码
        PackageSetting ps;
        if (existSetting != null) {
            ps = existSetting;
        } else {
            ps = new PackageSetting();
        }
        ps.isHook = isHook;
        ps.dependSystem = dependSystem;
        ps.apkPath = packageFile.getPath();
        ps.libPath = libDir.getPath();
        ps.packageName = pkg.packageName;
        ps.appId = VUserHandle.getAppId(mUidSystem.getOrCreateUid(pkg));
        if (res.isUpdate) {
            ps.lastUpdateTime = installTime;
        } else {
            ps.firstInstallTime = installTime;
            ps.lastUpdateTime = installTime;
            for (int userId : VUserManagerService.get().getUserIds()) {
                boolean installed = userId == 0;
                ps.setUserState(userId, false/*launched*/, false/*hidden*/, installed);
            }
        }
        //无关代码
        //保存Hook插件信息
        if(isHook) {
            HookCacheManager.HookCacheInfo info = new HookCacheManager.HookCacheInfo(ps.packageName,(String)(pkg.mAppMetaData.get(HookCacheManager.HOOK_PROCESS)),(String)(pkg.mAppMetaData.get(HookCacheManager.HOOK_INFO)));
            HookCacheManager.put((String)(pkg.mAppMetaData.get(HookCacheManager.HOOK_PROCESS)),info);
        }
        else if (notify) {
            notifyAppInstalled(ps, -1);
        }
        res.isSuccess = true;
        return res;
    }

2.3 如何Hook

实际可以在任意地方Hook,但为了更好的Hook,这里在应用apk加载之后,attachBaseContext方法调用之前进行Hook,这样便可以Hook所有应用的方法了。例如在VClientImpl.java

private void bindApplicationNoCheck(String packageName, String processName, ConditionVariable lock) {
        //无关代码
        NativeEngine.launchEngine();
        Object mainThread = VirtualCore.mainThread();
        NativeEngine.startDexOverride();
        Context context = createPackageContext(data.appInfo.packageName);
        System.setProperty("java.io.tmpdir", context.getCacheDir().getAbsolutePath());

	//无关代码

	Object boundApp = fixBoundApp(mBoundApplication);
        mBoundApplication.info = ContextImpl.mPackageInfo.get(context);
        mirror.android.app.ActivityThread.AppBindData.info.set(boundApp, data.info);
        VMRuntime.setTargetSdkVersion.call(VMRuntime.getRuntime.call(), data.appInfo.targetSdkVersion);

	//进行Hook
        try {
            tryHook(processName,context.getClassLoader());
        }catch (Exception e) {
            e.printStackTrace();
        }

	//无关代码

	VirtualCore.get().getComponentDelegate().beforeApplicationCreate(mInitialApplication);
	 try {
            mInstrumentation.callApplicationOnCreate(mInitialApplication);
            InvocationStubManager.getInstance().checkEnv(HCallbackStub.class);
            if (conflict) {
                InvocationStubManager.getInstance().checkEnv(AppInstrumentation.class);
            }
            Application createdApp = ActivityThread.mInitialApplication.get(mainThread);
            if (createdApp != null) {
                mInitialApplication = createdApp;
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(mInitialApplication, e)) {
                throw new RuntimeException(
                        "Unable to create application " + mInitialApplication.getClass().getName()
                                + ": " + e.toString(), e);
            }
        }
        VActivityManager.get().appDoneExecuting();
        VirtualCore.get().getComponentDelegate().afterApplicationCreate(mInitialApplication);
    }
   
    //根据进程名获取Hook插件并Hook
    private void tryHook(String process, ClassLoader apkClassLoader) {
        String[] infos = VPackageManager.get().getInstalledHookPlugins(process);
        if(infos != null) {
            for(String info : infos) {
                int size = info.charAt(0);
                String pluginName = info.substring(1,1 + size);
                String hookInfoName = info.substring(1 + size);

		DexClassLoader hookClassLoader = new DexClassLoader(VEnvironment.getPackageResourcePath(pluginName).getAbsolutePath(),
                        VEnvironment.getDalvikCacheDirectory().getAbsolutePath(),
                        VEnvironment.getPackageLibPath(pluginName).getAbsolutePath(),
                        apkClassLoader);

		FastHookManager.doHook(hookInfoName,hookClassLoader,apkClassLoader,hookClassLoader,hookClassLoader,false);
            }
        }
    }

三、Hook微信

3.1 准备一个Hook插件

根据FastHook框架要求,提供一下信息:

1. HookMethodInfo.java(Hook方法、Forward方法具体实现)

public class HookMethodInfo {
    public static void hook(Object thiz, Context context) {
        Log.d("FastHookManager","hook attachBaseContext2");
        forward(thiz,context);
        Toast toast = Toast.makeText(context,"hook attachBaseContext2",Toast.LENGTH_LONG);
        toast.show();
    }
    
    public native static void forward(Object thiz, Context context);
}

可以看到Hook方法的逻辑很简单,只是弹出一个Toast,内容为hook attachBaseContext2

2. HookInfo.java(根据FastHook框架规定提供HOOK_ITEMS信息)

public class HookInfo {
    public static String[][] HOOK_ITEMS = {
            {"1",
                    "com.tencent.tinker.loader.app.TinkerApplication","attachBaseContext","Landroid/content/Context;",
                    "com.example.fasthookplugin.HookMethodInfo","hook","Ljava/lang/Object;Landroid/content/Context;",
                    "com.example.fasthookplugin.HookMethodInfo","forward","Ljava/lang/Object;Landroid/content/Context;"}
    };
}

使用的是Inline模式,Hook的是attachBaseContext方法,这是应用被系统调用的第一个方法。

3. 配置AndroidManifest.xml(配置Hook插件信息)

<application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <meta-data
            android:name="fasthook.hook.plugin"
            android:value="true"/>

	<meta-data
            android:name="fasthook.hook.process"
            android:value="com.tencent.mm"/>
	
	<meta-data
            android:name="fasthook.hook.info"
            android:value="com.example.fasthookplugin.HookInfo"/>
	<activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

		<category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

配置apk为Hook插件,Hook的目标进程为com.tencent.mm即微信主进程,具体Hook信息位于HookInfo类HOOK_ITEMS数组。

三、实际效果

安装上VirtualFastHook和Hook插件后,运行看看实际效果

VirtualFastHook主界面.png
安装Hook插件.png

运行微信.png

Hook成功.png
所有的Hook插件左上角都有一个红色小图标,代表该应用为Hook插件

四、结语

上述只是一个基本Hook操作,实际还可以做出更多有用的功能,下面这个是我随手做的7.0.3版本微信消息防撤回

接受消息.png
阻止对方撤回消息.png

对方显示消息已撤回.png

参考

FastHook——一种高效稳定、简洁易用的Android Hook框架

FastHook:github.com/turing-tech…

VirtualApp:github.com/asLody/Virt…

FastHook系列

  1. FastHook——一种高效稳定、简洁易用的Android Hook框架
  2. FastHook——巧妙利用动态代理实现非侵入式AOP
  3. FastHook——远超YAHFA的优异稳定性
  4. FastHook——实现.dynsym段和.symtab段符号查询