lunes, 15 de noviembre de 2010

Usando proxies con Python

Presento un pequeño código para usar proxies SOCKS 4 y 5 en python, en principio la idea era mostrarlo junto un servidor proxy en C, pero viendo que con esto se podía tumbar, tardará un poco más...



El código es este (para descargar completo más abajo):


#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Escrito por kenkeiras
# Bajo la WTFPL <http://sam.zoy.org/wtfpl/>
from socket import socket, AF_INET, SOCK_STREAM, inet_aton, getaddrinfo,\
                   SOL_TCP
from string import letters
from random import choice

import sys

# Lee siempre una cantidad de un socket
def sock_read(sock, n):
    b = "" # Inicia el buffer
    i = 0  # Tamaño del buffer

    while (i < n): # Mientas no lee todo

        c = sock.recv(n - i) # Lee lo que falta

        if (len(c) < 1): # Si no se leyó nada
                         # es que el socket está cerrado
            raise Exception("Closed socket") 

        b += c      # Añadir lo nuevo al buffer
        i += len(c) # y actualizar su tamaño

    return b # Devolver el buffer
    

# Convertir sockaddr a una dirección de red
def str2host(addr, ipv = 4):
    for i in letters: # Si hay letras
        if i in addr[0]: # es un nombre de dominio
            d = getaddrinfo(addr[0], addr[1], 0, 0, SOL_TCP)
            # La primera dirección, el
            # primer dato de la última tupla

            for j in d:
                if (len(j[-1]) == 2) and (ipv == 4): # A ipv4 tuple
                    return inet_aton(j[-1][0])
                elif (len(j[-1]) == 4) and (ipv == 6): # A ipv6 tuple
                    return inet_aton(j[-1][0])
            raise Exception("Host not found")

    # Sino se traduce directamente
    return inet_aton(addr[0])

# Pasa un número de 2 bytes a str
# Suponiendo little endian
def uint16str(n):
    data = [] # Prepara el buffer
    data.append( chr(n & 255) ) # Lee el byte menos significativo
    n >>= 8 # Mueve todo un byte a la derecha

    data.append( chr(n & 255) ) # Lee el nuevo lsb

    data.reverse() # Le da la vuelta (little endian)

    return ''.join(data) # Y lo convierte en una cadeba

# Envía el código de excepción de un servidor SOCKS4
def SOCKS4_ex(ans):

    # Relación código/respuesta
    s4_ex = { 91 : "Request rejected or failed",
              92 : "Request rejected becasue SOCKS server cannot connect to identd on the client",
              93 : "Request rejected because the client program and identd report different user-ids"}
    raise Exception(s4_ex[ord(ans)]) # Produce la excepción

# Envía el código de excepción de un servidor SOCKS5
def SOCKS5_ex(ans):

    # Relación código/respuesta
    s5_ex = { 1 : "General SOCKS server failure",
              2 : "Connection not allowed by ruleset",
              3 : "Network unreachable",
              4 : "Host unreachable",
              5 : "Connection refused",
              6 : "TTL expired",
              7 : "Command not supported",
              8 : "Address type not supported" }

    raise Exception(s5_ex[ord(ans)]) # Produce la excepción

# Especifica la dirección a un proxy SOCKS
def SOCKS_hop(sock, addr, proto = 4, ipv = 4):
    if (proto == 4): # Protocolo SOCKS4
        # Mensaje de conexión
        sock.send(chr(4) + chr(1) + uint16str(addr[1]) + str2host(addr) +\
             chr(0))

        # Mensaje de confirmación/error
        code = sock_read(sock, 8)[1]

        # Si algo falló
        if (code != chr(90)):
            # Mandar una excepción
            SOCKS4_ex(code)

    
    elif (proto == 5): # Protocolo SOCKS5
        atype = None
        if (ipv == 4): # Hosts ipv4
            atype = chr(1)
        elif (ipv == 6): # Hosts ipv6
            atype = chr(4)
        else:
            raise Exception("Unknown IP version")

        sock.send(chr(5) + chr(1) + chr(0))

        # Mensaje de confirmación/error
        code = sock_read(sock, 2)

        if (code != (chr(5) + chr(0))):
            raise Exception("Requiere autenticación")

        # Mensaje de conexión
        l = sock.send(chr(5) + chr(1) + chr(0) + atype + str(str2host(addr)) +\
             uint16str(addr[1]))

        # Mensaje de confirmación/error
        code = sock_read(sock, l)[1]

        # Si algo falló
        if (code != chr(00)):
            # Mandar una excepción
            SOCKS5_ex(code)

    else: # Protocolo desconocido
        raise Exception("Unknown SOCKS version")



