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

En esta entrada veremos cómo implementar un protocolo de comunicación de red utilizando la pila TPC/IP. Utilizaremos el protocolo TCP en la capa de transporte y para la capa de Aplicación utilizaremos el nuevo protocolo donde viajarán nuestros datos.

Para implementar nuestro propio protocolo tendremos que “picar” código y para ello he elegido como lenguaje de programación Python en su versión 2.7, pues utilizaremos las librerías del software Scapy que nos permitirá generar y entender los paquetes con los que trabajaremos.

Una mejor explicación, Scapy es un software que permite la manipulación interactiva de paquetes, también tiene implementado un gran abanico de protocolos de red. Además, este programa permite diseccionar, construir, enviar y recibir paquetes de red.

El protocolo que implementaremos nos permitirá comunicar un cliente con un servidor, ambos TCP. El cliente realizará una petición con un identificador único y el servidor responderá con un nombre asociado a ese identificador.

¡Pongámonos manos a la obra!

Para empezar vamos a crear dos sockets TCP, un cliente y un servidor. En Internet podemos encontrar ejemplos de implementación de los mismos, por ejemplo:

client.py

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

def main():
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((TCP_IP, TCP_DPORT))
        sleep(200)
        s.close()

    except Exception as client_error:
        print('Error: {}').format(client_error)

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

server.py

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


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:', addr)
        conn.close()

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

Ahora vamos a implementar el protocolo, para ello vamos a crear un archivo Python que luego importaremos a los dos anteriores programas, como una librería.

En el siguiente enlace encontrareis documentación sobre cómo crear un protocolo con Scapy, es bastante descriptiva y sencilla.

Como bien explica esta página web, añadir un protocolo o mejor dicho añadir una nueva capa en Scapy es fácil, y tendremos que trabajar con los campos “fields” del protocolo.

Una capa en Scapy es una subclase de la clase Packet. Una capa está compuesta por una lista de fields concatenadas, esta lista se implementa con el atributo fields_desc de Scapy.

Veamos un ejemplo, vamos a crear la clase Pedro que hereda de la clase Packet, y vamos a añadir ciertos fields que serán los campos de nuestro protocolo.

pedro.py

class Pedro(Packet):
    name = "Pedro"
    fields_desc = [
        ByteEnumField("direction", None, DIRECTIONS),
        ByteEnumField("service", None, SERVICE_CODES),
    ]

En el código anterior se puede ver la clase Pedro con dos fields, estos son de tipo ByteEnum, que quiere decir que se trata de un field que va a tratar con enumeradores de tipo Byte, a este field se le añade el nombre que corresponderá en el protocolo, el valor por defecto y el nombre del Enum asociado a ese field.

En nuestro caso tendremos por un lado el Enum denominado “DIRECTIONS” cuyo fin es el de saber la dirección del paquete, es decir, si se trata de una pregunta o una respuesta y por otro lado tenemos el Enum “SERVICE_CODES”, cuyo fin es la función que se llevará a cabo.

Quedando la clase de la siguiente manera:

pedro.py

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),
    ]

Veamos con Scapy como quedaría nuestra capa:

Ilustración 1: Capa del protocolo Pedro

Ilustración 1: Capa del protocolo Pedro.

Siguiendo un poco la documentación de Scapy y de manera que el lector pueda seguir el post, en la siguiente imagen veremos cómo se construyen los paquetes con Scapy y cómo quedaría con nuestro protocolo:

Ilustración 2: Orden de los paquetes con la capa Pedro

Ilustración 2: Orden de los paquetes con la capa Pedro

Con esto dicho, vamos a proceder a ampliar el protocolo, añadiendo una capa extra en la cual viajaran los datos que dependen de la variable “direction” de la capa anterior. Por lo tanto vamos a implementar dos clases, una para la petición y otra para la respuesta.

La primera estará compuesta por un field de tipo Byte donde el cliente enviará un número para solicitar al servidor el nombre correspondiente a ese identificador.

La segunda se compondrá de un campo de tipo String donde el servidor responderá al cliente con el nombre asociado al identificador tenga la pregunta.

En la clase para la respuesta también se define la función “answers” la cual permite a la librería de Scapy interpretar que se trata de una respuesta a la clase anterior.

pedro.py

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)

Para finalizar la implementación del protocolo vamos a implementar una característica de Scapy bind_layer() y es que cuando se disecciona las capas Scapy tratará de adivinar por nosotros cuál es la siguiente capa.

Le haremos entender a Scapy que cuando se reciba un paquete con los valores de los fields direction a “0” y el servicio a “1” las capas del protocolo estarán compuestas por la capa Pedro y la siguiente será PedroReqYourName.

Para las respuestas, Scapy sabrá que cuando un paquete contenga los valores direction a “1” y el servicio a “1” las capas del protocolo quedarán compuestas por la capa Pedro y la siguiente será PedroRespYourName.

pedro.py

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

Con esto tendríamos implementado la parte de como se estructuran los campos y las capas del protocolo, así como su lugar en la pila de protocolos TCP/IP.

En la segunda parte de este post veremos cómo enviar y recibir paquetes haciendo uso de nuestro protocolo. Rellenaremos los campos necesarios para que el servidor sea capaz de entender y responder acorde a lo implementado. Además, veremos como el cliente será capaz de entender la respuesta y mostraremos por pantalla de la linea de comandos la respuesta recibida por el servidor.

Comments

  1. Interesante tu post no tenia idea que se podria crear tu propio protocol.

  2. Scapy es una herramienta bastante interesante para tratar los paquetes de una red.
    Te animo a que la pruebes.