6B259EF7 |. 8D45 08 lea eax,[arg.1]
6B259EFA |. C645 FC 01 mov byte ptr ss:[ebp-0x4],0x1
6B259EFE |. 8D9E 84000000 lea ebx,dword ptr ds:[esi+0x84]
6B259F04 |. 50 push eax
6B259F05 |. 8BCB mov ecx,ebx
6B259F07 |. E8 847CBDFF call WeChatWi.6AE31B90 ; 根据ID查询信息
6B259F0C |. 8BF0 mov esi,eax
很多同学通过Hook方法获取好友信息,群信息,群内信息,因为进行登录操作后,程序会从数据库中逐条读取出好友列表,经过分析,上述汇编中有如下操作。
push eax :压入 wxid
call 完后个人信息存放eax中
Hook的方式就是将 CALL 改为 JMP 到自己的函数中,在自己的函数中执行这个CALL,并且获取eax寄存器中存放的用户数据。
但是这样有缺点
:这个函数是经常在调用的,并且返回的eax中不一定就是用户数据需要进一步过滤,而且反复的调用加大了负载,产生不必要的消耗,可能随时崩溃,很不稳定!
这种方式还能查询群内成员等等. 请看章节【拓展】
先看以下三个账号的数据
0507F4E0 0961C2C8
0507F4E4 05209B38
0507F4E8 xxx
filehelper
0961C2C8 0505FBF0
0961C2CC 0507F4E0
0961C2D0 0961CC68
0961C2D4 00000000
0961C2D8 051C40C0 UNICODE "wxid_1111"
0505FBF0 05209B38
0505FBF4 0961C2C8
0505FBF8 05209B38
0505FBFC 00000001
0505FC00 09588B88 UNICODE "wxid_2222"
0961CC68 05209B38
0961CC6C 0961C2C8
0961CC70 05209B38
0961CC74 00000001
0961CC78 051C4200 UNICODE "wxid_3333"
这里其实就是HOOK的这个CALL返回的存在eax中的用户数据
发现前三条数据中存放地址,并且有以下特点
首先我访问了这个共同指向的指针 05209B38
05209B38 0961C6D8
05209B3C 0507F4E0
05209B40 0961CC68
通过观察上面三个用户,可以发现
第二个用户的第二个指针 -> 第一个用户的第一个数据地址
第一个用户的第一个指针 -> 第二个用户的第一个数据地址
第一个用户的第三个指针 -> 第三个用户的第一个数据地址
第三个用户的第二个指针 -> 第一个用户的第一个数据地址
第一个用户的第二个指针 -> filehelper用户第一个数据地址
filehelper的第二个指针 -> ROOT的一个数据地址
第二个指针的作用可能是寻找指向他的结点,即 父节点
因为上学期学了数据结构 可以联想到 线索树
,我们可以给树添加一个ROOT结点等等操作。
那是否这些数据指向的一个公共地址就是一个 ROOT结点呢?
我做了试验,我随机找了一个地址
第一次实验:
0x0505FBF0 第二个用户
根据分析可能第二条数据是 父节点
访问 dd [0x0505FBF0 + 0x4] 来到第三个用户
访问 dd [[[0x0505FBF0 + 0x4] + 0x4] 来到第一个用户
访问 dd [[[[0x0505FBF0 + 0x4] + 0x4] + 0x4] 来到filehelper
访问 dd [[[[[0x0505FBF0 + 0x4] + 0x4] + 0x4] +0x4] 来到ROOT
说明 filehelper 就是 头结点
继续测试另外两个指针是否指向左右儿子:
因为我的测试账号用只有三个账号,所以测试起来很方便的发现叶子结点的儿子结点指向了ROOT结点
。
所以猜测其储存列表数据的数据结构是一颗 外加根节点的二叉树
图中:
微信内部就存有数据缓存,并且只要获取到根节点就可以遍历出整个好友列表。
通过传入的参数逐层分析后,可以发现这个根节点地址是一个定值。所以可以直接获取而不用调用CALL,毕竟修改代码可能会对程序造成一些影响,能用访问内存的方式获取数据是再好不过的了。
//************************************************************
// Class : WeChatFunFriendsTree
// Desc : 微信好友列表获取
// Author : Stdchi
// Time : 2020/2/22
//************************************************************
#include "pch.h"
#include "WeChatFunFriendsTree.h"
// 初始化静态变量
WeChatFunFriendsTree* WeChatFunFriendsTree::instance = NULL;
DWORD WeChatFunFriendsTree::wechatWin = (DWORD)WeChatUtils::getWeChatModule();
WeChatFriendsElem * WeChatFunFriendsTree::ElemHead = NULL;
WeChatFriendsElem * WeChatFunFriendsTree::ElemP = NULL;
// 单例模式
WeChatFunFriendsTree WeChatFunFriendsTree::getInstance() {
if (instance == NULL)
instance = new WeChatFunFriendsTree;
return * instance;
}
WeChatFunFriendsTree::WeChatFunFriendsTree() {
if (instance == NULL) {
instance = this;
}
}
// 获取ROOT结点首地址
DWORD WeChatFunFriendsTree::getRoot() {
DWORD add = BIAS+ wechatWin;// BIAS 偏移地址
DWORD rootAdd = *(DWORD *)add + 0x28 + 0x84;
return root = *(DWORD *)rootAdd;
}
// 获取HEAD结点地址
DWORD WeChatFunFriendsTree::getHead() {
DWORD headAdd = root + 0x4;
return head = *(DWORD *)headAdd;
}
// 遍历结点
VOID WeChatFunFriendsTree::traverse(DWORD add) {
if (add == root) return;
DWORD left = *(DWORD *)add;
DWORD right = *(DWORD *)(add + 0x8);
// 获取到该结点到信息
WeChatFriendsElem * _elemP = getDetail(add); // 返回元素结构体指针
if (ElemHead == NULL) {
ElemHead = _elemP;
ElemP = _elemP;
ElemP->next = NULL;
}
else {
ElemP->next = _elemP;
ElemP = ElemP->next;
ElemP->next = NULL;
}
traverse(left);
traverse(right);
}
DWORD WeChatFunFriendsTree::getPcontent(DWORD address) {
return *(DWORD *)address;
}
// 清空链表
VOID WeChatFunFriendsTree::deleteElemList() {
WeChatFriendsElem * _t = ElemHead;
WeChatFriendsElem * _tnext = _t->next;
while (_t != NULL) {
delete _t;
_t = _tnext;
_tnext = _t->next;
}
ElemHead = ElemP = NULL; // 重置指针
}
// 获取好友链表头结点
WeChatFriendsElem * WeChatFunFriendsTree::getList() {
if (ElemHead != NULL) {
deleteElemList(); //链表清空
}
// 遍历
getRoot();
getHead();
traverse(head);
return ElemHead;
}
WeChatFriendsElem * WeChatFunFriendsTree::getDetail(DWORD address) {
DWORD wxidAdd = address + 0x10;
DWORD wxidLenAdd = wxidAdd + 0x4;
size_t wxidLen = size_t(getPcontent(wxidLenAdd));
wstring wxid = StringUtils::getWstring(getPcontent(wxidAdd), wxidLen);
WeChatFriendsElem * p = new WeChatFriendsElem;
p->id = wxid;
return p;
}
经过我的分析,群的列表
也有一个相同的数据结构,并且存有群内成员的id,所以根本不需要去调用CALL获取,只要通过 内存访问
即可获取到数据!这里就由大家去探索把,我就不详细叙述辽。