【標題:Tag&Rename 1.7 文章二 :此軟體的註冊碼演算法。( 進來看看 8^) )】

看雪資料發表於2000-11-14

【標題:Tag&Rename 1.7 文章二 :此軟體的註冊碼演算法。(  進來看看 8^)  )】
==========================================================================================
軟體      :Tag&Rename 1.7

軟體簡介  :一個可以修改MP3 和 VQF 音樂檔案中的TAG說明的程式。目前尚未支援MP3最新的ID3v2
            但是,仍然是一個很好用的編輯工具。

下載處    :軟體主頁: http://www.softpointer.com/tr.htm
==========================================================================================
本文作者  :McNy@Work
日期      :2000年11月11日 --> 2000年11月13日
Email      :mcny_work@yahoo.com
            (郵件主題請以"WANTED:McNycn"開始,注意英文字母大小寫,否則我將收不到喔!)

-------------------------
我寫了一個註冊碼測試器(若你輸入的S/N為正確的註冊碼,它會告訴你,否則只會顯示你的"註冊碼
生成串")。 實在沒多大用途,不過有興趣的話,可以試看看。會一擊即中也說不得!  (請使用
下面地址下載。)

(不過,可以改編成窮舉法算碼器,不過不知道要算多久才有結果(日月可計吧!)。
想要源程式可與我聯絡)


<無法從瀏覽器直接下載!!需要使用Flashget,Netants,...等工具進行下載(但不支援斷點續傳)>
網址    :  http://www.geocities.com/mcny_work    (只有兩個頁面,正在建造中,施工緩慢)
下載地址:  http://www.geocities.com/mcny_work/file/2000/tagren17_kt.zip
(使用方法:請看readme.txt)

~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
終於將本文輸完了(有點神經衰退了 8^) )。看雪學院、論壇給我的幫助,實在是太多太多了!!
在此,感謝看雪與來訪論壇的各位大俠。並以【 Tag&Rename 1.7 文章一】、【二】 獻給大家。。。

~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
    【目錄】
§========§
§  一、前言      §
§  二、正文      §
§  三、後記      §
§  四、附錄      §
§========§

----------------------------------【一、前言】--------------------------------------------

下面程式碼是取自『Tag&Rename 1.7 文章一』的『第二部分』,有見過那篇文章的朋友或許會有印象。
本文的演算法,是從程式碼 **(C3)** 處的call逐漸向上追蹤所得。起始點是call內部深處所呼叫的
CompareStringA  。(注:直接bpx CompareStringA 並不能截住!因為實際上是調
用CompareStringA + 0D 的地址。 所以bpx應該是設在CompareStringA再加 0D 的地址。)

    由於涉及演算法的核心程式碼冗長(不少於600行)。經筆者整理以後,本文用C++來演示。
(由於筆者程式設計水平實在有限,希望大家能夠幫忙最佳化。。。,不勝感激。)

                    ...
                017F:0045655F  MOV EAX,ESI
                017F:00456561  CALL 00402F68
                017F:00456566  LEA EDX,[EBP-04]
                017F:00456569  LEA EAX,[EBP-18]
                017F:0045656C  CALL 00456460      ///由輸入S/N,產生"註冊碼生成串"的
                                                    ///主要呼叫。
                017F:00456571  MOV DL,01
                017F:00456573  MOV EAX,[00410060]
                017F:0045657D  MOV [EBP-08],EAX
                017F:00456580  LEA EAX,[EBP-08]
                017F:00456583  CALL 00456404
                017F:00456588  MOV EDX,[EBP-04]
                017F:0045658B  MOV EAX,[EBP-08]
                017F:0045658E  MOV ECX,[EAX]
      **(C3)**  017F:00456590  CALL NEAR [ECX+50]  //比較"註冊碼生成串'和兩百餘個
                                                    //"正確的串"。
                                                    //若全部不匹配返回EAX=FFFFFFFF
                                                    //(內部會呼叫 Kernel32!CompareStringA)
                017F:00456593  INC EAX            
      **(A3)**  017F:00456594  JZ 0456598          /// EAX=0 時跳轉。我們改這裡!!!
        ==>    017F:00456598  MOV BL,01          /// 若上一行不跳轉,則註冊碼正確。
                017F:0045659B  CALL 00402F68
                017F:004565A0  XOR EAX,EAX
                    ....




