首页
社区
课程
招聘
一个有着简单解码过程的Crackme分析
发表于: 2006-7-28 09:57 6922

一个有着简单解码过程的Crackme分析

2006-7-28 09:57
6922

看大家最近破CrackMe破得那么欢,我不出手是否有点跟不上形势了呢

下载地址:http://www.crackmes.de/users/ox87k/cry0k/

使用工具:IDA

  这是个汇编写的Crackme,用OllyDbg无法加载,一加载就“非法操作”(是的,仅仅是加载,还没
按F9键运行,马上就报非法操作然后退出OD,找了半天没找到抗OD的地方在哪里,另外笔者对于反加载
的原理也不太了解,望这方面的高人指导是荷)。用IDA加载以后停在入口点处:

================================================================
.0x87k:0041F000 start proc near
.0x87k:0041F000 push    ecx
.0x87k:0041F001 push    edx
.0x87k:0041F002 push    ebx
.0x87k:0041F003 push    esp
.0x87k:0041F004 push    ebp
.0x87k:0041F005 push    esi
.0x87k:0041F006 push    edi
.0x87k:0041F007 jmp     short loc_41F030

      ; 一些垃圾字节

.0x87k:0041F030 loc_41F030:                             ; CODE XREF: start+7j
.0x87k:0041F030 xor     eax, eax
.0x87k:0041F032 mov     eax, 0BFE000h
.0x87k:0041F037 add     eax, 666h
.0x87k:0041F03C mov     ecx, 0FFF666h
.0x87k:0041F041 xor     eax, ecx                        ; eax == 401000h
.0x87k:0041F043 mov     edi, eax
.0x87k:0041F045
.0x87k:0041F045 loc_41F045:                             ; CODE XREF: start+65j
.0x87k:0041F045 xor     ecx, ecx                        ; 解码器
.0x87k:0041F047 movzx   ecx, byte ptr [edi]
.0x87k:0041F04A add     cl, 3
.0x87k:0041F04D xor     cl, 8Ah
.0x87k:0041F050 add     cl, 2
.0x87k:0041F053 xor     cl, 6Fh
.0x87k:0041F056 add     cl, 1
.0x87k:0041F059 xor     cl, 0CCh
.0x87k:0041F05C mov     [edi], cl
.0x87k:0041F05E inc     edi
.0x87k:0041F05F cmp     edi, offset sub_401434
.0x87k:0041F065 jl      short loc_41F045                ; 解码器
.0x87k:0041F067 mov     eax, edi
.0x87k:0041F069 sub     eax, 433h                       ; sub eax, 434h
.0x87k:0041F06E dec     eax
.0x87k:0041F06F jmp     short loc_41F081                ; 检测调试器

      ; 一些垃圾字节

.0x87k:0041F081 loc_41F081:                             ; CODE XREF: start+6Fj
.0x87k:0041F081 mov     ecx, large fs:18h               ; 检测调试器
.0x87k:0041F088 mov     ecx, [ecx+30h]
.0x87k:0041F08B movzx   ecx, byte ptr [ecx+2]
.0x87k:0041F08F dec     ecx
.0x87k:0041F090 jl      short loc_41F0FC                ; 无调试器则跳

      ; 一些垃圾字节,当作代码执行就会非法操作,意图明显是抗调试的

.0x87k:0041F0FC loc_41F0FC:                             ; CODE XREF: start+90j
.0x87k:0041F0FC pop     edi
.0x87k:0041F0FD pop     esi
.0x87k:0041F0FE pop     ebp
.0x87k:0041F0FF pop     esp
.0x87k:0041F100 pop     ebx
.0x87k:0041F101 pop     edx
.0x87k:0041F102 pop     ecx
.0x87k:0041F103 jmp     eax
.0x87k:0041F103 start endp
================================================================

入口点的地址和区段名都不寻常,但是这不要紧,当你看到eax == 401000h这里应该眼前一亮吧!这不
就是大多数程序的代码区段默认起始地址吗!然后接下来的这小段一看就知道是在给地址为401000到
401434的代码段解码,呵呵,为什么一看就知道呢?因为它让我想到几个名词:病毒,多态变形……扯
远了吧,因为那几个异或操作太典型了,如果你的病毒用这种操作进行编码加密的话,绝对只有被杀毒
程序秒杀的份。

  运行IDA中附带的调试器,让这段代码解码完毕,等EIP到达41F06F时把它重置到41F0FC,再执行几
