首页 > 代码库 > 201310-upx3.08分析-Spider[4sT TeAm] 修:Crack_Qs

201310-upx3.08分析-Spider[4sT TeAm] 修:Crack_Qs

1.UPX壳

我们先来看一下数据是怎么被解压出来的,首先刚进入外壳时,就有这么几句代码:

/*462A40*/ pushad

/*462A41*/ mov esi,43F000

/*462A46*/ lea edi,dword ptr ds:[esi+FFFC2000]

/*462A4C*/ mov dword ptr ds:[edi+4C09C],BF0111E6

/*462A56*/ push edi

/*462A57*/ or ebp,FFFFFFFF

首先我们可以看到esi被赋值为43F000,那么看看这个43F000是什么东西,在数据窗口跳转过去之后看到了熟悉的Boolean,这是DELPHI程序CODE区段的典型特征,那么再来看下一句代码,

lea edi,dword ptr ds:[esi+FFFC2000]

这里对edi赋值了,动态调试会发现,其赋值结果为00401000

00401000是大多数WIN32程序第一个区段的起始地址,而且此时这里全部都是00,那么大胆的猜测一下,在解压过程中,esi指向压缩后的数据存放地址,edi指向解压后的数据存放地址;

那么接下来的解压算法就很简单了,因为我们这里并不去关注它具体的解压算法和压缩算法的细节,因为分析起来还是比较吃力的,暂时只关注它的解压所需的参数,以及解压的代码!那么这里简单的表达为如下代码(标注为XXXX的,都是加壳时产生的数值,并不固定):

mov esi,XXXXXXXX // 压缩后的数据存放地址

lea edi,[esi + XXXXXXXX] // 获取解压后的数据存放地址

mov dword ptr ds:[edi+XXXXX],XXXXXXXX // 可能是一个Key吧

// 准备参数,下面这段是解压算法

8A 06 46 88 07 47 01 DB 75 07 8B 1E 83 EE FC 11 DB 72 ED B8 01 00 00 00 01 DB 75 07 8B 1E 83 EE

FC 11 DB 11 C0 01 DB 73 0B 75 28 8B 1E 83 EE FC 11 DB 72 1F 48 01 DB 75 07 8B 1E 83 EE FC 11 DB

11 C0 EB D4 01 DB 75 07 8B 1E 83 EE FC 11 DB 11 C9 EB 52 31 C9 83 E8 03 72 11 C1 E0 08 8A 06 46

83 F0 FF 74 75 D1 F8 89 C5 EB 0B 01 DB 75 07 8B 1E 83 EE FC 11 DB 72 CC 41 01 DB 75 07 8B 1E 83

EE FC 11 DB 72 BE 01 DB 75 07 8B 1E 83 EE FC 11 DB 11 C9 01 DB 73 EF 75 09 8B 1E 83 EE FC 11 DB

73 E4 83 C1 02 81 FD 00 FB FF FF 83 D1 02 8D 14 2F 83 FD FC 76 0E 8A 02 42 88 07 47 49 75 F7 E9

42 FF FF FF 8B 02 83 C2 04 89 07 83 C7 04 83 E9 04 77 F1 01 CF E9 2C FF FF FF

当解压完毕的时候,我们可以发现代码段中还是存在一些问题,比如说CALL和JMP指令的目的地址是完全错误的,估计是因为压缩算法的关系,那么下面这段代码就是用来将其修正的,参数只有一个,也就是edi,作为指向CodeBase的指针

mov edi,esi // edi = esi = 00401000(CodeBase)

mov ecx,02681h // 外壳事先统计好的E8,E9的数量

L002:

mov al,byte ptr ds:[edi] // 从00401000开始搜索E8,E9指令

inc edi

sub al,00E8h

L005:

cmp al,01h

ja L002

cmp byte ptr ds:[edi],014h

jnz L002 // 从00401000开始搜索E8,E9指令

mov eax,dword ptr ds:[edi] // eax = Jump Code

