【Android视频号④ 问题总结】

news2024/11/24 11:31:24

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

环境介绍

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

问题&解决

模块源码更改

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

Xposed多进程通讯

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

广播

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

创建广播:在这里插入图片描述
发送广播:

在这里插入图片描述

接收广播:

在这里插入图片描述
这样两个进程的通讯就已经搭好了 真机测试没问题 完美执行!
然后
云机傻眼了!通过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

这边翻译一下就是
在这里插入图片描述
很明显 在云机上 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
在这里插入图片描述
然后我用过按钮点击 执行测试一下
在这里插入图片描述
一直报错 systemService 永远都是返回null
在这里插入图片描述
按理说bindService 之后调用重写函数就可以自动赋值 但是 永远都没有触发@Override!请朋友看了半天都说代码问题呀!!
然后…
在这里插入图片描述
在这里插入图片描述
没错 这个主线程不能调用bindService 搞了我一下午!!! 真是菜呀!!

关于微信

Tinker热加载

基础须知:

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

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

在这里插入图片描述
首先查看微信入口类
在这里插入图片描述
可以看到他有个父类TinkerApplication 点进去

在这里插入图片描述
类初始化之后,就会由系统调用生命周期函数 attachBaseContext
在这里插入图片描述
跟一下Tinker加载流程
在这里插入图片描述
找到 com.tencent.tinker.loader.TinkerLoader 的 tryLoad 方法
在这里插入图片描述
可以看到在tryLoadPatchFilesInternal 中比较重要的是loadTinkerJars
在这里插入图片描述
继续跟踪 loadTinkerJars 找到 SystemClassLoaderAdder.installDexes
在这里插入图片描述
进入installDexes,根据不同的系统环境, 安装热更新
在这里插入图片描述

创建了一个新的Classloader
在这里插入图片描述
替换线程上下文和LoadedApk的classloader在这里插入图片描述
检查一下 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的方方面面也算了解了一下 对微信的业务设计也是明白了一些~ 还稍微学习一些安卓正向开发~
加油咯 以梦为马!不负韶华!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/365681.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Leetcode第235题二叉搜索树的最近公共祖先|C语言

