【标    题】: S-Demo v 2.0 注册算法分析 
【工    具】: Peid,Ollydbg v1.10
【保护类型】: 无壳,未使用密码学算法
【难    度】: 中
【作    者】: 隐者无疆[BCG]
【目    的】: 练习注册算法的分析和注册机的编写。
【软件简介】:
    S-Demo是一个大多被破解者用来作破解动画过程的软件,它可以记录你的屏幕上的任何动作及鼠标的移动过程,同时使用了较高的压缩      率。当然压缩率可以选择,在正常的操作情况下,每分钟的生成的文件大小在200K左右(800x600x32bits)。
    S-Demo生成的文件可用软件附带的S-Player播放,也可生成可执行文件,在MS-Windows 9X,NT & Windows2000系统上脱离S-Demo播放。
    支持三种模式(全屏、拉伸、窗口)的播放,建议在全屏模式下查看动画,效果更佳。
    支持动画介绍,可说明动画作者及其想提供的一些必要信息。
【详细过程】:
1.用Peid 侦测。显示“Microsoft Visual C++ 6.0”。
2.关键代码如下:

      。。。省略部分代码。。。

0040804D  |.  >repne scas byte ptr es:[edi]                     ;  edi指向key
0040804F  |.  >not ecx
00408051  |.  >dec ecx
00408052  |.  >mov esi,ecx                                      ;  ecx 为key的长度
00408054  |.  >mov cl,byte ptr ss:[esp+14]                      ;  cl为key的首字符
00408058  |.  >mov dword ptr ss:[esp+10],esi                    ;  ss:[esp+10]=key_len
0040805C  |>  >/cmp byte ptr ds:[eax+41D528],cl                 ;  key 的首字符决定ebp的取值
00408062  |.  >|je short un_crack.00408097                      
00408064  |.  >|inc eax                                         ;  key的第0-15个字符必须为字符表中的一个
00408065  |.  >|cmp eax,10
00408068  |.^ >\jl short un_crack.0040805C                      ;  key 的首字符与字符表表的第eax个字符相同
0040806A  |>  >mov edi,un_crack.0041D568                        ;  ASCII "!@#@#SDFG^*&"
0040806F  |.  >or ecx,FFFFFFFF
00408072  |.  >xor eax,eax
00408074  |.  >repne scas byte ptr es:[edi]
00408076  |.  >not ecx
00408078  |.  >sub edi,ecx
0040807A  |.  >mov eax,ecx
0040807C  |.  >mov esi,edi
0040807E  |.  >mov edi,edx
00408080  |.  >shr ecx,2
00408083  |.  >rep movs dword ptr es:[edi],dword ptr ds:[esi]
00408085  |.  >mov ecx,eax
00408087  |.  >and ecx,3
0040808A  |.  >rep movs byte ptr es:[edi],byte ptr ds:[esi]
0040808C  |.  >pop edi
0040808D  |.  >pop esi
0040808E  |.  >pop ebp
0040808F  |.  >pop ebx
00408090  |.  >add esp,0CC
00408096  |.  >retn
00408097  |>  >cmp eax,10
0040809A  |.^ >jge short un_crack.0040806A
0040809C  |.  >mov ebx,dword ptr ss:[esp+E4]                    ;  ebx=name+"80586443474"
004080A3  |.  >mov ebp,0F                                       ;  ebp=0x0F
004080A8  |.  >sub ebp,eax                                      ;  eax=key的首字符序号;ebp赋初值
004080AA  |.  >mov edi,ebx                                      ;  edi-->name+"80586443474"
004080AC  |.  >or ecx,FFFFFFFF
004080AF  |.  >xor eax,eax
004080B1  |.  >repne scas byte ptr es:[edi]
004080B3  |.  >not ecx
004080B5  |.  >dec ecx                                          ;  ecx=Len(name)+0x0B
004080B6  |.  >xor edx,edx
004080B8  |.  >mov eax,ecx
004080BA  |.  >mov ecx,7
004080BF  |.  >div ecx                                          ;  eax=Len(name)+0x0B  ecx=7
004080C1  |.  >mov ecx,edx                                      ;  ecx为上述除法的余数
004080C3  |.  >test ecx,ecx
004080C5  |.  >jnz short un_crack.004080F2                      ;  此处必须跳转,故余数应为零,name长度可为(7*n-11)
004080C7  |.  >mov edi,un_crack.0041D520                        ;  ASCII "Guest"
004080CC  |.  >or ecx,FFFFFFFF
004080CF  |.  >xor eax,eax
004080D1  |.  >repne scas byte ptr es:[edi]
004080D3  |.  >not ecx
004080D5  |.  >sub edi,ecx                                      ;   edi-->"Guest"
004080D7  |.  >mov edx,ecx
004080D9  |.  >mov esi,edi
004080DB  |.  >mov edi,ebx
004080DD  |.  >shr ecx,2
004080E0  |.  >rep movs dword ptr es:[edi],dword ptr ds:[esi]
004080E2  |.  >mov ecx,edx
004080E4  |.  >and ecx,3
004080E7  |.  >rep movs byte ptr es:[edi],byte ptr ds:[esi]
004080E9  |.  >mov esi,dword ptr ss:[esp+10]
004080ED  |.  >mov ecx,5                                        ;  ecx=5
004080F2  |>  >mov eax,ebp                                      ;  eax<==ebp
004080F4  |.  >cdq
004080F5  |.  >idiv ecx                                         
004080F7  |.  >movsx eax,byte ptr ds:[edx+ebx]                  ;  ebx指向"Guest"
004080FB  |.  >and eax,8000000F                                 ;  eax为?
00408100  |.  >jns short un_crack.00408107
00408102  |.  >dec eax
00408103  |.  >or eax,FFFFFFF0
00408106  |.  >inc eax
00408107  |>  >mov cl,byte ptr ds:[eax+41D528]                  ;  eax应为?
0040810D  |.  >mov al,byte ptr ss:[esp+15]                      ;  al为key的第二位,由上述条件决定
00408111  |.  >cmp cl,al
00408113  |.  >je short un_crack.0040811F                       ;  必须跳转
00408115  |.  >mov edi,un_crack.0041D55C                        ;  ASCII "99#SDFG^*&"
0040811A  |.  >jmp un_crack.004081E4
0040811F  |>  >lea eax,dword ptr ds:[esi-3]                     ;  eax=esi-3
00408122  |.  >xor edi,edi
00408124  |.  >cdq
00408125  |.  >sub eax,edx
00408127  |.  >sar eax,1                                        ;  (key_len-edx-3)/2
00408129  |.  >test eax,eax
0040812B  |.  >jle un_crack.004081C0
00408131  |.  >lea esi,dword ptr ss:[esp+16]                    ;  esi指向key的第三位
00408135  |>  >/mov dl,byte ptr ds:[esi+1]                      ;  dl=key的第?+1位
00408138  |.  >|xor ecx,ecx
0040813A  |>  >|/cmp byte ptr ds:[ecx+41D528],dl                ;  dl=字符表的第??位,  是则跳转
00408140  |.  >||je short un_crack.0040815E                     ;  必须在某一次循环中从此处跳出
00408142  |.  >||inc ecx
00408143  |.  >||cmp ecx,10
00408146  |.^ >|\jl short un_crack.0040813A
00408148  |>  >|xor dl,dl                                       
0040814A  |>  >|mov bl,byte ptr ds:[esi]
0040814C  |.  >|xor ecx,ecx
0040814E  |>  >|/cmp byte ptr ds:[ecx+41D528],bl
00408154  |.  >||je short un_crack.0040817C                     ;  key的第?位为字符表中的第#个字符?是则跳转
00408156  |.  >||inc ecx
00408157  |.  >||cmp ecx,10
0040815A  |.^ >|\jl short un_crack.0040814E
0040815C  |.  >|jmp short <un_crack. 关键赋值>
0040815E  |>  >|cmp ecx,10
00408161  |.^ >|jge short un_crack.00408148                     ;  不能跳转
00408163  |.  >|sub ecx,ebp                                     ;  ebp的值在前面已经
00408165  |.  >|add ecx,3E80
0040816B  |.  >|mov edx,ecx
0040816D  |.  >|and edx,8000000F
00408173  |.^ >|jns short un_crack.0040814A
00408175  |.  >|dec edx
00408176  |.  >|or edx,FFFFFFF0
00408179  |.  >|inc edx
0040817A  |.^ >|jmp short un_crack.0040814A
0040817C  |>  >|cmp ecx,10
0040817F  |.  >|jge short <un_crack. 关键赋值>
00408181  |.  >|sub ecx,ebp                                     ;  ecx=?
00408183  |.  >|add ecx,3E80
00408189  |.  >|and ecx,8000000F
0040818F  |.  >|jns short un_crack.0040819A
00408191  |.  >|dec ecx
00408192  |.  >|or ecx,FFFFFFF0
00408195  |.  >|inc ecx
00408196  |.  >|jmp short un_crack.0040819A
00408198 >|>  >|xor cl,cl                                         
0040819A  |>  >|mov bl,cl
0040819C  |.  >|add esi,2
0040819F  |.  >|shl bl,4                                        ;  bl=bl*0x10
004081A2  |.  >|add bl,dl                                       ;  bl=?
004081A4  |.  >|mov edx,dword ptr ss:[esp+E0]
004081AB  |.  >|movsx ecx,cl
004081AE  |.  >|mov byte ptr ds:[edi+edx],bl                    ;  关键赋值。生成字符串,为后面的比较作准备
004081B1  |.  >|add ebp,ecx                                     ;  ebp的值在此处更新
004081B3  |.  >|inc edi
004081B4  |.  >|cmp edi,eax
004081B6  |.^ >\jl un_crack.00408135
004081BC  |.  >mov esi,dword ptr ss:[esp+10]                    ;  esi=len(key)
004081C0  |>  >lea edx,dword ptr ds:[esi+ebp-3]                 ;  edx=ebp+len(key)-3
004081C4  |.  >and edx,8000000F
004081CA  |.  >jns short un_crack.004081D1
004081CC  |.  >dec edx
004081CD  |.  >or edx,FFFFFFF0
004081D0  |.  >inc edx
004081D1  |>  >mov cl,byte ptr ss:[esp+esi+13]                  ;
004081D5  |.  >mov bl,byte ptr ds:[edx+41D528]                  ;  
004081DB  |.  >cmp cl,bl                                        ;  key 的最后一个字符字符表的第edx个字符  
004081DD  |.  >je short un_crack.00408211                       ;  此处必须跳转
004081DF  |.  >mov edi,un_crack.0041D550                        ;  ASCII "45#SDFG^*&"
004081E4  |>  >or ecx,FFFFFFFF
004081E7  |.  >xor eax,eax
004081E9  |.  >repne scas byte ptr es:[edi]
004081EB  |.  >not ecx
004081ED  |.  >sub edi,ecx
004081EF  |.  >mov edx,ecx
004081F1  |.  >mov esi,edi
004081F3  |.  >mov edi,dword ptr ss:[esp+E0]
004081FA  |.  >shr ecx,2
004081FD  |.  >rep movs dword ptr es:[edi],dword ptr ds:[esi]
004081FF  |.  >mov ecx,edx
00408201  |.  >and ecx,3
00408204  |.  >rep movs byte ptr es:[edi],byte ptr ds:[esi]
00408206  |.  >pop edi
00408207  |.  >pop esi
00408208  |.  >pop ebp
00408209  |.  >pop ebx
0040820A  |.  >add esp,0CC
00408210  |.  >retn
00408211  |>  >mov ecx,dword ptr ss:[esp+E0]
00408218  |.  >pop edi
00408219  |.  >pop esi
0040821A  |.  >pop ebp
0040821B  |.  >mov byte ptr ds:[eax+ecx],0
0040821F  |.  >pop ebx
00408220  |.  >add esp,0CC
00408226  \.  >retn



