启动画面修改工具InnoCustomize注册算法分析
昨天想换掉Windows的启动画面,找了些资料,发现还要自己做图,又要改boot.ini文件,很是麻烦,没想到找到的这个工具可以轻松的做到这一点,很酷很方便,而且能修改的不止这些...
[Cracker] : prince
[时间] : 2005.05.13
[声明] : 只做技术交流,不做商业用途,如果你手头宽裕并喜欢这个软件的话请支持正版软件。
[E-mail] : Cracker_prince@163.com
[软件信息]
[软件说明] :
InnoCustomize的功能特点:
能够修改的项目包括:启动画面、登录界面、墙纸、可视化风格和主题;
能够将bmp、gif、jpg、pcd、psd、psp、png等多种格式的图片转换成启动画面;
提供启动画面编辑器,用户不需要使用Photoshop等图片编辑工具就能够修饰自己的启动画面;
支持ZIP文件,即使多个主题、登录界面等放在ZIP里,用户也不用解压,直接添加就可以使用,不但操作起来简单而且节省硬盘空间;
登录界面支持logonxp文件格式;
更安全的替换,即使用户从网上下载的启动画面、登录界面资源是EXE文件,也可以放心使用,因为InnoCustomize不是象某些软件那样直接替换EXE文件,而是经过分析,抽取其中的资源来替换;
这样做除了更安全以外,还有一个明显的好处:
如果这个登录界面是德国人做的,那他的EXE就是德文Windows的EXE,这种情况下即使能够使用,当我们面对陌生的德文时多少也会有点遗憾。而使用InnoCustomize就会不有这样的问题。
能够同时浏览多个资源,如果用户要更换主题,那他/她不用一个个去翻,因为InnoCustomize会一次把所有的主题都列出来,直接选择自己喜欢的就可以了;
可以让InnoCustomize在每次开机,或者每天、每周第一次开机时自己更换启动画面或者登录界面。
运行环境:
Windows XP 各个版本(全部功能)
Windows 2000 各个版本(仅支持启动画面、墙纸)
InnoCustomize的安装包中含有一些资源,安装之后替换一下就能够看到效果。
[保护方式] : 序列号
[限制方式] : NAG窗口(将近1分钟的NAG,受不了)
[外壳保护] : ASPack 2.12 -> Alexey Solodovnikov
[编译器/语言]: Borland Delphi 6.0 - 7.0 / Object PASCAL
[下载地址] : http://www.milsoft.net/main.asp
[分析过程] :
ASPackDie搞定外壳,无须修复,可直接运行,无自校验。省了好多事啊~ 注册情况:有3个输入框,购买人,用户名和注册码,分别填入CZero, prince和87654321,点“确定”按钮,无反应,呵呵,有准备啊!下断点GetWindowTextA试试,还是没反应,没办法,请出DeDe,分析后看到该“确定”按钮的响应代码为0047E1D8,在OD中下断0047E1D8,点“确定”,OK,断下来了:
-------------------------------------------------------------------------------
0047E1D8 /. 55 push ebp ; 断在这里
0047E1D9 |. 8BEC mov ebp,esp
0047E1DB |. 83C4 F8 add esp,-8
0047E1DE |. 8955 F8 mov dword ptr ss:[ebp-8],edx
0047E1E1 |. 8945 FC mov dword ptr ss:[ebp-4],eax
0047E1E4 |. 33D2 xor edx,edx
0047E1E6 |. 8B45 FC mov eax,dword ptr ss:[ebp-4]
0047E1E9 |. 8B80 04030000 mov eax,dword ptr ds:[eax+304]
0047E1EF |. 8B08 mov ecx,dword ptr ds:[eax]
0047E1F1 |. FF51 64 call dword ptr ds:[ecx+64] ; 跟进看看
0047E1F4 |. 8B45 FC mov eax,dword ptr ss:[ebp-4]
0047E1F7 |. 8B80 10030000 mov eax,dword ptr ds:[eax+310]
0047E1FD |. C740 0C 01000000 mov dword ptr ds:[eax+C],1
0047E204 |. 59 pop ecx
0047E205 |. 59 pop ecx
0047E206 |. 5D pop ebp
0047E207 \. C3 retn
--------------------------------------------------------------------------------
只有0047E1F1这一个CALL,跟进:
--------------------------------------------------------------------------------
00444E0C . 3A50 58 cmp dl,byte ptr ds:[eax+58]
00444E0F . 74 11 je short Unpacked.00444E22
00444E11 . 8850 58 mov byte ptr ds:[eax+58],dl
00444E14 . 6A 00 push 0 ; /Arg1 = 00000000
00444E16 . 33C9 xor ecx,ecx ; |
00444E18 . BA 0CB00000 mov edx,0B00C ; |
00444E1D . E8 42120000 call Unpacked.00446064 ; \Unpacked.00446064
00444E22 > C3 retn
--------------------------------------------------------------------------------
00444E1D这个CALL就不要跟进了,进去就是一直在打转,得不到有用信息。看来这个“确定”并没有参与注册码的计算。再试试内存断点,在内存中搜索假码87654321,下内存访问断点,被断下后CTRL+F9执行到返回,OD没有响应,看来可能是在某个线程中,但是点击“确定”按钮却没有被断下,说明在“确定”之前就已经计算比较过了,如果是这样,软件是怎么做到知道用户的输入呢?想到了什么?Timer! 回到DeDe,哈,看到了Timer1Timer和Timer2Timer对应的响应代码:0047E208和0047E2F0,在OD中下这两个断点,这里注意,下完断点之后先将他们关闭,否则你都没有机会回到程序界面去。输入完所有资料之后在OD中开启刚才下的断点,马上就被断下,呵呵Timer开启了。
--------------------------------------------------------------------------------
0047E2F0 /. 55 push ebp
0047E2F1 |. 8BEC mov ebp,esp
0047E2F3 |. 33C9 xor ecx,ecx
0047E2F5 |. 51 push ecx
0047E2F6 |. 51 push ecx
0047E2F7 |. 51 push ecx
0047E2F8 |. 51 push ecx
0047E2F9 |. 51 push ecx
0047E2FA |. 51 push ecx
0047E2FB |. 51 push ecx
0047E2FC |. 51 push ecx
0047E2FD |. 8955 F4 mov dword ptr ss:[ebp-C],edx
0047E300 |. 8945 FC mov dword ptr ss:[ebp-4],eax
0047E303 |. 33C0 xor eax,eax
0047E305 |. 55 push ebp
0047E306 |. 68 EDE34700 push Unpacked.0047E3ED
0047E30B |. 64:FF30 push dword ptr fs:[eax]
0047E30E |. 64:8920 mov dword ptr fs:[eax],esp
0047E311 |. 8B45 FC mov eax,dword ptr ss:[ebp-4]
0047E314 |. 8B80 10030000 mov eax,dword ptr ds:[eax+310]
0047E31A |. 8378 0C 00 cmp dword ptr ds:[eax+C],0
0047E31E |. 0F84 AE000000 je Unpacked.0047E3D2 ; 这里一定不能跳,手动修改跳转标志
0047E324 |. 8B45 FC mov eax,dword ptr ss:[ebp-4] ; 下面就是判断比较过程,
0047E327 |. 8B80 10030000 mov eax,dword ptr ds:[eax+310]
0047E32D |. 33D2 xor edx,edx
0047E32F |. 8950 0C mov dword ptr ds:[eax+C],edx
0047E332 |. 8D55 F0 lea edx,dword ptr ss:[ebp-10]
0047E335 |. 8B45 FC mov eax,dword ptr ss:[ebp-4]
0047E338 |. 8B80 34030000 mov eax,dword ptr ds:[eax+334]
0047E33E |. E8 6D6BFCFF call Unpacked.00444EB0 ; 取假码87654321
0047E343 |. 8B45 F0 mov eax,dword ptr ss:[ebp-10] ; EAX=假码87654321
0047E346 |. 50 push eax
0047E347 |. 8D55 EC lea edx,dword ptr ss:[ebp-14]
0047E34A |. 8B45 FC mov eax,dword ptr ss:[ebp-4]
0047E34D |. 8B80 00030000 mov eax,dword ptr ds:[eax+300]
0047E353 |. E8 586BFCFF call Unpacked.00444EB0 ; 取用户名prince
0047E358 |. 8B45 EC mov eax,dword ptr ss:[ebp-14] ; EAX=用户名prince
0047E35B |. 5A pop edx
0047E35C |. E8 FF200000 call Unpacked.00480460 ; 关键函数,跟进
0047E361 |. 8845 FB mov byte ptr ss:[ebp-5],al
0047E364 |. 807D FB 00 cmp byte ptr ss:[ebp-5],0
0047E368 |. 74 47 je short Unpacked.0047E3B1
0047E36A |. 8D55 E8 lea edx,dword ptr ss:[ebp-18]
0047E36D |. 8B45 FC mov eax,dword ptr ss:[ebp-4]
0047E370 |. 8B80 34030000 mov eax,dword ptr ds:[eax+334]
0047E376 |. E8 356BFCFF call Unpacked.00444EB0
0047E37B |. 8B45 E8 mov eax,dword ptr ss:[ebp-18]
0047E37E |. 50 push eax
0047E37F |. 8D55 E4 lea edx,dword ptr ss:[ebp-1C]
0047E382 |. 8B45 FC mov eax,dword ptr ss:[ebp-4]
--------------------------------------------------------------------------------
0047E35C处跟进:
--------------------------------------------------------------------------------
00480460 /$ 55 push ebp
00480461 |. 8BEC mov ebp,esp
00480463 |. B9 07000000 mov ecx,7
00480468 |> 6A 00 /push 0
0048046A |. 6A 00 |push 0
0048046C |. 49 |dec ecx
0048046D |.^ 75 F9 \jnz short Unpacked.00480468
0048046F |. 8955 F8 mov dword ptr ss:[ebp-8],edx ; 假码写入堆栈
00480472 |. 8945 FC mov dword ptr ss:[ebp-4],eax ; 用户名写入堆栈
00480475 |. 8B45 FC mov eax,dword ptr ss:[ebp-4] ; EAX=用户名prince
00480478 |. E8 B347F8FF call Unpacked.00404C30 ; 检测用户名是否为空
0048047D |. 8B45 F8 mov eax,dword ptr ss:[ebp-8] ; 取假码87654321
00480480 |. E8 AB47F8FF call Unpacked.00404C30 ; 仍然是上面那个检测是否为空的函数
00480485 |. 33C0 xor eax,eax
00480487 |. 55 push ebp
00480488 |. 68 0E064800 push Unpacked.0048060E
0048048D |. 64:FF30 push dword ptr fs:[eax]
00480490 |. 64:8920 mov dword ptr fs:[eax],esp
00480493 |. 8D55 EC lea edx,dword ptr ss:[ebp-14]
00480496 |. 8B45 FC mov eax,dword ptr ss:[ebp-4] ; 取用户名prince
00480499 |. E8 AE84F8FF call Unpacked.0040894C ; 小写转大写函数,结果PRINCE
0048049E |. 8D45 E8 lea eax,dword ptr ss:[ebp-18]
004804A1 |. E8 DA42F8FF call Unpacked.00404780 ; 无关函数
004804A6 |. 8B45 EC mov eax,dword ptr ss:[ebp-14] ; 取大写后的用户名
004804A9 |. E8 9245F8FF call Unpacked.00404A40 ; 取用户名个数
004804AE |. 85C0 test eax,eax
004804B0 |. 7E 38 jle short Unpacked.004804EA
004804B2 |. 8945 E4 mov dword ptr ss:[ebp-1C],eax ; 用户名个数写入堆栈
004804B5 |. C745 F0 01000000 mov dword ptr ss:[ebp-10],1
004804BC |> 8D4D DC /lea ecx,dword ptr ss:[ebp-24]
004804BF |. 8B45 EC |mov eax,dword ptr ss:[ebp-14] ; EAX=大写后的用户名PRINCE
004804C2 |. 8B55 F0 |mov edx,dword ptr ss:[ebp-10]
004804C5 |. 0FB64410 FF |movzx eax,byte ptr ds:[eax+edx->; 循环取单个字符ASC码
004804CA |. 83C0 0F |add eax,0F ; EAX=该字符ASC码+0xF
004804CD |. BA 02000000 |mov edx,2 ; EDX=2
004804D2 |. E8 418AF8FF |call Unpacked.00408F18 ; 根据单个字符计算中间码,跟进
004804D7 |. 8B55 DC |mov edx,dword ptr ss:[ebp-24] ; 中间码送EDX
004804DA |. 8D45 E8 |lea eax,dword ptr ss:[ebp-18]
004804DD |. E8 6645F8FF |call Unpacked.00404A48 ; 未知
004804E2 |. FF45 F0 |inc dword ptr ss:[ebp-10]
004804E5 |. FF4D E4 |dec dword ptr ss:[ebp-1C]
004804E8 |.^ 75 D2 \jnz short Unpacked.004804BC ; 是否取完用户名?
004804EA |> 8B45 E8 mov eax,dword ptr ss:[ebp-18] ; 上面循环的计算结果“5F61585D5254”作为中间码1送EAX
004804ED |. E8 4E45F8FF call Unpacked.00404A40 ; 中间码1的个数,个数送EAX
004804F2 |. A8 01 test al,1 ; 检测该个数低位是否为0
004804F4 |. 74 0D je short Unpacked.00480503
004804F6 |. 8D45 E8 lea eax,dword ptr ss:[ebp-18]
004804F9 |. BA 24064800 mov edx,Unpacked.00480624
004804FE |. E8 4545F8FF call Unpacked.00404A48
00480503 |> 8D45 EC lea eax,dword ptr ss:[ebp-14]
00480506 |. E8 7542F8FF call Unpacked.00404780 ; 无关
0048050B |. 8B45 E8 mov eax,dword ptr ss:[ebp-18] ; 取中间码1“5F61585D5254”
0048050E |. E8 2D45F8FF call Unpacked.00404A40 ; 取中间码1的个数,送EAX
00480513 |. 85C0 test eax,eax ; 检测是否为空
00480515 |. 7E 53 jle short Unpacked.0048056A
00480517 |. 8945 E4 mov dword ptr ss:[ebp-1C],eax
0048051A |. C745 F0 01000000 mov dword ptr ss:[ebp-10],1
00480521 |> F645 F0 01 /test byte ptr ss:[ebp-10],1
00480525 |. 74 1E |je short Unpacked.00480545 ; 循环的奇偶次数分开计算
00480527 |. 8D45 D8 |lea eax,dword ptr ss:[ebp-28] ; 奇数次循环的计算:
0048052A |. 8B55 E8 |mov edx,dword ptr ss:[ebp-18] ; 取中间码1“5F61585D5254”
0048052D |. 8B4D F0 |mov ecx,dword ptr ss:[ebp-10] ; ECX=i*2-1(i从1开始)
00480530 |. 8A140A |mov dl,byte ptr ds:[edx+ecx] ; 取第EDX+ECX个字符
00480533 |. E8 3044F8FF |call Unpacked.00404968
00480538 |. 8B55 D8 |mov edx,dword ptr ss:[ebp-28]
0048053B |. 8D45 EC |lea eax,dword ptr ss:[ebp-14]
0048053E |. E8 0545F8FF |call Unpacked.00404A48
00480543 |. EB 1D |jmp short Unpacked.00480562
00480545 |> 8D45 D4 |lea eax,dword ptr ss:[ebp-2C] ; 偶次循环的计算:
00480548 |. 8B55 E8 |mov edx,dword ptr ss:[ebp-18] ; 取中间码1“5F61585D5254”
0048054B |. 8B4D F0 |mov ecx,dword ptr ss:[ebp-10] ; ECX=i*2(i从1开始)
0048054E |. 8A540A FE |mov dl,byte ptr ds:[edx+ecx-2] ; 取第EDX+ECX-2个字符
00480552 |. E8 1144F8FF |call Unpacked.00404968
00480557 |. 8B55 D4 |mov edx,dword ptr ss:[ebp-2C]
0048055A |. 8D45 EC |lea eax,dword ptr ss:[ebp-14]
0048055D |. E8 E644F8FF |call Unpacked.00404A48
00480562 |> FF45 F0 |inc dword ptr ss:[ebp-10]
00480565 |. FF4D E4 |dec dword ptr ss:[ebp-1C]
00480568 |.^ 75 B7 \jnz short Unpacked.00480521 ; 是否循环完成?
0048056A |> 8B45 F8 mov eax,dword ptr ss:[ebp-8] ; 假码
0048056D |. 8B55 EC mov edx,dword ptr ss:[ebp-14] ; 根据中间码1计算出的真码
00480570 |. E8 1746F8FF call Unpacked.00404B8C ; 进行比较了,呵呵
00480575 |. 75 0C jnz short Unpacked.00480583
00480577 |. 8B45 FC mov eax,dword ptr ss:[ebp-4]
--------------------------------------------------------------------------------
004804D2处为关键计算,跟进:
--------------------------------------------------------------------------------
00408F18 /$ 83FA 20 cmp edx,20
00408F1B |. 76 02 jbe short Unpacked.00408F1F
00408F1D |. 31D2 xor edx,edx
00408F1F |> 56 push esi
00408F20 |. 89E6 mov esi,esp
00408F22 |. 83EC 20 sub esp,20
00408F25 |. 51 push ecx
00408F26 |. B9 10000000 mov ecx,10 ; ECX=10
00408F2B |. E8 88FEFFFF call Unpacked.00408DB8 ; 根据单个字符计算中间码
00408F30 |. 89F2 mov edx,esi ; EDX=刚计算出的中间码
00408F32 |. 58 pop eax
00408F33 |. E8 38B9FFFF call Unpacked.00404870 ; 该中间码写入内存
00408F38 |. 83C4 20 add esp,20
00408F3B |. 5E pop esi
00408F3C \. C3 retn
--------------------------------------------------------------------------------
00408F2B处继续跟进:
--------------------------------------------------------------------------------
00408DB8 /$ 08C9 or cl,cl ; CL=10or10==10
00408DBA |. 75 17 jnz short Unpacked.00408DD3
00408DBC |. 09C0 or eax,eax
00408DBE |. 79 0E jns short Unpacked.00408DCE
00408DC0 |. F7D8 neg eax
00408DC2 |. E8 07000000 call Unpacked.00408DCE
00408DC7 |. B0 2D mov al,2D
00408DC9 |. 41 inc ecx
00408DCA |. 4E dec esi
00408DCB |. 8806 mov byte ptr ds:[esi],al
00408DCD |. C3 retn
00408DCE |$ B9 0A000000 mov ecx,0A
00408DD3 |> 52 push edx
00408DD4 |. 56 push esi
00408DD5 |> 31D2 /xor edx,edx ; EDX清零
00408DD7 |. F7F1 |div ecx ; EAX / ECX
00408DD9 |. 4E |dec esi
00408DDA |. 80C2 30 |add dl,30 ; DL=余数F+30
00408DDD |. 80FA 3A |cmp dl,3A ; 同3A比较
00408DE0 |. 72 03 |jb short Unpacked.00408DE5 ; 小于则跳
00408DE2 |. 80C2 07 |add dl,7 ; 不跳则DL=DL+7
00408DE5 |> 8816 |mov byte ptr ds:[esi],dl ; 作为本次计算结果写入内存
00408DE7 |. 09C0 |or eax,eax
00408DE9 |.^ 75 EA \jnz short Unpacked.00408DD5 ; EAX是否为0?
00408DEB |. 59 pop ecx
00408DEC |. 5A pop edx
00408DED |. 29F1 sub ecx,esi
00408DEF |. 29CA sub edx,ecx
00408DF1 |. 76 10 jbe short Unpacked.00408E03
00408DF3 |. 01D1 add ecx,edx
00408DF5 |. B0 30 mov al,30
00408DF7 |. 29D6 sub esi,edx
00408DF9 |. EB 03 jmp short Unpacked.00408DFE
00408DFB |> 880432 /mov byte ptr ds:[edx+esi],al
00408DFE |> 4A dec edx
00408DFF |.^ 75 FA \jnz short Unpacked.00408DFB
00408E01 |. 8806 mov byte ptr ds:[esi],al
00408E03 \> C3 retn
--------------------------------------------------------------------------------
很清晰的过程 :-) 现在都清楚了:
将用户名转换为大写,循环取用户名单个字符加上0xF,除以0x10,余数加0x30,同0x3A比较,小于则直接保存为本次循环的结果,大于则加7,作为结果保存;循环2次。将两次计算的结果颠倒保存,再取用户名的下个字符进行同样的计算。所以整个用户名计算出的中间码1的个数就是用户名的个数的2倍。接下来根据这个中间码1计算真正的注册码:将该串所有的奇数位和偶数位调换,结果就是真正的注册码了。看到这里,发现什么了吗?对,实际上在第1次计算中间码1的时候,不进行单个字符的计算结果调换,直接就是真码了,后面的操作完全是兜了个圈子,没有用的,呵呵,看程序吧,注册机源码(C语言):
--------------------------------------------------------------------------------
///////////////////////////////////////
// program: Keygen for InnoCustomize //
// Author : Coded by prince //
// Date : 2005.05.13 //
// E-mail : cracker_prince@163.com //
///////////////////////////////////////
#include "stdafx.h"
#include <stdio.h>
char chUserName[128] = {0};
char chKey[256] = {0};
void ConvertToCap( char *pchUserName )
{
int j = 0;
while ( pchUserName[j] != '\0' )
{
if ( pchUserName[j] >= 0x61 && pchUserName[j] <= 0x7a )
{
pchUserName[j] -= 0x20;
}
j++;
}
}
int main(int argc, char* argv[])
{
printf( "Please input your name:\n" );
scanf( "%s", chUserName );
ConvertToCap( chUserName );
int i = 0;
int j = 0;
int nRemainder = 0;
int nQuotient = 1;
while ( chUserName[i] != '\0' )
{
chUserName[i] += 0xf;
nQuotient = chUserName[i];
while ( nQuotient != 0 )
{
nRemainder = nQuotient % 0x10;
nQuotient = nQuotient / 0x10;
nRemainder += 0x30;
if ( nRemainder >= 0x3a )
{
nRemainder += 7;
}
chKey[j] = nRemainder;
j++;
}
i++;
}
printf( "Your key is: %s\n", chKey );
return 0;
}
----------------------------------------------------------------------------------------
以上程序在VC6.0+Win2K下测试通过,任何问题可至cracker_prince@163.com
菜鸟写菜文~
prince 2005.05.13
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)