# Malware Analysis Practice1

# 1.1、通过修改注册表添加服务的方式实现自启动

# 实验要求

编写代码,编辑注册表的 Run/RunOnce/RunOnceEx 键(任选其一,并明确三个键的区别),达到让某一程序在系统启动后自动运行的目的(可以以计算器、记事本等作为目标程序)。

以服务方式实现自启动,以 DLL 或者 EXE 方式均可。

# 实验环境

Windows 7 或 Windows 10 主机(虚拟机);

代码编辑器;

C/C++ 代码运行所需环境。

# 实验目的

了解恶意代码自启动常用手段。

# 实验步骤和方法

下面为实现自启动计算器功能代码

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
#include <Windows.h>
#include <stdio.h>
#include <string.h>

int main() {
HKEY hKey;
LONG regResult;
const char* name = "auto_run_calc";
const char* value = "C:\\Windows\\System32\\calc.exe";

regResult = RegOpenKeyExA(
HKEY_CURRENT_USER,
"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
0,
KEY_WRITE,
&hKey
);

if (regResult != ERROR_SUCCESS) {
printf("无法打开注册表键,错误代码: %ld\n", regResult);
if (regResult == ERROR_ACCESS_DENIED) {
printf("可能缺少管理员权限\n");
}
return 1;
}

regResult = RegSetValueExA(
hKey,
name,
0,
REG_SZ,
(const BYTE*)value,
(DWORD)(strlen(value) + 1)
);

if (regResult != ERROR_SUCCESS) {
printf("自启动项写入失败,错误代码: %ld\n", regResult);
RegCloseKey(hKey);
return 1;
}

printf("自启动项写入成功!\n名称: %s\n路径: %s\n", name, value);
RegCloseKey(hKey);
return 0;
}

可以看到代码成功执行

查看注册表,已经 成功 写入

# Run / RunOnce / RunOnceEx 详细区别与实现

# 详细对比表格

特性 Run RunOnce RunOnceEx
执行时机 每次用户登录时 下一次用户登录时 下一次用户登录时
执行后处理 保留在注册表中 自动删除 自动删除
执行顺序 无特定顺序 按字母顺序执行 支持定义依赖关系和顺序
适用场景 常驻程序、守护进程 一次性安装、清理任务 复杂安装、多步骤配置
错误处理 无特殊机制 失败可能重试 支持错误处理和回滚

# 具体代码实现

# 1. Run 键实现(持久化自启动)

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
#include <windows.h>
#include <iostream>

BOOL SetRunAutoStart(const char* programName, const char* programPath) {
HKEY hKey;
LONG lResult;
const char* keyPath = "Software\\Microsoft\\Windows\\CurrentVersion\\Run";

lResult = RegOpenKeyEx(HKEY_CURRENT_USER,
keyPath,
0,
KEY_WRITE,
&hKey);

if (lResult != ERROR_SUCCESS) {
return FALSE;
}

lResult = RegSetValueEx(hKey,
programName,
0,
REG_SZ,
(BYTE*)programPath,
strlen(programPath) + 1);

RegCloseKey(hKey);
return (lResult == ERROR_SUCCESS);
}

BOOL RemoveRunAutoStart(const char* programName) {
HKEY hKey;
LONG lResult;
const char* keyPath = "Software\\Microsoft\\Windows\\CurrentVersion\\Run";

lResult = RegOpenKeyEx(HKEY_CURRENT_USER, keyPath, 0, KEY_WRITE, &hKey);
if (lResult != ERROR_SUCCESS) return FALSE;

lResult = RegDeleteValue(hKey, programName);
RegCloseKey(hKey);
return (lResult == ERROR_SUCCESS);
}

# 2. RunOnce 键实现(一次性执行)

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

BOOL SetRunOnceAutoStart(const char* programName, const char* programPath) {
HKEY hKey;
LONG lResult;
const char* keyPath = "Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce";

lResult = RegOpenKeyEx(HKEY_CURRENT_USER, keyPath, 0, KEY_WRITE, &hKey);
if (lResult != ERROR_SUCCESS) return FALSE;

lResult = RegSetValueEx(hKey,
programName,
0,
REG_SZ,
(BYTE*)programPath,
strlen(programPath) + 1);

RegCloseKey(hKey);
return (lResult == ERROR_SUCCESS);
}

