首页
论坛
专栏
课程

[原创]某游戏控件遍历

6天前 1600

[原创]某游戏控件遍历

6天前
1600


1、自动喊话

想实现自动喊话怎么办?可以通过键鼠模拟,也可以找到喊话call,或者也可以找到控件输入call,在调用发送喊话的call,我们这里要找的是控件输入call


1.1 控件输入call

我们输入字符的时候肯定是要访问当前控件对象的,那么我可以从这个编辑框内容的长度来当做一个突破口, 当前有多少字符长读,我们就搜多少



最终有7个结果



我们可以移动编辑里的光标,发现有个地址的内容变了,很显然这个地址是存放是光标的位置



剩下的3个都是存储字符串长度的,我们随便找个然后下内存写入断点



输入字符被断下



然后我们Ctrl+F9不断的返回,沿途记录call



那么先从第一个call 分析



分析第一个参数,发现很像是一个结构体



具体分析如下:

0019FA64  0000000B                              //字符的数量
0019FA68  0000000C                              //字符的数量+1
0019FA6C  14F6F360   ASCII "ddad4631111"           //ascll的指针
0019FA70  0000000C                              //unicode的长度
0019FA74  0000000C                              //unicode的长度
0019FA78  12BD3E00  UNICODE "ddad4631111"      //unicode的指针

在来看看ecx是什么

从这些名字不难看出来应该是控件相关的



并且在+11c的位置可以到是当前编辑框里的内容,那么这个应该就是控件对象了



1.2 测试输入call

先构造这么一个结构体



调用一下,发现可以成功输入



1.3 发送call

他要发送内容,肯定是要发包的,我们可以在Send函数上下个断点

喊话之后被断下



Ctrl+F9返回,沿途记录call



直接从后面的call开始测试,这里有个简易的方法就是把call Nop掉,然后看看效果还在不在,但是Nop的时候注意堆栈平衡

然后把这个call Nop掉之后发现不会喊话了



那么我们直接调用这个call做个测试,发现可以调用成功



2、控件遍历

2.1 追踪来源

来到刚才的控件输入call,我们追ecx的来源



eax



eax又是这个call的返回值



然后重点来分析一下这个call


2.2 链表套数组结构

006E1A20    8BD1            mov edx,ecx                           ; 跟节点
006E1A22    8B8A 9C000000   mov ecx,dword ptr ds:[edx+0x9C]       ; 大循环
006E1A28    85C9            test ecx,ecx
006E1A2A    75 04           jnz short 画江山.006E1A30                ; 0
006E1A2C    B0 01           mov al,0x1
006E1A2E    EB 05           jmp short 画江山.006E1A35                ; 1
006E1A30    E8 FBF5FFFF     call 画江山.006E1030
006E1A35    807A 79 00      cmp byte ptr ds:[edx+0x79],0x0        ; 大循环结束条件
006E1A39    74 36           je short 画江山.006E1A71                 ; 大循环非正常跳出
006E1A3B    84C0            test al,al
006E1A3D    74 32           je short 画江山.006E1A71                 ; 大循环非正常跳出
006E1A3F    8B82 80000000   mov eax,dword ptr ds:[edx+0x80]       ; 数量=([edx+0x80]-[edx+0x7C])/4
006E1A45    2B42 7C         sub eax,dword ptr ds:[edx+0x7C]
006E1A48    C1F8 02         sar eax,0x2                           ; eax/4
006E1A4B    EB 03           jmp short 画江山.006E1A50                ; 1
006E1A4D    8D49 00         lea ecx,dword ptr ds:[ecx]
006E1A50    8BC8            mov ecx,eax                           ; 小循环
006E1A52    48              dec eax                               ; eax--
006E1A53    85C9            test ecx,ecx
006E1A55    74 1D           je short 画江山.006E1A74                 ; 正常跳出循环,返回要寻找的控件对象
006E1A57    8B8A 8C000000   mov ecx,dword ptr ds:[edx+0x8C]
006E1A5D    8B0C81          mov ecx,dword ptr ds:[ecx+eax*4]      ; 画江山.01010101
006E1A60    8079 79 00      cmp byte ptr ds:[ecx+0x79],0x0        ; 当[ecx+0x79]不等于0的时候循环结束
006E1A64  ^ 74 EA           je short 画江山.006E1A50
006E1A66    8B92 8C000000   mov edx,dword ptr ds:[edx+0x8C]       ; edx=[edx+0x8C]
006E1A6C    8B1482          mov edx,dword ptr ds:[edx+eax*4]      ; edx=[edx+eax*4], 控件对象
006E1A6F  ^ EB B1           jmp short 画江山.006E1A22                ; 循环尾
006E1A71    33C0            xor eax,eax
006E1A73    C3              retn
006E1A74    8BC2            mov eax,edx                           ; edx
006E1A76    C3              retn

