[C++] Windows Socket Programming 예제

[개발환경]
Windows 10 64bit

[개발도구]
MS Visual Studio 2022(v143, SDK 10.0)
Release x86

 

Initialising Winsock

//
#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, Mswsock.lib, and Advapi32.lib
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")

#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "27015"

int __cdecl main(int argc, char** argv)
{
    WSADATA wsa;
    SOCKET s;

    wprintf(L"\\nInitialising Winsock...\\n");
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
        wprintf(L"Failed. Error code : %d", WSAGetLastError());
        return 1;
    }

    wprintf(L"Initialised.\\n");
    return 0;
}

WSAStartup()은 인자 두개받음, 첫 번째 인자는 버전, 두번째 인자는 winsock이 로드되고 나서 추가 정보를 담을 WSADATA 구조체

정상적으로 실행되었다면 WSAStartup은 0을 반환한다.

에러가 발생했을 경우 0이 아닌 값을 반환하며, WSAGetLastError() 함수를 통해 더 많은 정보를 확인할 수 있다.

Creating Socket

int __cdecl main(int argc, char** argv)
{
    WSADATA wsa;
    SOCKET s;

    wprintf(L"\\nInitialising Winsock...\\n");
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
        wprintf(L"Failed. Error code : %d", WSAGetLastError());
        return 1;
    }

    wprintf(L"Initialised.\\n");

    if ((s = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
        wprintf(L"Could not create socket : %d", WSAGetLastError());
    }

    wprintf(L"Socket Created\\n");

    return 0;
}

socket() 함수는 소캣 식별자를 반환한다.

AF_INET(IPv4)

SOCK_STREAM(TCP Protocol)

Protocol(0, IPPROTO_TCP, IPPROTO_UDP)

https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-socket?redirectedfrom=MSDN

성공적으로 소캣을 생성했다. 이제 다른 서버로의 연결을 시도해볼 수 있다.

Connect to Server

다른 서버로의 연결을 위해서는 원격지의 주소와 포트번호를 알아야 한다.

sockaddr_in 이라는 구조체는 아래와 같이 구성되어 있으며 각각의 필드에 적절히 값을 채워준다.

// IPv4 AF_INET sockets:
struct sockaddr_in {
    short            sin_family;   // e.g. AF_INET, AF_INET6
    unsigned short   sin_port;     // e.g. htons(3490)
    struct in_addr   sin_addr;     // see struct in_addr, below
    char             sin_zero[8];  // zero this if you want to
};

typedef struct in_addr {
  union {
    struct {
      u_char s_b1,s_b2,s_b3,s_b4;
    } S_un_b;
    struct {
      u_short s_w1,s_w2;
    } S_un_w;
    u_long S_addr;
  } S_un;
} IN_ADDR, *PIN_ADDR, FAR *LPIN_ADDR;

struct sockaddr {
    unsigned short    sa_family;    // address family, AF_xxx
    char              sa_data[14];  // 14 bytes of protocol address
};

아직 서버측 프로그램이 작성되지 않았기 때문에 임시 서버 역할을 하는 구글(google.com)로 연결을 시도해 본다.

int __cdecl main(int argc, char** argv)
{
    WSADATA wsa;
    SOCKET s;
    struct sockaddr_in server;

    //Intialising Winsock
    wprintf(L"\\nInitialising Winsock...\\n");
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
        wprintf(L"Failed. Error code : %d", WSAGetLastError());
        return 1;
    }

    wprintf(L"Initialised.\\n");

    //Create Socket
    if ((s = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
        wprintf(L"Could not create socket : %d", WSAGetLastError());
    }

    wprintf(L"Socket Created\\n");

    server.sin_family = AF_INET;
        //connect to google.com
    InetPton(AF_INET, L"142.250.207.46", &server.sin_addr.s_addr);
    server.sin_port = htons(80);

    //Connect to Remote Server
    if (connect(s, (struct sockaddr*)&server, sizeof(server)) < 0) {
        wprintf(L"Connect error : %d\\n", WSAGetLastError());
        return 1;
    }
    wprintf(L"Connected\\n");

    return 0;
}

ping -c 1 google.com 명령을 통해 google의 IP를 확인할 수 있다.

이전에는 inet_addr(”IP주소”) 함수로 IP주소를 바인딩해주었지만, 보안 강화로 deprecated 되었다.

Windows에서는 InetPton()함수를 사용하여 바인딩해준다.

InetPton(address_family, string_to_be_converted, buffer_to_store_the_converted_string) 으로 사용한다.

IP 주소형식으로 된 문자열을 받아 지정한 구조체 멤버에 할당해준다.

