Internet網絡的迅速發展,為軟件高效傳播開辟更加廣闊的天地。如國內著名的金蜘蛛軟件下載中心,就是一個典型的發布軟件集散地。發布共享軟件主要包括兩種形式:日期限制形式和電子注冊形式。日期限制形式允許下載軟件的用戶使用軟件一段時間,如一個月等,如果用戶認可該軟件,可購買該軟件的注冊序列號繼續使用;電子注冊形式就是根據用戶所用機器的硬件信息產生注冊碼,並在軟件中對某些先進或常用功能進行限制,如果用戶要使用其全部功能,必須將軟件采集的有關硬件信息反饋給開發者,並交一定的注冊費可獲得該軟件在自己機器中的注冊碼,才能正常使用。
前一種形式很容易給盜版者造成可乘之機,如果制作盜版者購買了一個注冊序列號並公布於天下,則所有用戶使用這個注冊號都可進行正常使用;後者對用戶來說注冊手段稍顯復雜些,對開發者來說也需要一定的編程真功夫,但其具有“八點鎖緊”功能,防盜性卻是不容置疑。本文根據自己的實踐,將後者的實現過程介紹給想要制作發布共享軟件的讀者。
一、注冊源
在WIN98/95的保護模式下,要根據硬件信息形成注冊碼可不是一件容易的事,在實模式下可通過硬盤端口1F6H和1F7H直接讀取硬盤的序列號等信息作為注冊的數據源,但這一方法在保護模式下卻被亮出了紅牌。利用BIOS中的主板序列號、BIOS版本序列號或主機出廠日期和標志等,完全可以作為注冊碼的注冊源。如ROMBIOS中F000H-FFFFH區域中就存在與硬件配置有關的信息,還可以采集其它一處或幾處主板等的信息作為注冊碼的生產基地。例如可根據F000H:FFF5H-F000H:FFFFH 中存放的主機出廠日期和主機標志值,產生應用程序的注冊碼。由於計算機產品的更新換代比較快,而且所有用戶使用的計算機不可能配置都完全相同,所以注冊碼產生的源也不會完全相同。而且這些硬件信息內容在任何操作系統下均完全相同,兼容性非常好,更不會因為操作系統的更新而造成注冊功能失效。
注冊源確定之後,關鍵的問題就是共享軟件安裝程序如何采集注冊源信息,並讓用戶將其返回給開發者。最簡單的方法就是將采集到的注冊源信息經過位操作加密後存放到一個文本中,形成注冊碼的數據源資料。這個注冊源數據串可稍長一些,但不宜過長,使用戶能夠通過電子郵箱、電話或信件順利轉給開發者為宜。如筆者安裝程序是用C語言編制的,如果將上述內存地址作為注冊源,數據串文本文件名為KEYID.DOC,長度為20個字符。其示例代碼如下:
FILE *fp2;
unsigned int keyrom[9];
unsigned char buff[0x410];
unsigned char pathstmp[80];
unsigned char path[80]={"C:\\WBCOOL"};
unsigned int far *pt=(unsigned int far*)0xf000fff6L;
......
outportb(0x21,0x2);
strcpy(pathstmp,path);
strcat(pathstmp,"\\");
strcat(pathstmp,"KEYID.DOC");
for(i=0;i<5;i++)
keyrom[i]=(*(pt+i)+0x1818)^0x5858;//第一級加密算法
sprintf(buff,"KEYID:%04x%04x%04x%04x%04x",
keyrom[0],keyrom[1],keyrom[2],keyrom[3],keyrom[4]);
buff[0x1a]=0;
if((fp2=fopen(pathstmp,"wb"))==NULL)
{
printf("FILE %s CREATE ERROR!",pathtmp);
} else {
fseek(fp2,0L,SEEK_SET);
fprintf(fp2,"%s\xd\xa",buff);
fclose(fp2);
}
outportb(0x21,0x0);
二、注冊機
開發者得到用戶提供的注冊源數據之後,就需要利用注冊機生成注冊碼並返回給用戶。注冊機利用既定的位操作和不可逆算法,形成用戶比較容易操作的字符串注冊碼,注冊碼的長度一般為8-16位為宜,用戶只需注冊一次就可以長期使用,所以注冊碼的長度不會影響用戶的注冊操作。當然注冊機的算法應與共享軟件中的算法部分基本相同。對於遠程用戶,注冊機應該具有從鍵盤和內存兩種取得注冊源數據的功能,所以注冊機的加密算法實際為兩個分支:第一個分支是從鍵盤獲取注冊源數據後直接根據注冊算法形成注冊碼的過程,是直接給遠程用戶反饋注冊碼的過程;第二個分支是直接從ROM BIOS中根據注冊源算法取得注冊源數據,再根據注冊算法形成注冊碼的過程,是直接讀取本地機注冊碼的。
用戶得到注冊碼後,根據共享發布軟件的注冊方法進行一次注冊,應用程序會自動將這個注冊碼存放到軟件的特定位置處,當應用程序被他人拷貝到其它機器中去後,由於注冊碼因不同機器而異,所以應用程序的功能或使用次數仍然受限,要在其它機器中使用該應用程序,還必須進行重新注冊,達到共享軟件發布目的。同時由於注冊源數據的算法和注冊碼算法均可因人而異,因此這種方法非常可靠。本人實現的注冊機帶參數時接受鍵盤輸入注冊源;不帶任何參數時從本地機器內直接采集注冊源數據。我的注冊機示例程序如下:
#include <conio.h>
#include <dos.h>
#include <io.h>
#include <dir.h>
#include <alloc.h>
#include <string.h>
#include <stdio.h>
#include <process.h>
#include <fcntl.h>
#include <ctype.h>
#include <stdlib.h>
unsigned char Buff[18];
unsigned char Buff1[18];
unsigned int keyrom[9];
unsigned int sum,sum1,sumi,sumj;
unsigned int far *pt=(unsigned int far *)0xf000fff6L;
unsigned int i=0,j=0,m,imecom;
unsigned char p;
unsigned int nn,nn1,nn2;
unsigned char rbuff[100],cc,cc1,cc2;
int fp;
void main(int argc,char *argv[])
{
if(argc>=2){
printf("KEYID:");
scanf("%s",rbuff);//接受鍵盤輸入遠程注冊源
j=strlen(rbuff);
if(j!=20) exit(1);
for(i=0;i<20;i++){//讀入20位注冊源數據
if((rbuff[i]>='a')&&(rbuff[i]<='f')) rbuff[i]&=0xdf;
if((rbuff[i]>='A')&&(rbuff[i]<='F')) rbuff[i]-=0x37;
else if((rbuff[i]>='0')&&(rbuff[i]<='9')) rbuff[i]-=0x30;
else exit(1);
}
for(i=0;i<5;i++){//形成字符串
cc1=rbuff[i*4]&0xf;
cc2=rbuff[i*4+1]&0xf;
cc=(cc1<<4)|cc2;
nn1=(unsigned int)cc;
cc1=rbuff[i*4+2]&0xf;
cc2=rbuff[i*4+3]&0xf;
cc=(cc1<<4)|cc2;
nn2=(unsigned int)cc;
nn=(nn1<<8)|nn2;
keyrom[i]=nn;
}
sum=0x1234;
sum1=0x7456;
for(sumj=0;sumj<4;sumj++){//形成16位注冊碼
for(sumi=0;sumi<5;sumi++){
sum+=keyrom[sumi]; //形成前4位碼
sum1+=keyrom[sumi];
}
sum^=0x1234<<sumj; //進行移位異或處理
sum1^=0x7456<<sumj;
sprintf(Buff+4*sumj,"%04x",sum);
sprintf(Buff1+4*sumj,"%04x",sum1);
} //形成16位注冊碼
printf("\nWIN-KEY:");
printf(Buff);
printf("\nDOS-KEY:");
printf(Buff1);
exit(1);
} else {
sum=0x1234;
sum1=0x7456;
for(sumj=0;sumj<4;sumj++){//形成16位注冊碼
for(sumi=0;sumi<5;sumi++){
sum+=(*(pt+sumi)+0x1818)^0x5858;
sum1+=(*(pt+sumi)+0x1818)^0x5858;
}
sum^=0x1234<<sumj;
sum1^=0x7456<<sumj;//進行移位異或處理
sprintf(Buff+4*sumj,"%04x",sum);
sprintf(Buff1+4*sumj,"%04x",sum1);
}
printf("\nWIN-KEY:");
printf(Buff);
printf("\nDOS-KEY:");
printf(Buff1);
}
}
三、注冊碼
當用戶注冊成功後,注冊碼就被寫到共享軟件的相應位置。這時共享軟件必須對用戶注冊碼進行實時檢測與判斷,才能實現注冊限制功能。這時要求共享軟件必須內部取得注冊源數據,並利用注冊機中相同的算法產生內部注冊碼。這就要求共享軟件直接讀取ROM BIOS的注冊源信息,並在共享軟件中需要限制的功能處增加注冊碼檢測判斷功能,這需要根據共享軟件的實際需要、軟件大小和實現的難易程度來確定限制的數量,使盜版者很難進行解密。這樣既使計算機中多個共享軟件使用相同的注冊源,也不會發生注冊沖突問題;既使是使用了相同的注冊源數據,由於注冊算法的不同注冊碼也不會相同;即使解密者知道注冊算法的注冊源地址,由於無法知道注冊算法而且注冊點遍布整個共享軟件,也很難進行盜版。因此,這一注冊方法使共享軟件有效地跨越各種系統平台。
要在共享軟件內部產生注冊碼,必須在共享軟件中讀取ROM BIOS數據源內存數據。WINDOWS保護模式下必須利用段選擇符方法和API編程接口提供的函數才能實現:
1.AllocSelector(Selector)分配一個與參數相同的空選擇器
2.FreeSelector(Selector) 釋放分配的選擇器
3.SetSelectorBase() 設置選擇器描述符物理起始地址
4.GetSelectorBase() 獲取選擇器描述符物理起始地址
5.SetSelectorLimit() 設置選擇器描述符訪問界限
6.GetSelectorLimit() 獲取選擇器描述符訪問界限
其中函數AllocSelector(Selector) 是保護模式下物理內存訪問的關鍵,Selector是分配空選擇器的段寄存器模板,可以利用GlobalAlloc()函數分配內存,再利用GlobalHandleToSel()函數將內存句柄轉換為相應選擇器,內存單元訪問結束後再利用GlobalFree()釋放分配的內存。最簡單的方法就是將系統的數據段寄存器__DS直接作為模板參數,這個參數在一般應用程序中完全可以正常使用。然後利用SetSelectorBase()和SetSelectorLimit( )函數分別設置內存的物理起始地址和訪問界限值,利用正常的指針操作*pt=Value和Value=*pt訪問物理內存單元,訪問結束後必須使用FreeSelector()函數釋放分配的選擇器,因為WINDOWS 並不自動釋放無用的選擇器,而且系統的選擇器共享資源是非常有限,只有8192個供使用。根據以上原理及注冊機中的注冊源和注冊碼算法,就不難實現共享軟件內部注冊碼函數:
UINT ImeCmpkey(void)
{ //共享軟件內部注冊碼產生函數
static unsigned int sum;
static BOOL flag;
static unsigned int far *pt;
static UINT Sel1,Sel2;
static WORD Seg,Off,Start;
static DWORD Bas,Lim;
flag=TRUE;
sum=0x1234;
__asm mov Sel1,ds;//將DS作為模板
Sel2=AllocSelector(Sel1);//分配一個新選擇符
if(Sel2==NULL){
flag=FALSE;
pt=(unsigned int far*)0xf000fff0L;
} else {
Seg=0xffff; //絕對地址段址
Off=0x10; //絕對地址偏移
Start=0x0;
Bas=((unsigned long)Seg)<<4|Start;
Lim=(unsigned long)Off-1;
SetSelectorBase(Sel2,Bas);
SetSelectorLimit(Sel2,Lim);
pt=(unsigned int far*)((((unsigned long)Sel2)<<16)|Start);
}
for(j=0;j<4;j++){//形成16位注冊碼
for(i=0;i<5;i++) sum+=(*(pt+3+i)+0x1818)^0x5858;//形成前4位
sum^=0x1234<<j;//進行移位異或處理
wsprintf((LPSTR)sImeG.ImeKey+4*j,(LPSTR)"%04x",sum);
}
if(flag==TRUE) FreeSelector(Sel2);
sImeG.ImeKey[16]=0;//對注冊碼本身加密
for(i=16;i>0;i--) sImeG.ImeKey[16-i]^=(unsigned char)i;
for(i=0;i<16;i++){ //判斷注冊碼
if(sImeG.ImeKey[i]!=lpImeL->ZcMyOk[i]) break;
}
if(i==16){
sImeG.ZcFlag=FALSE;
sImeG.ZcCount=0x0;
lpImeL->UseNum=0x0;
for(i=0;i<16;i++) sImeG.ImeKey[i]=0x0;
return(0);
} else {
sImeG.ZcFlag=TRUE;
sImeG.ZcCount=0x0;
sImeG.iSel = 1;
sImeG.FScrCz = TRUE;
lstrcpy(sImeG.szSel[0],(LPSTR)"注冊:_________________");
sImeG.szSel[0][6]=0x11;
sImeG.szSel[0][23]=0x0;
UpdateInList();
return(~0);
}
}
四、注冊點
共享軟件內部注冊碼產生後,需要對抗盜版的注冊點的多少取決於共享軟件的自身價值、開發者的加密深度和軟件實現的復雜程度等諸多因素,同時這也決定了注冊提示信息的顯示頻度,來套磁用戶進行合法使用。但就筆者自身而言,至少應該將共享軟件中實現難度較大、深受用戶歡迎以及普遍使用的功能加上注冊點。建議不同平台之間的注冊碼要分別設計注冊算法和注冊碼。其代碼示例如下:
注冊點一:
if(lpImeL->UseNum>=0x3f80){
if(ImeCmpKey()==~0){
sImeG.ZcFlag=TRUE;
} else {
sImeG.ZcFlag=FALSE;
lpImeL->UseNum=0x0;
}
} else lpImeL->UseNum+=sImeG.iWord;
注冊點二:
if(ImeCmpKey()==~0){
sImeG.ZcFlag=TRUE;
return;
} else {
sImeG.ZcFlag=FALSE;
lpImeL->UseNum=0x0;
}
五、注冊口
對於共享軟件,不管其實現何種功能,最好采取再線注冊方式,這樣可以減少用戶很多重復操作。同時應該采取多個注冊入口,如本人軟件可以在增加或刪除詞組等時進行注冊,只要一處注冊成功整個軟件就算注冊成功,並注意對注冊口輸入的注冊碼進行再加密處理。
筆者共享軟件中注冊口代碼示例代碼如下:
if(sImeG.ZcFlag==TRUE){
if((cCharCode==0x8)||(cCharCode==0x4b)){
if(sImeG.ZcCount>0){ //刪除鍵處理
if(sImeG.ZcCount<17)
sImeG.szSel[0][sImeG.ZcCount+6]=0x5f;
else sImeG.szSel[0][sImeG.ZcCount+6]=0x0;
sImeG.ZcCount--;
sImeG.szSel[0][sImeG.ZcCount+6]=0x11;
lpImeL->ZcMyOk[sImeG.ZcCount]=0x0;
sImeG.iSel = 0x1;
sImeG.FScrCz = TRUE;
UpdateInList();
} else MessageBeep(-1);
} else if (cCharCode==0xd){//回車鍵處理
if(sImeG.ZcCount==0x10){
sImeG.ZcFlag=FALSE;
sImeG.ZcCount=0x0;
sImeG.iSel = 0x0;
ScrnCode(sImeG.iStart);
sImeG.FScrCz = TRUE;
UpdateInList();
for(i=16;i>0;i--)
lpImeL->ZcMyOk[16-i]^=(unsigned char)i;
lpImeL->ZcMyOk[16]=0;
for(i=0;i<16;i++){
if(sImeG.ImeKey[i]!=lpImeL->ZcMyOk[i]) break;
}
if(i==16){//寫入注冊碼
for(i=0;i<16;i++) sImeG.ImeKey[i]=0x0;
j=GetSystemDirectory(FileName,80);
if((j==0)||(j>64)){
wsprintf((LPSTR)sImeG.ImeBuff,(LPSTR)"系統路徑非法!");
ErrMessageBox((LPSTR)sImeG.ImeBuff);
for(i=0;i<16;i++) lpImeL->ZcMyOk[i]=0x0;
ShowMessTs(8);
} else {
lstrcat(FileName,(LPSTR)"\\");
lstrcat(FileName,(LPSTR)"WBCOOL.IME");
if((hTmp=_lopen(FileName,READ_WRITE))==-1){
wsprintf((LPSTR)sImeG.ImeBuff,(LPSTR)"程序打開出錯!");
ErrMessageBox((LPSTR)sImeG.ImeBuff);
for(i=0;i<16;i++) lpImeL->ZcMyOk[i]=0x0;
ShowMessTs(8);
} else {
_llseek(hTmp,0x12345L,SEEK_SET);//12345為注冊碼地址
_lwrite(hTmp,lpImeL->ZcMyOk,16);
_lclose(hTmp);
ShowMessTs(7);
}
}
} else {
for(i=0;i<16;i++){
sImeG.ImeKey[i]=0x0;
lpImeL->ZcMyOk[i]=0x0;
}
ShowMessTs(8);
}
} else MessageBeep(-1);
} else if ((cCharCode>=0x30)&&(cCharCode<='~')){
if(sImeG.ZcCount<16){
if((cCharCode>='A')&&(cCharCode<='Z')) cCharCode^=0x20;
lpImeL->ZcMyOk[sImeG.ZcCount]=cCharCode;
sImeG.szSel[0][sImeG.ZcCount+6]=cCharCode;
sImeG.ZcCount++;
sImeG.szSel[0][sImeG.ZcCount+6]=0x11;
sImeG.iSel = 0x1;
sImeG.FScrCz = TRUE;
UpdateInList();
} else MessageBeep(-1);
} else MessageBeep(-1);
return(iRet);
}
總之,共享發布軟件的制作應做到:注冊源要選准、注冊算法要多變、注冊碼要再加密、注冊機要管好、注冊點要多方位、注冊方式要在線、注冊入口要多點。這樣才能確保軟件的安全可靠。