struct TreeNode* lowestCommonAncestor(struct TreeNode* root, struct TreeNode* p, struct TreeNode* q) {if(root->val>p->val&&root->val<q->val)return root;//若p结点的值<q结点的值&#xff0c;而根节点的值位于两者之间&#xff0c;说明…

Java StringBuffer StringBuilder,超详细整理,适合新手入门

目录 一、StringBuffer和StringBuilder的区别是什么&#xff1f; 二、StringBuffer的示例 三、StringBuilder的示例 四、为什么StringBuffer和StringBuilder比String更适合在循环中使用&#xff1f; 五、如何将String对象转换为StringBuilder或StringBuffer对象&#xff1…

论文投稿指南——中文核心期刊推荐(综合性经济科学)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

常见的排序算法 | 直接插入排序 | 希尔排序 | 选择排序 | 堆排序 | 冒泡排序 | 快速排序 | 归并排序 |(详解,附动图,代码)

思维导图&#xff1a; 一.插入排序 1.直接插入排序&#xff08;InsertSort&#xff09; ①手机通讯录时时刻刻都是有序的&#xff0c;新增一个电话号码时&#xff0c;就是使用插入排序的方法将其插入原有的有序序列。 ②打扑克 步骤&#xff1a; ①如果一个序列只有一个数&am…

【Android视频号③ Xposed插件编写】

这节 就是将frida代码翻译为Xposed 然后利用Sekiro服务进行接口调用 Xposed环境 我的测试环境是 LSPosed 它是完全兼容XP模块的 &#xff08;免重启调试起来方便一点&#xff09;下载后用Magisk安装即可. 模块编写可以参考这篇文章 XPosed模块编写教程 翻译代码 首先需要拦…

Java数据结构与算法第十一课---反射、枚举以及lambda表达式

一 : 反射 1.定义 Java的反射&#xff08;reflection&#xff09;机制是在运行状态中&#xff0c;对于任意一个类&#xff0c;都能够知道这个类的所有属性和方法&#xff1b;对于任意一个对象&#xff0c;都能够调用它的任意方法和属性&#xff0c;既然能拿到&#xff0c;那么…

k8s1.23.0+ubuntu20.04+docker23+hyperv

问题 k8s node节点加入到集群时卡住 “[preflight] Running pre-flight checks” # master节点重新生成加入命令 kubeadm token create --ttl 0 --print-join-command参考 注意 k8s1.24使用containerd而不再使用docker&#xff0c;因此使用k8s1.23版本 环境 k8s: 1.23.0 u…

【Python入门第十五天】Python字典

字典&#xff08;Dictionary&#xff09; 字典是一个无序、可变和有索引的集合。在 Python 中&#xff0c;字典用花括号编写&#xff0c;拥有键和值。 实例 创建并打印字典&#xff1a; thisdict {"brand": "Porsche","model": "911&q…

2023年湖北施工员怎么报考?甘建二告诉你

湖北施工员怎么报考&#xff1f;考施工员需要了解哪些知识呢&#xff1f;跟甘建二一起来看下 一、湖北施工员报名条件&#xff1a; 甘建二告诉你&#xff0c;施工员报名条件基本没有限制&#xff0c;年满18岁即可。个人名义都可以报考&#xff0c;限制不多&#xff0c;不是跟安…

20230223-EF6用原生的命令执行SQL命令

目录:一、环境说明二、背景三、示例代码四、思考总结一、环境说明本机环境&#xff1a;windows10 操作系统 使用工具&#xff1a;Visual Studio 2022 Net版本&#xff1a;Net6.0二、背景在使用winform EF 开发过程中&#xff0c;需要用到类似 QueryList QueryScalar 等功能的时…

MySQL中MVCC如何解决不可重复读以及幻读?

了解MVCC之前&#xff0c;我们首先需要了解以下两个概念&#xff1a;一致性非锁定读和锁定读&#xff0c;了解这两个概念之后我们在逐步分析MVCC。 一致性非锁定读和锁定读 一致性非锁定读(快照读) 对于 一致性非锁定读的实现&#xff0c;通常做法是加一个版本号或者时间戳字…

【12-JVM面试专题-垃圾回收器好坏评价的标准?吞吐量和响应时间?生产环境中,如何选择合适的垃圾收集器?如何判断是否使用G1垃圾收集器?】

垃圾回收器好坏评价的标准&#xff1f;吞吐量和响应时间&#xff1f;生产环境中&#xff0c;如何选择合适的垃圾收集器&#xff1f;如何判断是否使用G1垃圾收集器&#xff1f; 垃圾回收器好坏评价的标准&#xff1f;吞吐量和响应时间&#xff1f;生产环境中&#xff0c;如何选择…

python基于vue戒烟网站

可定制框架:ssm/Springboot/vue/python/PHP/小程序/安卓均可开发目录 开发语言&#xff1a;Python python框架&#xff1a;django/flask 软件版本&#xff1a;python 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;PyCharmscode 项目介…

Java常用算法

关于时间复杂度&#xff1a; 平方阶 (O(n2)) 排序 各类简单排序&#xff1a;直接插入、直接选择和冒泡排序。线性对数阶 (O(nlog2n)) 排序 快速排序、堆排序和归并排序。O(n1)) 排序&#xff0c; 是介于 0 和 1 之间的常数。希尔排序。线性阶 (O(n)) 排序 基数排序&#xff0c…

技能提升:Python技术应用工程师职业技能提升

职业技术培训-Python技术应用工程师分为高级培训班、中级培训班及初级培训班。 Python是一种跨平台的计算机程序设计语言&#xff0c;是一个高层次的结合了解释性、编译性、互动性和面向对象的语言。最初被设计用于编写自动化脚本Shell&#xff08;适用于Linux操作系统&#xf…

2-并发篇

线程有哪些状态 java的线程状态有6种&#xff1a; 操作系统中有5状态的说明 注意java的runnable对应了就绪、运行、阻塞I/O 线程池的核心参数 主要是说线程池的一个实习类 threadPoolExecutor.class 1.corePoolSize 核心线程数据&#xff08;可以为0&#xff09; 最多保…

第一章:网络参考模型

一、专业术语 ISO---&#xff08;International Organization for Standardization&#xff09;国际标准化组织 OSI---&#xff08;Open System Interconnection Reference Model&#xff09;开放式系统互联通信参考模型 IEEE---(Institute of Electrical and Electronics Engi…

数字经济赋能乡村建设,助力乡村全面振兴

我国农村正朝着全面振兴的方向迈步发展&#xff0c;与此同时&#xff0c;我国高速发展的数字经济正在成为驱动经济社会全方位高质量发展的重要引擎&#xff0c;数字经济赋能乡村建设是乡村振兴的重要战略方向。数字经济通过将数据要素纳入农业生产、将数字产品和服务融入农民生…

3年经验,3轮技术面+1轮HR面,拿下字节30k*16薪offer,这些自动化测试面试题值得大家借鉴

面试一般分为技术面和hr面&#xff0c;形式的话很少有群面&#xff0c;少部分企业可能会有一个交叉面&#xff0c;不过总的来说&#xff0c;技术面基本就是考察你的专业技术水平的&#xff0c;hr面的话主要是看这个人的综合素质以及家庭情况符不符合公司要求&#xff0c;一般来…

K8s调度器Scheduler

当创建k8s pod的时候调度器会决定pod在哪个node上被创建且运行&#xff0c;调度器给apiserver发出了一个创建pod的api请求&#xff0c;apiserver首先将pod的基本信息保存在etcd&#xff0c;apiserver又会把这些信息给到每个node上的kubelet进程&#xff0c;kubelet一直在监听这…