被老大一吹我就激动了,发个QU的查壳方式吧。


                                    Quick unpack查壳方式

by nbw
4 [NE365]

    很多查壳软件,原理都类似,前阵子backer同学说了一下peid的查壳方式,很不错。我这里单表quick unpack 查壳方式。用的是Quick Unpack v1.0。查壳分为2种,一种是硬编码查找OEP地方的特征,类似弄个反汇编引擎检查每句话是啥指令,还有一种是自己组织了一个壳的特征库,将OEP代码跟每个库进行比较,有个相似度比较。下面说以下。

1.直接硬编码比较

//打开目标文件
012B35B7          6A 03           push    3
012B35B9          68 000000C0     push    C0000000
012B35BE          53              push    ebx
012B35BF          E8 08FFFFFF     call    <jmp.&kernel32.CreateFileA>

//判断MZ标记
012B35EF          50              push    eax
012B35F0          E8 E7FEFFFF     call    <jmp.&kernel32.ReadFile>
012B35F5          66:813D 74562B0>cmp     word ptr [12B5674], 5A4D
012B35FE          74 15           je      short 012B3615

//判断PE标记
012B365B          68 F8000000     push    0F8
012B3660          68 80562B01     push    012B5680                                  ; ASCII "PE"
012B3665          A1 6C562B01     mov     eax, [12B566C]
012B366A          50              push    eax
012B366B          E8 6CFEFFFF     call    <jmp.&kernel32.ReadFile>
012B3670          813D 80562B01 5>cmp     dword ptr [12B5680], 4550
012B367A          74 15           je      short 012B3691

//读入每个节区属性
012B36A6          6A 00           push    0
012B36A8          68 7C562B01     push    012B567C
012B36AD          6A 28           push    28
012B36AF          A1 78562B01     mov     eax, [12B5678]
012B36B4          8D0480          lea     eax, [eax+eax*4]
012B36B7          8D04C5 50572B01 lea     eax, [eax*8+12B5750]
012B36BE          50              push    eax
012B36BF          A1 6C562B01     mov     eax, [12B566C]
012B36C4          50              push    eax
012B36C5          E8 12FEFFFF     call    <jmp.&kernel32.ReadFile>
012B36CA          FF05 78562B01   inc     dword ptr [12B5678]
012B36D0          4B              dec     ebx
012B36D1        ^ 75 D3           jnz     short 012B36A6

//读入OEP内容,长度0x64
012B36ED          6A 00           push    0
012B36EF          68 7C562B01     push    012B567C
012B36F4          6A 64           push    64
012B36F6          56              push    esi
012B36F7          A1 6C562B01     mov     eax, [12B566C]
012B36FC          50              push    eax
012B36FD          E8 DAFDFFFF     call    <jmp.&kernel32.ReadFile>

下面开始判断壳的类型。方法就是根据入口处的特征码。比如我这里读入的OEP内容是:

00401092 > $  B8 BC584000   mov     eax, 004058BC
00401097   .  50            push    eax
00401098   .  64:FF35 00000>push    dword ptr fs:[0]
0040109F   .  64:8925 00000>mov     fs:[0], esp
004010A6   .  33C0          xor     eax, eax
004010A8   .  8908          mov     [eax], ecx
004010AA   .  50            push    eax
004010AB   .  45            inc     ebp
004010AC   .  43            inc     ebx
004010AD   .  6F            outs    dx, dword ptr es:[edi]
004010AE   .  6D            ins     dword ptr es:[edi], dx
004010AF   .  70 61         jo      short 00401112

//判断每句OEP部分的一些特征
012B3820          803E B8         cmp     byte ptr [esi], 0B8
012B3823          75 41           jnz     short 012B3866
012B3825          807E 05 50      cmp     byte ptr [esi+5], 50
012B3829          75 3B           jnz     short 012B3866
012B382B          807E 06 64      cmp     byte ptr [esi+6], 64
012B382F          75 35           jnz     short 012B3866
012B3831          807E 07 FF      cmp     byte ptr [esi+7], 0FF
012B3835          75 2F           jnz     short 012B3866
012B3837          807E 08 35      cmp     byte ptr [esi+8], 35
012B383B          75 29           jnz     short 012B3866
012B383D          807E 09 00      cmp     byte ptr [esi+9], 0
012B3841          75 23           jnz     short 012B3866
012B3843          807E 0A 00      cmp     byte ptr [esi+A], 0
012B3847          75 1D           jnz     short 012B3866
012B3849          807E 0B 00      cmp     byte ptr [esi+B], 0
012B384D          75 17           jnz     short 012B3866
012B384F          807E 0C 00      cmp     byte ptr [esi+C], 0
012B3853          75 11           jnz     short 012B3866
012B3855          807E 0D 64      cmp     byte ptr [esi+D], 64
012B3859          75 0B           jnz     short 012B3866
012B385B          807E 0E 89      cmp     byte ptr [esi+E], 89
012B385F          75 05           jnz     short 012B3866