返回到此处
004083C8  |.  >add esp,18
004083CB  |.  >mov edi,un_crack.0041D1D0                        ;  ASCII "Clayman"
004083D0  |.  >mov esi,ebx                                      ;  ebx = ds:[0012E0F8]
004083D2  |>  >/mov dl,byte ptr ds:[esi]
004083D4  |.  >|mov cl,byte ptr ds:[edi]
004083D6  |.  >|mov al,dl
004083D8  |.  >|cmp dl,cl
004083DA  |.  >|jnz short <un_crack.出错!!!>
004083DC  |.  >|test al,al
004083DE  |.  >|je short un_crack.004083F6
004083E0  |.  >|mov cl,byte ptr ds:[esi+1]
004083E3  |.  >|mov dl,byte ptr ds:[edi+1]
004083E6  |.  >|mov al,cl
004083E8  |.  >|cmp cl,dl
004083EA  |.  >|jnz short <un_crack.出错!!!>
004083EC  |.  >|add esi,2
004083EF  |.  >|add edi,2
004083F2  |.  >|test al,al
004083F4  |.^ >\jnz short un_crack.004083D2
004083F6  |>  >xor eax,eax
004083F8  |.  >jmp short <un_crack.Yahoo!!!>
004083FA >|>  >sbb eax,eax
004083FC  |.  >sbb eax,-1
004083FF >|>  >test eax,eax
00408401  |.  >je short <un_crack.成功!!>

     。。。省略以后的代码。。。


