diff --git a/DWeChatRobot/DWeChatRobot.vcxproj b/DWeChatRobot/DWeChatRobot.vcxproj index fdd7e9ce0e1d4ebe4cec71bb1bf3e94cacad473c..287ff4a00c8f225b7c62c7cf9186b601e92d845d 100644 --- a/DWeChatRobot/DWeChatRobot.vcxproj +++ b/DWeChatRobot/DWeChatRobot.vcxproj @@ -340,6 +340,7 @@ + @@ -386,4 +387,4 @@ - \ No newline at end of file + diff --git a/DWeChatRobot/DWeChatRobot.vcxproj.filters b/DWeChatRobot/DWeChatRobot.vcxproj.filters index c373ad71842b47141b36b79591e17b9fda6f174d..997b5c1f86a3e927be47f1f69cda6def9b16db5a 100644 --- a/DWeChatRobot/DWeChatRobot.vcxproj.filters +++ b/DWeChatRobot/DWeChatRobot.vcxproj.filters @@ -338,5 +338,8 @@ 群相关\获取群成员昵称 + + wxsocket + - \ No newline at end of file + diff --git a/DWeChatRobot/DbExecuteSql.cpp b/DWeChatRobot/DbExecuteSql.cpp index cf9f03d36c2ec874bdb30b6b03bc6e3ae569d91f..f650488fb6700c1f07c596c21a8feb3ea5e891ad 100644 --- a/DWeChatRobot/DbExecuteSql.cpp +++ b/DWeChatRobot/DbExecuteSql.cpp @@ -1,4 +1,5 @@ #include "pch.h" +#include // sqlite3_callbackָ typedef int (*sqlite3_callback)( @@ -17,10 +18,10 @@ typedef int(__cdecl *Sqlite3_exec)( ); /* -* ⲿʱݵIJṹ -* ptrDbݿ -* ptrSqlsqlĵַ -*/ + * ⲿʱݵIJṹ + * ptrDbݿ + * ptrSqlsqlĵַ + */ #ifndef USE_SOCKET struct executeParams { @@ -30,10 +31,10 @@ struct executeParams #endif /* -* ѯĽṹ -* ColNameֶl_ColName`ColName`ַ -* contentֵֶl_content`content`ַ -*/ + * ѯĽṹ + * ColNameֶl_ColName`ColName`ַ + * contentֵֶl_content`content`ַ + */ struct SQLResultStruct { char *ColName; @@ -44,10 +45,10 @@ struct SQLResultStruct }; /* -* ⲿʱķ -* SQLResultAddr`SQLResult`׳Աַ -* lengthѯ -*/ + * ⲿʱķ + * SQLResultAddr`SQLResult`׳Աַ + * lengthѯ + */ struct executeResult { DWORD SQLResultAddr; @@ -60,8 +61,8 @@ executeResult result = {0}; vector> SQLResult; /* -* ȡݿϢĻص -*/ + * ȡݿϢĻص + */ int GetDbInfo(void *data, int argc, char **argv, char **azColName) { DbInfoStruct *pdata = (DbInfoStruct *)data; @@ -112,8 +113,8 @@ int GetDbInfo(void *data, int argc, char **argv, char **azColName) } /* -* DLLڲѯõĻصֱʾѯô -*/ + * DLLڲѯõĻصֱʾѯô + */ int query(void *data, int argc, char **argv, char **azColName) { for (int i = 0; i < argc; i++) @@ -126,9 +127,9 @@ int query(void *data, int argc, char **argv, char **azColName) } /* -* ⲿʱʹõĻص`SQLResult` -* returnintִгɹ`0`ִʧܷط0ֵ -*/ + * ⲿʱʹõĻص`SQLResult` + * returnintִгɹ`0`ִʧܷط0ֵ + */ int selectdbinfo(void *data, int argc, char **argv, char **azColName) { executeResult *pdata = (executeResult *)data; @@ -159,9 +160,9 @@ int selectdbinfo(void *data, int argc, char **argv, char **azColName) } /* -* ղѯͷڴ -* returnvoid -*/ + * ղѯͷڴ + * returnvoid + */ void ClearResultArray() { if (SQLResult.size() == 0) @@ -190,13 +191,13 @@ void ClearResultArray() } /* -* ִSQLں -* ptrDbݿ -* sqlҪִеSQL -* callbackصַ -* dataݸصIJ -* returnvoid*ִгɹָ룬ִʧܷ`0` -*/ + * ִSQLں + * ptrDbݿ + * sqlҪִеSQL + * callbackصַ + * dataݸصIJ + * returnvoid*ִгɹָ룬ִʧܷ`0` + */ void *ExecuteSQL(DWORD ptrDb, const char *sql, DWORD callback, void *data) { DWORD WeChatWinBase = GetWeChatWinBase(); @@ -209,10 +210,10 @@ void *ExecuteSQL(DWORD ptrDb, const char *sql, DWORD callback, void *data) } /* -* ⲿõִSQLӿ -* lpParameter`executeParams`ͽṹָ -* returnDWORDSQLִгɹ`SQLResult`׳Աַ򷵻0 -*/ + * ⲿõִSQLӿ + * lpParameter`executeParams`ͽṹָ + * returnDWORDSQLִгɹ`SQLResult`׳Աַ򷵻0 + */ #ifndef USE_SOCKET DWORD ExecuteSQLRemote(LPVOID lpParameter) { @@ -316,4 +317,37 @@ int SelectDataRemote(LPVOID lpParameter) } return 0; } +#else +vector> SelectData(DWORD db_hanle, const char *sql) +{ + vector> ret; + ClearResultArray(); + void *status = SelectData(db_hanle, sql, &result); + if (SQLResult.size() == 0) + return ret; + vector index; + for (size_t i = 0; i < SQLResult[0].size(); i++) + index.push_back(SQLResult[0][i].ColName); + ret.push_back(index); + for (auto it : SQLResult) + { + vector item; + for (size_t i = 0; i < it.size(); i++) + { + if (!it[i].isblob) + { + string content(it[i].content); + item.push_back(content); + } + else + { + string b64_str = base64_encode((BYTE *)it[i].content, it[i].l_content); + item.push_back(b64_str); + } + } + ret.push_back(item); + } + ClearResultArray(); + return ret; +} #endif diff --git a/DWeChatRobot/DbExecuteSql.h b/DWeChatRobot/DbExecuteSql.h index be96319e56bd0186de3d243eede73672031b0860..699f8fdbc5a4191975312f088a89b0544edaf8be 100644 --- a/DWeChatRobot/DbExecuteSql.h +++ b/DWeChatRobot/DbExecuteSql.h @@ -1,12 +1,14 @@ #pragma once -#include +#include -int GetDbInfo(void* data, int argc, char** argv, char** azColName); -int selectdbinfo(void* data, int argc, char** argv, char** azColName); -int query(void* data, int argc, char** argv, char** azColName); +int GetDbInfo(void *data, int argc, char **argv, char **azColName); +int selectdbinfo(void *data, int argc, char **argv, char **azColName); +int query(void *data, int argc, char **argv, char **azColName); #ifndef USE_SOCKET extern "C" __declspec(dllexport) DWORD ExecuteSQLRemote(LPVOID lpParameter); extern "C" __declspec(dllexport) int SelectDataRemote(LPVOID lpParameter); +#else +vector> SelectData(DWORD db_hanle, const char *sql); #endif -void* ExecuteSQL(DWORD ptrDb, const char* sql, DWORD callback, void* data); -void* SelectData(DWORD db, const char* sql, void* data); \ No newline at end of file +void *ExecuteSQL(DWORD ptrDb, const char *sql, DWORD callback, void *data); +void *SelectData(DWORD db, const char *sql, void *data); diff --git a/DWeChatRobot/LogMsgInfo.cpp b/DWeChatRobot/LogMsgInfo.cpp index 4a61dd5bc6a4c8930461d67af23f04701adbbbc2..5c91f3539329aa1c0c9a1a93fa68ee64fbbaa5ea 100644 --- a/DWeChatRobot/LogMsgInfo.cpp +++ b/DWeChatRobot/LogMsgInfo.cpp @@ -18,48 +18,49 @@ static DWORD JmpBackAddr = WeChatWinBase + HookLogMsgJmpBackOffset; // Ƿ־HOOK־ static BOOL LogMsgHooked = false; // HOOKǰָڻָ -char LogOldAsmCode[5] = { 0 }; +char LogOldAsmCode[5] = {0}; -static void SendLogToComServer(wchar_t* logmsg) { - // _variant_t log = logmsg; - // PostComMessage(WX_LOG_MESSAGE, &log); - delete[] logmsg; - logmsg = NULL; +static void SendLogToComServer(wchar_t *logmsg) +{ + // _variant_t log = logmsg; + // PostComMessage(WX_LOG_MESSAGE, &log); + delete[] logmsg; + logmsg = NULL; } /* -* ӡ־Ϣ -* msg־Ϣ -* returnvoid -*/ -VOID PrintMsg(DWORD msg) { - if (!msg) - return; - DWORD dwId = 0; - char* utf8_message = (char*)msg; - int c_size = MultiByteToWideChar(CP_UTF8, 0, utf8_message, -1, 0, 0); - wchar_t* wmessage = new wchar_t[c_size + 1]; - memset(wmessage, 0, (c_size + 1) * 2); - MultiByteToWideChar(CP_UTF8, 0, utf8_message, -1, wmessage, c_size); - c_size = WideCharToMultiByte(CP_ACP, 0, wmessage, -1, 0, 0, 0, 0); - char* message = new char[c_size + 1]; - memset(message, 0, c_size + 1); - WideCharToMultiByte(CP_ACP, 0, wmessage, -1, message, c_size, 0, 0); - delete[] wmessage; - wmessage = NULL; -#ifdef _DEBUG - cout << message; -#endif - delete[] message; - message = NULL; - return; + * ӡ־Ϣ + * msg־Ϣ + * returnvoid + */ +VOID PrintMsg(DWORD msg) +{ + if (!msg) + return; + DWORD dwId = 0; + char *utf8_message = (char *)msg; + int c_size = MultiByteToWideChar(CP_UTF8, 0, utf8_message, -1, 0, 0); + wchar_t *wmessage = new wchar_t[c_size + 1]; + memset(wmessage, 0, (c_size + 1) * 2); + MultiByteToWideChar(CP_UTF8, 0, utf8_message, -1, wmessage, c_size); + c_size = WideCharToMultiByte(CP_ACP, 0, wmessage, -1, 0, 0, 0, 0); + char *message = new char[c_size + 1]; + memset(message, 0, c_size + 1); + WideCharToMultiByte(CP_ACP, 0, wmessage, -1, message, c_size, 0, 0); + delete[] wmessage; + wmessage = NULL; + cout << message; + delete[] message; + message = NULL; + return; } /* -* HOOKľʵ֣־ô -*/ -__declspec(naked) void doprintmsg(){ - __asm { + * HOOKľʵ֣־ô + */ +__declspec(naked) void doprintmsg() +{ + __asm { pushad; pushfd; push eax; @@ -69,31 +70,33 @@ __declspec(naked) void doprintmsg(){ popad; call NextCallAddr; jmp JmpBackAddr; - } + } } /* -* ʼHOOK΢־ -* returnvoid -*/ -VOID HookLogMsgInfo() { - WeChatWinBase = GetWeChatWinBase(); - if (LogMsgHooked || !WeChatWinBase) - return; - HookLogMsgInfoAddr = WeChatWinBase + HookLogMsgInfoAddrOffset; - NextCallAddr = WeChatWinBase + HookLogMsgInfoNextCallOffset; - JmpBackAddr = WeChatWinBase + HookLogMsgJmpBackOffset; - HookAnyAddress(HookLogMsgInfoAddr,(LPVOID)doprintmsg, LogOldAsmCode); - LogMsgHooked = true; + * ʼHOOK΢־ + * returnvoid + */ +VOID HookLogMsgInfo() +{ + WeChatWinBase = GetWeChatWinBase(); + if (LogMsgHooked || !WeChatWinBase) + return; + HookLogMsgInfoAddr = WeChatWinBase + HookLogMsgInfoAddrOffset; + NextCallAddr = WeChatWinBase + HookLogMsgInfoNextCallOffset; + JmpBackAddr = WeChatWinBase + HookLogMsgJmpBackOffset; + HookAnyAddress(HookLogMsgInfoAddr, (LPVOID)doprintmsg, LogOldAsmCode); + LogMsgHooked = true; } /* -* ֹͣHOOK΢־ -* returnvoid -*/ -VOID UnHookLogMsgInfo() { - if (!LogMsgHooked) - return; - UnHookAnyAddress(HookLogMsgInfoAddr, LogOldAsmCode); - LogMsgHooked = false; -} \ No newline at end of file + * ֹͣHOOK΢־ + * returnvoid + */ +VOID UnHookLogMsgInfo() +{ + if (!LogMsgHooked) + return; + UnHookAnyAddress(HookLogMsgInfoAddr, LogOldAsmCode); + LogMsgHooked = false; +} diff --git a/DWeChatRobot/ReceiveMessage.cpp b/DWeChatRobot/ReceiveMessage.cpp index 3460c13917ee47d6eb4fddab476be0b749975952..a0fc9681f22d9e81052368d41cd39bf49b0e92a8 100644 --- a/DWeChatRobot/ReceiveMessage.cpp +++ b/DWeChatRobot/ReceiveMessage.cpp @@ -232,7 +232,6 @@ _declspec(naked) void dealReceiveMessage() __asm { pushad; pushfd; - // mov eax, [edi]; push edi; call ReceiveMessage; add esp, 0x4; diff --git a/DWeChatRobot/base64/README.md b/DWeChatRobot/base64/README.md new file mode 100644 index 0000000000000000000000000000000000000000..22ca9cccee8a2f39e91132478da7991017103fb8 --- /dev/null +++ b/DWeChatRobot/base64/README.md @@ -0,0 +1,6 @@ +# ע +˴Դ²ֿ⣺ +[cpp-base64](https://github.com/ReneNyffenegger/cpp-base64) + +# Thanks +[cpp-base64](https://github.com/ReneNyffenegger/cpp-base64) diff --git a/DWeChatRobot/base64/base64.cpp b/DWeChatRobot/base64/base64.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cb27e5311fc7acfcf3c42e4b6e8e7199e69a3c90 --- /dev/null +++ b/DWeChatRobot/base64/base64.cpp @@ -0,0 +1,312 @@ +#include "pch.h" +/* + base64.cpp and base64.h + + base64 encoding and decoding with C++. + More information at + https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp + + Version: 2.rc.08 (release candidate) + + Copyright (C) 2004-2017, 2020, 2021 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +*/ + +#include "base64.h" + +#include +#include + +// +// Depending on the url parameter in base64_chars, one of +// two sets of base64 characters needs to be chosen. +// They differ in their last two characters. +// +static const char *base64_chars[2] = { + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "+/", + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "-_"}; + +static unsigned int pos_of_char(const unsigned char chr) +{ + // + // Return the position of chr within base64_encode() + // + + if (chr >= 'A' && chr <= 'Z') + return chr - 'A'; + else if (chr >= 'a' && chr <= 'z') + return chr - 'a' + ('Z' - 'A') + 1; + else if (chr >= '0' && chr <= '9') + return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2; + else if (chr == '+' || chr == '-') + return 62; // Be liberal with input and accept both url ('-') and non-url ('+') base 64 characters ( + else if (chr == '/' || chr == '_') + return 63; // Ditto for '/' and '_' + else + // + // 2020-10-23: Throw std::exception rather than const char* + //(Pablo Martin-Gomez, https://github.com/Bouska) + // + throw std::runtime_error("Input is not valid base64-encoded data."); +} + +static std::string insert_linebreaks(std::string str, size_t distance) +{ + // + // Provided by https://github.com/JomaCorpFX, adapted by me. + // + if (!str.length()) + { + return ""; + } + + size_t pos = distance; + + while (pos < str.size()) + { + str.insert(pos, "\n"); + pos += distance + 1; + } + + return str; +} + +template +static std::string encode_with_line_breaks(String s) +{ + return insert_linebreaks(base64_encode(s, false), line_length); +} + +template +static std::string encode_pem(String s) +{ + return encode_with_line_breaks(s); +} + +template +static std::string encode_mime(String s) +{ + return encode_with_line_breaks(s); +} + +template +static std::string encode(String s, bool url) +{ + return base64_encode(reinterpret_cast(s.data()), s.length(), url); +} + +std::string base64_encode(unsigned char const *bytes_to_encode, size_t in_len, bool url) +{ + + size_t len_encoded = (in_len + 2) / 3 * 4; + + unsigned char trailing_char = url ? '.' : '='; + + // + // Choose set of base64 characters. They differ + // for the last two positions, depending on the url + // parameter. + // A bool (as is the parameter url) is guaranteed + // to evaluate to either 0 or 1 in C++ therefore, + // the correct character set is chosen by subscripting + // base64_chars with url. + // + const char *base64_chars_ = base64_chars[url]; + + std::string ret; + ret.reserve(len_encoded); + + unsigned int pos = 0; + + while (pos < in_len) + { + ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0xfc) >> 2]); + + if (pos + 1 < in_len) + { + ret.push_back(base64_chars_[((bytes_to_encode[pos + 0] & 0x03) << 4) + ((bytes_to_encode[pos + 1] & 0xf0) >> 4)]); + + if (pos + 2 < in_len) + { + ret.push_back(base64_chars_[((bytes_to_encode[pos + 1] & 0x0f) << 2) + ((bytes_to_encode[pos + 2] & 0xc0) >> 6)]); + ret.push_back(base64_chars_[bytes_to_encode[pos + 2] & 0x3f]); + } + else + { + ret.push_back(base64_chars_[(bytes_to_encode[pos + 1] & 0x0f) << 2]); + ret.push_back(trailing_char); + } + } + else + { + + ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0x03) << 4]); + ret.push_back(trailing_char); + ret.push_back(trailing_char); + } + + pos += 3; + } + + return ret; +} + +template +static std::string decode(String encoded_string, bool remove_linebreaks) +{ + // + // decode(…) is templated so that it can be used with String = const std::string& + // or std::string_view (requires at least C++17) + // + + if (encoded_string.empty()) + return std::string(); + + if (remove_linebreaks) + { + + std::string copy(encoded_string); + + copy.erase(std::remove(copy.begin(), copy.end(), '\n'), copy.end()); + + return base64_decode(copy, false); + } + + size_t length_of_string = encoded_string.length(); + size_t pos = 0; + + // + // The approximate length (bytes) of the decoded string might be one or + // two bytes smaller, depending on the amount of trailing equal signs + // in the encoded string. This approximation is needed to reserve + // enough space in the string to be returned. + // + size_t approx_length_of_decoded_string = length_of_string / 4 * 3; + std::string ret; + ret.reserve(approx_length_of_decoded_string); + + while (pos < length_of_string) + { + // + // Iterate over encoded input string in chunks. The size of all + // chunks except the last one is 4 bytes. + // + // The last chunk might be padded with equal signs or dots + // in order to make it 4 bytes in size as well, but this + // is not required as per RFC 2045. + // + // All chunks except the last one produce three output bytes. + // + // The last chunk produces at least one and up to three bytes. + // + + size_t pos_of_char_1 = pos_of_char(encoded_string[pos + 1]); + + // + // Emit the first output byte that is produced in each chunk: + // + ret.push_back(static_cast(((pos_of_char(encoded_string[pos + 0])) << 2) + ((pos_of_char_1 & 0x30) >> 4))); + + if ((pos + 2 < length_of_string) && // Check for data that is not padded with equal signs (which is allowed by RFC 2045) + encoded_string[pos + 2] != '=' && + encoded_string[pos + 2] != '.' // accept URL-safe base 64 strings, too, so check for '.' also. + ) + { + // + // Emit a chunk's second byte (which might not be produced in the last chunk). + // + unsigned int pos_of_char_2 = pos_of_char(encoded_string[pos + 2]); + ret.push_back(static_cast(((pos_of_char_1 & 0x0f) << 4) + ((pos_of_char_2 & 0x3c) >> 2))); + + if ((pos + 3 < length_of_string) && + encoded_string[pos + 3] != '=' && + encoded_string[pos + 3] != '.') + { + // + // Emit a chunk's third byte (which might not be produced in the last chunk). + // + ret.push_back(static_cast(((pos_of_char_2 & 0x03) << 6) + pos_of_char(encoded_string[pos + 3]))); + } + } + + pos += 4; + } + + return ret; +} + +std::string base64_decode(std::string const &s, bool remove_linebreaks) +{ + return decode(s, remove_linebreaks); +} + +std::string base64_encode(std::string const &s, bool url) +{ + return encode(s, url); +} + +std::string base64_encode_pem(std::string const &s) +{ + return encode_pem(s); +} + +std::string base64_encode_mime(std::string const &s) +{ + return encode_mime(s); +} + +#if __cplusplus >= 201703L +// +// Interface with std::string_view rather than const std::string& +// Requires C++17 +// Provided by Yannic Bonenberger (https://github.com/Yannic) +// + +std::string base64_encode(std::string_view s, bool url) +{ + return encode(s, url); +} + +std::string base64_encode_pem(std::string_view s) +{ + return encode_pem(s); +} + +std::string base64_encode_mime(std::string_view s) +{ + return encode_mime(s); +} + +std::string base64_decode(std::string_view s, bool remove_linebreaks) +{ + return decode(s, remove_linebreaks); +} + +#endif // __cplusplus >= 201703L diff --git a/DWeChatRobot/base64/base64.h b/DWeChatRobot/base64/base64.h new file mode 100644 index 0000000000000000000000000000000000000000..3d2bab761c82d1d9af350c4efe7bb9bb41e148c8 --- /dev/null +++ b/DWeChatRobot/base64/base64.h @@ -0,0 +1,35 @@ +// +// base64 encoding and decoding with C++. +// Version: 2.rc.08 (release candidate) +// +#pragma once +#ifndef BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A +#define BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A + +#include + +#if __cplusplus >= 201703L +#include +#endif // __cplusplus >= 201703L + +std::string base64_encode(std::string const &s, bool url = false); +std::string base64_encode_pem(std::string const &s); +std::string base64_encode_mime(std::string const &s); + +std::string base64_decode(std::string const &s, bool remove_linebreaks = false); +std::string base64_encode(unsigned char const *, size_t len, bool url = false); + +#if __cplusplus >= 201703L +// +// Interface with std::string_view rather than const std::string& +// Requires C++17 +// Provided by Yannic Bonenberger (https://github.com/Yannic) +// +std::string base64_encode(std::string_view s, bool url = false); +std::string base64_encode_pem(std::string_view s); +std::string base64_encode_mime(std::string_view s); + +std::string base64_decode(std::string_view s, bool remove_linebreaks = false); +#endif // __cplusplus >= 201703L + +#endif /* BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A */ diff --git a/DWeChatRobot/base64/pch.h b/DWeChatRobot/base64/pch.h new file mode 100644 index 0000000000000000000000000000000000000000..6f70f09beec2219624baeca92e2cd7deaa104fb4 --- /dev/null +++ b/DWeChatRobot/base64/pch.h @@ -0,0 +1 @@ +#pragma once diff --git a/DWeChatRobot/pch.cpp b/DWeChatRobot/pch.cpp index 47eb8c97b5e1866174b99908e398879595ca810e..44fe86ac2d82d1f42756f02c55c8060b7b416b71 100644 --- a/DWeChatRobot/pch.cpp +++ b/DWeChatRobot/pch.cpp @@ -237,7 +237,8 @@ void PrintProcAddr() printf("AddFriendByV3 0x%08X\n", (DWORD)AddFriendByV3); printf("AddFriendByWxid 0x%08X\n", (DWORD)AddFriendByWxid); printf("AddBrandContact 0x%08X\n", (DWORD)AddBrandContact); - printf("SelectData 0x%08X\n", (DWORD)SelectData); + void *(*select_data)(DWORD, const char *, void *) = SelectData; + printf("SelectData 0x%08X\n", (DWORD)select_data); void *(__stdcall * search_contact_by_net)(wchar_t *) = SearchContactByNet; printf("SearchContactByNet 0x%08X\n", (DWORD)search_contact_by_net); printf("AddChatRoomMember 0x%08X\n", (DWORD)AddChatRoomMember); diff --git a/DWeChatRobot/wxsocket.cpp b/DWeChatRobot/wxsocket.cpp index e30a8904b6e4e89859c5724336eab5c8a2adec29..6d9a3a28112ccc7d9cbb96a07bbb60a3de884437 100644 --- a/DWeChatRobot/wxsocket.cpp +++ b/DWeChatRobot/wxsocket.cpp @@ -468,8 +468,18 @@ void request_event(mg_http_message *hm, string &ret) DWORD db_handle = get_http_param_int(hm, jData, "db_handle", method); wstring sql = get_http_param_str(hm, jData, "sql", method); string a_sql = unicode_to_utf8(WS2LW(sql)); - // TODO: ݿѯĿǰ - // SelectData(db_handle, a_sql.c_str(), NULL); + vector> items = SelectData(db_handle, a_sql.c_str()); + json ret_data = {{"data", json::array()}, {"result", "OK"}}; + for (auto it : items) + { + json temp_arr = json::array(); + for (size_t i = 0; i < it.size(); i++) + { + temp_arr.push_back(it[i]); + } + ret_data["data"].push_back(temp_arr); + } + ret = ret_data.dump(); break; } case WECHAT_SET_VERSION: diff --git a/DWeChatRobot/wxsocketapi.h b/DWeChatRobot/wxsocketapi.h index 592b3e03c70841bee7bc6562118441575381648b..7a5600dd742b3e3d6bd796b176aa5fa304ad30bc 100644 --- a/DWeChatRobot/wxsocketapi.h +++ b/DWeChatRobot/wxsocketapi.h @@ -3,6 +3,7 @@ #include // mongoose: https://github.com/cesanta/mongoose #include "mongoose/mongoose.h" +#include "base64/base64.h" #pragma comment(lib, "ws2_32.lib") extern "C" __declspec(dllexport) void http_start(int port); extern "C" __declspec(dllexport) int http_close(); diff --git a/README.md b/README.md index d10ed573e6fc30fc359679164a4fe0e79a84d29c..a442efb6586845624b095b0d523b07718798ccec 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,46 @@ # 描述 PC微信机器人,实现以下功能: -1. 获取通讯录 -2. 发送文本、图片、文件、xml文章、名片、群艾特消息 -3. 根据wxid查询好友信息 -4. 根据群ID获取所有群成员wxid -5. 检测好友状态(是否好友、被删除、被拉黑) -6. 接收各类消息,可写回调函数进行处理 -7. 封装COM接口,方便使用自己喜欢的语言进行调用 -8. 群管理 -9. 微信多开 +1. 获取通讯录 +2. 发送文本、图片、文件、xml文章、名片、群艾特消息 +3. 根据wxid查询好友信息 +4. 根据群ID获取所有群成员wxid +5. 检测好友状态(是否好友、被删除、被拉黑) +6. 接收各类消息,可写回调函数进行处理 +7. 封装COM接口,方便使用自己喜欢的语言进行调用 +8. 群管理 +9. 微信多开 # 用途 -1. 淘客发单 -2. 无痕清粉 -3. 微信公众号采集 -4. 聊天记录备份 -5. 其他你能想到的用途 +1. 淘客发单 +2. 无痕清粉 +3. 微信公众号采集 +4. 聊天记录备份 +5. 其他你能想到的用途 # 可用版本 -微信电脑版**3.5.0.46** -微信电脑版**3.6.0.18** +微信电脑版**3.5.0.46** +微信电脑版**3.6.0.18** 微信电脑版**3.7.0.26** -微信电脑版**3.7.0.30** -主分支对应微信3.7.0.30版本,其他版本请查看对应分支。 +微信电脑版**3.7.0.30** +主分支对应微信3.7.0.30版本,其他版本请查看对应分支。 # 编译环境 **Visual Studio 2019**(平台配置:win32(x86)) # 原理 -通过逆向PC微信,定位到关键CALL,dll内联汇编调用 -注册32位COM组件,供64位/32位进程外部调用 +通过逆向PC微信,定位到关键CALL,dll内联汇编调用 +注册32位COM组件,供64位/32位进程外部调用 # 目录说明 -`./CWeChatRobot`:COM组件的实现代码 -`./DWeChatRobot`:注入的DLL实现代码,根据平台配置可编译出socket版和COM版 -`./wxRobot`: 包含C#的调用示例 -`./Python`:python示例和接口测试文件 -`./wxDriver`:driver的实现代码,有些函数具有一定的学习意义 -`./Release/CWeChatRobot.exe`:编译的COM组件 -`./Release/DWeChatRobot.dll`:编译的DLL文件 -`./Release/socket`:包含wxDriver.dll和socket接口的DLL -`./Release/WeChatTools.exe`:用于调试时注入或卸载DLL程序,具体参阅相关代码 +`./CWeChatRobot`:COM组件的实现代码 +`./DWeChatRobot`:注入的DLL实现代码,根据平台配置可编译出socket版和COM版 +`./wxRobot`: 包含C#的调用示例 +`./Python`:python示例和接口测试文件 +`./wxDriver`:driver的实现代码,有些函数具有一定的学习意义 +`./Release/CWeChatRobot.exe`:编译的COM组件 +`./Release/DWeChatRobot.dll`:编译的DLL文件 +`./Release/socket`:包含wxDriver.dll和socket接口的DLL +`./Release/WeChatTools.exe`:用于调试时注入或卸载DLL程序,具体参阅相关代码 # 注册COM -以管理员权限执行以下命令: +以管理员权限执行以下命令: ```shell # 安装 CWeChatRobot.exe /regserver @@ -48,81 +48,84 @@ CWeChatRobot.exe /regserver CWeChatRobot.exe /unregserver ``` # 调用 -**Python:** -参考[wxRobot.py](/Python/wxRobot.py) -**C#:** -参考[ComWechatRobotCsharp](https://github.com/RingoStudio/ComWechatRobotCsharp),感谢@RingoStudio 的贡献 -**易语言:** -参考[ESDK](/ESDK),感谢@lovezm 的贡献 -# 更多功能 -后续计划功能: -1. 实现http调用 +**Python:** +参考[wxRobot.py](/Python/wxRobot.py) +**C#:** +参考[ComWechatRobotCsharp](https://github.com/RingoStudio/ComWechatRobotCsharp),感谢@RingoStudio 的贡献 +**易语言:** +参考[ESDK](/ESDK),感谢@lovezm 的贡献 +# 更多功能 +1. 尝试添加issue中的功能 -有空的时候会按照上述顺序进行开发,不过嘛,计划只是计划,如果未实现也请见谅 -**也欢迎您提交PR** +有空的时候会按照上述顺序进行开发,不过嘛,计划只是计划,如果未实现也请见谅 +**也欢迎您提交PR** # 更新记录 ## 2022.04.01 -1. 使用SAFEARRAY返回通讯录列表,可正确显示好友昵称中的特殊符号 -2. README中添加目录说明 -3. 更新C#示例代码,添加好友列表的遍历示例 -## 2022.04.11 -1. 修改获取个人信息接口和发送文章接口,兼容老版wxid(未经测试,如有问题请提ISSUE) -2. 添加接收消息的接口,可以写回调对消息进行处理(参考Python示例文件) +1. 使用SAFEARRAY返回通讯录列表,可正确显示好友昵称中的特殊符号 +2. README中添加目录说明 +3. 更新C#示例代码,添加好友列表的遍历示例 +## 2022.04.11 +1. 修改获取个人信息接口和发送文章接口,兼容老版wxid(未经测试,如有问题请提ISSUE) +2. 添加接收消息的接口,可以写回调对消息进行处理(参考Python示例文件) ## 2022.04.12 -1. 添加发送群艾特消息的接口 +1. 添加发送群艾特消息的接口 ## 2022.04.12 -1. 添加通过群ID获取所有群成员wxid接口 +1. 添加通过群ID获取所有群成员wxid接口 ## 2022.04.13 -1. 更新群艾特接口,可同时艾特多人 +1. 更新群艾特接口,可同时艾特多人 ## 2022.04.18 -1. 添加获取数据库句柄接口(部分句柄,获取全量句柄需Hook) -2. 添加执行SQL命令接口 -3. 添加在线数据库备份接口 -## 2022.06.01 -1. 适配微信**3.7.0.26**版本,部分功能未经测试,如有问题请报issue -## 2022.06.02 -1. 添加通过好友申请接口(配合接收消息接口可自动通过好友) -2. 添加获取聊天记录数据库句柄(好友申请消息类型为0x25) -3. 优化了StartService接口,重复注入时不再关闭远程进程 -## 2022.06.04 -1. 完成通过wxid和v3数据加好友的COM接口(后续添加通过微信号、手机号、QQ号查询V3数据接口) -2. 优化接收消息逻辑,添加消息时间;新增Hook发送消息,返回数据中以一个BOOL值区分发送和接收 -3. 修复了一个BUG,该BUG可能导致Release配置下,COM接口无法正常加载DWeChatRobot.dll计算偏移 -## 2022.06.07 -1. 添加获取当前微信版本(读注册表)和启动微信的接口 -2. 优化数据库查询接口,现在可以正常查询BLOB类型 -## 2022.06.10 -1. 新增关注公众号、网络查询用户信息、Hook语音、未加密图片、自定义微信版本号接口 -2. Hook语音和图片的接口暂时还有瑕疵,图片收到后有可能不会自动下载;语音消息的文件名暂时是时间戳,计划替换为该条消息id。有空再做优化。 -## 2022.06.13 -1. 优化发送艾特消息接口,新增一个参数,指示是否自动填充被艾特人昵称 -2. 优化发送文章消息接口,新增一个参数,用于展示消息卡片缩略图 -3. 新增删除好友接口 -4. 新增发送小程序接口 -## 2022.06.18 -1. 修复了多个BUG -2. 整理代码结构,方便后续开发基于websocket的接口 -3. 添加64位程序注入DLL到32位程序的driver -## 2022.06.24 -1. 解决Python脚本中,socket接收数据可能不完整的问题 -2. 解决心跳时如果同步了同一个人的多条消息,只会返回一条的问题 -3. 感谢@shangdev 提供的思路,现在开启hook图片的时会修改自动下载图片时段为全天 -## 2022.06.30 -1. 已适配3.7.0.30版本 -## 2022.07.19 -1. 新增修改备注接口 -2. 新增群管理功能,包括添加成员、删除成员、设置公告、修改群名称、设置群内个人昵称、获取群成员昵称 -## 2022.07.24 -1. 添加多开管理 -## 2022.07.28 -1. 解决部分已知问题,优化多开管理 -2. 重构COM中的部分实现 -## 2022.08.13 -1. 现在消息HOOK内容包含消息ID -2. 完成发送消息的http接口,可参考[wxDriverTest.py](/Release/socket/wxDriverTest.py),其他接口还需要一点时间 -3. 新增项目配置文件,感谢@amchii 提供的方法 +1. 添加获取数据库句柄接口(部分句柄,获取全量句柄需Hook) +2. 添加执行SQL命令接口 +3. 添加在线数据库备份接口 +## 2022.06.01 +1. 适配微信**3.7.0.26**版本,部分功能未经测试,如有问题请报issue +## 2022.06.02 +1. 添加通过好友申请接口(配合接收消息接口可自动通过好友) +2. 添加获取聊天记录数据库句柄(好友申请消息类型为0x25) +3. 优化了StartService接口,重复注入时不再关闭远程进程 +## 2022.06.04 +1. 完成通过wxid和v3数据加好友的COM接口(后续添加通过微信号、手机号、QQ号查询V3数据接口) +2. 优化接收消息逻辑,添加消息时间;新增Hook发送消息,返回数据中以一个BOOL值区分发送和接收 +3. 修复了一个BUG,该BUG可能导致Release配置下,COM接口无法正常加载DWeChatRobot.dll计算偏移 +## 2022.06.07 +1. 添加获取当前微信版本(读注册表)和启动微信的接口 +2. 优化数据库查询接口,现在可以正常查询BLOB类型 +## 2022.06.10 +1. 新增关注公众号、网络查询用户信息、Hook语音、未加密图片、自定义微信版本号接口 +2. Hook语音和图片的接口暂时还有瑕疵,图片收到后有可能不会自动下载;语音消息的文件名暂时是时间戳,计划替换为该条消息id。有空再做优化。 +## 2022.06.13 +1. 优化发送艾特消息接口,新增一个参数,指示是否自动填充被艾特人昵称 +2. 优化发送文章消息接口,新增一个参数,用于展示消息卡片缩略图 +3. 新增删除好友接口 +4. 新增发送小程序接口 +## 2022.06.18 +1. 修复了多个BUG +2. 整理代码结构,方便后续开发基于websocket的接口 +3. 添加64位程序注入DLL到32位程序的driver +## 2022.06.24 +1. 解决Python脚本中,socket接收数据可能不完整的问题 +2. 解决心跳时如果同步了同一个人的多条消息,只会返回一条的问题 +3. 感谢@shangdev 提供的思路,现在开启hook图片的时会修改自动下载图片时段为全天 +## 2022.06.30 +1. 已适配3.7.0.30版本 +## 2022.07.19 +1. 新增修改备注接口 +2. 新增群管理功能,包括添加成员、删除成员、设置公告、修改群名称、设置群内个人昵称、获取群成员昵称 +## 2022.07.24 +1. 添加多开管理 +## 2022.07.28 +1. 解决部分已知问题,优化多开管理 +2. 重构COM中的部分实现 +## 2022.08.13 +1. 现在消息HOOK内容包含消息ID +2. 完成发送消息的http接口,可参考[wxDriver.py](/Release/socket/wxDriver.py),其他接口还需要一点时间 +3. 新增项目配置文件,感谢@amchii 提供的方法 + +## 2022.08.21 +1. 所有功能http接口封装完成,可接受get、post请求 +2. 提供http接口调用示例,参考[wxDriver.py](/Release/socket/wxDriver.py) # 打赏作者 -请给作者一个star,感谢感谢 +请给作者一个star,感谢感谢 # 免责声明 代码仅供交流学习使用,请勿用于非法用途和商业用途!如因此产生任何法律纠纷,均与作者无关! diff --git a/Release/socket/SWeChatRobot.dll b/Release/socket/SWeChatRobot.dll index 0048524d03610380cb2afaac94fede274815c3e9..88b7a5528d94779ff6980495a6afd6effa29c1b5 100644 Binary files a/Release/socket/SWeChatRobot.dll and b/Release/socket/SWeChatRobot.dll differ diff --git a/Release/socket/wxDriver.dll b/Release/socket/wxDriver.dll index c906eee83925ababfe112cbbf212fdac1cda7470..7dad9332b465ffd825d6f49fc64ba431d9844338 100644 Binary files a/Release/socket/wxDriver.dll and b/Release/socket/wxDriver.dll differ diff --git a/Release/socket/wxDriver.py b/Release/socket/wxDriver.py index b0ee3a8dcc70c28f3098fb29f935d86512102cfe..4249e5e71417280187ba246580d02cb6dd479243 100644 --- a/Release/socket/wxDriver.py +++ b/Release/socket/wxDriver.py @@ -1,7 +1,9 @@ import ctypes import json import copy +import threading import requests +import socketserver if ctypes.sizeof(ctypes.c_void_p) == ctypes.sizeof(ctypes.c_ulonglong): driver = ctypes.cdll.LoadLibrary('./wxDriver64.dll') @@ -10,7 +12,7 @@ else: new_wechat = driver.new_wechat new_wechat.argtypes = None -new_wechat.restype = ctypes.c_bool +new_wechat.restype = ctypes.c_int start_listen = driver.start_listen start_listen.argtypes = [ctypes.c_int,ctypes.c_int] @@ -68,7 +70,6 @@ class WECHAT_HTTP_APIS: # database WECHAT_DATABASE_GET_HANDLES = 32 # 获取数据库句柄 WECHAT_DATABASE_BACKUP = 33 # 备份数据库 - # TODO: 数据库查询目前不可用 WECHAT_DATABASE_QUERY = 34 # 数据库查询 # version @@ -99,7 +100,7 @@ class WECHAT_HTTP_API_PARAM_TEMPLATES: "wxids": "", "msg": "", "auto_nickname": 1}, - APIS.WECHAT_MSG_SEND_CARD: {"receiver":'', + APIS.WECHAT_MSG_SEND_CARD: {"receiver":"", "shared_wxid":"", "nickname":""}, APIS.WECHAT_MSG_SEND_IMAGE: {"receiver":"", @@ -185,6 +186,55 @@ class WECHAT_HTTP_API_PARAM_TEMPLATES: get_http_template = WECHAT_HTTP_API_PARAM_TEMPLATES().get_http_template +class ReceiveMsgSocketServer(socketserver.BaseRequestHandler): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + class ReceiveMsgStruct(ctypes.Structure): + _fields_ = [("pid", ctypes.wintypes.DWORD), + ("type", ctypes.wintypes.DWORD), + ("isSendMsg", ctypes.wintypes.DWORD), + ("msgid", ctypes.c_ulonglong), + ("sender", ctypes.c_wchar * 80), + ("wxid", ctypes.c_wchar * 80), + ("message", ctypes.c_wchar * 0x1000B), + ("filepath", ctypes.c_wchar * 260), + ("time", ctypes.c_wchar * 30) + ] + + def handle(self): + conn = self.request + while True: + try: + ptr_data = conn.recv(1024) + try: + if ptr_data.decode() == 'bye': break + except UnicodeDecodeError: + pass + while len(ptr_data) < ctypes.sizeof(self.ReceiveMsgStruct): + data = conn.recv(1024) + if len(data) == 0: break + ptr_data += data + if ptr_data: + ptr_receive_msg = ctypes.cast(ptr_data, ctypes.POINTER(self.ReceiveMsgStruct)) + ReceiveMsgSocketServer.msg_callback(ptr_receive_msg.contents) + except OSError: + break + except: + pass + conn.sendall("200 OK".encode()) + conn.close() + + @staticmethod + def msg_callback(data): + msg = {'pid': data.pid, 'time': data.time, 'type': data.type, + 'isSendMsg': data.isSendMsg, 'msgid': data.msgid, + 'wxid': data.wxid,'message': data.message, + 'sendto' if data.isSendMsg else 'from': data.sender} + # TODO: 在这里写额外的消息处理逻辑 + + print(msg) + def post_wechat_http_api(api,port,data = {}): url = "http://127.0.0.1:{}/api/?type={}".format(port,api) resp = requests.post(url = url,data = json.dumps(data)) @@ -207,13 +257,27 @@ def get_wechat_pid_list() -> list: pass return pid_list -def test(): - test_port = 8000 - pids = get_wechat_pid_list() - if len(pids) == 0: - pids.append(new_wechat()) - start_listen(pids[0],test_port) - +def start_socket_server(port: int = 10808, + request_handler = ReceiveMsgSocketServer, + main_thread: bool = True) -> int or None: + ip_port = ("127.0.0.1", port) + try: + s = socketserver.ThreadingTCPServer(ip_port, request_handler) + if main_thread: + s.serve_forever() + else: + socket_server = threading.Thread(target=s.serve_forever) + socket_server.setDaemon(True) + socket_server.start() + return socket_server.ident + except KeyboardInterrupt: + pass + except Exception as e: + print(e) + return None + +def test(test_port): + post_wechat_http_api(APIS.WECHAT_LOG_START_HOOK,port = test_port) if post_wechat_http_api(APIS.WECHAT_IS_LOGIN,port = test_port)["is_login"] == 1: print(post_wechat_http_api(APIS.WECHAT_GET_SELF_INFO,port = test_port)) @@ -239,8 +303,21 @@ def test(): print(post_wechat_http_api(APIS.WECHAT_CONTACT_GET_LIST,port = test_port)) data = {"wxid":"filehelper"} print(post_wechat_http_api(APIS.WECHAT_CONTACT_CHECK_STATUS,data = data,port = test_port)) - - stop_listen(pids[0]) + dbs = post_wechat_http_api(APIS.WECHAT_DATABASE_GET_HANDLES,port = test_port) + db_handle = dbs['data'][0]['handle'] + sql = "select * from Contact limit 1;" + data = {"db_handle":db_handle,"sql":sql} + res = post_wechat_http_api(APIS.WECHAT_DATABASE_QUERY,data = data,port = test_port) + print(res) + post_wechat_http_api(APIS.WECHAT_LOG_STOP_HOOK,port = test_port) if __name__ == '__main__': - test() + port = 8000 + pids = get_wechat_pid_list() + if len(pids) == 0: + pids.append(new_wechat()) + start_listen(pids[0],port) + post_wechat_http_api(APIS.WECHAT_LOG_START_HOOK,8000) + post_wechat_http_api(APIS.WECHAT_MSG_START_HOOK,8000,{"port":10808}) + start_socket_server() + stop_listen(pids[0]) diff --git a/Release/socket/wxDriver64.dll b/Release/socket/wxDriver64.dll index fdc9d6c8f28f75922b6af34819fb3043639a11dd..c1fe9d73a2d0505b80c622afa5e506c4c1581928 100644 Binary files a/Release/socket/wxDriver64.dll and b/Release/socket/wxDriver64.dll differ diff --git a/wxDriver/inject.cpp b/wxDriver/inject.cpp index b57afcbae20a2a653140f892409feba09477a508..0bbdb9eefd3d0ef1415319359bc79772d6bd9f24 100644 --- a/wxDriver/inject.cpp +++ b/wxDriver/inject.cpp @@ -42,6 +42,8 @@ BOOL RemoveDll(DWORD pid) #endif if (pFunc == NULL) return false; - CallRemoteFunction(hp.GetHandle(), pFunc, dwHandle); + do + CallRemoteFunction(hp.GetHandle(), pFunc, dwHandle); + while (hp.WeChatRobotBase() != 0); return true; }