diff options
author | George Hazan <ghazan@miranda.im> | 2022-09-28 13:56:13 +0300 |
---|---|---|
committer | George Hazan <ghazan@miranda.im> | 2022-09-28 13:56:13 +0300 |
commit | 3e1bb4ac88fe352f955cd639aeb1e3f57163b180 (patch) | |
tree | 589fc168398d7a27c66d8dfab5b862b1540035ff /protocols/WhatsAppWeb | |
parent | 49069bcc23da6ef9790c6efb2d0e5d3c92c6b7fd (diff) |
WhatsApp: fix for packet decoding
Diffstat (limited to 'protocols/WhatsAppWeb')
-rw-r--r-- | protocols/WhatsAppWeb/src/noise.cpp | 70 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/proto.cpp | 9 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/proto.h | 20 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/server.cpp | 126 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/utils.cpp | 25 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/utils.h | 1 |
6 files changed, 145 insertions, 106 deletions
diff --git a/protocols/WhatsAppWeb/src/noise.cpp b/protocols/WhatsAppWeb/src/noise.cpp index 79dc19e266..b85bc8eb7b 100644 --- a/protocols/WhatsAppWeb/src/noise.cpp +++ b/protocols/WhatsAppWeb/src/noise.cpp @@ -30,6 +30,9 @@ WANoise::WANoise(WhatsAppProto *_ppro) : ephemeral.priv.assign(pPrivKey->data, sizeof(pPrivKey->data)); ec_key_pair_destroy(pKeys); + // ephemeral.pub.assign("\xd7\x58\xeb\xcc\x79\xb8\x58\xde\xc7\x60\x5c\x12\x22\xc1\x3b\x7c\xf6\x73\x38\x0b\x89\x56\xf1\xe2\xa1\xb0\xaa\x3a\xba\xbc\x08\x3f", 32); + // ephemeral.priv.assign("\xa0\xef\xd2\xbd\x2d\x4a\x6f\x9c\xd0\x9e\xc5\x75\x3c\x78\x78\xed\xe5\xec\x99\xd7\x4b\xeb\xf8\xb0\xdd\x1e\xe2\xc1\x85\xc4\xd8\x72", 32); + // prepare hash memcpy(hash, noise_init, 32); updateHash(intro_header, 4); @@ -132,6 +135,15 @@ void WANoise::init() preKey.keyid = ppro->getDword(DBKEY_PREKEY_KEYID); } +void WANoise::finish() +{ + deriveKey("", 0, salt, encKey); + decKey.assign(encKey.data(), encKey.length()); + readCounter = writeCounter = 0; + memset(hash, 0, sizeof(hash)); + bInitFinished = true; +} + void WANoise::deriveKey(const void *pData, size_t cbLen, MBinBuffer &write, MBinBuffer &read) { auto *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); @@ -162,65 +174,46 @@ void WANoise::mixIntoKey(const void *n, const void *p) MBinBuffer WANoise::decrypt(const void *pData, size_t cbLen) { - auto &pVar = (bInitFinished) ? readCounter : writeCounter; - uint8_t iv[12]; - memset(iv, 0, 8); - memcpy(iv + 8, &pVar, sizeof(int)); - pVar++; + generateIV(iv, (bInitFinished) ? readCounter : writeCounter); MBinBuffer res; uint8_t outbuf[1024 + EVP_MAX_BLOCK_LENGTH]; - int dec_len = 0, final_len = 0; + int tag_len = 0, dec_len = 0, final_len = 0; EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, (BYTE *)decKey.data(), iv); + + EVP_DecryptUpdate(ctx, nullptr, &tag_len, hash, sizeof(hash)); + cbLen -= 16; + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (BYTE *)pData + cbLen); + for (size_t len = 0; len < cbLen; len += 1024) { size_t portionSize = cbLen - len; EVP_DecryptUpdate(ctx, outbuf, &dec_len, (BYTE *)pData + len, (int)min(portionSize, 1024)); res.append(outbuf, dec_len); } - EVP_DecryptFinal_ex(ctx, outbuf, &final_len); + + if (!EVP_DecryptFinal_ex(ctx, outbuf, &final_len)) + ppro->debugLogA("Decryption failed"); + if (final_len) res.append(outbuf, final_len); EVP_CIPHER_CTX_free(ctx); - updateHash(pData, cbLen); + updateHash(pData, cbLen + 16); return res; } -bool WANoise::decodeFrame(const void *pData, size_t cbLen) +MBinBuffer WANoise::decodeFrame(const void *pData, size_t cbLen) { if (!bInitFinished) { - proto::HandshakeMessage msg; - if (msg.ParseFromArray(pData, (int)cbLen)) { - auto &static_ = msg.serverhello().static_(); - auto &payload_ = msg.serverhello().payload(); - auto &ephemeral_ = msg.serverhello().ephemeral(); - - updateHash(ephemeral_.c_str(), ephemeral_.size()); - mixIntoKey(ephemeral.priv.data(), ephemeral_.c_str()); - - MBinBuffer decryptedStatic = decrypt(static_.c_str(), static_.size()); - mixIntoKey(ephemeral.priv.data(), decryptedStatic.data()); - - MBinBuffer decryptedCert = decrypt(payload_.c_str(), payload_.size()); - - proto::CertChain cert; cert.ParseFromArray(decryptedCert.data(), (int)decryptedCert.length()); - proto::CertChain::NoiseCertificate::Details details; details.ParseFromString(cert.intermediate().details()); - if (details.issuerserial() != 0) { - ppro->ShutdownSession(); - return false; - } - - MBinBuffer encryptedPub = encrypt(noiseKeys.pub.data(), noiseKeys.pub.length()); - mixIntoKey(noiseKeys.priv.data(), ephemeral_.c_str()); - ppro->ProcessHandshake(encryptedPub); - } - return true; + MBinBuffer res; + res.assign(pData, cbLen); + return res; } - return false; + return decrypt(pData, cbLen); } MBinBuffer WANoise::encodeFrame(const void *pData, size_t cbLen) @@ -244,11 +237,8 @@ MBinBuffer WANoise::encodeFrame(const void *pData, size_t cbLen) MBinBuffer WANoise::encrypt(const void *pData, size_t cbLen) { - auto counter = encodeBigEndian(writeCounter); uint8_t iv[12]; - memset(iv, 0, sizeof(iv)); - memcpy(iv + 8, counter.c_str(), sizeof(int)); - writeCounter++; + generateIV(iv, writeCounter); MBinBuffer res; uint8_t outbuf[1024 + 64]; diff --git a/protocols/WhatsAppWeb/src/proto.cpp b/protocols/WhatsAppWeb/src/proto.cpp index 6ca625e2e4..c21b6e24c7 100644 --- a/protocols/WhatsAppWeb/src/proto.cpp +++ b/protocols/WhatsAppWeb/src/proto.cpp @@ -22,11 +22,6 @@ static int CompareOwnMsgs(const WAOwnMessage *p1, const WAOwnMessage *p2) return strcmp(p1->szPrefix, p2->szPrefix); } -static int CompareRequests(const WARequest *p1, const WARequest *p2) -{ - return strcmp(p1->szPrefix, p2->szPrefix); -} - static int CompareUsers(const WAUser *p1, const WAUser *p2) { return strcmp(p1->szId, p2->szId); @@ -38,7 +33,7 @@ WhatsAppProto::WhatsAppProto(const char *proto_name, const wchar_t *username) : m_tszDefaultGroup(getWStringA(DBKEY_DEF_GROUP)), m_arUsers(10, CompareUsers), m_arOwnMsgs(1, CompareOwnMsgs), - m_arPacketQueue(10, CompareRequests), + m_arPacketQueue(10), m_wszDefaultGroup(this, "DefaultGroup", L"WhatsApp"), m_bHideGroupchats(this, "HideChats", true) { @@ -258,7 +253,7 @@ int WhatsAppProto::SendMsg(MCONTACT hContact, int, const char *pszMsg) payLoad.addAttr("type", "relay"); payLoad.content.assign(pBuf, cbBinaryLen); - int pktId = WSSendNode(szMsgId, 0, payLoad, &WhatsAppProto::OnSendMessage); + int pktId = WSSendNode(0, payLoad); mir_cslock lck(m_csOwnMessages); m_arOwnMsgs.insert(new WAOwnMessage(pktId, hContact, szMsgId)); diff --git a/protocols/WhatsAppWeb/src/proto.h b/protocols/WhatsAppWeb/src/proto.h index 7d5263952c..7d02b425bb 100644 --- a/protocols/WhatsAppWeb/src/proto.h +++ b/protocols/WhatsAppWeb/src/proto.h @@ -12,11 +12,15 @@ Copyright © 2019-22 George Hazan #define KEY_BUNDLE_TYPE "\x05" class WhatsAppProto; -typedef void (WhatsAppProto:: *WA_PKT_HANDLER)(const JSONNode &node, void*); +typedef void (WhatsAppProto:: *WA_PKT_HANDLER)(const void *pData, int cbLen); struct WARequest { - CMStringA szPrefix; + WARequest(WA_PKT_HANDLER _1, void *_2 = nullptr) : + pHandler(_1), + pUserInfo(_2) + {} + WA_PKT_HANDLER pHandler; void *pUserInfo; }; @@ -89,13 +93,13 @@ class WANoise public: WANoise(WhatsAppProto *_ppro); - void finish() { bInitFinished = true; } + void finish(); void init(); MBinBuffer decrypt(const void *pData, size_t cbLen); MBinBuffer encrypt(const void *pData, size_t cbLen); - bool decodeFrame(const void *pData, size_t cbLen); + MBinBuffer decodeFrame(const void *pData, size_t cbLen); MBinBuffer encodeFrame(const void *pData, size_t cbLen); }; @@ -164,15 +168,13 @@ class WhatsAppProto : public PROTO<WhatsAppProto> bool WSReadPacket(const WSHeader &hdr, MBinBuffer &buf); int WSSend(const MessageLite &msg, WA_PKT_HANDLER = nullptr, void *pUserIndo = nullptr); - int WSSendNode(const char *pszPrefix, int flags, WANode &node, WA_PKT_HANDLER = nullptr); + int WSSendNode(int flags, WANode &node, WA_PKT_HANDLER = nullptr); void OnLoggedIn(void); void OnLoggedOut(void); bool ServerThreadWorker(void); void ShutdownSession(void); - bool ProcessHandshake(const MBinBuffer &szkeyEnc); - void SendKeepAlive(); /// Request handlers /////////////////////////////////////////////////////////////////// @@ -180,7 +182,9 @@ class WhatsAppProto : public PROTO<WhatsAppProto> void OnGetAvatarInfo(const JSONNode &node, void*); void OnGetChatInfo(const JSONNode &node, void*); void OnSendMessage(const JSONNode &node, void*); - void OnStartSession(const JSONNode &node, void*); + + void OnProcessHandshake(const void *pData, int cbLen); + void OnStartSession(const void *pData, int cbLen); // binary packets void ProcessBinaryPacket(const uint8_t *pData, size_t cbLen); diff --git a/protocols/WhatsAppWeb/src/server.cpp b/protocols/WhatsAppWeb/src/server.cpp index 4633fd74ac..1bd6c70b49 100644 --- a/protocols/WhatsAppWeb/src/server.cpp +++ b/protocols/WhatsAppWeb/src/server.cpp @@ -52,8 +52,73 @@ void WhatsAppProto::SendKeepAlive() ///////////////////////////////////////////////////////////////////////////////////////// -bool WhatsAppProto::ProcessHandshake(const MBinBuffer &keyEnc) +void WhatsAppProto::ShutdownSession() +{ + if (m_bTerminated) + return; + + debugLogA("WhatsAppProto::ShutdownSession"); + + // shutdown all resources + if (m_hServerConn) + Netlib_Shutdown(m_hServerConn); + + OnLoggedOut(); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void WhatsAppProto::OnStartSession(const void *pData, int cbLen) +{ + proto::HandshakeMessage msg; + if (!msg.ParseFromArray(pData, cbLen)) { + debugLogA("Error parsing data, exiting"); + ShutdownSession(); + return; + } + +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void WhatsAppProto::OnProcessHandshake(const void *pData, int cbLen) { + proto::HandshakeMessage msg; + if (!msg.ParseFromArray(pData, cbLen)) { + debugLogA("Error parsing data, exiting"); + +LBL_Error: + ShutdownSession(); + return; + } + + auto &static_ = msg.serverhello().static_(); + auto &payload_ = msg.serverhello().payload(); + auto &ephemeral_ = msg.serverhello().ephemeral(); + + // std::string ephemeral_("\xe5\x3d\xff\xc6\x59\xef\xa7\x64\xf6\x48\xf3\x46\x15\xb4\x4f\x13\xfa\xc6\x29\x18\x34\xd4\xa4\xa9\xad\xa0\xa3\x05\xaa\x4d\xda\x36", 32); + // std::string static_("\xa9\x70\xc0\xfe\xe0\x72\x3b\x0a\x6e\x0a\xd4\xe1\xb4\x5f\xcd\xb8\x06\x68\xdd\x6b\x45\x36\xe0\x7d\x0d\xe9\x00\x4d\xb6\xaf\xfa\xa3\xb7\x54\x82\x24\xa9\xe4\x2c\xd4\xe5\xd0\x2f\xd6\x31\x2b\xca\xec", 48); + // std::string payload_("\x11\xcc\xb8\x74\xe6\x27\x29\x37\x65\xb2\x9e\x47\x53\x89\xf7\xce\x23\x03\x9c\xd4\x9e\x4b\x12\xdc\x3f\x10\xe4\x68\xfe\xfd\x31\x80\x1d\x48\x01\x9c\x31\xef\x54\xdb\xa1\x8f\xdb\x6b\x53\x84\xbb\x6d\xb4\x0c\x61\x1f\xcd\xe7\x3c\x0e\xe2\x18\xe4\x95\xf7\xbc\x5b\xbf\x80\x93\x21\x98\x80\x20\x9b\x71\x27\x47\x39\xe9\x04\x08\x5d\xd2\x62\x48\xf6\x23\xba\xa0\x31\xfc\x7c\xeb\xa0\xaa\x56\x04\x71\x71\x84\x9b\x08\xa4\xc9\x33\xd5\x07\x04\x5f\x1c\xd2\x6f\x7d\x5d\x83\x29\x5f\x80\x4a\xbf\x7c\xd9\x7c\xd0\x2b\x9a\x1e\xe0\x28\x33\x89\xb5\x3b\xd2\xe7\x7f\xfc\xd6\xa8\x55\xe2\x9c\x6e\x5f\x6f\x08\xa1\xf3\xfd\x5e\xff\x56\xee\x6f\x31\x47\xeb\xd4\x07\x92\x81\x72\x68\x91\x9d\xe9\xb3\x5f\x1d\x61\x8e\xce\x55\x4b\xbe\x74\x5d\xef\xea\xe4\x23\x63\x0d\x7c\xd4\xa3\xf8\xa1\x29\x60\xd7\x2c\xe8\xfc\xb5\x89\x99\x32\x95\xfc\xec\x6f\x7b\x2a\x23\xf4\x75\xbe\xe3\x21\x6a\x71\x3f\xc4\x1b\x99\x9f\x42\xd5\x19\xd8\xcc\xe7\xab\x90\xdd\xe5\xd8\xa5\xe3\xb5\x5c\xa2\x54\xf3\x4b\x0c\xa1\xe2\xa2\x91\xa3\xd0\x92\x6d\xfa\xab\x5a\xf6\x80\xee\x84\xbe\xaa\x75\x5e\xee\x6b\x91\x49", 257); + + m_noise->updateHash(ephemeral_.c_str(), ephemeral_.size()); + m_noise->mixIntoKey(m_noise->ephemeral.priv.data(), ephemeral_.c_str()); + + MBinBuffer decryptedStatic = m_noise->decrypt(static_.c_str(), static_.size()); + m_noise->mixIntoKey(m_noise->ephemeral.priv.data(), decryptedStatic.data()); + + MBinBuffer decryptedCert = m_noise->decrypt(payload_.c_str(), payload_.size()); + + proto::CertChain cert; cert.ParseFromArray(decryptedCert.data(), (int)decryptedCert.length()); + proto::CertChain::NoiseCertificate::Details details; details.ParseFromString(cert.intermediate().details()); + if (details.issuerserial() != 0) { + debugLogA("Invalid certificate serial number, exiting"); + goto LBL_Error; + } + + MBinBuffer encryptedPub = m_noise->encrypt(m_noise->noiseKeys.pub.data(), m_noise->noiseKeys.pub.length()); + m_noise->mixIntoKey(m_noise->noiseKeys.priv.data(), ephemeral_.c_str()); + + // create reply proto::ClientPayload node; MFileVersion v; @@ -89,7 +154,7 @@ bool WhatsAppProto::ProcessHandshake(const MBinBuffer &keyEnc) pPairingData->set_eskeyval(m_noise->preKey.pub.data(), m_noise->preKey.pub.length()); pPairingData->set_eskeysig(m_noise->preKey.signature.data(), m_noise->preKey.signature.length()); node.set_allocated_devicepairingdata(pPairingData); - + node.set_passive(false); } // generate login packet @@ -125,50 +190,18 @@ bool WhatsAppProto::ProcessHandshake(const MBinBuffer &keyEnc) MBinBuffer payload(node.ByteSize()); node.SerializeToArray(payload.data(), (int)payload.length()); - + MBinBuffer payloadEnc = m_noise->encrypt(payload.data(), payload.length()); - + auto *pFinish = new proto::HandshakeMessage_ClientFinish(); pFinish->set_payload(payloadEnc.data(), payloadEnc.length()); - pFinish->set_static_(keyEnc.data(), keyEnc.length()); + pFinish->set_static_(encryptedPub.data(), encryptedPub.length()); proto::HandshakeMessage handshake; handshake.set_allocated_clientfinish(pFinish); - WSSend(handshake); - - m_noise->finish(); - return true; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::ShutdownSession() -{ - if (m_bTerminated) - return; - - debugLogA("WhatsAppProto::ShutdownSession"); + WSSend(handshake, &WhatsAppProto::OnStartSession); - // shutdown all resources - if (m_hServerConn) - Netlib_Shutdown(m_hServerConn); - - OnLoggedOut(); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WhatsAppProto::OnStartSession(const JSONNode &root, void*) -{ - int status = root["status"].as_int(); - if (status != 200) { - debugLogA("Session start failed with error %d", status); - ShutdownSession(); - return; - } - - CMStringA ref = root["ref"].as_mstring(); - ShowQrCode(ref); + m_noise->finish(); } ///////////////////////////////////////////////////////////////////////////////////////// @@ -236,7 +269,8 @@ bool WhatsAppProto::ServerThreadWorker() auto &pubKey = m_noise->ephemeral.pub; auto *client = new proto::HandshakeMessage::ClientHello(); client->set_ephemeral(pubKey.data(), pubKey.length()); proto::HandshakeMessage msg; msg.set_allocated_clienthello(client); - WSSend(msg, &WhatsAppProto::OnStartSession); + WSSend(msg, &WhatsAppProto::OnProcessHandshake); + // OnProcessHandshake(0, 0); bool bExit = false; MBinBuffer netbuf; @@ -302,7 +336,6 @@ bool WhatsAppProto::ServerThreadWorker() auto *pReq = m_arPacketQueue.find((WARequest *)&szPrefix); if (pReq != nullptr) { root << CHAR_PARAM("$id$", szPrefix); - (this->*pReq->pHandler)(root, pReq->pUserInfo); } else ProcessPacket(root); } @@ -370,8 +403,15 @@ void WhatsAppProto::ProcessBinaryPacket(const uint8_t *pData, size_t cbDataLen) return; } - if (!m_noise->decodeFrame(pData, payloadLen)) - debugLogA("cannot decrypt incoming message"); + MBinBuffer pkt = m_noise->decodeFrame(pData, payloadLen); + + if (m_arPacketQueue.getCount()) { + WARequest req = m_arPacketQueue[0]; + m_arPacketQueue.remove(0); + + (this->*req.pHandler)(pkt.data(), (int)pkt.length()); + } + else debugLogA("cannot handle incoming message"); pData += payloadLen; cbDataLen -= payloadLen; diff --git a/protocols/WhatsAppWeb/src/utils.cpp b/protocols/WhatsAppWeb/src/utils.cpp index 9f687d0368..a584d2431c 100644 --- a/protocols/WhatsAppWeb/src/utils.cpp +++ b/protocols/WhatsAppWeb/src/utils.cpp @@ -51,13 +51,18 @@ bool WhatsAppProto::getBlob(const char *szSetting, MBinBuffer &buf) ///////////////////////////////////////////////////////////////////////////////////////// // sends a piece of JSON to a server via a websocket, masked -int WhatsAppProto::WSSend(const MessageLite &msg, WA_PKT_HANDLER /*pHandler*/, void * /*pUserInfo*/) +int WhatsAppProto::WSSend(const MessageLite &msg, WA_PKT_HANDLER pHandler, void *pUserInfo) { if (m_hServerConn == nullptr) return -1; // debugLogA("Sending packet: %s", msg..DebugString().c_str()); + if (pHandler != nullptr) { + mir_cslock lck(m_csPacketQueue); + m_arPacketQueue.insert(new WARequest(pHandler, pUserInfo)); + } + int cbLen = msg.ByteSize(); ptrA protoBuf((char *)mir_alloc(cbLen)); msg.SerializeToArray(protoBuf, cbLen); @@ -71,7 +76,7 @@ int WhatsAppProto::WSSend(const MessageLite &msg, WA_PKT_HANDLER /*pHandler*/, v static char zeroData[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; -int WhatsAppProto::WSSendNode(const char *pszPrefix, int flags, WANode &node, WA_PKT_HANDLER pHandler) +int WhatsAppProto::WSSendNode(int flags, WANode &node, WA_PKT_HANDLER pHandler) { if (m_hServerConn == nullptr) return 0; @@ -100,18 +105,13 @@ int WhatsAppProto::WSSendNode(const char *pszPrefix, int flags, WANode &node, WA int pktId = ++m_iPktNumber; if (pHandler != nullptr) { - auto *pReq = new WARequest; - pReq->pHandler = pHandler; - pReq->szPrefix = pszPrefix; - mir_cslock lck(m_csPacketQueue); - m_arPacketQueue.insert(pReq); + m_arPacketQueue.insert(new WARequest(pHandler)); } char postPrefix[3] = {',', 0, (char)flags}; MBinBuffer ret; - ret.append(pszPrefix, strlen(pszPrefix)); ret.append(postPrefix, sizeof(postPrefix)); WebSocket_SendBinary(m_hServerConn, ret.data(), ret.length()); @@ -639,3 +639,12 @@ std::string encodeBigEndian(uint32_t num, size_t len) } return res; } + +void generateIV(uint8_t *iv, int &pVar) +{ + auto counter = encodeBigEndian(pVar); + memset(iv, 0, sizeof(iv)); + memcpy(iv + 8, counter.c_str(), sizeof(int)); + + pVar++; +} diff --git a/protocols/WhatsAppWeb/src/utils.h b/protocols/WhatsAppWeb/src/utils.h index aa8f220292..c05dbd5410 100644 --- a/protocols/WhatsAppWeb/src/utils.h +++ b/protocols/WhatsAppWeb/src/utils.h @@ -103,3 +103,4 @@ public: ///////////////////////////////////////////////////////////////////////////////////////// std::string encodeBigEndian(uint32_t num, size_t len = sizeof(uint32_t)); +void generateIV(uint8_t *iv, int &pVar); |