• 标 题:【标题:Tag&Rename 1.7 文章二 :此软件的注册码算法。( 进来看看 8^) )】
  • 作 者:mcny@work
  • 时 间:2000-11-14 14:31:20
  • 链 接:http://bbs.pediy.com

【标题: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";

==========================================================================================
全文结束(有不对的地方,还望大家多多指正与包涵!)