# 3. RunOnceEx 键实现(高级一次性执行)

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
#include <windows.h>
#include <iostream>

// RunOnceEx 支持更复杂的结构,可以设置依赖关系和标志
BOOL SetRunOnceExAutoStart() {
HKEY hKey, hSubKey;
LONG lResult;
const char* keyPath = "Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx";

// 创建RunOnceEx键
lResult = RegCreateKeyEx(HKEY_CURRENT_USER,
keyPath,
0,
NULL,
REG_OPTION_NON_VOLATILE,
KEY_WRITE,
NULL,
&hKey,
NULL);

if (lResult != ERROR_SUCCESS) return FALSE;

// 创建子键用于具体任务
const char* subKeyPath = "Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx\\0001";
lResult = RegCreateKeyEx(HKEY_CURRENT_USER,
subKeyPath,
0,
NULL,
REG_OPTION_NON_VOLATILE,
KEY_WRITE,
NULL,
&hSubKey,
NULL);

if (lResult == ERROR_SUCCESS) {
// 设置要执行的程序
char exePath[MAX_PATH];
GetModuleFileName(NULL, exePath, MAX_PATH);

RegSetValueEx(hSubKey,
"", // 默认值
0,
REG_SZ,
(BYTE*)exePath,
strlen(exePath) + 1);

// 可以设置其他参数
const char* description = "一次性安装任务";
RegSetValueEx(hSubKey,
"Desc",
0,
REG_SZ,
(BYTE*)description,
strlen(description) + 1);

RegCloseKey(hSubKey);
}

RegCloseKey(hKey);
return TRUE;
}

# 1.2、以服务方式实现自启动,以 EXE 方式。

# 实验步骤和方法

下面为实现以服务方式实现自启动计算器,以 EXE 方式的代码。

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
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <locale.h>

int main() {
SC_HANDLE schSCManager, schService;

// 设置本地化以支持中文输出
setlocale(LC_ALL, "chs");

// 计算器的完整路径
TCHAR szPath[MAX_PATH] = _T("C:\\Windows\\System32\\calc.exe");

printf("=== 计算器服务安装程序 ===\n");
printf("目标程序: %s\n", szPath);

// 打开服务控制管理器
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (schSCManager == NULL) {
DWORD error = GetLastError();
if (error == ERROR_ACCESS_DENIED) {
printf("访问被拒绝。请以管理员身份运行程序。\n");
}
else {
printf("打开服务控制管理器失败 (%d)\n", error);
}
return 1;
}

printf("服务控制管理器打开成功。\n");

// 创建服务并设置其启动参数
schService = CreateService(
schSCManager, // SCManager database
_T("CalculatorService"), // name of service
_T("Windows Calculator Service"), // name to display
SERVICE_ALL_ACCESS, // desired access
SERVICE_WIN32_OWN_PROCESS, // service type
SERVICE_AUTO_START, // start type - 自动启动
SERVICE_ERROR_NORMAL, // error control type
szPath, // path to calculator executable
NULL, // no load ordering group
NULL, // no tag identifier
NULL, // no dependencies
NULL, // LocalSystem account
NULL // no password
);

if (schService == NULL) {
DWORD error = GetLastError();
fprintf(stderr, "创建服务失败 (%d)\n", error);

// 提供更详细的错误信息
if (error == ERROR_SERVICE_EXISTS) {
printf("服务已存在。请先使用 'sc delete CalculatorService' 删除它。\n");
}
else if (error == ERROR_PATH_NOT_FOUND) {
printf("在指定路径找不到计算器可执行文件。\n");
}
else if (error == ERROR_ACCESS_DENIED) {
printf("访问被拒绝。需要管理员权限。\n");
}

CloseServiceHandle(schSCManager);
return 1;
}
else {
printf("计算器服务安装成功。\n");
printf("服务名称: CalculatorService\n");
printf("显示名称: Windows Calculator Service\n");
printf("启动类型: 自动启动\n");
}

// 设置服务描述 - 使用可写缓冲区
TCHAR serviceDescription[] = _T("Automatically starts Windows Calculator as a system service");
SERVICE_DESCRIPTION sd;
sd.lpDescription = serviceDescription;

if (ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &sd)) {
printf("服务描述设置成功。\n");
}
else {
printf("设置服务描述失败 (%d)\n", GetLastError());
}

