Chinaunix首页 | 论坛 | 博客
  • 博客访问: 358176
  • 博文数量: 76
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 2363
  • 用 户 组: 普通用户
  • 注册时间: 2013-07-21 22:30
文章分类
文章存档

2014年(38)

2013年(38)

分类: 网络与安全

2013-08-20 09:43:20

要想在破解的道路上走得远点,单靠爆破是不行的。爆破熟练到一定程度后,应该学会算法分析,然后学着做出注册机。只有这样,才能在各式各样的软件算法面前游刃有余。但是,这样的进阶顺序却有一个突变,即从爆破到算法分析这样一个质的飞跃。要应付好这个飞跃,对于算法新手来说,是比较困难的。也就是说,新手朋友往往面对一个软件的海量代码不知所措,不能有效地选择好关键代码进行分析,从而导致算法分析或者注册机编写的失败。
下面,我们就一起研究一款软件,我将力求用通俗易懂的语言来讲解算法分析和注册机编写,相信大家,特别是对于想要从爆破转到算法分析上的新手朋友来说,将会有不错的收获。
 
认识软件
“知己知彼,百战不殆”。我们在进行软件破解的时候,自然要对我们的破解对象有个宏观的认识。
“4U WMA MP3 Converter”是一款能将WMA格式转换为MP3格式的软件,我们研究的版本“6.0.2”。软件有没有加壳呢?用PEiD侦测可以知道,并没加壳,而且是用Borland Delphi编写的。
现在我们总结一下。要破解软件,应该先收集软件信息。那么,我们需要什么信息呢?我个人认为,应该包括这些方面:软件用法,有没加壳,用何种语言编译等。只有这些信息收集完后,我们才能方便地进行下一步的工作。
 
算法分析
上面我们已经收集到足够的信息了,现在进入下一步工作。用OD载入,下断点“bp MessageBoxA”可以定位到以下位置。
 
view plainprint?

    0049494D  |.  E8 3E02F7FF  call  00404B90  
    00494952  |.  50  push  eax  
    00494953  |.  68 CC494900  push  004949CC  
    ; |Text = "Invalid Registration Code! ",CR,LF,"Please enter an available Registration Code."  
    00494958  |.  A1 ECEF4B00  mov  eax, dword ptr [4BEFEC]  
    0049495D  |.  8B00  mov  eax, dword ptr [eax]    

再往上看,我们可以知道,上面有个关键跳转。由此我们可以大胆断定,这里便是关键代码处。在上面靠前的地方下个断点,我选择在0049488C 处下断点。F9运行,会跳出一个注册窗口要求注册。不用管它,我们随便输入注册名“hokkien”,注册码为“12345abcde”,点注册按扭后被断,然后F8单步跟踪,可以发现以下的关键位置。
 
004948E4  call  00494A18
 
我们F7步入00494A18,来到下面的关键处。
 
