1. 问题描述

手机刷入 Magisk 等 Root 工具后,某些应用会因为检测到环境异常而无法运行。虽然一些环境隐藏方案可以解决大部分问题,但是仍有某些难以被隐藏的环境问题,比如手机刷入了第三方 ROM LineageOS等,能检测出 LineageOS 的手段有很多难以防止。所以这就需要针对特定的 app 去查看它到底检测了哪些环境问题,从而进行针对性的解决。

项目地址: https://github.com/lxmghct/xposed-env-logger

2. 实现思路

我打算仅针对自己的特殊需求来开发这样一个工具,而并不是覆盖所有的环境检测场景。我目前 Hook 了下面的几个 API 来检测环境问题:

  • android.os.SystemProperties.get(String key):获取系统属性
  • java.lang.System.getProperty(String key):获取系统属性
  • java.io.File 的三个构造函数:创建文件对象
  • java.io.File.exists():检查文件是否存在
  • java.io.FileInputStream(File file):打开文件输入流
  • java.io.FileReader(String path):打开文件读取器

3. xposed 开发环境搭建

  1. 在 settings.gradle 中添加 xposed 仓库
    dependencyResolutionManagement {
     repositories {
         maven { url 'https://api.xposed.info/' }
     }
    }
    
  2. 在 app/build.gradle 中添加 xposed 依赖
    dependencies {
     compileOnly 'de.robv.android.xposed:api:82'
    }
    
  3. 在 AndroidManifest.xml 中添加 xposed 标识
     <meta-data
         android:name="xposedmodule"
         android:value="true"/>
    
     <meta-data
         android:name="xposeddescription"
         android:value="@string/xposed_description"/>
    
     <meta-data
         android:name="xposedminversion"
         android:value="54"/>
    
     <meta-data
         android:name="xposedscope"
         android:resource="@array/xposedscope"/>
    
  4. 在 assets 目录下创建 xposed_init 文件,内容为包名+类名
    com.example.envlogger.MainHook
    

4. 代码实现

package com.example.envlogger;

import java.io.File;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class MainHook implements IXposedHookLoadPackage {

    private void writeLog(String msg) {
        XposedBridge.log("【EnvLogger】 " + msg);
    }

    @Override
    public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {

        XposedBridge.log("Hooking " + lpparam.packageName);

        // Hook SystemProperties.get(String)
        XposedHelpers.findAndHookMethod("android.os.SystemProperties", lpparam.classLoader,
                "get", String.class, new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        String key = (String) param.args[0];
                        String value = (String) param.getResult();
                        writeLog("[SystemProperties.get] " + key + " = " + value);
                    }
                });

        // Hook System.getProperty(String)
        XposedHelpers.findAndHookMethod("java.lang.System", lpparam.classLoader,
                "getProperty", String.class, new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        String key = (String) param.args[0];
                        String value = (String) param.getResult();
                        writeLog("[System.getProperty] " + key + " = " + value);
                    }
                });

        // Hook File(String)
        XposedHelpers.findAndHookConstructor("java.io.File", lpparam.classLoader,
                String.class, new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        String path = (String) param.args[0];
                        writeLog("[File] new File: " + path);
                    }
                });

        // File(String parent, String child)
        XposedHelpers.findAndHookConstructor("java.io.File", lpparam.classLoader,
                String.class, String.class, new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                String parent = (String) param.args[0];
                String child = (String) param.args[1];
                writeLog("[File] new File: parent=" + parent + " child=" + child);
            }
        });

        // File(File parent, String child)
        XposedHelpers.findAndHookConstructor("java.io.File", lpparam.classLoader,
                java.io.File.class, String.class, new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                File parent = (File) param.args[0];
                String child = (String) param.args[1];
                writeLog("[File] new File: parent=" + parent.getAbsolutePath() + " child=" + child);
            }
        });
        // Hook File.exists()
        XposedHelpers.findAndHookMethod("java.io.File", lpparam.classLoader,
        "exists", new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                File f = (File) param.thisObject;
                boolean result = (boolean) param.getResult();
                writeLog("[File.exists] " + f.getAbsolutePath() + " = " + result);
            }
        });

        // Hook FileInputStream(File)
        XposedHelpers.findAndHookConstructor("java.io.FileInputStream", lpparam.classLoader,
                java.io.File.class, new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        File file = (File) param.args[0];
                        writeLog("[FileInputStream] " + file.getAbsolutePath());
                    }
                });
        
        XposedHelpers.findAndHookConstructor("java.io.FileReader", lpparam.classLoader,
        String.class, new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                String path = (String) param.args[0];
                writeLog("[FileReader] " + path);
            }
        });
    }
}

5. 后续改进

  1. 可以考虑增加更多的 Hook 点,覆盖更多的环境检测手段。
  2. 输出日志到文件,方便后续分析。