// 启动服务
printf("正在启动计算器服务...\n");
if (!StartService(schService, 0, NULL)) {
DWORD error = GetLastError();
fprintf(stderr, "启动服务失败 (%d)\n", error);

if (error == ERROR_SERVICE_ALREADY_RUNNING) {
printf("服务已在运行。\n");
}
else if (error == ERROR_SERVICE_DISABLED) {
printf("服务已被禁用。\n");
}
}
else {
printf("计算器服务启动成功。\n");

// 等待一会儿让服务启动
Sleep(2000);

// 验证服务状态
SERVICE_STATUS_PROCESS ssStatus;
DWORD dwBytesNeeded;
if (QueryServiceStatusEx(schService, SC_STATUS_PROCESS_INFO,
(LPBYTE)&ssStatus, sizeof(SERVICE_STATUS_PROCESS),
&dwBytesNeeded)) {
printf("服务当前状态: ");
switch (ssStatus.dwCurrentState) {
case SERVICE_STOPPED: printf("已停止\n"); break;
case SERVICE_START_PENDING: printf("启动中\n"); break;
case SERVICE_STOP_PENDING: printf("停止中\n"); break;
case SERVICE_RUNNING: printf("运行中\n"); break;
case SERVICE_CONTINUE_PENDING: printf("继续中\n"); break;
case SERVICE_PAUSE_PENDING: printf("暂停中\n"); break;
case SERVICE_PAUSED: printf("已暂停\n"); break;
default: printf("未知状态\n"); break;
}
}
}

// 关闭句柄
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);

printf("\n=== 安装摘要 ===\n");
printf("服务已安装: CalculatorService\n");
printf("目标程序: C:\\Windows\\System32\\calc.exe\n");
printf("启动类型: 自动\n");
printf("\n服务管理命令:\n");
printf(" 启动: sc start CalculatorService\n");
printf(" 停止: sc stop CalculatorService\n");
printf(" 删除: sc delete CalculatorService\n");
printf(" 查询: sc query CalculatorService\n");

printf("\n按任意键退出...\n");
getchar();

return 0;
}

链接 advapi32.lib 库后在 Developer Command Prompt for VS 2022 中管理员身份下编译得到 exe

1
cl FileName.cpp advapi32.lib

在成功编译后运行 FileName.exe,可以看到服务已经安装成功,但是因为计算器 (calc.exe) 本身不是一个服务程序,是普通 GUI 应用程序,后续应该有特殊的服务入口点和服务控制处理器

查看服务管理器,服务已经安装 成功

# 2.1、DLL 文件编写

# 实验要求

编写一个 DLL,使得在动态加载该 DLL 时,能够弹出 “目标 DLL 已加载” 的对话框。同时,为 DLL 添加两个导出函数,分别实现读取文件并打印出来,以及写入文件的功能,并且能够被其他程序动态调用。

# 实验环境

Windows 7 或 Windows 10 主机(虚拟机);

代码编辑器;

C/C++ 代码运行所需环境。

# 实验目的

了解 DLL 的作用和调用其函数的方法。

# 实验步骤和方法

编写 dll 文件,MyFileDLL.cpp, 并且及时将项目属性改为动态链接库

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
#include <windows.h>
#include <fstream>
#include <iostream>
#include <string>

// DLL入口点
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
MessageBoxA(NULL, "目标DLL已加载", "DLL加载提示", MB_OK | MB_ICONINFORMATION);
}
return TRUE;
}

// 导出函数
extern "C" {
__declspec(dllexport) void ShowLoadMessage();
__declspec(dllexport) BOOL ReadAndPrintFile(LPCSTR filename);
__declspec(dllexport) BOOL WriteToFile(LPCSTR filename, LPCSTR content);
}

void ShowLoadMessage() {
MessageBoxA(NULL, "ShowLoadMessage函数被调用", "函数调用", MB_OK);
}

BOOL ReadAndPrintFile(LPCSTR filename) {
std::ifstream file(filename);
if (!file.is_open()) {
MessageBoxA(NULL, "无法打开文件", "错误", MB_OK | MB_ICONERROR);
return FALSE;
}

std::string line;
std::string content;
while (std::getline(file, line)) {
content += line + "\n";
}
file.close();

MessageBoxA(NULL, content.c_str(), "文件内容", MB_OK);
return TRUE;
}

