Hackeando o WhatsApp, parte 2 - analisando o protocolo VOIP do Whatsapp





Neste artigo, quero contar como quebrei várias partes do protocolo WhatsApp VoIP usando um dispositivo iOS desbloqueado e um conjunto de diferentes programas de análise.



Recentemente, o Whatsapp tem recebido muita atenção devido às vulnerabilidades e oportunidades para hackers.

Desse ponto de vista, é muito interessante pesquisar sua segurança.



Todos que também se interessarem por isso, sejam bem-vindos por baixo do gato.





Embora as páginas oficiais do Whatsapp tenham uma descrição de sua criptografia, na verdade, não há informações detalhadas sobre como funciona e como é implementado no protocolo.

Portanto, não há base para análises de segurança detalhadas no próprio Whatsapp.



Minha pesquisa foi baseada em três coisas:



1. Análise de tráfego de rede

2. Análise de binários

3. Análise de comportamento de aplicativo em diferentes modos



Toolkit



Para analisar o cliente Wahtsapp para iOS, usei as seguintes ferramentas:



- Descritor binário - bfdecrypt

- Desmontador de arquivo binário - Desmontador Hopper e Radare2

- Análise de tráfego de rede - Wireshark

- Análise de ação de aplicativo - Frida



A maneira como configuro o jailbreak no iOS está além do escopo deste artigo.



Analisando o tráfego da rede



Nesta parte, analisaremos o tráfego da rede do cliente Whatsapp durante uma chamada, que registraremos usando o Wireshark.



Para registrar esse tráfego, criei uma interface de rede virtual remota.



O comando para Makos é semelhante a este:



rvictl -s Aqui, o UUID do dispositivo precisa ser substituído pelo UUID do dispositivo com o cliente watsap.



O Wireshark detecta o uso de Session Traversal Utilities for NAT (STUN).

STUN é um protocolo de sinalização necessário para estabelecer conexões ponto a ponto entre clientes.







Aqui, o cliente WhatsApp usa pacotes TCP para se comunicar com diferentes servidores Watzap.

Ao mesmo tempo, os pacotes UDP são usados ​​para troca entre clientes.

Centenas de pacotes UDP passam em um minuto.

Vatsap usa Secure Real Time Protocol (SRTP) e é óbvio que esses pacotes UDP contêm dados SRTP sobre a chamada.



O protocolo SRTP fornece criptografia, autenticação e proteção contra ataques de repetição no tráfego RTP.



Vamos dar uma olhada mais de perto nos pacotes SRTP trocados entre os lados A e B.

Para isso, converta-os em hexadecimal:







Pode-se observar que os campos contêm cabeçalhos RTP específicos para SRTP.



Os primeiros quatro bytes (destacados em vermelho) são os 7 campos de cabeçalho RTP.



Vamos considerá-los em mais detalhes:



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







Os primeiros 2 bits contêm o número da versão (V), no nosso caso é a segunda versão.

O terceiro bit é um campo de informação opcional, no nosso caso está vazio.

O quarto bit - o campo de extensão (X) indica que, neste caso, não há outros cabeçalhos após o cabeçalho do pacote RTP.



Bits 5 a 8 - contém o número de identificadores CSRC seguindo o cabeçalho permanente.

CSRC (fonte de contribuição) é a fonte do fluxo de pacotes RTP que contribui para o fluxo total gerado pelo misturador RTP. O mixer insere uma lista de identificadores SSRC que identificam fontes parciais no cabeçalho dos pacotes RTP. Essa lista é chamada de lista CSRC. Por exemplo - uma conferência de áudio em que o mixer marca todos os alto-falantes cuja voz gera pacotes de saída. Isso permite que o lado receptor identifique o alto-falante, embora todos os pacotes tenham o mesmo ID SSRC.



8 bits é um marcador de bit (M). Usado no nível do aplicativo e determinado pelo perfil. Se este campo for definido, os dados do pacote terão algum significado especial para o aplicativo.



Os próximos 6 bits são códigos de tipo de dados adicionais. Esses dados não são definidos nos padrões RTP e SRTP. O significado desses bits é provavelmente um personalizado escolhido pelo Whatsapp.



Os últimos 17 bits indicam a fonte do relógio. O número é incrementado em ordem de 1 quando o próximo pacote de dados RTP é enviado, este código pode ser usado pelo receptor para registrar perdas de pacotes e para restaurar a verdadeira ordem dos fragmentos enviados. Pelo padrão, o valor inicial do código é aleatório, mas esta recomendação não é cumprida pelo watsap, porque como podemos ver nos dados do Wireshark, o valor inicial do watsap é sempre 0.