view plainprint?

    00494A3F  |.  55  push  ebp  
    00494A40  |.  68 734B4900  push  00494B73  
    00494A45  |.  64:FF30  push  dword ptr fs:[eax]  
    00494A48  |.  64:8920  mov  dword ptr fs:[eax], esp  
    00494A4B  |.  8D45 FC  lea  eax, dword ptr [ebp-4]  
    00494A4E  |.  BA 8C4B4900  mov  edx, 00494B8C  
    ;ASCII "Lb)a6Fcw9K9"  
    00494A53  |.  E8 48FFF6FF  call  004049A0    
    ;用固定字符串和注册名连接,返回固定字符串的长度  
    00494A58  |.  8B45 FC  mov  eax, dword ptr [ebp-4]  
    00494A5B  |.  E8 38FFF6FF  call  00404998    
    ;连接后的字符串的长度  
    00494A60  |.  8BF0  mov  esi, eax  
    00494A62  |.  D1FE  sar  esi, 1    
    ;ESI是连接注册名和固定字符串形成的新字符串的长度,然后进行运算,即长度值除2,结果记为N  
    00494A64  |.  79 03  jns  short 00494A69  
    00494A66  |.  83D6 00  adc  esi, 0  
    00494A69  |>  8D45 F0  lea  eax, dword ptr [ebp-10]  
    00494A6C  |.  50  push  eax  
    00494A6D  |.  8BCE  mov  ecx, esi  
    00494A6F  |.  BA 01000000  mov  edx, 1  
    00494A74  |.  8B45 FC  mov  eax, dword ptr [ebp-4]  
    00494A77  |.  E8 7401F7FF  call  00404BF0  
    ;返回指定的前N个字符,所得字符串记为str1   
    00494A7C  |.  8B45 F0  mov  eax, dword ptr [ebp-10]  
    00494A7F  |.  50  push  eax  
    00494A80  |.  8D45 EC  lea  eax, dword ptr [ebp-14]  
    00494A83  |.  50  push  eax  
    00494A84  |.  8B45 FC  mov  eax, dword ptr [ebp-4]  
    00494A87  |.  E8 0CFFF6FF  call  00404998  
    00494A8C  |.  8BC8  mov  ecx, eax    
    00494A8E  |.  8D56 01  lea  edx, dword ptr [esi+1]  
    ;edx=N+1  
    00494A91  |.  8B45 FC  mov  eax, dword ptr [ebp-4]  
    00494A94  |.  E8 5701F7FF  call  00404BF0  
    ;返回从N+1个位置开始的后面所有字符串,所得字符串记为str2  
    00494A99  |.  8B55 EC  mov  edx, dword ptr [ebp-14]  
    00494A9C  |.  8D45 FC  lea  eax, dword ptr [ebp-4]  
    00494A9F  |.  59  pop  ecx  
    00494AA0  |.  E8 3FFFF6FF  call  004049E4  
    ;合并字符串,即 str3=str2+str1,注意顺序!  
    00494AA5  |.  8D45 F8  lea  eax, dword ptr [ebp-8]  
    00494AA8  |.  50  push  eax  
    00494AA9  |.  B9 0A000000  mov  ecx, 0A  
    00494AAE  |.  BA 01000000  mov  edx, 1  
    00494AB3  |.  8B45 FC  mov  eax, dword ptr [ebp-4]  
    00494AB6  |.  E8 3501F7FF  call  00404BF0  
    ;取str3的前10个字符,结果记为str4   
    00494ABB  |.  8D45 F4  lea  eax, dword ptr [ebp-C]  
    00494ABE  |.  50  push  eax  
    00494ABF  |.  8B45 FC  mov  eax, dword ptr [ebp-4]  
    00494AC2  |.  E8 D1FEF6FF  call  00404998  
    00494AC7  |.  8BC8  mov  ecx, eax  
    00494AC9  |.  BA 06000000  mov  edx, 6  
    00494ACE  |.  8B45 FC  mov  eax, dword ptr [ebp-4]  
    00494AD1  |.  E8 1A01F7FF  call  00404BF0  
    ;从第6位置取str3后的所有字符串,结果记为str5  
    00494AD6  |.  837D F4 00  cmp  dword ptr [ebp-C], 0  
    00494ADA  |.  75 10  jnz  short 00494AEC  
    00494ADC  |.  8D45 F4  lea  eax, dword ptr [ebp-C]  
    00494ADF  |.  BA 8C4B4900  mov  edx, 00494B8C  
    ;ASCII "Lb)a6Fcw9K9"  
    00494AE4  |.  8B4D F8  mov  ecx, dword ptr [ebp-8]  
    00494AE7  |.  E8 F8FEF6FF  call  004049E4  
    00494AEC  |>  53  push  ebx  
    00494AED  |.  8B4D F4  mov  ecx, dword ptr [ebp-C]  
    00494AF0  |.  8B55 F8  mov  edx, dword ptr [ebp-8]  
    00494AF3  |.  8BC7  mov  eax, edi  
    00494AF5  |.  E8 92F0FFFF  call  00493B8C  
    ;关键,等下将用到这个函数  

上面这些代码是对注册名进行一定处理,我们以注册名“hokkien”进行具体的说明。首先,“hokkien”和“Lb)a6Fcw9K9” 合并为“hokkien Lb)a6Fcw9K9”,然后取N个字符(本注册名是N=9),即“hokkienLb”,和剩下的字符倒过来又组成新的字符串,即 “)a6Fcw9K9 hokkienLb”,然后再取前10位字符,即“)a6Fcw9K9h”,接着,从6个字符开始取后面的所有字符,有“w9K9hokkienLb”。其中,字符串“)a6Fcw9K9h”和“w9K9hokkienLb”是关键,后面将用于注册码的生成。
那么,上面对注册名进行这样复杂的处理有什么用呢?我们继续下去会发现“00494AF5 |. E8 92F0FFFF call 00493B8C”是关键,F7步入00493B8C,来到下面的代码处。
 
view plainprint?

    00493B8C  /$  55  push  ebp  
    00493B8D  |.  8BEC  mov  ebp, esp  
    00493B8F  |.  83C4 E0  add  esp, -20  
    00493B92  |.  53  push  ebx  
    00493B93  |.  56  push  esi  
    00493B94  |.  57  push  edi  
    00493B95  |.  33DB  xor  ebx, ebx  
    00493B97  |.  895D E0  mov  dword ptr [ebp-20], ebx  
    00493B9A  |.  895D F0  mov  dword ptr [ebp-10], ebx  
    00493B9D  |.  894D F8  mov  dword ptr [ebp-8], ecx  
    00493BA0  |.  8955 FC  mov  dword ptr [ebp-4], edx  
    00493BA3  |.  8B45 FC  mov  eax, dword ptr [ebp-4]  
    00493BA6  |.  E8 D50FF7FF  call  00404B80  
    00493BAB  |.  8B45 F8  mov  eax, dword ptr [ebp-8]  
    00493BAE  |.  E8 CD0FF7FF  call  00404B80  
    00493BB3  |.  33C0  xor  eax, eax  
    00493BB5  |.  55  push  ebp  
    00493BB6  |.  68 A83C4900  push  00493CA8  
    00493BBB  |.  64:FF30  push  dword ptr fs:[eax]  
    00493BBE  |.  64:8920  mov  dword ptr fs:[eax], esp  
    00493BC1  |.  8B45 F8  mov  eax, dword ptr [ebp-8]  
    00493BC4  |.  E8 CF0DF7FF  call  00404998 ;长度  
    00493BC9  |.  8945 F4  mov  dword ptr [ebp-C], eax    
    00493BCC  |.  837D F4 00  cmp  dword ptr [ebp-C], 0  
    00493BD0  |.  75 0D  jnz  short 00493BDF  
    00493BD2  |.  8D45 F8  lea  eax, dword ptr [ebp-8]  
    00493BD5  |.  BA C03C4900  mov  edx, 00493CC0  
    ;ASCII "Think Space"  
    00493BDA  |.  E8 990BF7FF  call  00404778  
    00493BDF  |>  33F6  xor  esi, esi  
    00493BE1  |.  BB 00010000  mov  ebx, 100  
    00493BE6  |.  8D45 F0  lea  eax, dword ptr [ebp-10]  
    00493BE9  |.  50  push  eax   ; /Arg1  
    00493BEA  |.  C745 E4 00010>mov  dword ptr [ebp-1C], 100;|  
    00493BF1  |.  C645 E8 00  mov  byte ptr [ebp-18], 0;|  
    00493BF5  |.  8D55 E4  lea  edx, dword ptr [ebp-1C];|  
    00493BF8  |.  33C9  xor  ecx, ecx;|  
    00493BFA  |.  B8 D43C4900  mov  eax, 00493CD4  
    ;|ASCII "%1.2x"  
    00493BFF  |.  E8 0461F7FF  call  00409D08  
    ; \WMAMP3Co.00409D08  

看了这些代码后,似乎没有什么重要信息。但仔细跟踪后发现,数字0X100是很关键的。如何关键我们等下进行说明,现在我们F7继续下去。
 
