自從聊天軟件消息撤回功能問世后,對於撤回的消息,我們對它一直有種強烈的好奇感。“Ta剛撤回了什么?是罵我的話?還是說喜歡我?還是把發給其他人的消息誤發給了我?好氣呀,都看不到了...”這是我們看到消息被撤回后的內心獨白。但是今天,看完了本篇文章你就可以說:
我們看一下效果圖,撤回的消息被我們看到了,相當於防(防止)撤回
好了,看完效果,接下來我們看一下怎么找到它的位置,並用代碼hook它。
本文用到的軟件工具:
- 微信 2.8.0.121
- Cheat Engine 7.0(用於內存搜索,下文簡稱CE)
- Ollydbg吾愛破解版(用於動態調試,下文簡稱OD)
- Visual Studio 2017(用於編寫Hook代碼,下文簡稱VS)
用CE打開微信進程
用另一個微信號給在電腦登錄的微信號隨機發一條消息,勾選UTF-16選項,然后在CE中搜索消息內容
撤回消息,看到一條xml消息,雙擊它添加到地址列表
打開OD,附加微信進程,用dd命令定位到上面的那個地址
再給電腦登錄那個微信號發一條消息,然后在上面那個地址下內存寫入斷點。為什么是內存寫入斷點?因為我們上文親眼目睹了消息撤回后這個地址的字符串被改寫了。
下好斷點后再把消息撤回,此時斷點被觸發,微信被斷下,斷下后,刪除內存斷點。在棧里尋找我們想要的內容,看到一個包含撤回提示,wxid和撤回內容的call
在反匯編窗口中跟隨這個call,點擊這個call,按F2在該call下斷點,按F9繼續運行。再給在電腦登錄那個微信號發一條消息並撤回,該call斷下
說明,這個call就是我們要找的消息撤回的位置,而且它有我們想要的數據
找到了call,來整理一下該call的數據,數據都是存在棧里,所以有:
- esp + 0x4 :撤回消息的提示
- esp + 0x50 : 撤回消息人的wxid
- esp + 0x78 : 撤回消息的內容
接下來編寫一個dll,來hook這個call,讀取esp偏移地址的數據。
在VS創建一個dll項目,核心代碼如下:
#include "resource.h"
#include "hook.h"
#include "module.h"
#include <wchar.h>
INT_PTR CALLBACK Dlgproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void DlgThread(HMODULE hInstance);
#define REVOCK_CALL_RVA 0x28c33f
#define REVOCK_CALL_TARGET_RVA 0x28ccd0
DWORD revockCallVA = 0;
DWORD revockCallTargetVA = 0;
DWORD revockCallJmpBackVA = 0;
DWORD wechatWinAddr = 0;
BYTE backCode[5];
HWND m_dialog_hwnd;
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
CreateThread(NULL, 0, LPTHREAD_START_ROUTINE(DlgThread), hModule, 0, NULL);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
void OnRevock(DWORD esp) {
wchar_t *tips = *(wchar_t **)(esp + 0x4);
wchar_t *msg = *(wchar_t **)(esp + 0x78);
if (NULL != tips) {
WCHAR buffer[0x8192];
wchar_t* pos = wcsstr(tips, L"撤回了一條消息");
if (pos!= NULL && NULL != msg) {
swprintf_s(buffer, L"%s,內容:%s",tips, msg);
SetDlgItemText(m_dialog_hwnd, IDC_EDIT1, buffer);
}
}
}
DWORD tEsp = 0;
_declspec(naked) void _OnRevock() {
__asm {
mov tEsp, esp
pushad
}
OnRevock(tEsp);
__asm {
popad
call revockCallTargetVA
jmp revockCallJmpBackVA
}
}
void DlgThread(HMODULE hInstance) {
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, Dlgproc);
}
INT_PTR CALLBACK Dlgproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_INITDIALOG: {
m_dialog_hwnd = hWnd;
wechatWinAddr = GetWxModuleAddress();
revockCallVA = wechatWinAddr + REVOCK_CALL_RVA;
revockCallTargetVA = wechatWinAddr + REVOCK_CALL_TARGET_RVA;
revockCallJmpBackVA = revockCallVA + 5;
StartHook5(wechatWinAddr+REVOCK_CALL_RVA,backCode,_OnRevock);
}
break;
case WM_CLOSE:
Unhook5(wechatWinAddr + REVOCK_CALL_RVA, backCode);
EndDialog(hWnd, TRUE);
break;
}
return 0;
}
Hook相關代碼:
int StartHook5(DWORD hookAddr, BYTE backCode[5], void(*FuncBeCall)()) {
DWORD jmpAddr = (DWORD)FuncBeCall - (hookAddr + 5);
BYTE jmpCode[5];
*(jmpCode + 0) = 0xE9;
*(DWORD *)(jmpCode + 1) = jmpAddr;
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, NULL, GetCurrentProcessId());
//備份被替換的
if (ReadProcessMemory(hProcess, (LPVOID)hookAddr, backCode, 5, NULL) == 0) {
return -1;
}
//寫入jmp指令
if (WriteProcessMemory(hProcess, (LPVOID)hookAddr, jmpCode, 5, NULL) == 0) {
return -1;
}
return 0;
}
int Unhook5(DWORD hookAddr, BYTE backCode[5]) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, NULL, GetCurrentProcessId());
if (WriteProcessMemory(hProcess, (LPVOID)hookAddr, backCode, 5, NULL) == 0) {
return -1;
}
return 0;
}
代碼寫完后,生成dll,把它注入到微信進程,防撤回消息就能實現了。
總結:微信版本一直會變化,相應的hook地址也會改變,但是有了這個思路,它更新版本,我們也能快速的找到call。這個消息撤回的call也比較好找,像我這樣初學逆向的朋友可以嘗試自己找一下。視頻教程:https://www.bilibili.com/video/BV1WE411V7ff