现在的位置: 首页 > 综合 > 正文

一种注入进程,获得完全操控的方法之二

2013年05月30日 ⁄ 综合 ⁄ 共 7814字 ⁄ 字号 评论关闭
作 者: menting
时 间: 2007-12-12,16:50
链 接: http://bbs.pediy.com/showthread.php?t=56442

看到了我的之一,我想大家会很清楚,因为这个方法的最大的BUG就是在回调函数之后来处理,如果前面已经处理了,
那么我们就无法处理了。而且又要修改系统DLL,比较不完美。我最近又发现了两种方法:
  一、修改RegisterClassA:
  相对前面的方法来说,这个方法很好用,因为,我们可以很容易把回掉函数改掉。类似如下代码:
//---------------------------------------------------------------------------
int CMuGame::InitRegClass()
{  
  //获取模块句柄:
  HINSTANCE hModule=::GetModuleHandle("User32.dll");
  //获取原始函数地址:
  fpsRegisterClassA=(REGWINCLASS)::GetProcAddress(hModule,"RegisterClassA");

  //获取当前内存块的保护类型:
  MEMORY_BASIC_INFORMATION _base;
  ::VirtualQuery((LPVOID)((DWORD)hModule+0x1000),&_base,sizeof(MEMORY_BASIC_INFORMATION));

  //处理为读写类型:
  ::VirtualProtect(_base.BaseAddress,_base.RegionSize,PAGE_READWRITE,&_base.Protect);

  //获得导出表的地址和大小:
  DWORD   dwExportSize=NULL;
  PDWORD  pdwExportAddr=NULL;

  appfun.Load(hModule);
  appfun.GetExportTable(&dwExportSize,&pdwExportAddr);

  //查找RegisterClassA函数地址:
  for(DWORD i=0;i<dwExportSize;i++,pdwExportAddr++)
  {
    if((*pdwExportAddr+(DWORD)hModule)==(DWORD)fpsRegisterClassA)
    {
      //计算跳转地址:
      DWORD  dword=(DWORD)fpnRegisterClassA-(DWORD)fpsRegisterClassA;
    
      //声明结构对象:
      _ASM_JMP _jmp;
      _jmp._01cmd =0xE9;
      memcpy((PDWORD)_jmp._02dat,&dword,4);
      memcpy((LPVOID)((DWORD)fpsRegisterClassA-5),&_jmp,5);

      //修改导出表地址:
      DWORD  dwAddr=*pdwExportAddr-5;
      memcpy(pdwExportAddr,&dwAddr,4);

      break;
    }
  }

  //处理完成后,恢复保护类型:
  DWORD dwProctect=NULL;
  ::VirtualProtect(_base.BaseAddress,_base.RegionSize,_base.Protect,&dwProctect);
  return NULL;
}
//----------------------------------------------------------------------------

  当然,我们可以修改IAT表来实现,但是我们大家都明白,IAT表很不确定,有的程序还没有IAT表,我们必须找个通用
的方法,那就是直接把User32.dll的导出表修改掉,因此,我们让程序正常调用RegisterClassA这个之后,千万没想到的是,现
在的导处API函数的地址已经被修改,而在中途转到我们的DLL中,然后再回到真正的函数地址入口:

//-----------------------------------------------------------------------------

ATOM WINAPI fpnRegisterClassA(WNDCLASS *lpWndClass)
{
  //替换"XX"窗口的回调函数:
  if(lstrcmp(lpWndClass->lpszClassName,"XX")==0) //如果是我们要替换的窗口,就替换,不是就不替换
  {
    fpsWndProc=lpWndClass->lpfnWndProc;
    lpWndClass->lpfnWndProc=fpnWndProc;
  }
  ATOM  atom=fpsRegisterClassA(lpWndClass);
  return atom;
}

LRESULT CALLBACK fpnWndProc(HWND hDlg,UINT message,WPARAM wParam,LPARAM lParam)
{
  switch(message)
  {
  case WM_INITDIALOG:
    {
      //保存窗口句柄:
      theApp.game.hWnd=hDlg;
      //修改标题,方便我们调用:
      ::SetWindowText(hDlg,_T("[PhantomNet] XX"));  //我们把标题修改掉,方便通信,可以不修改的。
      break;
    }
  case WM_CMD:   //自定义命令:
    {
      if((DWORD)wParam==0x01){PostQuitMessage(0);} //收到命令,我们直接用API让程序结束。
      if((DWORD)wParam==0x02){}   //……什么操作都可以
      break;
    }
  default:{break;}
  }
  //转到游戏的回调函数:
  return fpsWndProc(hDlg,message,wParam,lParam);
}

