技术交流,DH讲解. 本来之前就在写这个文章,还写了War3挂的教程,后来因为一些事就没有弄了. 现在过年在家又没有事就继续把这个写完吧.哈哈. 不要以为写个挂很容易,要想写个挂肯定要了解游戏呀. 我们现在来了解下地雷是怎么产生的? 其实只是我自己猜想的,毕竟这个游戏不是我写的... 1 用户选择了多大的棋盘,多少地雷后 棋盘应该是用一个二维数组来存储的,地雷数肯定是用一个全局变量来存储的.这点儿有异议没有? 没有我就继续往下说了... 2 生成地雷,肯定是随机的,那么一定会调用Rand函数咯. a.首先判断地雷数是否为0 ---是0->结束生成过程 | 不是0 | b.Rand产生X,Rand产生Y | c.判断二维数组中(X,Y)的值是否为有雷值---不是-->转到b | 是 | d.雷数减一---------->转到a 整个流程看懂了吧. 那么我们来实际分析呀,OD Time,从上面看我bp Rand然后进入步骤b,只要到了步骤c我们就能知道雷的内存区域了,是吧? 我们遍历区域就知道哪些格子是雷了塞. 下断点: 当断点断下来了后 我们可以看到堆栈窗口: 选中第一个,然后回车.返回:
01003940 /$ FF15 B0110001 call dword ptr [<&msvcrt.rand>] ; [rand
01003946 |. 99 cdq
01003947 |. F77C24 04 idiv dword ptr [esp+4]
0100394B |. 8BC2 mov eax, edx
0100394D \. C2 0400 retn 4
有点儿基础的朋友可以看出来,这里是它写了一个函数,先Rand一个数,然后整除另一个数,返回取余的结果. 其中这2个数是什么,上面那图我们看见2个返回到,是吧?那么选中第二个回车呗. 下断,单步走:我们就会发现压入栈的是1E=29,那么应该是30个,我们看一下是行还是列. 也就是这个这个函数传入的参数是行列数,哈哈因为这个C++的,所以PUSH那一下就是在传参数.好的看具体参数: 现在可以看出来,rand函数的参数用的返回地址,$010036D2,而rand出来值再对行列数取余,也就是[esp+4]就是这个30,这样就保证,最后这个值肯定小于30咯.
010036C7 |> /FF35 34530001 push dword ptr [1005334] ; 将列数压入栈,发现存列的内存地址了
010036CD |. |E8 6E020000 call 01003940 ; 返回X
010036D2 |. |FF35 38530001 push dword ptr [1005338] ; 将行压入栈,发现存行的内存地址
010036D8 |. |8BF0 mov esi, eax ; esi就是X
010036DA |. |46 inc esi ; 因为rand是0~n-1,所以这里就要+1
010036DB |. |E8 60020000 call 01003940 ; 返回Y
010036E0 |. |40 inc eax
010036E1 |. |8BC8 mov ecx, eax ; ecx就是Y
010036E3 |. |C1E1 05 shl ecx, 5 ; 将Y*32,这样就转换成一维数组
010036E6 |. |F68431 405300>test byte ptr [ecx+esi+1005340], 80 ; 我们这里发现数组的基址了.$80就是有雷咯
010036EE |.^ 75 D7 jnz short 010036C7
010036F0 |. |C1E0 05 shl eax, 5
010036F3 |. |8D8430 405300>lea eax, dword ptr [eax+esi+1005340]
010036FA |. |8008 80 or byte ptr [eax], 80
010036FD |. |FF0D 30530001 dec dword ptr [1005330] ; 减少雷总数
01003703 |.^\75 C2 jnz short 010036C7
我们知道上面这些,我们就来看看具体某一个雷,当我们插了小旗子后有什么变化没有: 我们执行到010036E1这句时候发现:esi是$10也就是16,ecx是3,那么16-3是雷.我们让游戏运行起来. 将这个设置成有雷后,这个内存的值就是8F了.点上红旗后: 有雷且有红旗这个值就是$8E.那么要是没有雷又被点了红旗会是什么值呢? 哈哈,$0E吧.记住了吧.我们来整理下. 1 有雷 $8F 2 无雷 $0F 3 红旗 $FE 1和3进行或,2和3进行或. 好,该Delphi了.
procedure TForm3.Button1Click(Sender: TObject);
var
pid:Integer;
h:Cardinal;
x,y:Integer;
readed:Cardinal;
Count:Cardinal;
I: Integer;
j: Integer;
buf:array of Byte;
begin
edt1.Text:='';
edt2.Text:='';
mmo1.Clear;
//查找扫雷游戏的PID
ProcessList.Snap;
pid:=ProcessList.FindPID('winmine.exe');
if pid = -1 then
Exit;
//打开进程
h:=OpenProcess(PROCESS_ALL_ACCESS,False,pid);
if h = 0 then
Exit;
try
//取得游戏的行列数
readed:=0;
ReadProcessMemory(h,Pointer($1005334),@Count,4,readed);
if readed>0 then
begin
edt1.Text:=IntToStr(Count);
x:=Count;
end;
readed:=0;
ReadProcessMemory(h,Pointer($1005338),@Count,4,readed);
if readed>0 then
begin
edt2.Text:=IntToStr(Count);
y:=Count;
end;
//获取雷数组内存区域的大小
Count:=Y shl 5 + X;
SetLength(buf,Count);
//读取雷内存
ReadProcessMemory(h,Pointer($1005340),buf,Count,readed);
//循环判断
for I := 1 to x do
for j := 1 to y do
if buf[32*J + I] = $8F then
begin
mmo1.Lines.Add(Format('雷:列:%D,行:%D',[i,j]));
buf[32*J + I]:= $8E;
end;
//写回去,这样小红旗都标记出来了....
WriteProcessMemory(h,Pointer($1005340),buf,Count,readed);
finally
CloseHandle(h);
end;
end;
哈哈,读取出来了吧.这里只是显示.下一篇文章,我们去寻找点击的call,然后实现自动点击.嘿嘿,秒杀.
我是DH,今天就讲这么多了.
|
请发表评论