Hacking WhatsApp, part 2 - parsing the Whatsapp VOIP protocol





In this article I want to tell you how I cracked several parts of the WhatsApp VoIP protocol using a jailbroken iOS device and a set of different analysis programs.



Recently, Whatsapp has received a lot of attention due to vulnerabilities and opportunities for hackers.

From this point of view, it is very interesting for research on its safety.



Everyone who is also interested in this, welcome under the cat.





Although the official pages of Whatsapp have a description of its encryption, in fact, there is no detailed information anywhere about how it works and how it is implemented into the protocol.

Hence, there is no basis for detailed security analytics on Whatsapp itself.



My research was based on three things:



1. Network traffic analytics

2. Binaries

analytics 3. Application behavior analytics in different modes



Toolkit



To analyze the Wahtsapp client for iOS, I used the following tools:



- Binary descriptor - bfdecrypt

- Binary file disassembler - Hopper Disassembler and radare2

- Network traffic analysis - Wireshark

- Application action analytics - Frida



How I set up the jailbreak on iOS is beyond the scope of this article.



Analyzing network traffic



In this part, we will analyze the network traffic of the Whatsapp client during a call, which we will record using Wireshark.



To record such traffic, I created a remote virtual network interface.



The command for Makos looks like this:



rvictl -s Here the device UUID needs to be replaced with the UUID of the device with the watsap client.



Wireshark detects the use of Session Traversal Utilities for NAT (STUN).

STUN is a signaling protocol that is required to establish peer-to-peer connections between clients.







Here the WhatsApp client uses TCP packets to communicate with different Watzap servers.

At the same time, UDP packets are used for exchange between clients.

Hundreds of UDP packets pass in a minute.

Vatsap uses Secure Real Time Protocol (SRTP) and it is obvious that these UDP packets contain SRTP data about the call.



SRTP protocol provides encryption, authentication and protection against replay attacks on RTP traffic.



Let's take a closer look at the SRTP packets exchanged between A and B sides.

To do this, convert them to hexadecimal:







It can be seen that the fields contain RTP headers specific to SRTP.



The first four bytes (highlighted in red) are the 7 RTP header fields.



Let's consider them in more detail:



0x8078001e = 0b10_0_0_0000_0_111100_00000000000011110 = V = 10 | P = 0 | X = 0 | CC = 0000 | M = 0 | PT = 111100 | SEQ = 00000000000011110







The first 2 bits contain the version number (V), in our case it is the second version.

The third bit is a field for optional information, in our case it is empty.

The fourth bit - the extension field (X) indicates that in this case there are no other headers after the RTP packet header.



Bits 5 to 8 - Contains the number of CSRC identifiers following the permanent header.

CSRC (contributing source) is the source of the RTP packet stream that contributes to the total stream generated by the RTP mixer. The mixer inserts a list of SSRC identifiers that identify partial sources into the header of RTP packets. This list is called a CSRC list. For example - an audio conference where the mixer marks all speakers whose voice generates outgoing packets. This allows the receiving side to identify the speaker, although all packets have the same SSRC ID.



8 bits is a bit marker (M). Used at the application level and determined by the profile. If this field is set, then the package data has some special meaning for the application.



The next 6 bits are additional data type codes. This data is not defined in the RTP and SRTP standards. The meaning of these bits is most likely a custom one chosen by Whatsapp.



The last 17 bits indicate the clock source. The number is incremented in order by 1 when the next RTP data packet is sent, this code can be used by the receiver to register packet losses and to restore the true order of the sent fragments. According to the standard, the initial value of the code is random, but this recommendation is not fulfilled by the watsap, because as we can see from the Wireshark data, the initial value of the watsap is always 0.



The next 4 bytes (highlighted in blue) are the packet timestamp.



4 bytes thereafter (green) - SSRC field. It identifies the synchronization source. This identifier is chosen at random so that there are no two equal SSRC codes within one RTP session. All applications must be able to detect when SSRCs are equal. If the sender changes its transport address, it must also change the SSRC identifier.



So, we found out that Whatsapp uses SRTP to protect calls.

This is confirmed by the structure of UDP packets exchanged between Watcap clients.



Also, Watzap uses the TCP protocol to exchange data between the client and the server.

Below we will see how the Noise Pipes Protocol is used to encrypt this part. Binary



analytics



