序文
数年前にソケットプログラミングを書きましたが、長い間使用していなかったため、このテクノロジーを忘れていました。最近、msfペイロードの実行原理を研究していて、ソケットプログラミング技術を使わなければならなかったので、この記事が作成されました。
この記事はmsfのテクノロジーとは関係ありません。基本的な記事なので、この記事を読んだ後、msfペイロードの原則を読み続けると、混乱を感じるでしょう。
Windowsでのソケットプログラミングの機能
Pythonベースのソケットプログラミングと比較して、Windowsでのソケットプログラミングは非常に複雑です。複数のステップが必要であり、複数のデータ構造をセットアップする必要があります。
Pythonが必要とするのは2行だけです
。C言語では多くの行が必要です。
しかし、ここでは、ソケットが何であるかを理解しやすいので、Cでソケットを記述する方法を誰もが知ってほしいと思っています。
コード(サーバー)
Windows Sock仕様のバージョン番号を設定する
Windowsでソケットをプログラムするために最初に必要なことは、ソケットのバージョンを設定することです。これは、2つのパーティが通信するためにソケット接続に使用するプロトコルのバージョンとして理解できます。これは、ソケットを作成するときの最初のステップです。
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");
}
}
ソケットの関連属性(ip、プロトコルなど)を定義します。
私の理解では、ソケットを作成するには2つのステップに分割する必要があります。最初にgetaddrinfo関数を使用して、ソケットの特定の属性(ipバージョン、tcp接続またはudp接続、socktypeなどのサポート)を定義します。次に、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の使用法はバインド関数です。バインドは、作成したソケットを特定のポートにバインドすることと理解できます。現時点では、他のホストがこのポートにアクセスでき、ポートにアクセスできます。これは、ソケット接続を確立します。
最初のステップで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のパラメーターの取得方法については、公式のMicrosoftドキュメントを確認できますが、ここでは直接回答します。
リスニングポート
情報をバインドしたら、ポートを開いて待機します。
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");
付録:関数構造を受け入れる
それについてお話しましょう。受け入れ後の2つのパラメーターの意味は、バインドの3つのパラメーターの意味と同じです。リンク先のホストに制限を課すために、ここでNULLを使用しているだけです。制限したい場合は、 addrinfo構造オブジェクトを定義すると、指定された条件でホストを制限して、このソケットに接続できます。
注意:accept関数を使用した後、新しいソケット接続が生成され、前に作成されたソケットは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();
ここで、2つの関数sendとrecvについて説明しておきます。これら2つの関数のパラメーターは同じです。これらは、確立されたソケット接続の記述子、データを格納するバッファー、送信または受信したデータの長さ、最後の1つです。フラグビットの具体的な使用方法については、こちらの記事を参照してください。通常、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で送信するデータを入力できます。