From b19b965416e77b3df6c6b00ecc668259ee45e1bd Mon Sep 17 00:00:00 2001 From: ljc545w Date: Wed, 11 May 2022 17:10:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0DWeChatRobot=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DWeChatRobot/CheckFriendStatus.cpp | 51 ++++++++++++++++++++++++++- DWeChatRobot/DbBackup.cpp | 24 +++++++++++-- DWeChatRobot/DbBackup.h | 7 +++- DWeChatRobot/DbExecuteSql.cpp | 48 +++++++++++++++++++++++++ DWeChatRobot/DbExecuteSql.h | 2 +- DWeChatRobot/FriendList.cpp | 29 +++++++++++++-- DWeChatRobot/GetChatRoomMemebers.cpp | 22 ++++++++++++ DWeChatRobot/GetDbHandles.cpp | 18 ++++++++-- DWeChatRobot/GetDbHandles.h | 15 ++++++++ DWeChatRobot/LogMsgInfo.cpp | 24 +++++++++++++ DWeChatRobot/ReceiveMessage.cpp | 42 +++++++++++++++++++++- DWeChatRobot/SelfInfo.cpp | 15 +++++++- DWeChatRobot/SendArticle.cpp | 31 ++++++++++++++++ DWeChatRobot/SendAtText.cpp | 37 +++++++++++++++++-- DWeChatRobot/SendCard.cpp | 20 +++++++++++ DWeChatRobot/SendFile.cpp | 32 ++++++++++++++++- DWeChatRobot/SendImage.cpp | 20 +++++++++++ DWeChatRobot/SendText.cpp | 18 ++++++++++ DWeChatRobot/UserInfo.cpp | 38 +++++++++++++++++++- DWeChatRobot/pch.cpp | 40 +++++++++++++++++++++ DWeChatRobot/pch.h | 13 +++++++ Release/DWeChatRobot.dll | Bin 43520 -> 43520 bytes 22 files changed, 530 insertions(+), 16 deletions(-) diff --git a/DWeChatRobot/CheckFriendStatus.cpp b/DWeChatRobot/CheckFriendStatus.cpp index 1a4e6bb..656cf0f 100644 --- a/DWeChatRobot/CheckFriendStatus.cpp +++ b/DWeChatRobot/CheckFriendStatus.cpp @@ -1,23 +1,38 @@ #include "pch.h" +// 检查好友状态CALL1偏移 #define CheckFriendStatusCall1Offset 0x78861210 - 0x787A0000 +// 检查好友状态CALL2偏移 #define CheckFriendStatusCall2Offset 0x03521CD0 - 0x02E20000 +// 检查好友状态CALL3偏移 #define CheckFriendStatusCall3Offset 0x03521DC0 - 0x02E20000 +// 检查好友状态CALL4偏移 #define CheckFriendStatusCall4Offset 0x0321FB90 - 0x02E20000 - +// 检查好友状态参数偏移 #define CheckFriendStatusParamOffset 0x0504F3BC - 0x02E20000 +// 好友状态码HOOK地址偏移 #define CheckFriendStatusHookOffset 0x5E0830B3 - 0x5DB60000 +// HOOK的CALL偏移 #define CheckFriendStatusNextCallOffset 0x5E083150 - 0x5DB60000 +// HOOK跳转的地址偏移 #define CheckFriendStatusHookJmpBackOffset 0x5E0830B8 - 0x5DB60000 +// HOOK的CALL地址 DWORD CheckFriendStatusNextCallAddress = GetWeChatWinBase() + CheckFriendStatusNextCallOffset; +// HOOK跳转的地址 DWORD CheckFriendStatusHookJmpBackAddress = GetWeChatWinBase() + CheckFriendStatusHookJmpBackOffset; +// 保存HOOK前的字节码,用于恢复 char OldAsmCode[5] = { 0 }; +// 是否HOOK标志 BOOL CheckFriendStatusHooked = false; +// 保存好友状态码并作为调用返回 DWORD LocalFriendStatus = 0x0; +/* +* 用于内存中平衡堆栈 +*/ struct FriendStatusParamStruct { DWORD fill0 = 0x0; DWORD fill1 = 0x0; @@ -28,12 +43,20 @@ struct FriendStatusParamStruct { char nullbuffer[0xC] = { 0 }; }; +/* +* 处理函数,参数不在状态码范围则不处理 +* result:好友状态码 +* return:void +*/ void dealVerifyUserResult(DWORD result) { if (result < 0xB0 || result > 0xB5) return; LocalFriendStatus = result; } +/* +* HOOK的具体实现,记录状态码并跳转到处理函数 +*/ __declspec(naked) void doHookVerifyUserResult() { __asm { pushfd; @@ -49,6 +72,10 @@ __declspec(naked) void doHookVerifyUserResult() { } } +/* +* 开始HOOK好友状态 +* return:void +*/ VOID HookFriendStatusCode(){ if (CheckFriendStatusHooked) return; @@ -58,6 +85,10 @@ VOID HookFriendStatusCode(){ CheckFriendStatusHooked = true; } +/* +* 取消HOOK好友状态 +* return:void +*/ VOID UnHookFriendStatusCode() { if (!CheckFriendStatusHooked) return; @@ -67,19 +98,37 @@ VOID UnHookFriendStatusCode() { CheckFriendStatusHooked = false; } +/* +* 供外部调用的检查好友状态接口1,启动HOOK +* return:void +*/ VOID CheckFriendStatusInitRemote() { HookFriendStatusCode(); } +/* +* 供外部调用的检查好友状态接口2,检查并返回状态码 +* lparameter:要检查的联系人wxid保存地址 +* return:DWORD,好友状态码 +*/ DWORD CheckFriendStatusRemote(LPVOID lparameter) { CheckFriendStatus((wchar_t*)lparameter); return LocalFriendStatus; } +/* +* 供外部调用的检查好友状态接口3,取消HOOK +* return:void +*/ VOID CheckFriendStatusFinishRemote() { UnHookFriendStatusCode(); } +/* +* 检查好友状态的具体实现 +* wxid:要检查的联系人wxid +* return:void +*/ VOID __stdcall CheckFriendStatus(wchar_t* wxid) { LocalFriendStatus = 0x0; DWORD WeChatWinBase = GetWeChatWinBase(); diff --git a/DWeChatRobot/DbBackup.cpp b/DWeChatRobot/DbBackup.cpp index ce389d3..abd032d 100644 --- a/DWeChatRobot/DbBackup.cpp +++ b/DWeChatRobot/DbBackup.cpp @@ -34,7 +34,6 @@ #define IDA_BASE 0x10000000 BOOL SQLite3_Backup_Init_Patched = FALSE; -DWORD lpAddressBackupDB = 0x0; typedef int(__cdecl* Sqlite3_open)(const char*, DWORD*); typedef DWORD(__cdecl* Sqlite3_backup_init)(DWORD, const char*, DWORD, const char*); @@ -50,6 +49,10 @@ DWORD OffsetFromIdaAddr(DWORD idaAddr) { return idaAddr - IDA_BASE; } +/* +* 数据库备份函数 +* return:int,无异常返回`0`,有异常返回非0值 +*/ int __cdecl backupDb( DWORD pDb, /* Database to back up */ const char* zFilename, /* Name of file to back up to */ @@ -102,6 +105,9 @@ int __cdecl backupDb( return rc; } +/* +* 绕过加密数据库备份限制 +*/ VOID PatchSQLite3_Backup_Init() { if (SQLite3_Backup_Init_Patched) return; @@ -118,6 +124,9 @@ VOID PatchSQLite3_Backup_Init() { return; } +/* +* 备份回调函数 +*/ void XProgress(int a, int b) { #ifdef _DEBUG @@ -126,6 +135,12 @@ void XProgress(int a, int b) return; } +/* +* 数据库在线备份入口 +* DbHandle:要备份的数据库句柄 +* BackupFile:备份保存位置 +* return:int,无异常返回`0`,有异常返回非0值 +*/ int BackupSQLiteDB(DWORD DbHandle,const char* BackupFile) { DWORD wxBaseAddress = GetWeChatWinBase(); @@ -163,7 +178,12 @@ int BackupSQLiteDB(DWORD DbHandle,const char* BackupFile) return rc; } -BOOL BackupSQLiteDBRemote(LPVOID lpParameter) { +/* +* 供外部调用的数据库在线备份接口 +* lpParameter:`BackupStruct`类型结构体指针 +* return:int,无异常返回`0`,有异常返回非0值 +*/ +int BackupSQLiteDBRemote(LPVOID lpParameter) { BackupStruct* param = (BackupStruct*)lpParameter; int rc = BackupSQLiteDB(param->DbHandle,(const char*)param->BackupFile); return rc; diff --git a/DWeChatRobot/DbBackup.h b/DWeChatRobot/DbBackup.h index 31f72e7..75296bb 100644 --- a/DWeChatRobot/DbBackup.h +++ b/DWeChatRobot/DbBackup.h @@ -1,8 +1,13 @@ #pragma once #include +/* +* 外部调用时传递的参数类型 +* DbHandle:要备份的数据库句柄 +* BackupFile:备份的保存位置 +*/ struct BackupStruct { DWORD DbHandle; char* BackupFile; }; int BackupSQLiteDB(DWORD DbHandle, const char* BackupFile); -extern "C" __declspec(dllexport) BOOL BackupSQLiteDBRemote(LPVOID lpParameter); \ No newline at end of file +extern "C" __declspec(dllexport) int BackupSQLiteDBRemote(LPVOID lpParameter); \ No newline at end of file diff --git a/DWeChatRobot/DbExecuteSql.cpp b/DWeChatRobot/DbExecuteSql.cpp index 0dc94ad..a853e91 100644 --- a/DWeChatRobot/DbExecuteSql.cpp +++ b/DWeChatRobot/DbExecuteSql.cpp @@ -1,7 +1,9 @@ #include "pch.h" +// sqlite3_exec函数偏移 #define sqlite3_execOffset 0x66176570 - 0x64E20000 +// sqlite3_callback函数指针 typedef int(*sqlite3_callback)( void*, int, @@ -9,6 +11,7 @@ typedef int(*sqlite3_callback)( char** ); +// sqlite3_exec函数指针 typedef int(__cdecl* Sqlite3_exec)( DWORD, /* The database on which the SQL executes */ const char*, /* The SQL to be executed */ @@ -18,13 +21,24 @@ typedef int(__cdecl* Sqlite3_exec)( ); DWORD WeChatWinBase = GetWeChatWinBase(); +// sqlite3_exec函数地址 DWORD sqlite3_execAddr = WeChatWinBase + sqlite3_execOffset; +/* +* 外部调用时传递的参数结构 +* ptrDb:数据库句柄 +* ptrSql:保存sql的地址 +*/ struct executeParams { DWORD ptrDb; DWORD ptrSql; }; +/* +* 保存查询结果的结构 +* ColName:字段名;l_ColName:`ColName`字符数 +* content:字段值;l_content:`content`字符数 +*/ struct SQLResultStruct { char* ColName; DWORD l_ColName; @@ -32,14 +46,24 @@ struct SQLResultStruct { DWORD l_content; }; +/* +* 外部调用时的返回类型 +* SQLResultAddr:`SQLResult`首成员地址 +* length:查询结果条数 +*/ struct executeResult { DWORD SQLResultAddr; DWORD length; }; +// 外部调用时的具体返回对象 executeResult result = { 0 }; +// 保存查询结果的二维动态数组 vector > SQLResult; +/* +* 获取数据库信息的回调函数 +*/ int GetDbInfo(void* data,int argc,char** argv,char** azColName) { DbInfoStruct* pdata = (DbInfoStruct*)data; TableInfoStruct tb = { 0 }; @@ -84,6 +108,9 @@ int GetDbInfo(void* data,int argc,char** argv,char** azColName) { return 0; } +/* +* DLL内部查询用的回调函数,直接显示查询结果,用处不大 +*/ int query(void* data, int argc, char** argv, char** azColName) { for (int i = 0; i < argc; i++) { string content = argv[i] ? UTF8ToGBK(argv[i]) : "NULL"; @@ -93,6 +120,10 @@ int query(void* data, int argc, char** argv, char** azColName) { return 0; } +/* +* 外部调用时使用的回调函数,将结果存入`SQLResult`中 +* return:int,执行成功返回`0`,执行失败返回非0值 +*/ int select(void* data, int argc, char** argv, char** azColName) { executeResult* pdata = (executeResult*)data; vector tempStruct; @@ -118,6 +149,10 @@ int select(void* data, int argc, char** argv, char** azColName) { return 0; } +/* +* 清空查询结果,释放内存 +* return:void +*/ void ClearResultArray() { if (SQLResult.size() == 0) return; @@ -140,12 +175,25 @@ void ClearResultArray() { result.length = 0; } +/* +* 执行SQL的入口函数 +* ptrDb:数据库句柄 +* sql:要执行的SQL +* callback:回调函数地址 +* data:传递给回调函数的参数 +* return:BOOL,执行成功返回`1`,执行失败返回`0` +*/ BOOL ExecuteSQL(DWORD ptrDb,const char* sql,DWORD callback,void* data) { Sqlite3_exec p_Sqlite3_exec = (Sqlite3_exec)sqlite3_execAddr; int status = p_Sqlite3_exec(ptrDb,sql, (sqlite3_callback)callback,data,0); return status == 0; } +/* +* 供外部调用的执行SQL接口 +* lpParameter:`executeParams`类型结构体指针 +* return:DWORD,如果SQL执行成功,返回`SQLResult`首成员地址,否则返回0 +*/ DWORD ExecuteSQLRemote(LPVOID lpParameter){ ClearResultArray(); executeParams* sqlparam = (executeParams*)lpParameter; diff --git a/DWeChatRobot/DbExecuteSql.h b/DWeChatRobot/DbExecuteSql.h index dad14b0..0e8ed19 100644 --- a/DWeChatRobot/DbExecuteSql.h +++ b/DWeChatRobot/DbExecuteSql.h @@ -6,4 +6,4 @@ int select(void* data, int argc, char** argv, char** azColName); int query(void* data, int argc, char** argv, char** azColName); extern "C" __declspec(dllexport) DWORD ExecuteSQLRemote(LPVOID lpParameter); -BOOL ExecuteSQL(DWORD ptrDb, const char* sql, DWORD callback, void* data); +BOOL ExecuteSQL(DWORD ptrDb, const char* sql, DWORD callback, void* data); \ No newline at end of file diff --git a/DWeChatRobot/FriendList.cpp b/DWeChatRobot/FriendList.cpp index a01c294..8c94442 100644 --- a/DWeChatRobot/FriendList.cpp +++ b/DWeChatRobot/FriendList.cpp @@ -1,9 +1,17 @@ #include "pch.h" #include -using namespace std; -#define LeftTreeOffset 0x222F3BC +// 通讯录左树偏移 +#define LeftTreeOffset 0x222F3BC +/* +* 保存单个好友信息的结构体 +* wxIdAddr:wxid保存地址 +* wxNumberAddr:微信号保存地址 +* wxNickNameAddr:昵称保存地址 +* wxRemarkAddr:备注保存地址 +* WxFriendStructW:默认构造函数 +*/ struct WxFriendStructW { DWORD wxIdAddr; DWORD wxNumberAddr; @@ -17,8 +25,13 @@ struct WxFriendStructW { } }; +// 保存所有好友信息的动态数组 vector WxFriendList; +/* +* 供外部调用的获取好友列表接口1 +* return:int,联系人数量 +*/ int GetFriendListInit() { GetFriendList(); #ifdef _DEBUG @@ -27,6 +40,10 @@ int GetFriendListInit() { return WxFriendList.size(); } +/* +* 供外部调用的获取好友列表接口2 +* return:DWORD,WxFriendList第一个成员地址 +*/ DWORD GetFriendListRemote() { if (WxFriendList.size() == 0) return 0; @@ -37,11 +54,19 @@ DWORD GetFriendListRemote() { return (DWORD)&WxFriendList[0].wxIdAddr; } +/* +* 供外部调用的获取好友列表接口3,清空缓存 +* return:void +*/ void GetFriendListFinish() { WxFriendList.clear(); cout << WxFriendList.size() << endl; } +/* +* 获取好友列表的具体实现 +* return:void +*/ void __stdcall GetFriendList() { #ifdef _DEBUG wcout.imbue(locale("chs")); diff --git a/DWeChatRobot/GetChatRoomMemebers.cpp b/DWeChatRobot/GetChatRoomMemebers.cpp index 9f9fbb8..77ed2c6 100644 --- a/DWeChatRobot/GetChatRoomMemebers.cpp +++ b/DWeChatRobot/GetChatRoomMemebers.cpp @@ -1,17 +1,34 @@ #include "pch.h" +// 获取群成员CALL1偏移 #define GetChatRoomMembersCall1Offset 0x6246BBB0 - 0x61E20000 +// 获取群成员CALL2偏移 #define GetChatRoomMembersCall2Offset 0x61EDF550 - 0x61E20000 +// 获取群成员CALL3偏移 #define GetChatRoomMembersCall3Offset 0x622046D0 - 0x61E20000 +// 清空缓存CALL偏移 #define DeleteGetChatRoomMembersCacheCallOffset 0x6246BDD0 - 0x61E20000 +/* +* 外部调用的返回类型 +* members:群成员wxid字符串,以`^`分隔 +* length:members字符串长度 +*/ struct ChatRoomInfoStruct { wchar_t* members = NULL; DWORD length = 0; }; +/* +* 外部调用时的具体返回对象 +*/ ChatRoomInfoStruct chatroominfo = { 0 }; +/* +* 供外部调用的获取群成员列表接口 +* lparameter:保存群聊ID的地址 +* return:DWORD,调用成功且群成员数量不为0,返回`chatroominfo`首地址,否则返回0 +*/ DWORD GetChatRoomMembersRemote(LPVOID lparameter) { wchar_t* chatroomid = (WCHAR*)lparameter; if (chatroominfo.members != NULL) { @@ -31,6 +48,11 @@ DWORD GetChatRoomMembersRemote(LPVOID lparameter) { return 0; } +/* +* 获取群成员列表的具体实现 +* chatroomid:群聊ID +* return:BOOL,成功返回`1`,失败返回`0` +*/ BOOL __stdcall GetChatRoomMembers(wchar_t* chatroomid) { DWORD WeChatWinBase = GetWeChatWinBase(); DWORD GetChatRoomMembersCall1 = WeChatWinBase + GetChatRoomMembersCall1Offset; diff --git a/DWeChatRobot/GetDbHandles.cpp b/DWeChatRobot/GetDbHandles.cpp index b19647c..adcd678 100644 --- a/DWeChatRobot/GetDbHandles.cpp +++ b/DWeChatRobot/GetDbHandles.cpp @@ -1,12 +1,18 @@ #include "pch.h" -// 联系人相关库 +// 联系人相关库偏移 #define SqlHandleMicroMsgOffset 0x222F3FC -// 公众号相关库 +// 公众号相关库偏移 #define SqlHandlePublicMsgOffset 0x22553D0 +// 保存数据库信息的容器 vector dbs; +/* +* 根据数据库名从`dbs`中检索数据库句柄 +* dbname:数据库名 +* return:DWORD,如果检索成功,返回数据库句柄,否则返回`0` +*/ DWORD GetDbHandleByDbName(wchar_t* dbname) { if (dbs.size() == 0) GetDbHandles(); @@ -17,12 +23,20 @@ DWORD GetDbHandleByDbName(wchar_t* dbname) { return 0; } +/* +* 供外部调用的获取数据库信息接口 +* return:DWORD,`dbs`首个成员地址 +*/ DWORD GetDbHandlesRemote() { if (dbs.size() == 0) GetDbHandles(); return (DWORD)dbs.data() ; } +/* +* 获取数据库信息的具体实现 +* return:void +*/ void GetDbHandles() { dbs.clear(); DWORD WeChatWinBase = GetWeChatWinBase(); diff --git a/DWeChatRobot/GetDbHandles.h b/DWeChatRobot/GetDbHandles.h index 10d7a0e..6698747 100644 --- a/DWeChatRobot/GetDbHandles.h +++ b/DWeChatRobot/GetDbHandles.h @@ -2,6 +2,13 @@ #include #include +/* +* 保存数据库单个表信息的结构体 +* name:表名;l_name:`name`字符数 +* tbl_name:表名;l_tbl_name:`tbl_name`字符数 +* sql:建表语句;l_sql:`sql`字符数 +* rootpage:表编号;l_rootpage:`rootpage`字符数 +*/ struct TableInfoStruct { char* name; DWORD l_name; @@ -13,6 +20,14 @@ struct TableInfoStruct { DWORD l_rootpage; }; +/* +* 保存数据库信息的结构体 +* handle:数据库句柄 +* dbname:数据库名 +* l_dbname:`dbname`字符数 +* tables:保存库中所有表信息的容器 +* count:库中表的数量 +*/ struct DbInfoStruct { DWORD handle; wchar_t* dbname; diff --git a/DWeChatRobot/LogMsgInfo.cpp b/DWeChatRobot/LogMsgInfo.cpp index 0391983..ce37a4d 100644 --- a/DWeChatRobot/LogMsgInfo.cpp +++ b/DWeChatRobot/LogMsgInfo.cpp @@ -1,16 +1,29 @@ #include "pch.h" +// 微信日志HOOK地址偏移 #define HookLogMsgInfoAddrOffset 0x103408A4 - 0x0FC40000 +// HOOK的CALL偏移 #define HookLogMsgInfoNextCallOffset 0x11586DFC - 0x0FC40000 +// HOOK的跳转地址偏移 #define HookLogMsgJmpBackOffset 0x103408A9 - 0x0FC40000 +// 微信日志HOOK地址 DWORD HookLogMsgInfoAddr = GetWeChatWinBase() + HookLogMsgInfoAddrOffset; +// HOOK的CALL地址 DWORD NextCallAddr = GetWeChatWinBase() + HookLogMsgInfoNextCallOffset; +// HOOK的跳转地址 DWORD JmpBackAddr = GetWeChatWinBase() + HookLogMsgJmpBackOffset; +// 是否开启日志HOOK标志 BOOL LogMsgHooked = false; +// 保存HOOK前的指令用于恢复 char LogOldAsmCode[5] = { 0 }; +/* +* 处理函数,打印日志信息 +* msg:日志信息 +* return:void +*/ VOID PrintMsg(DWORD msg) { if (!msg) return; @@ -19,6 +32,9 @@ VOID PrintMsg(DWORD msg) { return; } +/* +* HOOK的具体实现,拦截日志并调用处理函数 +*/ __declspec(naked) void doprintmsg(){ __asm { pushad; @@ -33,6 +49,10 @@ __declspec(naked) void doprintmsg(){ } } +/* +* 开始HOOK微信日志 +* return:void +*/ VOID HookLogMsgInfo() { if (LogMsgHooked) return; @@ -40,6 +60,10 @@ VOID HookLogMsgInfo() { LogMsgHooked = true; } +/* +* 停止HOOK微信日志 +* return:void +*/ VOID UnHookLogMsgInfo() { if (!LogMsgHooked) return; diff --git a/DWeChatRobot/ReceiveMessage.cpp b/DWeChatRobot/ReceiveMessage.cpp index da95bd7..d079c91 100644 --- a/DWeChatRobot/ReceiveMessage.cpp +++ b/DWeChatRobot/ReceiveMessage.cpp @@ -1,9 +1,19 @@ #include "pch.h" #include +// 接收消息的HOOK地址偏移 #define ReceiveMessageHookOffset 0x034A4F60 - 0x02FE0000 +// HOOK的CALL偏移 #define ReceiveMessageNextCallOffset 0x034A0CE0 - 0x02FE0000 +/* +* 保存单条信息的结构 +* messagetype:消息类型 +* sender:发送者wxid;l_sender:`sender`字符数 +* wxid:如果sender是群聊id,则此成员保存具体发送人wxid,否则与`sender`一致;l_wxid:`wxid`字符数 +* message:消息内容,非文本消息是xml格式;l_message:`message`字符数 +* filepath:图片、文件及其他资源的保存路径;l_filepath:`filepath`字符数 +*/ struct messageStruct { DWORD messagetype; wchar_t* sender; @@ -16,16 +26,27 @@ struct messageStruct { DWORD l_filepath; }; +// 保存多条信息的动态数组 vector messageVector; +// 是否开启接收消息HOOK标志 BOOL ReceiveMessageHooked = false; +// 保存HOOK前的字节码,用于恢复 char OldReceiveMessageAsmCode[5] = { 0 }; - +// 接收消息HOOK地址 DWORD ReceiveMessageHookAddress = GetWeChatWinBase() + ReceiveMessageHookOffset; +// HOOK的CALL地址 DWORD ReceiveMessageNextCall = GetWeChatWinBase() + ReceiveMessageNextCallOffset; +// HOOK的跳转地址 DWORD JmpBackAddress = ReceiveMessageHookAddress + 0x5; +/* +* 消息处理函数,根据消息缓冲区组装结构并存入容器 +* messageAddr:保存消息的缓冲区地址 +* return:void +*/ VOID ReceiveMessage(DWORD messageAddr) { + // 此处用于区别是发送的还是接收的消息,发送的消息会被过滤 DWORD isSendMessage = *(DWORD*)(messageAddr + 0x3C); if (isSendMessage) return; @@ -67,12 +88,20 @@ VOID ReceiveMessage(DWORD messageAddr) { messageVector.push_back(message); } +/* +* 供外部调用的获取消息接口,优先返回较早消息 +* return:DWORD,messageVector第一个成员地址 +*/ DWORD GetHeadMessage() { if (messageVector.size() == 0) return 0; return (DWORD)&messageVector[0].messagetype; } +/* +* 供外部调用的删除消息接口,用于删除messageVector第一个成员,每读一条需要执行一次 +* return:void +*/ VOID PopHeadMessage() { if (messageVector.size() == 0) return; @@ -88,6 +117,9 @@ VOID PopHeadMessage() { messageVector.erase(k); } +/* +* HOOK的具体实现,接收到消息后调用处理函数 +*/ _declspec(naked) void dealReceiveMessage() { __asm { pushad; @@ -103,6 +135,10 @@ _declspec(naked) void dealReceiveMessage() { } } +/* +* 开始接收消息HOOK +* return:void +*/ VOID HookReceiveMessage() { if (ReceiveMessageHooked) return; @@ -110,6 +146,10 @@ VOID HookReceiveMessage() { ReceiveMessageHooked = TRUE; } +/* +* 停止接收消息HOOK +* return:void +*/ VOID UnHookReceiveMessage() { if (!ReceiveMessageHooked) return; diff --git a/DWeChatRobot/SelfInfo.cpp b/DWeChatRobot/SelfInfo.cpp index 801b3cd..b5942d6 100644 --- a/DWeChatRobot/SelfInfo.cpp +++ b/DWeChatRobot/SelfInfo.cpp @@ -1,14 +1,23 @@ #include "pch.h" #include +// 保存个人信息的字符串 wstring selfinfo = L""; +/* +* 外部调用时的返回类型 +* message:selfinfo.c_str() +* length:selfinfo字符串长度 +*/ struct SelfInfoStruct { DWORD message; DWORD length; } ret; - +/* +* 供外部调用的获取个人信息接口 +* return:DWORD,ret的首地址 +*/ DWORD GetSelfInfoRemote() { DWORD WeChatWinBase = GetWeChatWinBase(); vector SelfInfoAddr = { @@ -90,6 +99,10 @@ DWORD GetSelfInfoRemote() { return (DWORD)&ret; } +/* +* 删除个人信息缓存 +* return:void +*/ VOID DeleteSelfInfoCacheRemote() { if (ret.length) { ZeroMemory((wchar_t*)ret.message, ret.length*2 + 2); diff --git a/DWeChatRobot/SendArticle.cpp b/DWeChatRobot/SendArticle.cpp index 54132ba..072b4b9 100644 --- a/DWeChatRobot/SendArticle.cpp +++ b/DWeChatRobot/SendArticle.cpp @@ -1,14 +1,28 @@ #include "pch.h" +// 发送文章CALL1偏移 #define SendArticleCall1Offset 0x0F7454F0 - 0x0F6B0000 +// 发送文章CALL2偏移 #define SendArticleCall2Offset 0x0FA41F80 - 0x0F6B0000 +// 发送文章CALL3偏移 #define SendArticleCall3Offset 0x0F7794A0 - 0x0F6B0000 +// 发送文章CALL4偏移 #define SendArticleCall4Offset 0x0FA42150 - 0x0F6B0000 +// 发送文章CALL参数偏移 #define SendArticleParamOffset 0x118EEC34 - 0x0F6B0000 +// 清空缓存CALL1偏移 #define SendArticleClearCacheCall1Offset 0x0FCEB4F0 - 0x0F6B0000 +// 清空缓存CALL2偏移 #define SendArticleClearCacheCall2Offset 0x0F744200 - 0x0F6B0000 +/* +* 外部调用时传递的参数结构 +* wxid:接收人的保存地址 +* title:文章标题的保存地址 +* abstract:文章摘要的保存地址 +* url:文章链接的保存地址 +*/ struct SendArticleStruct { DWORD wxid; DWORD title; @@ -16,6 +30,11 @@ struct SendArticleStruct { DWORD url; }; +/* +* 供外部调用的发送文章消息接口 +* lparameter:SendArticleStruct类型结构体指针 +* return:void +*/ VOID SendArticleRemote(LPVOID lparameter) { SendArticleStruct* sas = (SendArticleStruct*)lparameter; wchar_t* wxid = (wchar_t*)sas->wxid; @@ -25,6 +44,10 @@ VOID SendArticleRemote(LPVOID lparameter) { SendArticle(wxid,title,abstract,url); } +/* +* 获取自己的wxid保存地址 +* return:DWORD,个人wxid保存地址 +*/ DWORD GetSelfWxIdAddr() { DWORD baseAddr = GetWeChatWinBase() + 0x222EB3C; char wxidbuffer[0x100] = { 0 }; @@ -41,6 +64,14 @@ DWORD GetSelfWxIdAddr() { return SelfWxIdAddr; } +/* +* 发送文章消息的具体实现 +* wxid:消息接收人wxid +* title:文章标题 +* abstract:文章摘要 +* url:文章链接 +* return:BOOL,成功返回`1`,失败返回`0` +*/ BOOL __stdcall SendArticle(wchar_t* wxid,wchar_t* title, wchar_t* abstract, wchar_t* url) { DWORD WeChatWinBase = GetWeChatWinBase(); DWORD SendArticleCall1 = WeChatWinBase + SendArticleCall1Offset; diff --git a/DWeChatRobot/SendAtText.cpp b/DWeChatRobot/SendAtText.cpp index 1ec98b9..b9bfed3 100644 --- a/DWeChatRobot/SendAtText.cpp +++ b/DWeChatRobot/SendAtText.cpp @@ -1,22 +1,44 @@ #include "pch.h" +// 发送艾特消息CALL偏移 #define SendAtTextCallOffset 0x6782E7B0 - 0x67370000 +// 清空缓存CALL偏移 #define DeleteAtTextCacheCallOffset 0x67404200 - 0x67370000 +/* +* 外部调用时传递的参数结构 +* chatroomid:群聊ID的保存地址 +* wxidlist:艾特列表的保存地址,真实类型应当是`wchar_t**` +* wxmsg:发送的内容保存地址 +* length:艾特的人数量,用于指示wxidlist长度 +*/ struct SendAtTextStruct { DWORD chatroomid; - DWORD wxid; + DWORD wxidlist; DWORD wxmsg; DWORD length; }; +/* +* 内存中使用的参数结构 +* 构造与Release版本vector动态数组相仿 +* 成员类型:`WxString` +* AtUser:类似`vector`的`data`方法,保存数组首个成员的地址 +* addr_end1:数组尾地址 +* addr_end2:数组尾地址 +*/ struct AtStruct { DWORD AtUser; DWORD addr_end1; DWORD addr_end2; }; +/* +* 供外部调用的发送艾特消息接口 +* lpParameter:SendAtTextStruct类型结构体指针 +* return:void +*/ void SendAtTextRemote(LPVOID lpParameter) { SendAtTextStruct* rp = (SendAtTextStruct*)lpParameter; wchar_t* wsChatRoomId = (WCHAR*)rp->chatroomid; @@ -24,12 +46,21 @@ void SendAtTextRemote(LPVOID lpParameter) { if (rp->length == 0) return; else if(rp->length == 1) - SendAtText(wsChatRoomId, (DWORD*)&rp->wxid, wsTextMsg,rp->length); + SendAtText(wsChatRoomId, (DWORD*)&rp->wxidlist, wsTextMsg,rp->length); else - SendAtText(wsChatRoomId, (DWORD*)rp->wxid, wsTextMsg, rp->length); + SendAtText(wsChatRoomId, (DWORD*)rp->wxidlist, wsTextMsg, rp->length); } +/* +* 发送艾特消息的具体实现 +* wsChatRoomId:群聊ID +* wsWxId:艾特的人列表 +* wsTextMsg:发送的消息内容 +* length:艾特的人数量 +* return:void +*/ void __stdcall SendAtText(wchar_t* wsChatRoomId, DWORD wsWxId[], wchar_t* wsTextMsg,int length) { + // +1的作用是补充一个空结构体,将`AtStruct`尾地址设定为空结构的首地址即可 WxString* AtUsers = new WxString[length + 1]; wstring AtMessage = L""; int querySuccess = 0; diff --git a/DWeChatRobot/SendCard.cpp b/DWeChatRobot/SendCard.cpp index 76d37db..415ca4b 100644 --- a/DWeChatRobot/SendCard.cpp +++ b/DWeChatRobot/SendCard.cpp @@ -1,14 +1,27 @@ #include "pch.h" +// 发送名片的CALL偏移 #define SendCardCallOffset 0x644FE7B0 - 0x64040000 +// 清空缓存的CALL偏移 #define DeleteCardCacheCallOffset 0x640D4200 - 0x64040000 +/* +* 外部调用时提供的参数结构 +* receiver:名片消息接收人wxid保存地址 +* sharedwxid:被推荐人的wxid保存地址 +* nickname:名片显示的昵称保存地址 +*/ struct SendCardStruct { DWORD receiver; DWORD sharedwxid; DWORD nickname; }; +/* +* 供外部调用的发送名片接口 +* lparameter:SendCardStruct类型结构体指针 +* return:void +*/ VOID SendCardRemote(LPVOID lparameter) { SendCardStruct* scs = (SendCardStruct*)lparameter; wchar_t* receiver = (WCHAR*)scs->receiver; @@ -17,6 +30,13 @@ VOID SendCardRemote(LPVOID lparameter) { SendCard(receiver,sharedwxid,nickname); } +/* +* 发送名片消息的具体实现 +* receiver:消息接收人wxid +* sharedwxid:被推荐人wxid +* nickname:名片显示的昵称 +* return:BOOL,发送成功返回`0`,发送失败返回`1` +*/ BOOL __stdcall SendCard(wchar_t* receiver, wchar_t* sharedwxid, wchar_t* nickname) { DWORD WeChatWinBase = GetWeChatWinBase(); DWORD SendCardCall = WeChatWinBase + SendCardCallOffset; diff --git a/DWeChatRobot/SendFile.cpp b/DWeChatRobot/SendFile.cpp index e78d093..1ef04b6 100644 --- a/DWeChatRobot/SendFile.cpp +++ b/DWeChatRobot/SendFile.cpp @@ -1,18 +1,37 @@ #include "pch.h" +// 发送文件CALL1偏移 #define SendFileCall1Offset (0x67A71DC0 - 0x67370000) +// 发送文件CALL2偏移 #define SendFileCall2Offset (0x68D81C83 - 0x67370000) +// 发送文件CALL3偏移 #define SendFileCall3Offset (0x68D8047A - 0x67370000) +// 发送文件CALL4偏移 #define SendFileCall4Offset (0x67702260 - 0x67370000) +// 发送文件参数偏移 #define SendFileParamsOffset (0x6959F170 - 0x67370000) - +// 清空缓存CALL偏移 #define DeleteSendFileCacheCallOffset (0x67404200 - 0x67370000) +/* +* 外部调用时传递的参数结构 +* wxid:wxid的保存地址 +* filepath:文件绝对路径的保存地址 +*/ struct FileParamStruct { DWORD wxid; DWORD filepath; }; +/* +* 内存中使用的参数结构 +* type:消息类型,文件消息为3 +* buffer:文件绝对路径 +* length:绝对路径字符数 +* maxLength:绝对路径最大字节数 +* fill:占位用空缓冲区 +* WxFileStruct:默认构造函数 +*/ struct WxFileStruct { int type = 3; wchar_t* buffer; @@ -27,11 +46,22 @@ struct WxFileStruct { } }; +/* +* 供外部调用的发送文件消息接口 +* lpParamStruct:FileParamStruct类型结构体指针 +* return:void +*/ void SendFileRemote(LPVOID lpParamStruct) { FileParamStruct* params = (FileParamStruct*)lpParamStruct; SendFile((WCHAR*)params->wxid, (WCHAR*)params->filepath); } +/* +* 发送文件消息的具体实现 +* receiver:接收人wxid +* FilePath:文件绝对路径 +* return:void +*/ void __stdcall SendFile(wchar_t* receiver, wchar_t* FilePath) { WxBaseStruct pReceiver(receiver); WxBaseStruct pFilePath(FilePath); diff --git a/DWeChatRobot/SendImage.cpp b/DWeChatRobot/SendImage.cpp index fec61bb..fd8ca73 100644 --- a/DWeChatRobot/SendImage.cpp +++ b/DWeChatRobot/SendImage.cpp @@ -1,20 +1,40 @@ #include "pch.h" +// 发送图片CALL1偏移 #define SendImageCall1Offset (0x6740A1C0 - 0x67370000) +// 发送图片CALL2偏移 #define SendImageCall2Offset (0x67A71DC0 - 0x67370000) +// 发送图片CALL3偏移 #define SendImageCall3Offset (0x6782E160 - 0x67370000) +// 清空缓存的CALL偏移 #define DeleteSendImageCacheCallOffset (0x67404200 - 0x67370000) +/* +* 外部调用时传递的参数结构 +* wxid:保存wxid的地址 +* imagepath:保存图片绝对路径的地址 +*/ struct ImageParamStruct { DWORD wxid; DWORD imagepath; }; +/* +* 供外部调用的发送图片消息接口 +* lpParamStruct:ImageParamStruct类型结构体指针 +* return:void +*/ void SendImageRemote(LPVOID lpParamStruct) { ImageParamStruct* params = (ImageParamStruct*)lpParamStruct; SendImage((WCHAR*)params->wxid, (WCHAR*)params->imagepath); } +/* +* 发送图片消息的具体实现 +* receiver:接收人wxid +* ImagePath:图片绝对路径 +* return:void +*/ void __stdcall SendImage(wchar_t* receiver, wchar_t* ImagePath) { DWORD WeChatWinBase = GetWeChatWinBase(); DWORD SendImageCall1 = WeChatWinBase + SendImageCall1Offset; diff --git a/DWeChatRobot/SendText.cpp b/DWeChatRobot/SendText.cpp index 16eb053..67a3988 100644 --- a/DWeChatRobot/SendText.cpp +++ b/DWeChatRobot/SendText.cpp @@ -1,14 +1,26 @@ #include "pch.h" +// 发送文本消息的CALL偏移 #define SendTextCallOffset 0x6782E7B0 - 0x67370000 +// 清空缓存的CALL偏移 #define DeleteTextCacheCallOffset 0x67404200 - 0x67370000 +/* +* 外部调用时传递的参数结构 +* wxid:wxid保存地址 +* wxmsg:发送的内容保存地址 +*/ struct SendTextStruct { DWORD wxid; DWORD wxmsg; }; +/* +* 供外部调用的发送文本消息接口 +* lpParameter:SendTextStruct类型结构体指针 +* return:void +*/ void SendTextRemote(LPVOID lpParameter) { SendTextStruct* rp = (SendTextStruct*)lpParameter; wchar_t* wsWxId = (WCHAR*)rp->wxid; @@ -16,6 +28,12 @@ void SendTextRemote(LPVOID lpParameter) { SendText(wsWxId, wsTextMsg); } +/* +* 发送文本消息的具体实现 +* wsWxId:接收人wxid +* wsTextMsg:发送的消息内容 +* return:void +*/ void __stdcall SendText(wchar_t* wsWxId, wchar_t* wsTextMsg) { WxBaseStruct wxWxid(wsWxId); WxBaseStruct wxTextMsg(wsTextMsg); diff --git a/DWeChatRobot/UserInfo.cpp b/DWeChatRobot/UserInfo.cpp index 65bad3e..ae45858 100644 --- a/DWeChatRobot/UserInfo.cpp +++ b/DWeChatRobot/UserInfo.cpp @@ -3,22 +3,40 @@ #include #include +// 获取好友信息CALL0偏移 #define GetUserInfoCall0Offset 0x6740A000 - 0x67370000 +// 获取好友信息CALL1偏移 #define GetUserInfoCall1Offset 0x679C9840 - 0x67370000 +// 获取好友信息CALL2偏移 #define GetUserInfoCall2Offset 0x67A71DC0 - 0x67370000 +// 获取好友信息CALL3偏移 #define GetUserInfoCall3Offset 0x677724A0 - 0x67370000 +// 清空缓存CALL1偏移 #define DeleteUserInfoCacheCall1Offset 0x67775990 - 0x67370000 +// 清空缓存CALL2偏移 #define DeleteUserInfoCacheCall2Offset 0x679CA340 - 0x67370000 +/* +* 外部调用时的返回类型 +* message:wUserInfo.c_str() +* length:wUserInfo字符串长度 +*/ struct GetUserInfoStruct { DWORD message; DWORD length; }; +// 保存好友信息的字符串 wstring wUserInfo = L""; +// 外部调用时的具体返回对象 GetUserInfoStruct ret = { 0 }; +/* +* 根据缓冲区内容拼接好友信息 +* address:缓冲区地址 +* return:void +*/ VOID WxUserInfo(DWORD address) { vector InfoType{ address + 0x10, @@ -64,7 +82,11 @@ VOID WxUserInfo(DWORD address) { #endif } - +/* +* 供外部调用的获取好友信息接口 +* lparamter:保存好友wxid的地址 +* return:DWORD,`ret`的首地址 +*/ DWORD GetWxUserInfoRemote(LPVOID lparamter) { wchar_t* userwxid = (wchar_t*)lparamter; @@ -76,6 +98,10 @@ DWORD GetWxUserInfoRemote(LPVOID lparamter) { return (DWORD)&ret; } +/* +* 供外部调用的清空好友信息缓存的接口 +* return:void +*/ VOID DeleteUserInfoCacheRemote() { if (ret.length) { ZeroMemory((wchar_t*)ret.message, ret.length * 2 + 2); @@ -84,6 +110,11 @@ VOID DeleteUserInfoCacheRemote() { } } +/* +* 根据wxid获取好友信息的具体实现 +* wxid:好友wxid +* return:BOOL,成功返回`1`,失败返回`0` +*/ BOOL __stdcall GetUserInfoByWxId(wchar_t* wxid) { DWORD WeChatWinBase = GetWeChatWinBase(); DWORD WxGetUserInfoCall0 = WeChatWinBase + GetUserInfoCall0Offset; @@ -131,6 +162,11 @@ BOOL __stdcall GetUserInfoByWxId(wchar_t* wxid) { return isSuccess; } +/* +* 根据wxid获取联系人昵称,主要用于发送艾特消息接口 +* wxid:联系人wxid +* return:wchar_t*,获取到的wxid +*/ wchar_t* __stdcall GetUserNickNameByWxId(wchar_t* wxid) { DWORD WeChatWinBase = GetWeChatWinBase(); DWORD WxGetUserInfoCall0 = WeChatWinBase + GetUserInfoCall0Offset; diff --git a/DWeChatRobot/pch.cpp b/DWeChatRobot/pch.cpp index 11559fb..fcdfbb8 100644 --- a/DWeChatRobot/pch.cpp +++ b/DWeChatRobot/pch.cpp @@ -3,6 +3,11 @@ #include "pch.h" // 褰撲娇鐢ㄩ缂栬瘧鐨勫ご鏃讹紝闇瑕佷娇鐢ㄦ婧愭枃浠讹紝缂栬瘧鎵嶈兘鎴愬姛銆 + +/* +* 鍒涘缓涓涓帶鍒跺彴绐楀彛 +* return锛欱OOL锛屾垚鍔熻繑鍥瀈0`锛屽け璐ヨ繑鍥瀈1` +*/ BOOL CreateConsole(void) { if (AllocConsole()) { AttachConsole(GetCurrentProcessId()); @@ -16,10 +21,17 @@ BOOL CreateConsole(void) { return 1; } +/* +* 鑾峰彇`WeChatWin.dll`鍩哄潃 +* return锛欴WORD锛宍WeChatWin.dll`妯″潡鍩哄潃 +*/ DWORD GetWeChatWinBase() { return (DWORD)GetModuleHandleA("WeChatWin.dll"); } +/* +* 灏嗗瀛楄妭瀛楃涓茶浆鎹㈡垚`std::string` +*/ void Wchar_tToString(std::string& szDst, wchar_t* wchar) { wchar_t* wText = wchar; @@ -31,6 +43,9 @@ void Wchar_tToString(std::string& szDst, wchar_t* wchar) delete[]psText;// psText鐨勬竻闄 } +/* +* 灏哢TF8缂栫爜鏁版嵁杞崲涓篏BK缂栫爜 +*/ string UTF8ToGBK(const std::string& strUTF8) { int len = MultiByteToWideChar(CP_UTF8, 0, strUTF8.c_str(), -1, NULL, 0); @@ -49,6 +64,13 @@ string UTF8ToGBK(const std::string& strUTF8) return strTemp; } +/* +* 瀵逛换鎰忓湴鍧娣诲姞HOOK +* dwHookAddr锛欻OOK鐨勭洰鏍囧湴鍧 +* dwJmpAddress锛氳烦杞埌鐨勫湴鍧 +* originalRecieveCode锛氫繚瀛樻棫鎸囦护鐨勬暟缁 +* return锛歷oid +*/ void HookAnyAddress(DWORD dwHookAddr, LPVOID dwJmpAddress,char* originalRecieveCode) { //缁勮璺宠浆鏁版嵁 @@ -72,6 +94,12 @@ void HookAnyAddress(DWORD dwHookAddr, LPVOID dwJmpAddress,char* originalRecieveC VirtualProtect((LPVOID)dwHookAddr, 5, OldProtext, &OldProtext); } +/* +* 瀵逛换鎰忓湴鍧鍙栨秷HOOK +* dwHookAddr锛欻OOK鐨勭洰鏍囧湴鍧 +* originalRecieveCode锛氫繚瀛樻棫鎸囦护鐨勬暟缁 +* return锛歷oid +*/ void UnHookAnyAddress(DWORD dwHookAddr, char* originalRecieveCode) { DWORD OldProtext = 0; @@ -80,11 +108,23 @@ void UnHookAnyAddress(DWORD dwHookAddr, char* originalRecieveCode) VirtualProtect((LPVOID)dwHookAddr, 5, OldProtext, &OldProtext); } +/* +* 鍙栨秷鎵鏈塇OOK +* return锛歷oid +*/ void UnHookAll() { UnHookLogMsgInfo(); + UnHookReceiveMessage(); return; } +/* +* 灏嗗崟瀛楃鏇挎崲涓烘寚瀹氱殑瀛楃涓 +* source锛氭簮瀛楃涓 +* replaced锛氳鏇挎崲鐨勫崟瀛楃 +* replaceto锛氭浛鎹㈡垚鐨勫瓧绗︿覆 +* return锛歴td::wstring锛屾浛鎹㈠悗鐨勫瓧绗︿覆 +*/ wstring wreplace(wstring source, wchar_t replaced, wstring replaceto) { wstring temp = L""; wchar_t* buffer = (wchar_t*)source.c_str(); diff --git a/DWeChatRobot/pch.h b/DWeChatRobot/pch.h index 66572a6..71fa50c 100644 --- a/DWeChatRobot/pch.h +++ b/DWeChatRobot/pch.h @@ -31,8 +31,18 @@ #endif //PCH_H using namespace std; +// 瀵逛簬瀵煎嚭鍑芥暟锛岄渶瑕佷娇鐢ㄦ瀹忎慨楗 #define DLLEXPORT extern "C" __declspec(dllexport) +/* +* 寰俊涓殑鍩虹鏁版嵁缁撴瀯 +* buffer锛歎NICODE瀛楃涓 +* length锛歚buffer`瀛楃鏁 +* maxLength锛歚buffer`鏈澶у瓧绗︽暟 +* fill1锛氬崰浣嶆垚鍛1锛岄粯璁や负0 +* fill2锛氬崰浣嶆垚鍛2锛岄粯璁や负0 +* WxBaseStruct锛氶粯璁ゆ瀯閫犲嚱鏁 +*/ struct WxBaseStruct { wchar_t* buffer; @@ -50,6 +60,9 @@ struct WxBaseStruct } }; +/* +* 涓嶄娇鐢ㄦ瀯閫犲嚱鏁扮殑寰俊鍩虹鏁版嵁缁撴瀯锛屼娇鐢ㄩ鐜囪緝浣 +*/ struct WxString { wchar_t* buffer = NULL; diff --git a/Release/DWeChatRobot.dll b/Release/DWeChatRobot.dll index 20777a754d1dd8fc530cbeb04982661d995221b9..e39603f874cbe84dfbd39094fbcb5f77cbbf0c45 100644 GIT binary patch delta 3844 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 delta 3744 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 TE*I26u2zK9+ifA*|GR$!tqC(Z -- GitLab