BOOL WriteToFile(LPCSTR filename, LPCSTR content) {
std::ofstream file(filename);
if (!file.is_open()) {
MessageBoxA(NULL, "无法创建文件", "错误", MB_OK | MB_ICONERROR);
return FALSE;
}

file << content;
file.close();

MessageBoxA(NULL, "内容已写入文件", "成功", MB_OK);
return TRUE;
}

链接 user32.lib 库后编译代码

1
cl /LD MyFileDLL.cpp user32.lib

可以看到成功产生 MyFileDLL.dll/lib 文件,说明编译成功

然后创建测试代码 TestDLL.cpp, 并且在编译后产生 exe

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
#include <windows.h>
#include <iostream>

using namespace std;

typedef void(*ShowMessageFunc)();
typedef BOOL(*ReadFileFunc)(LPCSTR);
typedef BOOL(*WriteFileFunc)(LPCSTR, LPCSTR);

int main() {
cout << "开始加载DLL..." << endl;

HMODULE hDll = LoadLibraryA("MyFileDLL.dll");
if (!hDll) {
cout << "DLL加载失败!" << endl;
return 1;
}
cout << "DLL加载成功!" << endl;

// 获取函数
ShowMessageFunc showMessage = (ShowMessageFunc)GetProcAddress(hDll, "ShowLoadMessage");
ReadFileFunc readFile = (ReadFileFunc)GetProcAddress(hDll, "ReadAndPrintFile");
WriteFileFunc writeFile = (WriteFileFunc)GetProcAddress(hDll, "WriteToFile");

if (showMessage && readFile && writeFile) {
showMessage(); // 这会弹出对话框

writeFile("test.txt", "Hello from DLL!");
readFile("test.txt");

cout << "所有操作完成!" << endl;
}
else {
cout << "获取函数失败!" << endl;
}

FreeLibrary(hDll);
cout << "程序结束,按任意键退出..." << endl;
cin.get();
return 0;
}

运行 TestDLL.exe,看到已经成功加载

DLL 已经 成功 运行并且调用

1
2
3
4
5
6
7
8
D:\SOFEWARE\VS\project\Project1111>TestDLL.exe
开始加载DLL...
DLL加载成功!
内容已写入文件: test.txt
文件内容:
Hello from DLL!
所有操作完成!
程序结束,按任意键退出...

# 2.2、DLL 的作用和调用其函数的方法

# 一、DLL 的作用

# 1.1 什么是 DLL

DLL(Dynamic Link Library,动态链接库) 是包含可由多个程序同时使用的代码和数据的库文件。

# 1.2 主要作用

# 代码共享和重用

1
2
3
4
// 多个程序可以共享同一个DLL中的函数
// 程序A ──┐
// ├─→ MyMath.dll (包含计算函数)
// 程序B ──┘

# 模块化开发

1
2
3
4
5
// 将系统分解为独立模块
- UI.dll // 用户界面模块
- Database.dll // 数据库操作模块
- Network.dll // 网络通信模块
- Business.dll // 业务逻辑模块

# 节省内存和磁盘空间

  • 多个程序共享同一个 DLL 的物理内存副本
  • 磁盘上只需存储一份 DLL 文件

# 易于更新和维护

1
2
3
// 更新DLL而不重新编译主程序
版本1: Calculator.dll v1.0
版本2: Calculator.dll v1.1 // 直接替换,主程序无需修改

# 支持多语言开发

1
2
3
4
// C++编写的DLL可以被C#、VB、Python等调用
C++ DLL → C#程序
→ Python脚本
→ VB应用

# 二、DLL 的调用方法

# 2.1 动态加载(显式链接)

# 使用 Windows 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
#include <windows.h>
#include <iostream>

typedef int (*AddFunc)(int, int);
typedef void (*ShowMessageFunc)();

int main() {
// 1. 加载DLL
HMODULE hDll = LoadLibrary("MyDLL.dll");
if (!hDll) {
std::cout << "无法加载DLL!" << std::endl;
return 1;
}

// 2. 获取函数地址
AddFunc add = (AddFunc)GetProcAddress(hDll, "Add");
ShowMessageFunc showMsg = (ShowMessageFunc)GetProcAddress(hDll, "ShowMessage");

// 3. 调用函数
if (add) {
int result = add(5, 3);
std::cout << "5 + 3 = " << result << std::endl;
}

if (showMsg) {
showMsg();
}

// 4. 卸载DLL
FreeLibrary(hDll);
return 0;
}

# 动态加载的特点

  • 灵活性:运行时决定加载哪个 DLL
  • 内存效率:需要时加载,不需要时卸载
  • 错误处理:可以处理 DLL 加载失败的情况
  • 复杂性:需要手动管理函数指针

# 2.2 静态加载(隐式链接)

# 使用导入库 (.lib)

1
2
3
4
5
6
7
8
9
// 方法1:使用 __declspec(dllimport)
__declspec(dllimport) int Add(int a, int b);
__declspec(dllimport) void ShowMessage();

int main() {
int result = Add(10, 20); // 直接调用,像普通函数一样
ShowMessage();
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
// 方法2:使用导入库链接
#pragma comment(lib, "MyDLL.lib")

// 声明外部函数
extern "C" int Add(int a, int b);
extern "C" void ShowMessage();

int main() {
Add(5, 3); // 编译器自动处理函数调用
ShowMessage();
return 0;
}

# 静态加载的特点

  • 简单易用:像调用普通函数一样
  • 编译时检查:编译器可以检查函数签名
  • 自动加载:程序启动时自动加载 DLL
  • 缺乏灵活性:DLL 必须在程序目录或系统路径中

# 三、DLL 的创建方法

# 3.1 导出函数的方法

# 方法 1:使用 __declspec (dllexport)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// MathDLL.h
#ifdef MATHDLL_EXPORTS
#define MATHDLL_API __declspec(dllexport)
#else
#define MATHDLL_API __declspec(dllimport)
#endif

extern "C" {
MATHDLL_API int Add(int a, int b);
MATHDLL_API int Subtract(int a, int b);
}

// MathDLL.cpp
#define MATHDLL_EXPORTS
#include "MathDLL.h"

MATHDLL_API int Add(int a, int b) {
return a + b;
}

MATHDLL_API int Subtract(int a, int b) {
return a - b;
}

# 方法 2:使用模块定义文件 (.def)

1
2
3
4
5
6
7
8
9
10
11
// MathDLL.def
LIBRARY MathDLL
EXPORTS
Add @1
Subtract @2

// MathDLL.cpp
extern "C" {
int Add(int a, int b) { return a + b; }
int Subtract(int a, int b) { return a - b; }
}

# 3.2 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
#include <windows.h>

BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
// DLL被加载到进程时调用
MessageBox(NULL, "DLL已加载", "提示", MB_OK);
break;

case DLL_PROCESS_DETACH:
// DLL从进程卸载时调用
break;

case DLL_THREAD_ATTACH:
// 新线程创建时调用
break;

case DLL_THREAD_DETACH:
// 线程结束时调用
break;
}
return TRUE;
}

# 四、总结

特性 动态加载 静态加载
加载时机 运行时按需加载 程序启动时自动加载
灵活性 高,可选择性加载 低,必须存在
复杂性 高,需要手动管理 低,自动管理
错误处理 可处理加载失败 程序无法启动
内存使用 按需使用 启动时占用

DLL 技术的核心价值

  • 模块化:促进代码组织和团队协作
  • 可扩展性:支持插件和功能扩展
  • 资源优化:共享代码,减少重复
  • 维护便利:独立更新组件

通过掌握 DLL 的创建和调用方法,可以构建更加灵活、可维护的软件系统。

# 3、简单多线程服务器

# 实验要求

编写一个简单的 echo 服务器程序(即:客户端与服务器建立连接后,在客户端输入消息,服务器端就会打印输入的消息),在 4444 端口进行监听。每有一个客户端进行连接时候,服务器创建一个子线程,对客户端程序进行服务。需要引入互斥量对共享代码区或全局变量进行互斥访问,要求使用信号传递等待机制。根据要求,客户端代码也需要自行编写。

# 实验环境

Windows 7 或 Windows 10 主机(虚拟机)至少两台;

代码编辑器;

C/C++ 代码运行所需环境。

# 实验目的

了解恶意代码在通信时,会使用到的最基本的技术。练习多线程编程。

# 实验步骤和方法

在主机和虚拟机上完成 mingw32/64 配置,将 bin 加入环境变量

以主机作为服务器端完成

服务器端代码

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <windows.h>

