飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 1976|回复: 0

[08版] 浅谈处理动态地址的一些策略

[复制链接]

该用户从未签到

发表于 2008-6-14 10:11:05 | 显示全部楼层 |阅读模式
——

    本课的目标软件为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@AF8@.00A11640

我们发现原来程序将算法CALL的地址保存在了一个指针变量[42A024]中  继续F7来到算法函数的真正入口:

00961640    64:A1 00000000       MOV EAX,DWORD PTR FS:[0]                 ; 算法CALL 01入口点
……
……
00961945    53                   PUSH EBX
00961946    68 04D19600          PUSH MBX@D34@.0096D104                   ; ASCII "%08lX"
0096194B    50                   PUSH EAX
0096194C    E8 F7200000          CALL MBX@D34@.00963A48
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@D34@.00963A10                   ; 比较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@D34@.009619FA                    ; 关键跳 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@D34@.00962AD0
……
……
009619DB    E8 F0100000          CALL MBX@D34@.00962AD0
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@AE4@.00A11640  
004258E4  - FF25 28A04200        JMP DWORD PTR DS:[42A028]               ; MBX@AE4@.00A11500
004258EA  - FF25 2CA04200        JMP DWORD PTR DS:[42A02C]               ; MBX@AE4@.00A11BA0

所以我们直接修改JMP指令也可:

004258DE    B8 01000000          MOV EAX,1
004258E3    C3                   RETN
004258E4  - FF25 28A04200        JMP DWORD PTR DS:[42A028]                ; MBX@B7C@.00A11500
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
PYG19周年生日快乐!
您需要登录后才可以回帖 登录 | 加入我们

本版积分规则

快速回复 返回顶部 返回列表