破解XMLSpy Enterprise 2004

看雪資料發表於2015-11-15

朋友的機器上有個XMLSpy 4.2版,本來打算玩玩,練練手,結果
折騰了不少時間也沒搞定。KeyFile保護,30天試用期(過期後還可
把時間調回去接著用:-)。所有的保護都在一個DLL內,非常明顯。
主程式只有一處呼叫了DLL中的東西,看起來相互間的關係很鬆散,
其實非常緊密。好象是主程式的訊息對映關係被做了手腳,輸入的
KeyCode正確,才會恢復(用VC/MFC寫的)。

    打算上網去要個KeyCode試試(可生成KeyFile),便於除錯,結
果早已升級了,下了個XMLSpy Enterprise 2004回來,要的試用
KeyCode倒是管用,但仍未看明白演算法以及保護是如何實現的(這
個軟體的彙編程式碼符號資訊比較少,能讀明白要費點工夫。慚愧
慚愧)。

    我試了試,等程式執行起來後dump了一個出來,希望能有正確
的函式呼叫關係,然後關掉唯一一處對DLL的呼叫。Import Table
已用ImpRec重建了,可仍出現記憶體訪問錯誤。跟了跟,好象所訪問
的記憶體是由那個DLL分配的。而且,執行程式後,用bpmb設的除錯
暫存器斷點也會被清除(用的是WinXP),看來進入了Ring0。


   乾脆下載回來一個4.3版的序號產生器(作者為DigitalFactory)。對
那個2004版不管用,但可以研究研究,再倒回去看彙編程式碼也許會清
爽些。


   下面是從序號產生器逆向工程做出來的程式碼。 

// KeyGen.cpp : Defines the entry point for the console application.
// 

#include "stdafx.h"
#include "KeyGen.h" 

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif 

/////////////////////////////////////////////////////////////////////////////
// The one and only application object 

CWinApp theApp; 

using namespace std; 

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0; 

// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
// TODO: code your application's behavior here.
CString strHello;
strHello.LoadString(IDS_HELLO);
//cout << (LPCTSTR)strHello << endl;


/////////////////////////////////////////////////////// 

WORD Encrypt(char * lpData,int nSize); 

//////////////////////////////////////////////////////////
//以下程式碼模仿XMLSpy 4.3 KeyGen by DigitalFactory
////////////////////////////////////////////////////////// 

//處始化常量(第一個dword最重要,最後的0xCB的含義) 

DWORD dwData[6]; 

dwData[0]=0xDE0000CB;
dwData[1]=0;
dwData[2]=0;
dwData[3]=0x44202020;
dwData[4]=0x48544145;
dwData[5]=0x16000000; 

BYTE cUserInfo[16]; //只用最多7個byte即可?
::ZeroMemory(cUserInfo,sizeof(cUserInfo)); 

// 

BYTE * lpData=cUserInfo; //此指標用於訪問陣列cUserInfo 

//step 1(以下4步設定cUserInfo中的資料) 

*(PWORD)lpData=Encrypt("cracker",7);  //UserName
lpData+=2; 

//step 2 

//"CrackerHome":公司名,後面的"/XMLSpy"是固定值