//----------------------------------------------------------------------------------------

上面的代码就是实现修改RegisterClassA注册类,来转到我们的回调函数的。我们的回调函数,是在真正的回调函数
之前就被执行的,我们可以截获部分消息,让真正的回调函数收不到,因为,我们事先已经替它处理了事件。这样就避免了部
分消息收不到,但是这样的方法,是在程序没有启动,而随程序启动而加载……,我们就没办法对现有的进程进行操作。

  二、直接替换回调函数:
  怎么能直接替换呢?很多朋友都会想改这个改那个,实际KS给我们4个函数来让我们做这个工作。但是局限在同一进程,
//----------------------------------------------
GetClassLong(HWND hWnd,int Index);
GetWindowLong(HWND hWnd,int Index);
SetClassLong(HWND hWnd,int nIndex,LONG dwNewLong);
SetWindowLong(HWND hWnd,int nIndex,LONG dwNewLong);
//----------------------------------------------
  有必要对这两类函数做说明,GetClassLong和SetClassLong在RegisterClassA这个函数运行时才起作用,而GetWindowLong
和SetWindowLong在CreateWindowA后才有作用。我们完全可以不用CreateRemoteThread这个来触发,我们的程序。我们在替换回调
函数时可以在其他进程对目标进程处理,我们只需要把程序写入到目标进程,我们没必要专门去创建一条线程来加载DLL,我们直接
把它写进去后,再让某个消息来触发加载。那样就会隐蔽点,这里简要说一下,为了安全期间不贴代码了。

//--------------------------------------------------

int CMgMuAssistantApp::LoadMain()
{
  //初始化SOCKET:
  if (!AfxSocketInit())
  {
    AfxMessageBox("Error InitSocket!");
  }
  //查找窗口是否存在:
  pWnd=CWnd::FindWindow(NULL,"XX");
  if(pWnd!=NULL)
  {
    //由于窗口有可能在创建前被找到,所以我们完善期间不用GetWindowLong,
    //保存原始窗口回调函数:
    fpsWndProc=(WNDPROC)::GetClassLong(pWnd->m_hWnd,GCL_WNDPROC);

    //由于SetClassLong只在注册类时起作用,我们必须用SetWindowLong;
    //修改回调函数:
    ::SetWindowLong(pWnd->m_hWnd,GWL_WNDPROC,(LONG)fpnWndProc);  

    //修改Socket函数:
    hooksocket.InitHookApi(::GetModuleHandle(NULL),0x00376000,0x0568);

    //装载OpenGL:
    PDWORD pdwAddr=(PDWORD)((DWORD)::GetModuleHandle(NULL)+0x00375A00);
    *pdwAddr=(DWORD)fpnRender;
    pdwAddr  =(PDWORD)((DWORD)::GetModuleHandle(NULL)+0x002B5E69);
    *pdwAddr=(DWORD)((DWORD)::GetModuleHandle(NULL)+0x00375A00);
  }

  return 0;
}

//---------------------------------------------------
  上面的代码是我针对某个游戏做的,由于游戏是基于OpenGL的,所以我就把OpenGL的Render修改下来,来处理我的代码
比如,我们要用到程序内部函数:

//-----------------------------------------------------------
//原程序是如下的调用方法:
00664B75  |.  50            push    eax
00664B76  |.  8D55 8C       lea     edx, dword ptr [ebp-74]
00664B79  |.  52            push    edx
00664B7A  |.  A1 181E9E07   mov     eax, dword ptr [79E1E18]
00664B7F  |.  83C0 14       add     eax, 14
00664B82  |.  50            push    eax
00664B83  |.  8B0D 141E9E07 mov     ecx, dword ptr [79E1E14]
00664B89  |.  83C1 23       add     ecx, 23
00664B8C  |.  51            push    ecx
00664B8D  |.  E8 8E7CF2FF   call    0058C820

//我把它做成C函数调用,这样我们就可以直接用这个函数来调用程序内部函数了:
int CMgMuAssistantApp::SetMainText(int x,int y,LPSTR str,int unk1,int unk2,int unk3)
{
  ShowText=(MAINTEXT)0x0058C820;
  ShowText(x,y,str,unk1,unk2,unk3);
  return 0;
}

