• 标 题:我的破解心得(5) (16千字)
  • 作 者:chcw
  • 时 间:2001-3-13 18:04:19
  • 链 接:http://bbs.pediy.com

破解程序:
    HyperCam Version 1.20.04(for Intel processors)

破解工具:
    SoftIce 3.0 for Windows95(或更高版本)
 
破解者:
  chcw

1. 用WinICE载入HyperCam.exe
2. 在HyperCam中选择License/Enter License Now,弹出HyperCam License对话框.
3. 按Ctrl-D,切换到SoftICE窗口,在SoftICE命令行设置断点 BPX MessageBoxA
4. 再按Ctrl-D,回到执行程序HyperCam.在Register和Key中随便填入注册码,如Register=chcw,Key=
  9530109.并选择一种注册类型,如Sigle User - Number of Licensed 1,添完上述内容后,选择
  'OK', 触发SoftICE。
5. SoftICE的光带停留在MessageBoxA函数的入口处:

    USER32!MessageBoxA
        015F:BFF541BA  PUSH EBP

  将光标移到MessageBoxA函数的返回处
       
        015F:BFF541D    RET 0010   

  并键入命令HERE,程序执行到光标所在处。(由于函数MessageBoxA要调用函数MessageBoxExA,
  所以在上述执行过程中会出现一个对话框,直接选'OK'或'确定’即可)
6. 连续按F10键,回到MessageBoxA函数被调用处:

        015F:00427881  CALL [USER32!MessageBoxA]
        015F:00427887  POP EDI
        015F:00427888  POP ESI
        015F:00427889  RET 000C
       
  这部分代码在程序HyperCam.exe中,但在程序调用MessageBoxA后,程序立即以子程序的方式返回,
  综合在调用MessageBoxA前的代码,可以看出这是程序HyperCam中的一个子程序(入口地址为00427853),
  其主要功能大致是显示一个信息框而已。因此,我们没有必要把注意力放在这里。继续按F10,跳出这
  个子程序。返回到调用它的上一层程序。

        015F:0040A03B  CALL 00427853 (调用上述子程序00427853)
     
  显然,既然CALL 00427853的功能是显示一个注册错误的信息框,那么在它被执行之前,一定有一些
  函数或语句是用来判断注册正确与否,并根据判断结果来决定是显示错误信息,还是提示注册成功
  (HyperCam在用户输入正确的注册码时,没有任何提示信息)。 从CALL 00427853语句向上找,在
  015F:0040A020处有一条语句JMP 0040A040,跳转地址正好是CALL 00427853的下一条语句。由于这是
  一条无条件跳转语句,我们继续往上找能使程序流程执行到这条跳转语句的条件跳转语句(这里的条
  件,就是指注册码正确与否)。 在015F:0040A00D和015F:00409FF2处各有一条条件跳转语句,但它
  们都不影响程序的主要流程。最后在015F:00409FD8处,有一个条件跳转:

        015F:00409FD1  CALL 004092E0    ;    调用004092E0检查注册码的有效性
        015F:00409FD6  TEST EAX, EAX    ;    若返回值为0,则显示错误信息。
        015F:00409FD8  JE  0040A022    ;

  分析以上语句,不难看处,当调用函数004092E0的返回值为0时,将显示注册错误的对话框窗口。可
  见,函数4092E0应该是HyperCam检查注册码是否正确的函数。键入命令BC *,清除原先的断点;并
  在015F:00409FD1处双击鼠标,设置执行断点。 
7. 按Ctrl D回到HyperCam中,仍使用原先的注册码,再次选择'OK'后。触发SoftICE,执行亮条停留在
  断点015F:00409FD1  CALL 004092E0处,先按F10,Step Out过这个函数的调用。此时,EAX的值为0
  并显示高亮,显然函数004092E0改动了EAX。在SoftICE的命令行下,键入r EAX=1,造成注册成功的假   
  象,再按Ctrl D,继续执行程序,返回到HyperCam。这时我们发现,提示用户输入注册信息的窗口
  HyperCam License已经关闭。但在License页中显示的信息还是未注册, 这是怎么回事呢?
      我们可以作这样一个假设,004092E0函数不仅通过返回值来表明注册信息正确与否,还通过传递
  指针参数来返回注册信息。调用004092E0的函数再进一步将这些注册信息记录在磁盘上。由于我们在
  上面程序的运行过程中没有填充这些信息,因而尽管程序的流程大致是正确的,但注册信息却没有被
  保存下来。
8. 如果要用Patch方法来破解HyperCam,显然不能只修改015F:00409FD8处的条件判断,具体该如何破解,
  我没有仔细研究。我主要分析了获取有效注册码的方法。
9. 继续上面的破解过程,在015F:00409FD1处设置有执行断点。按'OK'后,触发SofeICE,执行亮条停留
  在断点处,按F8,进入函数004092E0中。
10.接下来按F10键,到达函数体中的两个子函数调用子函数调用015F:00409313 CALL 和0041F3AC,
  015F:00931C CALL 0041F3BF该函数的大致作用  是判断时区和现在的时间,可能与注册方式中的临时
  性注册有关,由于我们选择单用户按份数注册的方式。因而可以直接跳过(Step Out)这两个函数。继
  续按F10键。
11.接下来进入两个循环:

    015F:0040932E    MOV EDX, DWORD PTR [ESI]
                ...    
    015F:00409348    CALL 0040EDB0        ;    字符串比较(stricmp)
    015F:0040934D    ADD ESP, 0000000C    
    015F:00409350    TEST EAX, EAX        ;    
    015F:00409352    JE 004096B5        ;    若eax==0则跳转    
    015F:00409358    ADD ESI, 00000004    ;    指向下一个字符串
    015F:0040935B    CMP ESI, 00436EBC    ;    字符串数组都比较完了吗?
    015F:00409361    JB 0040932E        ;    若未比较完,则继续比较。
    
      在循环中不断调用子程序0040EDB0,该程序的功能相当于stricmp,它将用户输入的姓名和系统内
  部的两个数组中的字符串进行比较。字符串数组如下:
    char *s1[]={"hacker","ED!SON","tHATDUDE","super user","saltine","xygorf","Borin Thibault",
        "Alexander Chen","-M-O-A-","^SaTaNa","Michael Jackon"}
    char *s2[]={"abel", "Monkey", "jackkuo", "TRACY"}
      写到这里,也许有人会猜想,这可能是程序在比较用户输入的姓名是否和系统保留的几个字符串相
  同,若相同,则继续检验注册码的正确性,也就是说,这是系统留下的后门。当时我也这么想,那么剩
  下的只有注册码了。于是,我按Ctrl D,回到HyperCam的注册窗口,将名字那一栏用上述数组中的某个
  字符串填写(如"saltine"),再按Ctrl D,在命令行输入s ds:0 l ffffffff '9530109',找到了存放注册码
  的内存地址0030:80527d70, 然后设置断点BPM 0030:80527d70,再键入X,运行程序。但是在拦截到对字符
  串'9530109'的所有访问中,却没有发现可能是检验注册码的语句。看来我们原先的猜想是错误的。再次
  分析上述的循环后发现。字符串数组s1、s2实际上是两个黑名单,当用户输入的注册名是数组中的某个
  字符串时,系统立即认为注册失败,不再检查注册码。而只要你输入的注册名不是上面数组中的某个字符
  串后,就可以跳过这两个循环,
12.继续按F10键,执行到下列语句处:

    015F:00409395    PUSH ECX        ;    将注册码地址压栈
    015F:00409396    CALL 004098C0
    015F:0040939B    MOV EBX, EAX        ;    EBX <- EAX
    015F:0040939D    ADD ESP, 4
    015F:004093A0    TEST EBX, EBX        ;    返回值在EBX中
    015F:004093A2    JZ 004096B5        ;    若EBX为0,则程序认为注册出错。

  由于子程序004098C0的参数为注册码,而该函数返回值决定注册是否成功,因而有必要分析该子程序。按
  F8,进入该程序:
              ...    
    015F:004098D6    CMP AL, 41        ;    
    015F:004098D8    JL 004098E6        ;    对注册码进行检查,
    015F:004098DA    MOV AL, [ECX]        ;      将不在'A'-'Z'范围内的字符过滤掉。
    015F:004098DC    CMP AL, 5A
    015F:004098DE    JG 004098E6
              ...     
    015F:00409903    CMP ECX, 00000080    
    015F:0040990D    JGE 004099A5        ;    检查注册码的长度(存放在ECX中),
    015F:00409913    CMP ECX, 03        ;    长度<=3或>=128的为非法。
    015F:00409916    JLE 004099A5
              ...    
    015F:0040991C    MOVSX EBP, BYTE PTR [ESP+16]    
    015F:00409921    SUB EBP, 00000041        ;    对注册码进行某种换算,
              ...                
    015F:0040992C    MOV AL, BYTE PTR [ESP+EBX+14]    ;    得到一个新的加密码SecretCode。
              ...
    015F:00409947    CALL 00409A20            
              ...    
    015F:00409967    INC EBX
    015F:00409968    CMP EBX, ECX
    015F:0040996A    JL 0040992C    
              ...    
    015F:0040996C    MOVSX ESI, BYTE PTR [EDI+0043B77F]    ;    将加密码分成前n-1个字符和    
    015F:0040997B    MOV BYTE PTR [EDI+0043B77F], 00        ;    最后一个字符两部分,
    015F:00409982    CALL 004099C0                ;    分别作某种运算,
              ...                    
    015F:0040998C    CMP ESI, EAX                ;    比较其结果是否相同。
    015F:0040998E    SETNE CL                ;    若不同,则注册为非法。
              ...
    015F:00409992    AND ECX, 0043B780    ;    将加密码SecretCode(去掉最后一个字符)的地址返回。
    015F:00409998    MOV EAX, ECX        ;    
  子程序004098C0对注册码进行检查和判断,生成SecretCode,并将结果放在EAX返回。
13.从子程序004098C0返回后,继续按F10键,下面一部分程序将再次检查你的注册姓名是否在黑名单s2上(看
  来作者对数组s2中的人恨之入骨)跳过这段程序。到015F:0040945B    MOV EAX,[EBP+14]处。
14.接下来,程序将对用户输入的注册名进行处理:

    015F:004094B9    LEA EDI, DWORD PTR [ESP+20]        ;    用户输入的注册名所存放的地址
                    ...     
    015F:004094C1    LEA EDX, DWORD PTR [ESP+00000120]    ;    系统处理后得到的新串LName存放的地址 
    015F:004094C8    REPNZ SCASB
    015F:004094CA    NOT ECX                    ;    取得注册名的串长度    
                    ...    
    015F:004094DE    REPZ MOVSD                ;    将注册名复制到新串LName中
                    ...    
    015F:004094F5    CMP ECX, 00000010            ;    检查用户输入的姓名是否小于16个字符,
    015F:004094F8    JL 004094B9                ;    如果是,则将姓名串不断重复复制到
                                ;    LName处,直到超过16个字符为止。
  由于我们输入的是"chcw", 因而系统处理后将得到串LName="CHCWCHCWCHCWCHCW"。
15.继续按F10,直到下列语句处:
    015F:004096D2    MOV AL, [EBX]        ;    AL <- SecretCode[0](EBX中存放的是SecretCode的地址)
    015F:004096D4    MOV EBP, [ESP+00000238]
    015F:004096DB    CMP AL, 50        ;    若AL=='P',则为第一种注册方式(Single User)    
    015F:004096DD  JZ 00409769        ;    跳转到第一种注册方式的处理语句处。
    015F:004096E3  CMP AL, 53        ;    若AL=='S',则为第二种注册方式(Unlimited Site License)
    015F:004096E5    JZ 0040974F        ;    跳转到第二种注册方式的处理语句处。
    015F:004096E7    CMP AL, 57        ;    若AL=='P',则为第三种注册方式(Unlimited Word-Wide License)
    015F:004096E9    JNZ 00409707        ;    若三种都不是,则注册出错。

    015F:004096EB    MOV EDI, DWORD PTR [ESP+0000023C]
    015F:004096F2    CMP EDI, 00000003        ;    判断用户输入的注册方式是否为3
    015F:004096F5    JNE 00409707            ;    若不是,则注册出错。
    015F:004096F7    CMP BYTE PTR [EBX+01], 4F    ;    判断SecretCode[1]是否为'O'
    015F:004096FB    JNE 00409707            ;    若不是,则注册出错。    
    015F:004096FD    CMP BYTE PTR [EBX+02], 52    ;    判断SecretCode[1]是否为'R'
    015F:00409701    JE 00409787            ;    若不是,则注册出错。
              ...
    015F:00409707    LEA ECX, DWORD PTR [ESP+00000230]    ;    注册出错处理
              ...
    015F:0040974F    MOV EDI, DWORD PTR [ESP+0000023C]
    015F:00409756    CMP EDI, 00000002        ;    判断用户输入的注册方式是否为2
    015F:00409759    JNE 00409707            ;    若不是,则注册出错。
    015F:0040975B    CMP BYTE PTR [EBX+01], 49    ;    判断SecretCode[1]是否为'I'
    015F:0040975F    JNE 00409707            ;    若不是,则注册出错。
    015F:00409761    CMP BYTE PTR [EBX+02], 54    ;    判断SecretCode[1]是否为'T'    
    015F:00409765    JE 00409787            
    015F:00409767    JMP 00409707            ;    若不是,则注册出错。

    015F:00409769    LEA EAX, DWORD PTR [EBX+01]    ;取得SecretCode[1]的地址
    015F:0040976C    PUSH 00000002
      015F:0040976E    PUSH EAX        ;    将SecretCode[1]的地址压栈    
      015F:0040976F    CALL 00409A40        ;    根据字符SecretCode[1]和字符SecretCode[2](大写字符)
                    ;计算注册的数目。由于采用的是26进制,因而最多可以表示26x26份拷贝。        
    015F:00409774    MOV EDI, DWORD PTR [ESP+00000244]
    015F:0040977B    ADD ESP, 00000008    
    015F:0040977E    CMP EDI, 00000001    ;    判断用户输入的注册方式是否为2
    015F:00409781    JNE 00409707        ;    若不是,则注册出错。

    015F:00409783    CMP EBP, EAX        ;    比较计算所得得注册数是否与用户输入的注册数相同
    015F:00409785    JNE 00409707        ;    若不是,则注册出错。


  从上面的程序段可以看出,该段程序的主要功能是对SecretCode进行检查,根据用户注册的方式不同,可
  分为三种情况:
    1. 第一种注册方式(Single User),SecretCode的第一个字符必须是'P',接下来的两个字符应为注册数目的
        26进制值。
    2.    第二种注册方式(Unlimited Site License),该种情况下,SecretCode的头三个字符必须是'S'、'I'、'T'。
    3. 第三种注册方式(Unlimited Word-Wide License),该种情况下,SecretCode的头三个字符必须是'W'、'O'、
        'R'。

16.在SecretCode的前三个字符与用户的注册方式完全相符后,继续按F10键,到下列语句处:

    015F:004097AB    PUSH 00436F68        ;    字符串"HCA"的地址压栈
        015F:004097B0    PUSH EBX        ;    SecretCode[3]的地址压栈
        015F:004097B1    CALL 0040ED70        ;    比较以SecretCode[3]为启始地址的字符串的前三个字符是否为'HCA'
    015F:004097B6    ADD ESP, 0000000C
    015F:004097B9    TEST EAX, EAX        ;    若不是"HCA",则注册出错。
    015F:004097BB    JNE 00409707

  该段程序检查SecretCode的第三到第六个字符是否为"HCA"(就是HyperCam的缩写),如果不是,则注册出错。
17.通过上述检查后,继续按F10键,到以下语句处:
    015F:004097C8    MOV DL, [EAX]    ;    DL <- LName[i]
    015F:004097CA    MOV BL, [ESI]    ;    BL <- Secret[6+j]
    015F:004097CC    MOV CL, DL
    015F:004097CE    CMP DL,    BL    ;    比较DL,BL是否相当。    
    015F:004097D0    JNZ 004097F0    ;    若不相等,则注册出错
    015F:004097D2    TEST CL, CL    ;    判断是否已经到达字符串的末尾
    015F:004097D4    JZ 004097EC
              ...
    015F:004097E2    ADD EAX, 02    ;    i+=2
    015F:004097E5    ADD ESI, 02    ;    j+=2
    015F:004097E8    TEST CL, CL    ;    判断是否已经到达字符串的末尾
    015F:004097EA    JNZ 004097C8    ;    若不是,则循环。

  这部分程序段将SecretCode(Secret在第14步处调用程序004098C0时产生)剩余的字符所组成的字符串与字符串LName进行比
  较。若二者相同,则认为注册成功。
18.至此,我们已经分析完HyperCam的注册检验过程。在此过程中,程序不在内存中生成任何合法的注册码,而仅根据用户输入
  的注册码产生一个SecretCode,并对此SecretCode进行后续的操作,这使得破解过程较为复杂。而且在整个注册检验过程中,
  程序还多次检验用户输入的注册名是否是常用的Cracker名字,这使破解的复杂程度进一步增加。因此,我们有理由相信,
  HyperCam程序在编写过程中,曾经有意识的加入了反Crack的代码。
19.下面我们要为HyperCam写一个注册码生成器,通过对HyperCam注册检验过程的分析,我们已经知道,该注册码生成器的输入
  信息为:
    1.用户注册的姓名Name。
    2.用户注册的方式Register type。
    3.对于以第一种注册方式(Single User)注册的用户,还需要输入注册的数目Copy Number.
    根据输入的信息,可以生成中间代码SecretCode,它由三部分组成,分别对应于下列信息:
    1.第一部分为3个字符,表示注册的方式代码,可以由输入2、3换算得到。
    2.第二部分为固定的三个字符"HCA"。
    3.第三部分为LName,可以由输入Name换算得到。
    得到了SecretCode之后,我们可以进一步求取最终的注册码Register Code, 这是一个逆向的过程,由于SecretCode的每
    个字符是通过Register Code的当前字符以及SecretCode的前一个字符生成的,因此,只要注册码的前面部分是正确的,
    我们就可以采用穷举法变换Register Code当前的字符,并检验由此生成的SecretCode是否合法,从而能从左自右,试探
    出全部合法的注册字符。由于每个字符只能取大写字符,因此全部的工作量只有26 X strlen(Register Code)。另外注册
    码Register Code除了要求能正确生成SecretCode外,其自身还要满足一定的条件(在第12步中已经指出),这可以通过
    对Register Code的最后一个字符进行检验来实现。

附:HyperCam的注册码生成器KeyMaker.C
---------------------- KeyMaker.C -----------------------------
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <ctype.h>
#include <process.h>

int shiftsum(char *result, int len)
{
int fact,i,x,sum=0;
char c;
fact=1;
if (len<fact) len=strlen(result);
if (len<=0) return sum;
for (i=0; i<len; i++) {
  c=result[i];
  x=c-'A';
  x*=fact;
  sum += x;
  fact = 26*fact;
}
return sum;
}

char checksum(char *regcode)
{
int sum=0,val,i;
char c,tmp;

for (i=0;i<strlen(regcode);i++) {
  c=regcode[i];
  val=c-'A';
  if (i & 1) val=val*2;
  if (val>=26) val=val-25;
  sum += val;
}

tmp=sum % 26;
return tmp;
}

int genregcode(char *regcode, char *result, int *tail)
{
char *table="EHIOTBVPSFRNWYQCJAGKUZXDML";
char tmpcode[27];
int  i,j,x,y;
long  len;
char c;

/* Filter characters that are not in 'A' to 'Z' */
for (i=0,j=0; regcode[i]!='\0'; i++)
  if (isupper(regcode[i])) tmpcode[j++]=regcode[i];
tmpcode[j]='\0';

/* Test the len of the regcode */
len=strlen(tmpcode);
if ((len>=128) || (len<=3)) {
  printf("error: cannot accept a regcode with mislen");
  return NULL;
}

i=j=0;
y=tmpcode[2]-'A';
for (i=0;i<len;i++)
if (i!=2) {
  c=tmpcode[i];
  if (c<'A') return NULL;
  if (tmpcode[j]>'Z') return NULL;
  x=strchr(table, c)-table;
  x=x-y;
  x--;
  if (x<0) x+=26;
  result[j++]=x+'A';
  y=c-'A';
}
result[j]='\0';

*tail=result[j-1]-'A';
result[j-1]='\0';

return checksum(result);
}

void main()
{
char regcode[27];
char secretcode[27],tmp[27];
int i,j,len;
char ch;
char LName[33],Name[33];
int rtype,copy;
int chksum,tail;
int again;

for (i=0;i<26;i++)
  regcode[i]='A';
regcode[i]='\0';

printf("\n\tHyper Cam Cracker\n\tWritten By Mr. Chcw\n\n");

printf("\tPlease enter your name: ");
gets(Name);
strcpy(LName, Name);

if (strlen(Name) >= 32) {
  printf("Error : User Name is too long\n");
  exit(1);
}

if (strlen(Name)<16) {
  while (strlen(LName)<16)
    strcat(LName, Name);
  LName[16]='\0';
}

do  {
  printf("\n\tPlease choose the license type:\n");
  printf("\t1. Single User\n");
  printf("\t2. Unlimited Site License\n");
  printf("\t3. Unlimited World Wide License\n\t");
  scanf("%d", &rtype);

  again=0;

  switch (rtype)  {
    case 1:
      printf("\n\tPlease enter number of lincensed: ");
      scanf("%d", &copy);

      secretcode[0] = 'P';
      secretcode[1] = 'A' + copy % 26;
      secretcode[2] = 'A' + copy / 26;
      break;
    case 2:
      strcpy(secretcode, "SIT");
      break;
    case 3:
      strcpy(secretcode, "WOR");
      break;
    default:
      printf("\tIncorrect option, please enter a number in 1-3");
      again=1;
  }
} while (again);

secretcode[3]='\0';
strcat(secretcode, "HCA");        // Add the HCA mark
strcat(secretcode, LName);        // Add the LName String
l