Vatsap client for iOS contains 2 main binaries - WhatsApp application binary and WhatsApp core framework.



In this part, we'll take a closer look at them with the Hopper Disassembler and radare2.



These binaries are encrypted when downloaded from the Appstore.

Here we tricked Apple into jailbreaking an iOS device and gaining access to these files.



Also add that these Whatsapp binaries were decrypted with bfdecrypt.



Next, I'll show you how I gathered information about the protocol fundamentals, algorithms, and open source libraries that Whatsapp uses.



Open source libraries are especially interesting because they can be easily parsed.



libsignal-protocol-c



Watsap uses libsignal-protocol-c - an open source library - which he implemented in the Signal Protocol.



The protocol is based on the Double Ratchet Algorithm, which encrypts watsap messages.



This library was found in the Whatsapp binaries for the following characteristic features:



r2 WhatsAppCore

[0x0082b517]> / _signal_

Searching 8 bytes in [0x0-0x654000]

hits: 33

0x00837a7b hit2_0 .il_key_data_from_signal_keydispatch_.

0x0083df33 hit2_1 ._torlice_signal_protocol_paramet.

0x008407c0 hit2_2 ​​.d_fac_3key_signal_message_big.

0x00840d50 hit2_3 .mmetric_signal_protocol_paramet.

0x00840e70 hit2_4 .ob_signal_protocol_paramet.

0x00841492 hit2_5 .pre_key_signal_messagesigna.

0x008de24b hit2_6 .agc_reset_alice_signal_protocol_paramet.

0x008de274 hit2_7 .rs_create_alice_signal_protocol_paramet.

0x008de440 hit2_8 .bitno_MRDTX_bob_signal_protocol_paramet.

0x008de467 hit2_9 .ters_create_bob_signal_protocol_paramet.

0x008e311c hit2_10 .pre_big_pre_key_signal_message_copy_pr.

0x008e3139 hit2_11 .ge_copy_pre_key_signal_message_create_.

0x008e3158 hit2_12 ._create_pre_key_signal_message_deserial.

0x008e317c hit2_13 .rialize_pre_key_signal_message_destroy.libsrtp




libsrtp



Watcap also uses libsrtp to implement its Secure Real Time Protocol.

The symbol names have been removed from the Whatsapp binaries, but despite this the binaries contain lines that directly indicate their link to libsrtp:



r2 WhatsApp

[0x1001ada34]> / libsrtp

0x100ee5546 hit1_0 .rc% 08XUnknown libsrtp error% duns.

0x100ee57eb hit1_1 .d to initialize libsrtp:% sFailed to r.

0x100ee580a hit1_2 .led to register libsrtp deinit.Failed.

0x100ee5831 hit1_3 .to deinitialize libsrtp:% sAES_CM_128_.

0x100ee5883 hit1_4 .ck crypto Init libsrtp. create pool ..

0x100f07b80 hit1_5. packet:% slibsrtpstat test% s: c.





Moreover, watsap binaries contain strings that are used in raw libsrtp code, for example “cloning stream (SSRC: 0x% 08x)”:



r2 WhatsApp

[0x1013ddb4f]> / cloning stream

Searching 14 bytes in [0x100000000-0x100fb4000]

hits: 1

0x100f07823 hit7_0 .sent! Srtp% s: cloning stream (SSRC: 0x% 08x).




PJSIP



Also, watsap uses PJSIP, which implements multimedia communications, signaling and audio and video data coding.



STUN is also implemented there, which is clearly seen when analyzing with Wireshark.



The library was identified by PJSIP line-by-line in the watcap binaries due to the debug information in PJSIP:



r2 WhatsApp

[0x1013ddb4f]> / pjmedia

Searching 7 bytes in [0x100000000-0x100fb4000]

hits: 180

0x100edd55f hit9_0 .piggybackio_ccpio_piggybackio_ccpio

0x100edd591 hit9_1 .r %d, stream %ppjmedia_audio_piggyback.

0x100edd5d4 hit9_2 .d, tx_packet %dpjmedia_audio_piggyback.

0x100edd601 hit9_3 .ideo_enabled %dpjmedia_audio_piggyback.

0x100eddcf3 hit9_4 .ibyuv converterpjmedia_converter_creat.

0x100eddd21 hit9_5 .rter count = %dpjmedia_converter_creat.