3.注册码验证算法分析:

字符表:str1[]="FZRHK01WGTPQSAVC",固定存放在ds:[41d528]
       str2[]="Guest"
       str3[]="Clayman"
  对注册名的要求:其长度name_len加11的和,为7的倍数
  对注册码的要求:
 注册码长度key_len为17
 注册码的第一个字符必须为字符表str1中的一个,记其位置为n1;ebp赋初值为0x0F-n1。
 注册码的第二个字符必须为str1[str2[ebp%5]&0xf]。
 注册码的第三至第十六个字符满足如下条件:
   为了便于叙述,设整型变量i. (i=0,1,2,3,4,5,6),注册码的第(2*i+3)位和第(2*i+4)位分别对应字符表str1中的第n2和第n3个字符,
   bl=(((n1-ebp+0x3E80)&0x8000000F)<<4),dl=((n2-ebp+0x3E80)&0x8000000F).
   (bl+dl)等于对应的str3[i]的Ascii码值。
   另外,从第三位到第十六位每比较完两位,ebp的值进行一次更新。更新的方法是:ebp=ebp+al
 注册码的第十七位必须为str1[(key_len+ebp-3)&0x8000000F].


4.注册码生成算法分析:
  
  注册码生成算法的一个关键是其中的第三至第十六个字符的生成。
  现在将str3中的字符的Ascii码值写成十六进制的形式,以'C'为例,它的Ascii码值为0x43。仔细分析可知,事实上bl和dl分别对应此Ascii码值的高4位和低4位。这些值是事先已经确定下来的。
  接下来就要看,如何选择n1和n2使它们经过一系列运算后,得到正确的bl和dl。以n1的产生为例:
  由dl=(n2-ebp+0x3E80)&0x8000000F可见,一个数与0x8000000F的and运算可以近似认为让它对0x10取模。
  即dl=(n2-ebp+0x3E80)%0x10。
  所以,n2=0x10*N+dl+ebp-0x3E80. 其中,dl为[0,15]之间的整数,ebp由前面的运算确定。通过尝试得到N的一个合适的取值,从而确定
