安全路透社
当前位置:安全路透社 > 安全客 > 正文

【技术分享】一种快速提取恶意软件中解密逻辑代码的方法

t01687d12c49a2bd18b.jpg

前言


在平时的恶意软件分析和逆向工作中,我们往往需要对某些类型的加密算法或者解压缩算法进行逆向。而这一逆向工作,可能会需要好几个小时、好几天、好几个月,甚至是好几年才能完成。在我们分析的过程中,常常需要弄明白恶意软件所使用的数据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 <windows.h>
#include <stdio.h>
#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
#-------------------------------------------------------------------------------
from ctypes import *
import sys
import os.path
import argparse
import re,struct
import subprocess, random
# MAP types to ctypes
LPBYTE = POINTER(c_ubyte)
LPCSTR = LPCTSTR = c_char_p
BOOL = c_bool
if os.name != 'nt':
    print ("Script can only be run from Windows")
    sys.exit("Sorry Windows only")
def assert_success(success):
    if not success:
        raise AssertionError(FormatError())
def LzwDecompress(hdll,data):
    inbuf = create_string_buffer(data)
    outbuf= create_string_buffer(len(data))
    success = hdll.Decompress(inbuf,outbuf)
    assert_success(success)
    return outbuf.raw
def CabExtract(match,pargs,data):
    offset = match.start()
    CabHeaderMagicValue = offset + 124
    CabSizeStart = offset + 132
    CabFileNameStart = offset + 184
    CabFileNameEnd = data[CabFileNameStart:].find('\0')
    CabName = data[CabFileNameStart:CabFileNameStart+CabFileNameEnd]
    CabSize = struct.unpack("L",data[CabSizeStart:CabSizeStart+4])[0]
    CabData = data[CabHeaderMagicValue:CabHeaderMagicValue+CabSize]
    FileName=pargs.input_file
    #Add magic value
    Cab="4D534346".decode('hex')+CabData[4:]
    print "Found our CAB Data at file offset-->{}".format(offset)
    CabDir=os.path.splitext(FileName)[0]
    if not os.path.exists(CabDir):
        os.makedirs(CabDir)
    else:
        CabDir+='_'+str(random.randint(1111,9999))
        os.makedirs(CabDir)
    CabFile=os.path.basename(FileName).split('.')[0]+".cab"
    with open(CabDir+"\\"+CabFile,"wb") as fp:
        fp.write(Cab)
    print "Wrote CAB File-->%s"%CabDir+"\\"+CabFile
    print "Expanding CAB File %s"%CabName
    args = [" -r ",CabDir + "\\" + CabFile,' ',CabDir]
    result=subprocess.Popen("expand "+"".join(args), stdout=subprocess.PIPE)
    result.wait()
    if "Expanding Files Complete" not in result.stdout.read():
        print "Error Expanding CAB file"
        sys.exit(1)
    ExpandedFile = CabDir + "\\" + CabName
    if not os.path.isfile(ExpandedFile):
        print "Did not find our expanded file %s"%CabName
        sys.exit(1)
    print "Check directory %s for expanded file %s"%(CabDir,CabName)
    return ExpandedFile
def DecompressRoutine(pargs,hlzw,data):
    LzwCompPattern = "\x08\x00\xA5\x04\x01\x12\x03"
    regex = re.compile(LzwCompPattern)
    for match in regex.finditer(data):
        offset=match.start()
        print "Found our compression header at file offset-->{}".format(offset)
        Deflated=LzwDecompress(hlzw,data[offset:])
        if Deflated:
            with open(pargs.out_file, "wb") as wp:
                wp.write(Deflated)
            print "Wrote decompressed stream to file-->%s"%(pargs.out_file)
            return True
    return False
def Start(pargs,hlzw,data):
    CabCompPattern = bytearray("46444944657374726F790000464449436F7079004644494973436162696E657400000000464449437265617465000000636162696E65742E646C6C004D6963726F736F6674")
    #Check For CAB file magic value first
    found = False
    regex = re.compile(CabCompPattern.decode('hex'))
    for match in regex.finditer(data):
        found = True
        ExpandedFile=CabExtract(match,pargs,data)
        if ExpandedFile:
            with open(ExpandedFile,"rb") as fp:
                ExpandedData=fp.read()
                DecompressRoutine(pargs,hlzw,ExpandedData)
            return True
    if not found:
        result=DecompressRoutine(pargs,hlzw,data)
        if result:
            return True
        else:
            return False
