【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 拓展&#…...

Springboot依赖注入Bean的三种方式,final+构造器注入Bean
文章目录Springboot依赖注入Bean的方式一、Field 注入/属性注入二、set注入三、构造器注入Springboot依赖注入Bean的方式 一、Field 注入/属性注入 Autowired注解的一大使用场景就是Field Injection。 Controller public class UserController {Autowiredprivate UserServic…...

【java】Spring Cloud --Spring Cloud Alibaba 微服务解决方案
文章目录1、Spring Cloud Alibaba 是什么先说说 Spring CloudSpring Cloud Alibaba和Spring Cloud 的区别和联系Spring Cloud Alibaba2、Spring Cloud Alibaba 包含组件阿里开源组件阿里商业化组件集成 Spring Cloud 组件3、Spring Cloud Alibaba 功能服务注册与发现支持多协议…...

CSS 6种选择器(超详细)
CSS6大种选择器(超详细) 一、常用的css基本选择器(4种) 1、标签选择器 结构: 标签名{css属性名:属性值} 作用:通过标签名,找到页面中所有的这类标签,设置样式 注意:1.标签选择器选择的是一类标签&#…...

mysql8.0.32-手动配置安装-具体流程步骤
文章目录1.下载mysql压缩编译版2.修改配置文件3.数据库初始化,安装windows服务,启动服务4.修改root密码5.作者答疑1.下载mysql压缩编译版 作者从官方下载:https://download.csdn.net/download/m0_67316550/87485720 2.修改配置文件 修改my…...

【项目】Vue3+TS 退出登录 menu header搭建
💭💭 ✨:【项目】Vue3TS 退出登录 menu header搭建 💟:东非不开森的主页 💜: 今天永远比昨天更好💜💜 🌸: 如有错误或不足之处,希望可以指正&#x…...

LoRaWAN模块在车辆跟踪定位中的应用
目前 GPS已经在资产的管理中得到了越来越多的运用,如车辆跟踪、车队跟踪、资产监控等;人员跟踪,宠物跟踪,等等。在所有追踪装置中,最重要的是它的电池期望和监视距离。鉴于 LoRaWAN的功率消耗很小,而且能在…...

软件测试分类
软件测试分类 从上图我们发现软件测试根据不同的分类条件会有不同的结果. 1. 按照阶段进行划分 1.1 单元测试(Unit Testing) 单元测试是对软件组成单元进行测试。其目的是检验软件基本组成单位的正确性。测试的对象是软件设计的最小单位:模块。 测试阶段&#x…...

外置的媒体查询,对性能又一次的优化提升
通常情况下我们写媒体查询都是写在一个样式文件中,对于浏览器加载的时候,会解析到最后一行样式时才会渲染页面,这样就会造成页面的白屏时间过长。 但是通常情况下大量的媒体查询样式都是无用的,现在浏览器允许我们在引用样式文件…...

【Galois工具开发之路】关于IDEA的gradle工程执行两次premain的bug~
文章目录关于premain方法问题记录解决方式关于premain方法 是Java Agent技术的一种,通过 -javaagent: 的方式,添加外部代理,代理入口方法为 premain 。另一种Java Agent技术则是动态attach到java进程的方式,这种方式则是使用 age…...

云计算 概念与技术
如果我倡导的计算机在未来得到使用,那么有一天,计算也可能像电话一样成为共用设施。计算机应用将成为一全新的、重要的产业的基础。 ——John McCarthy 云计算的概念 定义 Garther公司的定义 一种计算方式,能通过Internet技术将可扩展的和…...