近期闲来无事,碰巧老婆迷上了qq的连连看游戏,我看她在那里连的是在辛苦,并且水平实在太菜,每盘都输掉好惨,没办法,好歹我也是科班出身的计算机人员,发挥我的特长的时候到了~~!
    腾讯的软件产品是都没有加壳的,不知道这是好是坏,反正对于开发或破解人员来说是好事了。用Peid查连连看的可执行程序kyodaiRPG.exe,发现Microsoft Visual C++ 6.0 [Debug],想来也是,开发游戏当然vc是首选了。
开发这个外挂之前,当然要做一番分析。首先我们的目标是让连连看达到自动消去的功能,以前我也下过一个连连看的外挂,实际上是给出一个提示,需要玩家来根据提示消去,这个太麻烦,我们需要做的是自动消去,这个显然是可行的,给某一窗口加上定时器就行了。其次怎么样形成一个当前图标迷宫的问题,这个方法很多了,比较初级的是根据图标特征点取色,这个显然不好,连连看窗口不能被挡住不说,取色的特征点还要去慢慢试,我们想到连连看的图标迷宫在本地内存中肯定也会存放,我们只要找到这个地址,使用ReadProcessMemory取得后形成一个二维数组,用一定的寻路算法分析,就可以找到能够相连的2个点。至于如何进入连连看游戏内存,以及模拟鼠标移动,点击,网上资料都很多,参考一下就可以了,不过需要注意的是,模拟鼠标的最好方式是采用PostMessage,这样你将连连看的窗口最小化都没问题的。
废话不多说,开始行动来。首先登录qq游戏,找个连连看房间坐下后,连连看游戏出来了,使用OD附加。由于连连看提供了练习功能,上面提到的找图标迷宫内存基址使用练习来找方便了许多。
由于点‘练习’后每次出现的迷宫都不一样,显然是调用了rand随机生成,rand就是我们开始探索的入口了.
Bp rand , 然后我们走到kyodaiRPG.exe的领空,很容易发现以下形式的代码:

004563D3  |> /8B55 E0       /mov     edx, dword ptr [ebp-20]
004563D6  |. |83C2 01       |add     edx, 1
004563D9  |. |8955 E0       |mov     dword ptr [ebp-20], edx
004563DC  |> |8B45 E0        mov     eax, dword ptr [ebp-20]
004563DF  |. |3B45 FC       |cmp     eax, dword ptr [ebp-4]
004563E2  |. |7D 49         |jge     short 0045642D
004563E4  |. |FF15 84C74900 |call    dword ptr [<&MSVCRT.rand>]      ; [rand
004563EA  |. |25 07000080   |and     eax, 80000007
004563EF  |. |79 05         |jns     short 004563F6
004563F1  |. |48            |dec     eax
004563F2  |. |83C8 F8       |or      eax, FFFFFFF8
004563F5  |. |40            |inc     eax
004563F6  |> |05 F0000000   |add     eax, 0F0
004563FB  |. |8845 D8       |mov     byte ptr [ebp-28], al
004563FE  |. |8B4D FC       |mov     ecx, dword ptr [ebp-4]
00456401  |. |2B4D E0       |sub     ecx, dword ptr [ebp-20]
00456404  |. |D1E1          |shl     ecx, 1
00456406  |. |8B55 F4       |mov     edx, dword ptr [ebp-C]
00456409  |. |2BD1          |sub     edx, ecx
0045640B  |. |8B45 F8       |mov     eax, dword ptr [ebp-8]
0045640E  |. |8A4D D8       |mov     cl, byte ptr [ebp-28]
00456411  |. |880C10        |mov     byte ptr [eax+edx], cl
00456414  |. |8B55 FC       |mov     edx, dword ptr [ebp-4]
00456417  |. |2B55 E0       |sub     edx, dword ptr [ebp-20]
0045641A  |. |D1E2          |shl     edx, 1
0045641C  |. |8B45 F4       |mov     eax, dword ptr [ebp-C]
0045641F  |. |2BC2          |sub     eax, edx
00456421  |. |8B4D F8       |mov     ecx, dword ptr [ebp-8]
00456424  |. |8A55 D8       |mov     dl, byte ptr [ebp-28]
00456427  |. |885401 01     |mov     byte ptr [ecx+eax+1], dl
0045642B  |.^\EB A6         \jmp     short 004563D3
0045642D  |>  C745 E0 00000>mov     dword ptr [ebp-20], 0
00456434  |.  EB 09         jmp     short 0045643F
00456436  |>  8B45 E0       /mov     eax, dword ptr [ebp-20]
00456439  |.  83C0 01       |add     eax, 1
0045643C  |.  8945 E0       |mov     dword ptr [ebp-20], eax
0045643F  |>  817D E0 C8000> cmp     dword ptr [ebp-20], 0C8
00456446  |.  7D 3F         |jge     short 00456487
00456448  |.  8B45 E0       |mov     eax, dword ptr [ebp-20]
0045644B  |.  99            |cdq
0045644C  |.  F77D F4       |idiv    dword ptr [ebp-C]
0045644F  |.  8955 E4       |mov     dword ptr [ebp-1C], edx
00456452  |.  FF15 84C74900 |call    dword ptr [<&MSVCRT.rand>]      ; [rand
00456458  |.  99            |cdq
00456459  |.  F77D F4       |idiv    dword ptr [ebp-C]
0045645C  |.  8955 DC       |mov     dword ptr [ebp-24], edx
0045645F  |.  8B4D F8       |mov     ecx, dword ptr [ebp-8]
00456462  |.  034D DC       |add     ecx, dword ptr [ebp-24]
00456465  |.  8A11          |mov     dl, byte ptr [ecx]
00456467  |.  8855 E8       |mov     byte ptr [ebp-18], dl
0045646A  |.  8B45 F8       |mov     eax, dword ptr [ebp-8]
0045646D  |.  0345 DC       |add     eax, dword ptr [ebp-24]
00456470  |.  8B4D F8       |mov     ecx, dword ptr [ebp-8]
00456473  |.  034D E4       |add     ecx, dword ptr [ebp-1C]
00456476  |.  8A11          |mov     dl, byte ptr [ecx]
00456478  |.  8810          |mov     byte ptr [eax], dl
0045647A  |.  8B45 F8       |mov     eax, dword ptr [ebp-8]
0045647D  |.  0345 E4       |add     eax, dword ptr [ebp-1C]
00456480  |.  8A4D E8       |mov     cl, byte ptr [ebp-18]
00456483  |.  8808          |mov     byte ptr [eax], cl
00456485  |.^ EB AF         \jmp     short 00456436
00456487  |>  C745 EC 00000>mov     dword ptr [ebp-14], 0
0045648E  |.  C745 F0 00000>mov     dword ptr [ebp-10], 0
00456495  |.  EB 09         jmp     short 004564A0
00456497  |>  8B55 F0       /mov     edx, dword ptr [ebp-10]
0045649A  |.  83C2 01       |add     edx, 1
0045649D  |.  8955 F0       |mov     dword ptr [ebp-10], edx
004564A0  |>  837D F0 0B     cmp     dword ptr [ebp-10], 0B
004564A4  |.  7D 59         |jge     short 004564FF
004564A6  |.  C745 D4 00000>|mov     dword ptr [ebp-2C], 0
004564AD  |.  EB 09         |jmp     short 004564B8
004564AF  |>  8B45 D4       |/mov     eax, dword ptr [ebp-2C]
004564B2  |.  83C0 01       ||add     eax, 1
004564B5  |.  8945 D4       ||mov     dword ptr [ebp-2C], eax
004564B8  |>  837D D4 13    | cmp     dword ptr [ebp-2C], 13
004564BC  |.  7D 3F         ||jge     short 004564FD
004564BE  |.  8B4D C8       ||mov     ecx, dword ptr [ebp-38]
004564C1  |.  8B51 04       ||mov     edx, dword ptr [ecx+4]
004564C4  |.  8B45 F0       ||mov     eax, dword ptr [ebp-10]
004564C7  |.  6BC0 13       ||imul    eax, eax, 13
004564CA  |.  0355 D4       ||add     edx, dword ptr [ebp-2C]
004564CD  |.  33C9          ||xor     ecx, ecx
004564CF  |.  8A4C10 08     ||mov     cl, byte ptr [eax+edx+8]
004564D3  |.  85C9          ||test    ecx, ecx
004564D5  |.  74 24         ||je      short 004564FB
004564D7  |.  8B55 C8       ||mov     edx, dword ptr [ebp-38]
004564DA  |.  8B42 04       ||mov     eax, dword ptr [edx+4]
004564DD  |.  8B4D F0       ||mov     ecx, dword ptr [ebp-10]
004564E0  |.  6BC9 13       ||imul    ecx, ecx, 13
004564E3  |.  0345 D4       ||add     eax, dword ptr [ebp-2C]
004564E6  |.  8B55 F8       ||mov     edx, dword ptr [ebp-8]
004564E9  |.  0355 EC       ||add     edx, dword ptr [ebp-14]
004564EC  |.  8A12          ||mov     dl, byte ptr [edx]
004564EE  |.  885401 08     ||mov     byte ptr [ecx+eax+8], dl
004564F2  |.  8B45 EC       ||mov     eax, dword ptr [ebp-14]
004564F5  |.  83C0 01       ||add     eax, 1
004564F8  |.  8945 EC       ||mov     dword ptr [ebp-14], eax
004564FB  |>^ EB B2         |\jmp     short 004564AF
004564FD  |>^ EB 98         \jmp     short 00456497

这段代码大概的意思就是形成这么一个二维数组的迷宫,具体的形成方式有兴趣的朋友可以逆逆看,其实我们只是找这个谜宫基址,倒没必要花这个时间。我们看到标红处的代码,这个就是二维数组的一个标准的寻址方式,我们主要观察内存这个地址的内容,经过多次的点击‘练习’,发现确实这个地址就是二维迷宫的基址,我找出来的是0x0012A50C,在迷宫中,每个图标代表的函数一用一个字节大小来表示,空的位置为0x00.
  找到基址后,就可以形成用我们自定义的数据结构来表示这个迷宫里。我们这样来定义:
//图标结构
typedef struct{
  int X;
  int Y;
  BYTE IconType;
}CIconCell;
这个结构代表迷宫中的一个图标,X,Y为这个图标相对于连连看窗口的像素偏移大小,以便后面模拟鼠标用到。IconType为这个图标的类型,由于是连连看,具体图标代表什么类型我们就不关心了。

下面是得到图标迷宫二维数组的函数
void GenIconMatrix()
{
  
  char lpBuffer[210]; //19 * 11 + 1

  memset(lpBuffer,0,sizeof(lpBuffer));

  hWnd = ::FindWindow(NULL,WND_NAME);
  tID = ::GetWindowThreadProcessId(hWnd,&pID);//取得连连看窗口进程
  //从图标基址处读,形成数组
  HANDLE hProcess = OpenProcess(PROCESS_VM_READ,NULL,pID);
  if (!ReadProcessMemory(hProcess,(LPCVOID)BASE_ICON_ARRAY,lpBuffer,209,NULL))
  {
    WriteLog(ERR_LVL,"ReadProcessMemory 失败!");
    return;
  }
  for(int i=0;i<=208;i++)
  {
    //WriteLog(ERR_LVL,"lpBuffer[%d] -- [%x]",i,(unsigned char)lpBuffer[i]);
    CellMatrix[i/19][i%19].X = INIT_X + (ICON_W/2)*(2*(i%19+1)-1);
    CellMatrix[i/19][i%19].Y = INIT_Y + (ICON_H/2)*(2*(i/19+1)-1);
    CellMatrix[i/19][i%19].IconType = lpBuffer[i];
    
  }

  //  PrintIconMatrix();

//init find path array
InitArr(); 
  return;
}

得到迷宫二维数组后,我们就可以根据一定的寻路算法来找到能够相连的两点,在这里我只大概介绍一下寻路的算法,这个网上也介绍的很多了,有更好的算法的朋友请跟帖提点一二。
连连看寻路算法:
1.  按上下左右递归每一个图标,从一个结点到另一个节点,如要有一步不能走下去了,就再退回上个结点,向别的方向发展,都不行,就再退回上级结点,再向别的方向发展。
2.  那么第二步就是为(e,s,w,n)四个方向加权,也就是让它们之间有一个优先权,说白了就是先试哪一条路。
3.  .这一条路不能有两个以上的拐角(corner)按照面向对象的思想,很自然的,我给每个在递归算法中生成的位置结点加上了个corner的属性,来记录这条路到目前为止拐了几个角。

好了,到这里这个连连看外挂的核心问题已经被我们解决了,下面就是如何实现了。我这里的设计思想是写一个dll,用远程注入的方式使它进入到连连看的进程,在连连看进程中注册一个热键,用来呼出一个窗口(窗口的资源是在dll中,这里涉及到在dll中使用资源的问题)来控制我们的外挂。至于鼠标的模拟,我们采用PostMessage的方式.下面结合代码简单介绍一下:
1. dll远程注入,直接copy了Jeffrey Richter的《windows核心编程》中的代码,呵呵

BOOL WINAPI InjectLibW(DWORD dwProcessId, PCWSTR pszLibFile) {

   BOOL fOk = FALSE; // Assume that the function fails
   HANDLE hProcess = NULL, hThread = NULL;
   PWSTR pszLibFileRemote = NULL;

   __try {
      // Get a handle for the target process.
      hProcess = OpenProcess(
         PROCESS_QUERY_INFORMATION |   // Required by Alpha
         PROCESS_CREATE_THREAD     |   // For CreateRemoteThread
         PROCESS_VM_OPERATION      |   // For VirtualAllocEx/VirtualFreeEx
         PROCESS_VM_WRITE,             // For WriteProcessMemory
         FALSE, dwProcessId);
      if (hProcess == NULL) __leave;

      // Calculate the number of bytes needed for the DLL's pathname
      int cch = 1 + lstrlenW(pszLibFile);
      int cb  = cch * sizeof(WCHAR);

      // Allocate space in the remote process for the pathname
      pszLibFileRemote = (PWSTR) 
         VirtualAllocEx(hProcess, NULL, cb, MEM_COMMIT, PAGE_READWRITE);
      if (pszLibFileRemote == NULL) __leave;

      // Copy the DLL's pathname to the remote process's address space
      if (!WriteProcessMemory(hProcess, pszLibFileRemote, 
         (PVOID) pszLibFile, cb, NULL)) __leave;

      // Get the real address of LoadLibraryW in Kernel32.dll
      PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
         GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
      if (pfnThreadRtn == NULL) __leave;

      // Create a remote thread that calls LoadLibraryW(DLLPathname)
      hThread = CreateRemoteThread(hProcess, NULL, 0, 
         pfnThreadRtn, pszLibFileRemote, 0, NULL);
      if (hThread == NULL) __leave;

      // Wait for the remote thread to terminate
      WaitForSingleObject(hThread, INFINITE);

      fOk = TRUE; // Everything executed successfully
   }
       __finally { // Now, we can clean everthing up

      // Free the remote memory that contained the DLL's pathname
      if (pszLibFileRemote != NULL) 
         VirtualFreeEx(hProcess, pszLibFileRemote, 0, MEM_RELEASE);

      if (hThread  != NULL) 
         CloseHandle(hThread);

      if (hProcess != NULL) 
         CloseHandle(hProcess);
    }

        return(fOk);
}

2.注册热键,首先我们修改连连看的窗口过程,加入自己对某些消息的处理
  //更改对对碰窗口的WndProc,增加定时器
  oldproc=(WNDPROC)GetWindowLong(hWnd,GWL_WNDPROC);
SetWindowLong(hWnd,GWL_WNDPROC,(long)MyMsgProc);

窗口过程的回调函数如下:
LRESULT CALLBACK MyMsgProc(HWND hwnd,UINT umsg,WPARAM wparam,LPARAM lparam)
{
  //消息过滤
  //  int xPos,yPos;
  //  CString csx,csy,cscol;
  //
  int x1 = 0;
  int y1 = 0;
  int x2 = 0;
  int y2 = 0;

  switch (umsg)
  {
  case WM_TIMER:
    if (wparam == IDT_MYTIMER){
      
      //用一个bool变量来控制只注册一次热键
      if (!isReg){
        //使用RegisterHotKey注册热键
      if (::RegisterHotKey(hWnd,GlobalAddAtom("iloveyanzi"),NULL,VK_F11) )
//WriteLog(ERR_LVL,"RegisterHotKey成功!!");
        isReg = true;
      }
    }
    if (wparam == IDT_MYTIMER1){
      if (FindPair(&x1,&y1,&x2,&y2)){
        MouseClick(x1,y1);
        MouseClick(x2,y2);
      }
    }
  
    
    break;
  case WM_HOTKEY:
    WriteLog(ERR_LVL,"recv HotKey Msg!");
    //呼出外挂窗口
    
    if(pCWndWGMain == NULL){
    //  __asm int 3;  
      pCWndWGMain = new CKyoDaiHackDlg();

      //注意由于是使用dll中的窗口资源来建立窗口,需要做一个转换
      HINSTANCE save_hInstance = AfxGetResourceHandle(); 
      AfxSetResourceHandle(hHookDll);

      //创建"外挂呼出窗口"时把游戏窗口作为他的父窗口
BOOL ret =  pCWndWGMain->Create(MAKEINTRESOURCE(IDD_HACK_DIALOG),CWnd::FromHandle(hWnd));
      if(!ret)   //Create failed.
        AfxMessageBox("Error creating Dialog");
          
      pCWndWGMain->ShowWindow(SW_SHOW);
      ((CComboBox*)pCWndWGMain->GetDlgItem(IDC_COMBO1))->SetCurSel(2);
      //回复原先的资源句柄
      AfxSetResourceHandle(save_hInstance); 
          
    }
    else{
//显示 or 隐藏  pCWndWGMain->ShowWindow(pCWndWGMain->IsWindowVisible() ? SW_HIDE : SW_SHOW);
    
    }
    break;

  }
  return CallWindowProc(oldproc,hwnd,umsg,wparam,lparam);
}

3.模拟鼠标点击

//点击指定位置(i行j列)的Icon
void MouseClick(int i,int j)
{
  ::PostMessage(hWnd,WM_SETCURSOR,WPARAM(hWnd),MAKELPARAM(HTCLIENT,WM_MOUSEMOVE));
  ::PostMessage(hWnd,WM_MOUSEMOVE,NULL,MAKELPARAM(CellMatrix[i][j].X,CellMatrix[i][j].Y));
  ::PostMessage(hWnd,WM_LBUTTONDOWN,MK_LBUTTON,MAKELPARAM(CellMatrix[i][j].X,CellMatrix[i][j].Y));
  ::PostMessage(hWnd,WM_LBUTTONUP,NULL,MAKELPARAM(CellMatrix[i][j].X,CellMatrix[i][j].Y));
}

到这里,这个外挂所用到的所有技术就展现给大家了。希望大家拍砖。这是我的第一篇文章,希望给自己一个激励,向看雪的大牛们看齐吧,呵呵。

showbu 
2008年11月3日