提交 269203ce 编写于 作者: L ljc545w

完善http接口

上级 27b8d2f4
......@@ -340,6 +340,7 @@
<ClCompile Include="AddFriendByV3.cpp" />
<ClCompile Include="AddFriendByWxid.cpp" />
<ClCompile Include="AddBrandContact.cpp" />
<ClCompile Include="base64\base64.cpp" />
<ClCompile Include="CheckFriendStatus.cpp" />
<ClCompile Include="comclient.cpp" />
<ClCompile Include="DbBackup.cpp" />
......@@ -386,4 +387,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
\ No newline at end of file
</Project>
......@@ -338,5 +338,8 @@
<ClCompile Include="GetChatRoomMemberNickname.cpp">
<Filter>群相关\获取群成员昵称</Filter>
</ClCompile>
<ClCompile Include="base64\base64.cpp">
<Filter>wxsocket</Filter>
</ClCompile>
</ItemGroup>
</Project>
\ No newline at end of file
</Project>
#include "pch.h"
#include <map>
// sqlite3_callback函数指针
typedef int (*sqlite3_callback)(
......@@ -17,10 +18,10 @@ typedef int(__cdecl *Sqlite3_exec)(
);
/*
* 外部调用时传递的参数结构
* ptrDb:数据库句柄
* ptrSql:保存sql的地址
*/
* 外部调用时传递的参数结构
* ptrDb:数据库句柄
* ptrSql:保存sql的地址
*/
#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<vector<SQLResultStruct>> 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`中
* return:int,执行成功返回`0`,执行失败返回非0值
*/
* 外部调用时使用的回调函数,将结果存入`SQLResult`中
* return:int,执行成功返回`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)
}
/*
* 清空查询结果,释放内存
* return:void
*/
* 清空查询结果,释放内存
* return:void
*/
void ClearResultArray()
{
if (SQLResult.size() == 0)
......@@ -190,13 +191,13 @@ void ClearResultArray()
}
/*
* 执行SQL的入口函数
* ptrDb:数据库句柄
* sql:要执行的SQL
* callback:回调函数地址
* data:传递给回调函数的参数
* return:void*,执行成功返回数组指针,执行失败返回`0`
*/
* 执行SQL的入口函数
* ptrDb:数据库句柄
* sql:要执行的SQL
* callback:回调函数地址
* data:传递给回调函数的参数
* return:void*,执行成功返回数组指针,执行失败返回`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`类型结构体指针
* return:DWORD,如果SQL执行成功,返回`SQLResult`首成员地址,否则返回0
*/
* 供外部调用的执行SQL接口
* lpParameter:`executeParams`类型结构体指针
* return:DWORD,如果SQL执行成功,返回`SQLResult`首成员地址,否则返回0
*/
#ifndef USE_SOCKET
DWORD ExecuteSQLRemote(LPVOID lpParameter)
{
......@@ -316,4 +317,37 @@ int SelectDataRemote(LPVOID lpParameter)
}
return 0;
}
#else
vector<vector<string>> SelectData(DWORD db_hanle, const char *sql)
{
vector<vector<string>> ret;
ClearResultArray();
void *status = SelectData(db_hanle, sql, &result);
if (SQLResult.size() == 0)
return ret;
vector<string> 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<string> 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
#pragma once
#include<windows.h>
#include <windows.h>
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<vector<string>> 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);
......@@ -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:日志信息
* return:void
*/
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:日志信息
* return:void
*/
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微信日志
* return:void
*/
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微信日志
* return:void
*/
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微信日志
* return:void
*/
VOID UnHookLogMsgInfo() {
if (!LogMsgHooked)
return;
UnHookAnyAddress(HookLogMsgInfoAddr, LogOldAsmCode);
LogMsgHooked = false;
}
\ No newline at end of file
* 停止HOOK微信日志
* return:void
*/
VOID UnHookLogMsgInfo()
{
if (!LogMsgHooked)
return;
UnHookAnyAddress(HookLogMsgInfoAddr, LogOldAsmCode);
LogMsgHooked = false;
}
......@@ -232,7 +232,6 @@ _declspec(naked) void dealReceiveMessage()
__asm {
pushad;
pushfd;
// mov eax, [edi];
push edi;
call ReceiveMessage;
add esp, 0x4;
......
# 注意
此处源码来自以下仓库:
[cpp-base64](https://github.com/ReneNyffenegger/cpp-base64)
# Thanks
[cpp-base64](https://github.com/ReneNyffenegger/cpp-base64)
#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 <algorithm>
#include <stdexcept>
//
// 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 <typename String, unsigned int line_length>
static std::string encode_with_line_breaks(String s)
{
return insert_linebreaks(base64_encode(s, false), line_length);
}
template <typename String>
static std::string encode_pem(String s)
{
return encode_with_line_breaks<String, 64>(s);
}
template <typename String>
static std::string encode_mime(String s)
{
return encode_with_line_breaks<String, 76>(s);
}
template <typename String>
static std::string encode(String s, bool url)
{
return base64_encode(reinterpret_cast<const unsigned char *>(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 <typename String>
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<std::string::value_type>(((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<std::string::value_type>(((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<std::string::value_type>(((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
//
// 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 <string>
#if __cplusplus >= 201703L
#include <string_view>
#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 */
......@@ -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);
......
......@@ -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<vector<string>> 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:
......
......@@ -3,6 +3,7 @@
#include <signal.h>
// 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();
......
# 描述
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,感谢感谢
# 免责声明
代码仅供交流学习使用,请勿用于非法用途和商业用途!如因此产生任何法律纠纷,均与作者无关!
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])
......@@ -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;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册