Программирование сокетов в контексте реверс-инжиниринга | OTUS

Программирование сокетов в контексте реверс-инжиниринга

В этой статье мы рассмотрим программирование сокетов и разберём простейшую систему клиент-серверного TCP-чата. Понимание аспектов сетевого программирования поможет вам продвинуться в изучении реверс-инжиниринга.

Но прежде, чем мы приступим к разбору кода сервера либо клиента, необходимо (и это важно) внести в код следующую строку:

#define PORT 1337

Данная строка определяет константу PORT как 1337. Константа станет использоваться и на клиенте, и на сервере в роли сетевого порта, применяемого для создания соединения.

Рассмотрим серверную часть

Итак, код:

int Server() {
    // инициализируем переменные, нужные для работы сервера
    int server, sock, value;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[1024] = {0};
    const char *serverhello = "Server Hello";

    // создаём сокет
    server = socket(AF_INF, SOCK_STREAM, 0);

    // настраиваем сокет
    setsockopt(server, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));

    // настраиваем адрес
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // привязываем сокет к серверу
    bind(server, (struct sockaddr *)&address, (socklen_t*)&addrlen);

    // ожидание клиентов
    listen(server, 3);

    // принимаем соединение
    sock = accept(server, (struct sockaddr *)&address, (socklen_t*)&addrlen);

    // читаем сообщение, полученное посредством сокета
    value = read(sock, buffer, 1024);

    printf("%s\n", buffer);

    // отсылаем сообщение посредством сокета
    send(sock, serverhello, strlen(serverhello), 0);
    return 0;
}

Поначалу мы создаём файловый дескриптор сокета server с доменом AF_INET, кодом протокола 0 и типом SOCK_STREAM. Потом происходит настройка параметров сокета и адрес. Далее сокет привязывается к порту (сетевому адресу), а сервер начинает прослушивать указанный порт с наибольшей длиной очереди 3. В результате, после получения соединения сервер примет его в переменную sock и считает переданное значение в переменную value.

В конце концов, сервер отправит строку serverhello по соединению до возврата функции.

Что же, пришло время посмотреть, как выглядит инициализация серверных переменных в машинном коде:

reverse_dev_pic_21_1-20219-4f3ad6.jpeg

Поначалу создаются и инициализируются переменные сервера.

reverse_dev_pic_22_1-20219-e0bf02.jpeg

Потом происходит создание файлового дескриптора сокетов server посредством системной функции _socket. В качестве параметров для функции выступают протокол, тип и доменное имя, которые передаются посредством регистров edx, esi и edi.

reverse_dev_pic_23_1-20219-32257e.jpeg

Далее происходит вызов _setsockopt, что необходимо для задания параметров сокета в файле дескриптора “server».

reverse_dev_pic_24_2-20219-f086a8.jpg

Инициализация серверного адреса происходит посредством adress.sin_family, address.sin_addr.s_addr и address.sin_port.

reverse_dev_pic_25_2-20219-cfc0d4.jpg

После конфигурирования сервера он привязывается к интернет-адресу посредством _bind.

reverse_dev_pic_26_1-20219-6d7496.jpeg

А после привязки сервер слушает сокет, передавая файловый дескриптор server. При этом наибольшая длина очереди равняется трём.

reverse_dev_pic_27_2-20219-21d890.jpg

После установления соединения сервер будет принимать соединение сокета в переменную sock.

reverse_dev_pic_28_1-20219-5c8a7f.jpeg

Далее сервер считает сообщение, переданное в переменную value, используя для этого _read.

reverse_dev_pic_29_1-20219-cbffe4.jpeg

В конечном итоге, сервер через переменную s в машинном коде отошлёт сообщение serverhello.

Рассмотрим клиентскую часть

Код выглядит следующим образом:

int Client() {
    struct sockaddr_in address;
    int sock = 0, value;
    struct sockaddr_in server_addr;
    char* helloclient = "Client Hello";
    char buffer[1024] = {0};

    // создаём сокет
    server = socket(AF_INF, SOCK_STREAM, 0);

    // настраиваем объект сокета
    memset(&server_addr, 0, sizeof(server_addr));

    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);

    // форматируем IP-адрес в бинарный формат
    inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);

    // подсоединяемся к серверу
    connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));

    // отсылаем сообщение серверу
    send(sock, helloclient, strlen(helloclient), 0);

    // читаем ответ от сервера
    value = read(sock, buffer, 1024);

    printf("%s\n", buffer);
    return 0;
}

Итак, поначалу создаётся файловый дескриптор сокета sock посредством кода протокола 0 и переменной домена AF_INET типа SOCK_STREAM. Далее memset применяется в целях заполнения области памяти server_addr нулями. Это происходит до того, как будет установлена информация об адресе посредством server_addr.sin_family и server_addr.sin_port. До подключения клиента к серверу информация об адресе будет преобразована из текстового в двоичный формат посредством inet_pton. После подключения клиент отправит строку helloclient, а потом примет ответ сервера в переменную value. В итоге, переменная value выведется на экран, и произойдёт возврат из функции.

Машинный код инициализации переменных клиента:

reverse_dev_pic_29_2-20219-afec88.jpg

В первую очередь, инициализируем локальные переменные клиента.

reverse_dev_pic_30_1-20219-4f2454.jpeg

Дескриптор файла сокета «sock» создастся путём вызова системной функции _socket и благодаря передаче информации о протоколе, типе и домене с помощью регистров edx, esi и edi.

reverse_dev_pic_31_1-20219-0686c7.jpeg

Переменная server_address (это «s» в машинном коде) заполняется нулями (0x30) посредством системного вызова _memset.

reverse_dev_pic_32_1-20219-e068ef.jpeg

Далее происходит настройка адресной информации сервера.

reverse_dev_pic_33_1-20219-0856fc.jpeg

Потом осуществляется перевод адреса из текстового в двоичный формат посредством системной функции _inet_pton. Здесь важно заметить, что так как адрес в коде явно не указан, мы предполагаем localhost (127.0.0.1).

Далее осуществляется подключение клиента к серверу посредством системного вызова _connect.

reverse_dev_pic_35_1-20219-c487ad.jpeg

После того, как произойдёт подключение, клиент отправит на сервер строку helloClient.

reverse_dev_pic_36_1-20219-419a6d.jpeg

В конце концов, клиент получит ответ сервера в переменную value посредством системного вызова _read.

Источник

Не пропустите новые полезные статьи!

Спасибо за подписку!

Мы отправили вам письмо для подтверждения вашего email.
С уважением, OTUS!

Автор
2 комментария
Комментарий удален
Комментарий удален
Для комментирования необходимо авторизоваться
Популярное
Сегодня тут пусто