任何一个软件都不可能无懈可击,只要你用心去研究。
WhatsApp也一样存在漏洞,whatsapp中的漏洞,可以利用其来发送私聊和群聊,还可以伪造似乎来自可信源的假消息。
抓包分析WhatsApp后,发现WhatsApp使用protobuf2 protocol协议来进行加解密。
将protobuf2数据转变成JSON格式就可以看到了真实的参数,这些参数可以发送和修改参数来检查WhatsApp的安全。
请阅读下面的完整技术分析。
访问密钥
可以在生成 QR 码之前从 WhatsApp Web 的密钥生成阶段获取密钥:
获取这些密钥后,我们需要获取用户扫描二维码时由手机发送到 WhatsApp Web 的“秘密”参数:
让我们从 WhatsApp Web 开始。在生成二维码之前,WhatsApp Web 会生成用于加密和解密的公钥和私钥。
让我们称我们的私钥“ priv_key_list”和我们的公钥“ pub_key_list ” 。
这些密钥是通过使用 curve25519_donna 使用随机 32 字节创建的。
为了解密数据,我们开始创建解密代码。这将从 WhatsApp Web 获取私钥而不是随机字节,因为我们需要具有相同的密钥才能解密数据:
self.conn_data[ “private_key” ] = curve25519.Private( “”. join( [chr(x) for x in priv_key_list]))
self.conn_data[ “public_key” ] = self.conn_data[ “private_key” ].get_public( )
断言(self.conn_data[ “public_key” ].serialize() == “”. join( [chr(x) for x in pub_key_list]))
然后,创建二维码后,用手机扫描后,我们可以通过 websocket 将以下信息发送到 Whatsapp Web:
这里最重要的参数是secret,然后传递给setSharedSecret。这会将secret分成多个部分,并配置我们需要的所有加密功能,以解密 WhatsApp 流量。
首先,我们可以看到从字符串 ‘e’ 到 Array 的转换以及一些将秘密分成两部分的切片:‘n’,它是前 32 个字节和 ‘a’,它是第 64 个字节的字符到 ‘t’ 的结尾。
如果我们深入研究函数’’ E.SharedSecret’,我们可以看到它使用了两个参数,即前 32 个字节和来自 QR 生成的私钥:
在此之后,我们可以更新我们的 python 代码并添加以下行:
self.conn_data[ “shared_secret” ] = self.conn_data[ “private_key” ].get_shared_key(curve25519.Public(self.conn_data[ “secret” ][:32]), lambda key: key)
通过深入我们可以看到该函数使用了 HKDF 函数。所以我们找到了函数 ‘pyhkdf’ 并在我们的代码中使用它以与 WhatsApp 相同的方式扩展密钥:
shared_expended = self.conn_data[ “shared_secret_ex” ] = HKDF (self.conn_data[ “shared_secret” ], 80)
我们接下来有 hmac 验证函数,它将扩展数据作为参数 ‘e’ 并将其分为 3 个参数:
i – shared_expended 的前 32 个字节
r - 从 32 字节开始的 32 字节
o – 64 字节中的 16 字节
还有参数“s”,它是参数“n”和“a”的串联,来自之前的函数,它构成了我们的秘密的一部分。
然后函数 HmacSha256 将使用参数 ‘r’ 调用,它将使用参数 ‘s’ 对数据进行签名,然后我们将收到 hmac 验证器,该验证器将与切片的 ‘r’ 进行比较’t’ 从 32 字节到 64 字节,而 ‘t’ 是我们在数组格式中的秘密,如前所见。
在 python 中,它看起来像这样:
check_hmac = HmacSha256(shared_expended[32:64], self.conn_data[ “secret” ][:32] + self.conn_data[ “secret” ][64:]) if check_hmac != self.conn_data[ “secret” ][ 32:64]:引发ValueError(“错误 hmac 不匹配”)
该块中最后一个与加密相关的函数是“aesCbcDecrypt”,它使用参数“s”,该参数是从字节 64 到 expended shared 结尾的数据和来自秘密的字节 64 的数据之间的连接,以及“i”是前 32bytes 的 expended shared。
结果是我们稍后将使用的解密密钥。因此,如果我们翻译代码,它将如下所示:
keysDecrypted = AESDecrypt(shared_expended[:32], shared_expended[64:] + self.conn_data[ “secret” ][64:]) 解密后,我们将得到新的 ‘t’,即前 32 个字节,即加密密钥,以及接下来的 32 个字节,即 mac 密钥:
self.conn_data[ “key” ][ “aes_key” ] = keysDecrypted[:32]
self.conn_data[ “key” ][ “mac_key” ] = keysDecrypted[32:64]
整个代码将如下所示:
self.conn_data[ “private_key” ] = curve25519.Private( “”. join( [chr(x) for x in priv_key_list]))
self.conn_data[ “public_key” ] = self.conn_data[ “private_key” ].get_public( )
断言(self.conn_data[ “public_key” ].serialize() == “”. join( [chr(x) for x in pub_key_list]))
self.conn_data[ “secret” ] = base64.b64decode(ref_dict[ “secret” ])
self.conn_data[ “shared_secret” ] = self.conn_data[ “private_key” ].get_shared_key(curve25519.Public(self.conn_data[ “secret”) ” ][:32]), lambda键: 键)
shared_expended = self.conn_data[ “shared_secret_ex” ] = HKDF (self.conn_data[ “shared_secret” ], 80)
check_hmac = HmacSha256(shared_expended[32:64], self.conn_data[ “secret” ][:32] + self.conn_data[ “secret” ][64:])
if check_hmac != self.conn_data[ “secret” ][32:64]:
raise ValueError( “Error hmac mismatch” )
keysDecrypted = AESDecrypt(shared_expended[:32], shared_expended[64:] + self.conn_data[ “secret” ][64:])
self.conn_data[ “key” ][ “aes_key” ] = keysDecrypted[:32]
self.conn_data[ “key” ][ “mac_key” ] = keysDecrypted[32:64]
因此,在我们拥有可以重新生成所需的所有加密参数的代码后,我们可以继续解密过程。
消息分为两部分:标签和数据。我们将使用以下函数来解密消息:
def
解密传入消息(self, message): message = base64.b64decode(message)
message_parts = message.split( “,” , 1)
self.message_tag = message_parts[0]
content = message_parts[1]
check_hmac = hmac_sha256(self.conn_data[ “mac_key” ], content[32:])
if check_hmac != content[:32]:
raise ValueError( “Error hmac mismatch” )
self.decrypted_content = AESDecrypt(self.conn_data[ “aes_key” ], content[32:])
self.decrypted_seralized_content = whastsapp_read(self.decrypted_content, True)
返回self.decrypted_seralized_content
如您所见,我们接收 base64 格式的数据以便轻松复制 Unicode 数据,在 Burp 中,我们可以通过简单地按ctrl+b将数据编码为 base64,并将其传递给函数decrypt_incomping_message。该函数从内容中分离标签,并通过比较hmac_sha256 (self.conn_data[“ mac_key ”], content[32:]) 和 content[:32] 来检查我们的密钥是否可以解密数据。
如果一切正常,我们可以继续使用我们的 aes 密钥和 32 字节内容的 AES 解密步骤。
此内容首先包含 IV ,其大小为 aes 块大小,然后是实际数据:
self.decrypted_content = AESDecrypt(self.conn_data[ “aes_key” ], content[32:])
此函数的输出将是一个 protobuf,如下所示:
为了将其转换为 json,我们将使用“ whatsapp_read ”函数。
WhatsApp 加密解释(解密传入消息):
为了解密消息,我们首先必须了解 WhatsApp 协议的工作原理,因此我们从调试函数e.decrypt 开始:
我们将所有代码翻译成 python 来表示相同的函数,如下所示:
此代码首先从流中读取一个字节并将其移动到char_data。 然后它尝试使用函数read_list_size读取传入流的列表大小。
然后我们得到另一个字节,我们将其称为token_byte,它将传递给read_string,如下所示:
此代码使用 getToken并将我们的参数作为令牌数组中的位置传递:
这是whatsapp在通信中发送的第一项,然后我们翻译了函数readString中的所有函数并继续调试:
接下来您可以在函数 readNode 中看到函数“readAttributes”:
这个函数只是继续从流中读取更多字节,并通过我们之前在解析“action”标记时看到的相同标记列表来解析它们,它看起来像这样:
因此,WhatsApp 发送的第二个参数是对信使的实际操作,我们可以看到 WhatsApp 发送了{add:”replay”},这意味着新消息到达。
基本上,我们将继续执行代码,直到到达 readNode 的末尾,这将为我们提供已发送消息的三个部分:
接下来我们必须处理第三个参数,即 protobuf 并对其进行解密。
要获取 protobuf,我们可以查看 Whatsapp 实现的 protobuf 方案,然后将其复制到一个干净的 .proto 文件中,该文件可以从这里获得:
索引也可以从 Whatsapp protobuf 模式复制并使用命令编译为 python protobuf 文件,然后我们可以使用protobuf生成的python函数轻松地将protobuf转换为json……
…结果将如下所示:
WhatsApp 加密解释(加密传入消息)
加密过程与加密几乎相同,但顺序相反,反转writeNode函数,设定标记和标记属性,我们将它们转换为它在标记列表中的位置,然后以与在readNode 中所做的相同的方式重新实现所有函数,代码非常简单;首先我们检查我们得到的节点的长度是否为三。然后我们将标记属性的数量乘以 2 并将其传递给writeListStart,它将写入列表字符的开头和列表大小(与我们在readNode 中看到的相同),有了开始列表后,我们将进入writeString,它执行与readString相同的事情,因为您可以看到“action”转换为十,即标记索引中的“action”位置,依此类推。
通过这种方式,我们重新构建数据,因此我们解密和加密消息的代码将如下所示:
为了简化加密过程,还可以更改实际的writeChildren函数并添加了另一个实例类型以使加密更简单:
结果就是传入数据的加密和解密。