一夜梦惊人 发表于 2019-8-17 21:43

记一次反Xposed的处理

前言:最近两天在搞一个App,由于某些问题我就不会公布了。在这App中看到一个有意思的反xposed的处理,觉得有意思就公布出来了。
正文:这个App混淆如同一坨屎,所有的string都需要经过解密,而且class name和method name是尽量一样就一样,上个截图表示意思意思。

经过本人发现,string有两种解密算法,不在同一个类。两种解密算法不在同一个类,一个类居然用apktool反编译出来后没有。。。{:301_1002:}初步猜想是类似于热修复那样,运行时动态加载到class loader中,只不过不是本文重点,所以就不深究。而另一个则是直接一大片的计算。

{:301_1004:}为了偷懒我就是直接用xposed查看一下。hook后运行没有超过5s就退出,查看logcat直接是一大片的错误。经过分析,是解密算法解析出现问题,不能正常解析从而导致的出错。在查看方法的途中,注意到一个关键的地方。
objArr = Class.forName(String.valueOf(cArr, 0, 16)).getMethod(String.valueOf(cArr, 16, 13), null);
      objArr = Class.forName(String.valueOf(cArr, 0, 16)).getMethod(String.valueOf(cArr, 29, 13), null);
      objArr = Class.forName(String.valueOf(cArr, 42, 16)).getMethod(String.valueOf(cArr, 58, 11), null);
      objArr = Class.forName(String.valueOf(cArr, 69, 27)).getMethod(String.valueOf(cArr, 96, 12), null);
      objArr = Class.forName(String.valueOf(cArr, 69, 27)).getMethod(String.valueOf(cArr, 108, 13), null);
      objArr = Integer.valueOf(oix(objArr));

反射获取一些东西,还运用到了这个oix方法。
private static final int oix(Object[] objArr) {
      Object[] objArr2 = (Object[]) objArr.invoke(objArr.invoke(null, null), null);
      int length = objArr2.length;
      for (int i = 1; i < length; i++) {
            StringBuilder sb = new StringBuilder();
            sb.append(objArr.invoke(objArr2, null));
            sb.append(objArr.invoke(objArr2, null));
            if (sb.toString().hashCode() == 180000400) {
                return i;
            }
      }
      return 0;
    }

好的,看到这里就可以知道,基本上是xposed导致某些地方变化从而解析失败。
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
      for (int i=0;i<stackTraceElements.length;i++){
            StringBuilder stringBuilder = new StringBuilder("");
            stringBuilder.append(stackTraceElements.getClassName());
            stringBuilder.append(stackTraceElements.getMethodName());
            if (stringBuilder.toString().hashCode() == 180000400)
                returni;
      }
      return void;
面对这种,可以直接Hook这个getStackTrace方法,把有关于这个xposed的全部去掉就可以,我也就不贴代码了。
}
      Object[] objArr = (Object[]) ((Method) Ec).invoke(((Method) Ec).invoke(null, null), null);
      StringBuilder sb = new StringBuilder();
      sb.append(((Method) Ec).invoke(objArr[((Integer) Ec).intValue()], null));
      int hashCode = sb.append(((Method) Ec).invoke(objArr[((Integer) Ec).intValue()], null)).toString().hashCode();

说句实话,我个人觉得比较新奇,所以就发出来了。
最后附加一个小知识点:xposed的find and hook method方法需要拿到class,但是由于加固或者其他一些原因导致class not found,这是常有的问题。给一点通用的办法,非本人原创,是virjar所写在ucrack中的(真心推荐UCrack,一款在逆反的方面的好帮手)。
下面贴下代码:
package com.yymjr.android.xposedpay;

import android.annotation.SuppressLint;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Process;
import android.util.Log;

import com.google.common.collect.Maps;
import com.virjar.xposed_extention.ClassLoadMonitor;
import com.virjar.xposed_extention.ReflectUtil;
import com.virjar.xposed_extention.SharedObject;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.ConcurrentMap;

import dalvik.system.BaseDexClassLoader;
import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.IXposedHookZygoteInit;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import de.robv.android.xposed.callbacks.XCallback;

