AirPlay2技術淺析
手打目錄
-
前言
-
App的選取
- 投屏測試
-
抓包分析
- 抓包
- mDNS分析
- 握手協議
-
- info
-
- pair-setup
-
- pair-verify
-
- 第二個pair-verify
-
- 兩次fp-setup
-
-
映象資料
-
- 第一次SETUP
-
- 第二次SETUP
-
- 映象資料傳送分析
-
- 映象資料解密
-
-
音訊資料
-
- 抓包分析
-
- 第三個SETUP
-
- 程式碼分析
-
- timingPort互動
-
- controlPort互動
-
- 音訊資料傳送分析
-
- 音訊資料解密
-
-
其他請求
-
- GET_PARAMETER
-
- SET_PARAMETER
-
- feedback
-
- TEARDOWN-1
-
-
實現
-
參考連結
-
附件
前言
本文針對AirPlay2協議,選取了一款上線的app並進行逆向分析,實現了對AirPlay2協議的破解,並實現了投屏demo。
以下是本文涉及到的一些知識點:
- 協議:RTP,RTSP,DNS,DNS-SD,mDNS,NTP
- 加解密演算法:curve25519,ed25519,AES(cbc&ctr)
- 音視訊基礎:h264,aac
- Android中dex格式與so的逆向
App的選取
對於AirPlay的破解,國內外均有相關App實現,注意用途是將iPhone裝置的內容投屏到電視或者PC上。
這裡選取了國內投屏用的比較多的X播投屏,下載了最新版發現是加殼了,所以找了下老版本,所幸找到了7.1.0版本並未加殼,用IOS12測試可以正常投屏,本文針對此版本App進行逆向分析。
投屏測試
開啟投屏App,然後在iPhone中上滑->螢幕映象中找到投屏裝置,點選即可在App上看到iPhone畫面了。
IOS是如何發現裝置?連線過程是怎麼樣?資料是怎麼傳輸的?帶著這些疑問我們往下分析
以下會用server代表Android裝置,client代表IOS裝置
抓包分析
抓包
這裡用了root過的小米,小米root比較簡單,刷個開發版本即可,裝個最新版本 tcpdump
開始抓包
./data/local/tmp/tcpdump -i any -p -s 0 -w /sdcard/airplay.pcapng
開啟app前開始抓包,一直到投屏結束,並使用WireShark進行資料包分析
mDNS分析
server釋出了mDNS廣播如下圖所示:

image
這裡有4條DNS記錄:
-
一條A記錄
MIX2S-xiaomishouji.local: type A
-
3條SRV記錄:代表三個服務,用於DNS-SD
\344\271\220\346\212\225V2._airplay._tcp.local aa5401afc3c1@\344\271\220\346\212\225V2._raop._tcp.local \344\271\220\346\212\225V2._leboremote
因為X播裡面不止是iPhone投屏還有其他投屏,需要對這些服務進行篩選,所以這裡需要看下client最終用了哪些服務。
過濾條件修改為 ip.src==172.18.145.3 && mdns
,看下client端查詢的服務

image
這裡可以看到client只使用了raop和airplay兩個服務,查詢 SRV記錄
內容可以得知 airplay
使用了 52233
埠, raop
使用了 52244
埠
在frame146中,server傳送了回覆的組播包

image
由此可以得知,client和server是通過mDNS和DNS-SD實現了零配置網路,找到server之後便是client和server的互動
握手協議
過濾條件修改為 (ip.src==172.18.145.2 && ip.dst==172.18.145.3) || (ip.src==172.18.145.3 && ip.dst==172.18.145.2)
,過濾結果如圖所示:

image
首先是三次握手,從 frame230
開始傳送請求,可以發現server埠是 52244
即使用了 raop
服務
過濾 52233
埠發現沒有結果,說明client是沒有使用 airplay
服務。
下面針對選中230,Analyze->Follow->TCP Stream可以看到整個互動過程