Simplemente hay que levantar una conexión hacia un proxy y luego llamar a SOCKS_hop con los siguientes argumentos:
  • El socket que se uso para la conexión
  • Una tupla dirección/puerto como al conectar un socket
  • Versión de SOCKS: 4 o 5(Opcional, por defecto 4)
  • Versión del protocolo IP (Opcional, por defecto 4)
Si se produce un error en el proceso (no está conectado a un servidor SOCKS o le servidor no se pudo conectar al nuevo host, por ejemplo) manda una excepción.

Para este ejemplo, si se pasa algún parámetro, se leerá como una lista de proxies (ip:puerto   en cada línea) y va saltando entre proxies al azar. Sino se le pasa ningún parámetro se conecta a localhost en el puerto 1080 y intenta usarlo como servidor SOCKS5 para ver cuantas veces se puede conectar a si mismo [Cuidado]: unos cuantos hilos haciendo esto provocaron algo parecido a un DOS en un pequeño servidor SSH funcionando como proxy =P

if __name__ == "__main__":
    
    if (len(sys.argv) > 1):
        plist = []
        f = open(sys.argv[1], "rt")
        while True:
            txt = f.readline()
            if (len(txt) < 1):
                break
            addr = txt.split(":")
            plist.append((addr[0], int(addr[1])))

        f.close()


        last = choice(plist)  # Selecciona un proxy al azar
        next = last
        i = 0

        sys.stdout.write("[" + str(i) + "] " + str(next))
        sys.stdout.flush()
        sock = socket(AF_INET, SOCK_STREAM)
        sock.connect(next)
        sys.stdout.write("\n")
        while True:
            i += 1
            while (next == last):
                next = choice(plist)
            last = next
            sys.stdout.write("[" + str(i) + "] " + str(next))
            sys.stdout.flush()
            SOCKS_hop(sock, next)
            sys.stdout.write("\n")

    else:
        sock = socket(AF_INET, SOCK_STREAM)

        #sock.connect(('127.0.0.1', 4444))    
        sock.connect(('localhost', 1080))
        i = 0
        while True:
            #SOCKS_hop(sock, ('localhost', 4444))
            SOCKS_hop(sock, ('127.0.0.1', 1080), 5)
            i += 1
            print i



El código completo [pysocks.py]:

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Escrito por kenkeiras
# Bajo la WTFPL <http://sam.zoy.org/wtfpl/>

from socket import socket, AF_INET, SOCK_STREAM, inet_aton, getaddrinfo,\
                   SOL_TCP
from string import letters
from random import choice

import sys

# Lee siempre una cantidad de un socket
def sock_read(sock, n):
    b = "" # Inicia el buffer
    i = 0  # Tamaño del buffer

    while (i < n): # Mientas no lee todo

        c = sock.recv(n - i) # Lee lo que falta

        if (len(c) < 1): # Si no se leyó nada
                         # es que el socket está cerrado
            raise Exception("Closed socket") 

        b += c      # Añadir lo nuevo al buffer
        i += len(c) # y actualizar su tamaño

    return b # Devolver el buffer
    

# Convertir sockaddr a una dirección de red
def str2host(addr, ipv = 4):
    for i in letters: # Si hay letras
        if i in addr[0]: # es un nombre de dominio
            d = getaddrinfo(addr[0], addr[1], 0, 0, SOL_TCP)
            # La primera dirección, el
            # primer dato de la última tupla

            for j in d:
                if (len(j[-1]) == 2) and (ipv == 4): # A ipv4 tuple
                    return inet_aton(j[-1][0])
                elif (len(j[-1]) == 4) and (ipv == 6): # A ipv6 tuple
                    return inet_aton(j[-1][0])
            raise Exception("Host not found")

    # Sino se traduce directamente
    return inet_aton(addr[0])