#pragma comment(lib, "ws2_32.lib")

#define PORT 4444
#define BUFFER_SIZE 1024

// 共享资源 - 客户端连接计数
int client_count = 0;
HANDLE count_mutex;

// 线程函数声明
DWORD WINAPI client_handler(LPVOID data);

int main() {
WSADATA wsa;
SOCKET server_socket, client_socket;
struct sockaddr_in server_addr, client_addr;
int client_addr_len = sizeof(client_addr);
HANDLE thread_handle;
DWORD thread_id;

printf("初始化Winsock...\n");
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
printf("WSAStartup失败。错误代码: %d\n", WSAGetLastError());
return 1;
}
printf("Winsock初始化成功。\n");

// 创建互斥量
count_mutex = CreateMutex(NULL, FALSE, NULL);
if (count_mutex == NULL) {
printf("创建互斥量失败。错误代码: %d\n", GetLastError());
WSACleanup();
return 1;
}

// 创建服务器socket
if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
printf("创建socket失败。错误代码: %d\n", WSAGetLastError());
WSACleanup();
return 1;
}
printf("服务器socket创建成功。\n");

// 设置服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);

// 绑定socket
if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
printf("绑定失败。错误代码: %d\n", WSAGetLastError());
closesocket(server_socket);
WSACleanup();
return 1;
}
printf("绑定到端口 %d 成功。\n", PORT);

// 开始监听
if (listen(server_socket, 5) == SOCKET_ERROR) {
printf("监听失败。错误代码: %d\n", WSAGetLastError());
closesocket(server_socket);
WSACleanup();
return 1;
}
printf("服务器开始在端口 %d 监听连接...\n", PORT);

// 主循环,接受客户端连接
while (1) {
// 接受客户端连接
client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
if (client_socket == INVALID_SOCKET) {
printf("接受连接失败。错误代码: %d\n", WSAGetLastError());
continue;
}

printf("新的客户端连接来自: %s:%d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

// 创建新线程处理客户端
thread_handle = CreateThread(NULL, 0, client_handler, (LPVOID)client_socket, 0, &thread_id);
if (thread_handle == NULL) {
printf("创建线程失败。错误代码: %d\n", GetLastError());
closesocket(client_socket);
} else {
printf("为新客户端创建线程成功,线程ID: %lu\n", thread_id);
CloseHandle(thread_handle);
}
}

// 清理资源
closesocket(server_socket);
CloseHandle(count_mutex);
WSACleanup();
return 0;
}

