Skip to content

AirPlay2

fduncanh edited this page Oct 7, 2022 · 78 revisions

Analysis of AirPlay2 Technology (notes by author of AirServer, 2019-03-13)

Foreword

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:

1. Protocol: RTP, RTSP, DNS, DNS-SD, mDNS, NTP

2. Encryption and decryption algorithm: curve25519, ed25519, AES (cbc&ctr)

3. Audio and video foundation: h264, aac

4. Reverse of dex format and so in Android

App selection

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: The Android app being analysed in this article appears to be com.hpplay.happyplay 7.1.0 APK)

Screen casting test

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:

fig1

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

fig2

Here you can see that the client only uses raop and airplay two services, query SRV record, the content that can airplay uses port 52233port, raop uses port 52244

In frame146, the server sends the reply multicast packet

fig3

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.

Handshake protocol

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:

fig4

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 fig5

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

1. info

The content of the request package is shown in the following figure:

fig6

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].

2. pair-setup

Client sends 32 bytes <-> server replies 32 bytes

Search for the string "pair-setup" and find the following code

fig7

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

fig8

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

fig8a

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.

3. pair-verify

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

fig9

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

fig10

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

fig11

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

fig12

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 AES

    f1116c0 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

4. The second pair-verify

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 FdkDecodeAudioFun9Function

  • 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.

5. Two fp-setup

  • 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.

fig13

fig14

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.

Mirror data

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

1. First 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

Empty

2. Second 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 Image data transmission analysis

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

fig15

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

fig16

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

fig17

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

fig18

The payloadsize is the image data. According to the analysis, the data here is encrypted.

4.Image data decryption

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 FairPlay, 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 completed.

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.

Audio data

1. Packet capture analysis

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

fig19

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.

2. 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>

3. Code analysis

Since the code is written in java, using udp, the relationship between the code is confirmed by searching the DatagramSocket and related logs.

fig20

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;
            }
        }
    }
}

4. timingPort interaction

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_TimestampReceive_Timestamp and Transmit_Timestamp

So timingPort is used for ntp pairing.

5. controlPort interaction

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

6.Audio data transmission analysis

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

fig20a

Type = 2nd byte & 0x7F , only processing data of type 86 (edited: this is an error, both types 86 and 96 are processed: most audio data is type 96 (not resent) sent through the rtp data port, type 86 is resent audio data sent via the rtp control port)

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.

7. Audio data decryption

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.

fig21

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.

Other requests

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

Get volume data

2. SET_PARAMETER

Adjust volume data

3. FEEDBACK

Heartbeat

4. TEARDOWN-1

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.

5.TEARDOWN-2

<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.

achieve

Currently open source, you can see the source directly: Github.

Reference link