----------------------------------【二、正文】--------------------------------------------

Tag&Rename 1.7 使用了一個不可逆的註冊碼演算法。我們輸入的註冊碼S/N,運用該演算法產生一個
“註冊碼生成串”。再將我們的“註冊碼生成串”與程式中的“內部比較串”(實質上是程式設計時,事先
由正確的註冊碼所產生的“註冊碼生成串”)進行比較,若有其中一個“內部比較串”與之相同,則注
冊成功!


(一)產生“註冊碼生成串”的步驟:< 我們輸入S/N: 1234567890 >

  1)讀入使用者輸入的S/N (並去除 S/N 的空格字首與字尾!)  // eg:  S/N = "  SN-0001 45678-8890  "
    a)S/N可以是任何的英文字母、數字、標點符號            // ==> S/N = "SN-0001 45678-8890"
    b)S/N長度<=45

  2)將S/N 放入char  sn_area[0] 中,並以 0x80 作為串結束符。在 sn_area[0x38] 處放入(串長* 8 ) 。
    ( 見 **(D1)**  )
 
  3)呼叫不可逆演算法,產生“註冊碼生成串”:
    A)進行位操作.

    B)進行'和'操作  //!!! 是這裡造成本演算法的不可逆!!!            // 見**(D4)**

    C)改變Ap4[0] (指向A[] ) , 重複A)到C)直到FOR 迴圈結束        // 見**(D3)**

    D)將B[]與改變後的A[]相加(注意相加時的指標),結果放入A[]      // 見**(D5)**

    E)人為地將A[]分成四組(每一組4個位元組〕。逐個將每個小組變成字串,將結果加入outputsn 串尾。
      最後一組字串必須加入outputsn兩次。(最後產生一個近似四十個字元的字串)

    F) outputsn 就是我們求得的"註冊碼生成串",我們就是用它與程式內所有的"內部比較串'比較。


