iPhone手机可以把屏幕镜像和音频等投射到AppleTV上,中间走的是AirPlay协议,这个是苹果的私有协议并没有对外公开,所以如果要实现类似AppleTV的功能的话基本只能通过逆向分析和抓包分析,之前为了实现将iPhone手机屏幕投射到车机屏幕上逆向了市面上一些同类产品,同时抓包研究分析,整个过程还是挺困难的。实际上AirPlay并不仅仅包含屏幕镜像和音频,同时还包括图片和视频推送等功能,而且自IOS4.2以来到现在整个协议也经历了多次升级和变化,网上虽然也可以找到一些资料,但是这些资料通常都比较古老,和最新的协议差别已经比较大了,参考价值有限。我这里因为手头机器有限,所以只针对于IOS9.3.2到IOS10.2.1之间的版本做了适配,这个范围以外的版本没做研究,而且只实现了屏幕镜像和音频的功能,视频推送部分没做研究,图片推送因为现在已经和屏幕镜像统一成一个流程,所以也是支持的。这里简单做个总结也算是对之前工作的一个记录。
2017-4-4 15:45:9
AirPlay协议浅析
要进行屏幕投射,iPhone手机端会首先通过Bonjour技术发现局域网中的AirPlay接收端服务。而Bonjour技术是基于mDNS协议实现的。关于mDNS部分,苹果已经开源了一个工程:mDNSResponder,可以进行参考,另外JAVA下也有一个开源实现jmdns,而在Android平台中,自Android4.1开始官方实现了一套Nsd API(网络服务发现),也实现了此功能。
当iPhone手机端发现接收端以后就会通过TCP连接上来,然后会通过RTSP协议进行交互,不过经过苹果的数次升级和变更,现在虽然表面上还是基于RTSP协议,不过已经和标准的RTSP协议区别很大了,如果想学习标准的RTSP协议,有个非常好的开源项目live555值得参考。接下来重点来了,我们的接收端需要处理来自手机端发来的各种请求命令,并且做出正确的响应。代码大致如下:
if (!strcmp(method,"POST") && !strcmp(uri,"/pair-setup")) {
res = request_handle_pairsetup(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method,"POST") && !strcmp(uri,"/pair-verify")) {
res = request_handle_pairverify(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "POST") && !strcmp(uri,"/fp-setup")) {
res = request_handle_fpsetup(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "POST") && !strcmp(uri, "/auth-setup")) {
res = request_handle_authsetup(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "GET") && !strcmp(uri, "/info")) {
res = request_handle_info(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "GET") && !strcmp(uri, "/stream.xml")) {
res = request_handle_streamxml(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "OPTIONS")) {
res = request_handle_options(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "ANNOUNCE")) {
res = request_handle_announce(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "SETUP")) {
res = request_handle_setup(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "SET_PARAMETER")) {
res = request_handle_setparameter(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "FLUSH")) {
res = request_handle_flush(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "TEARDOWN")) {
res = request_handle_teardown(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "RECORD")) {
res = request_handle_record(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "GET_PARAMETER")) {
res = request_handle_getparameter(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method,"POST") && !strcmp(uri,"/feedback")) {
res = request_handle_feedback(conn, request, res, &responseData, &responseDataLen);
}
其中有些命令是为了兼容老版本协议而存在的。下面是一次屏幕镜像流程抓包过程,保留了最重要的步骤:
POST /pair-setup RTSP/1.0
Content-Length: 32
Content-Type: application/octet-stream
CSeq: 0
DACP-ID: A246DFE975717F87
Active-Remote: 613909526
User-Agent: AirPlay/310.17
h芁郆?W..??.4.8U期k9D晴錟Ka黱}
RTSP/1.0 200 OK
Content-Type: application/octet-stream
Server: AirTunes/150.33
CSeq: 0
Date: Mon Mar 13 06:09:54 2017
Content-Length: 32
.T.2怺檲.驜柞hK?^%??7暩.珞鳽h
=--------------------------------------------------------------------------
POST /pair-verify RTSP/1.0
X-Apple-AbsoluteTime: 511078195
X-Apple-PD: 1
Content-Length: 68
Content-Type: application/octet-stream
CSeq: 1
DACP-ID: A246DFE975717F87
Active-Remote: 613909526
User-Agent: AirPlay/310.17
....p.HK鳡.?n3啎R<礍0頡?<鼑%逯[Qh芁郆?W..??.4.8U期k9D晴錟Ka黱}
RTSP/1.0 200 OK
Content-Type: application/octet-stream
Server: AirTunes/150.33
CSeq: 1
Date: Mon Mar 13 06:09:54 2017
Content-Length: 96
..d€鳢.H"8.}%赋瑮淨墛戩8?.q!P?愈M豌k槿-l尨.7`l<A?掺?kF筶.r臷t<
凤誹.?馡┦鰦籔汖.Q筬-.蚃~Hs?
=--------------------------------------------------------------------------
POST /pair-verify RTSP/1.0
X-Apple-AbsoluteTime: 511078195
X-Apple-PD: 1
Content-Length: 68
Content-Type: application/octet-stream
CSeq: 2
DACP-ID: A246DFE975717F87
Active-Remote: 613909526
User-Agent: AirPlay/310.17
....欭i?.HN?.|.~]浩!?n?MP€?;哵?Q墻`疡?.H?.烢磧W@譂蓥.n謈
RTSP/1.0 200 OK
Content-Type: application/octet-stream
Server: AirTunes/150.33
CSeq: 2
Date: Mon Mar 13 06:09:54 2017
=--------------------------------------------------------------------------
POST /fp-setup RTSP/1.0
X-Apple-ET: 32
Content-Length: 16
Content-Type: application/octet-stream
CSeq: 3
DACP-ID: A246DFE975717F87
Active-Remote: 613909526
User-Agent: AirPlay/310.17
FPLY...........?
RTSP/1.0 200 OK
Server: AirTunes/150.33
CSeq: 3
Date: Mon Mar 13 06:09:54 2017
Content-Length: 142
FPLY.......?.絤茣b\.T
囍L.n郒e(搽.I碶梸塆蚵硟鄪|?;驨IzP??\髤1?(%崂賵濃.o.?欺,.S+B靋|鋒忸?czdw??g.楷.E.嬿.徾*?w霔犵.<..设
=--------------------------------------------------------------------------
POST /fp-setup RTSP/1.0
X-Apple-ET: 32
Content-Length: 164
Content-Type: application/octet-stream
CSeq: 4
DACP-ID: A246DFE975717F87
Active-Remote: 613909526
User-Agent: AirPlay/310.17
FPLY.......????蠖?蝴>.
W弻輠.lI空綟?N.╮竵]<T??Г+.篠_蟨?k綂.踼.藡勺B騁?-}僖a(灚5.4Y_d.苏?.{?颴xw珗 àB...疛mf}?麖.偖?r澇v.駧"@錑悓t陦.+?I7'_"
RTSP/1.0 200 OK
Server: AirTunes/150.33
CSeq: 4
Date: Mon Mar 13 06:09:54 2017
Content-Length: 32
FPLY........駧"@錑悓t陦.+?I7'_"
=--------------------------------------------------------------------------
SETUP rtsp://192.168.1.103/7297403806869373166 RTSP/1.0
Content-Length: 412
Content-Type: application/x-apple-binary-plist
CSeq: 5
DACP-ID: A246DFE975717F87
Active-Remote: 613909526
User-Agent: AirPlay/310.17
bplist00?.......
..
.........TekeyRetUmodelXdeviceIDTname[sessionUUID]sourceVersionZtimingPort^osBuildVersionSeivZmacAddressO.HFPLY.......<....p~?FPyo灺[??...#a徟?鷴z&Y卻简?`旐?逑B蜦.+6OL邴Z? YiPhone5,2_..B8:78:2E:C8:66:B6ViPhone_.$65459619-5B08-40EE-8E66-F6843CAB144BV310.17.汴U14D27O..玧...敆憭.^s5`_..B8:78:2E:C8:66:B4.....$.'.-.6.;.G.U.`.o.s.~.?????....!.'.:...............................N
RTSP/1.0 200 OK
Content-Type: application/x-apple-binary-plist
Server: AirTunes/150.33
CSeq: 5
Date: Mon Mar 13 06:09:54 2017
Content-Length: 0
=--------------------------------------------------------------------------
GET /info RTSP/1.0
X-Apple-ProtocolVersion: 0
CSeq: 6
DACP-ID: A246DFE975717F87
Active-Remote: 613909526
User-Agent: AirPlay/310.17
RTSP/1.0 200 OK
Server: AirTunes/150.33
Server: AirTunes/150.33
CSeq: 6
Date: Mon Mar 13 06:09:54 2017
Content-Length: 823
bplist00?..... ........(*....
..... )+RpkO. .T.2怺檲.驜柞hK?^%??7暩.珞鳽h燭nameXApple TVRvv..[statusFlags.D_..keepAliveLowPower.._..keepAliveSendStatsAsBodyRpi_.$b08f5a79-db29-4384-b456-a4784d9e6055]sourceVersionV220.68XdeviceID_..00:24:D7:B2:2E:60ZmacAddressUmodelZAppleTV3,2\audioFormats?.?....._..audioInputFormats..黖..audioOutputFormatsTtype.d?......e^audioLatencies?'?$&.#%%.YaudioTypeWdefault_..inputLatencyMicros._..outputLatencyMicros?$&.#%%.Xfeatures.....Z鱔displays??/123456(9:.0%%%0.78%;Vheight...Uwidth...Xrotation]widthPhysical^heightPhysical[widthPixels\heightPixels[refreshRate.<..[overscannedTuuid_.$e5f7a68d-7b0f-4305-984b-974f677a150b...).,.P.U.^.a.c.o.q.????????? .....'.*.1.E.J._.d.f.m.o.~.????????????..........!./.>.J.W.c.e.g.s.x...............<...............?
=--------------------------------------------------------------------------
GET_PARAMETER rtsp://192.168.1.103/7297403806869373166 RTSP/1.0
Content-Length: 8
Content-Type: text/parameters
CSeq: 7
DACP-ID: A246DFE975717F87
Active-Remote: 613909526
User-Agent: AirPlay/310.17
volume
RTSP/1.0 200 OK
Audio-Jack-Status: connected; type=analog
Server: AirTunes/150.33
CSeq: 7
Date: Mon Mar 13 06:09:54 2017
Content-Length: 18
Volume: 1.000000
=--------------------------------------------------------------------------
RECORD rtsp://192.168.1.103/7297403806869373166 RTSP/1.0
CSeq: 8
DACP-ID: A246DFE975717F87
Active-Remote: 613909526
User-Agent: AirPlay/310.17
RTSP/1.0 200 OK
Audio-Jack-Status: connected; type=analog
Audio-Latency: 4410
Server: AirTunes/150.33
CSeq: 8
Date: Mon Mar 13 06:09:54 2017
=--------------------------------------------------------------------------
SET_PARAMETER rtsp://192.168.1.103/7297403806869373166 RTSP/1.0
Content-Length: 20
Content-Type: text/parameters
CSeq: 9
DACP-ID: A246DFE975717F87
Active-Remote: 613909526
User-Agent: AirPlay/310.17
volume: -20.000000
RTSP/1.0 200 OK
Audio-Jack-Status: connected; type=analog
Server: AirTunes/150.33
CSeq: 9
Date: Mon Mar 13 06:09:54 2017
=--------------------------------------------------------------------------
SETUP rtsp://192.168.1.103/7297403806869373166 RTSP/1.0
Content-Length: 188
Content-Type: application/x-apple-binary-plist
CSeq: 10
DACP-ID: A246DFE975717F87
Active-Remote: 613909526
User-Agent: AirPlay/310.17
bplist00?.Wstreams??.....]timestampInfoTtype_..streamConnectionID?.
..?
TnameUSubSu?.UBePxT?.UAfPxT?.UBefEn?.UEmEnc.n.8骚勾$B?....*/DJMRX[adjmsv|~...............................?
RTSP/1.0 200 OK
Content-Type: application/x-apple-binary-plist
Server: AirTunes/150.33
CSeq: 10
Date: Mon Mar 13 06:09:54 2017
Content-Length: 85
bplist00?.Wstreams??...Ttype.nXdataPort..?.....!*...............................-
=--------------------------------------------------------------------------
SET_PARAMETER rtsp://192.168.1.103/7297403806869373166 RTSP/1.0
Content-Length: 18
Content-Type: text/parameters
CSeq: 11
DACP-ID: A246DFE975717F87
Active-Remote: 613909526
User-Agent: AirPlay/310.17
volume: 1.000000
RTSP/1.0 200 OK
Audio-Jack-Status: connected; type=analog
Server: AirTunes/150.33
CSeq: 11
Date: Mon Mar 13 06:09:54 2017
=--------------------------------------------------------------------------
SET_PARAMETER rtsp://192.168.1.103/7297403806869373166 RTSP/1.0
Content-Length: 18
Content-Type: text/parameters
CSeq: 12
DACP-ID: A246DFE975717F87
Active-Remote: 613909526
User-Agent: AirPlay/310.17
volume: 1.000000
RTSP/1.0 200 OK
Audio-Jack-Status: connected; type=analog
Server: AirTunes/150.33
CSeq: 12
Date: Mon Mar 13 06:09:54 2017
=--------------------------------------------------------------------------
SET_PARAMETER rtsp://192.168.1.103/7297403806869373166 RTSP/1.0
Content-Length: 18
Content-Type: text/parameters
CSeq: 13
DACP-ID: A246DFE975717F87
Active-Remote: 613909526
User-Agent: AirPlay/310.17
volume: 0.000000
RTSP/1.0 200 OK
Audio-Jack-Status: connected; type=analog
Server: AirTunes/150.33
CSeq: 13
Date: Mon Mar 13 06:09:54 2017
=--------------------------------------------------------------------------
POST /feedback RTSP/1.0
CSeq: 14
DACP-ID: A246DFE975717F87
Active-Remote: 613909526
User-Agent: AirPlay/310.17
RTSP/1.0 200 OK
Content-Type: application/octet-stream
Server: AirTunes/150.33
CSeq: 14
Date: Mon Mar 13 06:09:56 2017
一. POST /pair-setup POST /pair-verify等命令用于配对验证,其中会用到ed25519,SHA-512,AES等算法。
二. POST /fp-setup命令是FairPlay相关,FairPlay是苹果公司开发的一种DRM(数字版权管理)技术,苹果的视频和音频传输都在这种技术的保护之下被AES加密后传输,这个FairPlay技术也是整个AirPlay中最难的部分,真的很难。
三. 第一个SETUP命令,手机端会传输大量的参数给到接收端,其中最重要的两个参数是ekey和eiv,它们经过一些变换之后要用于AES解密的。要重点说的是参数是用plist格式保存的,与标准的RTSP协议完全不同。
四. GET /info命令 手机端会通过此命令获取接收端的一些参数,比如接收端能接受的音频格式,能播放的视频长和宽,分辨率等。
五. GET_PARAMETER和SET_PARAMETER命令用于调整音量大小,比较简单。
六. RECORD命令好像没什么具体要做的,因为重要的事情都在SETUP命令里面做了。
七. 第二个SETUP命令,手机端通过这个命令通知接收端,准备建立屏幕镜像传输通道,命令中的type参数等于110,代表是屏幕镜像。接收端会在应答报文中将自己的接收端口告诉手机端,也就是dataPort这个参数,默认一般是7100。手机端会通过TCP连接上这个端口,然后将屏幕镜像通过h264编码以后再通过AES加密处理后通过这个端口源源不断的传给接收端,而具体的承载协议是苹果自定义的。
八. POST /feedback命令 每秒钟手机端都会向接收端发一次这个命令,我理解为定时保活的意思。
上面就是一个基本的流程。当然到目前为止还没包括音频推送,音频推送是直到手机需要播放音频的时候才开始推送的。比如说你在屏幕镜像过程中在手机上打开酷我音乐,选中一首歌点击播放,那么这个时候手机端才会向接收端发送推送请求,命令类似下面这个样子:
=--------------------------------------------------------------------------
SETUP rtsp://192.168.1.103/7297403806869373166 RTSP/1.0
Content-Length: 203
Content-Type: application/x-apple-binary-plist
CSeq: 33
DACP-ID: 17FF8AAF28964A47
Active-Remote: 1549979302
User-Agent: AirPlay/310.17
bplist00?.Wstreams??....
..
........^redundantAudioTtypeRct[controlPortSspf[audioFormatZlatencyMax[usingScreenZlatencyMin...`...赲..?......?..?...(7<?KO[fr}亙唹帒?..............................?
RTSP/1.0 200 OK
Content-Type: application/x-apple-binary-plist
Server: AirTunes/150.33
CSeq: 33
Date: Tue Mar 14 13:49:43 2017
Content-Length: 122
bplist00?
..Wstreams??.... XdataPort.?[controlPort.?Ttype.`ZtimingPort.?.
...'*69>@K...............................N
=--------------------------------------------------------------------------
也是一个SETUP命令,不过这个时候命令中的type参数的值为96,代表是音频,因为音频推送用的是RTP+RTCP协议的组合所以接收端需要在应答包中将自己的RTP端口和RTCP端口通知手机端。其中dataPort参数指的是RTP端口,controlPort参数指的是RTCP端口,而timingPort指的是NTP(Network Time Protocol)协议端口。这个命令之后,手机端会将输出的音频信号用AAC-ELD编码再通过AES加密然后用RTP协议封装后源源不断的发到接收端的RTP端口上。
当用户在手机上停止播放声音以后,过若干秒后,实测不到10秒,手机端就会发送TEARDOWN命令到接收端,结束音频推送,如下:
=------------------------------------------------------------------------
TEARDOWN rtsp://192.168.0.7/3621678346853826649 RTSP/1.0
Content-Length: 69
Content-Type: application/x-apple-binary-plist
CSeq: 47
DACP-ID: 17FF8AAF28964A47
Active-Remote: 1549979302
User-Agent: AirPlay/310.17
bplist00?.Wstreams??.Ttype.`......................................
RTSP/1.0 200 OK
Connection: close
Server: AirTunes/150.33
CSeq: 47
Date: Tue Mar 14 13:50:07 2017
=------------------------------------------------------------------------
其中命令中最关键的参数是type,它的值为96,代表要结束的是音频推送。结束以后如果用户又再次在手机上播放歌曲,手机端又会重新向接收端发送type=96的SETUP命令建立音频推送。
当用户在手机上点击关闭屏幕镜像功能以后,手机端会向接收端发送type=110的TEARDOWN命令,结束整个屏幕镜像过程。参考命令如下:
=------------------------------------------------------------------------
TEARDOWN rtsp://192.168.0.7/3621678346853826649 RTSP/1.0
Content-Length: 69
Content-Type: application/x-apple-binary-plist
CSeq: 50
DACP-ID: 17FF8AAF28964A47
Active-Remote: 1549979302
User-Agent: AirPlay/310.17
bplist00?.Wstreams??.Ttype.n......................................
RTSP/1.0 200 OK
Connection: close
Server: AirTunes/150.33
CSeq: 50
Date: Tue Mar 14 13:50:10 2017
=------------------------------------------------------------------------
总结整个流程,正常情况下会有一个SETUP(type=110)/TEARDOWN(type=110),零到若干个SETUP(type=96)/TEARDOWN(type=96)。
另外如果只进行音频推送,而不进行屏幕镜像的话,那么整个流程与上面介绍的基本相同,只不过传递ekey和eiv的那个SETUP命令与建立音频推送的SETUP(type=96)命令合在了一起,另外手机端还会利用SET_PARAMETER命令将比如歌曲相关的图片传给接收端。还有就是在实测中发现只进行音频推送,而不进行屏幕镜像的话,音频采用的是ALAC (Apple lossless audio codec)进行编码,而不是AAC-ELD。
最后就是多路投射问题,也就是局域网内有多台苹果手机和一个接收端,多台手机能否同时进行投射,这个通过分析确认应该是可以实现的,没有问题。