我灌!
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是分配空选择器的段寄存器模板,可以利用Global Alloc()函数分配内存,再利用GlobalHandleToSel()函数将内存句柄 转换为相应选择器,内存单元访问结束后再利用GlobalFree()释放分 配的内存。最简单的方法就是将系统的数据段寄存器__DS直接作为模 板参数,这个参数在一般应用程序中完全可以正常使用。然后利用Se tSelectorBase()和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); } 总之,共享发布软件的制作应做到:注册源要选准、注册算法要 多变、注册码要再加密、注册机要管好、注册点要多方位、注册方式 要在线、注册入口要多点。这样才能确保软件的安全可靠。
我在灌!!! 在微软的操作系统中,硬盘的磁盘序列号是在硬盘分区后系统产生的8字节随机数字,理论上有重复的可能,但实际上找到两个相同的分区序列号很难,因此,在实际应用中可以把硬盘序列号作为唯一识别码用于我们的加密系统,实际应用中可以对序列号做几次逻辑运算,让别人觉得不是磁盘序列号。注意,软盘也有磁盘序列号,是在格式化后产生的,当进行磁盘复制时,两个磁盘的序列号也被复制为一样的。因此,不能利用软盘的序列号进行加密。当然,硬盘也可以用GHOST等软件克隆,但是为了使用某个软件而去克隆整个硬盘的情况是极少发生的。
---- 下面代码是一个C动态连接库,在Windows98/95/NT4.0+VC5.0环境下编译通过,用于产生密码锁。
// MyDll.cpp
//产生密码锁 /
#include < windows.h > #include < winbase.h > #include < stdio.h >
//#include "mydll.h "
BOOL APIENTRY DllMain(HANDLE hInst, DWORD ul_reason_being_called, LPVOID lpReserved) { return 1; UNREFERENCED_PARAMETER(hInst); UNREFERENCED_PARAMETER(ul_reason_being_called); UNREFERENCED_PARAMETER(lpReserved); }
/////////////////////////////////////////////////// DWORD APIENTRY GetDiskSerialNo(void) { LPCTSTR lpRootPathName= "c:\\ "; //取C盘的序列号 LPTSTR lpVolumeNameBuffer=new char[12];//磁盘卷标 DWORD nVolumeNameSize=12;
DWORD VolumeSerialNumber;//磁盘序列号 DWORD MaximumComponentLength; LPTSTR lpFileSystemNameBuffer=new char[10]; DWORD nFileSystemNameSize=10; DWORD FileSystemFlags; GetVolumeInformation(lpRootPathName, lpVolumeNameBuffer, nVolumeNameSize, &VolumeSerialNumber, &MaximumComponentLength, &FileSystemFlags, lpFileSystemNameBuffer, nFileSystemNameSize); VolumeSerialNumber^=0x90909090;//做一转换, 别让人一眼就看出是磁盘序列号 return VolumeSerialNumber; }
DWORD APIENTRY CreateLock(void) { DWORD dwNo; dwNo=GetDiskSerialNo(); dwNo^=0x90909090; return dwNo; }
DWORD APIENTRY TestKey(char far *sSN) {
DWORD dwSN; if(strlen(sSN)==0){return 0;} sscanf(sSN, "%8lX ",&dwSN); dwSN=~dwSN;///////// 加密运算 dwSN^=0x88888888;//////////加密运算 sprintf(sSN, "%8lX ",dwSN); if (dwSN==CreateLock())return 1; else return 0; }
---- 下面是解密机的源代码。因为功能简单,不用编译成Windows应用程序,做成控制台(DOS)下应用程序就可以完成任务。 //GetKey.C //解密机,获得密匙 #include < stdio.h > void main(void) { unsigned long dwSN; printf( "\n请输入密码锁号: "); scanf( "%8lX ",&dwSN); dwSN=~dwSN;//////解密运算 dwSN^=0x88888888;/////////解密运算 printf( "该用户的密匙是 %8lX\n ",dwSN);
}
---- 使用说明:上面的DLL随应用程序一同发行。解密机软件作者保留,千万别散发. ---- 注册时,调用CreateLock()得到机器的识别号(密码锁),在应用程序中显示给用户, 用户得到该序列号后通知软件作者(作者一定得留下联系方法),作者根据密码锁利用解密机(见程序)得到密匙,通过网络或电话送给用户,用户根据密匙注册,注册后应用程序把密匙写进Windows注册表,以备TestKey(x)函数检测密匙用。
---- 以后应用程序每次运行都要调用TestKey(x)函数(其中x=密匙),用于检测密匙是否正确,正确返回非零值(合法用户),程序继续运行,否则(非法用户)退出程序。
---- 下图用VC5.0编制的一个基于对话框的简单应用程序的界面(),当输入正确的密匙,将显示”合法用户”,否则显示”非法用户”,并退出程序.因篇幅所限,源程序不再这里给出,请见谅.
|