// 客户端处理线程函数
DWORD WINAPI client_handler(LPVOID data) {
SOCKET client_socket = (SOCKET)data;
char buffer[BUFFER_SIZE];
int bytes_received;
struct sockaddr_in client_addr;
int client_addr_len = sizeof(client_addr);

// 获取客户端地址信息
getpeername(client_socket, (struct sockaddr*)&client_addr, &client_addr_len);
char *client_ip = inet_ntoa(client_addr.sin_addr);
int client_port = ntohs(client_addr.sin_port);

// 使用互斥量安全地增加客户端计数
WaitForSingleObject(count_mutex, INFINITE);
client_count++;
int current_client_id = client_count;
printf("客户端 [%s:%d] 连接,分配ID: %d。当前总客户端数: %d\n",
client_ip, client_port, current_client_id, client_count);
ReleaseMutex(count_mutex);

// 与客户端通信
while (1) {
// 接收数据
bytes_received = recv(client_socket, buffer, BUFFER_SIZE - 1, 0);
if (bytes_received == SOCKET_ERROR) {
printf("从客户端 %d 接收数据错误。错误代码: %d\n", current_client_id, WSAGetLastError());
break;
}
if (bytes_received == 0) {
printf("客户端 %d 断开连接。\n", current_client_id);
break;
}

// 添加字符串结束符
buffer[bytes_received] = '\0';

// 去除换行符(可选)
if (buffer[bytes_received - 1] == '\n') {
buffer[bytes_received - 1] = '\0';
}

// 打印接收到的消息(受互斥量保护)
WaitForSingleObject(count_mutex, INFINITE);
printf("来自客户端 %d [%s:%d] 的消息: %s\n",
current_client_id, client_ip, client_port, buffer);
ReleaseMutex(count_mutex);

// 回显消息给客户端
if (send(client_socket, buffer, bytes_received, 0) == SOCKET_ERROR) {
printf("向客户端 %d 发送数据错误。错误代码: %d\n", current_client_id, WSAGetLastError());
break;
}
}

// 客户端断开连接,减少计数
WaitForSingleObject(count_mutex, INFINITE);
client_count--;
printf("客户端 %d 断开连接。当前总客户端数: %d\n", current_client_id, client_count);
ReleaseMutex(count_mutex);

// 关闭socket
closesocket(client_socket);
return 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
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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib")

#define SERVER_IP "192.168.168.245" // 服务器IP地址
#define PORT 4444
#define BUFFER_SIZE 1024

int main() {
WSADATA wsa;
SOCKET client_socket;
struct sockaddr_in server_addr;
char buffer[BUFFER_SIZE];
int bytes_received;

printf("初始化Winsock...\n");
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
printf("WSAStartup失败。错误代码: %d\n", WSAGetLastError());
return 1;
}
printf("Winsock初始化成功。\n");

// 创建客户端socket
if ((client_socket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
printf("创建socket失败。错误代码: %d\n", WSAGetLastError());
WSACleanup();
return 1;
}
printf("客户端socket创建成功。\n");

// 设置服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
server_addr.sin_port = htons(PORT);

// 连接服务器
if (connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
printf("连接服务器失败。错误代码: %d\n", WSAGetLastError());
closesocket(client_socket);
WSACleanup();
return 1;
}
printf("成功连接到服务器 %s:%d\n", SERVER_IP, PORT);
printf("输入消息发送到服务器,输入 'quit' 退出\n");

// 与服务器通信
while (1) {
printf("客户端: ");
fgets(buffer, BUFFER_SIZE, stdin);

// 检查是否退出
if (strncmp(buffer, "quit", 4) == 0) {
break;
}

// 发送消息到服务器
if (send(client_socket, buffer, strlen(buffer), 0) == SOCKET_ERROR) {
printf("发送失败。错误代码: %d\n", WSAGetLastError());
break;
}

// 接收服务器回显
bytes_received = recv(client_socket, buffer, BUFFER_SIZE - 1, 0);
if (bytes_received == SOCKET_ERROR) {
printf("接收失败。错误代码: %d\n", WSAGetLastError());
break;
}
if (bytes_received == 0) {
printf("服务器断开连接。\n");
break;
}

buffer[bytes_received] = '\0';
printf("服务器回显: %s", buffer);
}

// 清理资源
closesocket(client_socket);
WSACleanup();
printf("客户端退出。\n");
return 0;
}

将两端代码进行编译,得到 server.exe 和 client.exe

1
2
3
4
5
# 编译服务器
gcc -o server.exe server.c -lws2_32

# 编译客户端
gcc -o client.exe client.c -lws2_32
  1. 首先运行服务器:

    1
    server.exe
  2. 然后运行客户端(可以在多台机器或同一台机器的多个终端运行):

    1
    client.exe

可以看到在 server.exe 和 client.exe 运行后, 成功 实现了网络通信

# 核心代码分析

# 线程同步关键代码

1
2
3
4
5
6
7
// 互斥量保护共享资源
WaitForSingleObject(count_mutex, INFINITE);
client_count++;
int current_client_id = client_count;
printf("客户端连接,分配ID: %d。当前总客户端数: %d\n",
current_client_id, client_count);
ReleaseMutex(count_mutex);

# 客户端服务线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DWORD WINAPI client_handler(LPVOID data) {
SOCKET client_socket = (SOCKET)data;
// 线程主循环
while (1) {
bytes_received = recv(client_socket, buffer, BUFFER_SIZE - 1, 0);
if (bytes_received <= 0) break;

// 处理接收到的消息
buffer[bytes_received] = '\0';

// 回显消息
send(client_socket, buffer, bytes_received, 0);
}
return 0;
}

# 程序特点

  1. 多线程处理:每个客户端连接都在独立的线程中处理
  2. 互斥量保护:使用 WaitForSingleObjectReleaseMutex 保护共享变量
  3. 信号等待机制:互斥量提供了线程间的同步
  4. 错误处理:完善的错误检测和处理机制
  5. 资源管理:正确关闭 socket 和清理资源

这个程序演示了恶意代码通信时常用的基本技术:网络通信、多线程处理、线程同步