-
Notifications
You must be signed in to change notification settings - Fork 83
AirPlay2
Analysis of AirPlay2 Technology (notes by author of AirServer, 2019-03-13)
For the AirPlay2 protocol, this paper selects an online app and performs reverse analysis to achieve the crack of the
AirPlay2 protocol and implements the screen demo.
Here are some of the knowledge points covered in this article:
For the cracking of AirPlay, there are related App implementations at home and abroad. Note that the purpose is to project the content of the iPhone device to the TV or PC.
Here, I selected the most widely used X-casting screen projection in China. I downloaded the latest version and found that it was packed, so I searched for the old version. Fortunately, I found the 7.1.0 version without packing. It is normal to test with IOS12. Screencasting, this article conducts a reverse analysis of this version of the App. (Added: this may be what is being analysed.)
Open the screen mirroring app, then swipe up in the iPhone -> screen image to find the screen mirroring device, click to see the iPhone screen on the app.
How does IOS discover devices? How is the connection process? How is the data transferred? With these questions, let's analyze
**The following will use server to represent Android devices, and client to represent IOS devices
Packet capture analysis
- packet capture
The rooted xiaomi is used here, xiaomi root is relatively simple, just flash the development version, install the latest version of tcpdump and start capturing packets
./data/local/tmp/tcpdump -i any -p -s 0 -w /sdcard/airplay.pcapng
Start capturing packets before opening the app until the end of screen projection, and use WireShark to analyze data packets
** mDNS analysis
The server releases the mDNS broadcast as shown in the following figure:
There are 4 DNS records here:
- An A record
MIX2S-xiaomishouji.local: type A
- 3 SRV records: representing three services for 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
Because there are not only iPhone screen projections and other screen projections in X broadcast, these services need to be screened, so here we need to see which services the client finally uses.
The filter condition is modified to src==172.18.145.3 && mdns
, look at the service queried by the client
Here you can see that the client only uses raop and airplay two services, query SRV record
, the content
that can airplay
uses port 52233
port, raop uses port 52244
In frame146, the server sends the reply multicast packet
From this, it can be seen that the client and the server implement a zero-configuration network through mDNS and DNS-SD. After finding the server, the interaction between the client and the server occurs.
The filter condition is modified to
(ip.src==172.18.145.2 && ip.dst==172.18.145.3) || (ip.src==172.18.145.3 && ip.dst==172.18.145.2)
,
the filtering results are as shown:
The first is the three-way handshake, from frame230
, the beginning of the transfer request, the server port 52244
is where you can find the use of the raop
service.
The filter on port 52233
found no results, indicating that the client is not using the airplay
service.
Below for the selected frame 230, Analyze->Follow->TCP Stream shows the entire interaction process
The details are as follows:
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....}[email protected].,.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.....%
Next, look at the related requests one by one
The content of the request package is shown in the following figure:
The request and the return packet are in bplist format, parse it out and see
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>
Here is a reply listing properties supported by the server, not analysed here: see Reference link [8].
Client sends 32 bytes <-> server replies 32 bytes
Search for the string "pair-setup" and find the following code
FdkDecodeAudioFun8
Argument analysis
- The first argument is the client request packet
- The second argument is the client request packet length
- The third argument jg server->client returns the package content
- The fourth argument out_size returns the packet length
- The fifth argument is 1
- The sixth argument pairSessionId is the connection context, we see the code supports up to 16 device connections
Further analysis: open libhpplayaudio.so, use ida to find FdkDecodeAudioFun8
, search function relation of this call, as shown below
It seems very complicated. It seems very complicated. The keyword ed25519 was found in the analysis. It is an existing algorithm. After finding the source code, it can be matched with the assembly code.
Now we only need to focus on the FdkDecodeAudioFun8
implementation.
.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:000C7BF8 EXPORT 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:000C7BF8 PUSH.W {R4-R11,LR}
.text:000C7BFC SUB SP, SP, #0x14
.text:000C7BFE MOV R7, R0
.text:000C7C00 MOV R10, R2
.text:000C7C02 LDR R6, [SP,#0x38+arg_C]
.text:000C7C04 CMP R6, #0x10
.text:000C7C06 BHI loc_C7CEA
.text:000C7C08 LDR R4, =(unk_254118 - 0xC7C10)
.text:000C7C0A LSLS R6, R6, #2
.text:000C7C0C ADD R4, PC ; unk_254118
.text:000C7C0E ADD R4, R6
.text:000C7C10 LDR.W R3, [R4,#0x558] ; unk_25518+sessionid*4+0x558
.text:000C7C14 CMP R3, #0
.text:000C7C16 BEQ loc_C7CF0
.text:000C7C18 MOV R1, R2
.text:000C7C1A MOVS R2, #0
.text:000C7C1C BL _ZN7_JNIEnv20GetByteArrayElementsEP11_jbyteArrayPh ; _JNIEnv::GetByteArrayElements(_jbyteArray *,uchar *)
.text:000C7C20 MOV R8, R0 ; raw_data
.text:000C7C22 CMP R0, #0
.text:000C7C24 BEQ loc_C7CF6
.text:000C7C26 MOV R0, R7
.text:000C7C28 LDR R1, [SP,#0x38+arg_4]
.text:000C7C2A MOVS R2, #0
.text:000C7C2C BL _ZN7_JNIEnv19GetIntArrayElementsEP10_jintArrayPh ; _JNIEnv::GetIntArrayElements(_jintArray *,uchar *)
.text:000C7C30 LDR.W R11, [R4,#0x558] ; R11=unk_25518+sessionid*4+0x558
.text:000C7C34 LDR.W R5, [R11,#4] ; unk_25518+sessionid*4+0x558 The value of the address + 4, then take the value, indicating that it is a structure
.text:000C7C38 MOV R9, R0 ; out_size
.text:000C7C3A CBNZ R5, loc_C7C86
.text:000C7C3C MOVS R0, #0xE4 ; size
.text:000C7C3E BLX malloc ; E4=228
.text:000C7C42 MOV R1, R5 ; c
.text:000C7C44 MOVS R2, #0xE4 ; n
.text:000C7C46 STR.W R0, [R11,#4] ; Address to write to the application memory
.text:000C7C4A LDR.W R3, [R4,#0x558] ; R4=unk_25518+sessionid*4
.text:000C7C4E LDR R0, [R3,#4] ; s
.text:000C7C50 BLX memset
.text:000C7C54
.text:000C7C54 loc_C7C54 ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun8+7A↓j
.text:000C7C54 LDR.W R3, [R4,#0x558]
.text:000C7C58 LDR R3, [R3,#4] ; R3=unk_25518+sessionid*4+0x558+4
.text:000C7C5A STR R3, [SP,#0xC]
.text:000C7C5C BLX lrand48
.text:000C7C60 MOV R11, R0
.text:000C7C62 BLX lrand48
.text:000C7C66 LDR R3, [SP,#0xC]
.text:000C7C68 SMULBB.W R0, R0, R11 ; R0=lrand48*lrand48
.text:000C7C6C STRB R0, [R3,R5]
.text:000C7C6E ADDS R5, #1
.text:000C7C70 CMP R5, #0x20 ; ' '
.text:000C7C72 BNE loc_C7C54
.text:000C7C74 LDR.W R3, [R4,#0x558]
.text:000C7C78 LDR R2, [R3,#4]
.text:000C7C7A ADD.W R0, R2, #0x60
.text:000C7C7E ADD.W R1, R2, #0x20
.text:000C7C82 BL ed25519_create_keypair
.text:000C7C86
.text:000C7C86 loc_C7C86 ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun8+42↑j
.text:000C7C86 LDR R3, =unk_18C486
.text:000C7C88 ADD.W R0, R8, #0x20 ; R8=raw_data
.text:000C7C8C MOV R2, R8
.text:000C7C8E ADD R3, PC ; unk_254118
.text:000C7C90 ADD R6, R3
.text:000C7C92 LDR.W R1, [R6,#0x558]
.text:000C7C96 LDR R3, [R1,#4]
.text:000C7C98 ADDS R3, #0x80 ; Write from 128 bytes
.text:000C7C9A
.text:000C7C9A loc_C7C9A ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun8+AC↓j
.text:000C7C9A LDR.W R4, [R2],#4
.text:000C7C9E CMP R2, R0
.text:000C7CA0 STR.W R4, [R3],#4 ; Copy raw_data to memory
.text:000C7CA4 BNE loc_C7C9A
.text:000C7CA6 LDR R3, [R1,#4]
.text:000C7CA8 MOV R0, R7 ; a1
.text:000C7CAA LDR R1, [SP,#0x38+arg_0] ; jg
.text:000C7CAC MOVS R2, #0 ; jsize
.text:000C7CAE ADDS R3, #0x60 ; '`'
.text:000C7CB0 STR R3, [SP,#0x38+var_38] ; sp[0]=Stack address +96 or pk address
.text:000C7CB2 MOVS R3, #dword_20 ; 32 bytes
.text:000C7CB4 BL _ZN7_JNIEnv18SetByteArrayRegionEP11_jbyteArrayiiPKa ; SetByteArrayRegion(this, array(R1), start(R2), len(R3), buf)
.text:000C7CB8 MOVS R3, #0x20 ; ' '
.text:000C7CBA MOV R0, R7
.text:000C7CBC STR.W R3, [R9]
.text:000C7CC0 LDR R1, [SP,#0x38+arg_4]
.text:000C7CC2 MOVS R2, #0
.text:000C7CC4 MOVS R3, #1
.text:000C7CC6 STR.W R9, [SP,#0x38+var_38]
.text:000C7CCA BL _ZN7_JNIEnv17SetIntArrayRegionEP10_jintArrayiiPKi ; _JNIEnv::SetIntArrayRegion(_jintArray *,int,int,int const*)
.text:000C7CCE MOV R0, R7
.text:000C7CD0 MOV R1, R10
.text:000C7CD2 MOV R2, R8
.text:000C7CD4 MOVS R3, #0
.text:000C7CD6 BL _ZN7_JNIEnv24ReleaseByteArrayElementsEP11_jbyteArrayPai ; _JNIEnv::ReleaseByteArrayElements(_jbyteArray *,signed char *,int)
.text:000C7CDA MOV R0, R7
.text:000C7CDC LDR R1, [SP,#0x38+arg_4]
.text:000C7CDE MOV R2, R9
.text:000C7CE0 MOVS R3, #0
.text:000C7CE2 BL _ZN7_JNIEnv23ReleaseIntArrayElementsEP10_jintArrayPii ; _JNIEnv::ReleaseIntArrayElements(_jintArray *,int *,int)
.text:000C7CE6 MOVS R0, #0
.text:000C7CE8 B loc_C7CFA
.text:000C7CEA ; ---------------------------------------------------------------------------
.text:000C7CEA
.text:000C7CEA loc_C7CEA ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun8+E↑j
.text:000C7CEA MOV R0, #0xFFFFFFF8
.text:000C7CEE B loc_C7CFA
.text:000C7CF0 ; ---------------------------------------------------------------------------
.text:000C7CF0
.text:000C7CF0 loc_C7CF0 ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun8+1E↑j
.text:000C7CF0 MOV R0, #0xFFFFFFF7
.text:000C7CF4 B loc_C7CFA
.text:000C7CF6 ; ---------------------------------------------------------------------------
.text:000C7CF6
.text:000C7CF6 loc_C7CF6 ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun8+2C↑j
.text:000C7CF6 MOV.W R0, #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:000C7CFA ADD SP, SP, #0x14
.text:000C7CFC POP.W {R4-R11,PC}
.text:000C7CFC ; End of function Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun8
According to the analysis, draw the current data in the memory
Focus on the observed ed25519_create_keypair
function
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);
}
From the .text:000C7CB4 BL _ZN7_JNIEnv18SetByteArrayRegionEP11_jbyteArrayiiPKa ;
it can be found that
the 32 bytes sent by the server to the client is the public key generated by ed25519.
Client sends 68 bytes (the first 4 bytes are 01 00 00 00) <-> server replies with 96 bytes
The client requests the remaining 64 bytes of content in the packet
Similarly, find FdkDecodeAudioFun9
function
FdkDecodeAudioFun9
Argument analysis
-
The first argument client requests packet
-
The second argument client requests packet length (68)
-
The third argument jg server->client returns the package content
-
The fourth argument out_size returns the packet length
-
The fifth argument is 1
-
The sixth argument pairSessionId is the connection context, we see the code supports up to 16 device connections
The
FdkDecodeAudioFun9
function call diagram is as follows
Looking at the entire function structure in general, it is still a bit complicated, the specific function analysis is as follows
.text:000C7D08 ; signed int __fastcall Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9(_JNIEnv *a1, int a2, void *raw_data, int a4, void *jg, int out_size, int a7, unsigned int pairSessionId)
.text:000C7D08 EXPORT Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9
.text:000C7D08 Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9
.text:000C7D08 ; DATA XREF: LOAD:000014AC↑o
.text:000C7D08
.text:000C7D08 var_2B0 = -0x2B0
.text:000C7D08 var_2AC = -0x2AC
.text:000C7D08 var_2A8 = -0x2A8
.text:000C7D08 var_29C = -0x29C
.text:000C7D08 var_298 = -0x298
.text:000C7D08 var_294 = -0x294
.text:000C7D08 var_290 = -0x290
.text:000C7D08 var_28C = -0x28C
.text:000C7D08 var_284 = -0x284
.text:000C7D08 var_280 = -0x280
.text:000C7D08 var_1AC = -0x1AC
.text:000C7D08 var_18D = -0x18D
.text:000C7D08 var_18C = -0x18C
.text:000C7D08 anonymous_0 = -0x16D
.text:000C7D08 var_16C = -0x16C
.text:000C7D08 var_14C = -0x14C
.text:000C7D08 s = -0x12C
.text:000C7D08 var_EC = -0xEC
.text:000C7D08 anonymous_1 = -0xE8
.text:000C7D08 anonymous_3 = -0xE4
.text:000C7D08 anonymous_5 = -0xE0
.text:000C7D08 anonymous_7 = -0xDC
.text:000C7D08 anonymous_9 = -0xD8
.text:000C7D08 anonymous_11 = -0xD4
.text:000C7D08 anonymous_13 = -0xD0
.text:000C7D08 var_CC = -0xCC
.text:000C7D08 anonymous_16 = -0xC8
.text:000C7D08 anonymous_18 = -0xC4
.text:000C7D08 anonymous_20 = -0xC0
.text:000C7D08 anonymous_22 = -0xBC
.text:000C7D08 anonymous_24 = -0xB8
.text:000C7D08 anonymous_26 = -0xB4
.text:000C7D08 anonymous_28 = -0xB0
.text:000C7D08 var_AC = -0xAC
.text:000C7D08 var_8C = -0x8C
.text:000C7D08 anonymous_2 = -0x88
.text:000C7D08 anonymous_4 = -0x84
.text:000C7D08 anonymous_6 = -0x80
.text:000C7D08 anonymous_8 = -0x7C
.text:000C7D08 anonymous_10 = -0x78
.text:000C7D08 anonymous_12 = -0x74
.text:000C7D08 anonymous_14 = -0x70
.text:000C7D08 anonymous_15 = -0x6C
.text:000C7D08 anonymous_17 = -0x68
.text:000C7D08 anonymous_19 = -0x64
.text:000C7D08 anonymous_21 = -0x60
.text:000C7D08 anonymous_23 = -0x5C
.text:000C7D08 anonymous_25 = -0x58
.text:000C7D08 anonymous_27 = -0x54
.text:000C7D08 anonymous_29 = -0x50
.text:000C7D08 var_2C = -0x2C
.text:000C7D08 jg = 0
.text:000C7D08 out_size = 4
.text:000C7D08 pairSessionId = 0xC
.text:000C7D08
.text:000C7D08 ; __unwind {
.text:000C7D08 PUSH.W {R4-R11,LR}
.text:000C7D0C SUB.W SP, SP, #0x28C
.text:000C7D10 LDR.W R1, =(__stack_chk_guard_ptr - 0xC7D1E)
.text:000C7D14 MOV R6, R0
.text:000C7D16 LDR R3, [SP,#0x2B0+jg]
.text:000C7D18 MOV R9, R2
.text:000C7D1A ADD R1, PC ; __stack_chk_guard_ptr
.text:000C7D1C LDR R1, [R1] ; __stack_chk_guard
.text:000C7D1E LDR.W R10, [SP,#0x2B0+out_size]
.text:000C7D22 STR R1, [SP,#0x2B0+var_294]
.text:000C7D24 STR R3, [SP,#0x2B0+var_29C]
.text:000C7D26 LDR R3, [R1]
.text:000C7D28 STR R3, [SP,#0x2B0+var_2C]
.text:000C7D2A LDR R3, [SP,#0x2B0+pairSessionId]
.text:000C7D2C CMP R3, #0x10
.text:000C7D2E BHI.W loc_C8170
.text:000C7D32 LDR.W R4, =(unk_254118 - 0xC7D3E)
.text:000C7D36 MOV.W R11, R3,LSL#2
.text:000C7D3A ADD R4, PC ; unk_254118
.text:000C7D3C ADD R4, R11
.text:000C7D3E LDR.W R3, [R4,#0x558]
.text:000C7D42 CMP R3, #0
.text:000C7D44 BEQ.W loc_C8176 ; left branch
.text:000C7D48 MOV R1, R2 ; R1=R2=raw_data
.text:000C7D4A MOVS R2, #0
.text:000C7D4C BL _ZN7_JNIEnv20GetByteArrayElementsEP11_jbyteArrayPh ; _JNIEnv::GetByteArrayElements(_jbyteArray *,uchar *)
.text:000C7D50 MOV R5, R0 ; R5=R0=raw_data
.text:000C7D52 CMP R0, #0
.text:000C7D54 BEQ.W loc_C817C ; left branch
.text:000C7D58 MOV R0, R6
.text:000C7D5A LDR R1, [SP,#0x2B0+var_29C]
.text:000C7D5C MOVS R2, #0
.text:000C7D5E BL _ZN7_JNIEnv20GetByteArrayElementsEP11_jbyteArrayPh ; _JNIEnv::GetByteArrayElements(_jbyteArray *,uchar *)
.text:000C7D62 MOV R3, R0 ; R3=R0=jg
.text:000C7D64 MOV R0, R6
.text:000C7D66 CBNZ R3, loc_C7D76 ; right branch
.text:000C7D68 MOV R1, R9
.text:000C7D6A MOV R2, R5
.text:000C7D6C BL _ZN7_JNIEnv24ReleaseByteArrayElementsEP11_jbyteArrayPai ; _JNIEnv::ReleaseByteArrayElements(_jbyteArray *,signed char *,int)
.text:000C7D70 MOV R0, #0xFFFFFFFE
.text:000C7D74 B loc_C8180
.text:000C7D76 ; ---------------------------------------------------------------------------
.text:000C7D76
.text:000C7D76 loc_C7D76 ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+5E↑j
.text:000C7D76 MOV R1, R10
.text:000C7D78 MOVS R2, #0
.text:000C7D7A BL _ZN7_JNIEnv19GetIntArrayElementsEP10_jintArrayPh ; _JNIEnv::GetIntArrayElements(_jintArray *,uchar *)
.text:000C7D7E LDRSB.W R1, [R5] ; Take the first byte of the received packet, only 1 or 0
.text:000C7D82 CMP R1, #1 ; Determine if it is 1
.text:000C7D84 MOV R8, R0
.text:000C7D86 BNE.W loc_C80B2 ; is 1 left, not 1 right ********************************* ***********
.text:000C7D8A LDR.W R2, [R4,#0x558]
.text:000C7D8E LDR R7, [R2,#4]
.text:000C7D90 CBNZ R7, loc_C7DEA ; Not empty, right, left empty
.text:000C7D92 MOVS R0, #0xE4 ; size
.text:000C7D94 STR R2, [SP,#0x2B0+var_298]
.text:000C7D96 BLX malloc
.text:000C7D9A LDR R2, [SP,#0x2B0+var_298]
.text:000C7D9C MOV R1, R7 ; c
.text:000C7D9E STR R0, [R2,#4]
.text:000C7DA0 LDR.W R2, [R4,#0x558]
.text:000C7DA4 LDR R0, [R2,#4] ; s
.text:000C7DA6 MOVS R2, #0xE4 ; n
.text:000C7DA8 BLX memset
.text:000C7DAC MOV R3, R7
.text:000C7DAE MOV R1, R4
.text:000C7DB0
.text:000C7DB0 loc_C7DB0 ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+CE↓j
.text:000C7DB0 LDR.W R2, [R1,#0x558]
.text:000C7DB4 STR R3, [SP,#0x2B0+var_28C]
.text:000C7DB6 STR R1, [SP,#0x2B0+var_290]
.text:000C7DB8 LDR R2, [R2,#4]
.text:000C7DBA STR R2, [SP,#0x2B0+var_298]
.text:000C7DBC BLX lrand48
.text:000C7DC0 MOV R4, R0
.text:000C7DC2 BLX lrand48
.text:000C7DC6 LDR R3, [SP,#0x2B0+var_28C]
.text:000C7DC8 LDR R2, [SP,#0x2B0+var_298]
.text:000C7DCA LDR R1, [SP,#0x2B0+var_290]
.text:000C7DCC SMULBB.W R0, R0, R4
.text:000C7DD0 STRB R0, [R2,R3]
.text:000C7DD2 ADDS R3, #1
.text:000C7DD4 CMP R3, #0x20 ; ' '
.text:000C7DD6 BNE loc_C7DB0
.text:000C7DD8 LDR.W R3, [R1,#0x558]
.text:000C7DDC LDR R2, [R3,#4]
.text:000C7DDE ADD.W R0, R2, #0x60
.text:000C7DE2 ADD.W R1, R2, #0x20
.text:000C7DE6 BL ed25519_create_keypair
.text:000C7DEA
.text:000C7DEA loc_C7DEA ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+88↑j
.text:000C7DEA LDR R2, [SP,#0x2B0+pairSessionId]
.text:000C7DEC ADD.W R0, R5, #0x44
.text:000C7DF0 LDR R3, =(unk_254118 - 0xC7DF6)
.text:000C7DF2 ADD R3, PC ; unk_254118
.text:000C7DF4 ADD.W R3, R3, R2,LSL#2 ; R3=unk_254118+sessionid*4
.text:000C7DF8 ADD.W R2, R5, #0x24 ; R5=raw_data,R2=raw_data+36
.text:000C7DFC LDR.W R1, [R3,#0x558]
.text:000C7E00 LDR R3, [R1,#4] ; R3=unk_254118+sessionid*4+0x558+4
.text:000C7E02 ADDS R3, #0x80 ; R3=R3+128
.text:000C7E04
.text:000C7E04 loc_C7E04 ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+106↓j
.text:000C7E04 LDR.W R4, [R2],#4
.text:000C7E08 CMP R2, R0
.text:000C7E0A STR.W R4, [R3],#4
.text:000C7E0E BNE loc_C7E04 ; Received data is written to memory
.text:000C7E10 LDR R0, [R1] ; ptr
.text:000C7E12 CBZ R0, loc_C7E18
.text:000C7E14 BLX free
.text:000C7E18
.text:000C7E18 loc_C7E18 ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+10A↑j
.text:000C7E18 LDR R3, =(unk_254118 - 0xC7E22)
.text:000C7E1A MOV.W R0, #0x134 ; size
.text:000C7E1E ADD R3, PC ; unk_254118 ; R3=unk_254118
.text:000C7E20 ADD R11, R3 ; R11=R11+R3
.text:000C7E22 LDR.W R4, [R11,#0x558] ; R4=unk_254118+4*session+0x558
.text:000C7E26 BLX malloc
.text:000C7E2A MOVS R1, #0 ; c
.text:000C7E2C MOV.W R2, #0x134 ; n
.text:000C7E30 STR R0, [R4]
.text:000C7E32 LDR.W R3, [R11,#0x558]
.text:000C7E36 LDR R0, [R3] ; s
.text:000C7E38 BLX memset
.text:000C7E3C LDR.W R4, [R11,#0x558]
.text:000C7E40 MOVS R1, #0 ; c
.text:000C7E42 MOVS R2, #0x10 ; n
.text:000C7E44 LDR R0, [R4] ; R0=first address in the heap
.text:000C7E46 ADD.W R0, R0, #0x120 ; s
.text:000C7E4A BLX memset
.text:000C7E4E LDR R3, [R4]
.text:000C7E50 ADDS R2, R5, #4 ; R5=raw_data;R2=raw_data+4
.text:000C7E52 ADD.W R1, R5, #0x24 ; 4+32 bytes
.text:000C7E56 ADDS R3, #0x40 ; '@' ; R3=R3+64; Write from byte 64
.text:000C7E58
.text:000C7E58 loc_C7E58 ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+15A↓j
.text:000C7E58 LDR.W R0, [R2],#4
.text:000C7E5C CMP R2, R1
.text:000C7E5E STR.W R0, [R3],#4
.text:000C7E62 BNE loc_C7E58
.text:000C7E64 LDR R3, [SP,#0x2B0+pairSessionId]
.text:000C7E66 MOV.W R11, #0
.text:000C7E6A LDR R4, =(unk_254118 - 0xC7E72)
.text:000C7E6C LSLS R3, R3, #2
.text:000C7E6E ADD R4, PC ; unk_254118
.text:000C7E70 ADD R4, R3
.text:000C7E72 STR R3, [SP,#0x2B0+var_298]
.text:000C7E74
.text:000C7E74 loc_C7E74 ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+18E↓j
.text:000C7E74 LDR.W R3, [R4,#0x558] ; R3=unk_254118+4*session+0x558
.text:000C7E78 LDR R7, [R3] ; R7=Head address
.text:000C7E7A BLX lrand48
.text:000C7E7E STR R0, [SP,#0x2B0+var_290]
.text:000C7E80 BLX lrand48
.text:000C7E84 LDR R3, [SP,#0x2B0+var_290]
.text:000C7E86 SMULBB.W R0, R0, R3
.text:000C7E8A STRB.W R0, [R7,R11]
.text:000C7E8E ADD.W R11, R11, #1
.text:000C7E92 CMP.W R11, #0x20 ; ' '
.text:000C7E96 BNE loc_C7E74 ; R3=unk_254118+4*session+0x558
.text:000C7E98 LDR.W R3, [R4,#0x558] ; R3=unk_254118+4*session+0x558
.text:000C7E9C ADD R7, SP, #0x2B0+var_16C
.text:000C7E9E LDR R2, =(unk_1FDE68 - 0xC7EA6)
.text:000C7EA0 LDR R1, [R3] ; R1=Header address, private key
.text:000C7EA2 ADD R2, PC ; unk_1FDE68 ; R2=unk_1FDE68 = base_point
.text:000C7EA4 ADD.W R0, R1, #0x20 ; R0=R1+32, public key
.text:000C7EA8 BL curve25519_donna ; Generate public and private keys curve25519_donna(mypublic, mysecret, basepoint); 3 32b
.text:000C7EAC LDR.W R3, [R4,#0x558]
.text:000C7EB0 LDR R1, [R3]
.text:000C7EB2 ADD.W R2, R1, #0x40
.text:000C7EB6 ADD.W R0, R1, #0x80
.text:000C7EBA BL curve25519_donna ; Generate sharedkey: curve25519_donna(shared_key, mysecret, theirpublic);
.text:000C7EBE MOVS R1, #0 ; c
.text:000C7EC0 MOVS R2, #0x40 ; '@' ; n
.text:000C7EC2 MOV R0, R7 ; s
.text:000C7EC4 BLX memset
.text:000C7EC8 MOVS R2, #0x40 ; '@' ; n
.text:000C7ECA ADD R0, SP, #0x2B0+s ; s
.text:000C7ECC MOVS R1, #0 ; c
.text:000C7ECE BLX memset
.text:000C7ED2 LDR.W R4, [R4,#0x558]
.text:000C7ED6 MOV R2, R7
.text:000C7ED8 LDR R3, [R4] ; R3=Head address
.text:000C7EDA ADD.W LR, R3, #0x40 ; Write 32 bytes from 32
.text:000C7EDE ADDS R3, #0x20 ; ' ' ; R3=R3+32
.text:000C7EE0
.text:000C7EE0 loc_C7EE0 ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+1E8↓j
.text:000C7EE0 LDR R0, [R3]
.text:000C7EE2 ADDS R3, #8
.text:000C7EE4 LDR.W R1, [R3,#-4]
.text:000C7EE8 CMP R3, LR
.text:000C7EEA MOV R7, R2
.text:000C7EEC STMIA R7!, {R0,R1} ; R0,R1 write to R7, then increase
.text:000C7EEE MOV R2, R7
.text:000C7EF0 BNE loc_C7EE0 ; v96
.text:000C7EF2 LDR R2, [R4] ; Heap header address
.text:000C7EF4 ADD.W LR, SP, #0x2B0+var_14C
.text:000C7EF8 ADD.W R3, R2, #0x40 ; R3=first address +64
.text:000C7EFC ADDS R2, #0x60 ; '`' ; R2=First address +96
.text:000C7EFE
.text:000C7EFE loc_C7EFE ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+206↓j
.text:000C7EFE LDR R0, [R3]
.text:000C7F00 ADDS R3, #8
.text:000C7F02 LDR.W R1, [R3,#-4]
.text:000C7F06 CMP R3, R2
.text:000C7F08 MOV R7, LR
.text:000C7F0A STMIA R7!, {R0,R1}
.text:000C7F0C MOV LR, R7
.text:000C7F0E BNE loc_C7EFE ; v97
.text:000C7F10 MOVS R1, #0 ; c
.text:000C7F12 MOVS R2, #0x40 ; '@' ; n
.text:000C7F14 ADD R0, SP, #0x2B0+s ; s
.text:000C7F16 BLX memset
.text:000C7F1A LDR R3, [R4,#4] ; R3=unk_25118+4
.text:000C7F1C ADD.W R2, R3, #0x80 ; R2=R3+128
.text:000C7F20 ADD.W R1, R3, #0x40 ; R1=R3+64
.text:000C7F24 ADDS R3, #0xA0 ; R3=R3+160
.text:000C7F26
.text:000C7F26 loc_C7F26 ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+228↓j
.text:000C7F26 LDR.W R0, [R2],#4
.text:000C7F2A CMP R2, R3
.text:000C7F2C STR.W R0, [R1],#4
.text:000C7F30 BNE loc_C7F26
.text:000C7F32 LDR R3, [R4,#4]
.text:000C7F34 ADD R7, SP, #0x2B0+var_1AC
.text:000C7F36 ADD R0, SP, #0x2B0+s
.text:000C7F38 ADD R1, SP, #0x2B0+var_16C
.text:000C7F3A ADD.W R2, R3, #0x20
.text:000C7F3E ADDS R3, #0x60 ; '`'
.text:000C7F40 STR R2, [SP,#0x2B0+var_2B0]
.text:000C7F42 MOVS R2, #0x40 ; '@'
.text:000C7F44 BL ed25519_sign ; ed25519_sign(signature, message, message_len,public_key,private_key);
.text:000C7F48 MOVS R2, #0x20 ; ' ' ; n
.text:000C7F4A MOV R0, R7 ; s
.text:000C7F4C MOVS R1, #0 ; c
.text:000C7F4E BLX memset
.text:000C7F52 LDR R3, =(aPairVerifyAesK - 0xC7F5A)
.text:000C7F54 MOV R2, R7
.text:000C7F56 ADD R3, PC ; "Pair-Verify-AES-Key"
.text:000C7F58 ADD.W LR, R3, #0x10
.text:000C7F5C
.text:000C7F5C loc_C7F5C ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+264↓j
.text:000C7F5C LDR R0, [R3] ; "Pair-Verify-AES-Key"
.text:000C7F5E ADDS R3, #8
.text:000C7F60 LDR.W R1, [R3,#-4]
.text:000C7F64 CMP R3, LR
.text:000C7F66 MOV R4, R2
.text:000C7F68 STMIA R4!, {R0,R1}
.text:000C7F6A MOV R2, R4
.text:000C7F6C BNE loc_C7F5C
.text:000C7F6E LDRH R1, [R3]
.text:000C7F70 ADD.W R11, SP, #0x2B0+var_18C
.text:000C7F74 LDRB R3, [R3,#2]
.text:000C7F76 MOVS R2, #0x55 ; 'U'
.text:000C7F78 MOV R0, R11 ; s
.text:000C7F7A STRH R1, [R4]
.text:000C7F7C MOVS R1, #0 ; c
.text:000C7F7E STRB R3, [R4,#2]
.text:000C7F80 STRB.W R2, [SP,#0x2B0+var_18D]
.text:000C7F84 MOVS R2, #0x20 ; ' ' ; n
.text:000C7F86 BLX memset
.text:000C7F8A LDR R3, =(aPairVerifyAesI - 0xC7F92)
.text:000C7F8C MOV R2, R11
.text:000C7F8E ADD R3, PC ; "Pair-Verify-AES-IV"
.text:000C7F90 ADD.W LR, R3, #0x10
.text:000C7F94
.text:000C7F94 loc_C7F94 ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+29C↓j
.text:000C7F94 LDR R0, [R3] ; "Pair-Verify-AES-IV"
.text:000C7F96 ADDS R3, #8
.text:000C7F98 LDR.W R1, [R3,#-4]
.text:000C7F9C CMP R3, LR
.text:000C7F9E MOV R4, R2
.text:000C7FA0 STMIA R4!, {R0,R1}
.text:000C7FA2 MOV R2, R4
.text:000C7FA4 BNE loc_C7F94
.text:000C7FA6 LDRH R3, [R3]
.text:000C7FA8 STRH R3, [R4]
.text:000C7FAA ADD R4, SP, #0x2B0+var_280
.text:000C7FAC MOVS R3, #0x55 ; 'U'
.text:000C7FAE STRB.W R3, [R11,#0x1F]
.text:000C7FB2 MOV R0, R4
.text:000C7FB4 BL sha512_init
.text:000C7FB8 MOV R1, R7
.text:000C7FBA MOV R0, R4
.text:000C7FBC MOVS R2, #0x13
.text:000C7FBE LDR R7, =(unk_254118 - 0xC7FCC)
.text:000C7FC0 BL sha512_update
.text:000C7FC4 LDR R3, [SP,#0x2B0+var_298]
.text:000C7FC6 MOVS R2, #0x20 ; ' '
.text:000C7FC8 ADD R7, PC ; unk_254118
.text:000C7FCA MOV R0, R4
.text:000C7FCC ADD R7, R3
.text:000C7FCE LDR.W R3, [R7,#0x558]
.text:000C7FD2 LDR R1, [R3]
.text:000C7FD4 ADDS R1, #0x80
.text:000C7FD6 BL sha512_update
.text:000C7FDA LDR.W R3, [R7,#0x558]
.text:000C7FDE MOV R0, R4
.text:000C7FE0 LDR R1, [R3]
.text:000C7FE2 ADDS R1, #0xA0
.text:000C7FE4 BL sha512_final
.text:000C7FE8 MOV R0, R4
.text:000C7FEA BL sha512_init
.text:000C7FEE MOV R1, R11
.text:000C7FF0 MOV R0, R4
.text:000C7FF2 MOVS R2, #0x12
.text:000C7FF4 BL sha512_update
.text:000C7FF8 LDR.W R3, [R7,#0x558]
.text:000C7FFC MOVS R2, #0x20 ; ' '
.text:000C7FFE MOV R0, R4
.text:000C8000 LDR R1, [R3]
.text:000C8002 ADDS R1, #0x80
.text:000C8004 BL sha512_update
.text:000C8008 LDR.W R3, [R7,#0x558]
.text:000C800C MOV R0, R4
.text:000C800E LDR R1, [R3]
.text:000C8010 ADDS R1, #0xE0 ; E0=224
.text:000C8012 BL sha512_final
.text:000C8016 LDR.W R4, [R7,#0x558]
.text:000C801A MOV.W R0, #0x118 ; size
.text:000C801E LDR.W R11, [R4] ; R11=Head address
.text:000C8022 BLX malloc
.text:000C8026 MOVS R1, #0 ; c
.text:000C8028 MOV.W R2, #0x118 ; n
.text:000C802C STR.W R0, [R11,#0x130] ; The newly requested heap address is placed in the heap +304 position
.text:000C8030 BLX memset ; The new heap length is 0x118=280
.text:000C8034 LDR R1, [R4]
.text:000C8036 MOVS R2, #0x80
.text:000C8038 ADD R4, SP, #0x2B0+var_EC
.text:000C803A ADDS R1, #0xA0 ; A0=160
.text:000C803C LDR.W R0, [R1,#0x90] ; 90=144
.text:000C8040 BL f111710 ; The first unknown function: Calculate aes_key_expansion
.text:000C8044 ADD R2, SP, #0x2B0+var_284
.text:000C8046 MOVS R3, #0
.text:000C8048 STR R3, [R2]
.text:000C804A LDR.W R3, [R7,#0x558]
.text:000C804E LDR R3, [R3]
.text:000C8050 STR R4, [SP,#0x2B0+var_2A8]
.text:000C8052 ADD.W R1, R3, #0x120
.text:000C8056 ADDS R3, #0xE0 ; E0=224
.text:000C8058 STR R1, [SP,#0x2B0+var_2B0]
.text:000C805A ADD R1, SP, #0x2B0+s
.text:000C805C STR R1, [SP,#0x2B0+var_2AC]
.text:000C805E MOVS R1, #0x40 ; '@' ; R1=64
.text:000C8060 LDR R0, [R3,#0x50]
.text:000C8062 BL f1116c0 ; second unknown function
.text:000C8066 LDR.W R3, [R7,#0x558]
.text:000C806A ADD R7, SP, #0x2B0+var_AC
.text:000C806C LDR R3, [R3]
.text:000C806E ADD.W LR, R3, #0x40 ; LR=R3+64
.text:000C8072 ADDS R3, #0x20 ; ' ' ; R3=R3+32
.text:000C8074
.text:000C8074 loc_C8074 ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+37C↓j
.text:000C8074 LDR R0, [R3] ; Copy 32 bytes
.text:000C8076 ADDS R3, #8
.text:000C8078 LDR.W R1, [R3,#-4]
.text:000C807C CMP R3, LR
.text:000C807E MOV R2, R7
.text:000C8080 STMIA R2!, {R0,R1}
.text:000C8082 MOV R7, R2
.text:000C8084 BNE loc_C8074 ; Copy 32 bytes
.text:000C8086 LDMIA R4!, {R0-R3}
.text:000C8088 ADD R7, SP, #0x2B0+var_8C ; R7 = SP + 0x2B0 + VAR_8C
.text:000C808A STMIA R7!, {R0-R3}
.text:000C808C LDMIA R4!, {R0-R3}
.text:000C808E STMIA R7!, {R0-R3}
.text:000C8090 LDMIA R4!, {R0-R3}
.text:000C8092 STMIA R7!, {R0-R3}
.text:000C8094 LDMIA.W R4, {R0-R3}
.text:000C8098 MOVS R4, #0
.text:000C809A STMIA.W R7, {R0-R3}
.text:000C809E ADD R3, SP, #0x2B0+var_AC
.text:000C80A0 MOV R0, R6 ; a1
.text:000C80A2 STR R3, [SP,#0x2B0+var_2B0] ; jbyte *
.text:000C80A4 MOVS R2, #0 ; jsize
.text:000C80A6 MOVS R3, #0x60 ; jsize
.text:000C80A8 LDR R1, [SP,#0x2B0+var_29C] ; jbyteArray
.text:000C80AA BL _ZN7_JNIEnv18SetByteArrayRegionEP11_jbyteArrayiiPKa ; Take out 96 bytes and exit
.text:000C80AE MOVS R3, #0x60 ; '`'
.text:000C80B0 B loc_C8140
.text:000C80B2 ; ---------------------------------------------------------------------------
.text:000C80B2
.text:000C80B2 loc_C80B2 ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+7E↑j
.text:000C80B2 CMP R1, #0 ; R1=0
.text:000C80B4 BNE loc_C813A ; right
.text:000C80B6 LDR.W R2, [R4,#0x558]
.text:000C80BA LDR R3, [R2] ; R2=The first address of the heap
.text:000C80BC CMP R3, #0
.text:000C80BE BEQ loc_C818E ; left
.text:000C80C0 LDR R2, [R2,#4] ; R2=unk_25118+0x558+4
.text:000C80C2 CMP R2, #0
.text:000C80C4 BEQ loc_C818E
.text:000C80C6 ADD R2, SP, #0x2B0+var_284
.text:000C80C8 STR R1, [R2]
.text:000C80CA ADD.W R1, R3, #0x120
.text:000C80CE ADDS R3, #0xE0
.text:000C80D0 STR R1, [SP,#0x2B0+var_2B0]
.text:000C80D2 ADDS R1, R5, #4
.text:000C80D4 STR R1, [SP,#0x2B0+var_2AC]
.text:000C80D6 ADD R1, SP, #0x2B0+var_AC
.text:000C80D8 STR R1, [SP,#0x2B0+var_2A8]
.text:000C80DA MOVS R1, #0x40 ; '@'
.text:000C80DC LDR R0, [R3,#0x50] ; R0=&304
.text:000C80DE BL f1116c0
.text:000C80E2 LDR.W R4, [R4,#0x558]
.text:000C80E6 ADD.W LR, SP, #0x2B0+var_EC
.text:000C80EA LDR R2, [R4]
.text:000C80EC ADD.W R3, R2, #0x40
.text:000C80F0 ADDS R2, #0x60 ; '`'
.text:000C80F2
.text:000C80F2 loc_C80F2 ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+3FA↓j
.text:000C80F2 LDR R0, [R3]
.text:000C80F4 ADDS R3, #8
.text:000C80F6 LDR.W R1, [R3,#-4]
.text:000C80FA CMP R3, R2
.text:000C80FC MOV R7, LR
.text:000C80FE STMIA R7!, {R0,R1}
.text:000C8100 MOV LR, R7
.text:000C8102 BNE loc_C80F2
.text:000C8104 LDR R3, [R4]
.text:000C8106 ADD R7, SP, #0x2B0+var_CC
.text:000C8108 ADD.W LR, R3, #0x40
.text:000C810C ADDS R3, #0x20 ; ' '
.text:000C810E
.text:000C810E loc_C810E ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+416↓j
.text:000C810E LDR R0, [R3]
.text:000C8110 ADDS R3, #8
.text:000C8112 LDR.W R1, [R3,#-4]
.text:000C8116 CMP R3, LR
.text:000C8118 MOV R2, R7
.text:000C811A STMIA R2!, {R0,R1}
.text:000C811C MOV R7, R2
.text:000C811E BNE loc_C810E
.text:000C8120 LDR R3, [R4,#4]
.text:000C8122 MOVS R2, #0x40 ; '@'
.text:000C8124 ADD R0, SP, #0x2B0+var_AC
.text:000C8126 ADD R1, SP, #0x2B0+var_EC
.text:000C8128 ADD R3, R2
.text:000C812A BL ed25519_verify
.text:000C812E CMP R0, #0
.text:000C8130 ITE NE
.text:000C8132 MOVNE R4, #0
.text:000C8134 MOVEQ R4, #0xFFFFFFFD
.text:000C8138 B loc_C813E
.text:000C813A ; ---------------------------------------------------------------------------
.text:000C813A
.text:000C813A loc_C813A ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+3AC↑j
.text:000C813A MOV R4, #0xFFFFFFFC
.text:000C813E
.text:000C813E loc_C813E ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+430↑j
.text:000C813E ; Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+48A↓j
.text:000C813E MOVS R3, #0
.text:000C8140
.text:000C8140 loc_C8140 ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+3A8↑j
.text:000C8140 STR.W R3, [R8]
.text:000C8144 MOV R0, R6
.text:000C8146 MOV R1, R10
.text:000C8148 MOVS R2, #0
.text:000C814A MOVS R3, #1
.text:000C814C STR.W R8, [SP,#0x2B0+var_2B0]
.text:000C8150 BL _ZN7_JNIEnv17SetIntArrayRegionEP10_jintArrayiiPKi ; _JNIEnv::SetIntArrayRegion(_jintArray *,int,int,int const*)
.text:000C8154 MOV R0, R6
.text:000C8156 MOV R1, R9
.text:000C8158 MOV R2, R5
.text:000C815A MOVS R3, #0
.text:000C815C BL _ZN7_JNIEnv24ReleaseByteArrayElementsEP11_jbyteArrayPai ; _JNIEnv::ReleaseByteArrayElements(_jbyteArray *,signed char *,int)
.text:000C8160 MOV R0, R6
.text:000C8162 MOV R1, R10
.text:000C8164 MOV R2, R8
.text:000C8166 MOVS R3, #0
.text:000C8168 BL _ZN7_JNIEnv23ReleaseIntArrayElementsEP10_jintArrayPii ; _JNIEnv::ReleaseIntArrayElements(_jintArray *,int *,int)
.text:000C816C MOV R0, R4
.text:000C816E B loc_C8180
.text:000C8170 ; ---------------------------------------------------------------------------
.text:000C8170
.text:000C8170 loc_C8170 ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+26↑j
.text:000C8170 MOV R0, #0xFFFFFFF8
.text:000C8174 B loc_C8180
.text:000C8176 ; ---------------------------------------------------------------------------
.text:000C8176
.text:000C8176 loc_C8176 ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+3C↑j
.text:000C8176 MOV R0, #0xFFFFFFF7
.text:000C817A B loc_C8180
.text:000C817C ; ---------------------------------------------------------------------------
.text:000C817C
.text:000C817C loc_C817C ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+4C↑j
.text:000C817C MOV.W R0, #0xFFFFFFFF
.text:000C8180
.text:000C8180 loc_C8180 ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+6C↑j
.text:000C8180 ; Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+466↑j ...
.text:000C8180 LDR R3, [SP,#0x2B0+var_294]
.text:000C8182 LDR R2, [SP,#0x2B0+var_2C]
.text:000C8184 LDR R3, [R3]
.text:000C8186 CMP R2, R3
.text:000C8188 BEQ loc_C8194
.text:000C818A BLX __stack_chk_fail
.text:000C818E ; ---------------------------------------------------------------------------
.text:000C818E
.text:000C818E loc_C818E ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+3B6↑j
.text:000C818E ; Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+3BC↑j
.text:000C818E MOV R4, #0xFFFFFFFE
.text:000C8192 B loc_C813E
.text:000C8194 ; ---------------------------------------------------------------------------
.text:000C8194
.text:000C8194 loc_C8194 ; CODE XREF: Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9+480↑j
.text:000C8194 ADD.W SP, SP, #0x28C
.text:000C8198 POP.W {R4-R11,PC}
.text:000C8198 ; End of function Java_com_hpplay_happyplay_aaceld_FdkDecodeAudioFun9
After analysis, the memory structure diagram is as follows
The return packet is 96 bytes. Part 1 is (32 bytes) is ecdh_ours The second part is (64 bytes) is the ed25519 signature of (ecdh_ours + ecdh_theirs), and then the data after AES encryption.
- Key code 1, generate ecdh_ours and ecdh_private
curve25519_donna
Seeing this key function, Curve25519 is a currently widely-used Diffie-Hellman function, an algorithm
that allows both parties to calculate the key by exchanging public information
We still have curve25519_donna
: Find this function in the source code
int curve25519_donna(unsigned char *mypublic, const unsigned char *secret, const unsigned char *basepoint);
Whether or not it is consistent with our guess, or if there are pitfalls, we will continue to analyze.
In ida, unk_1FDE68 A 9 31 0 are consistent with the basepoint, then compare the relevant call functions to confirm that they correspond to our guess.
- Key code 2, generate ed25519 signature
void ed25519_sign(unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key, const unsigned char *private_key)
Function arguments
sig_msg = ecdh_ours + ecdh_theirs
public_key = ed_ours
Private_key = ed_private (the last 32 bytes are replaced, confirmed multiple times)
-
Key code 3, generate AES encryption key
generate key
sha512_init
sha512_update -> "Pair-Verify-AES-Key"
sha512_update -> ecdh_secret
sha512_final -> sha512_1
-
Key code 4, generate AES encryption iv
generate iv
sha512_init
sha512_update -> "Pair-Verify-AES-IV"
sha512_update -> ecdh_secret
sha512_final -> sha512_2
- Encryption algorithm judgment
With key and iv then encryption
Need to focus on f1116c0
with f1117b0
function, I don't know what these two functions are doing at first, but the algorithm is more complicated,
it should be an existing algorithm, we follow up and find
f1117b0 uses unk_118FE8Go
to see inside
Based on this clue we google 0xC3 0x72 0x16 0x1D
and find https://gnupg.org/ftp/gcrypt/historic/rijndael.c
,
so this is the aes encryption algorithm, which is relatively simple, continue to search and found
https://www.ghostscript.com/doc/base/aes.c
. This function is corresponding to f1117b0
for initializing the
key function aes_setkey_enc
function
f11117b0
Argument analysis
-
The first argument 304 base address ()
-
The second argument sha512_1 the first 16 bytes as the key
-
The third argument is 128
f1116c0
Is the final encryption function, confirmed to be CTR encryption by comparison with several encryption methods of AESf1116c0
Argument analysis- The first argument 304 base address
- The second argument 64
- The third argument iv_off
- The fourth argument sha512_2 the first 16 bytes as the initial iv
- The fifth argument 16b byte address
- The sixth argument input string
- The seventh output string
Client sends 68 bytes (the first 4 bytes are 00 00 00 00) <-> server replies with 0 bytes
is the same as the first pair-verify FdkDecodeAudioFun9
Function
- Key function
int ed25519_verify(const unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key)
ed25519_verify
Argument analysis
- The first argument is the 64-byte signature sent by the client, used as a checksum.
- The second argument is the signed message.
- The third argument, the length of the signature message
- The fourth argument, the public key of the client
According to the verification result: if correct continue, it in error, disconnect.
- First fp-setup
Client sends 16 bytes <-> server replies 142 bytes
- Second fp-setup
Client sends 164 <->server reply 32
The two fp-setups correspond to FdkDecodeAudioFun1
and FdkDecodeAudioFun2
respectively. The calling relationship between the two functions is as follows.
It looks very complicated and it takes time and effort to analyze Here are two ideas:
- Export this part of the assembly code, directly call the function to run
- Directly call the function in the .so file
Although both methods can achieve the goal, the code becomes uncontrollable, and after searching by chance find an airplay1 project
shairplay
with these two functions implemented, but the code is older, it is a project from seven years ago. By trying it out, I found that it can be used normally. This demo is also based on this open source code and has been extensively modified.
After the handshake protocol, there will be two SETUP requests (used in the data analysis) before the mirror data is sent.
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
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
Empty
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>
After the second setup, start sending data and add filters.
(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)) ,
the result is shown below
Here server side uses 7020 (tcp) and 7011 (udp) two ports, the client side uses 60373 and 59694 two ports
According to the size of the sent packet, 7020 is the receiving mirror data port. The following detailed analysis
- 7011 (udp) port analysis
7011 -> 60373 48b
60373 -> 7011 48b
7011 -> 60373 48b
60373 -> 7011 48b
Port 7011 is handled by UDPListenerScreenTC
Looked at the code, is the ntp protocol, sent every 3 seconds
Right-click udp package in wireshark, decode as, select ntp to see the parsing, this is relatively simple, we directly enter
the image data parsing analysis
- 7020 (tcp) port analysis
Find multiple image services through port information, mainly including the following classes
Modify the sLevel of LeLog to 0, re-package, locate it according to the printed log to confirm that it is
handled by GeneralMirrorService (requires accessories)
After the analysis, we know that there are two data types, first determine that the first 4 bytes are GET / POST, if it is not processed
according to the image data, the format is as follows
The payloadsize is the image data. According to the analysis, the data here is encrypted.
We see GeneralMirrorService
in this
if (!this.mIsAesInited) {
mainServer.this.initAESUseRAOPKey();
this.mIsAesInited = true;
}
mReturn = mainServer.this.decryptAES(vstreamdata_in, 0, payloadsize, mainServer.this.vstreamdata, 0);
Pay attention to the two functions initAESUseRAOPKey
and decryptAES
in mainServer, you can confirm that this is also aes
encrypted data.
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);
}
}
Among them, we look at it again, using CTR decryption, we need to know the key and iv to decrypt,
Through mainServer
Search in class mkey
Find the following code
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; 16 out outsize ver_signal streamid pairSessionId
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;
/ / Take the first 16 bytes of r51
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;
// Take the 16-31 of r51
java.lang.System.arraycopy(r0, r4, r6, r9, r10);
r0 = r200;
r4 = com.hpplay.happyplay.mainServer.this;
r4 = r4.mPlaybackService;
r6 = 1;
Analysis shows that both key and iv come from FdkDecodeAudioFun10
First clarify the meaning of several parameters
- aesekey
The ekey in the first SETUP is a 72-byte byte, and aesekey is a 16-bit byte after decrypting the ekey through FdkDecodeAudioFun3. FdkDecodeAudioFun3 is related to FirePlay, and its complexity is similar to FdkDecodeAudioFun1 and FdkDecodeAudioFun2. Call this function and twice-call fp-setup
, as in shairplay
to find the associated implementation
- ver_signal
Version judgment, 1 in airplay2
- streamid
The value of the streamConnectionID field of the second SETUP
- pairSessionId
Session ID, can be ignored
- Out is a 32-byte output
FdkDecodeAudioFun10
is the native layer code, according to the previous analysis, this function is relatively simple. No
analysis is required, if you are interested, you can read it yourself. The following is the key code:
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
The key in AES is the first 16 bytes of hash1
Iv in AES is the first 16 bytes of hash2
After you have the key and iv, you can decrypt it. The decrypted data is the H264 bare stream in avcc format.
At this point, the analysis of the screen image data is completer
Tips: When writing the code is here, the streamConnectionID was converted to %lld format, which will cause the correct image to be displayed for a while when the screen is projected, but then incorrect data for a while. Looking at the log afterwards, it was found that the output streamConnectionID was a negative value, resulting in decryption error. The correct format is %llu.
When the image package is captured, the audio package is not captured. Here, the package containing the audio is re-
captured.
Filter the port information and see the audio interaction
It is udp protocal related to audio, and several ports are found
- Server side
42820 audio port
46440 controlport && timeport (actually two ports, the same one is used for X broadcasts)
-
Client side
63658 controlport
59593 timeport
The client needs to know that server ports it must interact with. Go ahead and find the third 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>
Since the code is written in java, using udp, the relationship between the code is confirmed by searching the
DatagramSocket and related logs.
In order to confirm the role of the three ports, you need to analyze the code in the AudioServer
- Key code one
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 milliseconds
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;
}
}
}
}
server 46440 <-> client 59593
The data content is as follows
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 first sent 32 bytes of data to 59593, corresponding to:
The first 24 bytes are fixed as
0x80,0xd2,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0
The last 8 bytes are the transmission time of the ntp time.
Also received are 32 bytes, the first 8 bytes are fixed 80,d3,00,07,00,00,00,00
The last 32 bytes are Origin_Timestamp
,Receive_Timestamp
and Transmit_Timestamp
So timingPort is used for ntp pairing.
server 46440 <-> client 63658
Look at this line of code
this.type_tc = packet[1] & TransportMediator.KEYCODE_MEDIA_PAUSE;
, TransportMediator.KEYCODE_MEDIA_PAUSE
The value is 0x7F, and type_tc
has two possible values, 84 and 86
84 does have any effect in the code, you can ignore it. 86 is the retransmitted audio data code, the
package will contain the audio package
The port where the server receives audio data is 42820
Video data is encrypted, so audio encryption is very likely, and continue to analyze the code
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) {
/ / Use alac decoding
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)) {
/ / Use aac-eld decoding
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);
}
}
}
}
The audio package format is shown
Type = 2nd byte & 0x7F
, only processing data of type 86 (edited: this is incorrect: most data is type 96 (not retransmitted)
)
It should be noted here that if the audio data is 4 bytes and is {0x0, 0x68, 0x34, 0x0}, it means that there is no audio
data and it will not be processed.
Continue to analyze the AudioBuffer's putAacEldPacketInBuffer
method, the audio data is AES encrypted, but this time
the decryption is by CBC. The key code is as follows
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;
}
}
Again, we need to know the key and iv.
Through analysis, the key is a native function. FdkDecodeAudioFun11
Output, analysis code can be derived
eaeskey
The 16-byte ekey decrypted for 72 bytes, the hash is used as the key, the key code is as follows
sha512_init
sha512_update->eaeskey
sha512_update->ecdh_secret
sha512_final->eaeskey
Take the first 16 bytes of eaeskey as the key, iv is from the first SETUP
, when the client sends the eiv in the data bplist
After getting the key and iv, you can decrypt it.
Here is the AAC bare stream, access fdk-aac decoding to pcm, here encountered two problems
- Problem 1: The length of pcm is 3 times the original length
By printing the serial number, and then looking at the received package, it is found that each serial number will be sent 3
times and needs to be filtered.
After being Filtered, the duration is normal
- Problem 2: pcm playback is noise, abnormal music Through the log, it is found that fdk-aac returns error 0x4006, indicating that the data source is wrong. After inquiring about the problem through various possible methods, it was finally confirmed that it was the problem of the aes library selected in C by using the java layer decryption method. In each decryption, the aes_context needs to be re-initialized and then decrypted. Before, the aes_context was cached, which caused the error.
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......................................
Get volume data
Adjust volume data
Heartbeat
client->server
<plist version="1.0">
<dict>
<key>streams</key>
<array>
<dict>
<key>type</key>
<integer>96</integer>
</dict>
</array>
</dict>
</plis
According to type=96, it can be concluded that the audio service is destroyed.
<plist version="1.0">
<dict>
<key>streams</key>
<array>
<dict>
<key>type</key>
<integer>110</integer>
</dict>
</array>
</dict>
</plist>
According to type=110, it can be concluded that the image service is destroyed.
Currently open source, you can see the source directly: Github.