# Pasa un número de 2 bytes a str
# Suponiendo little endian
def uint16str(n):
    data = [] # Prepara el buffer
    data.append( chr(n & 255) ) # Lee el byte menos significativo
    n >>= 8 # Mueve todo un byte a la derecha

    data.append( chr(n & 255) ) # Lee el nuevo lsb

    data.reverse() # Le da la vuelta (little endian)

    return ''.join(data) # Y lo convierte en una cadeba

# Envía el código de excepción de un servidor SOCKS4
def SOCKS4_ex(ans):

    # Relación código/respuesta
    s4_ex = { 91 : "Request rejected or failed",
              92 : "Request rejected becasue SOCKS server cannot connect to identd on the client",
              93 : "Request rejected because the client program and identd report different user-ids"}
    raise Exception(s4_ex[ord(ans)]) # Produce la excepción

# Envía el código de excepción de un servidor SOCKS5
def SOCKS5_ex(ans):

    # Relación código/respuesta
    s5_ex = { 1 : "General SOCKS server failure",
              2 : "Connection not allowed by ruleset",
              3 : "Network unreachable",
              4 : "Host unreachable",
              5 : "Connection refused",
              6 : "TTL expired",
              7 : "Command not supported",
              8 : "Address type not supported" }

    raise Exception(s5_ex[ord(ans)]) # Produce la excepción

# Especifica la dirección a un proxy SOCKS
def SOCKS_hop(sock, addr, proto = 4, ipv = 4):
    if (proto == 4): # Protocolo SOCKS4
        # Mensaje de conexión
        sock.send(chr(4) + chr(1) + uint16str(addr[1]) + str2host(addr) +\
             chr(0))

        # Mensaje de confirmación/error
        code = sock_read(sock, 8)[1]

        # Si algo falló
        if (code != chr(90)):
            # Mandar una excepción
            SOCKS4_ex(code)

    
    elif (proto == 5): # Protocolo SOCKS5
        atype = None
        if (ipv == 4): # Hosts ipv4
            atype = chr(1)
        elif (ipv == 6): # Hosts ipv6
            atype = chr(4)
        else:
            raise Exception("Unknown IP version")

        sock.send(chr(5) + chr(1) + chr(0))

        # Mensaje de confirmación/error
        code = sock_read(sock, 2)

        if (code != (chr(5) + chr(0))):
            raise Exception("Requiere autenticación")

        # Mensaje de conexión
        l = sock.send(chr(5) + chr(1) + chr(0) + atype + str(str2host(addr)) +\
             uint16str(addr[1]))

        # Mensaje de confirmación/error
        code = sock_read(sock, l)[1]

        # Si algo falló
        if (code != chr(00)):
            # Mandar una excepción
            SOCKS5_ex(code)

    else: # Protocolo desconocido
        raise Exception("Unknown SOCKS version")


if __name__ == "__main__":
    
    if (len(sys.argv) > 1):
        plist = []
        f = open(sys.argv[1], "rt")
        while True:
            txt = f.readline()
            if (len(txt) < 1):
                break
            addr = txt.split(":")
            plist.append((addr[0], int(addr[1])))

        f.close()


        last = choice(plist)  # Selecciona un proxy al azar
        next = last
        i = 0

        sys.stdout.write("[" + str(i) + "] " + str(next))
        sys.stdout.flush()
        sock = socket(AF_INET, SOCK_STREAM)
        sock.connect(next)
        sys.stdout.write("\n")
        while True:
            i += 1
            while (next == last):
                next = choice(plist)
            last = next
            sys.stdout.write("[" + str(i) + "] " + str(next))
            sys.stdout.flush()
            SOCKS_hop(sock, next)
            sys.stdout.write("\n")

    else:
        sock = socket(AF_INET, SOCK_STREAM)

        #sock.connect(('127.0.0.1', 4444))    
        sock.connect(('localhost', 1080))
        i = 0
        while True:
            #SOCKS_hop(sock, ('localhost', 4444))
            SOCKS_hop(sock, ('127.0.0.1', 1080), 5)
            i += 1
            print i


[Referencias]
SOCKS 4
SOCKS 4A
RFC1928 - SOCKS5

2 comentarios: