【Android视频号④ 问题总结】

这节坑比较多~ 差点没把我给整死!!!

环境介绍

  • 首先我调试都是root过的真机,但是生产环境都是没有Root的云机,属于自己改的Rom
  • 框架也不是XP或LSP 是技术人员利用Xposed源码改的框架

问题&解决

模块源码更改

这个比较简单 就是把对应的Xposed关键字 替换一下 云机上的框架就可以识别了

Xposed多进程通讯

Sekiro需要自定义ip 这样就需要传递给微信 然后让微信去连接Sekiro服务
一开始询问同事 他们说用的是socket 通讯 这我感觉就有点大材小用~

广播

于是我想到了是广播 可以进程通讯嘛 在微信启动界面开启一个广播 这样我们就可以向微信发送广播消息 然后接收到消息 ip等地址 就可以动态的用Sekiro Client去连接 Sekiro Server

创建广播:【Android视频号④ 问题总结】_第1张图片
发送广播:

【Android视频号④ 问题总结】_第2张图片

接收广播:

【Android视频号④ 问题总结】_第3张图片
这样两个进程的通讯就已经搭好了 真机测试没问题 完美执行!
然后
云机傻眼了!通过adb locat 查看日志 发现并没有什么错误 广播也是创建了成功 但是就是接收不到广播消息 通过adb 发送广播也收不到~
所以怀疑可能是rom问题 对广播可能做了不友好的支持 那既然这样广播这条路就行不通了

AIDL

偶然翻到自己以前的帖子 通过AIDL去注册服务通讯
https://blog.csdn.net/u014431237/article/details/87743606
但是当时年少 不知道怎么去弄AIDL 就留了个记录 最后用netty通讯完成了 但是现在 4年多了 该给当初自己的一个交代了

Xposed 是 Android 平台上一个著名的框架。基于这个框架,我们可以在不需要 root 的情况下修改(hook)任何系统和 App的类和方法,正如作者介绍的那样 modify your ROM - without modifying any APK (developers) or flashing (users)!

Xposed 可以 hook 任何类的任何方法,但是仅限于在方法执行前和执行后加入钩子(hook),而并不能修改方法原有的代码。这在大多是情况是够用的,但是当涉及到多进程时就不行了。举个例子,你hook 了微信,获取到了微信昵称,想将昵称显示在 QQ 中,如果你想简单的通过一个变量来传值是行不通的,因为微信和 QQ运行在不同的进程中,在 QQ 进程中获取到的还是变量的初始值。

广播(Broadcast)是解决该问题的一种方法。但是广播的缺点是:1.不能确定什么时候能够收到, 2.创建广播和广播接收者都需要用到Context ,而在 hook 的类里并不总是有 Context。

最好的方法就是添加一个自定义的系统服务来进行进程间的数据共享。系统服务的优点有:1.服务从开机就启动了,并且一直存活到关机,2.可以简单的通过ServiceManager.getService()来调用。

在正常情况下是不能添加这样的系统服务的,但是我们可以借助 Xposed 来实现。实现进程间通信的服务需要用到 AIDL(Android Interface Definition Language),我们的服务也不例外。

代码编写

首先编写一个 ICustomService.aidl

interface ICustomService {
    String getClientId();
    String getServerIp();
    void setClientId(String clientid);
    void setServerIp(String serverip);
}

如何向 Android 系统注册我们的服务,则是用到了 android.os.ServiceManager 的 addService 方法,在不同 Android 版本中的实现略有区别。addService 方法使用 private 修饰,所以需要使用反射来调用。这里,我们使用 Xposed 调用。

Android 的系统服务都是在 com.android.server.SystemServer 中注册的。在 5.0 之前,SystemServer 的 SystemContext 是由 ActivityManagerService 的 main 方法返回的,而之后是由 createSystemContext 方法生成,并最终传递给了 ActivityManagerService 的构造方法(通过 ActivityManagerService.Lifecycle 和 SystemServiceManager)。

需要注意的一个地方是 5.0 以后的版本中,因为 selinux 的原因,服务名称需要加 user. 前缀,否则会抛出 java.lang.SecurityException 错误。

因为 CustomService 注册时,其他服务并没有初始化完成,所以需要找到其他的 hook 入口来完成 CustomService 的最终初始化,ActivityManagerService 的 systemReady 方法会在其他所有服务初始化完毕后调用,正是我们需要的。

我们需要 SystemContext 来对 CustomService 进行一些初始化,所以分别 hook ActivityManagerService 的 main 方法和构造方法来获取 SystemContext,并注册 CustomService。具体实现如下:

public class CustomService extends ICustomService.Stub {
    private static final String SERVICE_NAME = "custom.service";
    private static CustomService mCustomService;
    private static ICustomService mClient;
    private static Context mContext;