mov bl,byte ptr ds:[edi+04h]

shr ax,08h // ax = ax / 0x100

rol eax,010h // 将eax的高16位移到低16位上

xchg ah,al // 然后将低16位的低8位和高8位互换

sub eax,edi // eax = eax - edi(jump指令的下一条指令的地址)

sub bl,00E8h

add eax,esi // eax = eax + CodeBase(401000)

mov dword ptr ds:[edi],eax // 将eax作为jump Code填写回去

add edi,05h

mov al,bl

loopd L005[1]

接下来我们再来看看IAT表是怎么被填写上去的

/*462B6E*/ lea edi,dword ptr ds:[esi+XXXXXXXX]

获取加密后的函数表的位置,根据该表来手动填写IAT

该表的数据结构如下:

[Dll Name Key] -> [IAT RVA,ImageBase = 00401000] -> [Dll End Mark] -> [Function Name] -> [0x00]

DWORD -> DWORD -> byte -> char[?] -> byte

/*462B7D*/ lea eax,dword ptr ds:[eax+esi+XXXXXXXX]

DllName = Key + 00401000 + XXXXXXXX(该值是加壳时生成的)

现在了解了这些结构之后要编写一个静态脱壳机也不难了,首先数据的解压可以通过上面的第一段汇编代码来进行,然后修正E8,E9可以通过上面的第二段汇编代码来进行,唯一需要关注的是IAT,因为导入表被换了一种形式存储,需要根据其还原出正常的导入表;

2.UPXFIX的工作原理

那么接下来来关注一下UpxFix是怎么进行工作的;我们知道,UPX这款壳因为其简单,易用,诞生了不少变种,稍作改动之后就不能用UPX自带的-d命令来进行脱壳,而UPXFIX就是对其变种进行修复的,那么这让我们感兴趣的是,他是怎么对这些变种进行修复的?或者更通俗一些,这些变种是修改了哪里导致UPX无法用-d命令进行脱壳?UPX的-d命令是依靠什么来进行脱壳的?

这三个问题足够引起我们对upxfix的关注,那么就来分析一下UPXFIX的工作原理,看看它是如何对UPX变种壳进行修复的;

打开upxfix.exe后,随便选中一个UPX加壳的文件(即时是没有经过变种的,它也会对其进行修复一番,当然,这种修复是毫无意义的);不管修复是否成功,其都会弹出一句” Fix UPX file success”,表示修复成功,其实这是因为它做的只是判断目标文件是否是UPX壳,然后根据自己掌握的知识对其进行修复,并不去判断是否修复成功;

具体的分析在” upxfix.idb”中,关键函数是sub_401730,这很容易得到,只要断MessageBoxA函数就可以了;

那么来说一下它修复的关键:

首先它将第一个区段和第二个区段的名称修复成”UPX0”和”UPX1”,然后定位到OEP offset + 5的位置,读出4个字节,如果需修复文件是EXE,判断这4字节是否是0x00BE8D00,如果需修复文件是DLL,就判断这4字节是否是0x08247C80,由此来更近一步判断是否是UPX壳,然后从文件中再次读取数据:

起始地址:&pSectionHeader[0](第一个区段首地址) – 0x20

数据长度:0x20个字节

这组数据是相当关键的,其中存放了UPX-d命令脱壳时候判断其是否是UPX壳,以及脱壳时候所必需的数据,该数据结构大致如下(有部分是猜测的,并不一定准确):

Typedef struct _UPXDATA

{

0x00 DWORD UPXMark; // 必须为"UPX!",也就是0x21585055

0x04 WORD UPXMark1; // 必须为0x090C

0x06 BYTE UPXMark2; // 用意不明

0x07 BYTE UPXMark3; // 必需为0x07

0x08 DWORD Check1; // 具体含义不明,如果改动该值会导致UPX-d命令失败,猜测可能是校验值

0x0C DWORD Check2; //具体含义不明,如果改动该值会导致UPX-d命令失败,猜测可能是校验值(2DWORD的值并没有在UPXFIX中进行处理,所以并不知道其所代表的含义,upx-d命令分析时应该会得到结果)

0x10 DWORD UPXSectionSize; // UPX0UPX1这两个区段的VirtualSize之和

0x14 DWORD DWORD1; // 含义不明,计算方法:外壳OEP Offset - OEP上方0字节的长度 - UPX0的文件起始地址

0x18 DWORD UPXSectionSizebrk; // UPX0UPX1这两个区段的VirtualSize之和,不明白为什么要保存2

0x1C WORD WORD1; // 具体含义暂时不明

0x1E BYTE BYTE1; // 该值必须为0

0x1F BYTE Number; // UPXMark1的起始地址开始,一直到BYTE2结束(0x1B字节),这段数据的累加值

}

以上即为对UPXFIX的分析,更具体的过程可以用IDA打开” upxfix.idb”查看sub_401730函数中代码的注释

3.UPX-d命令的分析 --- Crack_Qs补

60              pushad                                             //保存现场  

BE 00F04300     mov     esi, 0043F000                              //把代码段放到esi寄存器

8DBE 0020FCFF   lea     edi, dword ptr [esi+FFFC2000]              //得到基址

C787 9CC00400 7>mov     dword ptr [edi+4C09C], 46CD167B            //将第一个函数的地址放到[edi+ 4C09C]

57              push    edi                                        //将基址压栈

83CD FF         or      ebp, FFFFFFFF

EB 0E           jmp     short 004629FA

90              nop

90              nop

90              nop

90              nop

8A06            mov     al, byte ptr [esi]                        //取出0043F004的一个字节

46              inc     esi                                       //指向下一个字节

8807            mov     byte ptr [edi], al                        //从00401000开始,开始还原代码

47              inc     edi                                       //指向下一个地址

01DB            add     ebx, ebx                                  //ebx + ebx,当ebx不等于零的时候跳转,下面的adc如果为,就取出下一个地址,并放到ebx中

75 07           jnz     short 00462A01

8B1E            mov     ebx, dword ptr [esi]                      //将0043F000放到ebx中

83EE FC         sub     esi, -4                                   //0043F000加4

11DB            adc     ebx, ebx                                  //进位加法器

72 ED           jb      short 004629F0                            // 向上跳转,ebx做为是否回跳的标志,循环处理代码

B8 01000000     mov     eax, 1                                    // eax = 1

01DB            add     ebx, ebx                                  //  ebx依然作为循环的标志

75 07           jnz     short 00462A13

8B1E            mov     ebx, dword ptr [esi]                      //esi指向的地址放到ebx里面

83EE FC         sub     esi, -4                                   //esi + 4

11DB            adc     ebx, ebx                                  //进位加法

11C0            adc     eax, eax                                  //进位加法

01DB            add     ebx, ebx                                  //ebx + ebx

73 0B           jnb     short 00462A24

75 28           jnz     short 00462A43                            //跳到下面

8B1E            mov     ebx, dword ptr [esi]

83EE FC         sub     esi, -4

11DB            adc     ebx, ebx

72 1F           jb      short 00462A43

48              dec     eax

01DB            add     ebx, ebx

75 07           jnz     short 00462A30

8B1E            mov     ebx, dword ptr [esi]

83EE FC         sub     esi, -4

11DB            adc     ebx, ebx

11C0            adc     eax, eax

EB D4           jmp     short 00462A08

01DB            add     ebx, ebx

75 07           jnz     short 00462A3F

8B1E            mov     ebx, dword ptr [esi]

83EE FC         sub     esi, -4

11DB            adc     ebx, ebx

11C9            adc     ecx, ecx

EB 52           jmp     short 00462A95

31C9            xor     ecx, ecx                                 // 清零ecx

83E8 03         sub     eax, 3                                   // eax - 3

72 11           jb      short 00462A5B

C1E0 08         shl     eax, 8

8A06            mov     al, byte ptr [esi]

46              inc     esi

