(转载公司内部论坛本人文章2020.7.6)
xposed原理
Android系统中,Zygote进程在启动的过程中,会创建一个Dalvik虚拟机实例,同时将Java运行时库加载到进程中来,以及注册一些Android核心类的JNI方法到Dalvik虚拟机实例中去。
而我们知道,系统其它所有进程都是由Zygote进程孵化的。Zygote在启动新的app进程时,都会将自身的Dalvik虚拟机实例复制到该App进程里,形成该App独立的Dalvik虚拟机实例,同时还会与Zygote进程一起共享Java运行时库。所以Xposed通过替换/system/bin/app_process程序控制zygote进程,使得app_process在启动过程中会加载XposedBridge.jar这个jar包,也进而使XposedBridge.jar加载到了每一个Android app进程里。
Xposed可以实现在不修改APK源码的情况下,hook app里的任意java方法,实现方法的修改和替换。
Xposed简单使用教程
手机可以通过 Xposed Installer
安装Xposed服务,但是需要ROOT权限。如果不想ROOT手机,也可以通过安装一个自带Xposed的虚拟系统,效果也是一样的。
想要hook apk的方法,首先要知道apk内该方法的具体路径,这个涉及到 apk反编译
的过程,这里不做细说。
通过Xposed Hook方法主要运用到 XposedHelpers.findAndHookMethod()
方法,demo代码如下:
public class Hooktest implements IXposedHookLoadPackage {
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
if (loadPackageParam.packageName.equals("想要hook apk的包名")) {
Class clazz = loadPackageParam.classLoader.loadClass("想要hook的类名");
XposedHelpers.findAndHookMethod(clazz, "想要hook的方法名", new XC_MethodHook() {
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
}
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
param.setResult("修改返回结果");
}
});
}
}
}
具体过程请查看教程。
Xposed检测与防护
Xposed的防护思路:App检测系统是否安装了Xposed模块,一旦检测到,便强制退出App。
因此Xposed防护的重点在于如何检测到系统是否安装了Xposed模块。
Xposed检测可以分为Java层和Native层:
Java层检测
1.检测系统是否安装了Xposed Installer软件包
通过PackageManager遍历系统中App的安装情况,判断系统是否有安装Xposed Installer相关的软件包。
public static boolean hasXposed(Context context) {
PackageManager packageManager = context.getPackageManager();
List<ApplicationInfo> applicationInfoList = packageManager.getInstalledApplications(PackageManager.GET_META_DATA);
for (ApplicationInfo applicationInfo: applicationInfoList) {
if (applicationInfo.packageName.equals("de.robv.android.xposed.installer")) {
return true;
}
}
return false;
}
2.自抛异常,读取堆栈信息判断
如果系统安装了Xposed Installer框架,程序方法异常栈中就会出现Xposed的 de.robv.android.xposed.XposedHelpers
和 de.robv.android.xposed.XposedBridge
相关日志。因此可以通过自抛异常并Catch来读取异常堆栈信息,检测系统是否安装了Xposed Installer
public static boolean isXposedExistByThrow() {
try {
throw new Exception("gg");
} catch (Exception e) {
for (StackTraceElement stackTraceElement : e.getStackTrace()) {
if (stackTraceElement.getClassName().contains("de.robv.android.xposed.XposedHelpers")) {
return true;
} else if (stackTraceElement.getClassName().contains("de.robv.android.xposed.XposedBridge")){
return true;
}
}
return false;
}
}
3.检查关键Java方法被变为Native JNI方法
如果App中的Java方法变成了Native JNI方法,则非常有可能被Xposed Hook了。因此,检查关键方法是不是变成Native JNI方法,也可以检测系统是否安装了Xposed Installer框架。
public boolean isXposedMethod() {
try {
Method method = getClass().getDeclaredMethod("方法名");
return Modifier.isNative(method.getModifiers());
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return false;
}
但是Xposed同样可以篡改isNative这个方法的返回值,让其返回false。
4.尝试反射XposedHelper类字段,判断是否ClassNotFoundException
public static boolean isXposedExists() {
try {
ClassLoader.getSystemClassLoader().loadClass("de.robv.android.xposed.XposedHelpers").newInstance();
ClassLoader.getSystemClassLoader().loadClass("de.robv.android.xposed.XposedBridge").newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
return true;
} catch (IllegalAccessException e) {
e.printStackTrace();
return true;
} catch (ClassNotFoundException e) {
e.printStackTrace();
return false;
}
return true;
}
总结: Java层检测Xposed方法有很多,但是前面我提到过,Xposed可以hook app里的任意java方法,自然也可以修改我们检测方法isXposedExists的返回值,所以理论上Java层的防护是没有太大作用。
Native层检测
无论在Java层做何种检测,Xposed都可以通过Hook相关检测方法,修改检测结果绕过检测,所以仅Java层检测是不够的。
但是前面我们提到,xposed hook方法还需要知道该方法的具体路径。因此我们把检测代码放在so库中,这样可以增加反编译拿到检测代码的位置的成本。同时由于Xposed Installer通常只能Hook Java层,这样也能避免检测方法被直接hook。
extern "C"
JNIEXPORT void
JNICALL
Java_com_tencent_signndk_Sign_sign(JNIEnv *env, jobject instance)
{
FILE *fp = NULL;
int BUFFER_SIZE = 1024;
char strLine[BUFFER_SIZE];
char* filepath = "/proc/self/maps";
char* xp_name = "XposedBridge.jar";
fp = fopen(filepath,"r");
while (!feof(fp))
{
fgets(strLine,BUFFER_SIZE,fp);
char* origin_str = strLine;
char* str = trim(origin_str);
if(strstr(str, xp_name) != NULL)
// if (contain(str,xp_name))
{
jclass jcls = env->FindClass("java/lang/IllegalArgumentException");
env->ThrowNew(jcls, "Argument cannot be null.");
break;
}
}
}
其他
在安全对抗领域,攻防都是一个长期的过程。如果只是客户端的防护是不够的,就这个wifi信息泄露这个例子,我们也进行了后台层面的防护,返回wifi信息的接口做了更加严格的判断。
END。
参考:
https://tech.meituan.com/2018/02/02/android-anti-hooking.html