Os próximos 4 bytes (destacados em azul) são o carimbo de data / hora do pacote.



4 bytes depois (verde) - campo SSRC. Ele identifica a fonte de sincronização. Este identificador é escolhido aleatoriamente para que não haja dois códigos SSRC iguais em uma sessão RTP. Todos os aplicativos devem ser capazes de detectar quando os SSRCs são iguais. Se o remetente alterar seu endereço de transporte, ele também deverá alterar o identificador SSRC.



Então, descobrimos que o Whatsapp usa o protocolo SRTP para proteger as chamadas.

Isso é confirmado pela estrutura dos pacotes UDP trocados entre clientes Watcap.



Além disso, o Watzap usa o protocolo TCP para trocar dados entre o cliente e o servidor.

A seguir, veremos como o protocolo Noise Pipes é usado para criptografar esta parte. Análise binária







O cliente WhatsApp para iOS contém 2 binários principais - o binário do aplicativo WhatsApp e a estrutura central do WhatsApp.



Nesta parte, vamos dar uma olhada neles com o Hopper Disassembler e o radare2.



Esses binários são criptografados quando baixados da Appstore.

Aqui, enganamos a Apple para fazer o jailbreak de um dispositivo iOS e obter acesso a esses arquivos.



Adicione também que esses binários do Whatsapp foram descriptografados com bfdecrypt.



A seguir, mostrarei como reuni informações sobre os fundamentos do protocolo, algoritmos e bibliotecas de código aberto que o Whatsapp usa.



Bibliotecas de código aberto são especialmente interessantes porque podem ser facilmente analisadas.



libsignal-protocol-c



O Watsap usa libsignal-protocol-c - uma biblioteca de código aberto - que ele implementou no Signal Protocol.



O protocolo é baseado no Double Ratchet Algorithm, que criptografa mensagens watsap.



Esta biblioteca foi encontrada nos binários do Whatsapp para os seguintes recursos salientes:



r2 WhatsAppCore

[0x0082b517]> / _signal_

Pesquisando 8 bytes em [0x0-0x654000] ocorrências

: 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 O



Watsap também usa libsrtp para implementar seu protocolo de tempo real seguro.

Os nomes dos símbolos foram removidos dos binários do Whatsapp, mas, apesar disso, os binários contêm linhas que indicam diretamente seu link para libsrtp:



r2 WhatsApp

[0x1001ada34]> / libsrtp

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

0x100ee57eb hit1_1 .d para inicializar libsrtp:% sFailed para r.

0x100ee580a hit1_2 .led para registrar libsrtp deinit.Failed.

0x100ee5831 hit1_3 .para desinicializar libsrtp:% sAES_CM_128_.

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

0x100f07b80 hit1_5. pacote:% slibsrtpstat teste% s: c.





Além disso, os binários watsap contêm strings que são usadas no código libsrtp bruto, por exemplo “fluxo de clonagem (SSRC: 0x% 08x)”:



r2 WhatsApp

[0x1013ddb4f]> / fluxo de clonagem

Pesquisando 14 bytes em [0x100000000-0x100fb4000] ocorrências

: 1

0x100f07823 hit7_0 .sent! Srtp% s: fluxo de clonagem (SSRC: 0x% 08x).




PJSIP



Além disso, o watsap usa o PJSIP, que implementa comunicações multimídia, sinalização e codificação de dados de áudio e vídeo.



O STUN também é implementado lá, o que é visto claramente ao analisar com o Wireshark.



A biblioteca foi identificada pelo PJSIP por strings nos binários watcap devido às informações de depuração em PJSIP:



r2 WhatsApp

[0x1013ddb4f]> / pjmedia

Pesquisando 7 bytes em [0x100000000-0x100fb4000]

acessos: 180

0x100edd55f hit9_0.

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 para% dpjmedia_format_get_vide.

0x100ee1446 hit9_13 .c_buf muito curtopjmedia_h26x_packetize.




mbed TLS



Também o watcap usa o mbed TLS de código aberto para implementar seu protocolo TLS.

Esta biblioteca no código Watzap foi identificada pelos seguintes nomes de função:



r2 WhatsAppCore

[0x0082b517]> / mbedtls

Pesquisando 7 bytes em [0x814000-0x934000] ocorrências

: 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 também usa o protocolo aberto de mensagens e presença (XMPP) para trocar mensagens assíncronas entre clientes.



Isso foi descoberto pelos nomes de classe no código watsap usado em XMPP:



r2 WhatsApp

[0x1013ddb4f]> / XMPP

Pesquisando 4 bytes em [0x1013ac000-0x1014b4000]

acessos: 150

Pesquisando 4 bytes em [0x100fb4000-0x1013ac000]

acessos: 150

Pesquisando 4 bytes em [0x100000000-0x100fb4000] ocorrências

: 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



De acordo com relatórios oficiais, Watzap usa o Noise Protocol Framework para se comunicar com segurança entre clientes e servidores.

O Noise Protocol Framework foi projetado para criar protocolos criptográficos fáceis de usar usando um conjunto de blocos discretos.

Mas, estritamente falando, o Watzap usa apenas o Noise Pipes Protocol, que foi retirado do Noise Protocol Framework mais completo.



Estas linhas foram encontradas nos binários Watzap:



“Noise_XX_25519_AESGCM_SHA256”,

• “Noise_IK_25519_AESGCM_SHA256”,

• “Noise_XXfallback_25519_AESGCM_SHA256”.



Essas linhas contêm os padrões de handshake implementados em clientes watsap.



A primeira linha pertence à classe WANoiseFullHandshake.

O segundo é para WANoiseResumeHandshake e o último para WANoiseFallbackHandshak.



Não consideraremos em detalhes como esse protocolo funciona na estrutura deste artigo.



Análise de tempo de execução



Nesta parte, exploraremos o comportamento do cliente watsap usando Frida.



Frida é o chamado Dinamic Instrumentation Toolkit, que é um conjunto de ferramentas que permite injetar seu próprio código em outros aplicativos na hora.

Vamos nos conectar a um processo no aplicativo e mudar seu comportamento usando um console JS interativo.



Transporte de chaves



Nesta parte, exploraremos os principais mecanismos de trabalho do protocolo watcap.

De acordo com a descrição oficial do Whatsapp que descreve a criptografia de uma chamada VOIP - o iniciador da chamada gera um segredo mestre SRTP aleatório de 32 bytes.

Em seguida, a mensagem criptografada é transmitida para o lado B com o conteúdo desse segredo mestre SRTP.

Esta informação é então usada para a reconstrução do lado B.



Primeiro, fiz um traçado usando a palavra “segredo”:



frida-trace -U WhatsApp -m "* [* * segredo *]" -m "* [* * * segredo]"

Após o início da chamada chamada vatsap método deriveSecretsFromInputKeyMaterial usando a classe WAHKDF:



+ [WAHKDF

deriveSecretsFromInputKeyMaterial: 0x121e08a20

sal: 0x0

a informação : 0x121e07840

outputLength: 0x2e

withMessageVersion: 0x3

]




Os valores de entrada 0x121e08a20 e 0x121e07840 apontam para objetos Objective-C.

Frida permite que você crie objetos Objective-C proxy para JavaScript.



O gancho deriveSecretsFromInputKeyMaterial é usado para imprimir a descrição dos objetos:



{

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]");

}

}





Depois que o script:



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

sal: nil

informações: <34393135 39303537 37313632 3040732e 77686174 73617070 2e6e6574>

Bytes: 46

withMessageVersion: 3

]




Os primeiro e terceiro parâmetros como objetos NSData ,que contém um buffer de byte estático.

O primeiro parâmetro tem 32 bytes, conforme descrito no artigo do WhatsApp.



O terceiro parâmetro é uma string ASCII contendo o JID do chamador.

Mais tarde veremos que, de fato, é a primeira linha que contém o segredo mestre.



Criptografando o segredo mestre



De acordo com o white paper do WhatsApp, o segredo mestre é uma parte necessária para proteger uma sessão de chamada.

Portanto, deve ser entregue com segurança ao lado B.

Para investigar como essa entrega ocorre, fiz um rastreamento contendo palavras-chave relevantes para o processo de criptografia:



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



Após iniciar a chamada, chame a função signal_encrypt da biblioteca libsignal-protocol-c.

Cabeçalho Signal_encrypt:



texto simples legível com o gancho de Frida:







os primeiros 4 bytes são usados ​​para serializar o segredo mestre usando os buffers de protocolo do Google.

(Serialização é o processo de traduzir alguma estrutura de dados em uma sequência de bits.)

Os próximos bytes são o próprio segredo mestre.

Os últimos 13 bytes são preenchimento de criptografia.

O texto simples é criptografado com AES-256 no modo CBC.

As chaves de criptografia são obtidas por meio do Algoritmo Double Ratchet, que faz parte do Protocolo de Sinal.