def main():
    parser=argparse.ArgumentParser()
    parser.add_argument("-i", '--infile' , dest='input_file',help="Input file to process",required=True)
    parser.add_argument("-o", '--outfile', dest='out_file',help="Optional Output file name",required=False)
    results = parser.parse_args()
    if not results.out_file:
        results.out_file=results.input_file + "_dec.txt"
    lzwdll="LzwDecompress.dll"
    lzwdllpath = os.path.dirname(os.path.abspath(__file__)) + os.path.sep + lzwdll
    if os.path.isfile(lzwdllpath):
        lzw = windll.LoadLibrary(lzwdllpath)
        lzw.Decompress.argtypes=(LPCSTR,LPCSTR)
        lzw.Decompress.restypes=BOOL
    else:
        print ("Missing LzwDecompress.DLL")
        sys.exit(1)
    with open(results.input_file,"rb") as fp:
        FileData=fp.read()
        Success=Start(results,lzw,FileData)
        if not Success:
            print("Did not find CAB or Compression routine in file %s")%(results.input_file)
if __name__ == '__main__':
    main()

为适应Reaver的多个变种,我们不久前更新了这个Python脚本。新的Reaver变种使用了微软的CAB包作为第一层压缩。该脚本执行以下操作:

1. 加载我们的DLL LzwDecompress.dll。

2. 尝试定位到修改后的LZW头部或Microsoft CAB的签名值。

3. 对于LZW解压缩例程,创建的两个字符串缓冲区作为指向缓冲区的指针。源缓冲区是指向需要解压缩的数据的指针,目标缓冲区是我们存储解压缩后数据的位置。

4. 调用Decompress,并将其传递给我们的两个参数。

5. 将数据写入文件。

 下面是脚本运行截图:

http://p9.qhimg.com/t014ccbc2e2ba42e398.png

下面的示例是使用LZW来解压缩一个旧版本的Reaver恶意软件例程。解压的数据将写入到文本文件中,如下所示:

RA@10001=ole32.dll
RA@10002=CoCreateGuid
RA@10003=Shlwapi.dll
RA@10004=SHDeleteKeyA
RA@10005=wininet.dll
RA@10006=InternetOpenA
RA@10007=InternetOpenUrlA
RA@10008=InternetCloseHandle
RA@10009=HttpQueryInfoA
RA@10010=InternetReadFile
[TRUNCATED]
RA@10276=image/jpeg
RA@10277=netsvcs
RA@10282=Global\%sEvt
RA@10283=\temp\%sk.~tmp
RA@10284=Global\%skey
RA@10285=%08x%s
RA@10286=%s\
RA@10287=%s\*.*
RA@10288=%s\%s
RA@10289=CMD.EXE
RA@10290=%s=
RA@10311=\%sctr.dll
RA@10312=\uc.dat
RA@10313=ChangeServiceConfig2A
RA@10314=QueryServiceConfig2A

下面是新版本Reaver恶意软件的例子,它使用Microsoft CAB添加了一层压缩:

http://p8.qhimg.com/t01033dda2c02d1696a.png

在这里,脚本成功将文件解压缩,并读取解压缩后的文件,最终找到了解压缩例程的魔法值,并将解压数据写入文本文件中。


总结


通过本文,我们了解到,可以直接利用汇编中已有的解压缩例程,将其放在Visual Studio中编译成DLL,最后再使用Python来调用。由于我们仅仅需要调用该例程来传递恶意软件的数据,因此并不需要再在C或者Python中重新调用接口。

上述方法的实现,需要我们对于汇编语言、栈以及例程中所需的寄存器有足够了解。一旦掌握了这些知识,该方法就很容易实现,并且可以用于任何函数之中。


原文链接:https://researchcenter.paloaltonetworks.com/2017/11/unit42-using-existing-malware-save-time/

未经允许不得转载:安全路透社 » 【技术分享】一种快速提取恶意软件中解密逻辑代码的方法

赞 (0)
分享到:更多 ()

评论 0

评论前必须登录!

登陆 注册