这是一个链表套数组的结构

1、一开始会有一个根节点,记做Root

1、有两个循环,一个大循环,一个小循环

2、大循环我们可以看做是一个大控件,小循环是大控件里的那些小控件

3、大循环的终止条件是:[控件对象+0x79]==0

小循环的终止条件是:[控件对象+0x79]!=0

4、在开始遍历小循环开始,会先计算出当前这个大控件里有多少小控件,怎么计算呢?

小控件数量=([对象+0x80]-[对象+0x7C])/4

5、然后小循环开始遍历,用这个数量当做下标i来遍历,但是这并不是终止条件,循环完一次下标-1

6、小循环结束之后,下一个大控件也是用这个下标继续遍历,然后又取出数量

7、遍历方式大循环和小循环都一样,下一个控件对象=[[当前对象+0x8C]+i*4]


以上的这种遍历方式是按照这个call的遍历方式来写的,,但是现在可以推测出这个call应该是游戏用来寻找编辑框对象的,所以会有一些多余的条件,但是我们的目的是要把所有的控件全部遍历出来

那么正确的流程应该是这样:从根节点开始遍历,判断一下这个节点是否存在小控件,如果有小控件的话就继续进去,然后继续判断是否有小控件这种,直到全部结束,那么最好的解决办法就是递归


2.3 寻找根节点的来源

现在我们知道是怎么遍历的了,就去找根节点的来源即可

来源于ecx



edi



[eax+0x2C]



[esi+0x2c]



[ecx+0x2c]



根节点: [[0x29D40AC]+0x2c]



3、递归实现控件遍历

char* content = (char*)malloc(256);
char* str1 = NULL;
char* str2 = NULL;
//递归遍历  参数:节点:层级
VOID Recursive(DWORD node, DWORD i)
{
	USES_CONVERSION;
	memset(content, 0, 256);
	DWORD Size= (*(int*)(node + 0x80) - *(int*)(node + 0x7C)) / 4;  //计算控件的数量
	DWORD node_temp = 0;
	for (int i = 0; i < Size; i++)
	{

		node_temp= *(int*)((*(int*)(node + 0x8C)) + i * 4);
		if (node_temp == 0)  //这里需要判断一下节点是否存在
		{
			continue;
		}
		str1 = W2A((WCHAR*)(*(int*)(node_temp + 0x40)));   //控件名    unicode转ascll
		//判断一下内容是否是空的
		if ((*(int*)(node_temp + 0x11C)) != 0)
		{
			str2 = W2A((WCHAR*)(*(int*)(node_temp + 0x11C)));   //控件内容
			memcpy(content, str2, strlen(str2));
			memset(content + 32, 0, 100);      //这个做了一个长度限制,因为超过这个长度输出的话就会崩溃
			MyOutputDebugStringA("层级:%d  控件对象:%X   控件名:%s   控件内容:%s", i, node_temp, str1, content);  //输出大控件信息
			memset(content, 0, 256);
		}
		else
		{
			MyOutputDebugStringA("层级:%d  控件对象:%X   控件名:%s ", i, node_temp, str1);
		}

		//判断这个对象里还有没有小控件,如果有的话继续递归
		if (((*(int*)(node + 0x80) - *(int*)(node + 0x7C)) / 4) > 0)
		{
			Recursive(node_temp, i + 1);   //递归
		 }

	}

}

//遍历控件按钮
void HookSend::OnBnClickedButton12()
{
	// 根节点=[[0x29D40AC]+0x2c]
	//数量=([edx+0x80]-[edx+0x7C])/4
	DWORD Root = *(int*)(*(int*)(0x29D40AC) + 0x2C);  //根节点
	MyOutputDebugStringA("控件遍历开始");
	Recursive(Root, 0);
	MyOutputDebugStringA("控件遍历结束");

	free(content);
}

遍历也花了一点时间,将近6万多个控件,把游戏中的所有控件遍历出来了







[公告]安全测试和项目外包请将项目需求发到看雪企服平台:https://qifu.kanxue.com

最新回复 (8)
咖啡_741298 6天前
2
0
不错,谢谢分享
萌克力 6天前
3
0
童年回忆 yjxsoft.com
敏而好学 5天前
4
0
童年回忆 yjxsoft.com
无边 5天前
5
0
幼儿园回忆 yjxsoft.com
whoami key 3天前
6
0
小白一枚,有点看不懂。
7
0
萌克力 童年回忆 yjxsoft.com
三个机器人?
8
0
童年回忆 yjxsoft.com
Tennn 5 1小时前
9
0
这种就是只能走工作室 不暴力。。。
游客
登录 | 注册 方可回帖
返回