自动化工具 硬货来啦!!使用纯 python 实现 Instruments 协议,跨平台 (win,mac,linux) 获取 iOS 性能数据

YueChen · 2020年12月15日 · 最后由 codeskyblue 回复于 2021年01月22日 · 2480 次阅读
本帖已被设为精华帖!

前言

获取 iOS 性能数据,一直都是比较麻烦的事情,之前在构建测试框架&平台的时候,获取 iOS 性能也是没有什么好的办法,也只能使用比较原始的方式,获取非越狱 iOS 性能数据,例如 xcode 调试可以获取,Instruments 获取数据,用起来也是极为不便。

到现在能做到跨平台,对非越狱 iOS 机器进行监控数据的貌似只有腾讯一家 PerfDog,确实非常优秀,但是腾讯对于 PerfDog 的技术还是比较保密的,GitHub 上比较有名的关于 iOS 设备控制仓库上例如 facebook idb,libimobiledevice 等,也都没有获取性能数据相关内容。

目前已经实现

使用纯 Python 代码跨平台 (win,mac,linux) 获取 iOS 部分性能数据&其他数据,10.3 < iOS version < 14.2 虽然可以获取绝大部分 iOS 设备性能数据,当然也有部分设备没有兼容,也希望有兴趣的同学可以一起探讨共同开发。最终开源项目地址会在文章末尾开放

基本工作原理介绍

  • Usbmux
  • Lcokdown
  • LaunchDaemon
  • Instruments 通道
  • Framework
    • DeveloperDiskImage.dmg
    • MobileDevice.framework
  • DTXMessage 流

Usbmux

usbmuxd 的主要作用就是,通过 usb 来构建一条 socket 通道来实现 Mac OS 与 iOS 之间的通讯

windows 上则是 C:\Program Files\Common Files\Apple\Mobile Device Support\AppleMobileDeviceService.exe 驱动来监听 27105 端口与 iOS 之间进行通讯

想做相关工具开发,获取数据显然必不可少,下面监听 usbmuxd

### 转移 usbmuxd
sudo  mv /var/run/usbmuxd /var/run/usbmuxx
### 监听 usbmuxd 
sudo socat -t100 -x -v UNIX-LISTEN:/var/run/usbmuxd,mode=777,reuseaddr,fork UNIX-CONNECT:/var/run/usbmuxx 

### socat 工具是以文本来展示的,看起来相当费劲,
### 通过管道转到 wiershark 上面,wireshark 查看 usbmuxd 
sudo socat -d -d -lf /dev/stdout -x -v 2>&1   UNIX-LISTEN:/var/run/usbmuxd,mode=777,reuseaddr,fork UNIX-CONNECT:/var/run/usbmuxx | awk '/^[<>]/{a=0; $1 == "<" ? "I" : "0"; next}{$0 = substr($0, 1, 48);printf "%.4x %s\n",a,$0;a+=NF}' | text2pcap  - -  | wireshark  -ki -

### 很重要 !!!用完之后必须要复位,不然你的电脑之后就找不到手机了,必须重启电脑才行
sudo  mv /var/run/usbmuxx /var/run/usbmuxd

LaunchDaemon & Lcokdown

用于与设备配对并启动其他服务。
成功配对后才能访问其他服务。成功配对需要解锁设备,并且用户单击手机屏幕上的 “信任此设备”。后面主要都是与 Lcokdown 服务进行交互通讯

主要交互方式通过 plist 格式文件进行交互来启动相关服务,这里咱们使用 python plistlib 来进行 byte 流转换

例如启动 com.apple.syslog_relay 服务

2020-12-15 14:04:53,987 - plist_service.py[line:88] - DEBUG: 发送 Plist: {'Request': 'StartService', 'Label': 'pyMobileDevice', 'Service': 'com.apple.syslog_relay'}
2020-12-15 14:04:53,987 - plist_service.py[line:90] - DEBUG: 发送 Plist byte: b'<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict>\n\t<key>Label</key>\n\t<string>pyMobileDevice</string>\n\t<key>Request</key>\n\t<string>StartService</string>\n\t<key>Service</key>\n\t<string>com.apple.syslog_relay</string>\n</dict>\n</plist>\n'

Instruments 服务

想要与 Instruments 服务进行通讯,第一个我们需要找到入口,第二个我们需要知道交换 DTXMessage 数据流格式,并对其进行编解码

这里如何开启通道,以及数据解析,就要看向Troy Bowman 大佬在逆向工程大会上发布的演讲。