句,就到达程序的真正入口点了。(据说可以用IDC脚本命令模拟这段解码的过程,可惜我不会用。)
入口点附近一如既往的可以看到GetModuleHandle,DialogBoxParam和ExitProcess这些函数,从中找出
对话框过程的指针并依此找到对话框过程,进入到处理WM_COMMAND消息的分支,来到:

================================================================
.text:004010BC loc_4010BC:                             ; CODE XREF: .text:004010B3j
.text:004010BC cmp     byte_406134, 0
.text:004010C3 jz      short loc_4010CC
.text:004010C5 mov     byte_406134, 0                  ; 停止校验和的线程,因为下面要改写代码段
.text:004010CC
.text:004010CC loc_4010CC:                             ; CODE XREF: .text:004010C3j
.text:004010CC push    32h
.text:004010CE push    offset byte_406089
.text:004010D3 push    68h                             ; 序列号输入框ID
.text:004010D5 push    dword ptr [ebp+8]
.text:004010D8 call    loc_4013FE                      ; GetDlgItemText
.text:004010DD cmp     eax, 0Fh                        ; 正确序列号必须是15字符
.text:004010E0 jnz     short loc_401127
.text:004010E2 mov     edi, offset loc_401111
.text:004010E7 xor     ecx, ecx
.text:004010E9 xor     edx, edx
.text:004010EB
.text:004010EB loc_4010EB:                             ; CODE XREF: .text:004010FEj
.text:004010EB movzx   ecx, byte_406089[edx]
.text:004010F2 xor     cl, 87h
.text:004010F5 add     cl, 1                           ; 对输入内容进行解码
.text:004010F8
.text:004010F8 loc_4010F8:                             ; 写入到下面nop组成的空白区
.text:004010F8 mov     [edi], cl
.text:004010FA inc     edx
.text:004010FB inc     edi
.text:004010FC cmp     edx, eax
.text:004010FE jnz     short loc_4010EB
.text:00401100 mov     edi, offset loc_401111
.text:00401105 mov     cl, [edi]
.text:00401107 cmp     cl, 68h
.text:0040110A jz      short loc_401111
.text:0040110C cmp     cl, 90h
.text:0040110F jnz     short loc_401120
.text:00401111
.text:00401111 loc_401111:                             ; CODE XREF: .text:0040110Aj
.text:00401111                                         ; DATA XREF: .text:004010E2o ...
.text:00401111 nop
.text:00401112 nop
.text:00401113 nop
.text:00401114 nop
.text:00401115 nop
.text:00401116 nop
.text:00401117 nop
.text:00401118 nop
.text:00401119 nop
.text:0040111A nop
.text:0040111B nop
.text:0040111C nop
.text:0040111D nop
.text:0040111E nop
.text:0040111F nop
.text:00401120
.text:00401120 loc_401120:                             ; CODE XREF: .text:0040110Fj
.text:00401120 pop     esi
.text:00401121 pop     edi
.text:00401122 pop     ebx
.text:00401123 leave
.text:00401124 retn    10h
.text:00401127
.text:00401127 loc_401127:                             ; CODE XREF: .text:004010E0j
.text:00401127 push    offset aInsertARightSer         ; "Insert a right serial ;)"
.text:0040112C push    6Ah                             ; 信息显示框ID
.text:0040112E push    dword ptr [ebp+8]
.text:00401131 call    loc_40141C                      ; SetDlgItemText
================================================================

这里只能找到显示“序列号不正确”信息的代码,却没有显示“序列号正确”的,这是为什么呢?查看
一下数据区段(起始地址在406000处)就会发现“正确信息”还是存在的,而且就在偏移量等于406000
的地方,但是却没有任何指向它的引用。再看看401111到40111F这个地方,为什么是一片空白的nop指
令呢?按理说这个地方本应该是“显示正确信息”的代码才合理呀!再结合4010EB开始的这个解码循环
(又是典型的异或操作,一看就知道是在解码!),程序的意图就明朗化了:把输入的序列号(字符编
码)当作加密过的机器指令序列进行解码,解码后写入到401111开始的空白区,如果输入的是正确的序
列号,解码完毕以后应该变成恰当的指令序列,使得“序列号正确”信息的字符串能够被访问到,并且
将其在信息显示框中显示出来。

  让我们考虑这片空白区中都能填入什么,最自然的是填入一个完整的SetDlgItemText调用:

00401111      68 00604000       push 00406000             ; 406000 = offset szInfoSerialIsRight
00401116      6A 6A             push 6A
00401118      FF75 08           push dword ptr [ebp+8]    ; [ebp+8] = Arg1 = hWnd
0040111B      E8 FC020000       call 0040141C
00401120      ....

