- UID
- 2198
注册时间2005-6-29
阅读权限255
最后登录1970-1-1
副坛主
该用户从未签到
|
——
本课的目标软件为Ultra Video Converter,,07版教程我们也有讨论过该程序的爆破,程序现在已更换了加密体系,并使用了 捆 绑 壳:MoleBox V2.3X -> MoleStudio.com 。可惜算法上设计为明码比较/:L ,我们将其视为非明码的软件来讨论不脱壳暴破使用MoleBox壳保护的程序。
我们随意输入注册码,下BP MessageBoxA,中断后返回到这里,我们向上找到关键跳转:
00413375 6A 40 PUSH 40
00413377 68 94434300 PUSH Ultra_Vi.00434394 ; ASCII "Sorry"
0041337C 68 6C434300 PUSH Ultra_Vi.0043436C ; ASCII "Invalid license name or license code"
00413381 8BCB MOV ECX,EBX
00413383 E8 202B0100 CALL Ultra_Vi.00425EA8 ; JMP 到 MFC42.#4224_CWnd::MessageBoxA
00413388 68 04040000 PUSH 404 ; 返回到这里
向上来到 004131EC 处并下断:
004131EC E8 ED260100 CALL Ultra_Vi.004258DE ; 这里进入算法CALL 01
004131F1 83C4 08 ADD ESP,8
004131F4 85C0 TEST EAX,EAX
004131F6 75 22 JNZ SHORT Ultra_Vi.0041321A ; EAX=1 则判断注册成功
004131F8 8B45 00 MOV EAX,DWORD PTR SS:[EBP]
004131FB 8B0E MOV ECX,DWORD PTR DS:[ESI]
004131FD 50 PUSH EAX
004131FE 51 PUSH ECX
004131FF FF15 F4714300 CALL DWORD PTR DS:[4371F4] ; 算法CALL 02
00413205 83C4 08 ADD ESP,8
00413208 85C0 TEST EAX,EAX
0041320A 0F84 65010000 JE Ultra_Vi.00413375 ; 对算法CALL 02返回数值做判断
00413210 C705 147D4300 010000>MOV DWORD PTR DS:[437D14],1 ; 若EAX=0 则向[437D14]赋值为1
0041321A 56 PUSH ESI
说明:该程序使用了两种算法机制,可能是为了照顾早期注册的用户吧,这里我们仅讨论针对算法CALL 01的爆破。[437D14]这个变量大家可以注意一下,它用于判断程序实现注册走的是哪条路线,[437D14]=1则代表CALL 02验证通过。
我们在004131EC处下断,点注册中断后,F7跟进来到这里:
004258DE - FF25 24A04200 JMP DWORD PTR DS:[42A024] ; MBX@[email protected]
我们发现原来程序将算法CALL的地址保存在了一个指针变量[42A024]中 继续F7来到算法函数的真正入口:
00961640 64:A1 00000000 MOV EAX,DWORD PTR FS:[0] ; 算法CALL 01入口点
……
……
00961945 53 PUSH EBX
00961946 68 04D19600 PUSH MBX@[email protected] ; ASCII "%08lX"
0096194B 50 PUSH EAX
0096194C E8 F7200000 CALL MBX@[email protected]
00961951 8B9424 B4030000 MOV EDX,DWORD PTR SS:[ESP+3B4] ; 假码地址送EDX
00961958 8D8C24 94000000 LEA ECX,DWORD PTR SS:[ESP+94] ; 真码地址送ECX
0096195F 6A 08 PUSH 8
00961961 51 PUSH ECX
00961962 52 PUSH EDX
00961963 E8 A8200000 CALL MBX@[email protected] ; 比较CALL 若相等则返回EAX=1
00961968 83C4 18 ADD ESP,18
0096196B 85C0 TEST EAX,EAX
0096196D 5F POP EDI
0096196E 5E POP ESI
0096196F 5D POP EBP
00961970 5B POP EBX
00961971 0F85 83000000 JNZ MBX@[email protected] ; 关键跳 EAX=1 则跳转
00961977 8D4C24 30 LEA ECX,DWORD PTR SS:[ESP+30]
0096197B C68424 8C030000 09 MOV BYTE PTR SS:[ESP+38C],9
00961983 E8 48110000 CALL MBX@[email protected]
……
……
009619DB E8 F0100000 CALL MBX@[email protected]
009619E0 B8 01000000 MOV EAX,1 ; 验证KEY成功,该函数返回EAX=1
009619E5 8B8C24 84030000 MOV ECX,DWORD PTR SS:[ESP+384]
009619EC 64:890D 00000000 MOV DWORD PTR FS:[0],ECX
009619F3 81C4 90030000 ADD ESP,390
009619F9 C3 RETN
很显然,将地址 00961971 处的 JNZ 修改为 JZ 即可实现爆破。我们修改跳转使程序保存我们输入的注册信息,KEY保存在程序目录下的data.ini文件中:
[Register]
License Name=Nisy
License Code=Bbs.ChinaPYG.CoM
然后运行程序,发现仍有注册提示,说明软件启动时还有检测,OD载入,我们先执行到程序OEP:
///////////////////////
0044DB23 > E8 00000000 CALL Ultra_Vi.0044DB28 ; OD载入后停到这里 F7一次
0044DB28 60 PUSHAD
0044DB29 E8 4F000000 CALL Ultra_Vi.0044DB7D ; F7一次
///////////////////////
0044DB7D E8 6EFBFFFF CALL Ultra_Vi.0044D6F0 ; F7一次
///////////////////////
0044D6F0 E8 EBFBFFFF CALL Ultra_Vi.0044D2E0 ; F7后来到这里
0044D6F5 58 POP EAX
0044D6F6 E8 55070000 CALL Ultra_Vi.0044DE50
0044D6FB 58 POP EAX
0044D6FC 894424 24 MOV DWORD PTR SS:[ESP+24],EAX
0044D700 61 POPAD
0044D701 58 POP EAX
0044D702 58 POP EAX
0044D703 FFD0 CALL EAX ; 在这里下断 F7后就可到达程序OEP
0044D705 E8 B0C50000 CALL Ultra_Vi.00459CBA
///////////////////////
004260E8 55 PUSH EBP ; 程序OEP
004260E9 8BEC MOV EBP,ESP
///////////////////////
我们在算法CALL处下断(该程序启动时和注册时的算法CALL是相同的)并运行,修改关键跳,程序显示已注册,测试其功能可用。于是用DUP制作Loader,但运行Loader时却出现了问题:
我们重新打开OD,再次载入该程序来分析原因,发现算法CALL的地址变了 /:017 看来算法CALL的地址被M壳做了保护,基地址动态生成。通过前面的分析,我们得知算法CALL的地址保存在指针变量[0042A024]中,所以我们OD重新载入程序,对该内存地址下硬件写入断点:
下硬断后F9运行行来到这里:
004548ED FF15 28174600 CALL DWORD PTR DS:[461728] ; kernel32.GetProcAddress
004548F3 8B4D E0 MOV ECX,DWORD PTR SS:[EBP-20] ; 将指针变量地址送入ECX
004548F6 8901 MOV DWORD PTR DS:[ECX],EAX ; 将EAX中保存的地址送[ECX]中
004548F8 EB 2C JMP SHORT Ultra_Vi.00454926
原来在壳段中实现了算法CALL的动态赋值,那我们等他赋值后再来定位关键跳不就可以了吗,我们用SMC技术来验证该假设:
在壳区段的这里Patch代码;
0044D700 61 POPAD ; 修改为 JMP 0044D859
0044D701 58 POP EAX
0044D702 58 POP EAX
0044D703 FFD0 CALL EAX ; 在这里下断 F7后就可到达程序OEP
0044D705 E8 B0C50000 CALL Ultra_Vi.00459CBA
SMC代码如下:
0044D859 8B1D 24A04200 MOV EBX,DWORD PTR DS:[42A024] ; 将指针变量的数据送EBX
0044D85F 66:C783 31030000 0F8>MOV WORD PTR DS:[EBX+331],840F ; 利用EBX来间接找到关键跳的地址
0044D868 61 POPAD ; 还原覆盖的代码
0044D869 58 POP EAX
0044D86A 58 POP EAX
0044D86B FFD0 CALL EAX
0044D86D ^ E9 93FEFFFF JMP Ultra_Vi.0044D705
注:若算法CALL入口地址为00A11972时候,关键跳的地址为00A11640,所以可求出关键跳到算法CALL入口点的地址:00A11971 - 00A11640 = 331,即其偏移值是一个定值的。我们通过[EBX+331]即可定位到关键跳的地址。
由于程序作者加壳时选择了对壳的CRC效检,所以无法直接运行使用SMC修改后的程序,但我们可以使用DUP来制作Loader来跳过壳的CRC效检 o(∩_∩)o...
地址0044D85F处指令访问的内存无法写入,该地址就是我们SMC的代码:MOV WORD PTR DS:[EBX+331],840F ,看来M壳不仅对算法CALL的地址做了动态处理,还禁止对该区段进行写操作。我们定位动态地址的思路有了,如何自己编写Loader来修改内存中数据就属于编程要解决的问题,我们不做深入讨论。这里我们仅利用现有工具(DUP)和学习到的爆破方法来讨论解题思路。
既然算法CALL部分被M壳进行了保护,但程序代码段的地址是固定的,是可以进行写操作的 :-),我们之所以调用算法CALL,就是需要得到算法CALL返回的数据来进行判断程序是否注册,那我们第二种思路就有了,程序何必调用算法CALL呢,我们直接赋值不就KO了。有了猜想,下面我们来进行验证:
由于程序启动时要调用算法CALL,所以我们在算法CALL下断(只要OD不关闭,可以认为程序的算法CALL地址为一个定值):
0041ACDD E8 FCAB0000 CALL Ultra_Vi.004258DE ; 启动时调用算法CALL
0041ACE2 BB 01000000 MOV EBX,1
0041ACE7 83C4 14 ADD ESP,14
0041ACEA 3BC3 CMP EAX,EBX
0041ACEC 8985 C4000000 MOV DWORD PTR SS:[EBP+C4],EAX
0041ACF2 75 05 JNZ SHORT Ultra_Vi.0041ACF9
0041ACF4 E8 F1AB0000 CALL Ultra_Vi.004258EA ; 该CALL读取注册文件 并再次调用算法CALL
通过分析,发现程序启动时有两次对KEY的验证,于是我们将这两处调用 CALL 004258DE 和 CALL 004258EA 修改为 MOV EAX,1。修改后程序显示注册,但一点转化功能程序就自动退出。 Why? 我们一猜就可以想到,当我们使用转化功能时,程序调用了算法CALL进行了判断。 我们再来验证一下我们的猜测:
对算法CALL下断,或者使用 Ctrl+S 来搜索 CALL 004258DE 和 CALL 004258EA 这两句指令(Ctrl+L搜索下一处)并对所有地址下断,发现当我们使用程序转化功能时,CALL 004258EA 还有三次调用,哦,天!多浪费执行效率,所以我们都MOV EAX,1。一共需要修改五处,如果直接在DUP中添加数据的话,会很麻烦,所以我们还制作成SMC补丁,然后使用DUP的文件对比来生成Loader:
0044D859 61 POPAD
0044D85A 58 POP EAX
0044D85B 58 POP EAX
0044D85C C705 DDAC4100 B8010000 MOV DWORD PTR DS:[41ACDD],1B8
0044D866 C705 F4AC4100 B8010000 MOV DWORD PTR DS:[41ACF4],1B8
0044D870 C705 9D0C4200 B8010000 MOV DWORD PTR DS:[420C9D],1B8
0044D87A C705 00CF4100 B8010000 MOV DWORD PTR DS:[41CF00],1B8
0044D884 C705 A0CE4100 B8010000 MOV DWORD PTR DS:[41CEA0],1B8
0044D88E FFD0 CALL EAX
0044D890 ^ E9 70FEFFFF JMP Ultra_Vi.0044D705
我们继续思考,由于这几处CALL都需要先执行JMP [内存地址]来对算法CALL寻址,而JMP指令也属于代码段:
004258DE - FF25 24A04200 JMP DWORD PTR DS:[42A024] ; MBX@[email protected]
004258E4 - FF25 28A04200 JMP DWORD PTR DS:[42A028] ; MBX@[email protected]
004258EA - FF25 2CA04200 JMP DWORD PTR DS:[42A02C] ; MBX@[email protected]
所以我们直接修改JMP指令也可:
004258DE B8 01000000 MOV EAX,1
004258E3 C3 RETN
004258E4 - FF25 28A04200 JMP DWORD PTR DS:[42A028] ; MBX@[email protected]
004258EA B8 01000000 MOV EAX,1
004258EF C3 RETN
修改的字节数正好6个字节,同源代码长度吻合 :-) 我们使用DUP添加代码来生成Loader:
下面我们继续分析,未注册版只允许转化文件的50%,我们是否可以直接从功能上实施爆破呢?我们再来回顾一下程序验证的一般流程。启动时调用算法CALL检测程序是否注册,一般会将算法CALL的返回值保存到一个全局变量中,当我们使用其功能时对该全局变量进行判断即可。于是我们在程序启动时下断:
0041ACDD E8 FCAB0000 CALL Ultra_Vi.004258DE ; 这里调用算法CALL
0041ACE2 BB 01000000 MOV EBX,1
0041ACE7 83C4 14 ADD ESP,14
0041ACEA 3BC3 CMP EAX,EBX
0041ACEC 8985 C4000000 MOV DWORD PTR SS:[EBP+C4],EAX ; 算法CALL返回的数据保存在[EBP+C4]中
所以我们在[EBP+C4]的地址处下硬件访问断点,添加一个文件并点击转化,中断到这里:
0041CE28 8B87 C4000000 MOV EAX,DWORD PTR DS:[EDI+C4]
0041CE2E 85C0 TEST EAX,EAX
0041CE30 75 5D JNZ SHORT Ultra_Vi.0041CE8F
我们修改代码,实现对全局变量的赋值:
0041CE28 C687 C4000000 01 MOV BYTE PTR DS:[EDI+C4],1 ; 这里将全局变量赋值为1
0041CE2F 90 NOP
0041CE30 EB 5D JMP SHORT Ultra_Vi.0041CE8F ; 这里修改为JMP
从上文的分析中我们得知,视频功能有三次对算法CALL的调用,我们又发现调用算法CALL前先检测[437D14]地址的数值
0041CE8F A1 147D4300 MOV EAX,DWORD PTR DS:[437D14] ; 注意[437D14]的数值
0041CE94 85C0 TEST EAX,EAX
0041CE96 74 08 JE SHORT Ultra_Vi.0041CEA0 ; 这里修改为 74 0D 即跳过算法CALL
0041CE98 FF15 F0714300 CALL DWORD PTR DS:[4371F0] ; MBX@70_1.00A42190
0041CE9E EB 05 JMP SHORT Ultra_Vi.0041CEA5
0041CEA0 E8 458A0000 CALL Ultra_Vi.004258EA
0041CEA5 8D4C24 14 LEA ECX,DWORD PTR SS:[ESP+14]
我们对[437D14]下断即可快速定位其余两处算法CALL,细心的朋友一定会问为何程序要判断这个地址呢?我们最初分析过,程序有两个算法体系,无论通过哪一种算法都可以实现注册,我们观察下这里的代码,如果EAX=1,则执行CALL DWORD PTR DS:[4371F0],即走判断算法CALL 02的路线,如果EAX=0,则走算法CALL 01的路线。所以我们可以推断出,该地址保存的数值就是用来判断启动时KEY是在哪种算法CALL中通过的验证。一个设计上的小把柄,只要我们细心就可以变成我们分析中的一条线索。 剩下的两处对算法CALL的调用修改同上,大家自行研究一下。如果大家有时间也可以来分析该程序的第二套算法体系,也欢迎对动态地址的处理一起讨论。
我们今天讨论这款软件的目的就在扩展大家的破解思路,程序是明码比较,但如果不是明码呢,我们应该如何入手?所以学习解密不要局限于结果,要重视过程,要从不同的角度来剖析程序的加密体系,寻找突破口、研究对策,这样我们分析一款程序时才会有更多的收获和提高。
本文仅做抛砖引玉,在 VM 当道的今天(不过当下貌似VM更多的用于木马的保护中 ^_^),不少程序的算法部分采用了VM保护,分析VM保护的代码是十分艰难,所以我们更要重视如何不通过修改算法CALL来实现程序的爆破。我们在设计软件的加密体系时,千万不要认为采用了VM保护就可以万事大吉。一个严密的加密体系,不仅要设计出优秀的算法(或保护算法代码),还要重视和做好防爆破等细节的处理。 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?加入我们
x
|