大佬的两个项目连接共大家参考

https://github.com/troybowman/dtxmsg

https://github.com/troybowman/ios_instruments_client

好了知道相关流程了,开始抄作业吧😄 😄
DTXMessage Header 头解析
https://github.com/troybowman/ios_instruments_client/blob/master/ios_instruments_client.h
https://github.com/facebook/idb/tree/master/PrivateHeaders IDB 仓库也有大量不同协议头,有兴趣的可自行查看,使用 python 构造 DTXMessage 头解析协议头示例

class DTXMessageHeader(Structure):
    _fields_ = [
        ('magic', c_uint32),
        ('cb', c_uint32),
        ('fragmentId', c_uint16),
        ('fragmentCount', c_uint16),
        ('length', c_uint32),
        ('identifier', c_uint32),
        ('conversationIndex', c_uint32),
        ('channelCode', c_uint32),
        ('expectsReply', c_uint32)
    ]


class DTXMessagePayloadHeader(Structure):
    _fields_ = [
        ('flags', c_uint32),
        ('auxiliaryLength', c_uint32),
        ('totalLength', c_uint64)
    ]

如何拿到 dtx 流数据可以参考项目:
https://github.com/danielpaulus/dtx_codec
该项目可以直接获取和模拟器进行交互时的 dtx ,可以用来测试数据解析是否正常。

sysmontap.py 示例效果展示

[
    {
        "PerCPUUsage": [
            {
                "CPU_NiceLoad": 0.0,
                "CPU_SystemLoad": -1.0,
                "CPU_TotalLoad": 11.881188118811878,
                "CPU_UserLoad": -1.0
            },
            {
                "CPU_NiceLoad": 0.0,
                "CPU_SystemLoad": -1.0,
                "CPU_TotalLoad": 17.0,
                "CPU_UserLoad": -1.0
            }
        ],
        "EndMachAbsTime": 656566442146,
        "CPUCount": 2,
        "EnabledCPUs": 2,
        "SystemCPUUsage": {
            "CPU_NiceLoad": 0.0,
            "CPU_SystemLoad": -1.0,
            "CPU_TotalLoad": 28.881188118811878,
            "CPU_UserLoad": -1.0
        },
        "Type": 33,
        "StartMachAbsTime": 656542341717
    },
    {
        "Processes": {
            "351": [
                351,            // pid 
                417710325760,   // memVirtualSize
                770048,         // memResidentSize
                0.0,            // cpuUsage
                528,
                -82,            
                934232,         // physFootprint
                819200,         // memAnon
                0.0,            // powerScore
                708608          // diskBytesRead
            ],
            "519": [
                519,
                418581921792,
                46628864,
                13.8574323237612,
                30281,
                6465,
                61965152,
                20381696,
                14.082756426586586,
                57790464
            ],
            "311": [
                311,
                417748434944,
                6635520,
                0.0,
                10189,
                43,
                1671552,
                1540096,
                0.0,
                22274048
            ],
            "271": [
                271,
                417744961536,
                4718592,
                0.0,
                8188,
                473,
                2130344,
                1998848,
                0.0,
                36442112
            ]
        },
        "Type": 5,
        "EndMachAbsTime": 656567535862,
        "StartMachAbsTime": 656542716738
    }
]

使用时注意事项及需求:

  • 电脑需要有 iOS 驱动 iTunes,Linux 安装 usbmuxd 驱动自行百度
  • 如果是 mac 可能没有 lockdown 权限,需要 sudo chmod 777 /var/db/lockdown/
  • 如果抛出 StartServiceError 异常,需要使用 xcode 激活一下设备,激活方式打开 xcode ,插上手机点击 “信任”,即可拔掉数据线。(理论上这一步也可以用脚本进行激活,暂未实现)

本项目源码地址:

https://github.com/YueChen-C/py-ios-device
既然看完了,要是能点个 star 就最好啦

本项目基于
https://github.com/iOSForensics/pymobiledevice 因为项目比较久,在项目基础上进行优化和修改并增加了 instruments 协议

参考项目:
https://github.com/troybowman/dtxmsg
https://github.com/troybowman/ios_instruments_client
https://github.com/danielpaulus/dtx_codec
https://github.com/libimobiledevice
https://github.com/facebook/idb

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
最佳回复
codeskyblue 回复

@codeskyblue 大佬终于开源了呀 ,最近也抽时间在看 xctest 相关,看大佬项目模拟器已经搞完了,赶紧去膜拜下的代码😝

共收到 22 条回复 时间 点赞

这个真的有点厉害。。。。

厉害,马上入手研究

5楼 已删除

转移 usbmuxd 这一步操作是为了什么?

想参与 怎么联系大佬

剪烛 回复

这个仅仅是为了抓包分析数据用的。正常情况不需要这样

李鹏 回复

有兴趣共同研究可加 v:cpj1352

卡斯 将本帖设为了精华贴 12月17日 06:49
YueChen 回复

已经添加 wx,感谢

执行:如果是 mac 可能没有 lockdown 权限,需要 sudo chmod 777 /var/db/lockdown/ 这可能提示无法修改,即便是使用了 sudo。 在 macOS 10.10 之后的系统需要先关闭内核保护才可。 重启 mac 电脑的时候按住 Command+R,进入恢复模式后(可能需要选择一个账户,再点击下一步) 状态栏:实用工具 - 终端,输入命令: csrutil disable reboot

老铁加油 !!!

李鹏 回复

新手一枚。有两个问题请教一下。 1、如何获取 APP NAME?get_processes 能返回所有进程和 name,我如果需要获取其中一个,我怎么获取对应的 appname 2、get_performance_by_process_id ;get_performance_by_app_name 其中需要回调函数作为参数,这个具体怎么用呢?回调函数是自己写一个么

YY 回复

这个直接查看源码 demo 里面有使用方法,问题 2 这种是其他小伙伴封装的使用方法

大佬,请教一下。我们这儿有个测试手机,之前运行都正常,但是突然开始出现以下错误,并且重启也无法恢复(换手机正常),请问遇见过吗?

2021-01-06 10:28:16,406 - lockdown.py[line:75] - ERROR: [Errno 13] Permission denied: '/var/db/lockdown/00008020-001E358234EA002E.plist'
Traceback (most recent call last):
  File "/Users/515/code/py-ios-device-main/instrument/RPC.py", line 214, in init
    self._cli = self.lockdown.start_service("com.apple.instruments.remoteserver")
  File "/Users/515/code/py-ios-device-main/util/lockdown.py", line 182, in start_service
    raise StartServiceError(f'Unable to start service={name!r} - {error}')
util.exceptions.StartServiceError: Unable to start service='com.apple.instruments.remoteserver' - SessionInactive

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/515/code/py-ios-device-main/demo/instrument_services/__init__.py", line 12, in <module>
    rpc = get_usb_rpc()
  File "/Users/515/code/py-ios-device-main/instrument/RPC.py", line 20, in get_usb_rpc
    if not rpc.init(DTXUSBTransport):
  File "/Users/515/code/py-ios-device-main/instrument/RPC.py", line 219, in init
    self._cli = self.lockdown.start_service("com.apple.instruments.remoteserver.DVTSecureSocketProxy")
  File "/Users/515/code/py-ios-device-main/util/lockdown.py", line 182, in start_service
    raise StartServiceError(f'Unable to start service={name!r} - {error}')
util.exceptions.StartServiceError: Unable to start service='com.apple.instruments.remoteserver.DVTSecureSocketProxy' - SessionInactive
剪烛 回复

看文章里这条,原因我也未知,有时候这个服务就会挂掉,需要连接下 xcode 或者 instruments 工具就能用了,instruments 挂掉了尝试激活 instruments 服务脚本我也在研究

如果抛出 StartServiceError 异常,需要使用 xcode 激活一下设备,激活方式打开 xcode ,插上手机点击 “信任”,即可拔掉数据线。(理论上这一步也可以用脚本进行激活,暂未实现)

YueChen 回复

但是奇怪就是,在出现这个错误的同时,Instruments 是可以正常获取数据的。我们尝试了很多方法,完全没办法,最后换手机了。orz 救命

增加一个 unix_socket.py 小工具,模拟 socat 中间人代理, 直接监听 iOS 与 usbmuxd 传输,可将数据包自动转换成明文数据,使用方法看 unix_socket.py

@YueChen 以前写的一个工具,经过层层审批终于开源了,多亏了你这个项目 https://github.com/alibaba/taobao-iphone-device

codeskyblue 回复

@codeskyblue 大佬终于开源了呀 ,最近也抽时间在看 xctest 相关,看大佬项目模拟器已经搞完了,赶紧去膜拜下的代码😝

YueChen 回复

我是拿真机搞的

codeskyblue 回复

大佬牛逼,真机试了下可以启动 wda 👍

YueChen 回复

是不是爽的飞起,终于可以不用 xcodebuild 了

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册