Creación de un protocolo de comunicación propio con Scapy (II)

En el post anterior implementamos la estructura de nuestro protocolo, en esta segunda parte veremos como utilizarlo.

Ahora vamos a implementar el envío y la recepción de paquetes con nuestro protocolo, por parte del servidor además explicaremos cómo evaluar los campos del protocolo para responder según el identificador de la pregunta. Por parte del cliente explicaremos cómo acceder a un field del protocolo y mostrarlo por pantalla.

Modificamos el main del cliente, quedando el código como se muestra a continuación:

client.py

def main():
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((TCP_IP, TCP_DPORT))
        socketsr1 = StreamSocket(s, Pedro)
        packet_to_send = Pedro(direction=0, service=1)/PedroReqYourName(name_number=NAME)
        ans = socketsr1.sr1(packet_to_send, timeout=2, verbose=False)
        server_name = ans.getlayer("PedroRespYourName").sprintf("%own_name%")
        print('Server name: {}').format(server_name)
        s.close()
    except Exception as client_error:
        print('Error: {}').format(client_error)


Hemos creado un Streamsocket con la clase Pedro, creamos el paquete para enviar y lo más importante hemos utilizado la funcion “sr1” de Scapy que nos servirá para enviar un paquete y recibir la primera respuesta por parte del servidor, como argumentos la función necesita el paquete ha enviar, un “timeout” de espera de la respuesta y el “verbose” para que no nos saque información por pantalla.

La respuesta la guardaremos en la variable denominada “ans” y la trataremos con las funciones de Scapy “getlayer” y “sprintf”. Con “getlayer” escogeremos la capa “PedroRespYourName” y con “sprintf” el valor de la variable “own_name”.

Una vez tengamos el nombre que responde el server lo mostraremos por pantalla y cerraremos la conexión con el servidor.

server.py

# NAMES TO SEND INSIDE THE RESPONSE
############################################################
SERVER_NAMES = {
    "1": "Pedro",
    "2": "Raul",
    "3": "Alvaro",
    "4": "Fernando"
}
############################################################


# FUNCTION TO RETURN THE NAME
############################################################
def get_the_name(layer):
    return_name = SERVER_NAMES[layer.sprintf("%name_number%")]
    return return_name
############################################################

# DICTIONARY WITH THE POSSIBLES ACTIONS
############################################################
actions = {
    "get_the_name": get_the_name,
}
############################################################

def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind((TCP_IP, TCP_PORT))
    s.listen(1)
    s.settimeout(100)
    while 1:
        conn, addr = s.accept()
        print('Connection address: {}').format(addr)
        try:
            data = conn.recv(BUFFER_SIZE)
            if data:
                received_packet = Ether() / IP() / TCP() / Pedro(data)
                required_name = actions[received_packet.getlayer("Pedro").sprintf("%service%")] \
                    (received_packet.getlayer("PedroReqYourName"))
                packet_to_send = str(Pedro(direction=1, service=1) / PedroRespYourName(own_name=required_name))
                conn.sendall(packet_to_send)
            else:
                pass
        except Exception as server_error:
            print('Error: {}').format(server_error)
            conn.close()

El servidor lo implementaremos de tal forma que para cada conexión nueva de un cliente haya una petición del protocolo propio Pedro y el servidor le conteste según la variable “own_name” de la petición.

Para ello evaluaremos la variable “name” de la pregunta según la función seleccionada en la variable “service” también de la pregunta.

Se ha definido un diccionario de cuatro nombres con identificadores únicos, los cuales son los nombres que el servidor responderá.

Finalmente el proyecto quedaría de la siguiente manera implementado:

client.py

El identificador de la petición lo hardcodearemos en el código con la variable “NAME” y el identificador numero 4.

import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
from protocolo_pedro.pedro import *

# SOCKET SETTINGS
############################################################
TCP_IP = '127.0.0.1'
TCP_DPORT = 5005
BUFFER_SIZE = 1024
NAME = 4
############################################################


def main():
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((TCP_IP, TCP_DPORT))
        socketsr1 = StreamSocket(s, Pedro)
        packet_to_send = Pedro(direction=0, service=1)/PedroReqYourName(name_number=NAME)
        ans = socketsr1.sr1(packet_to_send, timeout=2, verbose=False)
        server_name = ans.getlayer("PedroRespYourName").sprintf("%own_name%")
        print('Server name: {}').format(server_name)
        s.close()
    except Exception as client_error:
        print('Error: {}').format(client_error)


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print('[+] Bye!')

server.py

import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
from protocolo_pedro.pedro import *

# SOCKET SETTINGS
############################################################
TCP_IP = '127.0.0.1'
TCP_PORT = 5005
BUFFER_SIZE = 20  # Normally 1024, but we want fast response
############################################################

# NAMES TO SEND INSIDE THE RESPONSE
############################################################
SERVER_NAMES = {
    "1": "Pedro",
    "2": "Raul",
    "3": "Alvaro",
    "4": "Fernando"
}
############################################################


# FUNCTION TO RETURN THE NAME
############################################################
def get_the_name(layer):
    return_name = SERVER_NAMES[layer.sprintf("%name_number%")]
    return return_name
############################################################

# DICTIONARY WITH THE POSSIBLES ACTIONS
############################################################
actions = {
    "get_the_name": get_the_name,
}
############################################################


def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind((TCP_IP, TCP_PORT))
    s.listen(1)
    s.settimeout(100)
    while 1:
        conn, addr = s.accept()
        print('Connection address: {}').format(addr)
        try:
            data = conn.recv(BUFFER_SIZE)
            if data:
                received_packet = Ether() / IP() / TCP() / Pedro(data)
                required_name = actions[received_packet.getlayer("Pedro").sprintf("%service%")] \
                    (received_packet.getlayer("PedroReqYourName"))
                packet_to_send = str(Pedro(direction=1, service=1) / PedroRespYourName(own_name=required_name))
                conn.sendall(packet_to_send)
            else:
                pass
        except Exception as server_error:
            print('Error: {}').format(server_error)
            conn.close()


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print('[+] Bye!')

pedro.py

from scapy.all import *
from scapy.fields import *
from scapy.layers.inet import IP,TCP


class PedroReqYourName(Packet):
    name = "PedroReqYourName"
    fields_desc = [
        ByteField("name_number", None)
    ]


class PedroRespYourName(Packet):
    name = "PedroRespYourName"
    fields_desc = [
        StrField("own_name", None, fmt="H")
    ]

    def answers(self, other):
        return isinstance(other, PedroReqYourName)


class Pedro(Packet):
    name = "Pedro"
    SERVICE_CODES = {
        0x01: "get_the_name"
    }

    DIRECTIONS = {
        0x00: "Request",
        0x01: "Response"
    }

    fields_desc = [
        ByteEnumField("direction", None, DIRECTIONS),
        ByteEnumField("service", None, SERVICE_CODES),
    ]

bind_layers(Pedro, PedroReqYourName, direction=0x00, service=0x01)
bind_layers(Pedro, PedroRespYourName, direction=0x01, service=0x01)

Vamos a realizar la prueba:

$sudo python2.7 server.py

$sudo python2.7 client.py

img3

img4

Espero que os guste este post, si necesitáis cualquier aclaración no dudéis en escribir un comentario y os responderé lo antes posible.

Un saludo y nos vemos pronto!