0x100ede3e3 hit9_6 .rame, status=%dpjmedia_delay_buf_get_s.

0x100ede46e hit9_7 .%sec_delay_bufpjmedia_echo_create2: %.

0x100ede64d hit9_8 .eUnknown pjmedia-videodev error.

0x100ede90c hit9_9 .o errorUnknown pjmedia-audiodev error.

0x100edebba hit9_10 .ATENCY)Unknown pjmedia error %dUnspec.

0x100ee027e hit9_11 .queue.format.cpjmedia_format_get_vide.

0x100ee02ca hit9_12 .mat info for% dpjmedia_format_get_vide.

0x100ee1446 hit9_13 .c_buf too shortpjmedia_h26x_packetize.




mbed TLS



Also watcap uses the open source mbed TLS to implement its TLS protocol.

This library was identified in the Watzap code by the following function names:



r2 WhatsAppCore

[0x0082b517]> / mbedtls

Searching 7 bytes in [0x814000-0x934000]

hits: 41

0x008e299b hit5_0 .TLSErrorDomain_mbedtls_aes_crypt_cbc

0x008e29b2 hit5_1 ._aes_crypt_cbc_mbedtls_aes_crypt_cfb12.

0x008e29cc hit5_2 .s_crypt_cfb128_mbedtls_aes_crypt_cfb8.

0x008e29e4 hit5_3 .aes_crypt_cfb8_mbedtls_aes_crypt_ctr_.

0x008e29fb hit5_4 ._aes_crypt_ctr_mbedtls_aes_crypt_ecb_.

0x008e2a12 hit5_5 ._aes_crypt_ecb_mbedtls_aes_decrypt_mb.

0x008e2a27 hit5_6 .ls_aes_decrypt_mbedtls_aes_encrypt_mb.

0x008e2a3c hit5_7 .ls_aes_encrypt_mbedtls_aes_free_mbedt.

0x008e2a4e hit5_8 .edtls_aes_free_mbedtls_aes_init_mbedt.

0x008e2a60 hit5_9 .edtls_aes_init_mbedtls_aes_setkey_dec.

0x008e2a78 hit5_10 .aes_setkey_dec_mbedtls_aes_setkey_enc.

0x008e2a90 hit5_11 .aes_setkey_enc_mbedtls_cipher_auth_dec.

0x008e2aad hit5_12 .r_auth_decrypt_mbedtls_cipher_auth_enc.

0x008e2aca hit5_13 .r_auth_encrypt_mbedtls_cipher_check_ta.






XMPP



Watzap also uses the open Extensible Messaging and Presence Protocol (XMPP) to exchange asynchronous messages between clients.



This was discovered by the class names in the watsap code used in XMPP:



r2 WhatsApp

[0x1013ddb4f]> / XMPP

Searching 4 bytes in [0x1013ac000-0x1014b4000]

hits: 150

Searching 4 bytes in [0x100fb4000-0x1013ac000]

hits: 150

Searching 4 bytes in [0x100000000-0x100fb4000]

hits: 396

0x1013d05b5 hit12_0. @ _ OBJC_CLASS _ $ _ XMPPAckStanza @ _.

0x1013d05d6 hit12_1. @ _ OBJC_CLASS _ $ _ XMPPBinaryCoder.

0x1013d05fa hit12_2. @ _ OBJC_CLASS _ $ _ XMPPCallStanza.

0x1013d0624 hit12_3. @ _ OBJC_CLASS _ $ _ XMPPChatStateStanza.

0x1013d064b hit12_4. @ _ OBJC_CLASS _ $ _ XMPPConnection.

0x1013d0679 hit12_5. @ _ OBJC_CLASS _ $ _ XMPPError.

0x1013d069e hit12_6. @ _ OBJC_CLASS _ $ _ XMPPGDPRDeleteReport.

0x1013d06cd hit12_7. @ _ OBJC_CLASS _ $ _ XMPPGDPRGetReportSta.

0x1013d0707 hit12_8. @ _ OBJC_CLASS _ $ _ XMPPGDPRRequestRepor.

0x1013d0736 hit12_9. @ _ OBJC_CLASS _ $ _ XMPPIQStanza.

0x1013d0762 hit12_10. @ _ OBJC_CLASS _ $ _ XMPPMessageStanza.

0x1013d0787 hit12_11. @ _ OBJC_CLASS _ $ _ XMPPMessageStatusCha.

