首页
社区
课程
招聘
[原创]一个内存映像校验anti-debug程序的破解之路
发表于: 2013-3-5 21:08 3237

[原创]一个内存映像校验anti-debug程序的破解之路

2013-3-5 21:08
3237

新手F7步入,老鸟F8步过,第一次写文章,有什么不妥当的地方请大家指正,谢谢。
最近在做一些破解类的题目,前面遇到的都是比较简单的crackme程序,即使遇些anti-debug手段还没有意识到的时候都被OllyDbg的插件 anti anti-debug了。这个程序是我意识到的第一个anti-debug程序,一直不知道如何下手,纠结了将近一个星期直到今天终于将其破解,特别欣慰,特此记录一下破解过程。
首先说明一下我遇到的问题:OD载入这个crackme程序,和往常一样F8一步一步的执行指令,想看一下是哪个Call指令创建了窗口。但是F8了几遍,程序都不创建窗口,或是窗口一闪而逝,接着就终止运行了,直接F9程序运行没有任何问题。这个时候尝试在任意指令处下F2断点,F9运行,程序运行效果和F8单步一样,但下内存访问断点或硬件执行断点程序都能正常运行。以前破解都没有遇到过这种问题,所以这次比较困惑,到底程序使用了什么方式来进行anti-debug的呢?后来分析发现程序运行时创建了一个线程以5000ms为周期,不停的对.text段进行CRC校验,若是与事先计算好的校验值不同则调用ExitProcess函数退出。具体原因由于OD的断点机制,会将断点处的指令替换成CC即INT3指令,所以只要有F2断点或者F8单步时都会造成.text的CRC校验值的不同。
程序创建线程的代码段如下:
004011F9    56              PUSH ESI
004011FA    A3 24304000     MOV DWORD PTR DS:[403024],EAX
004011FF    FFD7            CALL EDI
00401201    6A 00           PUSH 0                                   ; pThreadId
00401203    6A 00           PUSH 0                                   ; CreationFlags
00401205    6A 00           PUSH 0                                   ; pThreadParm
00401207    68 B0104000     PUSH CrackMe.004010B0                    ; ThreadFunction
0040120C    6A 00           PUSH 0                                   ; StackSize
0040120E    6A 00           PUSH 0                                   ; pSecurity
00401210    A3 20304000     MOV DWORD PTR DS:[403020],EAX
00401215    FF15 08204000   CALL DWORD PTR DS:[<&KERNEL32.CreateThre>; kernel32.CreateThread
0040121B    5F              POP EDI
0040121C    B8 01000000     MOV EAX,1
00401221    5E              POP ESI
00401222    C3              RETN
线程创建后会调用004010B0处的代码执行。我们观察一下004010B0的代码,代码完成的功能见注释:
004010B0    53              PUSH EBX
004010B1    8B1D 10204000   MOV EBX,DWORD PTR DS:[<&KERNEL32.Sleep>] ; kernel32.Sleep
004010B7    55              PUSH EBP
004010B8    8B2D 14204000   MOV EBP,DWORD PTR DS:[<&KERNEL32.ExitPro>; kernel32.ExitProcess
004010BE    56              PUSH ESI
004010BF    57              PUSH EDI
004010C0    8B3D 18204000   MOV EDI,DWORD PTR DS:[<&KERNEL32.GetModu>; kernel32.GetModuleHandleA
004010C6    6A 00           PUSH 0
004010C8    FFD7            CALL EDI
004010CA    8B48 3C         MOV ECX,DWORD PTR DS:[EAX+3C]
004010CD    33D2            XOR EDX,EDX
004010CF    03C8            ADD ECX,EAX                              ; ecx 指向pe标志
004010D1    66:8B51 14      MOV DX,WORD PTR DS:[ECX+14]              ; dx指向扩展头结构的长度
004010D5    8B71 FC         MOV ESI,DWORD PTR DS:[ECX-4]             ; esi保存已经事先计算好的待对比的校验和
004010D8    8D540A 18       LEA EDX,DWORD PTR DS:[EDX+ECX+18]        ; edx指向.text段
004010DC    8B4A 08         MOV ECX,DWORD PTR DS:[EDX+8]             ; ecx存储.text段的尺寸
004010DF    8B52 0C         MOV EDX,DWORD PTR DS:[EDX+C]             ; .text节区RVA地址
004010E2    03D0            ADD EDX,EAX                              ; .text的绝对地址
004010E4    51              PUSH ECX
004010E5    52              PUSH EDX
004010E6    E8 45FFFFFF     CALL CrackMe.00401030                    ; CRC校验
004010EB    83C4 08         ADD ESP,8
004010EE    3BF0            CMP ESI,EAX                              ; 将现在计算的CRC值(eax中存放)与事先计算好的校验和对比
004010F0    75 09           JNZ SHORT CrackMe.004010FB               ; 不等则退出,相等则sleep 5000ms,然后继续校验
004010F2    68 88130000     PUSH 1388
004010F7    FFD3            CALL EBX                                 ; kernel32.Sleep
004010F9  ^ EB CB           JMP SHORT CrackMe.004010C6
004010FB    6A 01           PUSH 1
004010FD    FFD5            CALL EBP                                 ; kernel32.ExitProcess
004010FF  ^ EB C5           JMP SHORT CrackMe.004010C6
00401101    90              NOP
00401102    90              NOP
因此,为了能够F2下断点或F8单步调试,我们可以将004010F0处的JNZ SHORT CrackMe.004010FB直接nop掉,或者将00401201到00401215处的CreateThread参数入栈和函数调用全部nop掉。保存可执行文件为一个新的文件,使用OD加载这个破解后的文件,然后按照常规的方法下bp GetWindowTextA或bp EnableWindow断点进行就可以调试了。
还有一个方法更为便捷,不需要更改任何指令,只需要一个硬件执行断点。方法是:原程序载入后,在反汇编窗口右键,选择“查找”,“当前模块中的名称(标签)”快捷键CTRL+N,找到GetWindowTextA函数,右键选择“反汇编窗口中跟随输入函数”,这是反汇编窗口跳转到了77D3216B处,在这里下一个“硬件执行断点”。F9运行,ALT+F9执行到用户空间。程序中断到00401270行。
00401266    6A 0A           PUSH 0A
00401268    50              PUSH EAX
00401269    51              PUSH ECX
0040126A    FF15 64204000   CALL DWORD PTR DS:[<&USER32.GetWindowTex>; USER32.GetWindowTextA
00401270    68 10304000     PUSH CrackMe.00403010                    ; ASCII "Iceberg"
00401275    E8 96FEFFFF     CALL CrackMe.00401110                     ; 对ASCII "Iceberg"进行变换
0040127A    8D5424 08       LEA EDX,DWORD PTR SS:[ESP+8]
0040127E    8BF0            MOV ESI,EAX                              ; 将变换后的值放入esi存储
00401280    52              PUSH EDX                                 ; 将我们输入的字符串入栈
00401281    E8 BAFEFFFF     CALL CrackMe.00401140                    ; 对于我们输入的字符串进行变换
00401286    83C4 08         ADD ESP,8
00401289    3BF0            CMP ESI,EAX                              ; 比较转化后的值是否相同
0040128B    5E              POP ESI
0040128C    75 0E           JNZ SHORT CrackMe.0040129C               ; 不同则跳转,相同则调用EnableWindow使按钮可用
0040128E    A1 20304000     MOV EAX,DWORD PTR DS:[403020]
00401293    6A 01           PUSH 1
00401295    50              PUSH EAX
00401296    FF15 5C204000   CALL DWORD PTR DS:[<&USER32.EnableWindow>; USER32.EnableWindow
0040129C    81C4 00010000   ADD ESP,100
004012A2    C3              RETN
004012A3    90              NOP
004012A4    90              NOP
行00401275的call对字符串"Iceberg"进行变换运算的,F7步入观察变换步骤
00401110    8B5424 04       MOV EDX,DWORD PTR SS:[ESP+4]             ; CrackMe.00403010
00401114    33C0            XOR EAX,EAX
00401116    8A0A            MOV CL,BYTE PTR DS:[EDX]
00401118    84C9            TEST CL,CL
0040111A    74 1A           JE SHORT CrackMe.00401136
0040111C    80F9 41         CMP CL,41
0040111F    7C 15           JL SHORT CrackMe.00401136
00401121    80F9 5A         CMP CL,5A
00401124    0FBEC9          MOVSX ECX,CL
00401127    7E 03           JLE SHORT CrackMe.0040112C
00401129    83E9 20         SUB ECX,20
0040112C    03C1            ADD EAX,ECX
0040112E    8A4A 01         MOV CL,BYTE PTR DS:[EDX+1]
00401131    42              INC EDX
00401132    84C9            TEST CL,CL
00401134  ^ 75 E6           JNZ SHORT CrackMe.0040111C
00401136    35 78560000     XOR EAX,5678
0040113B    C3              RETN
具体运算我们并不关心,只关心它的运算结果,即执行完00401136后EAX的值,F4直接是程序运行到0040113B行,观察右侧寄存器EAX的值,可以看到EAX的值为00005789。
行00401281的CALL命令对我们输入的字符串进行变换,F7步入,观察变换方法

00401140    8B5424 04       MOV EDX,DWORD PTR SS:[ESP+4]
00401144    33C0            XOR EAX,EAX
00401146    8A0A            MOV CL,BYTE PTR DS:[EDX]
00401148    84C9            TEST CL,CL
0040114A    74 11           JE SHORT CrackMe.0040115D
0040114C    0FBEC9          MOVSX ECX,CL
0040114F    8D0480          LEA EAX,DWORD PTR DS:[EAX+EAX*4]
00401152    42              INC EDX
00401153    8D4441 D0       LEA EAX,DWORD PTR DS:[ECX+EAX*2-30]
00401157    8A0A            MOV CL,BYTE PTR DS:[EDX]
00401159    84C9            TEST CL,CL
0040115B  ^\75 EF           JNZ SHORT CrackMe.0040114C               
0040115D    35 34120000     XOR EAX,1234                             ; 将我们输入的字符串转化成十六进制数存放在EAX中并与1234进行异或
00401162    C3              RETN
F4直接运行到0040115D行,可以观察到eax的值为000045BD,即我们输入的密码17853的十六进制表示形式。F8使函数返回,到00401286,F8一步,到00401289。由行0040115D的注释可以知道程序对我们输入的字符串转化成十六进制数并与1234进行异或再和"Iceberg"进行变换运算后的值00005789比较,不等则跳转,相等则调用EnableWindow使“注册成功”按钮可用,因此只要将5789与1234进行异或再转换成十进制就能得到正确的密码了。转换使用的是OD带的破解辅助计算工具,很容易就能计算出密码为17853.到此对这个crackme的分析全部结束。
在此要特别感谢scusword对我的帮助,谢了曾哥
本文使用的crackme在附件中。


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 6
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//