• 标 题:破解XMLSpy Enterprise 2004
  • 作 者:softworm
  • 时 间:2003-9-19 周五, 下午10:22
  • 链 接:http://bbs.pediy.com

朋友的机器上有个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;
}