前言
前几年写过socket编程,后面很久不用就忘记了这块技术。最近在研究msf payload的执行原理,又得用到socket编程的技术,于是就有了这篇文章。
这篇文章跟msf中的技术没多少关系,属于一篇基础文章,看懂这篇文章后继续看msf payload原理就会有一种茅塞顿开的感觉。
windows下socket编程的特点
相比较于基于python的socket编程,windows下的socket编程十分复杂。需要多个步骤,还需要设置多个数据结构。
python 只需要两行:
而c语言需要很多很多很多行:
不过这里我还是希望大家能懂得用C语言去写socket,因为这样子更容易理解socket到底是个什么东西。
代码(服务端)
设置windows Sock规范版本号
windows下编程socket首先需要的是设置socket的版本,可以理解成希望进行socket连接的双方使用哪个版本的协议去通信,这是创建socket时最初的一步。
void set_editor() {
printf("开始设置版本号");
WSADATA container; //一个结构体,可以存储我们希望使用‘windows Sock规范’的版本号与ws2_32.dll支持的‘windows Sock规范’的最高的版本号。
WORD version_number; //存储版本号的容器
version_number = MAKEWORD(2, 2); //设置我们希望使用的‘windows Sock规范’的版本号,从后往前看的,版本号2.2,若为MSKEWORD(1,2),则版本号为2.1
if (WSAStartup(version_number, &container) < 0) {
// 设置我们希望使用的‘windows Sock规范’的版本号,如果设置失败则返回值不是0,如果成功则返回0
printf("ws2_32.dll is out of date.\n");
WSACleanup();//放弃加载ws2_32.dllwin。 备注:socket连接必须加载这个dll
exit(1);//退出程序,并输出错误码‘1’。
}
else
{
printf("win_socket规范版本设置成功\n");
}
}
定义socket的相关属性(ip,协议等)
我的理解,要创建一个socket需要分为两步,先利用getaddrinfo函数定义socket的具体属性,例如支持ip版本,tcp连接还是udp连接,socktype等。然后再利用socket函数来创建一个完整等socket,具体核心代码如下:
struct addrinfo* result = NULL; //我的理解是result存储的是hints的地址,getaddrinfo函数会将hints的地址传给result。
struct addrinfo hints; //存储着服务端的socket的相关详细数据。
ZeroMemory(&hints, sizeof(hints)); //初始化hints
hints.ai_family = AF_INET;//IPV4
hints.ai_socktype = SOCK_STREAM;//tcp模式
hints.ai_protocol = IPPROTO_TCP;//基于tcp协议
printf("开始执行getaddrinfo\n");
int Result = getaddrinfo("172.16.99.235", "55555", &hints, &result); //设置服务端的相关信息
if (Result != 0)
{
printf("getaddrinfo失败,错误码为%ld\n", WSAGetLastError());
safeexit();
}
printf("开始创建socket\n");
SOCKET sockett;
sockett = socket(result->ai_family, result->ai_socktype, result->ai_protocol);//创建一个socket
if (sockett == INVALID_SOCKET) {
printf("socket创建失败 %ld\n", WSAGetLastError());
freeaddrinfo(result);
safeexit();//这是我自己写等函数,用于安全退出socket。
}
绑定端口与ip
绑定端口与ip使用等是bind函数,绑定可以理解成把我们创建的socket与某一个端口绑定到一起,这时候别的主机访问这个端口就可以访问我们的端口就相当于申请跟我们建立socket连接.
因为socket的相关信息我们在第一步的时候通过getaddrinfo已经定义完了,这时候直接使用就行。
printf("开始bind端口\n");
Result = bind(sockett, result->ai_addr, (int)result->ai_addrlen);//将服务端的信息与socket进行绑定,相当于给socket指定一些详细信息,比如监听端口,使用协议等。
if (Result == SOCKET_ERROR) {
printf("绑定失败: %d\n", WSAGetLastError());
freeaddrinfo(result);
closesocket(sockett);
safeexit();
}
freeaddrinfo(result);//释放result使用的内存
至于bind里面等参数是怎么来的,可以去查微软官方文档,这里我只提供直接答案。
监听端口
绑定了信息之后就该打开端口监听了:
printf("开始listen\n");
Result = listen(sockett,SOMAXCONN);//开始监听端口,并将允许创建的连接数设置到最大
if (Result == SOCKET_ERROR) {
printf("监听失败 %d\n", WSAGetLastError());
closesocket(sockett);
safeexit();
}
整体比较简单没什么特别说的。
等待连接
监听到某个主机想连接到我们的时候,就需要调用accept函数来接受这个连接请求,具体代码实现如下:
char* recbuf = NULL;
char* sendbuf = NULL;
recbuf = "welcome !!!\n";
char* sockConnName = "Client";
SOCKET another_socket = accept(sockett,NULL, NULL);//等待连接,连接成功后创建指定当前socket链接为another_socket,过去的sockett可以关闭。
if (another_socket == INVALID_SOCKET) {
printf("接受链接失败 %d\n", WSAGetLastError());
closesocket(sockett);
safeexit();
}
closesocket(sockett);
printf("连接建立成功,关闭socket,准备传输数据\n");
附录:accept函数结构
这里说一下,accept后面的那两个参数的含义跟bind的三个参数的含义是一样的,只是说在这边我们使用NULL就代表不对想链接过来的主机做限制,如果想做限制的话可以定义一个addrinfo的结构体对象,就能限制使得指定条件的主机才能连接这个socket。
切记:使用accept函数后会生成一个新的socket连接,之前创建的socket可以使用closesocket函数抛弃掉。
发送数据
上面所有步骤结束后连接就已经建立好了,这时候可以选择发送数据,具体代码实现如下:
char* recbuf = NULL;
char* sendbuf = NULL;
recbuf = "welcome !!!\n";
char* sockConnName = "Client";
SOCKET another_socket = accept(sockett,NULL, NULL);//等待连接,连接成功后创建指定当前socket链接为another_socket,过去的sockett可以关闭。
if (another_socket == INVALID_SOCKET) {
printf("接受链接失败 %d\n", WSAGetLastError());
closesocket(sockett);
safeexit();
}
printf("连接建立成功,关闭socket,准备传输数据\n");
send(another_socket, recbuf, strlen(recbuf) + 1, 0); // 发送显示欢迎信息
int n = 20;
char recvBuf[11] = "1234567890";
int zonghe = 0;
while (n--) {
// 不断等待客户端请求的到来
char* wel = NULL;
wel = "Please enter the data:";
printf("一共传输20次数据,还剩%d次:\n", n + 1);
int num = recv(another_socket, recvBuf, 6, 0);
printf("%s : %s\n", sockConnName, recvBuf); // 接收信息
printf("当前轮次接收数据量为:%d Byte\n", num);
zonghe = zonghe + num;
printf("一共接受数据量为:%d Byte\n", zonghe);
send(another_socket, wel, strlen(wel) + 1, 0); // 发送显示欢迎信息
if (num == SOCKET_ERROR)
{
safeexit();
}
}
printf("数据接收完毕,退出程序");
shutdown(sockett, SD_SEND);
safeexit();
这里面值得说一下的就是send跟recv这两个函数,这两个函数的参数是一样的,分别是已经建立的socket连接的描述符,存储数据的buffer,发送或者接收数据的长度,最后一个为flag位具体用法看这篇文章,一般情况用0就好。
下面给出完整服务端代码,使用的时候可以用nc命令直接连接对应端口号即可:
#undef UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
// Need to link with Ws2_32.lib
#pragma comment (lib, "Ws2_32.lib")
// #pragma comment (lib, "Mswsock.lib")
#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "27015"
void safeexit()
{
WSACleanup();
exit(1);
}
/* 设置windows Sock规范版本号 */
void set_editor() {
printf("开始设置版本号");
WSADATA container; //一个结构体,可以存储我们希望使用‘windows Sock规范’的版本号与ws2_32.dll支持的‘windows Sock规范’的最高的版本号。
WORD version_number; //存储版本号的容器
version_number = MAKEWORD(2, 2); //设置我们希望使用的‘windows Sock规范’的版本号,从后往前看的,版本号2.2,若为MSKEWORD(1,2),则版本号为2.1
if (WSAStartup(version_number, &container) < 0) {
// 设置我们希望使用的‘windows Sock规范’的版本号,如果设置失败则返回值不是0,如果成功则返回0
printf("ws2_32.dll is out of date.\n");
WSACleanup();//放弃加载ws2_32.dllwin。 备注:socket连接必须加载这个dll
exit(1);//退出程序,并输出错误码‘1’。
}
else
{
printf("win_socket规范版本设置成功\n");
}
}
void llisten()
{
struct addrinfo* result = NULL; //我的理解是result存储的是hints的地址,getaddrinfo函数会将hints的地址传给result。
struct addrinfo hints; //存储着服务端的socket的相关详细数据。
ZeroMemory(&hints, sizeof(hints)); //初始化hints
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
printf("开始执行getaddrinfo\n");
int Result = getaddrinfo("172.16.99.235", "55555", &hints, &result); //设置服务端的相关信息
if (Result != 0)
{
printf("getaddrinfo失败,错误码为%ld\n", WSAGetLastError());
safeexit();
}
printf("开始创建socket\n");
SOCKET sockett;
sockett = socket(result->ai_family, result->ai_socktype, result->ai_protocol);//创建一个socket
if (sockett == INVALID_SOCKET) {
printf("socket创建失败 %ld\n", WSAGetLastError());
freeaddrinfo(result);
safeexit();
}
printf("开始bind端口\n");
Result = bind(sockett, result->ai_addr, (int)result->ai_addrlen);//将服务端的信息与socket进行绑定,相当于给socket指定一些详细信息,比如监听端口,使用协议等。
if (Result == SOCKET_ERROR) {
printf("绑定失败: %d\n", WSAGetLastError());
freeaddrinfo(result);
closesocket(sockett);
safeexit();
}
freeaddrinfo(result);//释放result使用的内存
printf("开始listen\n");
Result = listen(sockett, SOMAXCONN);//开始监听端口,并将允许创建的连接数设置到最大
if (Result == SOCKET_ERROR) {
printf("监听失败 %d\n", WSAGetLastError());
closesocket(sockett);
safeexit();
}
char* recbuf = NULL;
char* sendbuf = NULL;
recbuf = "welcome !!!\n";
char* sockConnName = "Client";
SOCKET another_socket = accept(sockett,NULL, NULL);//等待连接,连接成功后创建指定当前socket链接为another_socket,过去的sockett可以关闭。
if (another_socket == INVALID_SOCKET) {
printf("接受链接失败 %d\n", WSAGetLastError());
closesocket(sockett);
safeexit();
}
closesocket(sockett);
printf("连接建立成功,关闭socket,准备传输数据\n");
send(another_socket, recbuf, strlen(recbuf) + 1, 0); // 发送显示欢迎信息
int n = 20;
char recvBuf[11] = "1234567890";
int zonghe = 0;
while (n--) {
// 不断等待客户端请求的到来
char* wel = NULL;
wel = "Please enter the data:";
printf("一共传输20次数据,还剩%d次:\n", n + 1);
int num = recv(another_socket, recvBuf, 6, 0);
printf("%s : %s\n", sockConnName, recvBuf); // 接收信息
printf("当前轮次接收数据量为:%d Byte\n", num);
zonghe = zonghe + num;
printf("一共接受数据量为:%d Byte\n", zonghe);
send(another_socket, wel, strlen(wel) + 1, 0); // 发送显示欢迎信息
if (num == SOCKET_ERROR)
{
safeexit();
}
}
printf("数据接收完毕,退出程序");
shutdown(sockett, SD_SEND);
safeexit();
}
int main()
{
set_editor();
llisten();
}
运行结果:
windows上的服务端
linux上用nc连接:
可以在linux上输入数据进行传输: