程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 在Windows應用程序中實現電子注冊功能

在Windows應用程序中實現電子注冊功能

編輯:關於VC++

目前,國內軟件銷售過程中采用了一種新的方式:開發者根據計算機中不同的硬件配置標志直接在應用程序中設置密鑰,限制程序的使用次數或者限制某些先進功能的使用,然後將受限制的應用程序無償提供給用戶。用戶在試用一段時間之後如果覺得很滿意,就可以將安裝程序提取的硬件配置解密密鑰或已經采集機器配置情況的應用程序提供給開發者,並花少量費用購買自己機器中的電子注冊密鑰,從而能夠充分利用應用程序的所有功能。

在應用程序中利用電子注冊來限制應用程序的部分功能,這樣既可以讓用戶先試用然後再決定是否購買應用程序,又保護了開發者的合法勞動成果,減少了用戶與開發者之間的不必要的中間環節。開發者直接得到用戶購買軟件的費用,真正地體現了開發者所創造的價值;用戶在試用軟件之後再決定是否購買,從而使得用戶能夠得到稱心如意、物有所值的軟件。因此,不通過中間環節這種銷售方式降低了軟件的成本,使開發者和用戶雙方都受益。同時,這種方式還可以使得開發者能夠直接獲得用戶的反饋信息,促使開發者開發出功能更加完善的應用程序。

然而,要想在應用程序中實現電子注冊功能決不是件容易的事情,尤其是在Windows平台推出以後,要想實現一個跨平台的應用程序電子注冊功能,則要求開發者應具有豐富的編程技巧和實際開發經驗以及廣闊的開發視野。筆者通過實踐探索,終於成功地實現了跨越DOS、Windows 3.X和Windows 95平台的應用程序電子注冊功能。下面將闡述其實現原理及技巧。

一、注冊密鑰點的選擇與生成

實現應用程序的電子注冊功能,最關鍵的問題是采集硬件配置中的密鑰點。在DOS系統下,可以通過硬盤端口1F6H和1F7H直接讀取硬盤的序列號等作為密鑰算法的數據,因為每塊硬盤的型號、版本號和序列號均不同,只要用戶提供上述內容,利用這種方法生成的注冊密鑰在每台計算機中均不同,從而實現電子注冊的功能。著名的字表處理軟件CCED 5.18中采用的就是類似的方法。雖然這種方法在絕大數場合下很有效,甚至可以在Windows 3.X系統和Windows 95系統的兼容模式下通過,但在最高性能配置的Windows 95保護模式下卻行不通,原因是Windows 95保護模式下不允許通過端口方式讀取硬盤類型參數,所以利用這種方法無法實現跨平台的通用電子注冊功能。

本人仔細分析計算機中ROM區的F000H-FFFFH內容後,發現該區域中記錄著很多與硬件配置有關的信息(如CMOS配置信息、主板名稱、型號和序列號、主機標志字節和生產日期等)。可以采集其中一處或幾處作為注冊密鑰算法的原始數據(如機器ROM區中的F000H:FFF5H-F000H:FFFFH中依次存放主機出廠日期和主機標志字節的內容),這些硬件特有的信息對於不同型號的計算機來說是不可能相同的。因此,完全可以將其作為注冊密鑰算法的原始數據,而且這些內容在DOS、Windows 3.X和Windows 95下均相同。需要注意的是,如果在實際應用中真的將該采集點作為算法的原始數據,則不應該包括F000:FFF0H開始的前五個字節的內容,原因是該地址已被用作機器熱啟動時的入口地址,在DOS、Windows 3.X和Windows 95系統中對熱啟動復合鍵Ctrl+Alt+Del的處理程序均不同,因此該處的內容在三者之中也都不相同,讀者應記住這一點。

