查看原文
其他

ratel,让Xposed模块在免root的环境下跑起来

virjar 看雪学院 2019-05-27

我一直觉得,如果只是在进程内使用xposed的话,是不是可以不用root,不替换受精卵。


后来我知道了有app加固这个东西,那就是给app套了一层代码,这样的话我是不是也可以类似加固这样给apk套一个壳,在别人的apk运行之前执行某些特殊逻辑。


然后,我知道droidPlugin和virtualAPP这个东西,原来我们可以欺骗Android系统,直接让一个apk文件run起来。


最好,还有一个virtualXposed,竟然可以在virtualAPP的环境下,跑起xposed。


virtualXposed的出现,我觉得理论上一个app,肯定是可以在非root的环境下run起来的,只是基于app容器引擎确实有点重。


所以,我抄了抄virtualXposed,把virtualApp替换掉,直接采用一个轻量级的壳用来驱动各个模块,然后貌似尽然可以行通。


怎么做:



1. 做一个壳划线标题

@Override
   protected void attachBaseContext(Context base)
{
       //第一步需要call supper,否则Application并不完整,还无法作为context使用,当然此时base context是可用状态
       super.attachBaseContext(base);
       //exposed框架,在driver下面定义,所以需要在替换classloader之前,完成exposed框架的so库加载
       ExposedBridge.initOnce(this, getApplicationInfo(), getClassLoader());

       if (!checkSupport()) {
           throw new IllegalStateException("epic 不支持的版本");
       }
       //释放两个apk,一个是xposed模块,一个是原生的apk,原生apk替换为当前的Application作为真正的宿主,xposed模块apk在Application被替换之前作为补丁代码注入到当前进程
       releaseApkFiles();

       //替换classloader
       Class<?> contextImplClazz = XposedHelpers.findClassIfExists("android.app.ContextImpl", base.getClassLoader());
       Object contextImpl = XposedHelpers.callStaticMethod(contextImplClazz, "getImpl", base);
       Object loadApk = XposedHelpers.getObjectField(contextImpl, "mPackageInfo");
       ClassLoader parentClassLoader = RetalDriverApplication.class.getClassLoader();
       try {
           //  Class<?> aClass = XposedHelpers.findClass("android.app.LoadedApk", RetalDriverApplication.class.getClassLoader());
           parentClassLoader = (ClassLoader) XposedHelpers.getObjectField(loadApk, "mClassLoader");
       } catch (Exception e) {
           //ignore
       }
       String originApkSourceDir = new File(ratelWorkDir(this), originAPKFileName).getAbsolutePath();
       PathClassLoader originClassLoader = new PathClassLoader(originApkSourceDir, parentClassLoader);
       XposedHelpers.setObjectField(loadApk, "mClassLoader", originClassLoader);

       //context中的resource,仍然绑定在老的apk环境下,现在把他们迁移
       ApplicationInfo appinfoInLoadedApk = (ApplicationInfo) XposedHelpers.getObjectField(loadApk, "mApplicationInfo");
       appinfoInLoadedApk.sourceDir = originApkSourceDir;
       XposedHelpers.setObjectField(loadApk, "mAppDir", originApkSourceDir);
       XposedHelpers.setObjectField(loadApk, "mResDir", originApkSourceDir);
       XposedHelpers.setObjectField(loadApk, "mResources", null);
       Resources resources = (Resources) XposedHelpers.callMethod(loadApk, "getResources", currentActivityThread());
       if (resources != null) {
           XposedHelpers.setObjectField(contextImpl, "mResources", resources);
       }
       //替换之后,再也无法访问容器apk里面的资源了,容器中的所有资源全部被替换为原始apk的资源
       loadResources(originApkSourceDir);
   }


在我的壳运行的时候,初始化xposed框架环境,然后加载指定xposed模块,然后根据一般apk的壳的套路,将控制权交给原始apk。



2. 留两个插桩,分别等待xposed模块apk和原始apk归位




3. 清单文件,从原始apk文件复制过来,放到壳对应的apk下面


AndroidManifest.xml中很多配置,都是需要Android系统可见的,如果不提前定义,那么很多功能无法使用,或者使用virtualAPP这样提前弄好插桩。我在想如果我只是正对于单个app做AOP的话,其实这个时候配置都是已知的了,所以把所有定义全部copy过来。



4. 重新打包

使用我们自己的的壳作为入口apk,那么dex文件需要使用我们提供的壳的dex,但是AndroidManifes.xml需要使用我们将要包装那个apk的。AndroidManifes.xml里面的代码入口(application),需要修正到我们的壳入口。当然资源也需要使用原始apk的,毕竟我们的壳目前来看没有啥资源需要用的。


这些资源拼凑好之后,用apktool重新打包(没法用AndroidStudio,编译都不会过)。由于我们不会修改代码,所以apktool不会走到smali这一层,所以其实这里用apktool,我觉得还是很稳定的。



 5. 签名、发布、安装


其实,我们就是把三个apk揉成一个apk。


原始apk,由于有壳或者各种混淆,各种反逆向导致很难重打包,所以我们并不会真正对原始apk进行拆包。


xposed模块apk,他是作用在原始apk上面的,但是必须使用原始apk的进程身份注入。在没有xposed的环境下,做不到,所以现在这个工种交给了我们的壳(driver)。


ratel-driver,我们的壳apk。他将会是产出的apk的代码入口点,他会在坏事情做好之后,加载原始apk。


由于其实使用沙盒的方式run起来的,并没有对原始apk进行拆包,所以我觉得不需要考虑过签名问题,即使有xposed都进来了,还不能把他给栏了么。


产生的新包的packageName和原始apk的package相同,所以签名后安装的时候,肯定和之前安装在系统的那个apk的证书不一致,把之前那个卸了再装这个就好了。


另外,我现在只是把一个demo跑起来了,还没有各种验证。估计还是有些兼容性问题的。


我把demo开源了,代码地址:


https://gitee.com/virjar/ratel


再讲讲效果,测试的apk:

public class MainActivity extends Activity {

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
   }

   @Override
   protected void onResume() {
       super.onResume();
       TextView textView = findViewById(R.id.indexTextView);
       textView.setText(text());
   }

   private String text() {
       return "原始文本";
   }

}


会首先显示:“原始文本”这个文案:

public class DemoAppHooker implements IXposedHookLoadPackage {
   @Override
   public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
       Class<?> aClass = XposedHelpers.findClass("com.virjar.ratel.demoapp.MainActivity", lpparam.classLoader);
       XposedHelpers.findAndHookMethod(aClass, "text", XC_MethodReplacement.returnConstant("被hook后的文本"));
   }
}


然后我hook了他,把文案给改了。最好,打开app,如下图:


手机刚刚装了一个新系统,没有root,没有xposed环境。


放一下github地址:


https://github.com/virjar/ratel



- End -



看雪ID:virjar                    

https://bbs.pediy.com/user-791488.htm



本文由看雪论坛 virjar 原创

转载请注明来自看雪社区



热门图书推荐:

逆向经典,初学者必备
立即购买!




热门技术文章推荐:






公众号ID:ikanxue
官方微博:看雪安全

商务合作:wsc@kanxue.com

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存