//---------------------------------------------------------------------

下面是我的回调函数,我们发现我们再发送命令给目标程序时,目标程序就乖乖的按我们说的做,让它放那,它就放那,让
它返回什么,它就乖乖返回什么,我们可以设置更多的命令来对程序操作:
//--------------------------------------------------------------------
LRESULT CALLBACK fpnWndProc(HWND hDlg,UINT message,WPARAM wParam,LPARAM lParam)
{
  //保存窗口句柄:
  theApp.game.hWnd=hDlg;

  switch(message)
  {
  case WM_INITDIALOG:{break;}
  case WM_PAINT:{break;}
  case WM_CMD:
    {
      if((DWORD)wParam==0x01){PostQuitMessage(0);}
      //返回的是主程序句柄:
      if((DWORD)wParam==0x02){
        //保存主程序句柄:
        theApp.m_hWndMainApp=(HWND)lParam;
        //返回游戏句柄:
        ::SendMessage(theApp.m_hWndMainApp,WM_CMD,0x02,(LPARAM)hDlg);
      }
      if((DWORD)wParam==0x08){
        //连接主程序,这里我创建了SOCKET来网络传输数据:
        theApp.m_uPortMainApp=(UINT)lParam;
        pClient=new CClient;
        pClient->Close();
        pClient->Create();
        if(lstrcmp(theApp.m_strAddrMainApp,"")==0)
        {
          //获取主机地址:
          char hostname[MAX_PATH]={0};
          ::gethostname(hostname,MAX_PATH);
          hostent *addr=::gethostbyname(hostname);
          theApp.m_strAddrMainApp=inet_ntoa(*(struct in_addr *)addr->h_addr_list[0]);
        }
        if(pClient->Connect(theApp.m_strAddrMainApp,theApp.m_uPortMainApp)==FALSE)
        {
          ::SendMessage(theApp.m_hWndMainApp,WM_CMD,0x08,(LPARAM)-1);
        }else{::SendMessage(theApp.m_hWndMainApp,WM_CMD,0x08,(LPARAM)1);}
      }
      if((DWORD)wParam==0x10){    
        //修改标题,方便我们调用:
        ::SetWindowText(hDlg,_T("[MG] MU"));
      }
      if((DWORD)wParam==0x11){
        CRect rect;
        //获取窗口矩形:
        ::GetWindowRect(hDlg,&rect);
        //移动窗口:
        ::MoveWindow(hDlg,LOWORD(lParam),HIWORD(lParam),rect.Width(),rect.Height(),TRUE);
      }
      return TRUE; //直接返回,不让游戏回调处理,这个是我们的命令机密不能让程序知道:
    }
  case WM_COPYDATA:  //我们替我们的目标程序处理进程间通信消息,为了方便点:)
    {
      //wParam:窗口句柄
      //lParam:数据
      theApp.OnCopyData(CWnd::FromHandle((HWND)wParam),(COPYDATASTRUCT*)lParam); return TRUE;
    }
  default:{break;}
  }
  //转到游戏的回调函数:
  return fpsWndProc(hDlg,message,wParam,lParam);
}

//-----------------------------------------------------------------------------------------------

  实际这回是在上回的基础而来的,我们总结一下就知道,当我们完全操作了回调函数时,一个应用程序就几乎成了你
手中的蛋糕,想怎么样吃就怎么样吃:).KS在进程的通信和相互性上没做多少,我们很多时候都要用CreateRemoteThread这个
函数来做DLL的加载,实际不用它的方法还是有很多的,只是没有被人发现而已,同过前面的说明,我想大家都会明白了,怎么
样把一个程序完全操控,但是回过头来一想,如果我们对一个进程做了完全操作,我们可以再用这个进程操作其它的进程,那么
会是烦琐点,但是是件很让人恐怖的事。前面的这个回调函数处理的事情很少,因为,我没把它做的有多强,我不想把它做多强
实际,当一个进程的回调函数交给你来处理,那么我们就可以做很多我们想做的事。当我们将DLL放入某个进程时,我们就可以
调用它的任意一个函数,来为我们工作,我们只需要做影射,把我们要调用的函数和地址影射到我们的DLL中,然后再加工~~~~

:)^^^^
 

抱歉!评论已关闭.