利用上述方法取得注冊密鑰算法的原始數據後,開發者就可以確定自己的加密算法,這可以通過編程語言中豐富的位操作功能來實現。然後將注冊加密算法增加到應用程序中需要限制的部分,並可根據應用程序的實際需要和限制的功能任意設置多處,使盜版者很難解密,從而有效地保護開發者的成果。利用這一方法,即使機器中有多個應用程序使用相同的硬件配置信息采集點,也不可能發生任意加密沖突問題;即便是使用了相同的算法原始數據,由於算法不同,注冊密鑰也不會完全相同;即使解密者知道加密算法的原始數據,由於無法知道加密算法,再加上加密算法貫穿於整個應用程序,所以很難解密。因此,上述方法可以有效地實現跨越DOS、Windows 3.X和Windows 95平台的電子注冊功能。此外,由於ROM 區關鍵點的內容不可能發生變化,所以即使將來推出新型的操作系統平台,這種方法仍然會很有效。

二、利用解密密鑰建立聯系 

應用程序的加密部分完成之後,就需要建立相應的解密密鑰。所謂解密密鑰,就是將加密算法的原始數據經過加密之後,直接顯示給用戶並寫入應用程序的相應位置。這樣,用戶既可以通過電話或計算機網絡給開發者提供注冊功能的算法原始數據,也可以將安裝後的應用程序寄給開發者。加密密鑰既可以是ROM 區域內的原始數據(最好不要原樣提供),也可以是由原始數據經過一定換算後形成的新的數據。因此,開發者提供的應用程序中的加密算法部分應包括兩部分:將機器ROM 區域內的數據經過解密密鑰算法後形成解密密鑰,再將解密密鑰數據經注冊算法後形成注冊密鑰。

應用程序中注冊密鑰的算法、注冊密鑰的長度、顯示或提供給開發者的方式可自己確定,但解密密鑰的長度和算法應與注冊密鑰完全相同。解密密鑰也沒有必要做得那麼復雜,只需進行簡單處理後就可以實現,例如本文程序中實現的方法是將ROM中采集的數據簡單地減去0x2020。

三、電子注冊密鑰生成程序

開發者得到用戶提供的解密密鑰原始數據後,需要利用專用的密鑰生成程序將其轉換成注冊密鑰,並將注冊密鑰交給用戶。注冊密鑰的算法與應用程序中判斷注冊密鑰的加密算法程序應該完全相同。該程序一般應具有以下三種取得注冊密鑰算法原始數據的方式,以方便進行密鑰的處理。該程序的名稱為READKEY.EXE,其功能如下:

(1)當READKEY不帶參數時,則直接從當前機器中取得注冊密鑰;

(2)當READKEY帶參數時,則從鍵盤輸入解密密鑰來獲取注冊密鑰;

(3)當READKEY帶EXE文件名參數時,則從相應應用程序的特定位置取得解密密鑰,並生成注冊密鑰。

用戶得到注冊密鑰後,重新安裝一次應用程序或在需要輸入注冊密鑰處直接輸入密鑰,則應用程序會自動將這個注冊密鑰存放到文件的特定位置處,當應用程序被他人拷貝到其它機器中之後,由於注冊密鑰隨機器的不同而不同,所以應用程序的功能或使用次數仍然受限,要想在其它機器中使用該應用程序,則必須重新注冊。

應用程序中解密密鑰和注冊密鑰的位置,可先用特殊字符來標識,然後用DEBUG 等程序直接查找其位置,再修改其它程序中讀取或寫入數據的地址值。至於解密密鑰顯示和注冊密鑰的輸入方式,可由開發者確定是用安裝程序的方法還是在應用程序中直接處理的方法。

