要想在破解的道路上走得远点,单靠爆破是不行的。爆破熟练到一定程度后,应该学会算法分析,然后学着做出注册机。只有这样,才能在各式各样的软件算法面前游刃有余。但是,这样的进阶顺序却有一个突变,即从爆破到算法分析这样一个质的飞跃。要应付好这个飞跃,对于算法新手来说,是比较困难的。也就是说,新手朋友往往面对一个软件的海量代码不知所措,不能有效地选择好关键代码进行分析,从而导致算法分析或者注册机编写的失败。
下面,我们就一起研究一款软件,我将力求用通俗易懂的语言来讲解算法分析和注册机编写,相信大家,特别是对于想要从爆破转到算法分析上的新手朋友来说,将会有不错的收获。
认识软件
“知己知彼,百战不殆”。我们在进行软件破解的时候,自然要对我们的破解对象有个宏观的认识。
“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
总结
新手朋友如果要顺利的进行算法分析,除了耐心外,最好掌握一门编程语言,并且善于应用,只有这样,算法分析和注册机的编写才能游刃有余。好了,就到这里,祝大家学习愉快。
阅读(1966) | 评论(0) | 转发(1) |