view plainprint?

    00493C19  |> /8B45 FC  /mov  eax, dword ptr [ebp-4]  
    ;“)a6Fcw9K9h”  
    00493C1C  |. |8B55 EC  |mov  edx, dword ptr [ebp-14]  
    ;计数器  
    00493C1F  |. |0FB64410 FF  |movzx  eax, byte ptr [eax+edx-1]  
    ;取“)a6Fcw9K9h”相应位字符的ASCII  
    00493C24  |. |03C3  |add  eax, ebx  
    ;与上次运算结果相加  
    00493C26  |. |B9 FF000000  |mov  ecx, 0FF  
    00493C2B  |. |99  |cdq  
    00493C2C  |. |F7F9  |idiv  ecx  
    00493C2E  |. |8BDA  |mov  ebx, edx ;%模  
    00493C30  |. |3B75 F4  |cmp  esi, dword ptr [ebp-C]  
    00493C33  |. |7D 03  |jge  short 00493C38  
    00493C35  |. |46  |inc  esi  
    ;另一个计数器  
    00493C36  |. |EB 05  |jmp  short 00493C3D  
    00493C38  |> |BE 01000000  |mov  esi, 1  
    00493C3D  |> |8B45 F8  |mov  eax, dword ptr [ebp-8]  
    ;字符串“w9K9hokkienLb”  
    00493C40  |. |0FB64430 FF  |movzx  eax, byte ptr [eax+esi-1]  
    00493C45  |. |33D8  |xor  ebx, eax  
    00493C47  |. |8D45 E0  |lea  eax, dword ptr [ebp-20]  
    00493C4A  |. |50  |push  eax ; /Arg1  
    00493C4B  |. |895D E4  |mov  dword ptr [ebp-1C], ebx;|  
    00493C4E  |. |C645 E8 00  |mov  byte ptr [ebp-18], 0;|  
    00493C52  |. |8D55 E4  |lea  edx, dword ptr [ebp-1C];|  
    00493C55  |. |33C9  |xor  ecx, ecx;|  
    00493C57  |. |B8 D43C4900  |mov  eax, 00493CD4  
    ; |ASCII "%1.2x"  
    00493C5C  |. |E8 A760F7FF  |call  00409D08  
    00493C61  |. |8B55 E0  |mov  edx, dword ptr [ebp-20]  
    00493C64  |. |8D45 F0  |lea  eax, dword ptr [ebp-10]  
    00493C67  |. |E8 340DF7FF  |call  004049A0  
    00493C6C  |. |FF45 EC  |inc  dword ptr [ebp-14]  
    00493C6F  |. |4F  |dec  edi ;减1  
    00493C70  |.^\75 A7  \jnz  short 00493C19  

上面的代码是注册码生成的关键!我们现在进行详细分析。首先取出“)a6Fcw9K9h”的相应位的ASCII,记为A1。然后(A1+上次运算的结果)%0XFF(特别的,第一次运算时,第一次结果设为0X100),所得结果为A2;接着,取出“w9K9hokkienLb”的相应位的 ASCII,记为B1;最后,B1异或A2,所得的结果为本次运算的新结果。这样说估计比较难理解,那我们现在就用前几位运算来说明一下。
第一次运算:“)a6Fcw9K9h”的“)”的ASCII为0X29,0X29+0X100==0X129,然后,0X129%0XFF=0X2A。“w9K9hokkienLb”的w的ASCII为0X77,然后,0X77 XOR 0X2A=0X5D,则0X5D就是本次的运算结果。
第二次运算:“)a6Fcw9K9h”的“a”的ASCII为0X61,0X61+0X5D==0XBE,然后,0XBE%0XFF=0XBE。“w9K9hokkienLb”的9的ASCII为0X39,然后,0XBE XOR 0X39=0X87,则0X87就是本次的运算结果。
其他位的运算类似,我就不再细说了。最后我们得到由“)a6Fcw9K9h”和“w9K9hokkienLb”运算出来的16进制数字 “5D87F64FE948F85BA6”,然后跟“100”合并为“1005D87F64FE948F85BA6”。那么这个字符串有什么作用呢?我们继续。
 
view plainprint?

    00494AFD  |.  50  push  eax  
    00494AFE  |.  8B03  mov  eax, dword ptr [ebx]  
    00494B00  |.  B9 05000000  mov  ecx, 5  
    00494B05  |.  BA 01000000  mov  edx, 1  
    00494B0A  |.  E8 E100F7FF  call  00404BF0  
    00494B0F  |.  FF75 E8  push  dword ptr [ebp-18]  
    00494B12  |.  68 A04B4900  push  00494BA0  
    00494B17  |.  8D45 E4  lea  eax, dword ptr [ebp-1C]  
    00494B1A  |.  50  push  eax  
    00494B1B  |.  8B03  mov  eax, dword ptr [ebx]  
    00494B1D  |.  B9 05000000  mov  ecx, 5  
    00494B22  |.  BA 06000000  mov  edx, 6  
    00494B27  |.  E8 C400F7FF  call  00404BF0  
    00494B2C  |.  FF75 E4  push  dword ptr [ebp-1C]  
    00494B2F  |.  68 A04B4900  push  00494BA0  
    00494B34  |.  8D45 E0  lea  eax, dword ptr [ebp-20]  
    00494B37  |.  50  push  eax  
    00494B38  |.  8B03  mov  eax, dword ptr [ebx]  
    00494B3A  |.  B9 05000000  mov  ecx, 5  
    00494B3F  |.  BA 0B000000  mov  edx, 0B  
    00494B44  |.  E8 A700F7FF  call  00404BF0  
    00494B49  |.  FF75 E0  push  dword ptr [ebp-20]  
    00494B4C  |.  8BC3  mov  eax, ebx  
    00494B4E  |.  BA 05000000  mov  edx, 5  
    00494B53  |.  E8 00FFF6FF  call  00404A58  
    00494B58  |.  33C0  xor  eax, eax  
    00494B5A  |.  5A  pop  edx  
    00494B5B  |.  59  pop  ecx  
    00494B5C  |.  59  pop  ecx  
    00494B5D  |.  64:8910  mov  dword ptr fs:[eax], edx  
    00494B60  |.  68 7A4B4900  push  00494B7A  
    00494B65  |>  8D45 E0  lea  eax, dword ptr [ebp-20]  
    00494B68  |.  BA 08000000  mov  edx, 8  
    00494B6D  |.  E8 92FBF6FF  call  00404704  
    00494B72  \.  C3  retn  

