Android Socket 应用 - 网络编程与进程通讯
前阵子空闲时间学习了下 okhttp 源码,主要是重新学习 socket 网络编程,以及 okhttp 框架的一些特点。上周末,一个制作三星刷机包的朋友告诉我:国行三星手机通过官方 crom service 应用解锁手机后,才可以刷第三方刷机包,解锁后就保修就废了,很多人都不愿意解锁。于是朋友托我研究 crom service 上锁,如果研究成功,那么玩家更乐意解锁刷机。在研究上锁,刚好涉及到 android 底层 socket 通讯知识,同时分享下。文末介绍这次三星上锁之旅(反编译相关),成功但尴尬的结局。
socket 网络编程
在 okhttp 中,一次网络请求,在一条拦截链中完成,拦截链中每个拦截器完成比较少的任务(如重定向,失败重连,连接池,缓存等),很多大神也分享过拦截链的代码,就不重复介绍,这里主要介绍一次网络请求整个流程,可分以下四步:
序号 | 方法 | 描述 |
---|---|---|
1 | connectSocket | 通过 socket 连接远程服务器 |
2 | connectTls | 传输层安全协议,握手 |
3 | writeRequest | 写请求信息 |
4 | readResponse | 读响应信息 |
以下代码为整合的,忽略细节的,一次 https 1.x 网络请求代码:
|
|
上述代码,在不关心传输层安全协议,重连,100-continue等情况下,看上去还是挺简单的,先连接远程服务器,然后建立 SSLSocket ,获取输入输出流,然后写请求信息,即可得到响应消息。满足 http 其中三个特点:1. C/S模式;2. 快速简单,支持多种请求方式; 3. 灵活,支持任意类型数据对象。
请求信息
请求格式:
|
|
- 第一行,请求行,说明请求类型,请求资源路径以及 http 版本
- 第二行开始为请求头信息,直至空行,一般包含 User-Agent 和 Host
- 空行
- 最后为请求传输的内容,可能为空
举例:
|
|
响应信息
响应格式:
|
|
举例:
|
|
- 第一行,状态行,说明 http 版本,响应状态码以及响应状态信息
- 第二行开始为响应头信息,直至空行
- 空行
- 最后为响应内容,可能为空
socket 进程通讯
android 进程间通讯除了 binder 通讯,还有另外一种比较常见的 socket 通讯,在 native 层出现比较多。如果你想更深入 android 系统,应该克隆一份 aosp 源码。对着系统源码,你将更容易理解其中奥妙。虽然本人 c++ 语言比较差,但看着代码也能理解其用意,和仿照系统源码学习编写 c++ 代码(jni编程)。文章接下来比较多贴代码的地方,需要仔细阅读代码注释。
在 android 系统,socket 通讯为 c/s 模式,其中服务器在 init 进程 fork 各个 service 进程时创建,即 socket 服务器依附于 service 进程,而客户端在运行时连接服务器。
一般情况下,我们只关注以下 socket 提供的函数,下文涉及。
|
|
服务器启动
这里描述的服务启动不单单是 socket 服务器启动,而是整个服务进程创建和启动过程,包含三个步骤:
- 脚本解释
- 启动服务
- 程序运行
其中第二步,启动服务,才是真正从 init 进程 fork 出服务进程,并创建相关的 socket 服务器。
脚本解释
在 android 启动时,init 进程开始执行初始化,其中会解释 init.rc 并执行,在 init.rc 引入其他 rc 格式脚本文件,其中有服务声明,部分服务带有 socket 声明。研究三星上锁时涉及到 vold 进程,因此下文将围绕 vold 进程相关 socket 知识展开。
vold.rc 脚本 - system/vold/vold.rc
|
|
脚本说明:
- 第1行,创建 vold 服务进程,其中二进制程序为 /system/bin/vold
- 第2-3行,启动 /system/bin/vold 程序的四个参数,这两行是接着第一行的,属于 service 节点参数
- 第4行,ServiceManager 会对相应类别的服务执行相应方法,略
- 第5行,创建名为 vold,类别为流的 socket 服务器,设置权限,用户及用户组,对应文件 /dev/socket/vold
- 第6行,创建名为 cryptd,类别为流的 socket 服务器,设置权限,用户及用户组,对应文件 /dev/socket/cryptd
- 第7行,设置 io 优先级,范围 0-7
- 第8行,fork 调用后得到的子进程号写入到文件 /dev/cpuset/foreground/tasks
查看服务进程可通过 ps | grep ${name} 命令, 查看 socket 服务器可通过 ls -l /dev/socket/${name}, 如下图
init 初始化 - /system/core/init/init.cpp
init 进程乃所有用户进程的始祖,初始化时解释执行 init.rc ,fork 出多个服务子进程
|
|
服务解释器代码 - system/core/init/service.cpp :
|
|
启动服务
服务启动代码 - system/core/init/service.cpp :
|
|
创建 socket 服务器 - system/core/init/util.cpp
此处 socket 服务器仅完成了 bind 操作,而 listen 和 accept 操作在二进制程序运行时才执行
|
|
程序运行
每个服务中 socket 服务器功能不一样,实现可以在 c++ 中,也可以在 java 中,因为 android 提供了 LocalServerSocket, LocalSocket 等类实现 socket 通讯,典型的 java 实现有应用进程的 ZygoteInit。这里以 vold 程序为例,在 c++ 中实现。
在 vold 程序运行时,会创建 VolumeManager 和 NetlinkManager, 并设置 CommandListener,而 CommandListener 是 socket 服务器关键,CommonListener 继承 FrameworkListener,FrameworkListener 继承 SocketListener。从名字可以推断,这里的 socket 通讯主要是用于服务端执行客户端发来的命令,并返回结果,接下来贴代码环节。
vold main 函数 - system/vold/main.cpp
|
|
SocketListener - system/core/libsysutils/src/SocketListener.cpp
职责:listen 和 创建线程进行 accept 客户端
|
|
FrameworkListener - system/core/libsysutils/src/FrameworkListener.cpp
职责:主要负责收发数据,即接收和分发命令,并返回客户端执行结果
|
|
CommandListener - system/vold/CommandListener.cpp
职责:主要负责注册各种命令解释执行器,统一管理
|
|
客户端连接
客户端可以在 c++ 或 java 中连接,在 vold.rc 中, socket vold 被声明了 0660 权限,即仅 root 用户, mount 组别的客户端才可以连接进行读写,若设有 selinux_label,要求更严格,关注系统安全的同学可以深入学习 SELinux 和 SEAndroid。
在源码中搜索了一番,发现只有 vdc.cpp 程序会发起 vold 的 socket 连接
|
|
三星上锁
这里需要一定的反编译知识,不感兴趣同学直接跳过
目标应用(官方下载): crom_service.apk,使用 dex2jar 反编译 dex 得到 jar 文件,使用 jd-gui 查看,可得如图代码结构:
通过静态分析,上图已标记各个 native 方法的功能,同时可发现解锁和上锁的流程如下:
上传用户信息和操作命令
-> 获取操作 token
-> 校验,解锁或上锁
通过 baksmali/smali 工具,干掉 ui 相关代码得到 dex 文件后,再通过 dex2jar 得 jar 文件,新建同包名应用,整理得:
通过 apktool 反编译 apk ,可发现 app 运行时需要是 system 用户和 system 用户组,需在 AndroidManifest.xml 的 manifest 节点添加 android:sharedUserId=”android.uid.system”。
问题一:编译运行,没停止运行,报 SEAndroid 权限错误,日志以 avc:开头
答案:原因应用签名非官方签名,而是 as 的 debug.keystore。经验证,当安装官方 crom_service.apk,相关的 packageInfo.applicationInfo.seinfo 为 platform,而通过 as 安装的是 default。看了一下官网介绍,然而并不想研究 SEAndroid ,我比较蠢和是个急性子,遇到这种问题,不先百度,直接就反编译去改 /system/framework/service.jar 里面的 PackageManagerService,赋值 packageInfo.applicationInfo.seinfo 的函数是 scanPackageDirtyLI,以下贴的都是官方源码,非三星相应代码,差不多的。
scanPackageDirtyLI - frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
|
|
assignSeinfoValue - frameworks/base/services/core/java/com/android/server/pm/SELinuxMMAC.java
|
|
先简单粗暴地,判断包名是 com.sec.android.app.kwb ,然后直接赋值 platform ,相关代码插桩看 Android 反编译分享
如果想了解 seinfo 的使用和如何传到 native 层,和应用启动相关,虽然涉及到 LocalSocket 方式与 Zygote 进程通讯,但是展开需要很大的篇幅,这里不作介绍,简单说明是从 ActivityManagerService.startProcessLocked 可出发查看源码。
startProcessLocked - frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
|
|
问题二:seinfo 对了,权限够了,没报错,但是没效果。
答案:这种情况只能使用屠龙刀 IDA 静态分析 system/lib64/libkwb.so 了。通过 IDA 静态分析后,发现虽然 java 层写的上锁命令是 17, 但是 libkwb.so 只处理 1 和 23 命令,其中 1 是解锁,只要使用 Hex Fiend 将 23 修改为 17 即可。另外在 vold.rc 添加了 socket frigate ,解锁和上锁实质上是修改 /dev/block/steady 文件。上述所有 socket 通讯知识都因 socket frigate 引起我的兴趣而学习得到。
上图为 setTokenToUnlock 对应的 jni 方法,sub_1994 函数是关键,转到 sub_1994 可发现只处理 1 和 23 命令。
跳到 sub_1994 汇编指令视图,下图可知指令位置,那么修改指令;事实上,我不是很懂这些汇编指令码,但尝试左边第二个字节数值的 +4 ,操作数就 +1 ;23 - 17 = 6 , 0x6 * 0x4 = 0x18 , 0x5E - 0x18 = 0x46, bingo。
修改完成,替换 system/lib64/libkwb.so ,运行 app ,上锁成功。重启,系统和 recovery 都不能进,只能进入三星挖煤模式-定制 bootloader,bootloader 界面显示 CROM SERVICE : Lock ,成功上锁。朋友说正常现象,上锁后只能刷官方固件才能开机。本人验证了下也是,刷官方系统,确实上锁了,不能刷第三方 recovery。朋友让我试试 knox 和 samsung pay 能不能用,发现是不行的,看来跟上锁没什么关系,听他说可能跟 bootloader 的 WARRANTY VOID 值相关。
整个过程中,尝试过 cat /dev/block/steady 查看,但是看不出什么,但是可以断点调试 libkwb.so ,懒得使用我那个华硕笔记本调试了,学到知识就好。以后再反编译 knox 和 samsung pay 看看到底校验了什么东西,不让使用。
引用
HTTP请求头详解: http://blog.csdn.net/kfanning/article/details/6062118/
总结
本文主要介绍了 socket 网络编程和进程通讯相关概念和函数,具体的读写操作,协议并没有提及,事实上在 TCP/UDP 网络编程中,还需要考虑传输数据的顺序,边界及丢失等等。更多,请阅读《Java+TCPIP+Socket编程.pdf》。