//以上特征都符合,认为是"PECompact 2.xx"
012B3861          BF E4382B01     mov     edi, 012B38E4                             ; ASCII "PECompact 2.xx"


2.相似度比较

下面由PESniffer进行比较,这种方式比较时候把所有壳的特征组成一个链表,不再象上面那样每个壳写入一段比较代码,类似于杀毒软件特征库,便于管理、节省空间。

比较时候采用模糊比较,如果一样的数据达到90%,则认为属于该壳,虽然可能误报,但可以查出来一些壳的升级版本之类,应该说比较不错。


//调用PESniffer的比较模块,传入的参数为目标文件路径: "F:\PECompat.exe"
00409CD5        |.  8D4C24 0C     lea     ecx, [esp+C]
00409CD9        |.  51            push    ecx
00409CDA        |.  6A 00         push    0
00409CDC        |.  6A 00         push    0
00409CDE        |.  50            push    eax
00409CDF        |.  E8 00470500   call    <jmp.&PESniffer.AnalyzeFile>

进入:

//打开文件:"F:\PECompat.exe"
10001C99          56              push    esi
10001C9A          68 A7000008     push    80000A7
10001C9F          6A 03           push    3
10001CA1          56              push    esi
10001CA2          6A 01           push    1
10001CA4          68 00000080     push    80000000
10001CA9          FF7424 20       push    dword ptr [esp+20]
10001CAD          FF15 08100010   call    [<&KERNEL32.CreateFileA>]                   ; KERNEL32.CreateFileA

//建立该文件的文件映象
10001CBD          56              push    esi
10001CBE          50              push    eax
10001CBF          FF15 50100010   call    [<&KERNEL32.GetFileSize>]                   ; KERNEL32.GetFileSize
10001CC5          3BC6            cmp     eax, esi
10001CC7          A3 AC210010     mov     [100021AC], eax
10001CCC          76 3D           jbe     short 10001D0B
10001CCE          56              push    esi
10001CCF          50              push    eax
10001CD0          56              push    esi
10001CD1          6A 02           push    2
10001CD3          56              push    esi
10001CD4          FF35 80100010   push    dword ptr [10001080]
10001CDA          FF15 34100010   call    [<&KERNEL32.CreateFileMappingA>]            ; KERNEL32.CreateFileMappingA
10001CE0          83F8 FF         cmp     eax, -1
10001CE3          A3 84100010     mov     [10001084], eax
10001CE8          74 21           je      short 10001D0B
10001CEA          56              push    esi
10001CEB          56              push    esi
10001CEC          56              push    esi
10001CED          6A 04           push    4
10001CEF          50              push    eax
10001CF0          FF15 30100010   call    [<&KERNEL32.MapViewOfFile>]                 ; KERNEL32.MapViewOfFile

//判断MZ标记
100012DE          66:813F 4D5A    cmp     word ptr [edi], 5A4D
100012E3          74 08           je      short 100012ED

//判断PE标记
10001307          8138 50450000   cmp     dword ptr [eax], 4550
1000130D        ^ 75 D6           jnz     short 100012E5

下面判断壳。所有壳的特征可以看作被放在一个链表中,将链表每个节点中的特征与目标文件OEP的内容进行比较,如果符合,则认为是该壳。

这种方法很象杀毒软件扫描病毒。

//扫描循环
10001398         /837D 0C 00      /cmp     dword ptr [ebp+C], 0
1000139C         |0F85 84000000   |jnz     10001426
                ................
100013B3         |50              |push    eax
100013B4         |FF75 10         |push    dword ptr [ebp+10]
100013B7         |8B45 F8         |mov     eax, [ebp-8]
100013BA         |03C8            |add     ecx, eax
100013BC         |8D86 04010000   |lea     eax, [esi+104]
100013C2         |FFB6 14050000   |push    dword ptr [esi+514]      //这个是匹配过程中略过的数据。如果匹配时候碰到该内容,则不予处理
100013C8         |FF75 F4         |push    dword ptr [ebp-C]
100013CB         |51              |push    ecx                      //读入的OEP内容
100013CC         |FFB6 18050000   |push    dword ptr [esi+518]      //需要匹配的数据长度
100013D2         |50              |push    eax                      //目标匹配数据
100013D3         |E8 3B020000     |call    10001613
100013D8         |83C4 1C         |add     esp, 1C
100013DB         |85C0            |test    eax, eax
100013DD         |0F84 D7000000   |je      100014BA
                ................
