上一次发了一篇不太全的分析,这次来收尾,总结了一下网上关于这个CM的破文和自己跟出来的东西。
在分析之前,先看看CrackMe的模样:
Same old same old,Name+Serial
第一步:反汇编算法分析
Button1的汇编代码:
004501DD 55 push ebp
004501DE |. 68 4F034500 push 0045034F
004501E3 |. 64:FF30 push dword ptr fs:[eax]
004501E6 |. 64:8920 mov dword ptr fs:[eax], esp
004501E9 |. 8D55 FC lea edx, dword ptr [ebp-4]
004501EC |. 8B83 FC020000 mov eax, dword ptr [ebx+2FC]
004501F2 |. E8 25F2FDFF call 0042F41C
004501F7 |. 8D55 F8 lea edx, dword ptr [ebp-8]
004501FA |. 8B83 00030000 mov eax, dword ptr [ebx+300]
00450200 |. E8 17F2FDFF call 0042F41C
00450205 |. 8D45 F4 lea eax, dword ptr [ebp-C]
00450208 |. BA 68034500 mov edx, 00450368 ; i am bin laden
0045020D |. E8 963CFBFF call 00403EA8
00450212 |. 8D45 F0 lea eax, dword ptr [ebp-10]
00450215 |. BA 80034500 mov edx, 00450380 ; i am yi zhi lao hu
0045021A |. E8 893CFBFF call 00403EA8
0045021F |. 8B45 FC mov eax, dword ptr [ebp-4]
00450222 |. E8 A93EFBFF call 004040D0
00450227 |. 83F8 0A cmp eax, 0A
0045022A |. 0F8C FC000000 jl 0045032C
00450230 |. 8B45 FC mov eax, dword ptr [ebp-4]
00450233 |. E8 983EFBFF call 004040D0
00450238 |. 83F8 10 cmp eax, 10
0045023B |. 0F8F EB000000 jg 0045032C ; 用户名长度在10到16之间
00450241 |. 8B45 F8 mov eax, dword ptr [ebp-8]
00450244 |. E8 873EFBFF call 004040D0
00450249 |. 83F8 11 cmp eax, 11
0045024C |. 0F8C DA000000 jl 0045032C
00450252 |. 8B45 F8 mov eax, dword ptr [ebp-8]
00450255 |. E8 763EFBFF call 004040D0
0045025A |. 83F8 16 cmp eax, 16
0045025D |. 0F8F C9000000 jg 0045032C ; 密码长度在17到22之间
00450263 |. 8D45 FC lea eax, dword ptr [ebp-4]
00450266 |. 8B55 F4 mov edx, dword ptr [ebp-C]
00450269 |. E8 6A3EFBFF call 004040D8 ; 合并 UsrBin=用户名+szBin
0045026E |. BB 64000000 mov ebx, 64
00450273 |. 8D45 88 lea eax, dword ptr [ebp-78]
00450276 |> C600 2E /mov byte ptr [eax], 2E
00450279 |. 40 |inc eax
0045027A |. 4B |dec ebx
0045027B |.^ 75 F9 \jnz short 00450276 ; 初始化12F5B4到12F617为0x2E
0045027D |. 8B45 FC mov eax, dword ptr [ebp-4]
00450280 |. E8 4B3EFBFF call 004040D0
00450285 |. 8BF8 mov edi, eax
00450287 |. 85FF test edi, edi
00450289 |. 7E 47 jle short 004502D2
0045028B |. BB 01000000 mov ebx, 1 ; i=1
00450290 |> 8B45 F0 /mov eax, dword ptr [ebp-10]
00450293 |. E8 383EFBFF |call 004040D0
00450298 |. 8BF0 |mov esi, eax
0045029A |. 85F6 |test esi, esi
0045029C |. 7E 30 |jle short 004502CE
0045029E |. B9 01000000 |mov ecx, 1 ; j=1
004502A3 |> 8B45 FC |/mov eax, dword ptr [ebp-4]
004502A6 |. 0FB64418 FF ||movzx eax, byte ptr [eax+ebx-1]
004502AB |. 8B55 F8 ||mov edx, dword ptr [ebp-8]
004502AE |. 0FB6540A FF ||movzx edx, byte ptr [edx+ecx-1]
004502B3 |. F7EA ||imul edx
004502B5 |. 51 ||push ecx
004502B6 |. B9 1A000000 ||mov ecx, 1A
004502BB |. 33D2 ||xor edx, edx
004502BD |. F7F1 ||div ecx
004502BF |. 59 ||pop ecx
004502C0 |. 83C2 41 ||add edx, 41
004502C3 |. 8D0419 ||lea eax, dword ptr [ecx+ebx]
004502C6 |. 885405 87 ||mov byte ptr [ebp+eax-79], dl ; String[i+j]=UsrBin[i]*Code[j]%26+65
004502CA |. 41 ||inc ecx
004502CB |. 4E ||dec esi
004502CC |.^ 75 D5 |\jnz short 004502A3 ; j<=18
004502CE |> 43 |inc ebx
004502CF |. 4F |dec edi
004502D0 |.^ 75 BE \jnz short 00450290 ; i<=24
004502D2 |> 8D45 EC lea eax, dword ptr [ebp-14]
004502D5 |. E8 363BFBFF call 00403E10
004502DA |. 8B45 F8 mov eax, dword ptr [ebp-8]
004502DD |. E8 EE3DFBFF call 004040D0
004502E2 |. 8BF8 mov edi, eax
004502E4 |. 85FF test edi, edi
004502E6 |. 7E 1F jle short 00450307
004502E8 |. 8D5D 8E lea ebx, dword ptr [ebp-72]
004502EB |> 8D45 84 /lea eax, dword ptr [ebp-7C]
004502EE |. 8A13 |mov dl, byte ptr [ebx]
004502F0 |. E8 033DFBFF |call 00403FF8
004502F5 |. 8B55 84 |mov edx, dword ptr [ebp-7C]
004502F8 |. 8D45 EC |lea eax, dword ptr [ebp-14]
004502FB |. 8B4D EC |mov ecx, dword ptr [ebp-14]
004502FE |. E8 193EFBFF |call 0040411C
00450303 |. 43 |inc ebx
00450304 |. 4F |dec edi
00450305 |.^ 75 E4 \jnz short 004502EB ; 将String+6"之后的len(code)个位的字符串倒置作为密码
00450307 |> 8B45 EC mov eax, dword ptr [ebp-14]
0045030A |. 8B55 F8 mov edx, dword ptr [ebp-8]
0045030D |. E8 0A3FFBFF call 0040421C
00450312 |. 75 18 jnz short 0045032C
00450314 |. 6A 40 push 40
00450316 |. B9 94034500 mov ecx, 00450394 ; 恭喜你
0045031B |. BA 9C034500 mov edx, 0045039C ; 注册成功!请联系我!qq:609841314
00450320 |. A1 04204500 mov eax, dword ptr [452004]
00450325 |. 8B00 mov eax, dword ptr [eax]
00450327 |. E8 48E9FFFF call 0044EC74
0045032C |> 33C0 xor eax, eax
0045032E |. 5A pop edx
0045032F |. 59 pop ecx
00450330 |. 59 pop ecx
第二步:用C语言还原算法
虽然最后一步的还原不是太地道,不过算出来就是了
#include <stdio.h>
#include <string.h>
//从网上找了两个字符串操作函数稍微改动一了下************************
/*从字符串的左边截取n个字符*/
void GetLeftStr(char *dst,char *src, int n)
{
char*p = src;
intlen = strlen(src);
if(n>len)n = len;
while(n--)*(dst++) = *(p++);
*(dst++)='\0';
return;
}
/*把字符串反过来*/
void reverse(char s[])
{
intc,j,i;
for(i=0,j=strlen(s)-1;i<j;i++,j--)//完成倒置功能,不包括字符串结束符'/0'
{
c=s[i];
s[i]=s[j];
s[j]=c;
}
}
int main()
{
char szBen[]="i am Bin Laden";
char user[50]="";
int UsrLen=0;
int i=0,j,code1=0,code2=0,code12=0,code22=0,k=0,pass1=0,pass2=0;
int i2,j2,k2,ijkSignal=0;
char code[50]="";
char Pass[50]="";
char LeftStr[50]="";
char LeftStr2[50]="";
int CodeLen=0;
int count=1;
memset(user,0,50);
memset(code,0,50);
memset(Pass,0,50);
memset(LeftStr,0,50);
//因为怕调试麻烦,所以我就不用scanf,而是直接设置用户和密码了
strcpy(user,"abcdefghij");
strcpy(code,"12klmnopqrstuvwxyz");
UsrLen=strlen(user);
if(UsrLen<0x0A|| UsrLen>0x10)
goto end;
CodeLen=strlen(code);
if(CodeLen<0x0A|| CodeLen>0x16)
goto end;
strcat(user,szBen);
UsrLen=strlen(user);
//计算第一部分
for(i=0;i<UsrLen;i++)
for(j=0;j<18;j++)
{
Pass[i+j]=(user[i]*code[j])%26+65; //这里是重点***这里是重点
}
//计算第二部分
GetLeftStr(LeftStr,Pass,CodeLen+5);
reverse(LeftStr);
GetLeftStr(LeftStr2,LeftStr,CodeLen);
printf("\nMiddleString:%s\n",LeftStr2);
end:
return0;
}
第三步:研究其规律并根据规律写注册机
根据在VC调试第二步中的代码,发现“计算第一部分”中生成的字符,不全是第一次被设置成一个值之后,以后就会是这个值。
换句话说Pass[1]在i=0,j=1的时候被设置成了x,但是Pass[1]的最终取值取决于它在双重循环中最后被赋值的那个操作。
根据调试得知它最后被赋值是在i=1,j=0的时候。
很奇妙地,Pass[2]最后被赋值是在i=2,j=0的时候(看出规律了?)
现在我们把C语言还原出来的算法中重点的一句拿出来看看
Pass[i+j]=(user[i]*code[j])%26+65; //这里是重点***这里是重点
从上面研究出来的规律我们可以看出,要使得算出来的Pass中的某个元素为最终的模样,j必须为0,也就是说我们完全可以把这条语句替换为
Pass[i]=(user[i]*code[0])%26+65; //这里是重点***这里是重点
在求出code[0]之前,我们首先看看第一步分析汇编算法时,在“成功信息”弹出来之前有" 将String+6"之后的len(code)个位的字符串倒置作为密码"的操作,从这个操作我们看出
1 用user[0],user[1],user[2],user[3],user[4]算出来的Pass对应的字符跟最终的密码没半毛线关系
2 用user[5]算出来的Pass中对应的字符实际上就是算出来的Key的最后一位(也就是说从i=5开始,第一次算出来的Pass字符是最后一个序列号字符,同理:i=6时算出来的Pass字符是序列号倒数第二个字符)
举个例,当输入的密码为18位的时候
我们应用上面举的例子中的最后一条来算code[0],代码如下
for(code[0]=65; code[0]<91; code[0]++)
现在既然求出code[0]了, 那么根据表达式
Pass[i]=(user[i]*code[0])%26+65;
其他位的序列号也可以算出来了
for(int i=1;i<=LenOfCode;i++)
Pass[i]=user[i+4]*code[0]%26+65
CraclkMe源码(因为这个exe链接失效了,所以我找到了作者发的源码重新编译一遍
procedure TForm1.Button1Click(Sender: TObject);
var
name,code,laden,tiger,str:string;
i,j:integer;
part:array[1..100]of char;
begin
name:=edit1.Text;
code:=edit2.Text;
laden:='i am Bin Laden';
tiger:='i am yi zhi lao hu';
if (length(name)<10) or (length(name)>16) then
exit;
if (length(code)<17) or (length(code)>22) then
exit;
name:=name+laden;
for i:=1 to 100 do
part[i]:='.';
for i:=1 to length(name) do
for j:=1 to length(tiger) do
part[i+j]:=chr(((ord(name[i])*ord(code[j]))mod 26)+65);
str:='';
for i:=1 to length(code) do
str:=part[i+6]+str;
if str=code then
Application.MessageBox('注册成功!请联系我!QQ:609841314','恭喜你',MB_ICONINFORMATION+MB_OK);
end;