Libsignal-protocol-c e Signal Protocol não são abordados neste artigo. Resultado



Signal_encrypt:







O resultado contém mais bytes porque uma tag de autenticação foi adicionada à mensagem que usa HMAC-SHA256.



Cobrimos a primeira parte do protocolo WhatsApp VoIP.



Para resumir, o segredo mestre é serializado e criptografado usando uma chave AES de 256 bits no modo CBC.

A chave de criptografia e a chave de autenticação foram obtidas usando a biblioteca de código aberto libsignal-protocol-c.



Analisar o segredo mestre



Vamos ver como o segredo mestre é criptografado.



Fazemos um rastreamento com a palavra-chave de sinal:



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

Frida mostra que a função textecure__signal_message__pack está envolvida na criptografia do segredo mestre.



A função cria uma mensagem de sinal contendo o segredo mestre criptografado e outros parâmetros necessários: Os







bytes destacados em verde são usados ​​para serialização.

Bytes azuis - chave catraca do remetente (usada para criptografia ponta a ponta).

O contador de mensagens é bytes laranja.

Finalmente, os bytes do segredo mestre são destacados em verde.



Ao rastrear o XMPP, podemos ver que o método writeNoiseFrameToSocketWithPayload da classe XMPPStream é chamado.

Este método envia mensagens XMPP criptografadas pelo protocolo Noise Pipes usando o protocolo TCP para o servidor watchap.



Aqui eu abri o conteúdo em carga útil:







Esta é uma mensagem XMPP binária contendo a mensagem de sinalização criada acima.

Para desmontagem, criamos um rastreamento da classe XMPPBinaryCoder.



Esta classe possui um método de serialização que cria uma representação binária de strings XMPP.

Ao exibir esses parâmetros, você pode ver vários pares de chaves anexados à mensagem XMPP:



- [XMPPBinaryCoder serialize:

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

id =' 1555415586-10 '

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

[oferta call-id ='

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

[taxa de áudio =' 8000 'enc =' opus ']

[taxa de áudio =' 16000 'enc =' opus ']

[net medium =' 3 ']

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

[encoptar keygen = '2']

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

]

]

] compactado: 0x0]




Consegui fazer uma notificação falsa sobre uma chamada perdida de A para B, embora a chamada tenha sido iniciada por Mallory ...

Isso se tornou possível depois de reescrever o criador da chamada e de parâmetros no JID no lado A.

Embora o nome de Mallory seja mostrado na notificação.

Quando a Parte B começa a responder a tal mensagem, a Parte A é chamada em vez de Mallory.

Esse comportamento será mais interessante para analisar posteriormente.







Vamos resumir os resultados intermediários - no vatsap, o segredo mestre criptografado é compactado em uma mensagem de sinal, que é adicionada às strings XMPP.

As strings XMPP também contêm o ID e o JID de ambos os lados.



Transferindo o segredo principal para a outra parte



De acordo com a descrição oficial dos clientes Watsup, use o protocolo Noise Pipes com Curve25519, AESGCM e SHA256 do Noise Protocol Framework.



Se você usar rastreio contendo palavras-chave relacionadas ao Noise Protocol Framework, poderá ver que a classe WANoiseStreamCipher é usada para criptografar chamadas para servidores Vatsap.

A classe usa o método encryptPlaintext.

Após o início da chamada, o valor do texto simples é a mensagem XMPP descrita acima.

A mensagem é criptografada novamente usando a biblioteca mbed TLS mbedtls_gcm_crypt_and_tag.

mbedtls_gcm_setkey tem 256 bits de tamanho, o que significa que AES-256-GCM é usado.

A chave de criptografia é usada a partir do protocolo Noise Pipes, que não é abordado neste artigo.

O texto simples criptografado então passa pelo TCP para o servidor watsap (isso pode ser visto com o Wireshark).

O servidor irá então encaminhar esta mensagem para a parte chamada para iniciar a chamada.







Modelagem de chave



Nesta parte, veremos como funciona a função de formação de chave (KDF) /

Os resultados foram obtidos usando Frida durante o rastreamento da classe WAHKDF e da biblioteca libcommonCrypto.

A classe WAHKDF foi usada para extrair chaves, códigos salt e one-time ao inicializar streams SRTP.

O método deriveSecretsFromInputKeyMaterial é chamado 10 vezes antes do início da chamada:



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



.



.



Além disso, os desenvolvedores também devem remover constantes de string que contêm informações críticas ou podem ser úteis para identificar a funcionalidade.



All Articles