if(dwData[0]&2)
{
  *(PWORD)lpData=Encrypt("CrackerHome/XMLSpy",11);
  lpData+=2;


//step 3 

if(dwData[0]&4)//?
{
  lpData+=2;


//step 4 

if(dwData[0]&8)//?
{
  *lpData=0x36;
  lpData++;


//step 5(以下設定dwData中的資料) 

BYTE * lpKey=(BYTE *)dwData;//此指標用於訪問陣列dwData 

lpKey+=3; 

if(dwData[0]&0x40)//?
{
  *((WORD *)lpKey)=0x029A;
  lpKey+=2;


//step 6 

*((WORD *)lpKey)=Encrypt("XMLSpy",6);//產品名,固定值?
lpKey+=2; 

//step 7 

if(dwData[0]&0x10)//試用版的標記(取了時間)?
{
  *((DWORD *)lpKey)=time(0);
  lpKey+=4;


//step 8 

if(dwData[0]&0x20)//?
{
  *((DWORD *)lpKey)=5;
  lpKey+=4;


//step 9 

*((DWORD *)lpKey)=0x54494453; //這個值與當前版本不同!?
*((WORD *)(lpKey+4))=0x5042; 

//step 10(用cUserInfo中的資料加密後修改dwData[0]) 

DWORD dwTmp=MAKELONG(Encrypt((char *)cUserInfo,lpData - cUserInfo),0);
dwTmp<<=8;
dwData[0]|=dwTmp; 

//step 11(用dwData[0]-dwData[4]加密後修改dwData[5]->KeyCode本身的校驗) 

dwData[5]|=MAKELONG(Encrypt((char *)dwData,0x14),0); 


//step 12(以下建立KeyCode) 

char szTable[]="0123456789ABCDEFGHKMNPSTUXabcdefghkmqstuz"; //又一個換碼錶
char szKeyCode[256]; 

::ZeroMemory(szKeyCode,sizeof(szKeyCode));
char * lpszKeyCode=szKeyCode; 

for(int i=0;i<6;i++)  //每輪迴圈計算一段KeyCode(6段)
{
 for(int j=0;j<6;j++)  //每輪迴圈計算一個字元
 {
     *lpszKeyCode=szTable[dwData[i]%(sizeof(szTable)-1)];
     lpszKeyCode++; 

     dwData[i]=dwData[i]/(sizeof(szTable)-1);
 } 

 if(5!=i)
 {
    *lpszKeyCode=0x2D;//'-'
    lpszKeyCode++;
 }


printf("The KeyCode= %s
",szKeyCode); 

return nRetCode;


WORD Cipher[]=//密碼錶,加密解密用的同一個表
{
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81,
0x0E40, 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880,
0xC841, 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1,
0xDA81, 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01,
0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540,
0xD701, 0x17C0, 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380,
0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, 0xF001, 0x30C0,
0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, 0x3600,
0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80,
0xFE41, 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1,
0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01,
0x2BC0, 0x2A80, 0xEA41, 0xEE01, 0x2EC0, 0x2F80, 0xEF41,
0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580,
0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1,
0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, 0xA001,
0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480,
0xA441, 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0,
0x6E80, 0xAE41, 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900,
0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, 0xB981, 0x7940,
0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01, 0x7EC0, 0x7F80,
0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0,
0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, 0x7200,
0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
}; 

WORD Encrypt(char * lpData,int nSize)
{
 WORD wResult=0;
 WORD wTemp; 

 for(int i=0;i<nSize;i++)
 {
    wTemp=wResult;
    wTemp &= 0x00FF; 

    wTemp ^= ((WORD)(* (lpData+i))) & 0x00FF;//轉化為WORD,高位元組為FF,應清除 

    wResult=(wResult>>8)^(Cipher[wTemp]);
 } 

 return wResult;
}


搞清楚這個序號產生器的寫法,可以明白不少原來沒弄懂的彙編碼。和上一個4.3版所用的
演算法是一樣的,資料結構略有差異。只要在SoftIce和IDA中拿到足夠的資料,就可以
寫出序號產生器。只是不是自己從頭做的,感覺有點勝之不武:-)


以下為XMLSpy Enterprise 2004 序號產生器:

一點說明: 

最終的KeyCode是從dwData陣列計算得來的。雖然定義為dword陣列,實際上是作為字
節流來處理的(一個大buffer),並不以dword為邊界。比上一個KenGen,多出了一個
dword。以下為這28個byte的解釋(以試用KeyCode的值為例進行說明)。這是在最後
進行兩輪迴圈計算KeyCode以前的值,在軟體中會將使用者的KeyCode解密(呼叫
XMLSpyLicMan.dll中1001BFF0處的函式),得到這個結果。 

+0:0xFB  關鍵位元組,每一位都有一定的含義(只知道一部分)。試用KeyCode為0xFB,
          正式版應用0xEB,直接賦值 

+1:0x0D 0xF4 使用者和版本資訊的密文校驗值,在step10中填入 

+3:0x01 0x00 使用者數(=0x0001 -> 1使用者),在step5中填入 

+5:0x23 0xB5 Encrypt("XMLSpy",6)的結果,在step6中填入 

+7:0x20 0x92 0x87 0x3F 即0x3F879220,若為試用版,這裡是超期日期。 

+0xB:0x21 0x05 0x60 0x3F 另一個日期。從試用KeyCode看,這裡應該是KeyCode發
                          布的日期,比前一個值小1個月 

+0xF:0x30 0x45 0x50 0x48 0x53 0x44 0x46  “0EPHSDF”
         後6個字元代表啟用的模組。對試用KeyCode,前面多一個“0”,
         不知什麼含義。我的KenGen只用了後面6字元  

+0x16: 0xF2 0x9E 對前面22位元組呼叫Encrypt的結果,用於校驗KeyCode本身的合法性
                 在step11中填入 

+0x18:0x00 0x00 0x00 0x18 固定值0x18000000,在4.3版中為0x16000000 


注意,在建立KeyCode的過程中對第一位元組(0xFB)的一些位進行了測試,在不同的狀態
下可能會跳過一些賦值程式碼,導致後續的資料位置提前。我的KenGen中模組代表字元
的位置就提前了。 


// KeyGen.cpp : Defines the entry point for the console application.
// 

#include "stdafx.h"
#include "KeyGen.h" 

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif 

/////////////////////////////////////////////////////////////////////////////
// The one and only application object 

CWinApp theApp; 

using namespace std; 

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
   int nRetCode = 0; 

// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
   // TODO: change error code to suit your needs
   cerr << _T("Fatal Error: MFC initialization failed") << endl;
   nRetCode = 1;
}
else
{
  // TODO: code your application's behavior here.
  CString strHello;
  strHello.LoadString(IDS_HELLO);
  //cout << (LPCTSTR)strHello << endl;


/////////////////////////////////////////////////////// 

WORD Encrypt(char * lpData,int nSize); 

//////////////////////////////////////////////////////////////////////
// KeyGen for XML Spy Enterprise 2004 (Thanks to DigitalFactory!) //
////////////////////////////////////////////////////////////////////// 

//處始化常量(第一個dword最重要,最後的0xEB的含義) 

DWORD dwData[7]; 

dwData[0]=0x000000EB; //這裡不能用0xCB,試用版為FB,使step7的if為true
dwData[1]=0x20000000; //
dwData[2]=0x20202020;
dwData[3]=0x20202020;
dwData[4]=0x20202020; //dwData[4],dwData[5]的6個0x20->模組名
dwData[5]=0x00002020;
dwData[6]=0x18000000; 

BYTE cUserInfo[16];//只用最多7個byte即可?
::ZeroMemory(cUserInfo,sizeof(cUserInfo)); 

// 

BYTE * lpData=cUserInfo; //此指標用於訪問陣列cUserInfo 

//step 1(以下4步設定cUserInfo中的資料,但用了dwData[0]的最後byte) 

*(PWORD)lpData=Encrypt("cracker",7); //UserName
lpData+=2; 

//step 2 

//"CrackerHome":公司名,後面的"/XMLSpy"是固定值

if(dwData[0]&2)
{
  *(PWORD)lpData=Encrypt("CrackerHome/XMLSpy",11);
  lpData+=2;


//step 3 

if(dwData[0]&4) //這裡為false,含義不清楚
{
  lpData+=2;


//step 4 

if(dwData[0]&8)  //
{
  //2004版用'8',我忘了在哪見過'8',有點印象
  //(一開始用'7',不對-> dwData[0]中間4位不對) 

  //在XMLXpyLicMan.dll的1004ADF8  

  *lpData=0x38; //'8'
  lpData++;


//step 5(以下設定dwData中的資料) 

BYTE * lpKey=(BYTE *)dwData;  //此指標用於訪問陣列dwData 

lpKey+=3; 

if(dwData[0]&0x40)  //使用者數
{
  *((WORD *)lpKey)=0x0001;
  lpKey+=2;


//step 6 

*((WORD *)lpKey)=Encrypt("XMLSpy",6);  //產品名,固定值
lpKey+=2; 

//step 7 

//試用版的標記,將dwData[0]的低位元組改為0xEB,使if為false
//這個if不滿足,會使下面資料的位置提前 

if(dwData[0]&0x10)
{
  //KeyCode釋出日期(超期日期)?
  //從試用KeyCode解出的結果看,這個值大於下一個dword
  //0x3F879220-0x3F600521=1個月 

  *((DWORD *)lpKey)=time(0);
  lpKey+=4;


//step 8 

if(dwData[0]&0x20)  //這一步執行了,含義不清楚
{
  *((DWORD *)lpKey)=5;
  lpKey+=4;


//step 9 

//字元代表模組名
//E:Enterprise P:Professional H:Home S:styleVision
//D:AuthenticDesktop F:MapForce 

*((DWORD *)lpKey)=0x53485045; //"EPHS"
*((WORD *)(lpKey+4))=0x4644;  //"DF" 

//step 10(用cUserInfo中的資料加密後修改dwData[0]中間的4位) 

DWORD dwTmp=MAKELONG(Encrypt((char *)cUserInfo,lpData - cUserInfo),0);
dwTmp<<=8;
dwData[0]|=dwTmp; 

//step 11(用dwData前22個byte加密後修改dwData[5]中的4位->KeyCode本身的校驗) 

dwData[5]|=MAKELONG(0,Encrypt((char *)dwData,0x16)); 

//step 12(以下建立KeyCode) 

char szTable[]="0123456789ABCDEFGHKMNPSTUXabcdefghkmqstuz";  //又一個換碼錶
char szKeyCode[256]; 

::ZeroMemory(szKeyCode,sizeof(szKeyCode));
char * lpszKeyCode=szKeyCode; 

for(int i=0;i<7;i++)  //每輪迴圈計算一段KeyCode(7段)
{
  for(int j=0;j<6;j++)  //每輪迴圈計算一個字元
  {
     *lpszKeyCode=szTable[dwData[i]%(sizeof(szTable)-1)];
     lpszKeyCode++; 

     dwData[i]=dwData[i]/(sizeof(szTable)-1);
  } 

  if(6!=i)
  {
     *lpszKeyCode=0x2D;  //'-'
     lpszKeyCode++;
  }


printf("The KeyCode= %s
",szKeyCode);
///////////////////////////////////////////////////////////////// 

return nRetCode;


WORD Cipher[]=//密碼錶,加密解密用的同一個表
{
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81,
0x0E40, 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880,
0xC841, 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1,
0xDA81, 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01,
0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540,
0xD701, 0x17C0, 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380,
0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, 0xF001, 0x30C0,
0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, 0x3600,
0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80,
0xFE41, 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1,
0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01,
0x2BC0, 0x2A80, 0xEA41, 0xEE01, 0x2EC0, 0x2F80, 0xEF41,
0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580,
0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1,
0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, 0xA001,
0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480,
0xA441, 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0,
0x6E80, 0xAE41, 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900,
0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, 0xB981, 0x7940,
0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01, 0x7EC0, 0x7F80,
0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0,
0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, 0x7200,
0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
}; 

WORD Encrypt(char * lpData,int nSize)
{
  WORD wResult=0;
  WORD wTemp; 

  for(int i=0;i<nSize;i++)
  {
     wTemp=wResult;
     wTemp &= 0x00FF; 

     wTemp ^= ((WORD)(* (lpData+i))) & 0x00FF;  //轉化為WORD,高位元組為FF,應清除 

     wResult=(wResult>>8)^(Cipher[wTemp]);
  } 

  return wResult;
}

相關文章