[转载]共享软件的注册加密法

出处:CSDN
作者:宋立波

[---  资料是从免费网站上获取的,上载在这里,只为交流学习目的,文章原作者保留所有权力,
如本博客的内容侵犯了你的权益,请与以下地址联系,本人获知后,马上删除。同时本人深表歉意,并致以崇高的谢意!
erwin_609@msn.com  ---]

  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);
  }

  总之,共享发布软件的制作应做到:注册源要选准、注册算法要多变、注册码要再加密、注册机要管好、注册点要多方位、注册方式要在线、注册入口要多点。这样才能确保软件的安全可靠。



posted @ 2007-06-13 22:50  xerwin  阅读(1212)  评论(1编辑  收藏  举报