python-imobiledevice教程01 - 使用AfcClient和InstallationProxyClient后台安装应用

为了加深对libimobiledevice的理解,这两天用Python重写了几个核心服务的调用例子,放在了GitHub/python-imobiledevice_demo。这是教程的第一篇,讲解如何通过AfcClient和InstallationProxyClient实现上传IPA文件和后台安装App。

python调用imobiledevice依赖于 python-imobiledevice,可以获取libimobiledevice的Cython binding (包名为python-imobiledevice);MacOS也可以参考之前的搞定MacOS上的python-imobiledevice,两套Python真心害死人一文来从源码安装。

imobiledevice基础

使用python-imobiledevice需要先引入imobiledevice:

from imobiledevice import *

imobiledevice.iDevice() 会自动寻找已连接的iOS设备,或者可以指定UDID来等待某个设备的接入,设备的UDID可以通过libimobiledevice自带的idevice_id命令来查看。

设备上的服务,大多是通过LockdownClient来启动的,定义函数来从lockdown获取service_client:

def lockdown_get_service_client(service_class):
	ld = LockdownClient(iDevice())
	return ld.get_service_client(service_class)

之后就可以通过服务的Client名来获取对应的客户端了:

  afc = lockdown_get_service_client(AfcClient)
  instproxy = lockdown_get_service_client(InstallationProxyClient)

其中LockdownClient().get_service_client()实际上是内部封装了start_serviceafc = lockdown_get_service_client(AfcClient)等价于:

	dev = iDevice()
	ld = LockdownClient(dev)
	svrport = ld.start_service(AfcClient)
	afc = AfcClient(dev, svrport)

其中ld.start_service(AfcClient)虽然传递的是Class,实际获取的是cython/afc.pxi中的AfcClient.__service_name__,即 com.apple.afc。此时就可以AfcClient与AFC服务进行交互了。

使用AfcClient上传IPA文件

首先写一个上传文件的函数,将IPA的payload写入到iDevice中:

def afc_upload_file(filename, local_stream):
	afc = lockdown_get_service_client(AfcClient)

	# 使用afc服务打开filename文件,并写入local_stream中的所有内容
	testipa = afc.open(filename, mode="w+")
	testipa.write(local_stream.read())
	testipa.close()

这样,调用 afc_upload_file("test.ipa", open("payload/pangunew.ipa")) 就会将本地 payload/pangunew.ipa 的文件,写入到AFC根目录(默认为/private/var/mobile/Media)下的 test.ipa 文件中了。

相应的,afc还提供了其他目录文件操作,例如创建目录afc.make_directory(path)或删除文件/目录afc.remove_path(path)。不过这些操作都被限制在AFC的根目录/private/var/mobile/Media中。

另外还有两个很有用的函数:

	# 显示 / 下的所有文件和目录
	print afc.read_directory("/")
	# 获取test.ipa的信息
	print afc.get_file_info("test.ipa")

使用InstallationProxyClient安装上传的IPA

com.apple.mobile.installation_proxy 是一个用来查看/安装/升级/卸载/管理用户App的服务,这里只用到其中的upgrade()函数。更多可以参考libimobiledevice作者的另一个作品ideviceinstaller,其中使用了InstallationProxyClient的几乎全部功能。

这里用到的是upgrade而不是install,是因为upgrade不会理会App是否已经存在。另外参数部分第一次用到libplist,直接传一个空Dict即可:

import plist
def instproxy_install_file(filename):
	instproxy = lockdown_get_service_client(InstallationProxyClient)
	instproxy.upgrade(filename, plist.Dict({}))

另外演示InstallationProxyClient的一个功能,通过browse()查看已安装应用的信息:

	# dump application info
	client_options = plist.Dict({
		"ApplicationType": "User",		# Any, System, User
	})
	for app in instproxy.browse(client_options):
		print "[CFBundleIdentifier] %s" % app["CFBundleIdentifier"]
		print "[EnvironmentVariables] %s" % app["EnvironmentVariables"]

不过注意,目前libplist对中文支持好像有问题,导出System列表时居然引起Python core dump。反正不是很常用,不管了…

完整源码可以在GitHub找到:afc_and_instproxy_upgrade_ipa.py

关于Afc2Client

AFC (Apple File Conduit)是苹果自带的文件服务,其读写权限被限制在/private/var/mobile/Media下。而AFC2 (Apple File Conduit”2”)则是由Saurik编写的能够让已越狱机器,访问整个root文件系统(所以Afc2Client的根目录才是/)。

启动方法,在Cydia里搜索 Apple File Conduit”2” 并安装,然后将 AfcClient 换成 Afc2Client:

	afc2 = lockdown_get_service_client(Afc2Client)

ps: afc2确实是利器,之前成功用它删掉LaunchDaemons下的plist,救活因安装虚拟内存白苹果的iPad。附带一个afc的shellafc_shell.py,可以像ifuse一样上传下载和浏览机器的内容。