我的破解心得(5) (16千字)

看雪資料發表於2001-03-13

破解程式:
    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

相關文章