0x1013d07b9 hit12_12. @ _ OBJC_CLASS _ $ _ XMPPMultiReceipt.

0x1013d07dc hit12_13. @ _ OBJC_CLASS _ $ _ XMPPNotificationStan.

...




Noise Protocol Framework



According to official reports, Watzap uses the Noise Protocol Framework to securely communicate between clients and servers.

The Noise Protocol Framework was designed to create easy-to-use cryptographic protocols using a set of discrete blocks.

But strictly speaking, Watzap only uses the Noise Pipes Protocol, which was taken from the more complete Noise Protocol Framework.



These lines were found in the watzap binaries:



“Noise_XX_25519_AESGCM_SHA256”,

• “Noise_IK_25519_AESGCM_SHA256”,

• “Noise_XXfallback_25519_AESGCM_SHA256”.



These lines contain the handshake patterns implemented in watsap clients.



The first line belongs to the WANoiseFullHandshake class.

The second is to WANoiseResumeHandshake and the last to WANoiseFallbackHandshak.



We will not consider in detail how this protocol works in the framework of this article.



Runtime Analysis



In this part, we will explore the behavior of the watsap client using Frida.



Frida is the so-called Dinamic Instrumentation Toolkit, which is a set of tools that allow you to inject your own code into other applications on the fly.

We will connect to a process in the application and change its behavior using an interactive JS console.



Key Transport



In this part, we will explore the key mechanisms of work of the watcap protocol.

According to the official description from Whatsapp describing the encryption of a VOIP call - the initiator of the call generates a random 32 byte SRTP master secret.

Then the encrypted message is transmitted to side B with the content of this SRTP master secret.

This information is then used for the B-side reconstruction.



First I made a trace using the word “secret”:



frida-trace -U WhatsApp -m "* [* * Secret *]" -m "* [* * secret *]"

Following the initiation of the call call vatsap deriveSecretsFromInputKeyMaterial method using WAHKDF class:



+ [WAHKDF

deriveSecretsFromInputKeyMaterial: 0x121e08a20

salt: 0x0

the info : 0x121e07840

outputLength: 0x2e

withMessageVersion: 0x3

]




The input values ​​0x121e08a20 and 0x121e07840 point to Objective-C objects.

Frida allows you to create proxy Objective-C objects for JavaScript.



The deriveSecretsFromInputKeyMaterial hook is used to print the description of objects:



{

onEnter: function (log, args, state) {

log ("+ [WAHKDF deriveSecretsFromInputKeyMaterial:" +

ObjC.Object (args [2]) .toString () + "\ n" +

"salt:" + ObjC.Object (args [3]) .toString () + "\ n" +

"info:" + ObjC. Object (args [4]) .toString () + "\ n" +

"bytes:" + args [5] .toInt32 () + "\ n" +

"withMessageVersion:" + args [6] .toInt32 () + "\ n]");

}

}





After the script:



+ [WAHKDF deriveSecretsFromInputKeyMaterial: <09a38e76 fe90e4f1 26ed66d0 5a6783ba d48776b6 1daaf7c9 39c005ea 2d8ccdf6>

salt: nil

info: <34393135 39303537 37313632 3040732e 77686174 73617070 2e6e6574>

bytes: 46

withMessageVersion: 3

]




The first and third parameters like NSData objects ,which contain a static byte buffer.

The first parameter is 32 bytes long, as described in the WhatsApp white paper.



The third parameter is an ASCII string containing the JID of the caller.

Later we will see that, indeed, it is the first line that contains the master secret.



Encrypting the master secret



According to the WhatsApp white paper, the master secret is a necessary part to secure a call session.

Therefore, it must be safely delivered to side B.

To investigate how such delivery takes place, I made a trace containing keywords relevant to the encryption process:



frida-trace -U WhatsApp -m "* [* * crypt *]" -i "* crypt *"



After initiating the call, call the signal_encrypt function from the libsignal-protocol-c library.

Signal_encrypt header:



plaintext readable with Frida's hook:







The first 4 bytes are used to serialize the master secret using Google's protocol buffers.

(Serialization is the process of translating some data structure into a sequence of bits.)

The next bytes are the master secret itself.

The last 13 bytes are encryption padding.

Plaintext is encrypted with AES-256 in CBC mode.