1000140C         |FF35 94210010   |push    dword ptr [10002194]
10001412         |8B0D 9C210010   |mov     ecx, [1000219C]
10001418         |FF75 FC         |push    dword ptr [ebp-4]
1000141B         |E8 E00B0000     |call    10002000               //获得壳的名称
10001420         |FF45 FC         |inc     dword ptr [ebp-4]
10001423         |8B75 14         |mov     esi, [ebp+14]
10001426         |837D 0C 01      |cmp     dword ptr [ebp+C], 1
1000142A         |0F85 8D000000   |jnz     100014BD
                ................
100014A9         |8B0D 9C210010   |mov     ecx, [1000219C]
100014AF         |FF75 FC         |push    dword ptr [ebp-4]
100014B2         |E8 490B0000     |call    10002000
100014B7         |FF45 FC         |inc     dword ptr [ebp-4]
100014BA         |8B75 14         |mov     esi, [ebp+14]
100014BD         |FF45 08         |inc     dword ptr [ebp+8]
100014C0         |A1 A0210010     |mov     eax, [100021A0]
100014C5         |8B4D 08         |mov     ecx, [ebp+8]
100014C8         |3B48 04         |cmp     ecx, [eax+4]
100014CB        ^\0F8C C7FEFFFF   \jl      10001398               //循环回去,进行下一个壳的特征比较

上面的循环,关键函数是:

call    10001613

进入,看一下:

//将OEP处的数据与壳特征数据进行逐字节比较,看有多少相同字节,记录到edi内
//edx纪录的是特征中的有效数据长度
1000163B          8A08            mov     cl, [eax]
1000163D          0FB6F1          movzx   esi, cl
10001640          3B75 18         cmp     esi, [ebp+18]
10001643          74 0C           je      short 10001651
10001645          43              inc     ebx
10001646          3B75 18         cmp     esi, [ebp+18]
10001649          74 06           je      short 10001651
1000164B          3A0C02          cmp     cl, [edx+eax]
1000164E          75 01           jnz     short 10001651
10001650          47              inc     edi
10001651          40              inc     eax
10001652          FF4D 0C         dec     dword ptr [ebp+C]
10001655        ^ 75 E4           jnz     short 1000163B

//判断是否有相同内容的数据
10001657          33F6            xor     esi, esi
10001659          3BFE            cmp     edi, esi
1000165B          74 29           je      short 10001686

//这里计算一样数据占的比例,用的浮点指令。相当于 (edi/ebx) * 100
1000165D          897D F8         mov     [ebp-8], edi
10001660          8975 FC         mov     [ebp-4], esi
10001663          DF6D F8         fild    qword ptr [ebp-8]
10001666          895D F8         mov     [ebp-8], ebx
10001669          8975 FC         mov     [ebp-4], esi
1000166C          DC0D C8100010   fmul    qword ptr [100010C8]
10001672          DF6D F8         fild    qword ptr [ebp-8]
10001675          DEF9            fdivp   st(1), st
10001677          E8 0A0B0000     call    <jmp.&MSVCRT._ftol>

//判断是不是100%一样呢?
10001681        |.  83F8 64       cmp     eax, 64
10001684        |.  74 12         je      short 10001698

//判断是不是90%以上一样呢?
10001693        |.  83F8 5A       cmp     eax, 5A
10001696        |.^ 72 EE         jb      short 10001686


看了上面的肯定觉得很乱,我这里总结一下,具体意思如下:

比如现在有某个壳的特征:

002143A4  60 E8 00 00 00 00 5D B9 01 01 01 01 80 31 15 41  `?...]?€1A
                                  -----------
002143B4  81 F9                                            侚.

反汇编为:

002143A4          60              pushad
002143A5          E8 00000000     call    002143AA
002143AA          5D              pop     ebp
002143AB          B9 01010101     mov     ecx, 1010101      // 这里的 1010101 是不计入最终计算的
002143B0          8031 15         xor     byte ptr [ecx], 15
002143B3          41              inc     ecx
002143B4          81F9 00000000   cmp     ecx, 0

特征数据长度为0x12,其中的 01 是允许变得,也就是不算为特征。

现在考虑识别时候。假如现在某个文件OEP前0x12个字节为:

002143A4  60 E8 02 00 00 00 5D B9 01 01 01 01 80 31 15 51  `?...]?€1Q
002143B4  81 F9                                            侚.

将这段内容和上面的特征比较以下,有下面结果:

1.总长度 0x12,有4个0x01,所以有效长度为 0x12 - 0x04 = 0x0E
2.除去4个0x01,以及不一样的字节,总共有 0x0C 个字节内容一样
3. 0x0C / 0x0E * 0x64 = 0x55 ,说白了就是计算以下百分比,这里是 85%
4.0x55 < 0x5A ,so,这个文件不属于这个特征码

这里的 0x5A 就是 90%,如果有90%以上的内容一样,就判定为符合该特征了。