83F0 FF         xor     eax, FFFFFFFF

74 75           je      short 00462ACA

D1F8            sar     eax, 1

89C5            mov     ebp, eax

EB 0B           jmp     short 00462A66

01DB            add     ebx, ebx

75 07           jnz     short 00462A66

8B1E            mov     ebx, dword ptr [esi]

83EE FC         sub     esi, -4

11DB            adc     ebx, ebx

72 CC           jb      short 00462A34

41              inc     ecx

01DB            add     ebx, ebx

75 07           jnz     short 00462A74

8B1E            mov     ebx, dword ptr [esi]

83EE FC         sub     esi, -4

11DB            adc     ebx, ebx

72 BE           jb      short 00462A34

01DB            add     ebx, ebx

75 07           jnz     short 00462A81

8B1E            mov     ebx, dword ptr [esi]

83EE FC         sub     esi, -4

11DB            adc     ebx, ebx

11C9            adc     ecx, ecx

01DB            add     ebx, ebx

73 EF           jnb     short 00462A76

75 09           jnz     short 00462A92

8B1E            mov     ebx, dword ptr [esi]

83EE FC         sub     esi, -4

11DB            adc     ebx, ebx

73 E4           jnb     short 00462A76

83C1 02         add     ecx, 2

81FD 00FBFFFF   cmp     ebp, -500                               //迷惑指令

83D1 02         adc     ecx, 2                                  //进位加法

8D142F          lea     edx, dword ptr [edi+ebp]       //edi + ebp的地址装载到edx,即原来的代码段的地址

83FD FC         cmp     ebp, -4                                 //判断跳转标志,EBP小于等于-4就跳

76 0E           jbe     short 00462AB4

8A02            mov     al, byte ptr [edx]                      //取出代码段的一字节

42              inc     edx                                     //指向下一个地址

8807            mov     byte ptr [edi], al                      //取出的代码放到edi里面

47              inc     edi                                     //指向下一个代码

49              dec     ecx                                     //计数器

75 F7           jnz     short 00462AA6                          //关于计数器(ecx)的跳转

E9 42FFFFFF     jmp     004629F6                                //向上面跳,跳到add ebx,ebx

8B02            mov     eax, dword ptr [edx]                    //处理输入表

83C2 04         add     edx, 4                                  //edx + 4,指向下一个地址

8907            mov     dword ptr [edi], eax                    //将代码放到edi

83C7 04         add     edi, 4                                  // edi + 4, 存放代码的地址

83E9 04         sub     ecx, 4                                  //ecx  - 4

77 F1           ja      short 00462AB4

01CF            add     edi, ecx                     //edi + ecx,指向接收代码的地址的最后一个字节

E9 2CFFFFFF     jmp     004629F6                                //跳到 add ebx,ebx

5E              pop     esi

89F7            mov     edi, esi

B9 81260000     mov     ecx, 2681

8A07            mov     al, byte ptr [edi]              //指向我们原来代码段的代码,取出到AL里面

47              inc     edi                                     //指向下一个字节

2C E8           sub     al, 0E8                                 //处理CALL

3C 01           cmp     al, 1                                   //判断al是否大于1

77 F7           ja      short 00462AD2                          //循环,到下一个CALL的第一个字节为止

803F 14         cmp     byte ptr [edi], 14

75 F2           jnz     short 00462AD2

8B07            mov     eax, dword ptr [edi]                    //取出里面的地址,里面的地址是定位CALL的绝对地址要用到的

8A5F 04         mov     bl, byte ptr [edi+4]                    //得到下条地址的开始字节放到AL里面,CALL绝对地址就是下条指令开始+刚才上面取出的那个数字

66:C1E8 08      shr     ax, 8                                   //ax右移8位

C1C0 10         rol     eax, 10                                 //eax算术左移 8位

86C4            xchg    ah, al                                  //交换内容

29F8            sub     eax, edi                                //eax - edi

80EB E8         sub     bl, 0E8                                 //再减去E8