The encryption keys are obtained using the Double Ratchet Algorithm, which is part of the Signal Protocol.

Libsignal-protocol-c and Signal Protocol are not covered in this article.



Signal_encrypt







result : The result contains more bytes because an authentication tag has been added to the message that uses HMAC-SHA256.



We have covered the first part of the WhatsApp VoIP protocol.



To summarize, the master secret is serialized and encrypted using a 256-bit AES key in CBC mode.

The encryption key and authentication key were obtained using the open source libsignal-protocol-c library.



Parse the master secret



Let's see how the master secret is encrypted.



We make a trace with the signal keyword:



frida-trace -U WhatsApp -i “* signal *”

Frida shows that the textsecure__signal_message__pack function is involved in encrypting the master secret.



The function creates a Signal message containing the encrypted master secret and other required parameters: The







bytes highlighted in green are used for serialization.

Blue bytes - sender key ratchet (used for end-to-end encryption).

The message counter is orange bytes.

Finally, the bytes of the master secret are highlighted in green.



When tracing XMPP, we can see that the writeNoiseFrameToSocketWithPayload method from the XMPPStream class is called.

This method sends XMPP messages encrypted by the Noise Pipes Protocol using the TCP protocol to the watchap server.



Here I opened the content in payload:







This is a binary XMPP message containing the signaling message created above.

For disassembly, we create a trace of the XMPPBinaryCoder class.



This class has a serialize method that creates a binary representation of XMPP strings.

When displaying these parameters, you can see various key pairs appended to the XMPP message:



- [XMPPBinaryCoder serialize:

[call from='49**********@s.whatsapp.net '

id =' 1555415586-10 '

to='49**********@s.whatsapp.net '

[offer call-id =' 45D7827C624353A70084AED9B8C509D3

'call-creator='49**********@s.whatsapp .net '

[audio rate =' 8000 'enc =' opus']

[audio rate = '16000' enc = 'opus']

[net medium =' 3 ']

[capability ver =' 1 '{5b}]

[encopt keygen = '2']

[enc v = '2' type = 'pkmsg' {201b}]

]

]

] compressed: 0x0]




I managed to make a fake notification about a missed call from A to B, although the call was actually initiated by Mallory ...

This became possible after rewriting the call-creator and from parameters in the JID on the A side.

Although the Mallory name is shown in the notification.

When Party B starts to respond to such a message, then Party A is called instead of Mallory.

This behavior will be more interesting to analyze later.







Let's summarize the intermediate results - in the watsap, the encrypted master secret is packed into a signal message, which is added to the XMPP strings.

XMPP strings also contain the ID and JID of both sides.



Transferring the master secret to the other party



According to the official description from Watsup clients use the Noise Pipes protocol with Curve25519, AESGCM, and SHA256 from the Noise Protocol Framework.



If you use tracing containing keywords related to the Noise Protocol Framework, you can see that the WANoiseStreamCipher class is used to encrypt calls to Vatsap servers.

The class uses the encryptPlaintext method.

After the call is initiated, the plaintext value is the XMPP message described above.

The message is then encrypted again using the mbed TLS library mbedtls_gcm_crypt_and_tag.

mbedtls_gcm_setkey is 256 bits in size, which means AES-256-GCM is used.

The encryption key is used from the Noise Pipes Protocol, which is not covered in this article.

The encrypted plaintext then goes through TCP to the watsap server (this can be seen with Wireshark).

The server will then forward this message to the called party to initiate the call.







Key Shaping



In this part, we will look at how the Key Shaping Function (KDF) works /

The results were obtained using Frida while tracing the WAHKDF class and libcommonCrypto library.

The WAHKDF class was used to extract keys, salt and one-time codes when initializing SRTP streams.

The deriveSecretsFromInputKeyMaterial method is called 10 times before the call starts:



+[WAHKDF deriveSecretsFromInputKeyMaterial: <09a38e76 fe90e4f1 26ed66d0 5a6783ba d48776b6 1daaf7c9 39c005ea 2d8ccdf6>, salt: nil, info: <34393135 39303537 37313632 3040732e 77686174 73617070 2e6e6574>, bytes: 46, withMessageVersion: 3] => result: <4633c47f 94d5ed59 93a6dba8 514d5fb8 5092ba90 4256f8d3 4d56e72e 665bcd4c 5b6c418b db811e7f 84a70c83 f401>+[WAHKDF deriveSecretsFromInputKeyMaterial: <09a38e76 fe90e4f1 26ed66d0 5a6783ba d48776b6 1daaf7c9 39c005ea 2d8ccdf6>, salt: nil, info: <34393137 ******** ******** ******** ******** 6170702e 6e6574>, bytes: 46, withMessageVersion: 3] => result: <a174670a e25d8138 4de0ed3b f4ce7f76 c62c1d00 9ece6573 2ecb497b 1f6ed09c 18c444b9 c180fbd3 51713739 761c>+[WAHKDF deriveSecretsFromInputKeyMaterial: <34354437 38323743 36323433 35334137 30303834 41454439 42384335 30394433>, salt: <00000000>, info: <34393135 39303537 37313632 3040732e 77686174 73617070 2e6e6574>, bytes: 4, withMessageVersion: 3] => result: <0ec654fd>+[WAHKDF deriveSecretsFromInputKeyMaterial: <34354437 38323743 36323433 35334137 30303834 41454439 42384335 30394433>, salt: <01000000>, info: <34393135 39303537 37313632 3040732e 77686174 73617070 2e6e6574>, bytes: 4, withMessageVersion: 3] => result: +[WAHKDF deriveSecretsFromInputKeyMaterial: <34354437 38323743 36323433 35334137 30303834 41454439 42384335 30394433>, salt: <04000000>, info: <34393135 39303537 37313632 3040732e 77686174 73617070 2e6e6574>, bytes: 4, withMessageVersion: 3] => result: +[WAHKDF deriveSecretsFromInputKeyMaterial: <34354437 38323743 36323433 35334137 30303834 41454439 42384335 30394433>, salt: <00000000>, info: <34393137 ******** ******** ******** ******** 6170702e 6e6574>, bytes: 4, withMessageVersion: 3] => result: +[WAHKDF deriveSecretsFromInputKeyMaterial: <34354437 38323743 36323433 35334137 30303834 41454439 42384335 30394433>, salt: <01000000>, info: <34393137 ******** ******** ******** ******** 6170702e 6e6574>, bytes: 4, withMessageVersion: 3] => result: +[WAHKDF deriveSecretsFromInputKeyMaterial: <34354437 38323743 36323433 35334137 30303834 41454439 42384335 30394433>, salt: <04000000>, info: <34393137 ******** ******** ******** ******** 6170702e 6e6574>, bytes: 4, withMessageVersion: 3] => result:



, JID .

6 SRTP , 3 .



JavaScript:



const crypto = require(«crypto»);



// master secret

const keyMaterial = new Buffer(

«09a38e76fe90e4f126ed66d05a6783bad48776b61daaf7c939c005ea2d8ccdf6»,

«hex»

);

// JID param: 4915905771620@s.whatsapp.net

const info = «3439313539303537373136323040732e77686174736170702e6e6574»;

const salt = new Buffer(

«0000000000000000000000000000000000000000000000000000000000000000»,

«hex»

);

const initialKey = crypto.createHmac(«sha256», salt)

.update(keyMaterial)

.digest();

const temp1 = crypto.createHmac(«sha256», initialKey)

.update(new Buffer(info + «01», «hex»))

.digest();

const temp2 = new Buffer(temp1.toString(«hex») + info + «02», «hex»);

const temp3 = crypto.createHmac(«sha256», initialKey)

.update(temp2)

.digest();



const result = Buffer.concat([temp1, temp3.slice(0, 14)]);

console.log(result.toString(«hex»));



// 4633c47f94d5ed5993a6dba8514d5fb85092ba904256f8d34d56e72e665bcd4c5b6c418bdb811e7f84a70




SRTP .

Frida.

KDF libcommonCrypto .



3 HMAC-SHA256.

KDF RFC 5869.







SRTP, libsrtp, VOIP .

, libsrtp .

.

libsrtp .



libsrtp , .

.

, libsrtp.

, .

libsrtp 12 .

Frida .

Frida.



srtp_aes_icm_context_init libsrtp.

SRTP AES-ICM.

, , .



srtp_aes_icm_context_init, 2 :



debug_print(srtp_mod_aes_icm, «key: %s»,

srtp_octet_string_hex_string(key, base_key_len));

debug_print(srtp_mod_aes_icm, «offset: %s», v128_hex_string(&c→offset));