因此,机器码是:

             68 00 60 40 00 6A 6A FF 75 08 E8 FC 02 00 00

算一下正好15个字节,刚好填满空白区。但这不是唯一的答案,因为注意到下面显示“序列号错误”用
的也是同一个函数调用,只有最后一个参数不同,我们完全可以在将“正确信息”的部分参数入栈后跳
到下面那个调用的相应位置,“借用”该调用的剩余部分。所以答案也可以是:

00401111      68 00604000       push 00406000
00401116      EB 14             jmp  0040112C
...

或者是:

00401111      68 00604000       push 00406000
00401116      6A 6A             push 6A
00401118      EB 14             jmp  0040112E
...

甚至还可以是:

00401111      68 00604000       push 00406000
00401116      6A 6A             push 6A
00401118      40                inc  eax
00401119      48                dec  eax
0040111A      EB 12             jmp  0040112E
...

(又想到了一个词:多态变形……奇怪,为什么我最近总是想到这个词)注意:jmp指令后剩余的空白
区填入什么已经不重要了,哪怕不能当作代码执行也不要紧,因为它们终归会被跳过,这些余下的空白
区实际上是可以任意填写的。总之答案的数目可以相当多,我们只以第一个为例,解码操作是字节先与
87h异或再加1,那么加密操作就应该是先减1再与87h异或,因此:

68 00 60 40 00 6A 6A FF 75 08 E8 FC 02 00 00
67 FF 5F 3F FF 69 69 FE 74 07 E7 FB 01 FF FF 逐字节减1
E0 78 D8 B8 78 EE EE 79 F3 80 60 7C 86 78 78 与87h异或――得到序列号

结果中有许多字符已经不是可显示字符了,象编码等于E0的字符甚至连ASCII字符都不是,我不知道这
种字符在文本输入框中应该怎么打。

  顺带说一下:对这个CrackMe还是以静态分析为主,调试器只起到辅助解码代码段的作用。不要尝
试在主要代码区域下断点或者修改字节之类的操作,因为程序在对话框初始化的时候建了四个线程,第
一个线程循环扫描主要代码区域是否有Int3断点,第二个线程循环检测建立第一个线程的call指令是否
被修改过,第三个线程计算初始化时主要代码段的双字校验和,第四个线程循环计算程序运行期间的校
验和是否与初始值相等,这四个线程中除了第三个在计算一遍完毕以后就退出以外,其他三个都是常驻
的(当然在写入上述空白区域之前要先让第四个线程退出,否则体现程序正常功能的写入操作也将被视
为企图打补丁),只要让它们中任何一个发现不对劲,马上就向主窗口发送关闭消息。当然原理上说是
可以把建立这些线程的代码全部刷白,不过我连抗OD的部分都没找到,对于哪些地方该刷,心里也很没
谱。

  按照惯例本来应该把这个程序逆向出源代码来的,只是程序中似乎有声音资源,处理这个资源的代
码远比主要代码繁杂,不是一时半会能搞定的,所以只能先干正事了。


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 7
支持
分享
最新回复 (7)
雪    币: 47147
活跃值: (20450)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
2
文章不错,CrackMe转份本地
上传的附件:
2006-7-28 10:15
0
雪    币: 461
活跃值: (93)
能力值: ( LV9,RANK:1170 )
在线值:
发帖
回帖
粉丝
3
好文,就是有点看不懂,下载收藏!
2006-7-29 09:30
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
新来的,有些不明白。。。。。。。。。
2006-7-29 13:20
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
学习中。。。。。。
2006-7-29 20:37
0
雪    币: 203
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
idc脚本:)

auto from, x,i;
from =0x401000;
for (i=0 ;i<434;i=i+1)
{
x=Byte(from);
x=x+0x3;
x=x^0x8A;
x=x+0x2;
x=x^0x6f;
x=x+0x1;
x=x^0xcc;
PatchByte(from,x);
from = from+1;
}
2006-7-30 17:39
0
雪    币: 433
活跃值: (176)
能力值: ( LV13,RANK:1250 )
在线值:
发帖
回帖
粉丝
7
最初由 hren 发布
idc脚本:)

auto from, x,i;
from =0x401000;
for (i=0 ;i<434;i=i+1)
........


谢谢,让我看看
2006-7-30 19:54
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
好复杂啊,收藏先
2006-7-31 23:19
0
游客
登录 | 注册 方可回帖
返回
//