【文章标题】: 一个MD5算法的Keygenme
【文章作者】: push_eax
【下载地址】: 自己搜索下载
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------菜鸟
【详细过程】
闲来无事,抓出个不知何时下的Keygenme,练练手也好。分析后可知,这个Keygenme有2张数据表,由注册名经MD5运算后进行查表,得出2个DWORD数据,然后再由注册码经过一个可逆循环得出2个DWORD数据,最后对比是否正确的。
开始分析,查壳,无壳。Od载入,随便输入Name和Sierial很容易得知Sierial应为16位。很简单的来到下面:
00401D19 6A 28 push 28
00401D1B 68 E430>push KeyGenMe.004030E4
00401D20 FF35 04>push dword ptr ds:[403004]
00401D26 E8 CF01>call <jmp.&user32.GetWindowTextA> //这个call读入Name
00401D2B 68 D430>push KeyGenMe.004030D4
00401D30 50 push eax //压入的参数是Name长度
00401D31 68 E430>push KeyGenMe.004030E4 //压入的参数是Name
00401D36 E8 C5F2>call KeyGenMe.00401000
这个call是对Name的初步处理,我们跟入:
00401000 55 push ebp
00401001 8BEC mov ebp,esp
00401003 83C4 F0 add esp,-10
…………
0040104B 8B55 0C mov edx,dword ptr ss:[ebp+C]
0040104E 8B7D 08 mov edi,dword ptr ss:[ebp+8]
00401051 8B75 10 mov esi,dword ptr ss:[ebp+10]
00401054 C706 01>mov dword ptr ds:[esi],67452301
0040105A C746 04>mov dword ptr ds:[esi+4],EFCDAB89
00401061 C746 08>mov dword ptr ds:[esi+8],98BADCFE
00401068 C746 0C>mov dword ptr ds:[esi+C],10325476
这里4个常数,01234567,89ABCDEF,FEDCBA98,7654321(内存中是低位存放低字节,高位存放高字节,所以显示的是67452301,EFCDAB89,98BADCFE,10325476)基本上可以肯定是MD5算法。出这个CALL,看看压入CALL的参数:
00401D2B 68 D430>push KeyGenMe.004030D4
可以看到出CALL后,这个004030D4返回了一个字符串,应该是MD5码吧,找个MD5计算的程序对比下,OK肯定了是MD5加密的CALL,继续向下走。
00401D43 8D35 D4>lea esi,dword ptr ds:[4030D4] //取得MD5码的地址
00401D49 B9 0400>mov ecx,4
00401D4E 0BD0 or edx,eax
00401D50 33C0 xor eax,eax
00401D52 AC lods byte ptr ds:[esi]
00401D53 C1E2 08 shl edx,8
00401D56 E2 F6 loopd short KeyGenMe.00401D4E
00401D58 0BD0 or edx,eax
00401D5A 8915 9D>mov dword ptr ds:[40309D],edx
小循环根据MD5字符取得一个DWORD数据,记为m2="N[4]N[3]N[2]N[1]",其中N[m]表示MD5字符串的第m个字符
00401D60 B9 0400>mov ecx,4
00401D65 33C0 xor eax,eax
00401D67 33D2 xor edx,edx
00401D69 0BD0 or edx,eax
00401D6B AC lods byte ptr ds:[esi]
00401D6C C1E2 08 shl edx,8
00401D6F E2 F8 loopd short KeyGenMe.00401D69
00401D71 0BD0 or edx,eax
00401D73 8915 95>mov dword ptr ds:[403095],edx //取得m1="N[8]N[7]N[6]N[5]"
00401D79 6A 0C push 0C
00401D7B 68 C730>push KeyGenMe.004030C7 //字符串"ChinaCracker",呵呵,原来是国产的
00401D80 E8 53FC>call KeyGenMe.004019D8 //关键call 1,计算出一个从[4032D6]开始的DWORD数据表,记为table2
00401D85 68 9530>push KeyGenMe.00403095 //压入m1
00401D8A 68 9D30>push KeyGenMe.0040309D //压入m2
00401D8F E8 E8FB>call KeyGenMe.0040197C //关键call 2
跟入关键CALL 1,来到:
004019E3 BE D632>mov esi,KeyGenMe.004032D6 //[4032D6]开始是个DWORD数据表,记做Table2
004019E8 B8 AE43>mov eax,KeyGenMe.004043AE
004019ED 8D4E 48 lea ecx,dword ptr ds:[esi+48]
004019F0 BA 0001>/mov edx,100 // 将从[4043AE]开始的1000H
004019F5 8B38 |/mov edi,dword ptr ds:[eax] // 字节内容复制到[40331E]
004019F7 83C0 04 ||add eax,4 // 至[40431E]的区间内。
004019FA 8939 ||mov dword ptr ds:[ecx],edi 生成一个1000H大小的数据表
004019FC 83C1 04 ||add ecx,4 记为table1
004019FF 4A ||dec edx
00401A00 75 F3 |\jnz short KeyGenMe.004019F5
00401A02 3D AE53>|cmp eax,KeyGenMe.004053AE
00401A07 7C E7 \jl short KeyGenMe.004019F0
上面这个小循环生成1000H的数据表table 1,[40331E]是开始地址,继续:
00401A09 8B55 08 mov edx,dword ptr ss:[ebp+8] //"ChinaCracker"
00401A0C BF 6643>mov edi,KeyGenMe.00404366
00401A11 33C0 xor eax,eax
00401A13 2BFE sub edi,esi
00401A15 C745 FC>mov dword ptr ss:[ebp-4],12 //循环12H次,记N为循环计数器
00401A1C 33C9 /xor ecx,ecx
00401A1E C745 F8>|mov dword ptr ss:[ebp-8],4 //小循环:
00401A25 33DB |/xor ebx,ebx
00401A27 8A1C02 ||mov bl,byte ptr ds:[edx+eax]
00401A2A C1E1 08 shl ecx,8
00401A2D 0BCB or ecx,ebx
00401A2F 40 inc eax //eax表示从"ChinaCracker"取的字符个数
00401A30 3B45 0C cmp eax,dword ptr ss:[ebp+C] // "ChinaCracker"的长度12
00401A33 7C 02 jl short KeyGenMe.00401A37//每次取4个字符,比较是否取满12
00401A35 33C0 xor eax,eax //取满清零EAX,重新开始取
00401A37 8B5D F8 mov ebx,dword ptr ss:[ebp-8] //小循环计数器
00401A3A 4B dec ebx
00401A3B 895D F8 mov dword ptr ss:[ebp-8],ebx
00401A3E 75 E5 jnz short KeyGenMe.00401A25 //小循环从"ChinaCracker"中取4字节组成1个DWORD数据赋给ECX, ECX="ChinaCracker"的第((12-N )mod 4)+1个4字节
00401A40 8B1C3E mov ebx,dword ptr ds:[esi+edi] //从[404366]开始每次取1个DWORD数据
00401A43 83C6 04 add esi,4
00401A46 33D9 xor ebx,ecx //异或
00401A48 8B4D FC |mov ecx,dword ptr ss:[ebp-4]
00401A4B 895E FC |mov dword ptr ds:[esi-4],ebx //运算后的数据覆盖table2的相应DWORD数据
00401A4E 49 dec ecx //N-1
00401A4F 894D FC |mov dword ptr ss:[ebp-4],ecx
00401A52 75 C8 \jnz short KeyGenMe.00401A1C
上面这个小循环:从[404366]取12H个DWORD数据,处理后赋给table2,table2长度为12H
00401A54 BB D632>mov ebx,KeyGenMe.004032D6
00401A59 33C0 xor eax,eax
00401A5B A3 5E43>mov dword ptr ds:[40435E],eax //[40435E]和[404362]2个全局变量清零,记[40435E]为key1,[4043620]为key2
00401A60 A3 6243>mov dword ptr ds:[404362],eax
00401A65 8BF3 mov esi,ebx
循环开始,对table2进运算:
00401A67 BF 0900>mov edi,9 //循环9次
00401A6C 8D05 5E>/lea eax,dword ptr ds:[40435E]
00401A72 8D0D 62>|lea ecx,dword ptr ds:[404362]
00401A78 50 push eax //key1地址
00401A79 51 push ecx //key2地址
00401A7A E8 FDFE>|call KeyGenMe.0040197C //记为函数f1
00401A7F A1 6243>|mov eax,dword ptr ds:[404362] //EAX=key2
00401A84 8B0D 5E>|mov ecx,dword ptr ds:[40435E] //ECX=key1
00401A8A 8906 mov dword ptr ds:[esi],eax //每次覆盖table2的2个DWORD数据
00401A8C 894E 04 |mov dword ptr ds:[esi+4],ecx
00401A8F 83C6 08 |add esi,8 //table2指针+8
00401A92 4F |dec edi
00401A93 75 D7 \jnz short KeyGenMe.00401A6C
又一循环,对table1进行运算:
00401A95 8D73 4C lea esi,dword ptr ds:[ebx+4C]
00401A98 C745 F4>mov dword ptr ss:[ebp-C],4 //循环4次
00401A9F BF 8000>/mov edi,80 //小循环,循环80H次
00401AA4 8D0D 5E>|/lea ecx,dword ptr ds:[40435E]
00401AAA 8D15 62>||lea edx,dword ptr ds:[404362]
00401AB0 51 |push ecx key1地址
00401AB1 52 push edx key2地址
00401AB2 E8 C5FE>||call KeyGenMe.0040197C //f1
00401AB7 8B0D 62>||mov ecx,dword ptr ds:[404362] //ecx=key2
00401ABD 8B15 5E>||mov edx,dword ptr ds:[40435E] //ecx=key1
00401AC3 894E FC ||mov dword ptr ds:[esi-4],ecx //每次覆盖table1的2个数据
00401AC6 8916 ||mov dword ptr ds:[esi],edx
00401AC8 83C6 08 ||add esi,8
00401ACB 4F ||dec edi
00401ACC 75 D6 |\jnz short KeyGenMe.00401AA4 //小循环结束
00401ACE FF4D F4 |dec dword ptr ss:[ebp-C]
00401AD1 75 CC \jnz short KeyGenMe.00401A9F//循环结束
00401AD3 59 pop ecx
00401AD4 5A pop edx
00401AD5 5E pop esi
00401AD6 5F pop edi
00401AD7 5B pop ebx
00401AD8 C9 leave
00401AD9 C2 0800 retn 8
上面两个大循环分别对table2和table1作处理,起作用的是都是call 0040197C,跟入:
00401987 8B45 08 mov eax,dword ptr ss:[ebp+8]
0040198A 8B4D 0C mov ecx,dword ptr ss:[ebp+C]
0040198D 8B00 mov eax,dword ptr ds:[eax] //EAX=key2
0040198F 8B31 mov esi,dword ptr ds:[ecx] //ESI=key1
00401991 BF D632>mov edi,KeyGenMe.004032D6 //table2首地址
00401996 C745 FC>mov dword ptr ss:[ebp-4],10 //循环10H次
0040199D 8BDF mov ebx,edi
小循环:
0040199F 3303 /xor eax,dword ptr ds:[ebx]
004019A1 8BD0 |mov edx,eax
004019A3 50 |push eax
004019A4 E8 67FF>|call KeyGenMe.00401910 //记为函数f2()
004019A9 8B4D FC |mov ecx,dword ptr ss:[ebp-4] //循环计数器
004019AC 33C6 |xor eax,esi
004019AE 83C3 04 |add ebx,4 ;
004019B1 49 |dec ecx
004019B2 8BF2 |mov esi,edx
004019B4 894D FC |mov dword ptr ss:[ebp-4],ecx
004019B7 75 E6 \jnz short KeyGenMe.0040199F
小循环用C语言描述,即:
for(n=0;n<16;n++)
{EAX=table2[n] xor ESI;
EDX=EAX; EAX=f2(EAX);
EAX=EAX xor ESI; ESI=EDX;}
004019B9 8B4F 40 mov ecx,dword ptr ds:[edi+40] //table2的第17个数据,table2[16]
004019BC 8B57 44 mov edx,dword ptr ds:[edi+44] //table2最后1个数据,table2[17]
004019BF 33C8 xor ecx,eax
004019C1 33D6 xor edx,esi
004019C3 8915 62>mov dword ptr ds:[404362],edx //key2=ESI xor table2[17]
004019C9 890D 5E>mov dword ptr ds:[40435E],ecx //key1=EAX xor table2[16]
接着就出这个CALL了,看样子还要跟入f2,即00401910,那就跟入吧,来到这里:
00401918 8B4D 08 mov ecx,dword ptr ss:[ebp+8] //[ebp+8]是压入CALL的参数变量
0040191B 8AC1 mov al,cl
0040191D 25 FF00>and eax,0FF
00401922 C1E9 08 shr ecx,8
00401925 8BD0 mov edx,eax //edx= 0FF AND 参数变量
00401927 8AC1 mov al,cl
00401929 BF D632>mov edi,KeyGenMe.004032D6
0040192E 25 FF00>and eax,0FF
00401933 C1E9 08 shr ecx,8
00401936 8BF0 mov esi,eax //esi=(0FF00H AND 参数变量)>>8
00401938 8BC1 mov eax,ecx
0040193A C1E8 08 shr eax,8
0040193D 25 FF00>and eax,0FF // eax=(FF000000H AND 参数变量)>>24
00401942 81E1 FF>and ecx,0FF //ecx=(0FF0000H AND 参数变量)>>16
00401948 81E6 FF>and esi,0FFFF
0040194E 81E2 FF>and edx,0FFFF
00401954 8B4487 >mov eax,dword ptr ds:[edi+eax*4+48] //查表table1,[edi+48]是table1的开始地址
00401958 8B9C8F >mov ebx,dword ptr ds:[edi+ecx*4+448]
0040195F 8B8CB7 >mov ecx,dword ptr ds:[edi+esi*4+848]
00401966 03C3 add eax,ebx
00401968 33C1 xor eax,ecx
0040196A 8B8C97 >mov ecx,dword ptr ds:[edi+edx*4+C48]
00401971 03C1 add eax,ecx
这个call用C语言描述为:
DWORD f2(para)
{DWORD eax=0,ecx=0,edx=0,esi=0,ebx=0;
edx=para&0ffH;esi=(para0ff00H)>>8;eax=(para&0ff000000H)>>24;ecx=(para&0ff0000H)>>16;
eax=table1[eax];ebx=table1[ecx+0x100];ecx=table1[esi+0x200];
eax+=ebx;eax^=ecx;ecx=table1[edx+0x300];eax+=ecx;}
注意DWORD占4字节,上面位移,要除以4([edi+48]是table1的开始地址
,则[edi+eax*4+48]就是table[eax])。
搞定,返回到最外面CALL:
00401D85 68 9530>push KeyGenMe.00403095 //压入的是前面m1的地址
00401D8A 68 9D30>push KeyGenMe.0040309D //m2的地址
00401D8F E8 E8FB>call KeyGenMe.0040197C //关键call 2
00401D94 90 nop
这CALL调用前面函数f1,继续对key1,key2作变换,只是压入参数变为m1,m2了。我们继续向下看:
00401E1C B9 0800>mov ecx,8 //传送2个DWORD,key1,key2地址相临,一起传送
00401E21 8D35 5E>lea esi,dword ptr ds:[40435E] //key1
00401E27 8D3D C0>lea edi,dword ptr ds:[4031C0] //保存最终key1,key2
00401E2D F3:A4 rep movs byte ptr es:[edi],byte ptr ds>
00401E2F 68 B630>push KeyGenMe.004030B6 //ASCII "[BCG][FCG][DFCG]"
00401E34 68 AC31>push KeyGenMe.004031AC //Sierial地址
00401E39 E8 46FD>call KeyGenMe.00401B //key call 3对Sierial作变换
前面可以看出,对Name的变化是MD5加密了的,我们不能根据这个算出的key1,key2作逆变换,得出用户名,那么写Keygen关键就在这个对Sierial处理的CALL:
00401B84 55 push ebp
00401B85 8BEC mov ebp,esp
00401B87 60 pushad
00401B88 8B75 0C mov esi,dword ptr ss:[ebp+C]// 由"[BCG][FCG][DFCG]"
00401B8B 8B06 mov eax,dword ptr ds:[esi] //得到4个常数
00401B8D 8B5E 04 mov ebx,dword ptr ds:[esi+4]
00401B90 8B4E 08 mov ecx,dword ptr ds:[esi+8]
00401B93 8B56 0C mov edx,dword ptr ds:[esi+C]
00401B96 A3 C053>mov dword ptr ds:[4053C0],eax //这里是4个常数
00401B9B 891D C4>mov dword ptr ds:[4053C4],ebx
00401BA1 890D C8>mov dword ptr ds:[4053C8],ecx
00401BA7 8915 CC>mov dword ptr ds:[4053CC],edx
00401BAD 55 push ebp
00401BAE 8B5D 08 mov ebx,dword ptr ss:[ebp+8]
00401BB1 BA 2037>mov edx,C6EF3720 ,edx初始化为0x C6EF3720
00401BB6 8B33 mov esi,dword ptr ds:[ebx]
00401BB8 8B7B 04 mov edi,dword ptr ds:[ebx+4]
这里esi="Sierial前8个字符",edi="Sierial后8个字符",具体的说,若Sierial=1234567887654321,则esi=78563412H,edi=21436587H。
循环记数器,下面循环开始:
00401BBB BD 2000>mov ebp,20
00401BC0 8BC6 /mov eax,esi
00401BC2 8BDE |mov ebx,esi
00401BC4 8BCE |mov ecx,esi
00401BC6 C1E0 04 |shl eax,4
00401BC9 0305 C8>|add eax,dword ptr ds:[4053C8] //eax=(esi>>5)+5d474346H,记为sa;
00401BCF C1EB 05 |shr ebx,5
00401BD2 031D CC>|addebx,dword ptr ds:[4053CC]//ebx=(esi<<4)+445b5d47H,记为sb
00401BD8 03CA |add ecx,edx //ecx=esi+edx,记为sc
00401BDA 33C8 |xor ecx,eax
00401BDC 33CB |xor ecx,ebx //(sc^sa)^sb,记为fs(esi)
00401BDE 2BF9 |sub edi,ecx //edi=edi-ecx
00401BE0 8BC7 |mov eax,edi
00401BE2 8BD8 |mov ebx,eax
00401BE4 8BC8 |mov ecx,eax
00401BE6 C1E0 04 |shl eax,4
00401BE9 0305 C0>|add eax,dword ptr ds:[4053C0]//eax=(edi>>5)+43465b5DH,记为da
00401BEF C1EB 05 |shr ebx,5
00401BF2 031D C4>|add ebx,dword ptr ds:[4053C4]//ebx=(edi<<)+4743425bH,记为db
00401BF8 03CA |add ecx,edx //ecx= edi+edx,记为dc
00401BFA 33C8 |xor ecx,eax
00401BFC 33CB |xor ecx,ebx //(da^dc)^db,记为fd(edi)
00401BFE 2BF1 |sub esi,ecx
00401C00 81EA B9>|sub edx,9E3779B9 //edx-=9E3779B9H
00401C06 4D |dec ebp
00401C07 75 B7 \jnz short KeyGenMe.00401BC0
00401C09 892D C0>mov dword ptr ds:[4053C0],ebp
00401C0F 892D C4>mov dword ptr ds:[4053C4],ebp
00401C15 892D C8>mov dword ptr ds:[4053C8],ebp
00401C1B 892D CC>mov dword ptr ds:[4053CC],ebp
00401C21 5D pop ebp
00401C22 8B5D 08 mov ebx,dword ptr ss:[ebp+8] //Sierial地址,保存Sierial计算出的2个值,记为s1,s2
00401C25 8933 mov dword ptr ds:[ebx],esi
00401C27 897B 04 mov dword ptr ds:[ebx+4],edi
00401C2A 61 popad
00401C2B C9 leave
00401C2C C2 0800 retn 8
上面这个循环用C语言描述的话:
edx=c6ef3720H;
for(i=0;i<20H;i++)
{edi=edi-fs(esi);
esi=esi-fd(edi);
edx-=9e3779b9;}
把edi,esi当作数列的话,则有:edi(N)=edi(N-1)-fs(esi(N-1));esi(N)=esi(N-1)-fd(edi(N))移项一下得到:esi(N-1)= esi(N)+ fd(edi(N));edi(N-1)= edi(N)+ fs(esi(N-1))。可见这个是可逆的,从edi(N),esi(N)我们能推算出最初始化的esi,edi,也就是说,写Keygen时由Name计算出的key1,key2得到正确的Sierial(正确的化,esi,edi被初始化为key1,key2)。
整个过程基本OK。写Keygen时:table1,table2两表由程序中固定数值换算得到。我把原始数据导出为data1.dat,data2.dat。写的时候按上面分析的得出table1,table2,再进行计算得到key1,key2,最后由key1,key2得到正确的Sierial即可。代码较长,这里就不赘述了,源代码和data1.dat,data2.dat见附件。
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!
2006年07月09日 上午 01:10:28
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)