微信好友列表数据结构分析

PC HOOK

Hook方法获取列表

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中的用户数据

发现前三条数据中存放地址,并且有以下特点

  1. 数据存在相互的关联
  2. 存在共同指向的指针

首先我访问了这个共同指向的指针 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结点

所以猜测其储存列表数据的数据结构是一颗 外加根节点的二叉树

在这里插入图片描述
(即兴画了个图 😄)

图中:

  1. 绿色为指向儿子结点
  2. 红色指向父节点
  3. 黄色为空余的结点指向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获取,只要通过 内存访问 即可获取到数据!这里就由大家去探索把,我就不详细叙述辽。

声明

只用于学习,无其他用途,有兴趣一起交流HOOK。版本2.8
转发请注明出处!!
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值