n2的值。

5.注册机源代码
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <string.h>

int seed=0;
char name[20];

char find_char(int ebp,int i)
{
    const char str[]="FZRHK01WGTPQSAVC";
    const con_int[]={0x4,0x3,0x6,0xC,0x6,0x1,0x7,0x9,0x6,0xD,0x6,0x1,0x6,0xE};
    int n=0x3e0,flag=0,index;
    
    while(!flag)
    {
      index=ebp+con_int[i]+n*0x10-0x3e80;
      if(index>=0&&index<16)
        flag=1;
      else
        ++n;
    }
    return str[index];
}

int rand()
{
  return 19841113*seed%0x10;
}


void main()
{
  char key[50],ch;
  const char str[]="FZRHK01WGTPQSAVC\0\0\0",str2[]="Guest";
  unsigned int r,i,name_len,key_len=17,index=0;
  const con_int[]={0x4,0x3,0x6,0xC,0x6,0x1,0x7,0x9,0x6,0xD,0x6,0x1,0x6,0xE};
  unsigned long ebp,edx,ecx;


  printf("********************************************************************************\n");
  printf("                         Keygen for S-Demo v 2.0                                \n");
  printf("                           maker:隐者无疆[BCG]                                  \n");
  printf("********************************************************************************\n");

  printf("请输入注册名(其长度加上11后,应为7的倍数):");
  scanf("%s",&name);
  name_len=strlen(name);


  while((name_len+11)%7!=0)
  {
    printf("请输入注册名(其长度加上11后,应为7的倍数):");
    scanf("%s",&name);
    name_len=strlen(name);
  }
  
  

  printf("请输入一个整数,用作生成随机数的种子:");
  scanf("%d",&seed);
  
  r=rand();
  ebp=15-r;
  edx=ebp%5;
  ecx=ebp/5;
  key[index++]=str[r];         //通过生成伪随机数,确定注册码的第一位。
  key[index++]=str[str2[edx]&0xf];      //生成注册码的第二位
  
  if(ecx&0x8000==1)          //模拟cdq
    edx=0xffff;
  else
    edx=0;                   

  for(i=0;i<(key_len-edx-3)/2;i++)        //生成注册码的第三至十六位
  {
    ch=find_char(ebp,2*i);
    key[index++]=ch;

    ch=find_char(ebp,2*i+1);
    key[index++]=ch;

    ebp+=con_int[2*i];         //更新ebp的值
  }
    
  key[index++]=str[(key_len+ebp-3)%0x10];          //生成注册码的第十七位
  key[index]='\0';

  printf("你的注册码为%s\n",&key);
  
  printf("Press any key to abort.");
  getchar();
  getchar();
  
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////