欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

突破封锁线:第五章--外围篇

突破封锁线:第五章--外围篇

【文章标题】: 突破封锁线:第五章--外围篇
【文章作者】: Austin
【作者邮箱】: austiny.cn@gmail.com
【软件名称】: 神州数码网络客户登陆程序
【软件大小】: 不重要
【下载地址】: 自己搜索下载
【加壳方式】: UltraProtect 1.x
【保护方式】: 加壳 网络封禁
【编写语言】: VC6.0
【使用工具】: VC6.0
【操作平台】: Windows2003
【软件介绍】: 神州数码小区宽带网络登陆客户端,诸多限制
【作者声明】: 技术交流中。。。
--------------------------------------------------------------------------------
【详细过程】
  网络采用了神州数码公司的网络管理产品。主要弊端是:1.封禁代理;2.封禁路由器;3.封禁BT。
  (以下省略声讨文字若干),总之,有压迫的地方就有反抗。让我们来解决它。
  
  一、知己知彼
  
  神州数码采用了802.1x的认证方式,即认证流于数据流分开,
  这样降低了认证服务器的负担,提高了网络通讯效率,也为我们破解提供了方便。
  
  下面描述一下正常情况下,使用客户端登陆的流程:
    1.打开客户端(废话)
    2.选择网卡,申请服务(此时客户端会向认证服务器发送第一个数据包,我们叫它服务包)
    3.填写正确的用户名和密码,登陆(此时客户端会向服务器发送第二个数据包,认证包)
    4.此后每隔三十秒,客户端会向服务器发送保持登陆的数据
     (这就是第三个包,连接包,根据收到的反馈信息,由客户端会随机产生)
    5.在客户端运行过程中,它会不断检验代理和BT软件的运行,一旦发现,主动断开连接。
  
  有上述分析,可以看到,路由器的封禁是在第3步实现的(通过读取网卡信息),而代理和BT的封禁在第5步实现。
  
  二、弱点分析
  
    1.在认证方式的设计中,一旦登陆成功,其连接包将不会改变,
      所以只要截下此包,以三十秒的周期向服务器发送,便可以强行关闭客户端,其代理和BT封禁便迎刃而解。
    2.对于路由器的封锁稍微麻烦些,关键是正确登陆信息的取得和发出,和保证客户端算出对应的连接信息,
      所以在此过程中,不可以抛开客户端。
  
  三、作战部署
  
  由前面的分析可以看到,破解的核心在于截包(网络嗅探)。
  通常的编程方法是利用wincap等开发库监视底层通讯,或者将网卡置于混杂模式,过滤通过其所有数据。
  但这些方法太面向底层,复杂且开销过大。杀鸡焉用牛刀?
  
  对付小小的神州数码,决定采用一种较为轻便优雅的方法----API拦截(或叫API钩子)。
  因为在Windows平台上,程序的功能最终都是通过调用Windows API来实现的。
  如这里的网络通讯,收发都使用了UDP协议,典型的由Windows API函数sendto和recvfrom来实现。
  所以截获了这两个函数,就能轻松完成截包工作。
  (这样,我们的任务就和大多数网游外挂要完成的任务相仿了)
  
  不过,要截获API函数,也不是一件简单的工作。
  因为API钩子的编写不像键盘钩子(用于偷密码),鼠标钩子(察看*密码、屏幕取词等)有现成的钩子函数可以使用,
  它是一种非常规编程技术。说白了,就是要我们自己的代码在别的程序的空间内运行。
  我们知道,在Windows平台下,使用了虚拟地址空间技术将不同进程的代码和数据分离,
  但dll文件在整个内存中是共享的,所以,通过使目标进程加载我们自己编写的dll文件,
  就有可能让自己的代码在目标进程中执行,这就是所谓的dll注入技术。
  
  四、瞒天过海
  
  我们提到要使用dll注入技术来完成我们的工作。
  幸好,Windows API中为我们提供了一个美妙的函数CreateRemoteThread,恰好可以为我们所用。
  这个函数用来在目标进程中运行一个线程,而如果这个线程所完成的任务就是加载我们的dll文件,
  那注入的任务不就正好搞定了吗?
  但是还有一个问题,我们可以让目标进程运行LoadLibraryA这个函数,因为它在每个进程空间里的地址都是一样的,
  但我们的dll路径如何传给目标呢?
  不用急,Windows也为我们准备好了,
  VirtualAllocEx和WriteProcessMemory函数正好完成了在目标进程里内存分配和写入数据的任务。
  这样,只要三个函数配合使用,目标进程就乖乖的加载好了我们的dll。
  
  BOOL WINAPI InjectLib(DWORD dwProcessId, LPCSTR strLibFile)
  {
    BOOL result=FALSE;
    HANDLE hProcess=NULL, hThread=NULL;
    LPCSTR strLibFileRemote=NULL;
    __try
    {
      hProcess=OpenProcess(
        PROCESS_CREATE_THREAD|
        PROCESS_VM_OPERATION |
        PROCESS_VM_WRITE,
        FALSE,dwProcessId);
      if(hProcess==NULL)__leave;
  
      int cch=1+strlen(strLibFile);
      int cb= cch*sizeof(CHAR);
  
      strLibFileRemote=(LPCSTR)
        VirtualAllocEx(hProcess,NULL,cb,MEM_COMMIT,PAGE_READWRITE);
      if(strLibFileRemote==NULL)__leave;
  
      if(!WriteProcessMemory(hProcess,(PVOID)strLibFileRemote,
        (PVOID)strLibFile,cb,NULL))__leave;
  
      PTHREAD_START_ROUTINE pfnThreadRtn=(PTHREAD_START_ROUTINE)
        GetProcAddress(GetModuleHandle("Kernel32"),"LoadLibraryA");
      if(pfnThreadRtn==NULL)__leave;
  
      hThread=CreateRemoteThread(hProcess,NULL,0,pfnThreadRtn,(PVOID)strLibFileRemote,0,NULL);
      if(hThread==NULL)__leave;
  
      WaitForSingleObject(hThread,INFINITE);
  
      result=TRUE;
    }
    __finally
    {
      if(strLibFileRemote!=NULL)
        VirtualFreeEx(hProcess,(PVOID)strLibFileRemote,0,MEM_RELEASE);
      if(hThread!=NULL)
        CloseHandle(hThread);
      if(hProcess!=NULL)
        CloseHandle(hProcess);
    }
    return result;
  }
  
  
  注意,现在只是加载,并没有运行。不过我们知道,每次dll被加载时,它的DllMain函数都会被调用,
  是的,聪明的你应该已经想到,我们的劫持代码就是在这儿被调用的。
  
  BOOL APIENTRY DllMain( HANDLE hModule, 
              DWORD  ul_reason_for_call, 
              LPVOID lpReserved
              )
  {
    switch(ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
      {
        //MessageBox(NULL,"Attached to Process","Caution",MB_OK);
        WORD wVersionRequested;
        WSADATA wsaData;
        int err;
         wVersionRequested = MAKEWORD(2,2);
   
        err = WSAStartup( wVersionRequested, &wsaData );
        if(err)MessageBox(NULL,"WSAStartup Error","Error",MB_OK);
  
        s=socket(AF_INET,SOCK_DGRAM,0);
        MyFunc();     //这里面就是我们的劫持代码
      }
    case DLL_PROCESS_DETACH:
      {
        //MessageBox(NULL,"Detached from Process","Caution",MB_OK);
      }
    case DLL_THREAD_ATTACH:
      {
        //MessageBox(NULL,"Attached to Thread","Caution",MB_OK);
      }
    case DLL_THREAD_DETACH:
      {
        //MessageBox(NULL,"Detached from Thread","Caution",MB_OK);
      }
    }
      return TRUE;
  }
  
  
  下面来关注劫持的问题。大家知道,Windows的每个进程都会维护一张虚拟函数地址表。
  程序显示链接的函数地址都保存在这张表中。
  所以,只要修改了这张表,就可以使得对API函数的调用编程对我们自己函数的调用(amazing, huh?)。
  于是话不多说,改!
  可是,我们会郁闷的发现,由于加壳的缘故,
  神州数码的客户端并没有使用显示链接到SendTo函数(就是我们要劫持的那个,记得吗),
  而是用LibraryA函数动态加载。所以,修改虚拟函数的方法失效了。
  
  真的没办法了吗?当然不是。不然我还写这篇东西干吗呢?
  凡走过比留下痕迹。改还是要改的,不过不改SendTo了,而是改GetProcAddress。
  因为动态加载通过它来返回函数地址,只要我们把对SendTo的加载返回成自己函数的地址,瞒天过海之计就成了。
  
  extern "C" __declspec(dllexport) void MyFunc()
  {
    HOOKAPI a;
    LPHOOKAPI pHookApi=&a;
    HMODULE hm1=(HMODULE)0x00400000;
  
    pHookApi->szFunc=(LPCSTR)"GetProcAddress";
    pHookApi->pNewProc=(PROC)MyGetProcAddress;
    HookAPIByName(hm1,"kernel32.dll",pHookApi);
      
    pHookApi->szFunc=(LPCSTR)"LoadLibraryA";
    pHookApi->pNewProc=(PROC)MyLoadLibrary;
    HookAPIByName(hm1,"kernel32.dll",pHookApi);  //这里劫持了LoadLibraryA函数
  }
  
  typedef struct tag_HOOKAPI 
  { 
    LPCSTR szFunc;//待HOOK的API函数名 
    PROC pNewProc;//新的函数指针 
    PROC pOldProc;//老的函数指针 
  }HOOKAPI, *LPHOOKAPI; 
  
  PIMAGE_IMPORT_DESCRIPTOR GetNamedImportDescriptor(HMODULE hModule, LPCSTR szImportMod) 
  { 
    //首先是DOS头 
    PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER) hModule; 
    if(pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE) return NULL; 
    PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDOSHeader + (DWORD)(pDOSHeader->e_lfanew)); 
    if(pNTHeader->Signature != IMAGE_NT_SIGNATURE) return NULL; 
    //如果没有Import部分,返回失败 
    if(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress == 0) 
      return NULL; 
    //取Import部分 
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR) 
      ((DWORD)pDOSHeader + (DWORD)(pNTHeader->OptionalHeader. 
      DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress)); 
    //寻找与szImportMod相配部分 
    while (pImportDesc->Name) 
    { 
      PSTR szCurrMod = (PSTR)((DWORD)pDOSHeader + (DWORD)(pImportDesc->Name)); 
      if (stricmp(szCurrMod, szImportMod) == 0) 
        break; //找到 
      pImportDesc++; 
    } 
    if(pImportDesc->Name == NULL) return NULL; 
    return pImportDesc; 
  } 
  
  HookAPIByName(HMODULE hModule/*被HOOK的目标进程MODULE*/, LPCSTR szImportMod/*如GDI32.DLL*/,LPHOOKAPI pHookApi/*指定函数
  名,如"MessageBoxW"*/) 
  { 
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc = 
      GetNamedImportDescriptor(hModule, szImportMod); 
    if (pImportDesc == NULL) 
      return FALSE; //需要改换的API不能取到正确描
    PIMAGE_THUNK_DATA pOrigThunk = (PIMAGE_THUNK_DATA)((DWORD)hModule + (DWORD)(pImportDesc->OriginalFirstThunk)); 
    PIMAGE_THUNK_DATA pRealThunk = 
      (PIMAGE_THUNK_DATA)((DWORD)hModule + (DWORD)(pImportDesc->FirstThunk)); 
    while(pOrigThunk->u1.Function) 
    { 
      if((pOrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG) != IMAGE_ORDINAL_FLAG) 
      { 
        PIMAGE_IMPORT_BY_NAME pByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)hModule + (DWORD)
  (pOrigThunk->u1.AddressOfData)); 
        if(pByName->Name[0] == '\0') 
          return FALSE; //失败 
        if(strcmpi(pHookApi->szFunc, (char*)pByName->Name) == 0) 
        { 
          //改变thunk保护属性 
          MEMORY_BASIC_INFORMATION mbi_thunk; 
          VirtualQuery(pRealThunk, &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION)); 
          VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize, PAGE_READWRITE, 
  &mbi_thunk.Protect); 
          //保存原来的API函数指针 
          if(pHookApi->pOldProc == NULL) 
            pHookApi->pOldProc = (PROC)pRealThunk->u1.Function; 
          //改变API函数指针 
          pRealThunk->u1.Function = (PDWORD)pHookApi->pNewProc; 
          //将thunk保护属性改回来 
          DWORD dwOldProtect; 
          VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize, 
            mbi_thunk.Protect, &dwOldProtect); 
        } 
      } 
      pOrigThunk++; 
      pRealThunk++; 
    } 
    SetLastError(ERROR_SUCCESS); 
    return TRUE; 
  } 
  
  
  五、实战演练
  
  现在万事俱备,只欠一个我们反复提到的----自己的函数就可以完成全部的任务了。
  那这个自己的函数要做些什么呢?其实很简单,就是把通过它发送的数据包一一记录下来备用即可。
  而记录的数据,将要被我们自己的程序所使用,所以存在dll的共享数据区里面。
  这是其一,对于解除代理和BT的封禁,这已经足够了。
  但要解决路由器的问题,还需要再加一点功能。
  我们会把正确的不带路由器的认证包(记为A包)存起来,然后在有路由器环境下,
  客户端会产生指示使用了路由器的认证包(记为B包),用A包代替B包发送出去,就起到了欺骗服务器的作用,
  解除了路由器的封禁。
  
  static int WINAPI Mysendto (
    SOCKET s,                        
    const char FAR * buf,            
    int len,                         
    int flags,                       
    const struct sockaddr FAR * to,  
    int tolen                        
  )
  {
    int ii=0;
    if(len!=26)
    {
      if(statue_client==0)
      {
        if(stat_authorize==0)
        {
          for(int tt=0;tt<len;tt++)
          {
            *(buf_authorize+tt)=*(buf+tt);
          }
          len_authorize=len;
          tolen_authorize=tolen;
          flags_authorize=flags;
          strcpy(to_authorize.sa_data,to->sa_data);
          to_authorize.sa_family=to->sa_family;
          stat_authorize=1;
          ii=::sendto(s,buf,len,flags,to,tolen);
        }
        else
        {
          ii=::sendto(s,buf_authorize,len_authorize,flags_authorize,&to_authorize,tolen_authorize);
        }
        statue_client=1;
      }
      else if(statue_client==1)
      {
        statue_client=2;
        for(int tt=0;tt<len;tt++)
        {
          *(buf_keepconn+tt)=*(buf+tt);
        }
        len_keepconn=len;
        tolen_keepconn=tolen;
        flags_keepconn=flags;
        strcpy(to_keepconn.sa_data,to->sa_data);
        to_keepconn.sa_family=to->sa_family;
        stat_keepconn=1;
        statue_client=3;
        ii=::sendto(s,buf,len,flags,to,tolen);
      }
    }
    else
    {
      for(int tt=0;tt<len;tt++)
      {
        *(buf_getservice+tt)=*(buf+tt);
      }
      len_getservice=len;
      tolen_getservice=tolen;
      flags_getservice=flags;
      strcpy(to_getservice.sa_data,to->sa_data);
      to_getservice.sa_family=to->sa_family;
      stat_getservice=1;
      ii=::sendto(s,buf,len,flags,to,tolen);
    }
    return ii;
  }
  
  
  六、屠城三日
  
  至此,神州数码的三大封禁已基本被我们解决,
  剩下来的工作,就是愉快的上网,愉快的用代理,愉快的用路由器,愉快的BT了。
  
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!
                                                       2006年05月13日 3:00:40

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功