debug_print .



, .

Hopper Disassembler:



int sub_100bbda00(int arg0, int arg1) {

r31 = r31 — 0x60;

var_30 = r24;

stack[-56] = r23;

var_20 = r22;

stack[-40] = r21;

var_10 = r20;

stack[-24] = r19;

saved_fp = r29;

stack[-8] = r30;

r19 = arg0;

sub_100bbf094(arg0, arg1 + 0x10);

r20 = r19 + 0x10;

sub_100bbf094(r20, arg1 + 0x10);

*(int16_t *)(r19 + 0x1e) = 0x0;

*(int16_t *)(r19 + 0xe) = 0x0;

if (*(int32_t *)dword_1012b5760 != 0x0) {

sub_100bbf048(&var_40);

sub_100bc085c(0x7, "%s: key: %s\n");

if (*(int32_t *)0x1012b5760 != 0x0) {

sub_100bbf048(r20);

sub_100bc085c(0x7, "%s: offset: %s\n");

}

}

sub_100bbbffc(&var_40, r19 + 0x30);

*(int32_t *)(r19 + 0xe0) = 0x0;

return 0x0;

}




19 22 .

, .

iOS Address Space Layout Randomization (ASLR) .



.



srtp_aes_icm_context_init :



const apiResolver = new ApiResolver(«objc»);

const resolvedMatches = apiResolver.enumerateMatches(

"+[NSURL URLWithUnicodeString:]"

);



const SCAN_SIZE = 100000;

const scanStart = resolvedMatches[0].address;

const scanResults = Memory.scanSync(

ptr(scanStart),

SCAN_SIZE,

// first bytes of the hexadecimal representation of srtp_aes_icm_context_init

«FF 83 01 D1 F8 5F 02 A9 F6 57 03 A9»

);



// srtp_err_status_t srtp_aes_icm_context_init(void *cv, const uint8_t *key)

const targetPointer = ptr(scanResults[0].address);

const targetFunction = new NativeFunction(targetPointer, «int», [

«pointer»,

«pointer»

]);



console.log(«scan start: » + scanStart);

console.log(«srtp_aes_icm_context_init: » + scanResults[0].address);



Interceptor.attach(targetFunction, {

onEnter: function(args) {

/*

static srtp_err_status_t srtp_aes_icm_context_init(void *cv, const uint8_t *key)



typedef struct {

v128_t counter; holds the counter value

v128_t offset; initial offset value

v128_t keystream_buffer; buffers bytes of keystream

srtp_aes_expanded_key_t expanded_key; the cipher key

int bytes_in_buffer; number of unused bytes in buffer

int key_size; AES key size + 14 byte SALT

} srtp_aes_icm_ctx_t;



*/

console.log(«srtp_aes_icm_context_init » + args[0] + " key:");

console.log(

hexdump(args[1], {

offset: 0,

length: 16

})

);

},

onLeave: function(args) {}

});





ApiResolver Frida .

ApiResolver .

Frida.

URLWithUnicodeString, 3 .

, linear search .



SCAN_SIZE .

12 12 .

, NativeFunction, 17 Frida ( ).

2 — encryption context (cv) encryption key (key).



srtp_aes_icm_context_init 6 6 SRTP .

key.



AES-ICM.

srtp_aes_icm_alloc, “allocating cipher with key length %d”.



key length , 16 .

AES-128-ICM SRTP .

46 key derivation function, 30 .

16 2 .

16 !







srtp_aes_icm_encrypt, libsrtp .



SRTP AES-128-ICM.

“block index: %d” .



SRTP srtp_aes_icm_encrypt:







12 , , .

SRTP payload.

4 ( ) – authentication tag.

6 , SRTP payload .



Call Integrity



SRTP .



libsrtp srtp_hmac_compute.

authentication tag SRTP .



srtp_hmac_compute Frida,

“intermediate state: %s” .



srtp_hmac_compute :



static srtp_err_status_t srtp_hmac_compute(void *statev,

const uint8_t *message,

int msg_octets,

int tag_len,

uint8_t *result)




srtp_hmac_compute HMAC-SHA1 .

Frida , tag_len SRTP .



tag_len message srtp_hmac_compute :

Attaching…

search srtp_hmac_compute in memory from: 0x1016380ac

found srtp_hmac_compute at: 0x10163b5f4