/*電子注冊功能密鑰讀取程序清單READKEY.C*/ 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
void readser(void);
void readser1(void);
unsigned char Buff[18];
unsigned int keyrom[9];
unsigned int sum,sumi,sumj;
unsigned int far *pt= 
(unsigned int far *)0xf000fff6L;
unsigned int i=0,j=0,m;
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>3)||((argc==2)&&(argv[1][1]!=':'))){
printf("USAGE:READKEY 程序路徑及名稱.\n");
scanf("%s",rbuff); //手工輸入加密密鑰
printf("sss:%s,%u\n",rbuff,strlen(rbuff));
j=strlen(rbuff);
if(j!=20) exit(1);
for(i=0;i<20;i++){
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);
}
printf("num:");
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;
printf("%04x",keyrom[i]);
}
printf("\n");
sum=0x2020;
for(sumj=0;sumj<4;sumj++){ //形成16位密鑰
for(sumi=0;sumi<5;sumi++)
sum-=keyrom[sumi]; //形成解密密鑰
sum^=0x0404<    sprintf(Buff+4*sumj,"%04x",sum);
}
printf(Buff);
exit(1);
}
if(argc>1){
strlwr(argv[1]);
if(strstr(argv[1],".EXE")==NULL){
printf("USAGE:READKEY 路徑及文件名.\n");
exit(1);
}
if((fp=open(argv[1],O_RDWR|O_BINARY))==-1){
printf("File %s open error!",argv[1]);
exit(1);
}
lseek(fp,0xf040L,SEEK_SET);//ROM 10個數據地址+200H
read(fp,keyrom,0xaL); //讀取數據
readser1(); //讀文件中的注冊密鑰
} else readser(); //讀機器中的注冊密鑰
}
void readser(void)
{
sum=0x2020;
for(sumj=0;sumj<4;sumj++){//形成16位密鑰
for(sumi=0;sumi<5;sumi++)
sum-=(*(pt+sumi)-0x2020);//形成解密密鑰
sum^=0x0404<    sprintf(Buff+4*sumj,"%04x",sum);
}
printf(Buff);
}
void readser1(void)
{
sum=0x2020;
for(sumj=0;sumj<4;sumj++){//形成16位密鑰
for(sumi=0;sumi<5;sumi++)
sum-=keyrom[sumi]; //形成解密密鑰
sum^=0x0404<    sprintf(Buff+4*sumj,"%04x",sum);
}
printf(Buff);
}

四、應用程序中密鑰的讀取及限制

當應用程序進行電子注冊之後,安裝程序會將注冊密鑰寫入到應用程序中。在應用程序中,判斷是否進行注冊的方法就是重新生成注冊密鑰並進行判斷處理。注冊密鑰的讀取函數如下:

void ImeCmpkey(void)
{//Windows下注冊密鑰的讀取函數
static unsigned int sum,sumi,sumj;
static BOOL flag;
static unsigned int far *pt;
static UINT Sel1,Sel2;
static WORD Seg,Off,Start;
static DWORD Bas,Lim;
flag=TRUE;
sum=0x2020;
__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(sumj=0;sumj<4;sumj++){ //形成16位密鑰
for(sumi=0;sumi<5;sumi++)
sum-=(*(pt+3+sumi)-0x2020);//形成解密密鑰
sum^=0x0404<    wsprintf((LPSTR)sImeG.ImeBuff+4*sumj,(LPSTR)"%04x",sum);
}
if(flag==TRUE) FreeSelector(Sel2);
sImeG.ImeBuff[16]==0;
for(sumi=0;sumi<16;sumi++)
sImeG.ImeBuff[sumi]+=(unsigned char)sumi;
if(lstrcmpi(sImeG.ImeBuff,sImeG.ImeKey)==0)
sImeG.UseFlag=FALSE;//已經注冊
else sImeG.UseFlag=TRUE;
}

利用注冊密鑰讀取函數,就可在應用程序的多處關鍵代碼部分增加程序功能的限制。例如,本人使用的限制代碼部分如下:

ImeCmpKey1();
if(lstrcmpi(sImeG.ImeBuff,sImeG.ImeKey)==0){
for(i=0;i<18;i++) sImeG.ImeBuff[i]=0;
sImeG.UseFlag=FALSE;//已經注冊
sImeG.UseNum=0;
} else{
sImeG.UseFlag=TRUE;//未注冊
}

由於直接讀取內存單元中的數據來生成注冊密鑰和注冊限制功能的代碼部分,其執行速度特別快,對應用程序幾乎沒有任何影響。因此,可以在應用程序中增加若干個注冊密鑰生成函數和限制功能代碼,使解密者知難而退,從而達到保護開發者的勞動成果的目的。這種增加注冊密鑰生成及判斷限制功能的方法,其缺點是使應用程序的長度增加了,但這對於目前高檔計算機的大容量硬盤來說只不過是九牛一毛而已,所以該方法非常可行。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved