注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

风雨夜归人

专业收集资料,个人爱好!

 
 
 

日志

 
 

过游戏保护详解  

2009-10-13 06:54:40|  分类: 外挂学习 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
一. 论驱动反外挂
  随着国内软件安全行业的发展,驱动这一名词逐渐被摘去神秘的光环. 而3721的出现,告诉了人们驱动这东西不仅仅是用于硬件
  越来越多的人认识到驱动的巨大作用,当<<MU>>引入了 nProtect 反外挂系统后,似乎驱动反外挂成了相当理想的选择.
  但这一切,只是看起来很美. 随着越来越多的ROOTKIT出现,各大杀毒厂商逐渐的加强了这一方面的监控. 越来越多的各类监控
  软件也使得驱动反外挂举步维难.
  在进入正题之前,首先要明确一点. 你的驱动将是游戏客户端的组成部分, 很多ROOTKIT上可以用的手段你不能使用.
  游戏玩家并不是专业人士,他们更相信他们所选择的杀毒软件. 总不能当你的游戏运行时,杀毒软件便提示说 - 这是个ROOTKIT
  首先我们抛开驱动的兼容性不谈 - 这也没法谈, 正如你驾驶汽车,你可以保证自己不出错. 但是你能保证其他人都能吗?
  说到驱动反外挂,你应该立马想到 HOOK SSDT与SSSDT 拦截API防止游戏进程被修改. 可是这真的那么有效吗?
  好吧,你想说阻止 OpenProcess,ReadProcessMemory,WriteProcessMemory 这三个API就好? 不 - 相信我,这只能防防菜鸟而已.
  即使你不考虑兼容性把 PsLookupProcessByProcessId,ObOpenObjectByPointer,ObOpenObjectByName,KeAttachProcess 等
  全部HOOK,真的就能阻止修改了吗?
  不,我们来看看下面的代码.
  复制内容到剪贴板
  代码:
  Function GetInfoTable(ATableType:dword):Pointer;
  var
  mSize: dword;
  mPtr: pointer;
  St: NTStatus;
  begin
  Result := nil;
  mSize := $4000;
  repeat
  mPtr := VirtualAlloc(nil, mSize, MEM_COMMIT or MEM_RESERVE, PAGE_READWRITE);
  if mPtr = nil then Exit;
  St := ZwQuerySystemInformation(ATableType, mPtr, mSize, nil);
  if St = STATUS_INFO_LENGTH_MISMATCH then
  begin
  VirtualFree(mPtr, 0, MEM_RELEASE);
  mSize := mSize * 2;
  end;
  until St <> STATUS_INFO_LENGTH_MISMATCH;
  if St = STATUS_SUCCESS
  then Result := mPtr
  else VirtualFree(mPtr, 0, MEM_RELEASE);
  end;
  function iOpenProcess(ProcessId:DWORD):DWORD;
  var
  HandlesInfo: PSYSTEM_HANDLE_INFORMATION_EX;
  ClientID:TClientID;
  pbi:_PROCESS_BASIC_INFORMATION;
  oa:TObjectAttributes;
  hProcessCur,hProcessToDup,hProcessToRet:DWORD;
  Ret:DWORD;
  I:Integer;
  begin
  SetPrivilege('SE_DEBUG',TRUE);
  Result:=0;
  FillChar(oa,SizeOf(TObjectAttributes),0);
  FillChar(ClientID,SizeOf(TClientID),0);
  oa.Length:=SizeOf(TObjectAttributes);
  HandlesInfo:=GetInfoTable(SystemHandleInformation);
  for I:=0 to HandlesInfo^.NumberOfHandles do
  begin
  If (HandlesInfo^.Information.ObjectTypeNumber=5) Then //OB_TYPE_PROCESS
  ClientID.UniqueProcess:=HandlesInfo^.Information.ProcessId;
  If ZwDuplicateObject(hProcessToDup,HandlesInfo^.Information.Handle,GetCurrentProcess,@hProcessCur,PROCESS_ALL_ACCESS,0,$4)=STATUS_SUCCESS then
  If ZwQueryInformationProcess(hProcessCur,ProcessBasicInformation,@pbi,Sizeof(_PROCESS_BASIC_INFORMATION),@Ret)=STATUS_SUCCESS then
  If (pbi.UniqueProcessId=ProcessId) Then
  If ZwDuplicateObject(hProcessToDup,HandlesInfo^.Information.Handle,GetCurrentProcess,@hProcessToRet,PROCESS_ALL_ACCESS,0,$4)=STATUS_SUCCESS then
  begin
  Result:=hProcessToRet;
  Break;
  end;
  end;
  if hProcessCur>0 then ZwClose(hProcessCur);
  if hProcessToDup>0 then ZwClose(hProcessToDup);
  VirtualFree(HandlesInfo,0,MEM_RELEASE);
  SetPrivilege('SE_DEBUG',FALSE);
  end;
  这是枚举系统中所有已知举柄达到取得进程Handle的函数. 你或许会认为,拦截ZwDuplicateObject,ZwQueryInformationProcess不就解决问题了?
  这没错,你是对的.但是你不能这样做,你做的是反外挂,不是ROOTKIT, 当你尝试这样做的时候,你会发现你的杀毒软件提示你. 这是ROOTKIT的典型行为
  怎么办? 难道你要象ROOTKIT那样关闭掉玩家的杀毒软件? 还是联系各大杀毒软件厂商告诉他们: 麻烦您修改你们的规则?
  这仅仅是RING 3的普通运用而已, 千万不要认为做外挂的不会驱动. 相反,与游戏开发公司那点可怜的薪水比起来. 外挂的利润只会让更多的驱动开发者
  加入这一行列. 即使你HOOK接管了这一切函数,不管是inline还是普通的ssdt. 下面的驱动很轻易的就能突破任意的HOOK.
  复制内容到剪贴板
  代码:
  .....................
  NTSTATUS NTAPI GetRealAddress(PIMPORT_ENTRY Import)
  {
  MODULE_INFORMATION mi,idmi;
  DWORD        i,j;
  DWORD        dwKernelBase;
  NTSTATUS        status;
  PDWORD        KiServiceTable;
  UNICODE_STRING NtdllName;
  if (KeGetCurrentIrql()!=PASSIVE_LEVEL) return STATUS_PASSIVE_LEVEL_REQUIRED;
  RtlZeroMemory(&mi,sizeof(mi));
  if (!NT_SUCCESS(status=MapKernelImage(&mi,&dwKernelBase))) return status;
  RtlZeroMemory(&idmi,sizeof(idmi));
  RtlInitUnicodeString(&NtdllName, L"[url=file://%5c%5csystemroot%5c%5csystem32%5c%5cntdll.dll/]\\SystemRoot\\System32\\ntdll.dll[/url]");
  if (!NT_SUCCESS(status=MapPeImage(&idmi,&NtdllName))) return status;
  try {
  for (i=0;Import.szName;i++){
  Import.dwAddress=0;
  switch (Import.dwType) {
  case IMPORT_BY_NAME:
  if (!(Import.dwAddress=GetProcRva(mi.hModule,Import.szName))) {
  #ifdef DEBUG
  DbgPrint("GetRealAddress(): Failed to get %s rva!\n",Import.szName);
  #endif
  }
  break;
  case IMPORT_BY_RVA:
  Import.dwAddress=(DWORD)Import.szName;
  break;
  case IMPORT_BY_ADDRESS:
  Import.dwAddress=(DWORD)Import.szName-dwKernelBase;
  break;
  case IMPORT_BY_SERVICE_ID:
  // do not search this rva if it has been already found
  if (!KiServiceTable_RVA) {
  if (!(KiServiceTable_RVA=FindKiServiceTable(mi.hModule))) {
  #ifdef DEBUG
  DbgPrint("GetRealAddress(): Failed to get KiServiceTable RVA!\n");
  #endif
  break;
  }
  }
  KiServiceTable=(PDWORD)(KiServiceTable_RVA+mi.hModule);
  Import.dwAddress=KiServiceTable[(DWORD)Import.szName]-mi.dwImageBase;
  break;
  case IMPORT_BY_SERVICE_NAME:
  if (!KiServiceTable_RVA){
  if (!(KiServiceTable_RVA=FindKiServiceTable(mi.hModule)))        break;
  }
  Import.dwId=GetIdForName(idmi.hModule,Import.szName);
  KiServiceTable=(PDWORD)(KiServiceTable_RVA+mi.hModule);
  Import.dwAddress=KiServiceTable[Import.dwId]-mi.dwImageBase;
  break;
  default:
  break;
  } //Case End
  if (Import.dwId==0){
  if (!KiServiceTable_RVA)
  KiServiceTable_RVA=FindKiServiceTable(mi.hModule);
  KiServiceTable=(PDWORD)(KiServiceTable_RVA+mi.hModule);
  for (j=0;KiServiceTable[j];j++){if (Import.dwAddress==KiServiceTable[j]-mi.dwImageBase){Import.dwId=j;break;}}
  }
  Import.dwAddress=dwKernelBase+Import.dwAddress;
  }
  }except(EXCEPTION_EXECUTE_HANDLER){
  return STATUS_ADD_FUNCTION_FAILED;
  }
  try {
  UnmapPeImage(&mi);
  UnmapPeImage(&idmi);
  }except(EXCEPTION_EXECUTE_HANDLER){
  return STATUS_CODE_REBUILDING_FAILED;
  }
  return STATUS_SUCCESS;
  }
  ...........
  恩..这不是完整的代码,这理所当然,不是么?
  面对任何HOOK,只需要从NT的内核文件中取出其真实的地址,很轻易的就可以饶过SSDT的HOOK,INLINE HOOK只需要恢复代码即可.
  更何况你的驱动肯定会比外挂的驱动还晚加载.
  即使除开上面这些不谈,你依然要面对你的驱动被PATCH,又或者被个假冒的驱动所替代. 更别说 lpk.dll usp10.dll 了.
  这时候你应该会想反驳我,看看 nPROTECT ,安博士 吧. 好的,那么我们来看看下面这段函数
  复制内容到剪贴板
  代码:
  NTSTATUS ReadPhysicalMemory(char *startaddress, UINT_PTR bytestoread, void *output)
  {
  HANDLE                        physmem;
  UNICODE_STRING        physmemString;
  OBJECT_ATTRIBUTES attributes;
  WCHAR                        physmemName[] = L"[url=file://%5c%5cdevice%5c%5cphysicalmemory/]\\device\\physicalmemory[/url]";
  UCHAR*                        memoryview;
  NTSTATUS                ntStatus = STATUS_UNSUCCESSFUL;
  __try
  {
  RtlInitUnicodeString( &physmemString, physmemName );
  InitializeObjectAttributes( &attributes, &physmemString, OBJ_CASE_INSENSITIVE, NULL, NULL );
  ntStatus=ZwOpenSection( &physmem, SECTION_MAP_READ, &attributes );
  if (ntStatus==STATUS_SUCCESS)
  {
  //hey look, it didn't kill it
  UINT_PTR length;
  PHYSICAL_ADDRESS        viewBase;
  UINT_PTR offset;
  UINT_PTR toread;
  viewBase.QuadPart = (ULONGLONG)(startaddress);
  length=0x2000;//pinp->bytestoread; //in case of a overlapping region
  toread=bytestoread;
  memoryview=NULL;
  DbgPrint("ReadPhysicalMemory:viewBase.QuadPart=%x", viewBase.QuadPart);
  ntStatus=ZwMapViewOfSection(
  physmem,  //sectionhandle
  NtCurrentProcess(), //processhandle (should be -1)
  &memoryview, //BaseAddress
  0L, //ZeroBits
  length, //CommitSize
  &viewBase, //SectionOffset
  &length, //ViewSize
  ViewShare,
  0,
  PAGE_READWRITE);
  if (ntStatus==STATUS_SUCCESS)
  {
  offset=(UINT_PTR)(startaddress)-(UINT_PTR)viewBase.QuadPart;
  RtlCopyMemory(output,&memoryview[offset],toread);
  ZwUnmapViewOfSection( NtCurrentProcess(), memoryview);
  }
  else
  {
  DbgPrint("ReadPhysicalMemory:ntStatus=%x", ntStatus);
  }
  ZwClose(physmem);
  };
  }
  __except(1)
  {
  DbgPrint("Error while reading physical memory\n");
  }
  return ntStatus;
  }
  直接读取物理内存, 到目前为止,这个方法依然对 nPROTECT 保护的进程有效.
  实际上反外挂的驱动能拦截的不过是API而已, 你能拦截 mov eax,[xxxxxxx] 吗?
  别忘记,你在驱动中采取的手段越多,驱动的兼容性必定越差.
  在家中的玩家还好说,可是面对目前主要的玩家多数在网吧上网的情况,你不的不考虑各种网吧管理软件.
  这样的情况,不谈兼容性光是你的驱动到底有没有机会被加载还是个问题....
  即使是在家中上网的玩家,你难道要告诉使用 Vista 或者 Windows 7 的普通用户: 请关闭你的UAC
  好吧,再这样写下去简直没完没了.  综上所述, 驱动反外挂, 这只是看起来很美而已.
  怎么办?
  二. 如何有效的阻止外挂
  前言中提到,要有效的反外挂,必先了解外挂如何运作.  在前文中,也描述了当前外挂主要的运作模式. 现在外挂已不是要求什么三步瞬移,格位刺杀之类的特殊功能了,对于工作室.
  他们的需要仅仅是稳定的机器人,如果游戏提供的话,他们常常还需要能够把挂机角色上的金钱物品邮寄或者交易给某个账号的功能. 那么制作一个这样的机器人至少需要的是什么?
  1. 游戏角色的生命值,魔法值之类的数据
  2. 游戏角色的物品数据
  3. 游戏角色周围的怪物数据
  4. 移动函数
  5. 热键函数 [假如客户端接受 SendMessage 模拟键盘这样的消息,这不需要]
  6. 选中怪物函数
  7. 打开NPC函数
  8. 打开仓库函数
  9. 交易或邮寄函数
  其中的 4-9 可以被一个数据包发送函数所替代,例如
  复制内容到剪贴板
  代码:
  procedure SendPack(buf:PChar;len:DWORD); stdcall;
  procedure TOSEND; stdcall;
  asm
  push    -1
  push    SENDPACK_STAK
  mov     eax, dword ptr fs:[0]
  push    eax
  mov     dword ptr fs:[0], esp
  sub     esp,$18
  push    ebx
  push    esi
  push    edi
  mov     edi, ecx
  xor     ebx, ebx
  xor     eax, eax
  jmp     SENDPACK_JMP
  end;
  begin
  asm
  pushad;
  mov  ecx, [CALL_BASE];
  push len;
  push buf;
  mov  ecx, [ecx+$20];
  call TOSEND;
  popad;
  end;
  end;
  procedure SendBuyItem(ItemId,ItemPos,ItemCount:DWORD);
  var
  //25 00 01 00 00 00 14 00 00 00 00 00 00 00 01 00 00 00 AA 21 00 00 01 00 00 00 01 00 00 00
  //25 00 01 00 00 00 ByteCount 00 00 00 00 GroupCount ItemId ItemPos ItemCount
  Pack:Array [0..29] of Byte;
  begin
  FillChar(Pack,SizeOf(Pack),0);
  Pack[0]:=$25;
  Pack[2]:=$01;
  Pack[6]:=$14;
  Pack[14]:=$01;
  CopyMemory(@Pack[18],@ItemId,4);
  CopyMemory(@Pack[22],@ItemPos,4);
  CopyMemory(@Pack[26],@ItemCount,4);
  SendPack(@Pack[0],30);
  end;
  归根结底, 要反外挂,主要防御的只有两点:
  1. 防止外部修改内存
  2. 防止外部调用函数
  对于第一点,比如修改某个怪物的数据,使得客户端判断该怪物在游戏角色的攻击范围之内.
  最佳的解决办法不是去HOOK什么内存读写函数. 而是把判断这些数据的责任交给服务器端.
  可如果是引进的游戏呢? 解决办法便是CRC32或者别的什么HASH算法校验这段内存数据.
  对于第二点,最简单的办法便是在函数内取得 ESP 判断函数的返回地址. 以上面的那段函数为例.
  只要游戏开发商稍微更改一下他的发包函数,判断下call 的来源, 我想这已经会让外挂的作者头痛
  很久.
  评论这张
 
阅读(2220)| 评论(1)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017