tag_len: 10

message: 81 ca 00 07 fe 67 2e 32 56 14 89 75 c5 c0 39 4a d3 a0 cd 48 8c 4b 61 8a 78 32 a7 89 1e b7 71 26 80 00 00 01tag_len: 4

message: 00 00 00 00tag_len: 10

message: 81 d0 00 02 fe 67 2e 32 b5 6f 93 8e 80 00 00 02tag_len: 4

message: 00 00 00 00tag_len: 4

message: 00 00 00 00tag_len: 4

message: 00 00 00 00tag_len: 4

message: 00 00 00 00tag_len: 10

message: 81 ca 00 07 83 42 f3 44 81 78 9f f5 39 b1 23 50 48 19 e0 f1 61 5b b5 32 dc b3 10 08 e7 47 a8 4b 80 00 00 01tag_len: 10

message: 81 d0 00 02 83 42 f3 44 94 60 21 fe 80 00 00 02tag_len: 4

message: 00 00 00 00tag_len: 4

message: 00 00 00 00tag_len: 10

message: 81 c8 00 12 fe 67 2e 32 87 b7 69 f8 5a 27 4c 76 b4 29 f6 5d 59 26 de af bd e9 4c 8b f3 ff 48 e3 a9 7e 62 cf db 9c 8a 3d 34 50 48 f8 fc 0e 88 7a 17 eb 17 94 9f 3d 91 27 89 d5 cc bd 21 ea 01 39 27 e1 05 07 66 69 1f 68 08 53 1a 18 02 9e bc 50 ed 8e 40 3e 8a 7b d3 b6 19 e8 54 6f 6b 58 ac 4e e3 25 f5 c2 e8 1c 97 bb 46 f9 38 45 80 00 00 03...




2 :



1. SRTP 4 .

Message SRTP .

4 authentication tag.

, , .

- .



2. 10 .

, VOIP .

SRTP , 10 :



const scanStart = new ApiResolver(«objc»).enumerateMatches(

"+[NSURL URLWithUnicodeString:]"

)[0].address;



console.log(«search srtp_hmac_compute in memory from: » + scanStart);



const size = 100000;

const matches = Memory.scanSync(

ptr(scanStart),

size,

// first bytes of the hexadecimal representation of srtp_hmac_compute

«E0 03 16 AA 4C 00 00 94 D5 02 01 91»

);

const targetPtr = ptr(matches[0].address);

console.log(«found srtp_hmac_compute at: » + matches[0].address);



const targetFunction = new NativeFunction(targetPtr, «int», [

«pointer»,

«pointer»,

«int»,

«int»,

«pointer»

]);



const MANIPULATABLE_TAG_SIZE = 10;

const manipulatedTag = Memory.alloc(MANIPULATABLE_TAG_SIZE);

manipulatedTag.writeByteArray([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]);



Interceptor.attach(ptr(targetFunction), {

onEnter: function(args) {

/*

static srtp_err_status_t srtp_hmac_compute(void *statev,

const uint8_t *message,

int msg_octets,

int tag_len,

uint8_t *result)

*/

console.log(«srtp_hmac_compute tag (» + args[3].toInt32() + "):");

const tag_len = args[3].toInt32();

if (tag_len === MANIPULATABLE_TAG_SIZE) {

console.log(

hexdump(args[1], {

length: args[2].toInt32()

})

);

args[3] = 0;

args[4].writePointer(manipulatedTag);

}

}

});




Frida, VOIP .

, SRTP .

, , .







WhatsApp VoIP …

, .



:



— libsignal-protocol-c, libsrtp, PJSIP mbed TLS VOIP .



— “master secret” 2 SRTP , AES-128-ICM.

key derivation function (HKDF), , nonces SRTP.



— Noise Pipes Protocol, Signal Protocol XMPP .

Signal Protocol, XMPP , Noise Pipes Protocol .

.



— VOIP – SRTP .

, , SRTP .



— SRTP , VOIP .



— .

, .

.



:



github.com/schirrmacher/files/blob/master/WhatsApp%20VoIP%20Protocol.pdf

github.com/schirrmacher/files/blob/master/WhatsApp

github.com/schirrmacher/files/blob/master/WhatsAppCore



– , , .



Frida .



.



.



Moreover, developers should also remove string constants that contain critical information or may be useful for identifying functionality.



All Articles