前言
创新互联建站技术团队10多年来致力于为客户提供成都做网站、成都网站设计、成都外贸网站建设、高端网站设计、成都营销网站建设、搜索引擎SEO优化等服务。经过多年发展,公司拥有经验丰富的技术团队,先后服务、推广了上1000+网站,包括各类中小企业、企事单位、高校等机构单位。
在平时的恶意软件分析和逆向工作中,我们往往需要对某些类型的加密算法或者解压缩算法进行逆向。而这一逆向工作,可能会需要好几个小时、好几天、好几个月,甚至是好几年才能完成。在我们分析的过程中,常常需要弄明白恶意软件所使用的数据Blob是什么。
要回答这个问题,本身就是一件有挑战性的工作,我通常并没有那么多的时间来对一些加密的程序做完全彻底的逆向。我一般只需要弄明白这个数据是恶意软件用来做什么的配置文件,甚至有的时候,我根本不知道这些数据是什么。尽管很不愿意接受这样的结果,但却是时常发生的。
目前,有几种方法可以解密恶意软件,并解压其中的数据。我们可以运行恶意软件并转储内存段、在调试器中对其进行调试、在解密/解压缩的部分放置Hook从而dump出其返回值、进行静态分析等等。虽然这些方法都很不错,但无疑要花费大量的时间。
如果我们有几个需要解密或解压缩的数据Blob,那么该怎么办呢?如果可以直接从恶意软件的解密/解压缩部分中得到其汇编代码,那便可以将其放在一个编译器中(比如Visual Studio),将其编译成动态链接库(DLL),然后再使用我们熟悉的脚本语言(比如Python)对其进行调用。
本文将重点讲解可以实现这一点的技术方法。在分析恶意软件Reaver的过程中,Unit 42安全小组发布了一个API调用及字符串的数据库查找工具,地址为:
https://github.com/pan-unit42/public_tools/tree/master/Reaver_Decompression
分析过程
我们以针对Reaver恶意软件家族的分析为例,尝试确定其使用的压缩算法,并确定是否可以在不运行恶意软件的前提下,从中逆向出其使用的算法。请注意,这里的前提是不运行恶意软件。
在我对该恶意软件的分析过程中,发现它似乎使用了一个修改过的Lempel-Ziv-Welch(LZW)压缩算法。我们所分析的Reaver恶意软件样本中,解压缩算法位于地址0x100010B2,其汇编代码大约有200行。解压缩例程如下所示:
- ; void __thiscall decompress(_DWORD *this, int nstream, int output, int zero, int zero2, int zero3)
- decompress proc near ; CODE XREF: decompressingData+5A↓p
- nstream = dword ptr 8
- output = dword ptr 0Ch
- zero = dword ptr 10h
- zero2 = dword ptr 14h
- zero3 = dword ptr 18h
- push ebp
- mov ebp, esp
- push ebx
- push esi
- push edi
- mov esi, ecx
- push 16512 ; unsigned int
- call Malloc
- pop ecx
- mov edi, eax
- mov ecx, 1020h
- xor eax, eax
- mov [esi], edi
- xor ebx, ebx
- rep stosd
为了简洁起见,我们没有展示该函数的全部代码。需要注意的地方是:
该函数调用约定(Calling Convention)是__thiscall(说明是C++);
该函数使用了5个参数;
该函数从恶意软件中调用一次(通过在IDA Pro中标识的交叉引用数量来看到的)。
下面是该函数调用部分的代码:
- xor eax, eax
- mov ecx, [ebp+v6]
- push eax
- push eax
- push eax
- movzx eax, word ptr [ebx+24]
- push dword ptr [edx] ; output
- lea eax, [eax+ebx+26]
- push eax
- call decompress
对调用解压缩函数的分析如下:
会清除EAX寄存器,因此EAX为0;
指向对象的指针存储在ECX(Thiscall)中;
EAX的三次push说明了解压缩例程的第3、4、5个参数始终为0;
第2个参数是指向目标缓冲区的指针;
第1个参数是指向压缩数据的指针。
而压缩的数据如下:
- 08 00 A5 04 01 12 03 06 8C 18 36 7A 04 21 62 25 ..¥.....Œ.6z.!b%
- 08 94 24 33 64 B8 20 C3 86 4D 03 05 02 09 1A 8C .”$3d¸ ÆM.....Œ
- 71 A3 C7 91 32 74 AA CC 29 23 C7 49 98 36 65 82 q£Ç‘2tªÌ)#ÇI˜6e‚
- 5C CC 58 F0 20 8E 1E 52 CA 9C 19 C2 E6 CD C8 25 ÌXð Ž.RÊœ.ÂæÍÈ%
- 65 F2 AC 1C D8 32 46 0E 98 32 9F C0 29 E3 06 67 eò¬.Ø2F.˜2ŸÀ)ã.g
- 9E 22 78 54 62 E4 69 50 06 0C A0 33 E5 94 09 43 ž"xTbäiP.. 3å”.C
- A7 8C 51 A4 4A 59 36 8D 01 75 0A 48 2B 61 D8 D4 §ŒQ¤JY6..u.H+aØÔ
- 29 83 75 A7 46 18 32 64 40 25 52 86 0D C8 32 60 )ƒu§F.2d@%R†.È2`
- C5 A6 34 DB 52 C6 0C 85 64 D4 D4 99 43 87 CA 9B Ŧ4ÛRÆ.…dÔÔ™C‡Ê›
- 35 44 A1 C8 49 63 27 8D DB 33 65 E6 D0 6D 4A A3 5D¡ÈIc'.Û3eæÐmJ£
- 07 93 37 7F EB C0 11 4C D8 B0 4C B8 61 C7 66 65 .“7.ëÀ.LØ°L¸aÇfe
- 8A B6 46 0F A1 81 E5 BC 19 93 78 8E 5F C0 6E 16 Š¶F.¡.å¼.“xŽ_Àn.
- A3 4D 38 85 4E 18 39 74 BC CA 29 4C 7A F3 59 19 £M8…N.9t¼Ê)LzóY.
为了简洁起见,在这里也不展示压缩数据的全部内容,其完整大小是45115字节。
第1-7字节(08 00 A5 04 01 12 03)是压缩例程的一个“魔法值”,我们在所有Reaver变种中都发现了这个头部。
在掌握了上述这些之后,我们就可以将注意力集中在解压缩例程的工作机制上。
请大家注意:在这里,我们可以监视调用或转储目标缓冲区内容后所得到的返回结果,其中会包含解压缩的数据,但是如果选择这种方法,就需要我们在调试器中运行代码。而我们的前提是不运行恶意软件样本。
创建DLL
在掌握了一定信息后,我们开始创建一个DLL。我们可以使用Visual Studio,或者任何能处理编译程序集(NASM/MASM)的编译器。创建一个新的空DLL项目,并添加一个新的头文件。
举例来说,我创建了一个头文件,如下所示:
- #pragma once
- #ifndef _DEFINE_LZWDecompress_DLL
- #define _DEFINE_LZWDecompress_DLL
- #ifdef __cplusplus
- extern "C" {
- #endif
- __declspec(dllexport) BOOL Decompress(char *src, char *dst);
- #ifdef __cplusplus
- }
- #endif
- BOOL Decompress(char *src, char *dst);
- #endif
上述代码会创建一个名为“Decompress”的文件,并且能接收两个参数。我们在这里之所以仅使用了两个参数,原因在于其他三个参数始终为0,所以无需定义他们。该函数的返回类型为布尔型。
针对源文件(.cpp或.c),需要从IDA Pro或其他调试器中获得汇编代码,再将其添加到源文件中。以下是我修复后的源文件代码:
- #include
- #include
- #include "TestDLL.h"
- BOOL Decompress(char *src, char *dst)
- {
- //Use calloc vs malloc. Temp buffer is for the dictionary
- void *pTmpbuff;
- pTmpbuff = (int*) calloc(0x4080u, sizeof(unsigned int));
- if (src && dst)
- {
- __asm
- {
- xor ebx, ebx; //Need to clear ebx register
- SUB ESP, 0x40; //Need to subtract stack, so we don’t overwrite some Ctypes return data
- MOV ESI, ESP;
- PUSH EAX;
- POP EDI; //Our Temp Buffer
- PUSH[EBP + 8]; //Source Buffer
- POP EAX;
- PUSH[EBP + 0xC]; //Destination Buffer
- POP EDX;
- LEA ECX, DWORD PTR DS : [EAX + 1]; //Where we start. Get the 1st DWORD of the compressed data appears to be magic value
- MOV DWORD PTR DS : [ESI], EDI;//Temp buffer address
- MOV DWORD PTR DS : [ESI + 0x1C], EDX;//Destination address
- MOV DWORD PTR DS : [ESI + 0x18], ECX;//Compressed Data
- MOV BYTE PTR DS : [ESI + 0x20], BL;//0
- MOV CL, BYTE PTR DS : [EAX];//08
- PUSH 1;
- POP EAX;
- MOV BYTE PTR DS : [ESI + 0x22], CL;
- SHL EAX, CL;
- MOV DWORD PTR DS : [ESI + 0x30], EBX;
- MOV WORD PTR DS : [ESI + 8], AX;
- INC EAX;
- MOV WORD PTR DS : [ESI + 0xA], AX;
- MOV EAX, DWORD PTR SS : [EBP + 0x10];
- MOV DWORD PTR DS : [ESI + 0x2C], EAX;
- LEA EAX, DWORD PTR DS : [EAX * 8 + 0x1F];
- SHR EAX, 5;
- SHL EAX, 2;
- CMP BYTE PTR SS : [EBP + 0x18], BL;
- MOV DWORD PTR DS : [ESI + 0x38], EAX;
- SETE AL;
- DEC EAX;
- AND AL, 1;
- ADD EAX, 0x0FF;
- CMP AL, BL;
- MOV BYTE PTR DS : [ESI + 0xC], AL;
- JNZ SHORT check3;
- MOV EAX, DWORD PTR SS : [EBP + 0x14];
- MOV DWORD PTR DS : [ESI + 0x14], EDX;
- MOV DWORD PTR DS : [ESI + 0x28], EAX;
- MOV DWORD PTR DS : [ESI + 0x34], EBX;
- check3:
- MOV ECX, ESI;
- CALL check4;
- check26:
- MOV ECX, ESI;
- CALL check10;
- MOV EDI, EAX;
- CMP DI, WORD PTR DS : [ESI + 0xA];
- JE Finished;
- CMP DI, WORD PTR DS : [ESI + 8];
- JNZ SHORT check22;
- MOV ECX, ESI;
- CALL check4;
- check24:
- MOV ECX, ESI;
- CALL check10;
- MOV EDI, EAX
- CMP DI, WORD PTR DS : [ESI + 8]
- JNZ SHORT check23;
- JMP SHORT check24;
- check22:
- CMP DI, WORD PTR DS : [ESI + 0X24]
- JNB SHORT check25;
- PUSH EDI
- JMP SHORT check27;
- check25:
- PUSH EBX;
- check27:
- MOV ECX, ESI;
- CALL check28;
- MOVZX AX, AL;
- PUSH EAX;
- PUSH EBX;
- MOV ECX, ESI;
- CALL check31;
- PUSH EDI;
- MOV ECX, ESI;
- CALL check35;
- MOV EBX, EDI;
- JMP SHORT check26;
- check10:
- MOVZX EAX, BYTE PTR DS : [ECX + 0x20];
- PUSH EBX;
- PUSH ESI;
- PUSH EDI;
- MOVZX EDI, BYTE PTR DS : [ECX + 0x23];
- ADD EAX, EDI;
- CMP EAX, 8;
- JA SHORT Check6;
- MOV EDX, DWORD PTR DS : [ECX + 0x18];
- MOVZX ESI, BYTE PTR DS : [EDX];
- JMP SHORT Check8;
- Check6:
- MOV EDX, DWORD PTR DS : [ECX + 0x18];
- CMP EAX, 0x10;
- JA SHORT Check7;
- MOVZX ESI, WORD PTR DS : [EDX];
- JMP SHORT Check8;
- Check7:
- MOVZX ESI, BYTE PTR DS : [EDX + 2];
- MOVZX EBX, WORD PTR DS : [EDX];
- SHL ESI, 0X10;
- OR ESI, EBX;
- Check8:
- MOV EBX, EAX;
- PUSH 0x20;
- SHR EBX, 3;
- ADD EBX, EDX;
- MOV DL, AL;
- AND DL, 7;
- MOV DWORD PTR DS : [ECX + 0X18], EBX;
- MOV BYTE PTR DS : [ECX + 0X20], DL;
- POP ECX;
- SUB ECX, EAX;
- MOV EAX, ESI;
- PUSH 0x20;
- SHL EAX, CL;
- POP ECX;
- SUB ECX, EDI;
- POP EDI;
- POP ESI;
- POP EBX;
- SHR EAX, CL;
- RETN;
- check28:
- MOV EAX, DWORD PTR DS : [ECX];
- MOV EDX, DWORD PTR SS : [ESP + 4];
- check30:
- MOVZX ECX, DX;
- MOV CX, WORD PTR DS : [EAX + ECX * 4];
- CMP CX, 0x0FFFF;
- JE SHORT check29;
- MOV EDX, ECX;
- JMP SHORT check30;
- check29:
- MOVZX ECX, DX;
- MOV AL, BYTE PTR DS : [EAX + ECX * 4 + 2];
- RETN 4;
- check31:
- MOVZX EDX, WORD PTR DS : [ECX + 0x24];
- LEA EAX, DWORD PTR DS : [ECX + 0x24];
- PUSH ESI;
- MOV ESI, DWORD PTR DS : [ECX];
- PUSH EDI;
- MOV DI, WORD PTR SS : [ESP + 0xC];
- MOV WORD PTR DS : [ESI + EDX * 4], DI;
- MOV ESI, DWORD PTR DS : [ECX];
- MOVZX EDX, WORD PTR DS : [EAX];
- MOV DI, WORD PTR SS : [ESP + 0x10];
- MOV WORD PTR DS : [ESI + EDX * 4 + 2], DI;
- INC WORD PTR DS : [EAX];
- MOV AX, WORD PTR DS : [EAX];
- POP EDI;
- CMP AX, 8;
- POP ESI;
- JE SHORT check32;
- CMP AX, 0x10;
- JE SHORT check32;
- CMP AX, 0x20;
- JE SHORT check32;
- CMP AX, 0x40;
- JE SHORT check32;
- CMP AX, 0x80;
- JE SHORT check32;
- CMP AX, 0x100;
- JE SHORT check32;
- CMP AX, 0x200;
- JE SHORT check32;
- CMP AX, 0x400;
- JE SHORT check32;
- CMP AX, 0x800;
- JNZ SHORT check33;
- check32:
- INC BYTE PTR DS : [ECX + 0x23];
- check33:
- RETN 8;
- check4:
- MOV EDX, ECX;
- PUSH EDI;
- MOV ECX, 0x1000;
- OR EAX, 0xFFFFFFFF;
- MOV EDI, DWORD PTR DS : [EDX]
- REP STOS DWORD PTR ES : [EDI];
- XOR EAX, EAX;
- POP EDI;
- CMP WORD PTR DS : [EDX + 8], AX;
- JBE SHORT check1;
- PUSH ESI;
- MOV ESI, DWORD PTR DS : [EDX];
- check2:
- MOVZX ECX, AX;
- MOV WORD PTR DS : [ESI + ECX * 4 + 2], AX;
- INC EAX;
- CMP AX, WORD PTR DS : [EDX + 8];
- JB SHORT check2;
- POP ESI;
- check1:
- MOV AX, WORD PTR DS : [EDX + 0xA];
- INC AX;
- MOV WORD PTR DS : [EDX + 0x24], AX;
- MOV AL, BYTE PTR DS : [EDX + 0x22];
- INC AL;
- MOV BYTE PTR DS : [EDX + 0x23], AL;
- RETN;
- check23:
- PUSH EDI;
- MOV ECX, ESI;
- CALL check35;
- MOV EBX, EDI;
- JMP SHORT check26;
- check35:
- PUSH EBP;
- MOV EBP, ESP;
- PUSH ESI;
- PUSH EDI;
- MOV ESI, ECX;
- NOP;
- MOV AX, WORD PTR SS : [EBP + 8];
- CMP AX, WORD PTR DS : [ESI + 8];
- JNB SHORT check36;
- NOP;
- MOV ECX, DWORD PTR DS : [ESI];
- MOV EDX, DWORD PTR DS : [ESI + 0x1C];
- MOV EDI, DWORD PTR DS : [ESI + 0x30];
- MOVZX EAX, AX;
- MOV AL, BYTE PTR DS : [ECX + EAX * 4 + 2];
- MOV BYTE PTR DS : [EDX + EDI], AL;
- INC DWORD PTR DS : [ESI + 0x30];
- NOP;
- MOV EAX, DWORD PTR DS : [ESI + 0x30];
- CMP EAX, DWORD PTR DS : [ESI + 0x2C];
- JNZ SHORT FuncRetn;
- MOV ECX, ESI;
- CALL check37;
- NOP;
- JMP SHORT FuncRetn;
- check36:
- MOVZX EDI, AX;
- MOV EAX, DWORD PTR DS : [ESI];
- MOV ECX, ESI;
- SHL EDI, 2;
- MOV AX, WORD PTR DS : [EDI + EAX];
- PUSH EAX;
- CALL check35;
- NOP;
- MOV EAX, DWORD PTR DS : [ESI];
- MOV ECX, ESI;
- MOV AX, WORD PTR DS : [EDI + EAX + 2];
- PUSH EAX;
- CALL check35;
- NOP;
- NOP;
- POP EDI;
- POP ESI;
- POP EBP;
- RETN 4;
- check38:
- MOVZX EDX, AL;
- MOVZX EDX, BYTE PTR DS : [EDX + ECX + 0xD];
- ADD DWORD PTR DS : [ECX + 0x34], EDX;
- MOV EDX, DWORD PTR DS : [ECX + 0x34];
- CMP EDX, DWORD PTR DS : [ECX + 0x28];
- JB SHORT FuncRetrn2;
- INC AL;
- CMP AL, 4;
- MOV BYTE PTR DS : [ECX + 0xC], AL;
- JNB SHORT Frtn;
- MOVZX EAX, AL;
- MOVZX EAX, BYTE PTR DS : [EAX + ECX + 0xD];
- SHR EAX, 1;
- MOV DWORD PTR DS : [ECX + 0x34], EAX;
- FuncRetrn2:
- MOV EAX, DWORD PTR DS : [ECX + 0x38];
- MOV EDX, DWORD PTR DS : [ECX + 0x14];
- IMUL EAX, DWORD PTR DS : [ECX + 0x34];
- SUB EDX, EAX;
- MOV DWORD PTR DS : [ECX + 0x1C], EDX;
- Frtn:
- RETN;
- FuncRetn:
- NOP;
- POP EDI;
- POP ESI;
- POP EBP;
- RETN 4;
- check37:
- MOV AL, BYTE PTR DS : [ECX + 0xC];
- AND DWORD PTR DS : [ECX + 0x30], 0;
- CMP AL, 0x0FF;
- JNZ SHORT check38;
- MOV EAX, DWORD PTR DS : [ECX + 0x38];
- SUB DWORD PTR DS : [ECX + 0x1C], EAX;
- RETN;
- Finished:
- MOV ESP,EBP;
- POP EBP;
- //Debug VS Release build have different stack sizes. The following is needed for the return parameters and CTYPES
- #ifdef _DEBUG
- ADD ESI, 0x120;
- #else
- ADD ESI, 0x58; //Need for Pythnon CTypes return parameters!
- #endif
- RETN;
- }
- }
- return TRUE;
- }
通过IDA Pro或者例如Immunity Debugger这样的反汇编程序来获取汇编代码并不难,但是在获得之后,还需要我们进行一些处理。特别需要注意的一个地方就是在代码块中进行的函数调用。在汇编中,每一次调用过程都需要一个名称(标签),并且所有的代码需要按照调用顺序正确地排列,否则将产生意外的结果,或者是直接崩溃。因此,我们在复制每个函数的汇编代码时都需要非常谨慎。在刚刚的例子中,为了方便快速,我直接使用了“check”来表示函数名称或者跳转的位置。
由于LZW使用索引将数据编码到字典中,解压例程所做的第一件事,就是分配内存中的16512字节(0x4080)的缓冲区来创建字典。在汇编中,它使用C++ API malloc分配缓冲区,并将缓冲区设置为NULL(这是malloc的工作方式)。有一种更简单有效的方法,是使用calloc函数,在减少指令数量的前提下实现缓冲区的分配。
我们首先在C++中进行编码,然后再Visual Studio中使用__asm关键字内嵌汇编语言。在__asm内的代码块就是我们放置汇编指令并进行必要调整的位置:
将EBX设置为0;
从栈中减去64字节(0x40),以防止我们覆盖任何栈的数据;
将栈指针保存到ESI中;
EDI指向我们通过calloc创建的字典缓冲区;
EAX指向我们的源数据;
EDX指向我们的目标缓冲区。
为了满足解压缩算法的要求,我们手工添加了下面的9行代码,其余代码直接从Immunity Debugger中复制即可:
- xor ebx, ebx; //Need to clear ebx register
- SUB ESP, 0x40; //Need to subtract stack, so we don’t overwrite some Ctypes return data
- MOV ESI, ESP;
- PUSH EAX;
- POP EDI; //Our Temp Buffer
- PUSH[EBP + 8]; //Source Buffer
- POP EAX;
- PUSH[EBP + 0xC]; //Destination Buffer
- POP EDX;
此时,我们需要做的就是更新汇编调用,跳转到有意义的名称,并按正确的顺序来排列它们。现在代码应该可以编译并运行了。但当例程结束后,我们必须手动恢复栈,从而让Python ctypes返回到正确的调用方。我们添加了以下代码:
- Finished:
- MOV ESP,EBP;
- POP EBP;
- //Debug VS Release build have different stack sizes. The following is needed for the return parameters and CTYPES
- #ifdef _DEBUG
- ADD ESI, 0x120;
- #else
- ADD ESI, 0x58; //Need for CTypes return parameters!!!!
- #endif
- RETN;
- }
在这里,我们尝试恢复堆栈指针寄存器(SP)和基址指针寄存器(BP),并将0x120或0x58添加到ESI,具体要取决于VS的版本是测试版还是正式版。
调用DLL
至此,我们就有了一个DLL,可以开始调用它,并通过Python和ctypes来传递它的数据。下面这个Python脚本的作用就是利用这个DLL,来解密Reaver的数据:
- #-------------------------------------------------------------------------------
- # Name: LzwDecompression
- # Purpose:
- #
- # Author: Mike Harbison Unit 42
- #
- # Created: 11/11/2017
- #-------------------------------------------------------------------------------
网页标题:一种快速提取恶意软件解密逻辑代码的方法
URL分享:http://www.mswzjz.com/qtweb/news36/184486.html网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联