tidevice 是一个专注于与 iOS 设备做交互的测试工具
链接 https://github.com/alibaba/taobao-iphone-device
以前没写过怎么做 tidevice 的开发的文章,不过猜测应该会有不少人喜欢看吧,所以趁着这次给 iOS 加新功能的契机,记录一下开发流程供想提 pr 的人参考一下吧。
从 iOS16 开始,iPhone 的开发者模式就需要手工开启了。
根据苹果官网提供的资料,需要手机连接 Xcode,然后才会出现开发者模式的开关。所以可以猜测,Xcode 肯定是发送了什么数据到手机。下面的步骤就是介绍怎么捕获到这些数据。
捕获和分析数据用到的工具
go-ios、vscode、一台苹果电脑
简单介绍一下go-ios,这个是用 go 语言写的一个同 iPhone 进行数据交互的一个工具,跟 tidevice 的功能比较类似,甚至功能还更多一点。它自带的这个抓包功能非常的好用,所以就直接拿来用了。
git clone https://github.com/danielpaulus/go-ios
cd go-ios
# 没有安装go环境,可以去官网go.dev下载开发环境
go build
# Output binary: ./go-ios
抓包之前提前关闭 Xcode
sudo ./go-ios dproxy
sudo chown -R $(whoami) dump-*
## 分析数据
使用 vscode 打开文件夹 dump-<时间戳> 会看到里面有很多 connection-开头的文件夹,每一个文件夹里面的数据都代表一次连接包含的数据。
实在太多了,有没有感觉。100 多个文件夹。我在里也没有什么太好的办法了,一个个的看过去吧。
打开其中一个文件的时候发现应该是的,后来证明确实是的(如果不是的话,就继续看,知道找到为止)
熟练一些的话,可以用一些脚本过滤出来可能得请求。
比如可以用下面的代码过滤出来所有 StartService 的代码
$ find . -name "jsondump.json" | xargs cat | grep -v PairRecordData | grep StartService
... 结果省略 ...
经过仔细的人工排查,发现其中一个 connection 的可能性比较高:启动了 Service "com.apple.amfi.lockdown" 这部分的代码
代码启动了 Service "com.apple.amfi.lockdown",设备返回连接端口号 49304
但是直接全文搜索 49304 却搜索不到,这个其实跟 go-ios 存储的结构有关系。需要做一下转化
49304 = 0xc098
#LittleEndian to BigEndian
0x98c0 = 39104
# Unsigned int to signed int16
2**16 - 39104 = 26432
用 Python 也可以很快的算出来
2**16 - socket.htons(49304)
搜索 26432 就可以找到对应的文件了。然后看到 jsondump.json 并没有多少内容,定位到文件所在的目录,发现里面还有两个文件
connection-#107-2023.01.16-03.28.19.046
|-- bindump-hostservice-to-proxy.txt
|-- from-device.bin
|-- jsondump.json
`-- to-device.bin
为了查看 bin 文件的内容,Vscode 安装个插件 Hex Editor
打开 to-device.bin 就可以看到下面的内容
这个结构相当简单,大概猜一下就出来了。
前 4 个字节代表整个文件的长度。后面接下来的 body 一看就是 plist 编码的。可以用代码解析一下里面的内容
import plistlib
raw = open("to-device.bin", "rb").read()
payload = plistlib.loads(raw[4:])
print(payload)
# 输出
# {'action': 0}
然后 device 返回了的内容在 from-device.bin 中可以查到
00 00 00 D9
这几个字节也看不出来啥意思,估计就是成功的 code 吧。
流程差不多理清楚了,用代码模拟一下
import tidevice
import plistlib
import struct
d = tidevice.Device()
conn = d.start_service("com.apple.amfi.lockdown")
body = plistlib.dumps({"action": 0})
head = struct.pack(">I", len(body))
conn.psock.sendall(head+body)
conn.psock.recv() # read until connection closed
看起来没问题了,代码封装成函数就行了。
本次 tidevice 的具体更新内容如下
https://github.com/alibaba/taobao-iphone-device/compare/0.9.14...0.10.1