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