【Android视频号④ 问题总结】
这节坑比较多~ 差点没把我给整死!!!
环境介绍
- 首先我调试都是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() {@Overrideprotected void afterHookedMethod(HZ_MethodHook.MethodHookParam param) throws Throwable {register(classLoader, (Context) GomenHelpers.getObjectField(param.thisObject, "mContext"));}});} else {GomenBridge.hookAllMethods(ActivityManagerService, "main", new HZ_MethodHook() {@Overrideprotected void afterHookedMethod(HZ_MethodHook.MethodHookParam param) throws Throwable {register(classLoader, (Context) param.getResult());}});}GomenBridge.hookAllMethods(ActivityManagerService, "systemReady", new HZ_MethodHook() {@Overrideprotected 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 hereLog.i(WXTAG, "注册服务完成。系统初始化....");}private String clientId ="客户端id";private String serverIp ="127.0.0.1";//重写方法@Overridepublic String getClientId() throws RemoteException {return clientId;}@Overridepublic String getServerIp() throws RemoteException {return serverIp;}@Overridepublic void setClientId(String clientid) throws RemoteException {this.clientId = clientid;}@Overridepublic void setServerIp(String serverip) throws RemoteException {this.serverIp =serverip;}
}
然后,创建一个 Xposed 模块用于注册服务
public class XposedMod implements IXposedHookLoadPackage {@Overridepublic 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热加载
基础须知:
- 热加载是可以让安卓App不重新安装但是又能改机运行逻辑的技术,腾讯系软件使用自家的热加载系统Tinker。
- 应用程序会从清单的appliction name开始进行运行, 一把程序加壳,多dex,热加载,都在这里做工作,这是应用程序自己的代码能被最早执行的地方。
- 热加载分成总体分资源,so, dex热加载,业务逻辑代码绝大部分在dex, 所以一般关注dex热加载比较多。
- 系统会先执行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() {@Overrideprotected 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的方方面面也算了解了一下 对微信的业务设计也是明白了一些~ 还稍微学习一些安卓正向开发~
加油咯 以梦为马!不负韶华!
相关文章:
【Android视频号④ 问题总结】
这节坑比较多~ 差点没把我给整死!!! 环境介绍 首先我调试都是root过的真机,但是生产环境都是没有Root的云机,属于自己改的Rom框架也不是XP或LSP 是技术人员利用Xposed源码改的框架 问题&解决 模块源码更改 这…...
推荐算法—widedeep原理知识总结代码实现
wide&deep原理知识总结代码实现1. Wide&Deep 模型的结构1.1 模型的记忆能力1.2 模型的泛化能力2. Wide&Deep 模型的应用场景3. Wide&Deep 模型的代码实现3.1 tensorflow实现3.2 pytorch实现今天,总结一个在业界有着巨大影响力的推荐模型,…...
PHP面向对象03:命名空间
PHP面向对象03:命名空间一、命名空间基础二、子空间三、命名空间访问1. 非限定名称2. 限定名称3. 完全限定名称四、全局空间五、命名空间应用六、命名空间引入一、命名空间基础 namespace,是指人为的将内存进行分隔,让不同内存区域的同名结构…...
Elasticsearch:使用 pipelines 路由文档到想要的 Elasticsearch 索引中去
路由文件 当应用程序需要向 Elasticsearch 添加文档时,它们首先要知道目标索引是什么。在很多的应用案例中,特别是针对时序数据,我们想把每个月的数据写入到一个特定的索引中。一方面便于管理索引,另外一方面在将来搜索的时候可以…...
前端开发常用的18个JavaScript框架和库
JavaScript 可以说是最流行的编程语言之一,也是Web 开发人员必须学习的 3 种语言之一,JavaScript 几乎可以做任何事情,更可以在包括物联网在内的多个平台和设备上运行。在WebGL库和SVG/Canvas元素的支持下,JavaScript变得惊人的强…...
理解、总结重点知识
一、常见的数据结构 1、数组结构 数组结构: 存储区间连续、内存占用严重、空间复杂度大 优点:随机读取和修改效率高,原因是数组是连续的(随机访问性强,查找速度快)缺点:插入和删除数据效率低&a…...
记一次从文件备份泄露到主机上线
前言 记录下某个测试项目中,通过一个文件备份泄露到主机上线的过程。 文件备份泄露 对于测试的第一项当然是弱口令,bp跑了一通词典,无果。目录又爆破了一通,发现一个web.rar可通,赶紧下载看看,如下图所示…...
8年测开经验面试28K公司后,吐血整理出1000道高频面试题和答案
1、python的数据类型有哪些 答:Python基本数据类型一般分为:数字、字符串、列表、元组、字典、集合这六种基本数据类型。 浮点型、复数类型、布尔型(布尔型就是只有两个值的整型)、这几种数字类型。列表、元组、字符串都是序列。 2、列表和元组的区别 答…...
Linux 基础知识之权限管理
目录一、权限的认识二、用户切换三、文件权限1.三类文件访问者2.文件权限类型3.文件访问权限4.文件权限值表示一、权限的认识 权限是对用户所能进行的操作的限制,如果不对用户作出限制,那么碰到恶意用户,就会损害其他用户的利益。 Linux是多用…...
百度LAC分词
对应数据的链接放这里了 import pandas as pd from util.logger import Log import os from util.data_dir import root_dir from LAC import LAC os_file_name os.path.split(os.path.realpath(__file__))[-1]# 加载LAC模型 lac LAC(mode"lac") # 载入自定义词典 …...
软件测试面试题 —— 整理与解析(1)
😏作者简介:博主是一位测试管理者,同时也是一名对外企业兼职讲师。 📡主页地址:🌎【Austin_zhai】🌏 🙆目的与景愿:旨在于能帮助更多的测试行业人员提升软硬技能…...
深入浅出C++ ——红黑树模拟实现STL中的set与map
文章目录一、红黑树二、用泛型红黑树模拟实现set三、用泛型红黑树模拟实现map一、红黑树 红黑树作为set和map的底层容器,既要实现插入key又要实现插入pair,所以做了稍许的改动,使其成为一颗泛型结构的红黑树,通过不同的实例化参数…...
自动化测试框架设计
大数据时代,多数的web或app产品都会使用第三方或自己开发相应的数据系统,进行用户行为数据或其它信息数据的收集,在这个过程中,埋点是比较重要的一环。 埋点收集的数据一般有以下作用: 驱动决策:ABtest、漏…...
【虚拟仿真】Unity3D中实现鼠标的单击、双击、拖动的不同状态判断
推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客 大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 一、前言 这篇文章分享一下虚拟仿真项目中经常碰到鼠标事件控制代码。 …...
【2023】Prometheus-相关知识点(面试点)
目录1.Prometheus1.1.什么是Prometheus1.2.Prometheus的工作流程1.3.Prometheus的组件有哪些1.4.Prometheus有什么特点1.5.Metric的几种类型?分别是什么?1.6.Prometheus的优点和缺点1.7.Prometheus怎么采集数据1.8.Prometheus怎么获取采集对象1.9.Promet…...
英语二-电子邮件邀请短文写作
1. 邮件模板 Dear 邀请人, Hope you have a great day. I am writing this email to invite you to attend 主题. Please kindly find the following information for your reference: Time: 时间 Address: 地点 We hope that nothing will prevent you from coming, as…...
如何快速一次性通过pmp考试?
我们就从三个方向进行了解 1.PMP考试难不难? 2.PMP如何备考? 3.考试过程中需要注意什么? 一,PMP考试难不难? 首先关注的问题是,PMP考试难吗?我想全球55%的通过率和学会这边93.9%的通过率&a…...
1-Linux 保存kernel panic信息到flash
在系统运行过程中,如果内核发生了panic,那么开发人员需要通过内核报错日志来进行定位问题。但是很多时候出现问题的时候没有接调试串口,而报错日志是在内存里面的,重启后就丢失了。所以需要一种方法,可以在系统发生crash时&#x…...
linux基本功系列-top命令实战
文章目录一. top命令介绍二. 语法格式及常用选项三. 参考案例3.1 显示进程信息3.2 显示完整的进程命令3.3 以批处理的形式展示3.4 设置信息更新频次3.5 显示指定进程号的信息3.6 top面板中常用参数3.7 其他用法四. top的相关说明4.1 交互命令介绍4.2 top面板每行信息的含义4.2.…...
6.5 拓展:如何实现 Web API 版本控制,同时兼容无版本控制的原始接口?
第6章 构建 RESTful 服务 6.1 RESTful 简介 6.2 构建 RESTful 应用接口 6.3 使用 Swagger 生成 Web API 文档 6.4 实战:实现 Web API 版本控制 6.5 拓展:如何实现 Web API 版本控制,同时兼容无版本控制的原始接口? 6.5 拓展&#…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...
2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...
安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...
群晖NAS如何在虚拟机创建飞牛NAS
套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...