image
具體內容如下
GET /info RTSP/1.0 X-Apple-ProtocolVersion: 1 Content-Length: 70 Content-Type: application/x-apple-binary-plist CSeq: 0 DACP-ID: D18733453E686899 Active-Remote: 252920595 User-Agent: AirPlay/371.4.7 bplist00...Yqualifier..ZtxtAirPlay..................................." RTSP/1.0 200 OK Content-Length: 836 Date: Tue, 18 Dec 2018 01:32:17 GMT Content-Type: application/x-apple-binary-plist Server: AirTunes/220.68 bplist00.......YaudioType........ .....$&(*..... ...%')+TtypeXdisplaysTuuid_..audioInputFormatsXfeatures[refreshRate.. "..!!._..aa:54:01:af:c3:c1...dUmodel.<VheightZAppleTV2,1]sourceVersion_..keepAliveLowPower.-/123456(9;<.0!!!0.78:!=]widthPhysicalV220.68.......[overscanned[widthPixelsO. .w'...n....R^....R..h?.!....$eT.ZmacAddress...,.....\audioFormatsTname.Rvv.....Z..._..inputLatencyMicros[statusFlagsWAppleTV.. "..!!.Wdefault_.$2e388006-13ba-4041-9a67-25dd4a43d536......._..outputLatencyMicros^audioLatenciesXrotation..\heightPixelsVmaxFPSXdeviceID_..audioOutputFormats_.$e0ff8a27-6738-3d56-8a16-cc53aacee925_..keepAliveSendStatsAsBody^heightPhysical.eUwidthRpiRpk..#..8............R.C...".d...j.N.....g.....W.T...+. .:...M...............v.i...v... .....a.m.?.P.....................j...........H.@...............>................ POST /pair-setup RTSP/1.0 Content-Length: 32 Content-Type: application/octet-stream CSeq: 1 DACP-ID: D18733453E686899 Active-Remote: 252920595 User-Agent: AirPlay/371.4.7 ...............f.......|..1...Rt RTSP/1.0 200 OK Content-Type: application/octet-stream Content-Length: 32 Server: AirTunes/220.68 CSeq: 1 ....M.r..Ej!S.......d...r...l.P3 POST /pair-verify RTSP/1.0 X-Apple-PD: 1 X-Apple-AbsoluteTime: 566789538 Content-Length: 68 Content-Type: application/octet-stream CSeq: 2 DACP-ID: D18733453E686899 Active-Remote: 252920595 User-Agent: AirPlay/371.4.7 .....K>..2?}.c...Z8...y.....X..h.i37...............f.......|..1...Rt RTSP/1.0 200 OK Content-Type: application/octet-stream Content-Length: 96 Server: AirTunes/220.68 CSeq: 2 ..f..(..abme......&> |k.....g.4'....r...'/jb..J ..p.tl..g.....?....F..+..\ ..7u.~.xs..|.. .F.... POST /pair-verify RTSP/1.0 X-Apple-PD: 1 X-Apple-AbsoluteTime: 566789538 Content-Length: 68 Content-Type: application/octet-stream CSeq: 3 DACP-ID: D18733453E686899 Active-Remote: 252920595 User-Agent: AirPlay/371.4.7 .............|.<....-..s.w...w....r...K.Lp...}.L ..Q....r_o...T.k2." RTSP/1.0 200 OK Content-Type: application/octet-stream Content-Length: 0 Server: AirTunes/220.68 CSeq: 3 POST /fp-setup RTSP/1.0 X-Apple-ET: 32 Content-Length: 16 Content-Type: application/octet-stream CSeq: 4 DACP-ID: D18733453E686899 Active-Remote: 252920595 User-Agent: AirPlay/371.4.7 FPLY............ RTSP/1.0 200 OK Content-Length: 142 Date: Tue, 18 Dec 2018 01:32:17 GMT Server: AirTunes/220.68 Content-Type: application/octet-stream FPLY..............G.....W.i5...........F....}....vd.Jk.E._6.l@.F.7.,.o^..?..zeW`...h..A> SK-<....g.e.-F.YE..|y....GF).......z....V.@...=.u.... POST /fp-setup RTSP/1.0 X-Apple-ET: 32 Content-Length: 164 Content-Type: application/octet-stream CSeq: 5 DACP-ID: D18733453E686899 Active-Remote: 252920595 User-Agent: AirPlay/371.4.7 FPLY..................U.B..^S,..}.m#W.].I.X......D.dd.D....#....^n.s..]~/. ....Z....g....q...f.'/CT.(..L...+.?....[.r..t..E.......O.up.....6q>2.....L........C.....% RTSP/1.0 200 OK Content-Length: 32 Date: Tue, 18 Dec 2018 01:32:17 GMT Server: AirTunes/220.68 Content-Type: application/octet-stream FPLY............L........C.....%
接下來一個一個看相關請求
1. info
請求包內容如下圖所示:

image
請求和回包都是bplist格式,解析出來看下
client->server
<plist version="1.0"> <dict> <key>qualifier</key> <array> <string>txtAirPlay</string> </array> </dict> </plist>
server->client
<plist version="1.0"> <dict> <key>sourceVersion</key> <string>220.68</string> <key>statusFlags</key> <integer>4</integer> <key>macAddress</key> <string>aa:54:01:af:c3:c1</string> <key>deviceID</key> <string>aa:54:01:af:c3:c1</string> <key>name</key> <string>AppleTV</string> <key>vv</key> <integer>2</integer> <key>keepAliveLowPower</key> <integer>1</integer> <key>keepAliveSendStatsAsBody</key> <integer>1</integer> <key>pi</key> <string>2e388006-13ba-4041-9a67-25dd4a43d536</string> <key>audioFormats</key> <array> <dict> <key>audioOutputFormats</key> <integer>33554428</integer> <key>type</key> <integer>100</integer> <key>audioInputFormats</key> <integer>33554428</integer> </dict> <dict> <key>audioOutputFormats</key> <integer>33554428</integer> <key>type</key> <integer>101</integer> <key>audioInputFormats</key> <integer>33554428</integer> </dict> </array> <key>audioLatencies</key> <array> <dict> <key>audioType</key> <string>default</string> <key>inputLatencyMicros</key> <false/> <key>outputLatencyMicros</key> <false/> <key>type</key> <integer>100</integer> </dict> <dict> <key>audioType</key> <string>default</string> <key>inputLatencyMicros</key> <false/> <key>outputLatencyMicros</key> <false/> <key>type</key> <integer>101</integer> </dict> </array> <key>pk</key> <data> sHcn1vbNbgi1jt5SXsPN6qJSrZ9oP+shLviiBSRlVOc= </data> <key>model</key> <string>AppleTV2,1</string> <key>features</key> <integer>130367356919</integer> <key>displays</key> <array> <dict> <key>height</key> <integer>1080</integer> <key>width</key> <integer>1920</integer> <key>rotation</key> <false/> <key>widthPhysical</key> <false/> <key>heightPhysical</key> <false/> <key>widthPixels</key> <integer>1920</integer> <key>heightPixels</key> <integer>1080</integer> <key>refreshRate</key> <integer>60</integer> <key>features</key> <integer>14</integer> <key>maxFPS</key> <integer>30</integer> <key>overscanned</key> <false/> <key>uuid</key> <string>e0ff8a27-6738-3d56-8a16-cc53aacee925</string> </dict> </array> </dict> </plist>
這裡基本是回覆server支援的特性,具體可看 參考連結[8] ,這裡不做分析
2. pair-setup
client傳送32位元組 <-> server回覆32位元組
搜尋字串"pair-setup",找到如下程式碼

image
FdkDecodeAudioFun8
引數分析
- 第1個引數 client請求包
- 第2個引數 client請求包長度
- 第3個引數jg server->client回包內容
- 第4個引數out_size 回包長度
- 第5個引數 1
- 第6個引數pairSessionId 連線上下文,看程式碼是支援最多16個裝置連線
進一步分析so,使用ida開啟libhpplayaudio.so找到 FdkDecodeAudioFun8
查下這個函式的呼叫關係,如下圖所示

image
看起來挺複雜,分析中找到ed25519這個關鍵詞,是個,搜尋發現是已有演算法,找到原始碼之後和彙編程式碼進行對比是可以匹配上。
現在只需要關注 FdkDecodeAudioFun8
的實現
.text:000C7BF8 ; signed int __fastcall Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun8(_JNIEnv *a1, int a2, int a3, int a4, int a5, int a6, int a7, unsigned int a8) .text:000C7BF8EXPORT Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun8 .text:000C7BF8 Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun8 .text:000C7BF8; DATA XREF: LOAD:0000149C↑o .text:000C7BF8 .text:000C7BF8 var_38= -0x38 .text:000C7BF8 var_2C= -0x2C .text:000C7BF8 arg_0=0 .text:000C7BF8 arg_4=4 .text:000C7BF8 arg_C=0xC .text:000C7BF8 arg_558=0x558 .text:000C7BF8 arg_C7D04=0xC7D04 .text:000C7BF8 .text:000C7BF8 ; __unwind { .text:000C7BF8PUSH.W{R4-R11,LR} .text:000C7BFCSUBSP, SP, #0x14 .text:000C7BFEMOVR7, R0 .text:000C7C00MOVR10, R2 .text:000C7C02LDRR6, [SP,#0x38+arg_C] .text:000C7C04CMPR6, #0x10 .text:000C7C06BHIloc_C7CEA .text:000C7C08LDRR4, =(unk_254118 - 0xC7C10) .text:000C7C0ALSLSR6, R6, #2 .text:000C7C0CADDR4, PC; unk_254118 .text:000C7C0EADDR4, R6 .text:000C7C10LDR.WR3, [R4,#0x558] ; unk_25518+sessionid*4+0x558 .text:000C7C14CMPR3, #0 .text:000C7C16BEQloc_C7CF0 .text:000C7C18MOVR1, R2 .text:000C7C1AMOVSR2, #0 .text:000C7C1CBL_ZN7_JNIEnv20GetByteArrayElementsEP11_jbyteArrayPh ; _JNIEnv::GetByteArrayElements(_jbyteArray *,uchar *) .text:000C7C20MOVR8, R0; raw_data .text:000C7C22CMPR0, #0 .text:000C7C24BEQloc_C7CF6 .text:000C7C26MOVR0, R7 .text:000C7C28LDRR1, [SP,#0x38+arg_4] .text:000C7C2AMOVSR2, #0 .text:000C7C2CBL_ZN7_JNIEnv19GetIntArrayElementsEP10_jintArrayPh ; _JNIEnv::GetIntArrayElements(_jintArray *,uchar *) .text:000C7C30LDR.WR11, [R4,#0x558] ; R11=unk_25518+sessionid*4+0x558 .text:000C7C34LDR.WR5, [R11,#4] ; unk_25518+sessionid*4+0x558 地址的值 + 4,再取值,說明是個結構體 .text:000C7C38MOVR9, R0; out_size .text:000C7C3ACBNZR5, loc_C7C86 .text:000C7C3CMOVSR0, #0xE4 ; size .text:000C7C3EBLXmalloc; E4=228 .text:000C7C42MOVR1, R5; c .text:000C7C44MOVSR2, #0xE4 ; n .text:000C7C46STR.WR0, [R11,#4] ; 寫入申請記憶體的地址 .text:000C7C4ALDR.WR3, [R4,#0x558] ; R4=unk_25518+sessionid*4 .text:000C7C4ELDRR0, [R3,#4] ; s .text:000C7C50BLXmemset .text:000C7C54 .text:000C7C54 loc_C7C54; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun8+7A↓j .text:000C7C54LDR.WR3, [R4,#0x558] .text:000C7C58LDRR3, [R3,#4] ; R3=unk_25518+sessionid*4+0x558+4 .text:000C7C5ASTRR3, [SP,#0xC] .text:000C7C5CBLXlrand48 .text:000C7C60MOVR11, R0 .text:000C7C62BLXlrand48 .text:000C7C66LDRR3, [SP,#0xC] .text:000C7C68SMULBB.WR0, R0, R11 ; R0=lrand48*lrand48 .text:000C7C6CSTRBR0, [R3,R5] .text:000C7C6EADDSR5, #1 .text:000C7C70CMPR5, #0x20 ; ' ' .text:000C7C72BNEloc_C7C54 .text:000C7C74LDR.WR3, [R4,#0x558] .text:000C7C78LDRR2, [R3,#4] .text:000C7C7AADD.WR0, R2, #0x60 .text:000C7C7EADD.WR1, R2, #0x20 .text:000C7C82BLed25519_create_keypair .text:000C7C86 .text:000C7C86 loc_C7C86; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun8+42↑j .text:000C7C86LDRR3, =unk_18C486 .text:000C7C88ADD.WR0, R8, #0x20 ; R8=raw_data .text:000C7C8CMOVR2, R8 .text:000C7C8EADDR3, PC; unk_254118 .text:000C7C90ADDR6, R3 .text:000C7C92LDR.WR1, [R6,#0x558] .text:000C7C96LDRR3, [R1,#4] .text:000C7C98ADDSR3, #0x80 ; 從128位元組開始寫入 .text:000C7C9A .text:000C7C9A loc_C7C9A; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun8+AC↓j .text:000C7C9ALDR.WR4, [R2],#4 .text:000C7C9ECMPR2, R0 .text:000C7CA0STR.WR4, [R3],#4 ; 複製raw_data到記憶體 .text:000C7CA4BNEloc_C7C9A .text:000C7CA6LDRR3, [R1,#4] .text:000C7CA8MOVR0, R7; a1 .text:000C7CAALDRR1, [SP,#0x38+arg_0] ; jg .text:000C7CACMOVSR2, #0; jsize .text:000C7CAEADDSR3, #0x60 ; '`' .text:000C7CB0STRR3, [SP,#0x38+var_38] ; sp[0]=堆疊地址+96即pk的地址 .text:000C7CB2MOVSR3, #dword_20 ; 32位元組 .text:000C7CB4BL_ZN7_JNIEnv18SetByteArrayRegionEP11_jbyteArrayiiPKa ; SetByteArrayRegion(this, array(R1), start(R2), len(R3), buf) .text:000C7CB8MOVSR3, #0x20 ; ' ' .text:000C7CBAMOVR0, R7 .text:000C7CBCSTR.WR3, [R9] .text:000C7CC0LDRR1, [SP,#0x38+arg_4] .text:000C7CC2MOVSR2, #0 .text:000C7CC4MOVSR3, #1 .text:000C7CC6STR.WR9, [SP,#0x38+var_38] .text:000C7CCABL_ZN7_JNIEnv17SetIntArrayRegionEP10_jintArrayiiPKi ; _JNIEnv::SetIntArrayRegion(_jintArray *,int,int,int const*) .text:000C7CCEMOVR0, R7 .text:000C7CD0MOVR1, R10 .text:000C7CD2MOVR2, R8 .text:000C7CD4MOVSR3, #0 .text:000C7CD6BL_ZN7_JNIEnv24ReleaseByteArrayElementsEP11_jbyteArrayPai ; _JNIEnv::ReleaseByteArrayElements(_jbyteArray *,signed char *,int) .text:000C7CDAMOVR0, R7 .text:000C7CDCLDRR1, [SP,#0x38+arg_4] .text:000C7CDEMOVR2, R9 .text:000C7CE0MOVSR3, #0 .text:000C7CE2BL_ZN7_JNIEnv23ReleaseIntArrayElementsEP10_jintArrayPii ; _JNIEnv::ReleaseIntArrayElements(_jintArray *,int *,int) .text:000C7CE6MOVSR0, #0 .text:000C7CE8Bloc_C7CFA .text:000C7CEA ; --------------------------------------------------------------------------- .text:000C7CEA .text:000C7CEA loc_C7CEA; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun8+E↑j .text:000C7CEAMOVR0, #0xFFFFFFF8 .text:000C7CEEBloc_C7CFA .text:000C7CF0 ; --------------------------------------------------------------------------- .text:000C7CF0 .text:000C7CF0 loc_C7CF0; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun8+1E↑j .text:000C7CF0MOVR0, #0xFFFFFFF7 .text:000C7CF4Bloc_C7CFA .text:000C7CF6 ; --------------------------------------------------------------------------- .text:000C7CF6 .text:000C7CF6 loc_C7CF6; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun8+2C↑j .text:000C7CF6MOV.WR0, #0xFFFFFFFF .text:000C7CFA .text:000C7CFA loc_C7CFA; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun8+F0↑j .text:000C7CFA; Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun8+F6↑j ... .text:000C7CFAADDSP, SP, #0x14 .text:000C7CFCPOP.W{R4-R11,PC} .text:000C7CFC ; End of function Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun8
根據分析,畫下目前記憶體裡面的資料

image
重點觀察下 ed25519_create_keypair
函式
void ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed) { ge_p3 A; sha512(seed, 32, private_key); private_key[0] &= 248; private_key[31] &= 63; private_key[31] |= 64; ge_scalarmult_base(&A, private_key); ge_p3_tobytes(public_key, &A); }
根據 .text:000C7CB4 BL _ZN7_JNIEnv18SetByteArrayRegionEP11_jbyteArrayiiPKa ;
可以發現server傳送給client的32位元組是ed25519生成的publickey
3. pair-verify
client傳送68位元組(前4個位元組是 01 00 00 00) <-> server回覆96位元組
client請求包剩餘64個位元組內容

image
同樣的,找到 FdkDecodeAudioFun9
函式
FdkDecodeAudioFun9
引數分析
- 第1個引數 client請求包
- 第2個引數 client請求包長度(68)
- 第3個引數jg server->client回包內容
- 第4個引數out_size 回包長度
- 第5個引數 1
- 第6個引數pairSessionId 連線上下文,看程式碼是支援最多16個裝置連線
檢視下 FdkDecodeAudioFun9
函式呼叫關係圖如下

image
大體看下整個函式結構,還是有點複雜,具體函式分析如下
文章過長此部分刪除
經過分析之後,此時記憶體結構圖如下

image
回包是96位元組
第1部分是(32位元組)是ecdh_ours
第2部分是(64位元組)是(ecdh_ours + ecdh_theirs)的ed25519簽名,再經過AES加密之後的資料
- 關鍵程式碼1,生成ecdh_ours和ecdh_private
curve25519_donna
看到這個關鍵函式,Curve25519目前應用廣泛的Diffie-Hellman函式,通過交換一些公開的資料就能讓通訊雙方相互算出金鑰的演算法
還是搜搜 curve25519_donna
找到這個函式的原始碼
int curve25519_donna(unsigned char *mypublic, const unsigned char *secret, const unsigned char *basepoint);
是否和我們猜測一致,或者有什麼坑,我們繼續往下分析
ida中unk_1FDE68 一個9 31個0 和 basepoint是一致的,再對比下相關呼叫函式可以確認是對應的
- 關鍵程式碼2,生成ed25519簽名
void ed25519_sign(unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key, const unsigned char *private_key)
函式引數
sig_msg = ecdh_ours + ecdh_theirs public_key = ed_ours private_key = ed_private(後32位元組被替換,確認了多次)
- 關鍵程式碼3,生成AES加密key
生成key
sha512_init sha512_update ->"Pair-Verify-AES-Key" sha512_update -> ecdh_secret sha512_final -> sha512_1
- 關鍵程式碼4,生成AES加密iv
生成iv
sha512_init sha512_update -> "Pair-Verify-AES-IV" sha512_update -> ecdh_secret sha512_final -> sha512_2
- 加密演算法判斷
有了key和iv然後是加密
需要關注的是 f1116c0
和 f1117b0
函式,一開始不知道這兩個函式是幹嘛的,不過看演算法比較複雜,應該是已有演算法,我們進行跟進發現
f1117b0 用到了 unk_118FE8
進去看

image
根據這個線索我們google下 0xC3 0x72 0x16 0x1D
找到 https://gnupg.org/ftp/gcrypt/historic/rijndael.c
,所以這個是aes加密演算法,這樣就比較簡單了,繼續搜尋找到了 https://www.ghostscript.com/doc/base/aes.c
和此函式對應關係更大是對應的, f1117b0
為初始化key函式 對應 aes_setkey_enc
函式
f1117b0
引數分析
- 第1個引數 304基地址()
- 第2個引數 sha512_1 前16位元組作為key
- 第3個引數 128
f1116c0
是最終加密函式,通過與AES幾個加密方式的對比,確認為CTR加密
f1116c0
引數分析
- 第1個引數 304基地址
- 第2個引數 64
- 第3個引數 iv_off
- 第4個引數 sha512_2 前16位元組作為初始iv
- 第5個引數 16b位元組的地址
- 第6個引數 輸入字串
- 第7個引數 輸出字串
4. 第二個pair-verify
client傳送68位元組(前4個位元組是 00 00 00 00) <-> server回覆0位元組
和第一個pair-verify一樣,在 FdkDecodeAudioFun9
函式中
- 關鍵函式
int ed25519_verify(const unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key)
ed25519_verify
引數分析
- 第1個引數 是client發過來的64位元組簽名,用作校驗
- 第2個引數 是簽名的訊息
- 第3個引數 簽名訊息的長度
- 第4個引數 client的公鑰
根據校驗結果,正確則繼續,錯誤則斷開連線
5. 兩次fp-setup
- 第一次fp-setup
client傳送16位元組 <-> server回覆142位元組
- 第二次fp-setup
client傳送164 <->server回覆 32
兩次fp-setup分別對應FdkDecodeAudioFun1和FdkDecodeAudioFun2,兩個函式的呼叫關係如下圖

image

image
看起來非常複雜,分析起來耗時耗力
這裡有兩個思路:
- 匯出此部分彙編程式碼,直接呼叫函式執行
- 直接呼叫so中的函式
雖然這兩個方法都可以達到目的,程式碼就變得不可控了,後經過各種搜尋偶然找到一個airplay1的專案 shairplay ,裡面有這兩個函式實現,但是程式碼比較老了,是七年前的專案。
抱著試一試的心態集成了下,發現是可以正常使用的,本文demo也是基於此開原始碼,做了大量修改。
映象資料
在握手協議之後,傳送映象資料之前會有兩次SETUP請求(在資料分析中會用到)
SETUP rtsp://172.18.145.2/4882189185445544350 RTSP/1.0 Content-Length: 535 Content-Type: application/x-apple-binary-plist CSeq: 6 DACP-ID: D18733453E686899 Active-Remote: 252920595 User-Agent: AirPlay/371.4.7 bplist00........... .. .................RetSeiv^timingProtocol[sessionUUIDVosName^osBuildVersion]sourceVersionZtimingPort_..isScreenMirroringSessionYosVersionTekeyXdeviceIDUmodelTnameZmacAddress. O....mD..9o.YRR.0./SNTP_.$43C10532-7CBC-419E-9BB3-528F7D6F9AE0YiPhone OSV16A404W371.4.7... V12.0.1O.HFPLY.......<.....nT=......9..X......w.Jw9.t.v..iK.c....Tj.u..G..KL.....X_..DC:0C:5C:B7:D6:DAYiPhone9,1jT..2v.. .i.P.h.o.n.e_..DC:0C:5C:B7:D6:D8...).,.0.?.K.R.a.o.z....................... ....... .'.r...................................... RTSP/1.0 200 OK Content-Length: 0 Server: AirTunes/220.68 CSeq: 6 SETUP rtsp://172.18.145.2/4882189185445544350 RTSP/1.0 Content-Length: 188 Content-Type: application/x-apple-binary-plist CSeq: 10 DACP-ID: D18733453E686899 Active-Remote: 252920595 User-Agent: AirPlay/371.4.7 bplist00...Wstreams.........Ttype]timestampInfo_..streamConnectionID.n. ..... .TnameUSubSu. UBePxT. .UAfPxT. .UBefEn. .UEmEnc.D...6QD......!/DFLOTZ]cfloux~................................ RTSP/1.0 200 OK Content-Length: 120 Date: Tue, 18 Dec 2018 01:32:18 GMT Content-Type: application/x-apple-binary-plist Server: AirTunes/220.68 bplist00..l.n.....YeventPort...ZtimingPortWstreamsXdataPort....cTtype... ..E*; 2.@....=...............................L
1. 第一次SETUP
client -> server
<plist version="1.0"> <dict> <key>et</key> <integer>32</integer> <key>eiv</key> <data> Bp5tRB8BOW/MWVJSGzALLw== </data> <key>timingProtocol</key> <string>NTP</string> <key>sessionUUID</key> <string>43C10532-7CBC-419E-9BB3-528F7D6F9AE0</string> <key>osName</key> <string>iPhone OS</string> <key>osBuildVersion</key> <string>16A404</string> <key>sourceVersion</key> <string>371.4.7</string> <key>timingPort</key> <integer>60373</integer> <key>isScreenMirroringSession</key> <true/> <key>osVersion</key> <string>12.0.1</string> <key>ekey</key> <data> RlBMWQECAQAAAAA8AAAAALFuVD0C1qvRjZI5wtJY4v0AAAAQd5dKdzn2dNJ2ysNpS4VjnfmFHlRqEnXFqUeXzEtMDLIdF/5Y </data> <key>deviceID</key> <string>DC:0C:5C:B7:D6:DA</string> <key>model</key> <string>iPhone9,1</string> <key>name</key> <string>xxx的 iPhone</string> <key>macAddress</key> <string>DC:0C:5C:B7:D6:D8</string> </dict> </plist>
server->client
空
2. 第二次SETUP
client->server
<plist version="1.0"> <dict> <key>streams</key> <array> <dict> <key>type</key> <integer>110</integer> <key>timestampInfo</key> <array> <dict> <key>name</key> <string>SubSu</string> </dict> <dict> <key>name</key> <string>BePxT</string> </dict> <dict> <key>name</key> <string>AfPxT</string> </dict> <dict> <key>name</key> <string>BefEn</string> </dict> <dict> <key>name</key> <string>EmEnc</string> </dict> </array> <key>streamConnectionID</key> <integer>4964383553955644435</integer> </dict> </array> </dict> </plist>
server->client
<plist version="1.0"> <dict> <key>streams</key> <array> <dict> <key>dataPort</key> <integer>7020</integer> <key>type</key> <integer>110</integer> </dict> </array> <key>eventPort</key> <integer>52244</integer> <key>timingPort</key> <integer>7011</integer> </dict> </plist>
3. 映象資料傳送分析
第二次setup之後開始傳送資料,加入過濾條件 (ip.src==172.18.145.2 || ip.src==172.18.145.3) && (ip.dst==172.18.145.3 || ip.dst==172.18.145.2) && ( udp || (tcp.srcport != 52244 && tcp.dstport != 52244))
,結果如下圖所示

image
這裡server端使用了7020(tcp)和7011(udp)兩個埠,client端使用了60373和59694兩個埠
根據傳送包的大小確認7020是接收映象資料埠,下面具體分析
- 7011(udp)埠分析
7011 -> 60373 48b
60373 -> 7011 48b
7011 -> 60373 48b
60373 -> 7011 48b
7011埠由UDPListenerScreenTC處理

image
看了下程式碼,是ntp協議,每隔3秒傳送一次
在wireshark中右鍵udp包,decode as,選取ntp即可看到解析,這塊比較簡單,我們直接進入解析映象資料的分析
- 7020(tcp)埠分析
通過埠資訊找到多個映象service,主要包括以下幾個類

image
修改LeLog 中sLevel為0,重新編包,根據打印出的log輔助定位,確認是由GeneralMirrorService處理(需要附件)
分析之後,我們知道,有兩個資料型別,先判斷前4個位元組是GET/POST,如果不是按照映象資料處理,格式如下圖

image
payloadsize即映象資料,根據分析得知這裡的資料是加密的。
4. 映象資料解密
我們看 GeneralMirrorService
中的這段
if (!this.mIsAesInited) { mainServer.this.initAESUseRAOPKey(); this.mIsAesInited = true; } mReturn = mainServer.this.decryptAES(vstreamdata_in, 0, payloadsize, mainServer.this.vstreamdata, 0);
關注mainServer中的initAESUseRAOPKey和decryptAES兩個函式,可以確認的是這個也是aes加密資料
public void initAESUseRAOPKey() { try { this.sks = new SecretKeySpec(this.mPlaybackService.mKey, "AES"); this.cipher = Cipher.getInstance("AES/CTR/NoPadding"); this.cipher.init(1, this.sks, new IvParameterSpec(this.mPlaybackService.mIv)); this.mNextDecryptCount = 0; Arrays.fill(this.og, (byte) 0); } catch (Throwable e) { LeLog.m1097w("Server", e); } }
其中我們再看,使用的是CTR解密,需要知道key和iv才能解密,
通過在 mainServer
類中搜索 mkey
找到如下程式碼
r4 = com.hpplay.happyplay.mainServer.this; r48 = r4.mAaceld; r0 = r200; r4 = com.hpplay.happyplay.mainServer.this; r49 = r4.aesekey; r50 = 16; r0 = r200; r4 = com.hpplay.happyplay.mainServer.this; r53 = r4.ver_signal; r0 = r200; r4 = com.hpplay.happyplay.mainServer.this; r54 = r4.streamid; r0 = r200; r0 = r0.pairSessionId; r55 = r0; // r49 =r4.aesekey; 16outoutsizever_signalstreamidpairSessionId r90 = r48.FdkDecodeAudioFun10(r49, r50, r51,r52,r53,r54,r55); r4 = 0; r4 = r52[r4]; r6 = 32; if (r4 != r6) goto L_0x69ec; L_0x69ba: r4 = 0; r0 = r200; r6 = com.hpplay.happyplay.mainServer.this; r6 = r6.mPlaybackService; r6 = r6.mKey; r9 = 0; r10 = 16; r0 = r51; // 取r51前16個位元組 java.lang.System.arraycopy(r0, r4, r6, r9, r10); r4 = 16; r0 = r200; r6 = com.hpplay.happyplay.mainServer.this; r6 = r6.mPlaybackService; r6 = r6.mIv; r9 = 0; r10 = 16; r0 = r51; // 取r51的16-31 java.lang.System.arraycopy(r0, r4, r6, r9, r10); r0 = r200; r4 = com.hpplay.happyplay.mainServer.this; r4 = r4.mPlaybackService; r6 = 1;
分析得知,key和iv均來自 FdkDecodeAudioFun10
先明確幾個入參的含義
- aesekey
第一次的SETUP中的ekey是72位元組,aesekey是ekey通過FdkDecodeAudioFun3解密後輸出為16位元組,FdkDecodeAudioFun3和FirePlay相關,複雜度類似於FdkDecodeAudioFun1和FdkDecodeAudioFun2。這個函式和兩次 fp-setup
一樣,在 shairplay
裡面找到了相關實現
- ver_signal
版本判斷,在airplay2中為1
- streamid
第二次SETUP的streamConnectionID欄位的值
- pairSessionId
會話標識,可忽略
- out為32位元組的輸出
FdkDecodeAudioFun10
是native層程式碼,根據前面的分析這個函式比較簡單,不再做分析,感興趣的可以自行閱讀,以下是關鍵程式碼:
sha512_init sha512_update->eaeskey sha512_update->ecdh_secret sha512_final->eaeskey sha512_init(&ctx); sha512_update->"AirPlayStreamKey"+streamConnectionID sha512_update->eaeskey sha512_final->hash1 sha512_init sha512_update->"AirPlayStreamIV"+streamConnectionID sha512_update->eaeskey sha512_final->hash2
AES中的key為hash1的前16位元組
AES中的iv為hash2的前16位元組
有了key和iv之後可以解密了,此時解密完的資料為avcc格式的H264裸流
至此,螢幕映象資料解析完畢。
tips:這裡擼程式碼的時候,streamConnectionID轉換為%lld格式,導致投屏時一會可以顯示正確影象,一會全是錯誤資料,後面看log發現輸出的streamConnectionID為負值,導致解密錯誤,正確應為%llu格式
音訊資料
1. 抓包分析
抓取映象包的時候沒有抓音訊包,這裡重新抓取含音訊的包。
過濾下埠資訊,看下音訊的互動

image
和音訊相關都是udp協議,這裡發現了幾個埠
- server端
42820 音訊埠
46440 controlport && timeport(其實是兩個埠,X播用了同一個)
- client端
63658 controlport
59593 timeport
client需要知道server埠肯定有個互動,繼續往前,找到了第三個SETUP
2. 第三個SETUP
SETUP rtsp://172.18.145.2/16274097868445272520 RTSP/1.0 Content-Length: 199 Content-Type: application/x-apple-binary-plist CSeq: 17 DACP-ID: 2F4085FA856F2D7D Active-Remote: 3115937391 User-Agent: AirPlay/366.74.2 bplist00...Wstreams........ .. . ......ZlatencyMax^redundantAudioZlatencyMinRctSspf[controlPort[usingScreen[audioFormatTtype...................`....(3BMPT`lx}....................................... RTSP/1.0 200 OK Content-Length: 118 Date: Mon, 14 Jan 2019 06:56:49 GMT Content-Type: application/x-apple-binary-plist Server: AirTunes/220.68 bplist00..D........ .. ..ZtimingPortWstreams..hXdataPort.`Ttype[controlPort.$. /.?,:8................................K
client->server
<plist version="1.0"> <dict> <key>streams</key> <array> <dict> <key>latencyMax</key> <integer>3750</integer> <key>redundantAudio</key> <integer>2</integer> <key>latencyMin</key> <integer>3750</integer> <key>ct</key> <integer>8</integer> <key>spf</key> <integer>480</integer> <key>controlPort</key> <integer>63658</integer> <key>usingScreen</key> <true/> <key>audioFormat</key> <integer>16777216</integer> <key>type</key> <integer>96</integer> </dict> </array> </dict> </plist>
server->client
<plist version="1.0"> <dict> <key>streams</key> <array> <dict> <key>dataPort</key> <integer>42820</integer> <key>controlPort</key> <integer>46440</integer> <key>type</key> <integer>96</integer> </dict> </array> <key>timingPort</key> <integer>46440</integer> </dict> </plist>
3. 程式碼分析
由於程式碼是java寫的,用的是udp,通過搜尋DatagramSocket和相關日誌確認了程式碼的關係

image
為了確認3個埠的作用,需要分析AudioServer中的程式碼
- 關鍵程式碼一
class C07971 extends Thread { C07971() { } public void run() { AudioServer.this.mStartClockTime = System.currentTimeMillis(); AudioServer.this.mLastSyncPacketTime = System.currentTimeMillis(); AudioServer.this.mSessionStartTime = System.currentTimeMillis(); AudioServer.this.mDiffToSource = 0; while (!AudioServer.this.mStopped) { // ntp 毫秒 AudioServer.this.writeTimeStamp(AudioServer.this.request, 24, (System.currentTimeMillis() - AudioServer.this.mLastSyncPacketTime) + AudioServer.this.mDiffToSource); try { AudioServer.this.csock.send(new DatagramPacket(AudioServer.this.request, AudioServer.this.request.length, AudioServer.this.mSocket.getInetAddress(), AudioServer.this.session.getTimingPort())); try { Thread.sleep(3000); } catch (Throwable e) { LeLog.m1097w("AudioServer", e); return; } } catch (Throwable e2) { LeLog.m1097w("AudioServer", e2); return; } catch (Throwable npe) { LeLog.m1097w("AudioServer", npe); return; } } } }
- 關鍵程式碼二
public void packetReceivedTC(byte[] packet, int len) { if (!this.mStopped) { this.type_tc = packet[1] & TransportMediator.KEYCODE_MEDIA_PAUSE; if (this.type_tc == 83) { this.mDiffToSource = readTimeStamp(packet, 24); this.mLastSyncPacketTime = System.currentTimeMillis(); if (this.audioBuf != null) { if (((this.mLastTimeStampsAp - this.audioBuf.getPlayPts()) * 10) / 441 > ((long) this.synctime)) { LeLog.m1087d("AudioServer", "Sync Audio ..."); this.audioBuf.setSync(); } if (readTimeStamp(packet, 24) - this.mLastTimeStampsTp <= 2500) { } } } else if (this.type_tc == 84) { this.mCurrentSeqNo_tc = ((packet[2] & 255) << 8) + (packet[3] & 255); this.mLastTimeStampsPc = read32(packet, 4); this.mDiffToSource = readTimeStamp(packet, 8); this.mLastSyncPacketTime = System.currentTimeMillis(); this.mSessionStartTime = System.currentTimeMillis(); if (this.audioBuf != null && ((read32(packet, 4) * 10) / 441) - ((this.audioBuf.getPlayPts() * 10) / 441) > 150) { } } else if (this.type_tc == 86) { int seqn = ((packet[6] & 255) * 256) + (packet[7] & 255); this.mCurrentSeqNo_tc = ((packet[6] & 255) << 8) + (packet[7] & 255); if (len > 16) { Arrays.fill(this.packet_buffer_retry, (byte) 0); System.arraycopy(packet, 16, this.packet_buffer_retry, 0, (len - 12) - 4); if (this.mType != 1) { this.audioBuf.putPacketInBuffer(this.mCurrentSeqNo_tc, this.packet_buffer_retry, len - 16); this.audioBuf.putAacEldPacketPts(this.mCurrentSeqNo_tc, read32(packet, 4)); } } } else { LeLog.m1087d("AudioServer", "type=" + this.type_tc + "-->unkown\n"); this.mCurrentSeqNo_tc = (short) (((packet[2] & 255) << 8) + (packet[3] & 255)); } } }
4. timingPort互動
server 46440 <-> client 59593
資料內容如下
server->client 80 d2 00 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 83 aa 7e 80 00 00 00 f3 client->server 80 d3 00 07 00 00 00 00 83 aa 7e 80 00 00 00 f3 83 b7 bc e9 3b d6 ea c8 83 b7 bc e9 3b e1 ae 70
46440先向59593傳送了32位元組資料,對應
前24位元組固定為
0x80,0xd2,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
後8個位元組為ntp時間的傳送時間,
收到的也是32位元組,前8個位元組固定 80,d3,00,07,00,00,00,00
後32個位元組是 Origin_Timestamp
, Receive_Timestamp
和 Transmit_Timestamp
所以timingPort是用作ntp對時用的
5. controlPort互動
server 46440 <-> client 63658
看這行程式碼 this.type_tc = packet[1] & TransportMediator.KEYCODE_MEDIA_PAUSE;
, TransportMediator.KEYCODE_MEDIA_PAUSE
的值是0x7F, type_tc
的值有兩種84和86
84在程式碼中沒看出什麼作用,可以先不管,86在程式碼中是重傳資料音訊資料,包中會含有音訊包
6. 音訊資料傳送分析
server接收音訊資料的埠為42820
視訊資料是加密的,那麼音訊加密可能性很大,繼續分析程式碼
public void packetReceived(byte[] packet, int len) { if (!this.mStopped) { playbackService com_hpplay_happyplay_playbackService = this.mPlaybackService; com_hpplay_happyplay_playbackService.mStreamCount += len; this.type = packet[1] & TransportMediator.KEYCODE_MEDIA_PAUSE; if (this.type == 96 || this.type == 86) { int off = 0; if (this.type == 86) { off = 4; } this.mCurrentSeqNo = ((packet[off + 2] & 255) << 8) + (packet[off + 3] & 255); if (this.mPreSeqNo > 0 && Math.abs(this.mCurrentSeqNo - this.mPreSeqNo) > 12) { this.mBadSeqCount++; if (this.mBadSeqCount >= 6) { LeLog.m1091i("AudioServer", "bad seq count " + this.mBadSeqCount); if (!(playbackService.getInstance().getPlayer() || Mirror.MirrorActivityStatus)) { LeLog.m1091i("AudioServer", "showActivity audio server"); Intent intent = new Intent(this.mContext, MirrorCourseActivity.class); intent.putExtra("type", 4); intent.addFlags(268435456); this.mContext.startActivity(intent); this.mStopped = true; this.mContext.sendBroadcast(new Intent(mainConst.MIRROR_FORCE_STOP)); } this.mBadSeqCount = 0; this.mPreSeqNo = -1; } } this.mPreSeqNo = this.mCurrentSeqNo; this.mLastTimeStampsAp = read32(packet, 4); if ((len - 12) - off == 4 && packet[12] == (byte) 0 && packet[13] == (byte) 104 && packet[14] == (byte) 52 && packet[15] == (byte) 0) { if (this.audioBuf.getStopstatus()) { this.audioBuf.setSync(); } this.mCurrentPlaySeqNo = this.mCurrentSeqNo; this.mIsSync = false; return; } if (!this.mIsSync) { this.mCurrentPlaySeqNo = this.mCurrentSeqNo - 1; this.mIsSync = true; } if (this.mType == 0) { //使用alac解碼 Arrays.fill(this.packet_buffer, (byte) 0); System.arraycopy(packet, off + 12, this.packet_buffer, 0, (len - 12) - off); this.audioBuf.putPacketInBuffer(this.mCurrentSeqNo, this.packet_buffer, (len - 12) - off); this.audioBuf.putAacEldPacketPts(this.mCurrentSeqNo, this.mLastTimeStampsAp); } else if ((this.mCurrentSeqNo & SupportMenu.USER_MASK) >= ((this.mCurrentPlaySeqNo + 1) & SupportMenu.USER_MASK)) { //使用aac-eld解碼 Arrays.fill(this.packet_buffer, (byte) 0); System.arraycopy(packet, off + 12, this.packet_buffer, 0, (len - 12) - off); this.audioBuf.putAacEldPacketInBuffer(this.mCurrentSeqNo, this.packet_buffer, (len - 12) - off); this.audioBuf.putAacEldPacketPts(this.mCurrentSeqNo, this.mLastTimeStampsAp); this.mSeqCount++; this.mCurrentPlaySeqNo = this.mCurrentSeqNo; } else if ((this.mCurrentSeqNo & SupportMenu.USER_MASK) != 0 && this.mPlaybackService.channel < 14 && !this.audioBuf.audioBuffer[this.mCurrentSeqNo % 512].ready) { LeLog.m1087d("AudioServer", "Frame " + this.mCurrentSeqNo + " not ready, pushed"); Arrays.fill(this.packet_buffer, (byte) 0); System.arraycopy(packet, off + 12, this.packet_buffer, 0, (len - 12) - off); this.audioBuf.putAacEldPacketInBuffer(this.mCurrentSeqNo, this.packet_buffer, (len - 12) - off); this.audioBuf.putAacEldPacketPts(this.mCurrentSeqNo, this.mLastTimeStampsAp); } } } }
音訊包格式如圖所示

image
type = 第2個位元組 & 0x7F
,只處理type為86的資料
這裡需要注意的是:如果音訊資料為4個位元組且為{0x0,0x68,0x34,0x0}時,表示沒有音訊資料,不處理。
7. 音訊資料解密
繼續分析AudioBuffer的putAacEldPacketInBuffer方法,音訊資料又是AES加密,不過這次解密為CBC方式解密,關鍵程式碼如下
public void initAES() { try { this.f867k = new SecretKeySpec(this.session.getAESKEY(), "AES"); this.f866c = Cipher.getInstance("AES/CBC/NoPadding"); this.f866c.init(2, this.f867k, new IvParameterSpec(this.session.getAESIV())); } catch (Throwable e) { LeLog.m1097w("Music", e); } } private int decryptAES(byte[] array, int inputOffset, int inputLen, byte[] output, int outputOffset) { try { return this.f866c.update(array, inputOffset, inputLen, output, outputOffset); } catch (Throwable e) { LeLog.m1097w("Music", e); return -1; } }
同樣的,我們需要知道key和iv。
通過分析,key為native函式 FdkDecodeAudioFun11
產出,分析程式碼可得出
eaeskey
為72位元組解密出的16位元組ekey,hash之後用作key,關鍵程式碼如下
sha512_init sha512_update->eaeskey sha512_update->ecdh_secret sha512_final->eaeskey
取eaeskey的前16位元組作為key,iv是第一次 SETUP
時client傳送資料bplist中的eiv
得到key和iv之後,即可進行解密
這裡解出的是AAC裸流,接入fdk-aac解碼為pcm,這裡面遇到兩個問題
- 問題1:pcm的時長是原始時長的3倍
通過列印序號,然後看了下收到的包,發現每個序號會發3遍,需要做過濾

image
做了過濾,時長正常
- 問題2:pcm播放是雜音,非正常音樂
通過日誌發現fdk-aac返回錯誤0x4006,表示資料來源錯誤。經過各種可能方法查詢問題,最後使用java層解密的方式才確認是C中選用的aes庫的問題,在每次解密需要重新初始化aes_context再進行解密,之前是快取了aes_context導致錯誤。
其他請求
GET_PARAMETER rtsp://172.18.145.2/16274097868445272520 RTSP/1.0 Content-Length: 8 Content-Type: text/parameters CSeq: 8 DACP-ID: 2F4085FA856F2D7D Active-Remote: 3115937391 User-Agent: AirPlay/366.74.2 volume RTSP/1.0 200 OK Content-Type: text/parameters Content-Length: 13 Server: AirTunes/220.68 CSeq: 8 volume: 0.0 SET_PARAMETER rtsp://172.18.145.2/16274097868445272520 RTSP/1.0 Content-Length: 20 Content-Type: text/parameters CSeq: 18 DACP-ID: 2F4085FA856F2D7D Active-Remote: 3115937391 User-Agent: AirPlay/366.74.2 volume: -20.000000 RTSP/1.0 200 OK Server: AirTunes/220.68 CSeq: 18 POST /feedback RTSP/1.0 CSeq: 26 DACP-ID: 2F4085FA856F2D7D Active-Remote: 3115937391 User-Agent: AirPlay/366.74.2 RTSP/1.0 200 OK Server: AirTunes/220.68 CSeq: 26 TEARDOWN rtsp://172.18.145.2/16274097868445272520 RTSP/1.0 Content-Length: 69 Content-Type: application/x-apple-binary-plist CSeq: 30 DACP-ID: 2F4085FA856F2D7D Active-Remote: 3115937391 User-Agent: AirPlay/366.74.2 bplist00...Wstreams.....Ttype.`......................................RTSP/1.0 200 OK Connection: close Server: AirTunes/220.68 CSeq: 30 TEARDOWN rtsp://172.18.145.2/16274097868445272520 RTSP/1.0 Content-Length: 69 Content-Type: application/x-apple-binary-plist CSeq: 31 DACP-ID: 2F4085FA856F2D7D Active-Remote: 3115937391 User-Agent: AirPlay/366.74.2 bplist00...Wstreams.....Ttype.n......................................
1. GET_PARAMETER
獲取音量資料
2. SET_PARAMETER
調整音量資料
3. feedback
心跳
4. TEARDOWN-1
client->server
<plist version="1.0"> <dict> <key>streams</key> <array> <dict> <key>type</key> <integer>96</integer> </dict> </array> </dict> </plist>
根據type=96可得出是銷燬音訊服務
- 5.TEARDOWN-2
<plist version="1.0"> <dict> <key>streams</key> <array> <dict> <key>type</key> <integer>110</integer> </dict> </array> </dict> </plist>
根據type=110可得出是銷燬映象服務
實現
下面是投屏的演示圖

image
參考連結
- shairplay
- ed25519演算法集
- ed25519演算法實現
- curve25519-donna演算法實現
- RTP協議文件
- Multicast DNS
- 區域網裝置發現之Bonjour協議
- Unofficial AirPlay Protocol Specification
- 通過NTP協議進行時間同步
- rtsp協議詳解
- AES演算法實現
附件
- classes.dex
- ida分析檔案