admin

一种快速提取恶意软件解密逻辑代码的方法

admin 安全防护 2022-12-15 268浏览 0

前言

在平时的恶意软件分析和逆向工作中,我们往往需要对某些类型的加密算法或者解压缩算法进行逆向。而这一逆向工作,可能会需要好几个小时、好几天、好几个月,甚至是好几年才能完成。在我们分析的过程中,常常需要弄明白恶意软件所使用的数据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__thiscalldecompress(_DWORD*this,intnstream,intoutput,intzero,intzero2,intzero3)
decompressprocnear;CODEXREF:decompressingData+5A↓p
nstream=dwordptr8
output=dwordptr0Ch
zero=dwordptr10h
zero2=dwordptr14h
zero3=dwordptr18h
pushebp
movebp,esp
pushebx
pushesi
pushedi
movesi,ecx
push16512;unsignedint
callMalloc
popecx
movedi,eax
movecx,1020h
xoreax,eax
mov[esi],edi
xorebx,ebx
repstosd

为了简洁起见,我们没有展示该函数的全部代码。需要注意的地方是:

该函数调用约定(Calling Convention)是__thiscall(说明是C++);

该函数使用了5个参数;

该函数从恶意软件中调用一次(通过在IDA Pro中标识的交叉引用数量来看到的)。

下面是该函数调用部分的代码:

xoreax,eax
movecx,[ebp+v6]
pusheax
pusheax
pusheax
movzxeax,wordptr[ebx+24]
pushdwordptr[edx];output
leaeax,[eax+ebx+26]
pusheax
calldecompress

对调用解压缩函数的分析如下:

会清除EAX寄存器,因此EAX为0;

指向对象的指针存储在ECX(Thiscall)中;

EAX的三次push说明了解压缩例程的第3、4、5个参数始终为0;

第2个参数是指向目标缓冲区的指针;

第1个参数是指向压缩数据的指针。

而压缩的数据如下:

0800A504011203068C18367A04216225..¥.....Œ.6z.!b%
0894243364B820C3864D030502091A8C.”$3d¸Ã†M.....Œ
71A3C7913274AACC2923C74998366582q£Ç‘2tªÌ)#ÇI˜6e‚
5CCC58F0208E1E52CA9C19C2E6CDC825ÌXðŽ.RÊœ.ÂæÍÈ%
65F2AC1CD832460E98329FC029E30667eò¬.Ø2F.˜2ŸÀ)ã.g
9E22785462E46950060CA033E5940943ž"xTbäiP..3å”.C
A78C51A44A59368D01750A482B61D8D4§ŒQ¤JY6..u.H+aØÔ
298375A746183264402552860DC83260)ƒu§F.2d@%R†.È2`
C5A634DB52C60C8564D4D4994387CA9BŦ4ÛRÆ.…dÔÔ™C‡Ê›
3544A1C84963278DDB3365E6D06D4AA35D¡ÈIc'.Û3eæÐmJ£
0793377FEBC0114CD8B04CB861C76665.“7.ëÀ.LذL¸aÇfe
8AB6460FA181E5BC1993788E5FC06E16жF.¡.å¼.“xŽ_Àn.
A34D38854E183974BCCA294C7AF35919£M8…N.9t¼Ê)LzóY.

为了简洁起见,在这里也不展示压缩数据的全部内容,其完整大小是45115字节。

第1-7字节(08 00 A5 04 01 12 03)是压缩例程的一个“魔法值”,我们在所有Reaver变种中都发现了这个头部。

在掌握了上述这些之后,我们就可以将注意力集中在解压缩例程的工作机制上。

请大家注意:在这里,我们可以监视调用或转储目标缓冲区内容后所得到的返回结果,其中会包含解压缩的数据,但是如果选择这种方法,就需要我们在调试器中运行代码。而我们的前提是不运行恶意软件样本。

创建DLL

在掌握了一定信息后,我们开始创建一个DLL。我们可以使用Visual Studio,或者任何能处理编译程序集(NASM/MASM)的编译器。创建一个新的空DLL项目,并添加一个新的头文件。

举例来说,我创建了一个头文件,如下所示:

#pragmaonce
#ifndef_DEFINE_LZWDecompress_DLL
#define_DEFINE_LZWDecompress_DLL

#ifdef__cplusplus
extern"C"{
#endif
__declspec(dllexport)BOOLDecompress(char*src,char*dst);
#ifdef__cplusplus
}
#endif
BOOLDecompress(char*src,char*dst);
#endif

上述代码会创建一个名为“Decompress”的文件,并且能接收两个参数。我们在这里之所以仅使用了两个参数,原因在于其他三个参数始终为0,所以无需定义他们。该函数的返回类型为布尔型。

针对源文件(.cpp或.c),需要从IDA Pro或其他调试器中获得汇编代码,再将其添加到源文件中。以下是我修复后的源文件代码:

#include<windows.h>
#include<stdio.h>
#include"TestDLL.h"
BOOLDecompress(char*src,char*dst)
{
//Usecallocvsmalloc.Tempbufferisforthedictionary
void*pTmpbuff;
pTmpbuff=(int*)calloc(0x4080u,sizeof(unsignedint));
if(src&&dst)
{
__asm
{
xorebx,ebx;//Needtoclearebxregister
SUBESP,0x40;//Needtosubtractstack,sowedon’toverwritesomeCtypesreturndata
MOVESI,ESP;
PUSHEAX;
POPEDI;//OurTempBuffer
PUSH[EBP+8];//SourceBuffer
POPEAX;
PUSH[EBP+0xC];//DestinationBuffer
POPEDX;
LEAECX,DWORDPTRDS:[EAX+1];//Wherewestart.Getthe1stDWORDofthecompresseddataappearstobemagicvalue
MOVDWORDPTRDS:[ESI],EDI;//Tempbufferaddress
MOVDWORDPTRDS:[ESI+0x1C],EDX;//Destinationaddress
MOVDWORDPTRDS:[ESI+0x18],ECX;//CompressedData
MOVBYTEPTRDS:[ESI+0x20],BL;//0
MOVCL,BYTEPTRDS:[EAX];//08
PUSH1;
POPEAX;
MOVBYTEPTRDS:[ESI+0x22],CL;
SHLEAX,CL;
MOVDWORDPTRDS:[ESI+0x30],EBX;
MOVWORDPTRDS:[ESI+8],AX;
INCEAX;
MOVWORDPTRDS:[ESI+0xA],AX;
MOVEAX,DWORDPTRSS:[EBP+0x10];
MOVDWORDPTRDS:[ESI+0x2C],EAX;
LEAEAX,DWORDPTRDS:[EAX*8+0x1F];
SHREAX,5;
SHLEAX,2;
CMPBYTEPTRSS:[EBP+0x18],BL;
MOVDWORDPTRDS:[ESI+0x38],EAX;
SETEAL;
DECEAX;
ANDAL,1;
ADDEAX,0x0FF;
CMPAL,BL;
MOVBYTEPTRDS:[ESI+0xC],AL;
JNZSHORTcheck3;
MOVEAX,DWORDPTRSS:[EBP+0x14];
MOVDWORDPTRDS:[ESI+0x14],EDX;
MOVDWORDPTRDS:[ESI+0x28],EAX;
MOVDWORDPTRDS:[ESI+0x34],EBX;
check3:
MOVECX,ESI;
CALLcheck4;
check26:
MOVECX,ESI;
CALLcheck10;
MOVEDI,EAX;
CMPDI,WORDPTRDS:[ESI+0xA];
JEFinished;
CMPDI,WORDPTRDS:[ESI+8];
JNZSHORTcheck22;
MOVECX,ESI;
CALLcheck4;
check24:
MOVECX,ESI;
CALLcheck10;
MOVEDI,EAX
CMPDI,WORDPTRDS:[ESI+8]
JNZSHORTcheck23;
JMPSHORTcheck24;
check22:
CMPDI,WORDPTRDS:[ESI+0X24]
JNBSHORTcheck25;
PUSHEDI
JMPSHORTcheck27;
check25:
PUSHEBX;
check27:
MOVECX,ESI;
CALLcheck28;
MOVZXAX,AL;
PUSHEAX;
PUSHEBX;
MOVECX,ESI;
CALLcheck31;
PUSHEDI;
MOVECX,ESI;
CALLcheck35;
MOVEBX,EDI;
JMPSHORTcheck26;
check10:
MOVZXEAX,BYTEPTRDS:[ECX+0x20];
PUSHEBX;
PUSHESI;
PUSHEDI;
MOVZXEDI,BYTEPTRDS:[ECX+0x23];
ADDEAX,EDI;
CMPEAX,8;
JASHORTCheck6;
MOVEDX,DWORDPTRDS:[ECX+0x18];
MOVZXESI,BYTEPTRDS:[EDX];
JMPSHORTCheck8;
Check6:
MOVEDX,DWORDPTRDS:[ECX+0x18];
CMPEAX,0x10;
JASHORTCheck7;
MOVZXESI,WORDPTRDS:[EDX];
JMPSHORTCheck8;
Check7:
MOVZXESI,BYTEPTRDS:[EDX+2];
MOVZXEBX,WORDPTRDS:[EDX];
SHLESI,0X10;
ORESI,EBX;
Check8:
MOVEBX,EAX;
PUSH0x20;
SHREBX,3;
ADDEBX,EDX;
MOVDL,AL;
ANDDL,7;
MOVDWORDPTRDS:[ECX+0X18],EBX;
MOVBYTEPTRDS:[ECX+0X20],DL;
POPECX;
SUBECX,EAX;
MOVEAX,ESI;
PUSH0x20;
SHLEAX,CL;
POPECX;
SUBECX,EDI;
POPEDI;
POPESI;
POPEBX;
SHREAX,CL;
RETN;
check28:
MOVEAX,DWORDPTRDS:[ECX];
MOVEDX,DWORDPTRSS:[ESP+4];
check30:
MOVZXECX,DX;
MOVCX,WORDPTRDS:[EAX+ECX*4];
CMPCX,0x0FFFF;
JESHORTcheck29;
MOVEDX,ECX;
JMPSHORTcheck30;
check29:
MOVZXECX,DX;
MOVAL,BYTEPTRDS:[EAX+ECX*4+2];
RETN4;
check31:
MOVZXEDX,WORDPTRDS:[ECX+0x24];
LEAEAX,DWORDPTRDS:[ECX+0x24];
PUSHESI;
MOVESI,DWORDPTRDS:[ECX];
PUSHEDI;
MOVDI,WORDPTRSS:[ESP+0xC];
MOVWORDPTRDS:[ESI+EDX*4],DI;
MOVESI,DWORDPTRDS:[ECX];
MOVZXEDX,WORDPTRDS:[EAX];
MOVDI,WORDPTRSS:[ESP+0x10];
MOVWORDPTRDS:[ESI+EDX*4+2],DI;
INCWORDPTRDS:[EAX];
MOVAX,WORDPTRDS:[EAX];
POPEDI;
CMPAX,8;
POPESI;
JESHORTcheck32;
CMPAX,0x10;
JESHORTcheck32;
CMPAX,0x20;
JESHORTcheck32;
CMPAX,0x40;
JESHORTcheck32;
CMPAX,0x80;
JESHORTcheck32;
CMPAX,0x100;
JESHORTcheck32;
CMPAX,0x200;
JESHORTcheck32;
CMPAX,0x400;
JESHORTcheck32;
CMPAX,0x800;
JNZSHORTcheck33;
check32:
INCBYTEPTRDS:[ECX+0x23];
check33:
RETN8;
check4:
MOVEDX,ECX;
PUSHEDI;
MOVECX,0x1000;
OREAX,0xFFFFFFFF;
MOVEDI,DWORDPTRDS:[EDX]
REPSTOSDWORDPTRES:[EDI];
XOREAX,EAX;
POPEDI;
CMPWORDPTRDS:[EDX+8],AX;
JBESHORTcheck1;
PUSHESI;
MOVESI,DWORDPTRDS:[EDX];
check2:
MOVZXECX,AX;
MOVWORDPTRDS:[ESI+ECX*4+2],AX;
INCEAX;
CMPAX,WORDPTRDS:[EDX+8];
JBSHORTcheck2;
POPESI;
check1:
MOVAX,WORDPTRDS:[EDX+0xA];
INCAX;
MOVWORDPTRDS:[EDX+0x24],AX;
MOVAL,BYTEPTRDS:[EDX+0x22];
INCAL;
MOVBYTEPTRDS:[EDX+0x23],AL;
RETN;
check23:
PUSHEDI;
MOVECX,ESI;
CALLcheck35;
MOVEBX,EDI;
JMPSHORTcheck26;
check35:
PUSHEBP;
MOVEBP,ESP;
PUSHESI;
PUSHEDI;
MOVESI,ECX;
NOP;
MOVAX,WORDPTRSS:[EBP+8];
CMPAX,WORDPTRDS:[ESI+8];
JNBSHORTcheck36;
NOP;
MOVECX,DWORDPTRDS:[ESI];
MOVEDX,DWORDPTRDS:[ESI+0x1C];
MOVEDI,DWORDPTRDS:[ESI+0x30];
MOVZXEAX,AX;
MOVAL,BYTEPTRDS:[ECX+EAX*4+2];
MOVBYTEPTRDS:[EDX+EDI],AL;
INCDWORDPTRDS:[ESI+0x30];
NOP;
MOVEAX,DWORDPTRDS:[ESI+0x30];
CMPEAX,DWORDPTRDS:[ESI+0x2C];
JNZSHORTFuncRetn;
MOVECX,ESI;
CALLcheck37;
NOP;
JMPSHORTFuncRetn;
check36:
MOVZXEDI,AX;
MOVEAX,DWORDPTRDS:[ESI];
MOVECX,ESI;
SHLEDI,2;
MOVAX,WORDPTRDS:[EDI+EAX];
PUSHEAX;
CALLcheck35;
NOP;
MOVEAX,DWORDPTRDS:[ESI];
MOVECX,ESI;
MOVAX,WORDPTRDS:[EDI+EAX+2];
PUSHEAX;
CALLcheck35;
NOP;
NOP;
POPEDI;
POPESI;
POPEBP;
RETN4;
check38:
MOVZXEDX,AL;
MOVZXEDX,BYTEPTRDS:[EDX+ECX+0xD];
ADDDWORDPTRDS:[ECX+0x34],EDX;
MOVEDX,DWORDPTRDS:[ECX+0x34];
CMPEDX,DWORDPTRDS:[ECX+0x28];
JBSHORTFuncRetrn2;
INCAL;
CMPAL,4;
MOVBYTEPTRDS:[ECX+0xC],AL;
JNBSHORTFrtn;
MOVZXEAX,AL;
MOVZXEAX,BYTEPTRDS:[EAX+ECX+0xD];
SHREAX,1;
MOVDWORDPTRDS:[ECX+0x34],EAX;
FuncRetrn2:
MOVEAX,DWORDPTRDS:[ECX+0x38];
MOVEDX,DWORDPTRDS:[ECX+0x14];
IMULEAX,DWORDPTRDS:[ECX+0x34];
SUBEDX,EAX;
MOVDWORDPTRDS:[ECX+0x1C],EDX;
Frtn:
RETN;
FuncRetn:
NOP;
POPEDI;
POPESI;
POPEBP;
RETN4;
check37:
MOVAL,BYTEPTRDS:[ECX+0xC];
ANDDWORDPTRDS:[ECX+0x30],0;
CMPAL,0x0FF;
JNZSHORTcheck38;
MOVEAX,DWORDPTRDS:[ECX+0x38];
SUBDWORDPTRDS:[ECX+0x1C],EAX;
RETN;
Finished:
MOVESP,EBP;
POPEBP;
//DebugVSReleasebuildhavedifferentstacksizes.ThefollowingisneededforthereturnparametersandCTYPES
#ifdef_DEBUG
ADDESI,0x120;
#else
ADDESI,0x58;//NeedforPythnonCTypesreturnparameters!
#endif
RETN;
}
}
returnTRUE;
}

通过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中复制即可:

xorebx,ebx;//Needtoclearebxregister
SUBESP,0x40;//Needtosubtractstack,sowedon’toverwritesomeCtypesreturndata
MOVESI,ESP;
PUSHEAX;
POPEDI;//OurTempBuffer
PUSH[EBP+8];//SourceBuffer
POPEAX;
PUSH[EBP+0xC];//DestinationBuffer
POPEDX;

此时,我们需要做的就是更新汇编调用,跳转到有意义的名称,并按正确的顺序来排列它们。现在代码应该可以编译并运行了。但当例程结束后,我们必须手动恢复栈,从而让Python ctypes返回到正确的调用方。我们添加了以下代码:

Finished:
MOVESP,EBP;
POPEBP;
//DebugVSReleasebuildhavedifferentstacksizes.ThefollowingisneededforthereturnparametersandCTYPES
#ifdef_DEBUG
ADDESI,0x120;
#else
ADDESI,0x58;//NeedforCTypesreturnparameters!!!!
#endif
RETN;
}

在这里,我们尝试恢复堆栈指针寄存器(SP)和基址指针寄存器(BP),并将0x120或0x58添加到ESI,具体要取决于VS的版本是测试版还是正式版。

调用DLL

至此,我们就有了一个DLL,可以开始调用它,并通过Python和ctypes来传递它的数据。下面这个Python脚本的作用就是利用这个DLL,来解密Reaver的数据:

#-------------------------------------------------------------------------------
#Name:LzwDecompression
#Purpose:
#
#Author:MikeHarbisonUnit42
#
#Created:11/11/2017
#-------------------------------------------------------------------------------
fromctypesimport*
importsys
importos.path
importargparse
importre,struct
importsubprocess,random
#MAPtypestoctypes
LPBYTE=POINTER(c_ubyte)
LPCSTR=LPCTSTR=c_char_p
BOOL=c_bool
ifos.name!='nt':
print("ScriptcanonlyberunfromWindows")
sys.exit("SorryWindowsonly")
defassert_success(success):
ifnotsuccess:
raiseAssertionError(FormatError())
defLzwDecompress(hdll,data):
inbuf=create_string_buffer(data)
outbuf=create_string_buffer(len(data))
success=hdll.Decompress(inbuf,outbuf)
assert_success(success)
returnoutbuf.raw
defCabExtract(match,pargs,data):
offset=match.start()
CabHeaderMagicValue=offset+124
CabSizeStart=offset+132
CabFileNameStart=offset+184
CabFileNameEnd=data[CabFileNameStart:].find('')
CabName=data[CabFileNameStart:CabFileNameStart+CabFileNameEnd]
CabSize=struct.unpack("L",data[CabSizeStart:CabSizeStart+4])[0]
CabData=data[CabHeaderMagicValue:CabHeaderMagicValue+CabSize]
FileName=pargs.input_file
#Addmagicvalue
Cab="4D534346".decode('hex')+CabData[4:]
print"FoundourCABDataatfileoffset-->{}".format(offset)
CabDir=os.path.splitext(FileName)[0]
ifnotos.path.exists(CabDir):
os.makedirs(CabDir)
else:
CabDir+='_'+str(random.randint(1111,9999))
os.makedirs(CabDir)
CabFile=os.path.basename(FileName).split('.')[0]+".cab"
withopen(CabDir+"\"+CabFile,"wb")asfp:
fp.write(Cab)
print"WroteCABFile-->%s"%CabDir+"\"+CabFile
print"ExpandingCABFile%s"%CabName
args=["-r",CabDir+"\"+CabFile,'',CabDir]
result=subprocess.Popen("expand"+"".join(args),stdout=subprocess.PIPE)
result.wait()
if"ExpandingFilesComplete"notinresult.stdout.read():
print"ErrorExpandingCABfile"
sys.exit(1)
ExpandedFile=CabDir+"\"+CabName
ifnotos.path.isfile(ExpandedFile):
print"Didnotfindourexpandedfile%s"%CabName
sys.exit(1)
print"Checkdirectory%sforexpandedfile%s"%(CabDir,CabName)
returnExpandedFile
defDecompressRoutine(pargs,hlzw,data):
LzwCompPattern="x08x00xA5x04x01x12x03"
regex=re.compile(LzwCompPattern)
formatchinregex.finditer(data):
offset=match.start()
print"Foundourcompressionheaderatfileoffset-->{}".format(offset)
Deflated=LzwDecompress(hlzw,data[offset:])
ifDeflated:
withopen(pargs.out_file,"wb")aswp:
wp.write(Deflated)
print"Wrotedecompressedstreamtofile-->%s"%(pargs.out_file)
returnTrue
returnFalse
defStart(pargs,hlzw,data):
CabCompPattern=bytearray("46444944657374726F790000464449436F7079004644494973436162696E657400000000464449437265617465000000636162696E65742E646C6C004D6963726F736F6674")
#CheckForCABfilemagicvaluefirst
found=False
regex=re.compile(CabCompPattern.decode('hex'))
formatchinregex.finditer(data):
found=True
ExpandedFile=CabExtract(match,pargs,data)
ifExpandedFile:
withopen(ExpandedFile,"rb")asfp:
ExpandedData=fp.read()
DecompressRoutine(pargs,hlzw,ExpandedData)
returnTrue
ifnotfound:
result=DecompressRoutine(pargs,hlzw,data)
ifresult:
returnTrue
else:
returnFalse
defmain():
parser=argparse.ArgumentParser()
parser.add_argument("-i",'--infile',dest='input_file',help="Inputfiletoprocess",required=True)
parser.add_argument("-o",'--outfile',dest='out_file',help="OptionalOutputfilename",required=False)
results=parser.parse_args()
ifnotresults.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
ifos.path.isfile(lzwdllpath):
lzw=windll.LoadLibrary(lzwdllpath)
lzw.Decompress.argtypes=(LPCSTR,LPCSTR)
lzw.Decompress.restypes=BOOL
else:
print("MissingLzwDecompress.DLL")
sys.exit(1)
withopen(results.input_file,"rb")asfp:
FileData=fp.read()
Success=Start(results,lzw,FileData)
ifnotSuccess:
print("DidnotfindCABorCompressionroutineinfile%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. 将数据写入文件。

下面是脚本运行截图:

一种快速提取恶意软件解密逻辑代码的方法

下面的示例是使用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添加了一层压缩:

一种快速提取恶意软件解密逻辑代码的方法

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

总结

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

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

继续浏览有关 安全 的文章
发表评论