! 연결(Connection)”의 개념은 TCP 소켓 통신에만 적용된다. TCP의 경우 통신을 시작하기 전에 연결을 수립한 이후 “Stream”을 통해 다른 데이터에 의해 간섭받지 않는 파이프로 통신한다.
UDP, ICMP, ARP와 같은 다른 소켓들은 “연결(Connect)”의 개념을 가지고 있지 않는 비연결 통신이다.
즉, 모든 소켓으로부터 패킷을 계속 보내고 받는다.

Sending Data

int __cdecl main(int argc, char** argv)
{
    WSADATA wsa;
    SOCKET s;
    struct sockaddr_in server;
    const char* message;

    //Intialising Winsock
    wprintf(L"\\nInitialising Winsock...\\n");
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
        wprintf(L"Failed. Error code : %d", WSAGetLastError());
        return 1;
    }

    wprintf(L"Initialised.\\n");

    //Create Socket
    if ((s = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
        wprintf(L"Could not create socket : %d", WSAGetLastError());
    }

    wprintf(L"Socket Created\\n");

    server.sin_family = AF_INET;
    InetPton(AF_INET, L"142.250.207.46", &server.sin_addr.s_addr);
    server.sin_port = htons(80);

    //Connect to Remote Server
    if (connect(s, (struct sockaddr*)&server, sizeof(server)) < 0) {
        wprintf(L"Connect error : %d\\n", WSAGetLastError());
        return 1;
    }
    wprintf(L"Connected\\n");

    //Send Some Data
    message = "GET / HTTP/1.1\\r\\n\\r\\n";
    if (send(s, message, strlen(message), 0) < 0)
    {
        wprintf(L"Send Failed : %d\\n", WSAGetLastError());
        return 1;
    }
    wprintf(L"Data Send\\n");

    return 0;
}

Receiving Data

recv() 함수를 이용하여 전송한 데이터를 수신한다.

int __cdecl main(int argc, char** argv)
{
    WSADATA wsa;
    SOCKET s;
    struct sockaddr_in server;
    const char* message;
    char server_reply[5000];
    int recv_size;

    //Intialising Winsock
    wprintf(L"\\nInitialising Winsock...\\n");
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
        wprintf(L"Failed. Error code : %d", WSAGetLastError());
        return 1;
    }

    wprintf(L"Initialised.\\n");

    //Create Socket
    if ((s = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
        wprintf(L"Could not create socket : %d", WSAGetLastError());
    }

    wprintf(L"Socket Created\\n");

    server.sin_family = AF_INET;
    InetPton(AF_INET, L"142.250.207.46", &server.sin_addr.s_addr);
    server.sin_port = htons(80);

    //Connect to Remote Server
    if (connect(s, (struct sockaddr*)&server, sizeof(server)) < 0) {
        wprintf(L"Connect error : %d\\n", WSAGetLastError());
        return 1;
    }
    wprintf(L"Connected\\n");

    //Send Some Data
    message = "GET / HTTP/1.1\\r\\n\\r\\n";
    if (send(s, message, strlen(message), 0) < 0)
    {
        wprintf(L"Send Failed : %d\\n", WSAGetLastError());
        return 1;
    }
    wprintf(L"Data Send\\n");

    //Receive a reply from the server
    if ((recv_size = recv(s, server_reply, 4999, 0)) == SOCKET_ERROR) {
        wprintf(L"Recv Failed\\n");
    }
    wprintf(L"Reply Received\\n");

    server_reply[recv_size] = '\\0';
    puts(server_reply);
    return 0;
}

응답을 받기 위한 변수를 선언하고, recv()함수를 이용하여 4999byte 만큼 수신한다. 문자열을 읽기 위해 5000번째 주소(recv_size)에 널문자(“\0”)를 삽입하여 문자열을 구분하였다.

HTTP 요청 및 응답이 잘 이루어지는 것을 확인할 수 있다.

  • 실행결과

Close Socket

통신을 완료한 후에는 소켓을 정리하고 닫아주어야 한다. 다음의 함수가 그 역할을 수행한다.

closesocket(s);
WSACleanup();

Review

  1. 소켓 생성(Create Socket)
  2. 원격 서버로의 연결(Connect)
  3. 데이터 전송(Send some data)
  4. 응답 수신(Receive a reply)

간단한 예제 작성을 통해 Windows 소켓 통신을 알아 보았다.

웹 서버에 요청을 보내고 응답을 받아 클라이언트에 랜더링 하는 브라우저의 통신을 모방해 보았다.

여기서 www.google.com은 웹 서버가 되고 작성한 클라이언트 프로그램은 웹 브라우저가 된다.

반응형

'Reversing' 카테고리의 다른 글

[Cheat Engine] Socket Program 분석  (0) 2022.05.21
[C++] Windows Socket Programming 예제 2  (0) 2022.05.18
[Frida] Hooking send(), recv()  (0) 2022.05.10
[Frida] Binary Hooking 2  (0) 2022.05.03
[Frida] Binary Hooking 1  (1) 2022.04.28