看到上面这些代码,我们就明白了,这里还要进行进一步处理,即“1005D87F64FE948F85BA6”五个一组进行分组,分隔符为 “-”,且注册码的形式为“XXXXX-XXXXX-XXXXX”,所以,我们可以得出注册名“hokkien”的注册码为“1005D-87F64- FE948”。赶紧注册下,呵呵,当然成功了。
 
注册机代码的编写
下面我们用C语言编写一个注册机。首先,应该进行运算准备,即获取类似“)a6Fcw9K9h”和“w9K9hokkienLb”的字符串。
view plainprint?

    char regcode[100]={0};  
    char str1[50];  
    char  str2[50];  
    char str3[50];  
    char str4[50];  
    char str5[50];  
    char len;  
    char retlen;  
    char newstr[50];  
    charregname[50];  
    char* strconst="Lb)a6Fcw9K9";  
    char *codecon="100";  
    printf("请输入你需要注册的用户名:");  
    scanf("%s",regname);  
    strcpy(newstr,regname);  
    strcat(newstr,strconst);  
    len=strlen(newstr);  
    retlen=len>>1;  
    strncpy(str1,newstr,retlen);//取回前retlen个字符  
    str1[retlen]='\0';  
    get_n_str(retlen,str2,newstr,len);  
    strcpy(str3,str2);  
    strcat(str3,str1);  
    strncpy(str4,str3,0x0a);  
    str4[0x0a]='\0';  
    get_n_str(6-1,str5,str3,len);  

其中的函数get_n_str的原型是“void get_n_str(char n,char str[],char* constr,char strlen);”,功能是从字符串constr的第n位置开始把后面的字符全部复制给str。其实现代码如下:
 
view plainprint?

    void get_n_str(char n,char str[],char* constr,char strlen)// strlen是constr,的长度  
    {  
    for(int i=n;i<=strlen;i++)  
    str[i-n]=constr[i];  
    }  
      
    经过上面的代码,我们可以得到str4和str5,这是注册码生成的关键。下面的代码是对str4和str5进行运算从而生成注册码。  
      
    str4len=strlen(str4);  
    str5len=strlen(str5);  
    if(str4len<=str5len)  
    _strlen=str4len;  
    else   
    _strlen=str5len;  
    strcpy(regcode,codecon);  
    charstring[12];  
    unsigned char regtemp=((str4[0]+0x100)%0xFF)^(str5[0]);  
    char ctemp=0;  
    ultoa(regtemp,string,16);  
    strcat(regcode,string);  
    for(int i=1;i<_strlen;i++)  
    {  
    regtemp= ((regtemp+str4[i])%0xff)^(str5[i]);  
    ultoa(regtemp,string,16);  
    strcat(regcode,string);  
    }  
    printf("\n^_^恭喜你,你的注册码为:");  
    for(int n=0;n<15;n++)  
    {  
    printf("%C",(toupper(regcode[n])));  
    if(((n+1)%5==0)&(n+1)<15)  
    printf("-");  
    }  

经过上面这段代码后,我们就可以得到注册码了。编译连接后运行,输入hokkien,得到的注册码正好是“1005D-87F64-FE948”,如图1所示。
 
图1
 
总结
新手朋友如果要顺利的进行算法分析,除了耐心外,最好掌握一门编程语言,并且善于应用,只有这样,算法分析和注册机的编写才能游刃有余。好了,就到这里,祝大家学习愉快。
阅读(1921) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~