diff options
author | George Hazan <ghazan@miranda.im> | 2021-04-19 11:43:23 +0300 |
---|---|---|
committer | George Hazan <ghazan@miranda.im> | 2021-04-19 11:43:23 +0300 |
commit | b78ff7e78e16a0ec2e674d750a73df71909e3a8b (patch) | |
tree | 7cbfb52b81b23be1b5a1b605cfd04114dbb49515 /protocols | |
parent | a6d12ce24f94600dea3fb64aaa3b08c7b6be11ea (diff) |
WhatsApp: binary packets decoding into JSON structures
Diffstat (limited to 'protocols')
-rw-r--r-- | protocols/WhatsAppWeb/WhatsAppWeb.vcxproj | 1 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters | 3 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/server.cpp | 15 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/stdafx.h | 1 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/utils.cpp | 262 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/utils.h | 47 |
6 files changed, 325 insertions, 4 deletions
diff --git a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj index 9439401cf7..0b03a0b17e 100644 --- a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj +++ b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj @@ -56,6 +56,7 @@ <ClInclude Include="src\proto.h" /> <ClInclude Include="src\resource.h" /> <ClInclude Include="src\stdafx.h" /> + <ClInclude Include="src\utils.h" /> <ClInclude Include="src\version.h" /> </ItemGroup> <ItemGroup> diff --git a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters index 956e44e8eb..24ccddb69d 100644 --- a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters +++ b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters @@ -43,6 +43,9 @@ <ClInclude Include="src\version.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="src\utils.h"> + <Filter>Header Files</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ResourceCompile Include="res\version.rc"> diff --git a/protocols/WhatsAppWeb/src/server.cpp b/protocols/WhatsAppWeb/src/server.cpp index 1a66504532..ad3b94a64f 100644 --- a/protocols/WhatsAppWeb/src/server.cpp +++ b/protocols/WhatsAppWeb/src/server.cpp @@ -320,9 +320,6 @@ bool WhatsAppProto::ServerThreadWorker() continue; } - offset = 0; - debugLogA("Got packet: buffer = %d, opcode = %d, headerSize = %d, final = %d, masked = %d", bufSize, hdr.opCode, hdr.headerSize, hdr.bIsFinal, hdr.bIsMasked); - // we have some additional data, not only opcode if ((size_t)bufSize > hdr.headerSize) { netbuf.append(buf, bufSize); @@ -330,6 +327,9 @@ bool WhatsAppProto::ServerThreadWorker() break; } + offset = 0; + debugLogA("Got packet: buffer = %d, opcode = %d, headerSize = %d, final = %d, masked = %d", bufSize, hdr.opCode, hdr.headerSize, hdr.bIsFinal, hdr.bIsMasked); + // read all payloads from the current buffer, one by one size_t prevSize = 0; while (true) { @@ -424,6 +424,13 @@ bool WhatsAppProto::ServerThreadWorker() void WhatsAppProto::ProcessBinaryPacket(const MBinBuffer &buf) { + WAReader reader(buf.data(), buf.length()); + + JSONNode root; + if (!reader.readNode(root)) + return; + + debugLogA("packed JSON: %s", root.write().c_str()); } ///////////////////////////////////////////////////////////////////////////////////////// @@ -447,7 +454,7 @@ void WhatsAppProto::ProcessBlocked(const JSONNode &node) for (auto &it : node["blocklist"]) { auto *pUser = AddUser(it.as_string().c_str(), false); Ignore_Ignore(pUser->hContact, IGNOREEVENT_ALL); - Contact_RemoveFromList(pUser->hContact); + Contact_Hide(pUser->hContact); } } diff --git a/protocols/WhatsAppWeb/src/stdafx.h b/protocols/WhatsAppWeb/src/stdafx.h index c831328858..4434f9ba8c 100644 --- a/protocols/WhatsAppWeb/src/stdafx.h +++ b/protocols/WhatsAppWeb/src/stdafx.h @@ -76,6 +76,7 @@ struct ec_private_key : public signal_type_base }; #include "db.h" +#include "utils.h" #include "proto.h" #include "resource.h" diff --git a/protocols/WhatsAppWeb/src/utils.cpp b/protocols/WhatsAppWeb/src/utils.cpp index bc35b6ef5f..b6877f529a 100644 --- a/protocols/WhatsAppWeb/src/utils.cpp +++ b/protocols/WhatsAppWeb/src/utils.cpp @@ -69,3 +69,265 @@ bool WhatsAppProto::decryptBinaryMessage(size_t cbSize, const void *buf, MBinBuf } return true; } + +///////////////////////////////////////////////////////////////////////////////////////// + +bool WAReader::readAttributes(JSONNode &ret, int count) +{ + if (count == 0) + return true; + + for (int i = 0; i < count; i++) { + CMStringA name = readString(readInt8()); + if (name.IsEmpty()) + return false; + + CMStringA value = readString(readInt8()); + if (value.IsEmpty()) + return false; + + ret << CHAR_PARAM(name, value); + } + return true; +} + +uint32_t WAReader::readInt20() +{ + if (m_limit - m_buf < 3) + return 0; + + int ret = (int(m_buf[0] & 0x0F) << 16) + (int(m_buf[1]) << 8) + int(m_buf[2]); + m_buf += 3; + return ret; +} + +uint32_t WAReader::readIntN(int n) +{ + if (m_limit - m_buf < n) + return 0; + + uint32_t res = 0; + for (int i = 0; i < n; i++, m_buf++) + res = (res <<= 8) + *m_buf; + return res; +} + +bool WAReader::readList(JSONNode &parent, int tag) +{ + int size = readListSize(tag); + if (size == -1) + return false; + + JSONNode list(JSON_ARRAY); list.set_name("$list$"); + for (int i = 0; i < size; i++) { + JSONNode tmp; + if (!readNode(tmp)) + return false; + list << tmp; + } + + parent << list; + return true; +} + +int WAReader::readListSize(int tag) +{ + switch (tag) { + case LIST_EMPTY: + return 0; + case LIST_8: + return readInt8(); + case LIST_16: + return readInt16(); + } + return -1; +} + +bool WAReader::readNode(JSONNode &ret) +{ + int listSize = readListSize(readInt8()); + if (listSize == -1) + return false; + + int descrTag = readInt8(); + if (descrTag == STREAM_END) + return false; + + CMStringA name = readString(descrTag); + if (name.IsEmpty()) + return false; + ret.set_name(name.c_str()); + + if (!readAttributes(ret, (listSize-1)>>1)) + return false; + + if ((listSize % 2) == 1) + return true; + + int size, tag = readInt8(); + switch (tag) { + case LIST_EMPTY: case LIST_8: case LIST_16: + readList(ret, tag); + break; + + case BINARY_8: + size = readInt8(); + +LBL_Binary: + if (m_limit - m_buf < size) + return false; + + ret << CHAR_PARAM("$bin$", ptrA(mir_base64_encode(m_buf, size))); + m_buf += size; + break; + + case BINARY_20: + size = readInt20(); + goto LBL_Binary; + + case BINARY_32: + size = readInt32(); + goto LBL_Binary; + + default: + ret << CHAR_PARAM("$str$", readString(tag).c_str()); + } + + return true; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static int unpackHex(int val) +{ + if (val < 0 || val > 15) + return -1; + + return (val < 10) ? val + '0' : val - 10 + 'A'; +} + +static int unpackNibble(int val) +{ + if (val < 0 || val > 15) + return -1; + + switch (val) { + case 10: return '-'; + case 11: return '.'; + case 15: return 0; + default: return '0' + val; + } +} + +CMStringA WAReader::readPacked(int tag) +{ + int startByte = readInt8(); + bool bTrim = false; + if (startByte & 0x80) { + startByte &= ~0x80; + bTrim = true; + } + + CMStringA ret; + for (int i = 0; i < startByte; i++) { + BYTE b = readInt8(); + int lower = (tag == NIBBLE_8) ? unpackNibble(b >> 4) : unpackHex(b >> 4); + if (lower == -1) + return ""; + + int higher = (tag == NIBBLE_8) ? unpackNibble(b & 0x0F) : unpackHex(b & 0x0F); + if (higher == -1) + return ""; + + ret.AppendChar(lower); + ret.AppendChar(higher); + } + + return ret; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static char *SingleByteTokens[] = { + "", "", "", "200", "400", "404", "500", "501", "502", "action", "add", + "after", "archive", "author", "available", "battery", "before", "body", + "broadcast", "chat", "clear", "code", "composing", "contacts", "count", + "create", "debug", "delete", "demote", "duplicate", "encoding", "error", + "false", "filehash", "from", "g.us", "group", "groups_v2", "height", "id", + "image", "in", "index", "invis", "item", "jid", "kind", "last", "leave", + "live", "log", "media", "message", "mimetype", "missing", "modify", "name", + "notification", "notify", "out", "owner", "participant", "paused", + "picture", "played", "presence", "preview", "promote", "query", "raw", + "read", "receipt", "received", "recipient", "recording", "relay", + "remove", "response", "resume", "retry", "s.whatsapp.net", "seconds", + "set", "size", "status", "subject", "subscribe", "t", "text", "to", "true", + "type", "unarchive", "unavailable", "url", "user", "value", "web", "width", + "mute", "read_only", "admin", "creator", "short", "update", "powersave", + "checksum", "epoch", "block", "previous", "409", "replaced", "reason", + "spam", "modify_tag", "message_info", "delivery", "emoji", "title", + "description", "canonical-url", "matched-text", "star", "unstar", + "media_key", "filename", "identity", "unread", "page", "page_count", + "search", "media_message", "security", "call_log", "profile", "ciphertext", + "invite", "gif", "vcard", "frequent", "privacy", "blacklist", "whitelist", + "verify", "location", "document", "elapsed", "revoke_invite", "expiration", + "unsubscribe", "disable", "vname", "old_jid", "new_jid", "announcement", + "locked", "prop", "label", "color", "call", "offer", "call-id", + "quick_reply", "sticker", "pay_t", "accept", "reject", "sticker_pack", + "invalid", "canceled", "missed", "connected", "result", "audio", + "video", "recent" }; + +CMStringA WAReader::readString(int tag) +{ + if (tag >= 3 && tag < _countof(SingleByteTokens)) { + CMStringA ret = SingleByteTokens[tag]; + if (ret == "s.whatsapp.net") + return "c.us"; + return ret; + } + + switch (tag) { +// case DICTIONARY_0: return dict0[readInt8()]; +// case DICTIONARY_1: return dict1[readInt8()]; +// case DICTIONARY_2: return dict2[readInt8()]; +// case DICTIONARY_3: return dict3[readInt8()]; + case LIST_EMPTY: + return ""; + + case BINARY_8: + return readStringFromChars(readInt8()); + + case BINARY_20: + return readStringFromChars(readInt20()); + + case BINARY_32: + return readStringFromChars(readInt32()); + + case NIBBLE_8: + case HEX_8: + return readPacked(tag); + + case JID_PAIR: + CMStringA s1 = readString(readInt8()); + if (s1.IsEmpty()) + break; + + CMStringA s2 = readString(readInt8()); + if (s2.IsEmpty()) + break; + + return CMStringA(FORMAT, "%s@%s", s1.c_str(), s2.c_str()); + } + + // error + return ""; +} + +CMStringA WAReader::readStringFromChars(int size) +{ + if (m_limit - m_buf < size) + return ""; + + CMStringA ret((char*)m_buf, size); + m_buf += size; + return ret; +} diff --git a/protocols/WhatsAppWeb/src/utils.h b/protocols/WhatsAppWeb/src/utils.h new file mode 100644 index 0000000000..d37c8fb187 --- /dev/null +++ b/protocols/WhatsAppWeb/src/utils.h @@ -0,0 +1,47 @@ +/* + +WhatsAppWeb plugin for Miranda NG +Copyright © 2019-21 George Hazan + +*/ + +#define LIST_EMPTY 0 +#define STREAM_END 2 +#define DICTIONARY_0 236 +#define DICTIONARY_1 237 +#define DICTIONARY_2 238 +#define DICTIONARY_3 239 +#define LIST_8 248 +#define LIST_16 249 +#define JID_PAIR 250 +#define HEX_8 251 +#define BINARY_8 252 +#define BINARY_20 253 +#define BINARY_32 254 +#define NIBBLE_8 255 + +class WAReader +{ + const BYTE *m_buf, *m_limit; + + uint32_t readIntN(int i); + CMStringA readStringFromChars(int size); + +public: + WAReader(const void *buf, size_t cbLen) : + m_buf((BYTE*)buf), + m_limit((BYTE*)buf + cbLen) + {} + + __forceinline uint32_t readInt8() { return readIntN(1); } + __forceinline uint32_t readInt16() { return readIntN(2); } + __forceinline uint32_t readInt32() { return readIntN(4); } + + bool readAttributes(JSONNode &node, int count); + uint32_t readInt20(); + bool readList(JSONNode &node, int tag); + int readListSize(int tag); + CMStringA readPacked(int tag); + CMStringA readString(int tag); + bool readNode(JSONNode&); +}; |