(二)為什麼此演算法不可逆?
    A) 首先必須闡明: 整個演算法中,只有兩個主要變數:  sn_area[] ,A[] 。
        sn_area[] 的內容,在我們輸入S/N 後便確定下來了(不會再改變) 。
        只有 A[] 的內容,會在演算法中不斷的改變!!
        (其餘的常量都是TagRename 程式的內定值,不可擅自篡改! 8^) )

    B)假設現在seed=63,  這是最後一個迴圈了。我們會進行以下操作.        // 見 **(XX)**  處
      但是,為了方便闡述,我們臨時將幾個變數名做了一點調整:
             
              //這是正向過程的最後一個迴圈。
              buf1= Ap3[0] | (~ Ap2[0]);   
              buf1= buf1 ^ Ap1[0];         
              buf2=buf1+Ap4_old[0]+sna[0]+mydef[seed]+Ap3[0];          // <=關鍵
              Ap4_new[0]=buf2;                // Ap4_new[0], Ap4_old[0] 皆指向A[]
                                              // sna[0]包含我們所輸入的S/N
        可見,這一次迴圈中只有Ap4_new[0]被改變.

        若我們要作一個逆過程(從一個'內部比較串"找其正確註冊碼),則必須從已知的
        Ap4_new[0]求得原來的Ap4_old[0],好讓程式不斷的迴圈下去。逆過程如下:

              //這是逆過程的第一個迴圈。
              buf1= Ap3[0] | (~ Ap2[0]);      //位操作不變。
              buf1= buf1 ^ Ap1[0]; 
              Ap4_old[0]=Ap4_new[0]-( buf1+sna[0]+mydef[seed]+Ap3[0] );  // <=關鍵

        我們知道,這個逆過程中 Ap4_old[0] 是未知的。必須算出它的值,我們才
        能繼續算下去,一直到迴圈結束,得到正確的註冊碼。

              然而,這裡有一個矛盾!!因為 sna[0] 中存放的是我們輸入的註冊碼(在逆過程中,
        當然是指正確的註冊碼。 即:若此處的註冊碼與當初生成“內部比較串”時的註冊碼不同,
        則必定不會得到我們所期望的結果!)。可是,我們並不知道正確的註冊碼是什麼,
        所以 sna[0]在逆過程中也應該是未知的!!
       
        只有一個等式,卻有兩個未知數,當然無解啦! (本想寫個序號產生器,才花那麼多時間去分析
                                                      程式碼,這回可真是 '偷雞不著蝕把米'  8^(  )
       


(三)VC++程式碼(區域性):〔想要源程式可與我聯絡〕
//++++++++++++++++++++++++Tag&Rename 1.7 演算法(VC++)++++++++++++++++++++++++++++++++++++++++++++++++++
///Compile successful in VC++6.0
///假定 int 和 char 的長度都為 4 個位元組


//我們輸入的S/N串會放入sn_area[]中。
//假設我們輸入的S/N 為 1234567890 ,則sn_area[]如下。
//注意:必需以0x80作為串的結束符!! 而且 sn_area[0x38]處必需放入'串長x8'
//此列中 sn_area[0x38]=0x50 
//我們人為的將它分成16個區(用十六進位制數0-F表示)                          **(D1)**
char  sn_area[64]={
                  0x31,0x32,0x33,0x34, 0x35,0x36,0x37,0x38, 0x39,0x30,0x80,0x00, 0x00,0x00,0x00,0x00,
                  0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
                  0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
                  0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x50,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
                };


//都是常數,用來選擇sn_area的某一區
int sna_select[64]={ 0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xA,0xB,0xC,0xD,0xE,0xF,
                    0x1,0x6,0xB,0x0,0x5,0xA,0xF,0x4,0x9,0xE,0x3,0x8,0xD,0x2,0x7,0xC,
                    0x5,0x8,0xB,0xE,0x1,0x4,0x7,0xA,0xD,0x0,0x3,0x6,0x9,0xC,0xF,0x2,
                    0x0,0x7,0xE,0x5,0xC,0x3,0xA,0x1,0x8,0xF,0x6,0xD,0x4,0xB,0x2,0x9  };


//都是常數,用來進行四則運算. 
int mydef[64]= {0xD76AA478,0xE8C7B756,0x242070DB,0xC1BDCEEE,0xF57C0FAF,0x4787C62A,0xA8304613,0xFD469501, 
                0x698098D8,0x8B44F7AF,0xFFFF5BB1,0x895CD7BE,0x6B901122,0xFD987193,0xA679438E,0x49B40821,
                0xF61E2562,0xC040B340,0x265E5A51,0xE9B6C7AA,0xD62F105D,0x02441453,0xD8A1E681,0xE7D3FBC8,
                0x21E1CDE6,0xC33707D6,0xF4D50D87,0x455A14ED,0xA9E3E905,0xFCEFA3F8,0x676F02D9,0x8D2A4C8A,
                0xFFFA3942,0x8771F681,0x6D9D6122,0xFDE5380C,0xA4BEEA44,0x4BDECFA9,0xF6BB4B60,0xBEBFBC70,
                0x289B7EC6,0xEAA127FA,0xD4EF3085,0x04881D05,0xD9D4D039,0xE6DB99E5,0x1FA27CF8,0xC4AC5665,
                0xF4292244,0x432AFF97,0xAB9423A7,0xFC93A039,0x655B59C3,0x8F0CCC92,0xFFEFF47D,0x85845DD1,
                0x6FA87E4F,0xFE2CE6E0,0xA3014314,0x4E0811A1,0xF7537E82,0xBD3AF235,0x2AD7D2BB,0xEB86D391 };    



//我們人為的將A[]、B[]各分成四組.(每4個位元組一組)                        **(D2)**
//A[]和B[]的初始值都一樣, 但是A[]會在迴圈 **(D1)** 中不斷改變.
char A[16]={0x01,0x23,0x45,0x67, 0x89,0xAB,0xCD,0xEF, 0xFE,0xDC,0xBA,0x98, 0x76,0x54,0x32,0x10};
char B[16]={0x01,0x23,0x45,0x67, 0x89,0xAB,0xCD,0xEF, 0xFE,0xDC,0xBA,0x98, 0x76,0x54,0x32,0x10};



CString outputsn; //所求得的'註冊碼生成串'。

int p1,p2,p3,p4;  //使用它們來指定要使用A[]的哪個部分。

     
__inline bool CTagRen17snDlg::checkfor1targ()
{
        int i,buf1,seed;            
        int *Ap1,*Ap2,*Ap3,*Ap4,*sna;  //

        p1=0x08;  //Initialize the Started A[n] order
        p2=0x0c;
        p3=0x04;
        p4=0;

        for (seed=0;seed<64;seed++)    //seed=0 --> 63                  **(D3)**
        {
                Ap1=(int*)&A[p1];      //注意:這裡強制將char指標變成int指標,所以看到的資料會顛倒。
                                        //舉例,A[p1]的內容= 01 23 45 67
                                        //      Ap1[0]    = 67 45 23 01
                Ap2=(int*)&A[p2];
                Ap3=(int*)&A[p3];
                Ap4=(int*)&A[p4];
                sna=(int*)&sn_area[sna_select[seed]*4];  //注意:這裡也強制將char指標變成int指標。

                //Here start           
                if(seed<16)            //這裡就不用多說了吧,都是一些位操作.
                {
                        buf1= Ap3[0] &  Ap1[0];
                        buf1= ((~Ap3[0])& Ap2[0]) | buf1;
                }
                else if(seed<32)
                {
                        buf1= Ap2[0] &  Ap3[0];
                        buf1= ((~Ap2[0])& Ap1[0]) | buf1;
                }
                else if (seed<48)
                {
                        buf1= Ap3[0] ^ Ap1[0];
                        buf1= buf1 ^ Ap2[0];
                }
        else
                {
                        buf1= Ap3[0] | (~ Ap2[0]);          //                **(XX)**
                        buf1= buf1 ^ Ap1[0];
                }

                buf1=buf1+Ap4[0]+sna[0]+mydef[seed]+Ap3[0];  //<=該死的東西!!  **(D4)**

                Ap4[0]=buf1;          //整個for迴圈只改變A[]中的某一組

        ///改變p1,p2,p3,p4
                if (p1==0) p1=0x0c;
                else p1=p1-4;
                if (p2==0) p2=0x0c;
                else p2=p2-4;
                if (p3==0) p3=0x0c;
                else p3=p3-4;
                if (p4==0) p4=0x0c;
                else p4=p4-4;
        }//for_end

///
        int *pa,*pb;
    for (i=0;i<4;i++)  //Final Add
        {            
                pa=(int*)&A[i*4];        //注意:這裡也強制將char指標變成int指標。**(D5)**
                pb=(int*)&B[i*4];
                pa[0]=pa[0]+pb[0];
        }

///將pa[]的內容(共十六位元組,分成四位元組一組)轉換成字串(四十個字元/或近四十個字元)。
        char aaaa[50];
        outputsn.Empty();
        for (i=0;i<4;i++)
        {
              pa=(int*)&A[i*4];
              _itoa(pa[0],aaaa,16);      //注意:若pa[0]中的內容以0為前導,前導0不轉換!
                                          //舉例:pa[0]=00A5D41E ,則aaaa='A5D41E'
              outputsn=outputsn+aaaa;
        }
        outputsn=outputsn+aaaa;          //A[]的最後一組要多加一次!!(這樣便構成
                                          //          四十個字元/或近四十個字元)
        outputsn.MakeUpper();
        m_disbox1.SetWindowText(outputsn);

//此時,outputsn=註冊碼生成串
//舉例:  我們輸入S/N="1234567890" ,則 outputsn='EA4DC7F7799A0A3742C7EB089D297F529D297F52'
//    若S/N="AF00-12345-0000-1234" , 則 outputsn='5D94F02772BF9C521E723D981E74F9391E74F939'
//
//下面省略。。。--比較註冊碼生成串與所有的內部比較串(兩百餘個),若有其中一個內部比較串與
//                  註冊碼生成串相同,則所輸入的 S/N 正確。註冊成功。

...
...

}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

----------------------------------【正文完】----------------------------------------------



----------------------------------【三、後記】--------------------------------------------

其實,並非只有 Tag&Rename 1.5/1.6/1.7 使用這個演算法。SoftPointer 這個公司的另一個軟體
Advanced Ra-Renamer v.1.1 ( 軟體功能:修改Realplayer音樂檔案(*.ra)的TAG )也使用同一個演算法,
是一摸一樣的演算法!!所不同的唯有“內部比較串”!!


另一個破解方法:我們只需要將眾多內部比較串中的其中一個,改成我們自己的比較串,那麼用
                自己的SN 便能註冊成功了!(然而,我懷疑會有幾個人這樣做。這畢竟有
                點畫蛇添足。既然要對程式本身作修改,改變跳轉不是更加簡單省事??!)


----------------------------------【四、附錄】--------------------------------------------
//(僅供參考)
//幾個Tag&Rename1.5/1.6/1.7的內部比較串。1.7版本有兩百餘個比較串。(這裡不按原順序排列)
target_TAG[0] ="6BD7B460BF475A6B5415F1F97F19F2CD7F19F2CD"; //<=在程式的第一次比較,可以看到本串。
target_TAG[1] ="30FE82633BADBA6CFC08C0033DABE1E83DABE1E8";
target_TAG[2] ="E1E93D3A7BEF3F49B2A4AFC19A50F9B59A50F9B5";
target_TAG[3] ="603D7D9D717D8C9023A94720FC9345E0FC9345E0";
target_TAG[4] ="CD8816613549E28B2B12DB1D7B70DCF47B70DCF4";
target_TAG[5] ="458FC6FF176FAB84A09166ECA5D41EA5D41E";      //<=串長不一定是四十個字元!!
target_TAG[6] ="9298C14667EE91844C2E8AD8AA14FEF6AA14FEF6";
target_TAG[7] ="CEC148A772B83DE7C0E5F77EBA087B4EBA087B4";
target_TAG[8] ="A59469F69BBCF2E4276506C8E227368AE227368A";
target_TAG[9] ="EFF5E1F96337AAA215CA0F98E64DCE1DE64DCE1D";
target_TAG[10]="FA221E4FEC7662907D3BDE10A20B0074A20B0074";
target_TAG[11]="BFB419C3679775538D5B24282BD3FDA52BD3FDA5";
target_TAG[12]="A2F2A07D860EA1B2542236D87D317FE57D317FE5";
target_TAG[13]="9F5E527C3BA802D4AB41E6BD89F36EC189F36EC1";
target_TAG[14]="510718DA8965364AD3AFA44144D297044D2970";
target_TAG[15]="E296E8B3B6EC79FBBF686E98E8849A28E8849A2";

//幾個RA-Renamer1.1 的內部比較串,約有 180 個比較串。
target_RA[0]  ="728F0122AB2B16CBAF6BD4C7F3ACE41EF3ACE41E";
target_RA[1]  ="5711D97055A3AA85DBB00844AC126969AC126969";
target_RA[2]  ="D70BB2A0156DA6ED975853569B1741969B174196";

==========================================================================================
全文結束(有不對的地方,還望大家多多指正與包涵!)

相關文章