01F0            add     eax, esi                                //eax + esi,其中 esi是代码段开始的地方

8907            mov     dword ptr [edi], eax                    //这里处理CALL的地址,算出CALL的偏移到EDI里面

83C7 05         add     edi, 5                                  //edi + 5,指向call的后面

88D8            mov     al, bl                                  //bl的内容放到al中

E2 D9           loopd   short 00462AD7                          //循环处理CALL,其中ecx作为计数器

8DBE 00F00500   lea     edi, dword ptr [esi+5F000]              //代码段的起始地址 + 5F000

8B07            mov     eax, dword ptr [edi]                    //现在EDI指向我们的代码的输入表

09C0            or      eax, eax                                //eax 或 eax ,判断eax是否为零

74 3C           je      short 00462B46

8B5F 04         mov     ebx, dword ptr [edi+4]                  //取得这个地址的数据放到ebx

8D8430 AC2D0600 lea     eax, dword ptr [eax+esi+62DAC]          // 取得外壳段的KERNEL32.DLL的地址放eax

01F3            add     ebx, esi                                //我们代码段的起始地址加上刚才取出的那个数据

50              push    eax                                     //kernel32.dll的地址

83C7 08         add     edi, 8                                  //edi + 8

FF96 4C2E0600   call    dword ptr [esi+62E4C]                   //装载kernel32.dll

95              xchg    eax, ebp                                //交换数据,即eax指向kernel32.dll的地址

8A07            mov     al, byte ptr [edi]                      //取得现在的EDI的地址指向的数据放到AL

47              inc     edi                                     //指向下一个函

08C0            or      al, al                                  //al 或 al,判断al是否为零

74 DC           je      short 00462B04

89F9            mov     ecx, edi                                //取出的函数的名字放到ecx里面

57              push    edi                                     //函数名字压栈

48              dec     eax                                     //eax - 1

F2:AE           repne   scas byte ptr es:[edi]

55              push    ebp                                     //kernel32.dll的基址

FF96 502E0600   call    dword ptr [esi+62E50]                   //外壳的GetProcaddress

09C0            or      eax, eax                                //eax或eax,得到函数的地址

74 07           je      short 00462B40

8903            mov     dword ptr [ebx], eax                    //处理输入表

83C3 04         add     ebx, 4                                  //ebx + 4,指向下一个输入表的地址

EB E1           jmp     short 00462B21

FF96 602E0600   call    dword ptr [esi+62E60]

8BAE 542E0600   mov     ebp, dword ptr [esi+62E54]              //VirtualProtect的地址放到ebp

8DBE 00F0FFFF   lea     edi, dword ptr [esi-1000]               //指向PE头,即映像基址

BB 00100000     mov     ebx, 1000                               //把1000放到ebx,即ebx = 1000

50              push    eax

54              push    esp

6A 04           push    4

53              push    ebx

57              push    edi

FFD5            call    ebp                                     //改变属性

8D87 1F020000   lea     eax, dword ptr [edi+21F]                //现在eax指向PE头中区段的偏移起始位置

8020 7F         and     byte ptr [eax], 7F                      //改写区段名字

8060 28 7F      and     byte ptr [eax+28], 7F                   //改写区块属性第一个区块的属性

58              pop     eax

50              push    eax

54              push    esp

50              push    eax

53              push    ebx

57              push    edi

FFD5            call    ebp

58              pop     eax

61              popad                                           //恢复现场

8D4424 80       lea     eax, dword ptr [esp-80]

6A 00           push    0

39C4            cmp     esp, eax

75 FA           jnz     short 00462B7A

83EC 80         sub     esp, -80

E9 109FFEFF     jmp     0044CA98                                //跨区段的转移,跳到OEP

A0 2B4600B0     mov     al, byte ptr [B000462B]

2B46 00         sub     eax, dword ptr [esi]

9C              pushfd

idb下载:

链接: http://pan.baidu.com/s/1c0ACDJ6 密码: 4hd2