前言
最近玩微信的时候发现微信程序较小,突然感觉想分析一波微信,使用查壳工具发现程序没加壳,那么微信程序没有受到PE级上的保护,可能对dll进行了代码级保护,也就是加了VM,分析的主要内容如下:
1.使用HOOK技术实现微信登入界面多开
2.自编写劫持补丁实现消息防撤回
3.使用idapython脚本提取登入二维码图片
1.实现微信登入界面多开
微信可谓是家喻户晓,但是在电脑上只能登入一个微信号,对于某些人而言非常不方便,接着我来进行多开破解。
1.OD加载前先进行设置:Option-》debugg option-》events-》WInMain,程序从WinMain函数开始运行。
2.f9运行微信,暂停,打开堆栈窗口,进入GetMessageW函数,再进入NtUserGetMessage函数,在函数头部下断,在头部断下后,往下看堆栈,可以看到BaseThreadInitThunk函数,貌似跟线程初始化有关,在头部下断
3.再加载程序断在断点处,接着不断单步,可以看到CreateMutexW函数,说明程序是用该函数实现单开功能。创建互斥体的函数有CreateMutexA和CreateMutexW,前者是针对ASCII版,后者是针对Unicode版
4.接着看看CreateMutexW函数,该函数可以创建互斥体,第一个参数是SECURITY_ATTRIBUTES结构体,该结构体包含对象的安全描述符,可以指定此结构检索的句柄是否可继承和为各种对象(例如:文件、进程等)提供安全设置;第二个参数表示创建的互斥锁是否可被当前线程持有,第三个参数表示互斥对象名
[C] 纯文本查看 复制代码 CreateMutexW(
_In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes,
_In_ BOOL bInitialOwner,
_In_opt_ LPCWSTR lpName
);
5.在CreateMutexW函数下断,接着单步,发现程序有几次停在CreateMutexW函数处,CreateMutexW函数运行几次后发现微信的互斥对象名为_WeChat_App_Instance_Identity_Mutex_Name,这时再单步。
6.单步到je short WeChatWi.7B2FAE6F ,发现这是关键跳,跳不跳转由CreateMutexW函数返回值决定,修改zf标志寄存器的值时,发现窗口没有弹出,接着又执行CreateMutexW函数,这时又出现了微信的互斥对象名,说明这存在循环结构,不断判断是否存在微信的互斥对象名,存在则跳出。
7.那么把je修改为jmp,就可以实现多开破解,在OD中je跳转指令的地址为7B2FAE17,在OD中可以看出该跳转指令在WeChatWin模块中,在内存窗口看出基址为7AB10000,那么je的偏移为0x7EAE17.
8.使用HOOK技术来实现内存修改,dll的实现思路是:获取微信进程句柄和WeChatWin模块的地址,根据偏移找到跳转指令地址,随后进行内存写入,代码如下:
[C++] 纯文本查看 复制代码 HMODULE GetProcessModuleHandle(DWORD pid, string moduleName)
{
MODULEENTRY32 moduleEntry;
HANDLE handle = NULL;
handle = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
//用0填充内存区域
ZeroMemory(&moduleEntry, sizeof(MODULEENTRY32));
moduleEntry.dwSize = sizeof(MODULEENTRY32);
//找到进程中的WeChatWin.dll基址
if (Module32First(handle, &moduleEntry))
{
do
{
string szMod = moduleEntry.szModule;
if (szMod == moduleName)
{
return moduleEntry.hModule;
}
} while (Module32Next(handle, &moduleEntry));
}
CloseHandle(handle);
return 0;
}
DWORD WINAPI ThreadProc(LPVOID lParam)
{
//获取微信的pid
int AA = GetCurrentProcessId();
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, AA);
//写入的ASCII值:je->jmp
BYTE buff[] = { 0xEB };
//跳转指令的地址,0x7EAE17为偏移值
DWORD ProModAddr = (DWORD)GetProcessModuleHandle(AA, "WeChatWin.dll") + 0x7EAE17;
//写入内存
if (WriteProcessMemory(hProcess, (LPVOID)ProModAddr, buff, 1, NULL))
MessageBox(NULL, TEXT("写入!"), TEXT("标题"), MB_OKCANCEL);
else
MessageBox(NULL, TEXT("没有写入!"), TEXT("标题"), MB_OKCANCEL);
return 0;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
HANDLE hThread = NULL;
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
OutputDebugString("<Duokai.dll> Injection!!!");
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
CloseHandle(hThread);
break;
}
return TRUE;
}
运行结果如下:
8.由于dll需要注入工具,我又写了一个注入工具(终端版),注入前要把注入工具放到dll路径下,代码如下:
[C++] 纯文本查看 复制代码 bool setPriority(LPCTSTR lpszPrivilege)
{
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
HANDLE hToken;
LUID luid;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
return false;
//查询权限值
if (!LookupPrivilegeValue(NULL, lpszPrivilege, &luid))
return false;
//调整权限
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL))
return false;
return true;
}
DWORD GetWXPid()
{
DWORD Pid = 0;
PROCESSENTRY32 pe;
HANDLE hShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
pe.dwSize = sizeof(PROCESSENTRY32);
wchar_t *targetFile = L"WeChat.exe";
//找到微信进程
if (Process32First(hShot, &pe))
{
do
{
if (wcsstr(targetFile, pe.szExeFile))
{
Pid = pe.th32ProcessID;
break;
}
} while (Process32Next(hShot, &pe));
}
CloseHandle(hShot);
return Pid;
}
bool InjectDll(DWORD Pid)
{
GetCurrentDirectory(MAX_PATH, szPath);
wcscat_s(szPath, L"\\DuokaiDll.dll");
HANDLE hRemPro, hThread = NULL;
HMODULE hMod = NULL;
LPVOID MemAll=NULL;
LPTHREAD_START_ROUTINE pThreadProc;
//获取进程句柄
hRemPro = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
//在进程中申请一块虚拟内存区域
MemAll = VirtualAllocEx(hRemPro, NULL, (wcslen(szPath) + 1)*sizeof(wchar_t), MEM_COMMIT, PAGE_READWRITE);
if (!MemAll)
cout << "MemAll\n";
//往虚拟内存区域写入dll路径
WriteProcessMemory(hRemPro, MemAll, (void*)szPath, wcslen(szPath)*sizeof(wchar_t), NULL);
//创建线程
hMod = GetModuleHandle(L"Kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");
//注入dll
hThread = CreateRemoteThread(hRemPro, NULL, 0, pThreadProc, MemAll, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hRemPro);
return true;
}
int _tmain(int argc, _TCHAR* argv[])
{
setPriority(SE_DEBUG_NAME);
while (1)
{
DWORD pid = GetWXPid();
if (pid != 0)
{
InjectDll(pid);
break;
}
}
return 0;
}
9.前面提到微信的互斥对象名为_WeChat_App_Instance_Identity_Mutex_Name,由于该对象名唯一,修改之后也能实现双开,由于该字符串为Unicode编码,因此直接用CE搜索不到该字符串,接着对该字符串进行转码,转成可搜索的十六进制数组。
10.使用CE搜索字节数组,发现只有一条记录,接着移到下面,可以看到它的地址,在OD中查看该地址,可以看到该字符串
11.接着修改字符串的数值(摁enter),在OD中可以看到修改后的结果,这时就可以实现多开了。
12.同样用C++实现一个修改该字符串的程序,在程序中,我把W修改为a,不过这种方式,有个弊端就是不能通过程序来获取字符串地址,只能在已知的情况下进行修改,实现的代码如下:
[C++] 纯文本查看 复制代码 DWORD GetWXPid()
{
PROCESSENTRY32 pe;
HANDLE hShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
pe.dwSize = sizeof(PROCESSENTRY32);
string targetFile = "WeChat.exe";
while (1)
{
if (Process32First(hShot, &pe))
{
do
{
string szExeF=pe.szExeFile;
if (szExeF==targetFile)
{
DWORD Pid = pe.th32ProcessID;
return Pid;
}
} while (Process32Next(hShot, &pe));
}
}
CloseHandle(hShot);
}
int main()
{
int pid=GetWXPid();
HANDLE hpro = OpenProcess(PROCESS_ALL_ACCESS, TRUE, pid);
byte buff[] = { 0x61};
if(WriteProcessMemory(hpro, (LPVOID)0x0144EC38, buff, 1, NULL))
cout<<"SUCCESS\n";
else
cout<<"FAIL\n";
system("pause");
return 0;
}
2.实现消息防撤回
1.运行微信,往对方发送hello world2消息,接着打开内存窗口,搜索该字符串,成功找到该字符串,在内存中下内存防问断点,单步运行,发现出现了revokemsg字符串。
2.随后撤回该消息,发现断了下来,接着单步,发现了一个跟生成数据库语句有关的函数,在这下断之后发现,刚登入时,程序就断在这,在堆栈中看出它在进行数据库的升级操作,从使用的sql语句也可以看出微信使用的数据库为Sqlite,运行的过程中看到很多查询语句,微信中很多信息(例如:图标、好友微信号等)都存储在数据库中。那么发送消息之后,该消息会存储在数据库中,撤回消息时会对数据库进行删除操作并通过网络通信,把对方数据库中的信息进行删除。
3.之后,我接着下内存断点结果没有断下,感觉微信程序应该加了内存检验功能,由于撤销消息时会显示"**撤回一条消息",在OD中搜索该字符串,没有搜索到.接着使用ida进行搜索,那么要先配置好ida中文搜索功能,选中ida图标,右键-》属性-》目标栏后面填入-dCULTURE=all,接着加载WeChatWin.dll,在ida中也没搜索出来,接着在ida中搜索revokemsg字符串,看到"On RevokeMsg svrId : %d",感觉很特别,进入ida中,看到比较关键的函数sub_1028BC70,当该函数的返回值为1时,则会撤回消息。
[C++] 纯文本查看 复制代码
if ( (unsigned __int8)sub_1028BC70((wchar_t *)v243, (int)v244, (int)v245, v246) )
{
*(_OWORD *)&v244 = xmmword_113688E0;
v242 = xmmword_113688E0;
v241 = xmmword_113688E0;
v240 = xmmword_113688E0;
*(_OWORD *)&v236 = xmmword_113688E0;
sub_1005BE40(v373, HIDWORD(v373));
sub_104DE100(
(int)"02_manager\\SyncMgr.cpp",
2,
1388,
(int)"SyncMgr::doAddMsg",
(int)"SyncMgr",
"On RevokeMsg svrId : %d",
v232,
(int)v233,
(int)v234,
v235,
xmmword_113688E0,
4.在ida中计算出该函数的偏移,随后在OD中下断,断后返回上一级函数,发现把该函数NOP掉就能实现消息防撤回。
5.接下来自编写硬件补丁来实现破解,在内存窗口中可以看到,微信程序加载了winspool.drv,该文件为缓冲池驱动,接着修改该文件,生成一个新的winspool.drv文件并放到微信程序目录,由于程序是根据就近原则加载驱动的,所以能达到劫持的效果。
6.使用AheadLib工具打开系统盘中的winspool.drv,解析出实现的代码,在 VS中新建名为winspool的dll项目(在项目属性页,把文件的生成后缀名改为.drv),在该项目中,把源文件目录下的所有文件删除,把解析出来的winspool.cpp复制粘贴到源文件中。
7.找到Load()函数,在里面写劫持代码,并且加上一些功能函数,由于当微信加载WeChatWin.dll时,所以使用无限循环来进行判断,当加载时,在进行硬件补丁设置。代码如下:
[C++] 纯文本查看 复制代码 HMODULE GetProcessModuleHandle(DWORD pid, string moduleName)
{
MODULEENTRY32 moduleEntry;
HANDLE handle = NULL;
handle = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
//用0填充内存区域
ZeroMemory(&moduleEntry, sizeof(MODULEENTRY32));
moduleEntry.dwSize = sizeof(MODULEENTRY32);
//找到进程中的WeChatWin.dll基址
if (Module32First(handle, &moduleEntry))
{
do
{
string szMod = moduleEntry.szModule;
if (szMod == moduleName)
{
return moduleEntry.hModule;
}
} while (Module32Next(handle, &moduleEntry));
}
CloseHandle(handle);
return 0;
}
DWORD GetAddr()
{
int Pid = GetCurrentProcessId();
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
while (1)
{
DWORD Maddr = (DWORD)GetProcessModuleHandle(Pid, "WeChatWin.dll");
if (!Maddr)
{
continue;
}
//0x351098 为关键函数在dll中的偏移
DWORD Addr = Maddr + 0x351098;
return Addr;
}
return 0;
}
//异常捕捉
DWORD NTAPI ExceptionHandler(EXCEPTION_POINTERS * ExceptionInfo)
{
if ((DWORD)ExceptionInfo->ExceptionRecord->ExceptionAddress == RealAddr)
{
ExceptionInfo->ContextRecord->Eip += 5;
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
//设置硬件断点
void SetHwBreakPoint(HANDLE THREAD)
{
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_ALL;
GetThreadContext(THREAD, &ctx);
ctx.Dr0 = RealAddr;
ctx.Dr7 = 0x01;
SetThreadContext(THREAD, &ctx);
}
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, TRUE, (DWORD)lpParameter);
SuspendThread((HANDLE)hThread);
SetHwBreakPoint(hThread);
ResumeThread((HANDLE)hThread);
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// AheadLib 命名空间
namespace AheadLib
{
HMODULE m_hModule = NULL; // 原始模块句柄
DWORD m_dwReturn[229] = {0}; // 原始函数返回地址
// 加载原始模块
inline BOOL WINAPI Load()
{
TCHAR tzPath[MAX_PATH] = {0};
TCHAR tzTemp[MAX_PATH * 2];
//是否是WOW64位子系统下运行
BOOL isWow64Process = FALSE;
//判断是否为 WOW64子系统下的进程
//返回是否执行成功
if (IsWow64Process((HANDLE)-1, &isWow64Process))
{
if (isWow64Process)
{
GetSystemWow64Directory(tzPath,MAX_PATH);
}
else
{
GetSystemDirectory(tzPath, MAX_PATH);
}
}
AddVectoredExceptionHandler(1,(PVECTORED_EXCEPTION_HANDLER)ExceptionHandler);
int AA = GetCurrentThreadId();
HANDLE Thread = CreateThread(NULL, 0, ThreadProc, (LPVOID)GetCurrentThreadId(), 0, NULL);
lstrcat(tzPath, TEXT("\\winspool.drv"));
m_hModule = LoadLibrary(tzPath);
if (m_hModule == NULL)
{
wsprintf(tzTemp, TEXT("无法加载 %s,程序无法正常运行。"), tzPath);
MessageBox(NULL, tzTemp, TEXT("AheadLib"), MB_ICONSTOP);
}
return (m_hModule != NULL);
}
3.提取登入二维码图片
1.在逆向分析微信登入二维码,先在浏览器上打开微信页面登入界面,按f12,查看页面源码,发现一个png图片链接,打开发现是微信的图标照片
2.再往下可以看到实现微信功能的js脚本,和div布局代码,没有看到跟二维码相关的代码,那么从这里可以推测该二维码是由服务器传送过来并且时时需要更新。
3.由于第一步,看到一张png图片,先猜测二维码图片为png格式,png图片文件格式中有关键数据块,该数据块中有四个标准数据块,文件头数据块中的符号为IHDR。
4.在微信安装的文件夹可以看到WeChatNet.exe,那么微信需要通过它实现二维码刷新和登入功能。
5.使用OD加载微信,打开CE,附加微信,搜索IHDR,可以看到一个绿色的基址,在OD中查看可以看到PNG的二进制信息,
6.把基址内容移到下面后,选中-》右键->找出是什么改写了这个地址,由于二维码会时时更新,所以下次更新时,就能显示出改写的汇编代码,点击显示反汇编程序,在弹出的界面中可以看到是WeChatWIn.dll里面的函数在进行写入。
7.假设绿色的基址是我们要找的基址,那么PNG在内存中的地址为0x7B4011C8,接着使用如下代码,获取内存中png图片的二进制信息,使用二进制编辑工具打开发现了一个类似于二维码的图形,那么第三步的猜想是正确的。
[C++] 纯文本查看 复制代码 #include<iostream>
#include<algorithm>
#include<cstdio>
#include<windows.h>
#include<cstring>
#include <fstream>
using namespace std;
int total=0;
int addr=0x7B4011C8;
int pid;
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
ofstream out("wx.png", ios::out|ios::trunc);
if (!out.is_open())
{
cout << "文件创建失败!" << endl;
exit(0);
}
HANDLE hpro = OpenProcess(PROCESS_ALL_ACCESS, TRUE, pid);
for(int index=0; index<0x3000; index++)
{
byte readbyte=0;
ReadProcessMemory(hpro,(LPVOID)addr+index,&readbyte,1,NULL);
//cout<<readbyte<<endl;
out << readbyte;
//Thtotalbyte+=readbyte;
}
out.close();
return TRUE;
}
int main()
{
cout<<"请输入进程pid\n";
scanf("%d", &pid);
CreateThread(NULL,NULL,ThreadProc,NULL,NULL,NULL);
system("pause");
return 0;
}
8.png图片的大小仅为0x2d4大小,而且图片也不能正常打开,所以绿色基址不是要找的地址,接着试试其它地址,在用CE加载微信时,发现IHDR搜索的结果地址会变化,那这就意味着二维码的地址不是静态变量。那么当程序在往内存中写入二维码数据前,会往某函数传入二维码地址和二维码信息大小。
9.多摁几次再次扫描,再等微信二维码刷新几次,选中所有行信息,右键-》更改记录-》智能编辑地址,在校正地址栏输入-c,发现只有一个符合要求。使用CE搜索png地址
10.在内存窗口可以看到png的地址和png图片大小,使用第7步的代码(要修改地址)就可以提取出内存中的二维码。
11.使用ida动态调式微信,在ida中使用idapython脚本对png图片进行内存转储,同样也可以获取二维码,脚本代码如下:
[C++] 纯文本查看 复制代码 import idaapi
start_address=0x425CB28
data_length=0x3345
data = idaapi.dbg_read_memory(start_address, data_length)
fp = open('E:\\wx.png', 'wb')
fp.write(data)
fp.close()
|