[原创]调试外挂经验记录[更新]
我目前遇到的外挂绕过保护有两种:
1. 将外挂代码写到系统进程中。
2. 将外挂dll注入到游戏进程中。
游戏是有保护的,调试外挂,想知道外挂功能执行,打开游戏进行调试是不现实的,因为你想用OD之类的,你首先要绕过游戏的保护
。
有一个办法可以解决这个问题,那就是你写一个exe,伪装成游戏进程。伪装很简单,把自己写的exe的名字改成与游戏进程名一样就OK了。有些外挂即使你进程名和游戏进程名是一样的,还是会提示“请打开XXX游戏”,经过实际调试,原因是外挂使用FindWindow()函数,所以你要把你的程序的窗口名和窗口句柄改成和游戏一样的。这样就完全OK了。
伪装游戏的程序用易语言写最简单,直接生成一个窗口程序,在属性里面将窗口标题和窗口类名改了,然后静态编译,熟悉的话1分钟搞定,不熟悉的人10分钟也能搞定。
伪装进程还有一个好处,那就是游戏中存在的地址你的这个假进程不一定有,因为游戏是很大的,现在的游戏动辄1G+,而你的伪装程序可能就1M左右。所以当执行部分功能的时候会出现下图的消息框:

主要的是这句话:"XXXX1"指令引用的"XXXX2"内存。该内存不能为"written"。
XXXX2这个内存地址就是外挂写入游戏的内存地址,如果打开OD,将所有的异常忽略全部取消,OD就可以在这个错误发生的地方断下来,再根据调用堆栈,就可以定位这个功能执行的地方。
我所分析的外挂都是用易语言写的,发现了一个情况,易语言的组合框(就是下拉列表)中的功能可以用OD搜索到字符串,而复选框却不可以。经过分析,我认为组合框是要临时判断所选择的功能的,所以在执行功能的时候会用到组合框中的文字,所以OD搜索到相应的文字以后可以很快的定位功能实现地点,而复选框不用,所以无法根据复选框旁边的文字来定位功能实现的地点。
想要知道外挂进程向游戏进程写入数据的细节,可以自己写一个驱动级的监控软件。
///////////////////////////////////////////////////////////////////////////////////////////////////
2013/04/11更
易语言中定位事件的方法。
通过我写的一个易语言测试程序,比较调用堆栈,定位了一段汇编,只要易语言中产生外挂作者处理的事件就会运行到这个汇编中。比如,按钮被单击,选择框、单选框被选中等等。而由易语言处理的事件,比如窗口焦点等,都不会执行到这段汇编。
下面就是要提取特征码了。
0041A4C0 /$ 55 push ebp
0041A4C1 |. 8BEC mov ebp,esp
0041A4C3 |. 83EC 18 sub esp,0x18
0041A4C6 |. 53 push ebx
0041A4C7 |. 56 push esi
0041A4C8 |. 57 push edi
0041A4C9 |. 894D E8 mov [local.6],ecx
0041A4CC |. 8B45 08 mov eax,[arg.1]
0041A4CF |. 8B48 0C mov ecx,dword ptr ds:[eax+0xC]
0041A4D2 |. 51 push ecx
0041A4D3 |. 8B55 08 mov edx,[arg.1]
0041A4D6 |. 8B42 08 mov eax,dword ptr ds:[edx+0x8]
0041A4D9 |. 50 push eax
0041A4DA |. 8B4D 08 mov ecx,[arg.1]
0041A4DD |. 8B51 04 mov edx,dword ptr ds:[ecx+0x4]
0041A4E0 |. 52 push edx
0041A4E1 |. 8B45 08 mov eax,[arg.1]
0041A4E4 |. 8B08 mov ecx,dword ptr ds:[eax]
0041A4E6 |. 51 push ecx
0041A4E7 |. 8B4D E8 mov ecx,[local.6]
0041A4EA |. E8 419CFFFF call 易语言试.00414130
0041A4EF |. 8945 FC mov [local.1],eax
0041A4F2 |. 837D FC 00 cmp [local.1],0x0
0041A4F6 |. 75 04 jnz short 易语言试.0041A4FC
0041A4F8 |. 33C0 xor eax,eax
0041A4FA |. EB 67 jmp short 易语言试.0041A563
0041A4FC |> 56 push esi
0041A4FD |. 57 push edi
0041A4FE |. 8B55 08 mov edx,[arg.1]
0041A501 |. 8B42 0C mov eax,dword ptr ds:[edx+0xC]
0041A504 |. 83E8 01 sub eax,0x1
0041A507 |. 8945 EC mov [local.5],eax
0041A50A |. EB 09 jmp short 易语言试.0041A515
0041A50C |> 8B4D EC /mov ecx,[local.5]
0041A50F |. 83E9 01 |sub ecx,0x1
0041A512 |. 894D EC |mov [local.5],ecx
0041A515 |> 837D EC 00 cmp [local.5],0x0
0041A519 |. 7C 12 |jl short 易语言试.0041A52D
0041A51B |. 8B55 EC |mov edx,[local.5]
0041A51E |. 8B45 08 |mov eax,[arg.1]
0041A521 |. 8B4C90 10 |mov ecx,dword ptr ds:[eax+edx*4+0x10]
0041A525 |. 894D F0 |mov [local.4],ecx
0041A528 |. FF75 F0 |push [local.4]
0041A52B |.^ EB DF \jmp short 易语言试.0041A50C
0041A52D |> FF55 FC call [local.1]
0041A530 |. 5F pop edi
0041A531 |. 5E pop esi
0041A532 |. 895D F4 mov [local.3],ebx
0041A535 |. 8945 F8 mov [local.2],eax
0041A538 |. 8B55 E8 mov edx,[local.6]
0041A53B |. C782 F0010000>mov dword ptr ds:[edx+0x1F0],0x0
0041A545 |. 837D F4 00 cmp [local.3],0x0
0041A549 |. 74 13 je short 易语言试.0041A55E
0041A54B |. 8B45 08 mov eax,[arg.1]
0041A54E |. C740 24 01000>mov dword ptr ds:[eax+0x24],0x1
0041A555 |. 8B4D 08 mov ecx,[arg.1]
0041A558 |. 8B55 F8 mov edx,[local.2]
0041A55B |. 8951 28 mov dword ptr ds:[ecx+0x28],edx
0041A55E |> B8 01000000 mov eax,0x1
0041A563 |> 5F pop edi
0041A564 |. 5E pop esi
0041A565 |. 5B pop ebx
0041A566 |. 8BE5 mov esp,ebp
0041A568 |. 5D pop ebp
0041A569 \. C2 0400 retn 0x4
我提取其中这么一段汇编。
0041A50C |> /8B4D EC /mov ecx,[local.5]
0041A50F |. |83E9 01 |sub ecx,0x1
0041A512 |. |894D EC |mov [local.5],ecx
0041A515 |> |837D EC 00 cmp [local.5],0x0
0041A519 |. |7C 12 |jl short 易语言试.0041A52D
0041A51B |. |8B55 EC |mov edx,[local.5]
0041A51E |. |8B45 08 |mov eax,[arg.1]
0041A521 |. |8B4C90 10 |mov ecx,dword ptr ds:[eax+edx*4+0x10]
0041A525 |. |894D F0 |mov [local.4],ecx
0041A528 |. |FF75 F0 |push [local.4]
0041A52B |.^\EB DF \jmp short 易语言试.0041A50C
0041A52D |> FF55 FC call [local.1]
因为有[local.1]、jl short 易语言试.0041A52D之类的OD翻译的汇编语句,所以不能直接用来查找命令序列,而且OD的命令序列只能查找8条指令,还有易语言所编译出来的汇编和OD所翻译的汇编有时会不一样,所以使用ctrl+s查找命令序列的方法是很不精确甚至查不到的。
那么最好是使用二进制来定位,虽然提取二进制很麻烦,但是只要易语言不做大的更新,这个二进制特征码可以用很长很长时间了。
将前面的汇编的二进制提取出来
8B4DEC83E901894DEC837DEC007C128B55EC8B45088B4C9010894DF0FF75F0EBDFFF55FC
那么如何进入外挂作者的事件当中哪?在上面的
0041A52D |> FF55 FC call [local.1]
这个call中,按F7进入,就直接到了事件处理函数中。
当定位到这段汇编以后,要在
0041A52D |> FF55 FC call [local.1]
这个语句下断点,因为只要定位的位置正确,这个call是必然会被调用的,而别的地方不一定。
如果外挂中使用了线程,那么就会频繁的进入这段代码,因为这段代码就是用来调用外挂作者自己的函数的(这个描述可能不太准确,因为在外挂作者自己的函数中调用了外挂作者自己写的函数,可能不会执行到这段汇编)。所以需要使用条件断点。
如果外挂注入的是伪造进程,而伪造进程是用易语言写的,那么就会有两段这样的代码,需要在内存窗口中去查看,定位到的内存地址是在哪个模块中。