    public CustomService(Context context) {
        mContext = context;
    }

    public static ICustomService getClient() {
        if (mClient == null) {
            try {

                Class<?> ServiceManager = Class.forName("android.os.ServiceManager");
                Method getService = ServiceManager.getDeclaredMethod("getService", String.class);
                IBinder binder =(IBinder) getService.invoke(null, getServiceName());
                mClient = ICustomService.Stub.asInterface(binder);
            } catch (Throwable t) {
                Log.w(WXTAG, "AIDL服务对象失败: "+t.toString() );
                mClient = null;
            }
        }

        return mClient;
    }

    public static String getServiceName() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? "user." + SERVICE_NAME : SERVICE_NAME;
    }

    //注册系统服务
    public static void register(final ClassLoader classLoader) {
        Class<?> ActivityManagerService = GomenHelpers.findClass("com.android.server.am.ActivityManagerService", classLoader);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            GomenBridge.hookAllConstructors(ActivityManagerService, new HZ_MethodHook() {
                @Override
                protected void afterHookedMethod(HZ_MethodHook.MethodHookParam param) throws Throwable {
                    register(classLoader, (Context) GomenHelpers.getObjectField(param.thisObject, "mContext"));
                }
            });
        } else {
            GomenBridge.hookAllMethods(ActivityManagerService, "main", new HZ_MethodHook() {
                @Override
                protected void afterHookedMethod(HZ_MethodHook.MethodHookParam param) throws Throwable {
                    register(classLoader, (Context) param.getResult());
                }
            });
        }

        GomenBridge.hookAllMethods(ActivityManagerService, "systemReady", new HZ_MethodHook() {
            @Override
            protected void afterHookedMethod(HZ_MethodHook.MethodHookParam param) throws Throwable {
                mCustomService.systemReady();
            }
        });
    }

    private static void register(final ClassLoader classLoader, Context context) {
        mCustomService = new CustomService(context);

        Class<?> ServiceManager = GomenHelpers.findClass("android.os.ServiceManager", classLoader);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            GomenHelpers.callStaticMethod(
                    ServiceManager,
                    "addService",
                    getServiceName(),
                    mCustomService,
                    true
            );
        } else {
            GomenHelpers.callStaticMethod(
                    ServiceManager,
                    "addService",
                    getServiceName(),
                    mCustomService
            );
        }
        Log.i(WXTAG, "register: 系统服务完成");
    }

    private void systemReady() {
        // Make initialization here
        Log.i(WXTAG, "注册服务完成。系统初始化....");
    }


    private String clientId ="客户端id";
    private String serverIp ="127.0.0.1";
    //重写方法
    @Override
    public String getClientId() throws RemoteException {
        return clientId;
    }

    @Override
    public String getServerIp() throws RemoteException {
        return serverIp;
    }

    @Override
    public void setClientId(String clientid) throws RemoteException {
        this.clientId = clientid;
    }

    @Override
    public void setServerIp(String serverip) throws RemoteException {
        this.serverIp =serverip;
    }
}

然后,创建一个 Xposed 模块用于注册服务

public class XposedMod implements IXposedHookLoadPackage {
	@Override
	public void handleLoadPackage(LoadPackageParam loadPackageParam) throws Throwable {
		if ("android".equals(loadPackageParam.packageName)) {
			CustomService.register(loadPackageParam.classLoader);
		}
	}
}

调用服务

 			//获取AIDL服务
            ICustomService mService= CustomService.getClient();
            if(mService==null){
                Log.w(WXTAG, "获取系统服务 实例为null.");
                return;
            }
            Log.i(WXTAG, "获取ip:"+ mService.getServerIp());
            Log.i(WXTAG, "获取id:"+ mService.getClientId());

            mService.setServerIp(tempServerIp);
            mService.setClientId(tempetSekiroId);

至此服务编写完毕 真机没问题 开始跑云机!
但是问题 就是出现在 云机获取服务一直为null
通过命令 adb shell service list 可以查到服务确实创建成功 开始说了云机是没有root权限的
用adb logcat >1.log 查看日志 发现错误如下

E SELinux : avc: denied { find } for service=user.custom.service pid=31544 uid=10123 scontext=u:r:untrusted_app:s0:c123,c256,c512,c768 tcontext=u:object_r:default_android_service:s0 tclass=service_manager permissive=0

这边翻译一下就是
【Android视频号④ 问题总结】_第4张图片
很明显 在云机上 untrusted_app 没有访问 service的权限

用户自行开发的app需要访问底层serial port。我们开发的app在SELinux(或SEAndroid)中分为主要三种类型(根据user不同,也有其他的domain类型):

1)untrusted_app 第三方app,没有Android平台签名,没有system权限
2)platform_app 有android平台签名,没有system权限
3)system_app 有android平台签名和system权限
从上面划分,权限等级,理论上:untrusted_app < platform_app < system_app

