diff options
author | George Hazan <ghazan@miranda.im> | 2022-09-17 13:45:05 +0300 |
---|---|---|
committer | George Hazan <ghazan@miranda.im> | 2022-09-17 13:45:05 +0300 |
commit | 0c63ec79606a42d944aeed0fd52596c55d6a0768 (patch) | |
tree | c5c05944aca61028db0ad044f435b21452eae379 /protocols/WhatsAppWeb/src | |
parent | dccf65f5a9a66f72a9765b6d26b1ddee2374d61d (diff) |
WhatsApp: cleaning old garbage (macKey, encKey)
Diffstat (limited to 'protocols/WhatsAppWeb/src')
-rw-r--r-- | protocols/WhatsAppWeb/src/db.h | 3 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/proto.h | 21 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/server.cpp | 173 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/utils.cpp | 127 |
4 files changed, 116 insertions, 208 deletions
diff --git a/protocols/WhatsAppWeb/src/db.h b/protocols/WhatsAppWeb/src/db.h index 88c1808ac4..d2153b3305 100644 --- a/protocols/WhatsAppWeb/src/db.h +++ b/protocols/WhatsAppWeb/src/db.h @@ -20,9 +20,6 @@ Copyright © 2019-22 George Hazan #define DBKEY_PUB_KEY "PublicKey" #define DBKEY_PRIVATE_KEY "PrivateKey" -#define DBKEY_MAC_KEY "MacKey" -#define DBKEY_ENC_KEY "EncKey" - #define DBKEY_NICK "Nick" #define DBKEY_DEF_GROUP "DefaultGroup" #define DBKEY_AUTORUNCHATS "AutoRunChats" diff --git a/protocols/WhatsAppWeb/src/proto.h b/protocols/WhatsAppWeb/src/proto.h index 9406c414e9..e633e21ebf 100644 --- a/protocols/WhatsAppWeb/src/proto.h +++ b/protocols/WhatsAppWeb/src/proto.h @@ -8,6 +8,8 @@ Copyright © 2019-22 George Hazan #if !defined(PROTO_H) #define PROTO_H +#define APP_VERSION "2.2230.15" + class WhatsAppProto; typedef void (WhatsAppProto:: *WA_PKT_HANDLER)(const JSONNode &node, void*); @@ -62,30 +64,34 @@ struct WAOwnMessage class WANoise { + WhatsAppProto *ppro; int readCounter = 0, writeCounter = 0; bool bInitFinished = false, bSendIntro = false; MBinBuffer pubKey, privKey, salt, encKey, decKey; uint8_t hash[32]; - void decrypt(const void *pData, size_t cbLen, MBinBuffer &dest); - void encrypt(const void *pData, size_t cbLen, MBinBuffer &dest); void deriveKey(const void *pData, size_t cbLen, MBinBuffer &write, MBinBuffer &read); - void mixIntoKey(const void *pData, size_t cbLen); + void mixIntoKey(const void *n, const void *p); void updateHash(const void *pData, size_t cbLen); public: - WANoise(); + WANoise(WhatsAppProto *_ppro); void finish() { bInitFinished = true; } const MBinBuffer& getPub() const { return pubKey; } + void decrypt(const void *pData, size_t cbLen, MBinBuffer &dest); + void encrypt(const void *pData, size_t cbLen, MBinBuffer &dest); + bool decodeFrame(const void *pData, size_t cbLen); void encodeFrame(const void *pData, size_t cbLen, MBinBuffer &dest); }; class WhatsAppProto : public PROTO<WhatsAppProto> { + friend class WANoise; + class CWhatsAppProtoImpl { friend class WhatsAppProto; @@ -112,8 +118,6 @@ class WhatsAppProto : public PROTO<WhatsAppProto> CMStringW m_tszAvatarFolder; EVP_PKEY *m_pKeys; // private & public keys - MBinBuffer mac_key, enc_key; - WANoise *m_noise; bool getBlob(const char *pSetting, MBinBuffer &buf); @@ -156,10 +160,7 @@ class WhatsAppProto : public PROTO<WhatsAppProto> bool ServerThreadWorker(void); void ShutdownSession(void); - bool ProcessChallenge(const CMStringA &szChallenge); - bool ProcessSecret(const CMStringA &szSecret); - - bool decryptBinaryMessage(size_t cbSize, const void *buf, MBinBuffer &res); + bool ProcessHandshake(const MBinBuffer &szkeyEnc); void SendKeepAlive(); diff --git a/protocols/WhatsAppWeb/src/server.cpp b/protocols/WhatsAppWeb/src/server.cpp index ea34e3e372..515aa86458 100644 --- a/protocols/WhatsAppWeb/src/server.cpp +++ b/protocols/WhatsAppWeb/src/server.cpp @@ -52,99 +52,71 @@ void WhatsAppProto::SendKeepAlive() ///////////////////////////////////////////////////////////////////////////////////////// -bool WhatsAppProto::ProcessChallenge(const CMStringA &szChallenge) +bool WhatsAppProto::ProcessHandshake(const MBinBuffer &keyEnc) { - if (szChallenge.IsEmpty() || mac_key.isEmpty()) - return false; - - size_t cbLen; - void *pChallenge = mir_base64_decode(szChallenge, &cbLen); - - BYTE digest[32]; - unsigned cbResult = sizeof(digest); - HMAC(EVP_sha256(), mac_key.data(), (int)mac_key.length(), (BYTE*)pChallenge, (int)cbLen, digest, &cbResult); - - ptrA szServer(getStringA(DBKEY_SERVER_TOKEN)); - //CMStringA payload(FORMAT, "[\"admin\",\"challenge\",\"%s\",\"%s\",\"%s\"]", -// ptrA(mir_base64_encode(digest, cbResult)).get(), szServer.get(), m_szClientId.c_str()); - //WSSend(payload); - return true; -} - -///////////////////////////////////////////////////////////////////////////////////////// + proto::ClientPayload node; -bool WhatsAppProto::ProcessSecret(const CMStringA &szSecret) -{ - if (szSecret.IsEmpty()) - return false; + MFileVersion v; + Miranda_GetFileVersion(&v); - size_t secretLen = 0; - mir_ptr<BYTE> pSecret((BYTE *)mir_base64_decode(szSecret, &secretLen)); - if (pSecret == nullptr || secretLen != 144) { - debugLogA("Invalid secret key, dropping session (secret len = %u", (unsigned)secretLen); - return false; - } + // generate registration packet + if (m_szClientToken.IsEmpty()) { + uint8_t appVersion[16]; + mir_md5_hash((BYTE*)APP_VERSION, sizeof(APP_VERSION) - 1, appVersion); - ec_public_key pPeerPublic; - memcpy(pPeerPublic.data, pSecret, 32); + auto *pAppVersion = new proto::DeviceProps_AppVersion(); + pAppVersion->set_primary(v[0]); + pAppVersion->set_secondary(v[1]); + pAppVersion->set_tertiary(v[2]); + pAppVersion->set_quaternary(v[3]); - MBinBuffer privKey; - if (!getBlob(DBKEY_PRIVATE_KEY, privKey)) - return false; + proto::DeviceProps pCompanion; + pCompanion.set_os("Miranda"); + pCompanion.set_allocated_version(pAppVersion); + pCompanion.set_platformtype(proto::DeviceProps_PlatformType_DESKTOP); + pCompanion.set_requirefullsync(true); - ec_private_key pMyPrivate; - memcpy(pMyPrivate.data, privKey.data(), 32); + //MBinBuffer buf(pCompanion.ByteSize()); + //pCompanion.SerializeToArray(buf.data(), (int)buf.length()); - uint8_t *pSharedKey, *pSharedExpanded; - int sharedLen = curve_calculate_agreement(&pSharedKey, &pPeerPublic, &pMyPrivate); - { - BYTE salt[32], md[32]; - unsigned int md_len = 32; - memset(salt, 0, sizeof(salt)); - HMAC(EVP_sha256(), salt, sizeof(salt), pSharedKey, sharedLen, md, &md_len); - - hkdf_context *pHKDF; - hkdf_create(&pHKDF, 3, g_plugin.pCtx); - hkdf_expand(pHKDF, &pSharedExpanded, md, sizeof(md), 0, 0, 80); - hkdf_destroy(pHKDF); - } + auto *pPairingData = new proto::ClientPayload_DevicePairingRegistrationData(); + //pPairingData->set_deviceprops(buf.data(), buf.length()); + pPairingData->set_buildhash(appVersion, sizeof(appVersion)); + pPairingData->set_eregid(""); - // validation - { - unsigned int md_len = 32; - BYTE sum[32], md[32], enc[112], *key = pSharedExpanded + 32; - memcpy(enc, pSecret, 32); - memcpy(enc + 32, (BYTE *)pSecret + 64, 80); - memcpy(sum, (BYTE *)pSecret + 32, 32); - HMAC(EVP_sha256(), key, 32, enc, sizeof(enc), md, &md_len); - if (memcmp(md, sum, 32)) { - debugLogA("Secret key validation failed, exiting"); - return false; - } + node.set_allocated_devicepairingdata(pPairingData); } - - // woohoo, everything is ok, decrypt keys - { - BYTE enc[80], dec[112], key[32], iv[16]; - memcpy(key, pSharedExpanded, sizeof(key)); - memcpy(iv, pSharedExpanded+64, sizeof(iv)); - memcpy(enc, pSecret.get() + 64, sizeof(enc)); - - int dec_len = 0, final_len = 0; - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); - EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv); - EVP_DecryptUpdate(ctx, dec, &dec_len, enc, sizeof(enc)); - EVP_DecryptFinal_ex(ctx, dec + dec_len, &final_len); - dec_len += final_len; - EVP_CIPHER_CTX_free(ctx); - - enc_key.assign(dec, 32); - mac_key.assign(dec + 32, 32); - - db_set_blob(0, m_szModuleName, DBKEY_ENC_KEY, enc_key.data(), (int)enc_key.length()); - db_set_blob(0, m_szModuleName, DBKEY_MAC_KEY, mac_key.data(), (int)mac_key.length()); + // generate login packet + else { + node.set_passive(true); } + auto *pUserVersion = new proto::ClientPayload_UserAgent_AppVersion(); + pUserVersion->set_primary(v[0]); + pUserVersion->set_secondary(v[1]); + pUserVersion->set_tertiary(v[2]); + pUserVersion->set_quaternary(v[3]); + + auto *pUserAgent = new proto::ClientPayload_UserAgent(); + pUserAgent->set_allocated_appversion(pUserVersion); + pUserAgent->set_platform(proto::ClientPayload_UserAgent_Platform_WINDOWS); + pUserAgent->set_releasechannel(proto::ClientPayload_UserAgent_ReleaseChannel_RELEASE); + pUserAgent->set_mcc("000"); + pUserAgent->set_mnc("000"); + pUserAgent->set_osversion("10.0"); + pUserAgent->set_osbuildnumber("10.0"); + pUserAgent->set_manufacturer(""); + pUserAgent->set_device("Desktop"); + pUserAgent->set_localelanguageiso6391("en"); + pUserAgent->set_localecountryiso31661alpha2("US"); + + auto *pWebInfo = new proto::ClientPayload_WebInfo(); + pWebInfo->set_websubplatform(proto::ClientPayload_WebInfo_WebSubPlatform_WINDA); + + node.set_connecttype(proto::ClientPayload_ConnectType_WIFI_UNKNOWN); + node.set_connectreason(proto::ClientPayload_ConnectReason_USER_ACTIVATED); + node.set_allocated_useragent(pUserAgent); + node.set_allocated_webinfo(pWebInfo); return true; } @@ -162,26 +134,6 @@ void WhatsAppProto::OnRestoreSession1(const JSONNode&, void*) // WSSend(payload, &WhatsAppProto::OnRestoreSession2); } -void WhatsAppProto::OnRestoreSession2(const JSONNode &root, void*) -{ - int status = root["status"].as_int(); - if (status != 200) { - debugLogA("Attempt to restore session failed with error %d", status); - - if (status == 401 || status == 419) { - POPUPDATAW Popup = {}; - Popup.lchIcon = IcoLib_GetIconByHandle(Skin_GetIconHandle(SKINICON_ERROR)); - wcsncpy_s(Popup.lpwzText, TranslateT("You need to launch WhatsApp on your phone"), _TRUNCATE); - wcsncpy_s(Popup.lpwzContactName, m_tszUserName, _TRUNCATE); - Popup.iSeconds = 10; - PUAddPopupW(&Popup); - } - - ShutdownSession(); - return; - } -} - ///////////////////////////////////////////////////////////////////////////////////////// void WhatsAppProto::ShutdownSession() @@ -266,7 +218,7 @@ bool WhatsAppProto::ServerThreadWorker() return false; delete m_noise; - m_noise = new WANoise(); + m_noise = new WANoise(this); debugLogA("Server connection succeeded"); m_hServerConn = pReply->nlc; @@ -274,9 +226,6 @@ bool WhatsAppProto::ServerThreadWorker() m_iPktNumber = 0; m_szClientToken = getMStringA(DBKEY_CLIENT_TOKEN); - MFileVersion v; - Miranda_GetFileVersion(&v); - auto &pubKey = m_noise->getPub(); ptrA szPubKey(mir_base64_encode(pubKey.data(), pubKey.length())); auto *client = new proto::HandshakeMessage::ClientHello(); client->set_ephemeral(pubKey.data(), pubKey.length()); @@ -454,11 +403,6 @@ void WhatsAppProto::ProcessCmd(const JSONNode &root) { CMStringW wszType(root["type"].as_mstring()); if (wszType == L"challenge") { - CMStringA szChallenge(root["challenge"].as_mstring()); - if (!ProcessChallenge(szChallenge)) { - ShutdownSession(); - return; - } } } @@ -469,13 +413,6 @@ void WhatsAppProto::ProcessConn(const JSONNode &root) writeStr(DBKEY_ID, root["wid"]); m_szJid = getMStringA(DBKEY_ID); - CMStringA szSecret(root["secret"].as_mstring()); - if (!szSecret.IsEmpty()) - if (!ProcessSecret(szSecret)) { - ShutdownSession(); - return; - } - writeStr(DBKEY_NICK, root["pushname"]); writeStr(DBKEY_CLIENT_TOKEN, root["clientToken"]); writeStr(DBKEY_SERVER_TOKEN, root["serverToken"]); diff --git a/protocols/WhatsAppWeb/src/utils.cpp b/protocols/WhatsAppWeb/src/utils.cpp index fba42e8b8d..73ceb1ea94 100644 --- a/protocols/WhatsAppWeb/src/utils.cpp +++ b/protocols/WhatsAppWeb/src/utils.cpp @@ -7,8 +7,6 @@ Copyright © 2019 George Hazan #include "stdafx.h" -#define sharedKey(A, B, C) crypto_scalarmult((unsigned char*)A, (const unsigned char*)B, (const unsigned char*)C) - WAUser* WhatsAppProto::FindUser(const char *szId) { mir_cslock lck(m_csUsers); @@ -100,28 +98,6 @@ int WhatsAppProto::WSSendNode(const char *pszPrefix, int flags, WANode &node, WA BYTE iv[16]; Utils_GetRandom(iv, sizeof(iv)); - // allocate the buffer of the same size + 32 bytes for temporary operations - MBinBuffer enc; - enc.assign(writer.body.data(), writer.body.length()); - enc.append(mac_key.data(), mac_key.length()); - - int enc_len = 0, final_len = 0; - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); - EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, (BYTE *)enc_key.data(), iv); - EVP_EncryptUpdate(ctx, (BYTE *)enc.data(), &enc_len, (BYTE *)writer.body.data(), (int)writer.body.length()); - EVP_EncryptFinal_ex(ctx, (BYTE *)enc.data() + enc_len, &final_len); - EVP_CIPHER_CTX_free(ctx); - - // build the resulting buffer of the following structure: - // - packet prefix - // - 32 bytes of HMAC - // - 16 bytes of iv - // - rest of encoded data - - BYTE hmac[32]; - unsigned int hmac_len = 32; - HMAC(EVP_sha256(), mac_key.data(), (int)mac_key.length(), (BYTE *)enc.data(), enc_len, hmac, &hmac_len); - int pktId = ++m_iPktNumber; if (pHandler != nullptr) { @@ -138,57 +114,19 @@ int WhatsAppProto::WSSendNode(const char *pszPrefix, int flags, WANode &node, WA MBinBuffer ret; ret.append(pszPrefix, strlen(pszPrefix)); ret.append(postPrefix, sizeof(postPrefix)); - ret.append(hmac, sizeof(hmac)); - ret.append(iv, sizeof(iv)); - ret.append(enc.data(), enc_len); WebSocket_SendBinary(m_hServerConn, ret.data(), ret.length()); return pktId; } ///////////////////////////////////////////////////////////////////////////////////////// - -bool WhatsAppProto::decryptBinaryMessage(size_t cbSize, const void *buf, MBinBuffer &res) -{ - if (cbSize <= 32) - return false; - - // validate message first - { - unsigned int md_len = 32; - BYTE md[32]; - HMAC(EVP_sha256(), mac_key.data(), (int)mac_key.length(), (unsigned char *)buf+32, (int)cbSize-32, md, &md_len); - if (memcmp(buf, md, sizeof(md))) { - debugLogA("Message cannot be decrypted, check your keys"); - return false; - } - } - - // okay, let's decrypt this thing - auto *pBuf = (const unsigned char *)buf; - { - BYTE iv[16]; - memcpy(iv, pBuf + 32, sizeof(iv)); - res.assign(pBuf + 48, cbSize - 48); - res.append(mac_key.data(), 32); // reserve 32 more bytes for temp data - - int dec_len = 0, final_len = 0; - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); - EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, (BYTE*)enc_key.data(), iv); - EVP_DecryptUpdate(ctx, (BYTE*)res.data(), &dec_len, pBuf + 48, (int)cbSize - 48); - EVP_DecryptFinal_ex(ctx, (BYTE*)res.data() + dec_len, &final_len); - EVP_CIPHER_CTX_free(ctx); - } - return true; -} - -///////////////////////////////////////////////////////////////////////////////////////// // WANoise members static uint8_t intro_header[] = { 87, 65, 6, DICT_VERSION }; static uint8_t noise_init[] = "Noise_XX_25519_AESGCM_SHA256\0\0\0\0"; -WANoise::WANoise() +WANoise::WANoise(WhatsAppProto *_ppro) : + ppro(_ppro) { salt.assign(noise_init, 32); encKey.assign(noise_init, 32); @@ -229,18 +167,24 @@ void WANoise::deriveKey(const void *pData, size_t cbLen, MBinBuffer &write, MBin read.assign(out + 32, 32); } -void WANoise::mixIntoKey(const void *pData, size_t cbLen) +void WANoise::mixIntoKey(const void *n, const void *p) { - deriveKey(pData, cbLen, salt, encKey); + uint8_t tmp[32]; + crypto_scalarmult((unsigned char *)tmp, (const unsigned char *)n, (const unsigned char *)p); + + deriveKey(tmp, sizeof(tmp), salt, encKey); decKey.assign(encKey.data(), encKey.length()); readCounter = writeCounter = 0; } void WANoise::decrypt(const void *pData, size_t cbLen, MBinBuffer &dest) { + auto &pVar = (bInitFinished) ? readCounter : writeCounter; + uint8_t iv[12]; memset(iv, 0, 8); - memcpy(iv + 8, (bInitFinished) ? &readCounter : &writeCounter, sizeof(int)); + memcpy(iv + 8, &pVar, sizeof(int)); + pVar++; uint8_t outbuf[1024 + EVP_MAX_BLOCK_LENGTH]; @@ -249,7 +193,7 @@ void WANoise::decrypt(const void *pData, size_t cbLen, MBinBuffer &dest) EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, (BYTE *)decKey.data(), iv); for (size_t len = 0; len < cbLen; len += 1024) { size_t portionSize = cbLen - len; - EVP_DecryptUpdate(ctx, outbuf, &dec_len, (BYTE*)pData + len, min(portionSize, 1024)); + EVP_DecryptUpdate(ctx, outbuf, &dec_len, (BYTE*)pData + len, (int)min(portionSize, 1024)); if (len == 0) dest.assign(outbuf, dec_len); else @@ -259,6 +203,8 @@ void WANoise::decrypt(const void *pData, size_t cbLen, MBinBuffer &dest) if (final_len) dest.append(outbuf, final_len); EVP_CIPHER_CTX_free(ctx); + + updateHash(pData, cbLen); } bool WANoise::decodeFrame(const void *pData, size_t cbLen) @@ -270,25 +216,29 @@ bool WANoise::decodeFrame(const void *pData, size_t cbLen) auto &static_ = msg.serverhello().static_(); auto &payload = msg.serverhello().payload(); - uint8_t tmp[32]; - updateHash(ephemeral.c_str(), ephemeral.size()); - - sharedKey(tmp, privKey.data(), ephemeral.c_str()); - mixIntoKey(tmp, sizeof(tmp)); + mixIntoKey(privKey.data(), ephemeral.c_str()); MBinBuffer decryptedStatic, decryptedCert; decrypt(static_.c_str(), static_.size(), decryptedStatic); - - sharedKey(tmp, privKey.data(), decryptedStatic.data()); - mixIntoKey(tmp, sizeof(tmp)); + mixIntoKey(privKey.data(), decryptedStatic.data()); decrypt(payload.c_str(), payload.size(), decryptedCert); proto::CertChain cert; cert.ParseFromArray(decryptedCert.data(), (int)decryptedCert.length()); proto::CertChain::NoiseCertificate::Details details; details.ParseFromString(cert.intermediate().details()); - if (details.issuerserial() != 0) + if (details.issuerserial() != 0) { + ppro->ShutdownSession(); return false; + } + + MBinBuffer mainPub, mainPriv; + ppro->getBlob(DBKEY_PUB_KEY, mainPub); + ppro->getBlob(DBKEY_PRIVATE_KEY, mainPriv); + + MBinBuffer encryptedPub; + encrypt(mainPub.data(), mainPub.length(), encryptedPub); + mixIntoKey(mainPriv.data(), ephemeral.c_str()); } return true; } @@ -315,7 +265,30 @@ void WANoise::encodeFrame(const void *pData, size_t cbLen, MBinBuffer &res) void WANoise::encrypt(const void *pData, size_t cbLen, MBinBuffer &dest) { + uint8_t iv[12]; + memset(iv, 0, 8); + memcpy(iv + 8, &writeCounter, sizeof(int)); + writeCounter++; + + uint8_t outbuf[1024 + 64]; + + int enc_len = 0, final_len = 0; + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, (BYTE *)encKey.data(), iv); + for (size_t len = 0; len < cbLen; len += 1024) { + size_t portionSize = cbLen - len; + EVP_EncryptUpdate(ctx, outbuf, &enc_len, (BYTE *)pData + len, (int)min(portionSize, 1024)); + if (len == 0) + dest.assign(outbuf, enc_len); + else + dest.append(outbuf, enc_len); + } + EVP_EncryptFinal_ex(ctx, outbuf, &final_len); + if (final_len) + dest.append(outbuf, final_len); + EVP_CIPHER_CTX_free(ctx); + updateHash(dest.data(), dest.length()); } void WANoise::updateHash(const void *pData, size_t cbLen) |