import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;

public class MainHook implements IXposedHookLoadPackage, IXposedHookZygoteInit {
    private final static String TAG = "XposedPay-MainHook";
    private Context context;

    private volatile boolean hooked = false;

    private volatile boolean attached = false;

    @Override
    public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
      if (!lpparam.packageName.equals("com.eg.android.AlipayGphone")) return;
      if (!lpparam.isFirstApplication) {
            return;
      }
      if (hooked) {
            return;
      }
      hooked = true;
      findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook(XCallback.PRIORITY_HIGHEST * 2) {

            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                if (attached) {
                  return;
                }
                attached = true;
                context = (Context) param.args;
                hotLoadPlugin(lpparam.classLoader, context, lpparam);
                process();
            }
      });
    }

    @SuppressLint("PrivateApi")
    private void hotLoadPlugin(ClassLoader ownerClassLoader, Context context, XC_LoadPackage.LoadPackageParam lpparam) {

      if (hansInstantRun(MainHook.class.getClassLoader())) {
            Log.e("yymjr", "Cannot load module, please disable \"Instant Run\" in Android Studio.");
            return;
      }

      ClassLoader hotClassLoader = replaceClassloader(context, lpparam);
      if (hansInstantRun(hotClassLoader)) {
            Log.e("yymjr", "Cannot load module, please disable \"Instant Run\" in Android Studio.");
            return;
      }

      SharedObject.context = context;
      SharedObject.loadPackageParam = lpparam;

    }

    private void process(){
      Log.d(TAG, "Hooking Application Class");
      ClassLoadMonitor.addClassLoadMonitor("android.app.Application", new ClassLoadMonitor.OnClassLoader() {
            @Override
            public void onClassLoad(Class<?> clazz) {
                findAndHookMethod(clazz, "attach",new XC_MethodHook() {
                  @Override
                  protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        super.afterHookedMethod(param);
                  }
                });
            }
      });
    }

    private boolean hansInstantRun(ClassLoader classLoader) {
      Class<?> aClass = ReflectUtil.findClassIfExists(INSTANT_RUN_CLASS, classLoader);
      if (aClass != null) {
            return true;
      }
      aClass = ReflectUtil.findClassIfExists(INSTANT_RUN_CLASS_2, classLoader);
      return aClass != null;
    }

    private static ClassLoader replaceClassloader(Context context, XC_LoadPackage.LoadPackageParam lpparam) {
      ClassLoader classLoader = MainHook.class.getClassLoader();
      if (!(classLoader instanceof PathClassLoader)) {
            XposedBridge.log("classloader is not PathClassLoader: " + classLoader.toString());
            return classLoader;
      }

      //find real apk location by package name
      PackageManager packageManager = context.getPackageManager();
      if (packageManager == null) {
            XposedBridge.log("can not find packageManager");
            return classLoader;
      }

      PackageInfo packageInfo = null;
      try {
            packageInfo = packageManager.getPackageInfo(BuildConfig.APPLICATION_ID, PackageManager.GET_META_DATA);
      } catch (PackageManager.NameNotFoundException e) {
            //ignore
      }
      if (packageInfo == null) {
            XposedBridge.log("can not find plugin install location for plugin: " + BuildConfig.APPLICATION_ID);
            return classLoader;
      }

      return createClassLoader(classLoader.getParent(), packageInfo, context);
    }

    private static final String INSTANT_RUN_CLASS = "com.android.tools.fd.runtime.BootstrapApplication";
    private static final String INSTANT_RUN_CLASS_2 = "com.android.tools.ir.server.InstantRunContentProvider";
    private static ConcurrentMap<String, BaseDexClassLoader> classLoaderCache = Maps.newConcurrentMap();

    private static BaseDexClassLoader createClassLoader(ClassLoader parent, PackageInfo packageInfo, Context context) {
      if (classLoaderCache.containsKey(packageInfo.applicationInfo.sourceDir)) {
            return classLoaderCache.get(packageInfo.applicationInfo.sourceDir);
      }
      synchronized (MainHook.class) {
            if (classLoaderCache.containsKey(packageInfo.applicationInfo.sourceDir)) {
                return classLoaderCache.get(packageInfo.applicationInfo.sourceDir);
            }
            XposedBridge.log("create a new classloader for plugin with new apk path: " + packageInfo.applicationInfo.sourceDir);
            BaseDexClassLoader hotClassLoader = null;
            File ucrackOptimizeDir = new File(context.getFilesDir(), "ucrackDex");
            try {
                FileUtils.forceMkdir(ucrackOptimizeDir);
                hotClassLoader = new DexClassLoader(packageInfo.applicationInfo.sourceDir
                        , ucrackOptimizeDir.getAbsolutePath(),
                        packageInfo.applicationInfo.nativeLibraryDir,
                        parent);
            } catch (IOException e) {
                e.printStackTrace();
                hotClassLoader = new PathClassLoader(packageInfo.applicationInfo.sourceDir, parent);
            }
            classLoaderCache.putIfAbsent(packageInfo.applicationInfo.sourceDir, hotClassLoader);
            return hotClassLoader;
      }
    }

    private static final int DEBUG_ENABLE_DEBUGGER = 0x1;

    private XC_MethodHook debugAppsHook = new XC_MethodHook() {
      @Override
      protected void beforeHookedMethod(MethodHookParam param)
                throws Throwable {
            XposedBridge.log("-- beforeHookedMethod :" + param.args);
            int id = 5;
            int flags = (Integer) param.args;
            if ((flags & DEBUG_ENABLE_DEBUGGER) == 0) {
                flags |= DEBUG_ENABLE_DEBUGGER;
            }
            param.args = flags;
      }
    };

    @Override
    public void initZygote(StartupParam startupParam) throws Throwable {
      XposedBridge.hookAllMethods(Process.class, "start", debugAppsHook);
    }

}
最后面是让所有APP都是可调试状态,不需要的可以删除掉。
    implementation 'com.virjar:hermes-api:1.0.9'
