【原创】麦克疯 简单破解过程 【申请邀请码】
-------------------------------
软件名称: 麦克疯
版本号: 官方版本号 v6 下载日期 3.18 2010
下载地址: http://51mike.com
发布日期: 4.2.2010
作者 sippey
Email sippey@163.com
目的 : GF的要求。。。 汗
破解目标: 免插播广告,破解45秒拨号限制,解密被加密的WMV文件
工具软件: OllyDBG (很老的版本)
IDA
Winhex
FileMon
Reshacker
-------------------------------------
麦克疯是一个用于网络K歌的软件。比起其他的单机版K歌软件的优点在于
歌库可以从网上下载,不需要花钱或者让朋友copy歌库,而且歌库也能常用
常新。MTV的视频质量比单机版的差一点,不过自己用用也可以了,尤其是接在
电视上面,基本看不出来。 至于用户上传自己作品之类的功能,感觉没什么用。
没有破解的时候,注册的免费用户只能点播部分免费曲目,其他的大部分会在歌曲
之间插播广告。而且会有45秒播放限制,还有banner提示45秒显示。播放到45秒自动停止播放,提示是否要
注册VIP用户。简单的用FileMon探测,发现缓冲的文件存放在
%USERPROFILE%\Local Settings\Application Data\KaraKing\MediaCache (好像因版本不同而异)处,
文件类型WMV(据说还可能有其他格式 不过我没下载几首所以没有找到)。
使用暴风影音播放文件,提示文件损坏。 Winhex 打开缓存的WMV,发现文件头非标准WMV文件头,
怀疑加密。
PEID 查看主程序GameHall.exe, VC6编译,没壳。 哈哈,比较熟。
运行程序,用Spy++看一下,发现很多都是用网页控件搞定的,视频播放功能是用WMP和Flash的控件。
事实上整个程序就是IE控件嵌入到EXE里面,怀疑作者一开始想用Web搞定所有东东,弄纯网页版。
后来出现技术或安全等方面的考虑,就转而改用EXE。另外一种可能性就是作者是做网页功夫不错,想法也很奇特,所以
剑走偏锋就写出这样的程序。
主观分析到此结束。开工。
既然文件缓存是加密的,想要播放必然有两种可能。 1.播放时解密到一个临时文件,再播放。2.另一种是
直接写个自己的DShow filter,抢在WMV decoder filter之前先把文件流解密。
第一种方法简单,不过在运行过程中文件系统里会有完全解密的文件,第二种比较复杂,不过感觉加密效果比较
好。先猜作者用的第一种。
思路很简单,OllyDBG启动程序,注意要忽略所有的内存异常,直接加到调试设置中的忽略选项里面。
待程序已经准备好,并且下载完成一首MTV之后。bp CreateFileA/W,点击播放列表里面一个已经
缓冲好的mtv。 这样会断下几次,因为程序使用了IE控件,所以会有读网页缓存,cookie之类的操作。直到断在
一个MTV缓存文件夹中的文件。这个时候,程序已经准备好打开加密的文件并解密了。查看Call Stack
调用堆栈
ESP 返回地址 例程 / 参数 调用来自 Frame
00FA80C4 0053279C kernel32.CreateFileA GameHall.00532796 00FA810C
00FA80C8 02FADE81 FileName = "c:\documents and settings\xxxxx\local settings\application data\karaking\mediacache\1000025726noedxa.wmv"
00FA80CC 80000000 Access = GENERIC_READ
00FA80D0 00000003 ShareMode = FILE_SHARE_READ|FILE_SHARE_WRITE
00FA80D4 00FA80F0 pSecurity = 00FA80F0
00FA80D8 00000003 Mode = OPEN_EXISTING
00FA80DC 00000080 Attributes = NORMAL
00FA80E0 00000000 hTemplateFile = NULL
00FA8110 0052C2DC ? GameHall.005325E3 GameHall.0052C2D7
00FA813C 00524662 GameHall.0052C198 GameHall.0052465D 00FA8138
00FA8158 00524681 GameHall.00524641 GameHall.fopen+0A
00FA8168 00502758 00524672 <GameHall.fopen> GameHall.00502753
00FA816C 02FADE81 path = "c:\documents and settings\xxxxx\local settings\application data\karaking\mediacache\1000025726noedxa.wmv"
00FA8170 005C7CF4 mode = "rb"
00FA8278 005020C1 GameHall.005026B0 GameHall.ReadAndDecryptW
00FA827C 00FA82B0 Arg1 = 00FA82B0
00FA8280 01029490 Arg2 = 01029490
00FA8284 00FA82C0 Arg3 = 00FA82C0
00FA8288 00FA82C4 Arg4 = 00FA82C4
01029388 00501EA6 <GameHall.ReadAndDecrypt> 502000 GameHall.00501EA1
0102944C 004715A3 GameHall.00501DB0 GameHall.0047159E
01029450 01029490 Arg1 = 01029490
01029454 01029470 Arg2 = 01029470
010294AC 004DE4FE <GameHall.DecryptB1> GameHall.004DE4F9
01029504 004DA039 GameHall.004DE320 GameHall.004DA034
01029518 0044D9DB GameHall.004DA020 GameHall.0044D9D6
010295E0 0046F2EA GameHall.0044D790 GameHall.0046F2E5
01029604 00561D62 包含 GameHall.0046F2EA GameHall.00561D60
01029610 005612A7 GameHall.00561D52 GameHall.005612A2
01029688 00522C1A ? GameHall.00561184 GameHall.00522C15
010296F0 0051C1C6 GameHall.00522A7F GameHall.0051C1C1 010296EC
01029700 00549D72 包含 GameHall.0051C1C6 GameHall.00549D6F 01029720
01029724 0054A209 GameHall.00549D46 GameHall.0054A204 01029720
01029728 0000046D Arg1 = 0000046D
0102972C FFFFFFFE Arg2 = FFFFFFFE
01029730 0102976C Arg3 = 0102976C
01029734 00000000 Arg4 = 00000000
01029748 0051ED8B GameHall.0054A1EE GameHall.0051ED88 01029744
0102974C 0000046D Arg1 = 0000046D
01029750 FFFFFFFE Arg2 = FFFFFFFE
01029754 0102976C Arg3 = 0102976C
01029758 00000000 Arg4 = 00000000
01029760 0051FCDF GameHall.0051ED52 GameHall.0051FCDA 01029790
01029794 3E25EDF2 Maybe GameHall.0051FCA0 ieframe.3E25EDEF 01029790
...
mshtml.3DABFE4A 01029EEC
堆栈里面的地址已经有我的一些标号了。到这里的基本思路就是把几级的Call都F2设断。然后F9让程序跑飞,回去在点击
播放重来一次。 分析调用关系。 这时候也可以用IDA静态分析,我知道IDA也有debugger不过还是OllyDBG用着舒服,
所以只能两个程序切来切去。
IDA说00502753是调用 fopen所以我们只考虑它之前的调用。 几次试验后发现在004DA020的Call,应该是关键
的一个调用。它之后调用了 004DE320。
004DE320用IDA 分析一下,用F5功能出来源码。IDA 并没有猜出来一些CString的函数,可能是部分设置有问题,
所以我自己对着这汇编的大概给他们命了名,这样就能看出来不少了。
分析代码 基本就是先确定临时文件路径然后调用DecryptB1_471500去解密。调用的参数分别是源文件的地址
和解密后文件的临时地址。
DecryptB1_471500 (471500是函数地址)
IDA看出来里面竟然用了好多std::string的东西,看来不像是一个人写的。至少不是一个
时间写的。 IDA已经分析出来大部分string函数的名称,只需回到VC的include里面找到xstring文件
看看他们到底是干什么的就行了。不是我不熟悉std::string的函数,只是这些函数很多是private的,
实在是没有见过啊。
用OllyDbg进入471500后下断 WriteFile和WriteFileEx。 F9几次,直到发现在写那个新建的临时文件。
怎么判断是否是写那个文件?CreateFileA断下的时候记得先运行到返回看看hFile的数字,也就是eax的值。
把文件名和对应的handle记下来。如果你忘了记得话,Ctrl+F2重新运行程序吧。
此时堆栈中找到501DB0。501DB0也被DecryptB1调用。看看501DB0, 发现没什么特殊,只是有一个关键的调用
call 502000。跟进。
502000一开始用_alloc_probe在stack里面分配了5xxK的内存,看样子应该是作者在函数体里面定义了缓存区。
IDA看一下502000,结构比较清楚:
1 变量初始化
2 call 5026B0 获取文件头信息
3 call 483A40 判断两种解密算法
算法1
4 打开文件
5 call 502D30 继续准备解密key
6 然后就开始循环解密 貌似我这里只会解密前一部分(256KB) 使用的解密函数是5038F0 之后就直接写到文件了。
算法2
4 打开文件
5 call 502D30 继续准备解密key
6 然后就开始循环解密 call 5038F0
中间的一些SendMessage猜想是因为这段程序跑在主线程里面,如果不这样界面可能会卡掉。
本来打算写出解密算法的,无奈算法复杂,跟了一会儿没有耐心了。 于是开始看是否有对应的加密部分,如果没有,说明
文件是在服务器端被加密的(这样想要得到原始WMV文件就要看懂解密算法),如果有说明是在客户端被加密的。
点击下载一个MTV,然后等文件快下载完的时候,设断CreateFileA/W,跟踪堆栈,这个时候很容易让OllyDBG卡死。
后来分析原因是此处断在一个下载线程里面。我的做法是迅速截下调用堆栈的图 然后慢慢分析。
更有意思的是,这个CreateFileA/W 断下的地方可以找到一个可以播放的WMV,证明服务器没有加密。 这样就好办了。
在堆栈中找到关键的一个Call, 503BA0。 用IDA分析内容
char __stdcall EnCrypt1(int strDecryptedFilename, int strEncryptedFName)
{
int v3; // edx@1
char v4; // ST23_1@1
int v5; // [sp+1Ch] [bp-98h]@1
char v6; // [sp+20h] [bp-94h]@1
int (__stdcall **v7)(char); // [sp+74h] [bp-40h]@1
int v8; // [sp+B0h] [bp-4h]@1
v8 = 0;
v5 = (int)&unk_58B950;
sub_49C510(&v7);
v7 = &off_58B94C;
v3 = *((_DWORD *)&unk_58B950 + 1);
LOBYTE(v8) = 1;
*(int *)((char *)&v5 + v3) = (int)&off_58B93C;
std__basic_ios_char_std__char_traits_char____init(&v6, 0);
v8 = 3;
StlLibFunction2_49D8A0(0);
*(int *)((char *)&v5 + *(_DWORD *)(v5 + 4)) = (int)&off_58B944;
LOBYTE(v8) = 4;
v4 = EncryptMaybe_503CF0(strEncryptedFName, strDecryptedFilename, 1, strEncryptedFName, (int)&v5, 1, 0, 524288);
*(int *)((char *)&v5 + *(_DWORD *)(v5 + 4)) = (int)&off_58B944;
LOBYTE(v8) = 5;
sub_49CA80(&v6);
LOBYTE(v8) = 0;
*(int *)((char *)&v5 + *(_DWORD *)(v5 + 4)) = (int)&off_58B93C;
v7 = &off_58B94C;
std__ios_base___ios_base(&v7);
return v4;
}
可以发现他会调用503CF0。 503CF0很长,基本上可以确定是加密部分了。不过我们已经不再关心这一点。因为我们可以直接用copyfile代替加密和解密,这样不加密的
WMV文件会保存在缓存文件夹里面。以后想拿来这些MTV做其他用途也比较方便。
分别修改471500和503BA0处的代码。 让他们等价于CopyFile.
00471500 <> /E9 09341100 jmp 0058490E ; original : push -1; push 572088
00471505 |90 nop
00471506 |90 nop
00503BA0 <> /E9 3A0D0800 jmp 005848DF ; original : push -1;push 581B89
00503BA5 |90 nop
00503BA6 |90 nop
这里是Patch 代码,放在text段最后没有用到得地方
005848DF <> 60 pushad
005848E0 56 push esi
005848E1 57 push edi
005848E2 54 push esp
005848E3 55 push ebp
005848E4 8B5C24 34 mov ebx,dword ptr ss:[esp+34] ; un crypted file
005848E8 8B4B 04 mov ecx,dword ptr ds:[ebx+4]
005848EB 90 nop
005848EC 90 nop
005848ED 90 nop
005848EE 90 nop
005848EF 90 nop
005848F0 90 nop
005848F1 90 nop
005848F2 90 nop
005848F3 90 nop
005848F4 8B5C24 38 mov ebx,dword ptr ss:[esp+38]
005848F8 8B53 04 mov edx,dword ptr ds:[ebx+4]
005848FB 6A 00 push 0 ; /FailIfExists = FALSE
005848FD 52 push edx ; |NewFileName
005848FE 51 push ecx ; |ExistingFileName
005848FF FF15 80535800 call dword ptr ds:[<&KERNEL32.CopyFileA>] ; \CopyFileA
00584905 5D pop ebp
00584906 5C pop esp
00584907 5F pop edi
00584908 5E pop esi
00584909 61 popad
0058490A C2 0800 retn 8
0058490D 90 nop
0058490E <> 60 pushad
0058490F 55 push ebp
00584910 56 push esi
00584911 57 push edi
00584912 8B5424 30 mov edx,dword ptr ss:[esp+30]
00584916 8B4C24 34 mov ecx,dword ptr ss:[esp+34]
0058491A 6A 00 push 0 ; /FailIfExists = FALSE
0058491C 51 push ecx ; |NewFileName
0058491D 52 push edx ; |ExistingFileName
0058491E FF15 80535800 call dword ptr ds:[<&KERNEL32.CopyFileA>] ; \CopyFileA
00584924 5F pop edi
00584925 5E pop esi
00584926 5D pop ebp
00584927 61 popad
00584928 33C0 xor eax,eax
0058492A 40 inc eax
0058492B C2 0800 retn 8
复制到可执行文件,保存。
然后记着删掉已经缓存的文件,执行修改后exe。以后缓存的文件就都不是加密的了,哈哈。可以在WMP或者暴风里面直接播放。
虽然破掉了加密,但是还是有45秒播放的限制。这里我是这样想的,播放结束之后,程序会把解密的WMV文件删掉,于是我等播放到35秒左右的时候 bp DeleteFileA,嘿嘿果然断下来了。
查看调用堆栈以后,发现大概最终是在在窗口消息处理函数中调用的。
跟到消息处理里面,
004551B5 . 8BCE mov ecx,esi ; switch 9
004551B7 . E8 345F0300 call GameHall.0048B0F0
004551BC . 894424 20 mov dword ptr ss:[esp+20],eax
004551C0 . 8B85 40340000 mov eax,dword ptr ss:[ebp+3440]
004551C6 . DB4424 20 fild dword ptr ss:[esp+20]
004551CA . 3D D9040000 cmp eax,4D9
004551CF . D95C24 2C fstp dword ptr ss:[esp+2C]
....
00455426 . /74 1B je short GameHall.00455443
00455428 . |D94424 2C fld dword ptr ss:[esp+2C]
0045542C . |D80D F0B05800 fmul dword ptr ds:[58B0F0]
00455432 . |D81D ECB05800 fcomp dword ptr ds:[58B0EC] ;45.0
00455438 . |DFE0 fstsw ax
0045543A . |F6C4 41 test ah,41
0045543D . |75 04 jnz short GameHall.00455443
0045543F . |B3 01 mov bl,1
00455441 . /EB 02 jmp short GameHall.00455445
00455443 > |32DB xor bl,bl
00455445 > \8D8C24 14010000 lea ecx,dword ptr ss:[esp+114]
0045544C . E8 928C0F00 call <GameHall.DerefCString>
00455451 . 8D8C24 1C010000 lea ecx,dword ptr ss:[esp+11C]
00455458 . C78424 2C040000 FFFFFF>mov dword ptr ss:[esp+42C],-1
00455463 . E8 7B8C0F00 call <GameHall.DerefCString>
00455468 . 84DB test bl,bl
0045546A . 0F84 29070000 je GameHall.00455B99
00455470 . 8BCD mov ecx,ebp
00455472 . E8 99CB0200 call <GameHall.StopPlayDeleteFile> //这里就停掉了并且删除临时文件
00455477 . E9 1D070000 jmp GameHall.00455B99 // 跳走
0045547C > 8B95 D0330000 mov edx,dword ptr ss:[ebp+33D0] ; Case 7 of switch 00452F5F
00455482 . 42 inc edx
00455483 . 8BC2 mov eax,edx
怎么能够跳过 call <GameHall.StopPlayDeleteFile>?
0045546A . 0F84 29070000 je GameHall.00455B99
这里和后边的jmp位置相同,
在浮点运算之前有一些可以跳过检测45秒的,猜想基本上就是检测是否是VIP(vIP不会停止),是否付费(缴费以后也不会停止)。
找一个最早的检测
004553C0 . 83C4 08 add esp,8
004553C3 . 85C0 test eax,eax
004553C5 75 7C jnz short GameHall.00455443
这里, 直接爆破将jnz改完jmp
复制修改到可执行文件,保存。 运行发现没有问题了。
但因为是爆破的,有些地方处理的不好,比如还是有一个提示试看45秒的banner,很心烦。 直接打开reshacker找到那个图(在位图段里面)
看出来黄色的地方是透明的。 将这个位图导出,用mspaint修改为全黄~色~图。再重新导入,覆盖原有位图。 保存。
插播广告的问题的解决途径比较诡异,因为程序很多地方基于网络,想到可能会判断字串之类的。打开字符参考,果然如此,
很多xxxx=的字串,猜想是判断返回字串的值。
断下一个与ad有关的然后跟进去
00477A29 . 68 74595C00 push offset <GameHall.str_AndMark>
00477A2E . E8 1E670D00 call <GameHall.CStrLoadString>
00477A33 . 51 push ecx
00477A34 . C64424 68 05 mov byte ptr ss:[esp+68],5
00477A39 . 8BCC mov ecx,esp
00477A3B . 896424 58 mov dword ptr ss:[esp+58],esp
00477A3F . 68 6CBA5C00 push GameHall.005CBA6C ; ASCII "noadstate="
00477A44 . E8 08670D00 call <GameHall.CStrLoadString>
00477A49 . 51 push ecx
00477A4A . 8D4424 78 lea eax,dword ptr ss:[esp+78]
00477A4E . 8BCC mov ecx,esp
00477A50 . 896424 58 mov dword ptr ss:[esp+58],esp
00477A54 . 50 push eax
00477A55 . C64424 70 06 mov byte ptr ss:[esp+70],6
00477A5A . E8 F9630D00 call <GameHall.CStringOpEq>
00477A5F . 8D4C24 2C lea ecx,dword ptr ss:[esp+2C]
00477A63 . C64424 6C 04 mov byte ptr ss:[esp+6C],4
00477A68 . 51 push ecx
00477A69 . E8 42A80300 call <GameHall.GetNamedStr>
00477A6E 83C4 10 add esp,10
00477A71 50 push eax
00477A72 8D4C24 6C lea ecx,dword ptr ss:[esp+6C]
00477A76 . C64424 64 07 mov byte ptr ss:[esp+64],7
00477A7B . E8 9C670D00 call <GameHall.AssignStr>
00477A80 . 8D4C24 20 lea ecx,dword ptr ss:[esp+20]
00477A84 . C64424 60 04 mov byte ptr ss:[esp+60],4
00477A89 . E8 55660D00 call <GameHall.DerefCString>
00477A8E . 8B5424 68 mov edx,dword ptr ss:[esp+68]
00477A92 . 52 push edx
00477A93 E8 6DC80A00 call <GameHall.atoi>
00477A98 . 8BCC mov ecx,esp
00477A9A . 896424 58 mov dword ptr ss:[esp+58],esp
00477A9E . 68 74595C00 push offset <GameHall.str_AndMark>
00477AA3 . 8986 84010000 mov dword ptr ds:[esi+184],eax
00477AA9 . E8 A3660D00 call <GameHall.CStrLoadString>
00477AAE . 51 push ecx
00477AAF . C64424 68 08 mov byte ptr ss:[esp+68],8
00477AB4 . 8BCC mov ecx,esp
00477AB6 . 896424 58 mov dword ptr ss:[esp+58],esp
这段基本是找到字串里面的..noadstate=x&..
将x 提取出来,用atoi转换为数字,然后存在一个变量里面。
将00477A93 E8 6DC80A00 call <GameHall.atoi>
改为
00477A93 33C0 xor eax,eax
00477A95 40 inc eax
00477A96 90 nop
00477A97 90 nop
得到的值将永远为1
保存到exe。
修改了这些基本就可以正常用了。只需要免费注册一个账户就可以玩了。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课