最近在网上看了下各种相关帖子,都是比较老的版本,这里我将以3.6.0.18版本为例,对其进行分析 【供学术研究,请勿用于非法用途】
- 我在github上开源了3.6.0.18的源码,有需要学习的可以去看看
- PS:感觉对你有帮助就顺便给个star呗~
- github地址:https://github.com/mrsanshui/WeChatApi
先引入一个前言:
- 所有png图片都是有统一格式的
- 文件头数据块:89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52
- 文件尾数据块:00 00 00 00 49 45 4e 44 ae 42 60 82
- 使用16进制查看器随便打开一张png图片,可以看出文件头确实跟上述一致,那么我们就从这里做切入点
一、CE寻址
-
打开WX,切换到二维码登录页面,打开CE搜索字符串“IHDR”,选择这3个黑色的(绿色的是基址,我们要找的肯定不是基址)
-
智能编辑地址,全部减C,这里很多人都不知道为什么要减C
-
通过16进制编辑器可以看到“IHDR”的首字符所在位置在0xC的位置,所以减去C就可以找到PNG图片的头部了
-
减C之后可以看到有2个数值是“?PNG”,依次查找该地址被谁引用了(找上级指针)
二、验证
- 打开od附加,查找这个地址
- 可以看出他是一个结构体,第1个是存储图片的指针;第2个猜测他是图片的大小
验证方法1
- 编写脚本,使用od运行脚本,把图片下载下来
验证方法2
- 使用PChunter验证
- 把图片down下来之后,确认无误
三、寻找HOOK点
- 在这个地址下内存写入断点 (这里别看懵了,因为地址是随机的,我写教程的过程中不小心重新启动wx了…所以变了)
- 再次切换到二维码页面,会断在这里,删掉内存断点
- 通过栈回溯的方法,往上找,发现有2个call【其实这里再往上一直找,也能找到我第一篇文章《刷新登录二维码》的call,抛砖引玉多嘴提一句吧,找call并不是那么死板的,找一个call可以有N种方法,只不过是时间快慢以及经验取巧的问题罢了,世上无难事只怕有心人…】
- 经过验证:
- 第1个call完成之后,虽然图片数据刷新了,但是down下来的图片是空白的【猜测这个call只是构建了一块空白的画布】
- 第2个call完成之后,就可以成功down下来,那么我们就对这个地方进行Hook
四、编写代码
// 模块基址
DWORD baseAddr = GetBaseAddr();
// 要HOOK的地址
DWORD hookAddr = baseAddr + qrcodeOffser1;
// 要返回的地址
DWORD returnAddr = hookAddr + 5;
// 被覆盖的call的地址
DWORD callAddr = baseAddr + qrcodeOffser2;
void Hook()
{
//组装跳转数据
BYTE jmpCode[5] = { 0 };
jmpCode[0] = 0xE9;
//计算偏移
*(DWORD*)&jmpCode[1] = (DWORD)myCall - hookAddr - 5;
// 往内存中写入自己的数据
WriteProcessMemory(GetCurrentProcess(), (LPVOID)hookAddr, jmpCode, 5, 0);
}
__declspec(naked) void myCall()
{
__asm
{
// 调用call
call callAddr;
//保存环境
pushad;
pushf;
// 下载图片
push esi;
call downQrcode;
//恢复环境
popf;
popad;
//跳转回Hook点的下一条
jmp returnAddr;
}
}
void __stdcall downQrcode(DWORD esi)
{
// 图片大小
DWORD imgSizeAddr = esi + 0x4;
size_t imgSize = (size_t)*((LPVOID*)imgSizeAddr);
// 拷贝图片
char imgData[0xFFF] = { 0 };
memcpy(imgData, *((LPVOID*)esi), imgSize);
// 创建文件句柄
HANDLE hFile = CreateFileA("C:\\Users\\Administrator\\Desktop\\qrcode666.png", GENERIC_ALL, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == NULL)
{
OutputDebugStringA("创建文件句柄失败");
return;
}
// 写文件
DWORD dwRead = 0;
if (!WriteFile(hFile, imgData, imgSize, &dwRead, NULL))
{
OutputDebugStringA("写入失败");
return;
}
CloseHandle(hFile);
}