根据这篇帖子可以看到临时关闭SELinux权限的方法 Android——SELinux 权限简介
由于rom是自定义的 只需要在rom开发人员提供的app 执行一下 setenforce 0 指令即可

然后成功 可以通过AIDL交互了

对接ROM

总不可能每次启动App还得去手动执行一下 setenforce 0 指令吧 所以对接rom的SDK 还好也是AIDL
【Android视频号④ 问题总结】_第5张图片
然后我用过按钮点击 执行测试一下
【Android视频号④ 问题总结】_第6张图片
一直报错 systemService 永远都是返回null
【Android视频号④ 问题总结】_第7张图片
按理说bindService 之后调用重写函数就可以自动赋值 但是 永远都没有触发@Override!请朋友看了半天都说代码问题呀!!
然后…
【Android视频号④ 问题总结】_第8张图片
【Android视频号④ 问题总结】_第9张图片
没错 这个主线程不能调用bindService 搞了我一下午!!! 真是菜呀!!

关于微信

Tinker热加载

基础须知:

  1. 热加载是可以让安卓App不重新安装但是又能改机运行逻辑的技术,腾讯系软件使用自家的热加载系统Tinker。
  2. 应用程序会从清单的appliction name开始进行运行, 一把程序加壳,多dex,热加载,都在这里做工作,这是应用程序自己的代码能被最早执行的地方。
  3. 热加载分成总体分资源,so, dex热加载,业务逻辑代码绝大部分在dex, 所以一般关注dex热加载比较多。
  4. 系统会先执行Application里面的生命周期函数attachBaseContext,比我们熟悉的onCreate更早。

为什么说这个呢 因为hook微信的过程中 他会修复classloader 这样你XP所有hook 方法都会失效

【Android视频号④ 问题总结】_第10张图片
首先查看微信入口类
【Android视频号④ 问题总结】_第11张图片
可以看到他有个父类TinkerApplication 点进去

【Android视频号④ 问题总结】_第12张图片
类初始化之后,就会由系统调用生命周期函数 attachBaseContext
【Android视频号④ 问题总结】_第13张图片
跟一下Tinker加载流程
【Android视频号④ 问题总结】_第14张图片
找到 com.tencent.tinker.loader.TinkerLoader 的 tryLoad 方法
【Android视频号④ 问题总结】_第15张图片
可以看到在tryLoadPatchFilesInternal 中比较重要的是loadTinkerJars
【Android视频号④ 问题总结】_第16张图片
继续跟踪 loadTinkerJars 找到 SystemClassLoaderAdder.installDexes
【Android视频号④ 问题总结】_第17张图片
进入installDexes,根据不同的系统环境, 安装热更新
【Android视频号④ 问题总结】_第18张图片

创建了一个新的Classloader
【Android视频号④ 问题总结】_第19张图片
替换线程上下文和LoadedApk的classloader【Android视频号④ 问题总结】_第20张图片
检查一下 isPatch 值 如果load 成功 就会用tinker_xxx.dex等的类
在这里插入图片描述
所以
wx没替换classloader,我们就不替换xp的classloader;
wx替换了classloader,我们跟着替换xp的classloader。

Xposed 代码

 				// 这个时候,wx未修改classloader,保持xp的不变,
              XposedBridge.hookAllMethods(findClass("com.tencent.tinker.loader.app.TinkerApplication", lpparam.classLoader), "onBaseContextAttached", new XC_MethodHook() {
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            // 后面所有查找类加载类使用这个类加载器。不要再使用xp回调传过来的。或者使用TinkerClassLoader.findClass。
                            HookWX.wxClassLoader = (ClassLoader) XposedHelpers.callMethod(param.thisObject, "getClassLoader");
                            log("TinkerApplication 热加载完成~");
                            // 下面开始调用注入逻辑
                            TinkerLoadWx();
                        }
                    });

这样就解决了微信Tinker 改变了classloader 使插件失效的问题了

其他

组包解包都在这个类 com.tencent.mm.protocal.MMProtocalJni
每个业务请求都是异步 基本都会有dosence 和 NetEnd (一个执行一个响应)只要用ddms或者frida堆栈打印 定位到主要的业务包 写hook 是非常容易的
在我同一个号频繁切换手机和网络的过程中 居然封我号!说我网络行为或使用异常~

后记

云机视频号这个项目也可以成功跑了 这也算是完成了四年前的坑了吧 写了一个XPosed的项目 基本上对Xposed的方方面面也算了解了一下 对微信的业务设计也是明白了一些~ 还稍微学习一些安卓正向开发~
加油咯 以梦为马!不负韶华!

你可能感兴趣的:(逆向学习,逆向分析,AIDL,Xposed多进程,微信Tinker,云机调试)