记得在build.gradle中添上上面这个才可以。

最后打个广告
想要学习一些其他的xposed东西可以看看我另一篇文章:https://www.52pojie.cn/thread-873013-1-1.html没有咯{:301_1001:}虽然少,也就将就着看吧。

一夜梦惊人 发表于 2019-11-12 11:52

老中医w 发表于 2019-11-12 00:08
有一点没有太明白:
「基本上是xposed导致某些地方变化从而解析失败」,具体是什么地方呢?
看代码感觉像 ...

反射获取了栈堆,然后如果hook了这个方法就会导致栈堆中有xposed的存在,导致参数不对从而解密失败。

一夜梦惊人 发表于 2019-8-18 10:41

looooooc 发表于 2019-8-18 08:48
表哥,求能绕过app双向验证、绕过root检测的插件

我这里是没有的,因为root和xposed有太多办法来检测,比如什么包名,栈推,内存中加载的so,一些特有的文件等等,我都是根据app的检测来现写的,除非特别常用。

a250720 发表于 2019-8-17 22:10

可以可以 学习了

KARMA07007 发表于 2019-8-17 22:12

第一次看到-普通教程-代码-整齐规范的。

一夜梦惊人 发表于 2019-8-17 22:20

LGLG 发表于 2019-8-17 22:12
第一次看到-普通教程-代码-整齐规范的。

{:301_1009:}那大佬,给个热心值呗!!!

JiaGela233 发表于 2019-8-17 22:23

666,正好需要

多幸运遇见baby 发表于 2019-8-17 23:17

欢迎分析讨论交流,吾爱破解论坛有你更精彩!

kltdhc 发表于 2019-8-17 23:22

woc 这么厉害的吗、、、

Ericky 发表于 2019-8-17 23:28

无Bin无真相

otgserver 发表于 2019-8-17 23:53

这个文章好

ljn666 发表于 2019-8-18 04:40

好文章必须要顶上
页: [1] 2 3 4 5
查看完整版本: 记一次反Xposed的处理