El programa es una aplicación de consola que simula un intérprete de comandos. Recibe como parámetro el puerto de escucha, que deberá coincidir con el del programita de seoane.
Como las cadenas devueltas por la shell inversa pueden ser muy largas y no hay garantía de que llegue todo en un mismo paquete, se precisa un bucle de lectura del socket hasta que no quede nada en la cola, así como colocar un timeout en recv.
He encontrado un asunto engorroso a la hora de tratar el comando "cls" ya que la consola devuelve una cadena de esta forma: 'cls'+#10+#12+#13+#10. El carácter 12 (nueva página) es imprimible pero no es alfanumérico quedando fea su representación en pantalla, he decidido gestionarlo eliminando toda la cadena devuelta exceptuando el último retorno de línea, a imagen y semejanza del intérprete de comandos.
Aunque el shell inverso que escribió seoane funciona a la perfección con este servidor, me he permitido la licencia de hacer pequeños cambios para que se quede a la espera de la conexión, en caso de que el servidor no esté en marcha, y para que detecte la caída del mismo, cierre la consola y vuelva a quedar a la espera de otra conexión. De otra forma quedarían múltiples procesos cmd.exe abiertos en segundo plano. Los cambios realizados son mínimos, quedando muy fiel al código original. la shell inversa se cierra enviando el comando "exit"
El código del servidor es el siguiente:
delphi
program Server; {$APPTYPE CONSOLE} uses Windows, SysUtils, Winsock; const BufferSize = 1024; cls = 'cls'+#10+#12+#13+#10; // Borar pantalla de consola procedure clrscr; var ConSize: DWORD; ConInfo: CONSOLE_SCREEN_BUFFER_INFO; ConPos : TCOORD; hConsole: THANDLE; CharsWritten: DWORD; begin hConsole:= getstdhandle(STD_OUTPUT_HANDLE); if getconsolescreenbufferinfo(hConsole, ConInfo) then begin ConPos.x := 0; ConPos.y := 0; ConSize := ConInfo.dwsize.x * ConInfo.dwsize.y; if FillConsoleOutputCharacter(hConsole, ' ', ConSize, ConPos, CharsWritten) then if GetConsoleScreenBufferInfo(hConsole, ConInfo) then if FillConsoleOutputAttribute(hConsole, ConInfo.wAttributes, ConSize, ConPos, CharsWritten) then SetConsoleCursorPosition(hConsole, ConPos); end; end; // Recibe datos de la consola remota function Recibe(Sock: TSOCKET; Input: String): integer; var Buffer, clsBuffer: PCHAR; Len, clsLen: integer; TimeOut: DWORD; begin GetMem(Buffer, BufferSize); Result:= 0; TimeOut:= 1000; setsockopt(Sock, SOL_SOCKET, SO_RCVTIMEO, PCHAR(@TimeOut), sizeof(TimeOut)); // Leo hasta que no quede nada repeat Len:= recv(Sock, Buffer^, BufferSize-1, 0); if Len > 0 then begin CharToOemBuff(Buffer, Buffer, Len); // Si recibo cls borro la pantalla if StrLIComp(Buffer, 'cls', 3) = 0 then begin // La cadena cls tiene 7 caracteres, si no se leyó entera sigo leyendo // 'cls'+#10+#12+#13+#10; clsBuffer:= Buffer; clsLen:= Len; while clsLen < 7 do begin inc(clsBuffer, Len); Len:= recv(Sock, clsBuffer^, BufferSize - clsLen - 1, 0); clsLen:= clsLen + Len; end; Len:= clsLen; Buffer[Len]:= #0; // la cadena debe terminar en cero if StrLIComp(Buffer, cls, 7) = 0 then clrscr; Write(String(Buffer + 6)); end // Si es exit salimos else if StrLIComp(PCHAR(Input), 'exit', 4) = 0 then break // Imprimimos la cadena else begin Buffer[Len]:= #0; // la cadena debe terminar en cero if Result = 0 then Write(String(Buffer + length(Input))) else Write(String(Buffer)); end; Result:= Result + Len; end; until (Len <= 0); FreeMem(Buffer); end; var Wsa: TWSADATA; Sock_e: TSOCKET; // Sock de escucha Sock_c: TSOCKET; // Sock de comunicación TimeOut: DWORD; ServerAddr, ClientAddr: sockaddr_in; Len: integer; Port: WORD; Input: String; begin Port:= StrToIntDef(ParamStr(1), 55555); Len:= 0; Input:= ''; // Inicializamos Writeln('Inicializando el servidor...'+#10); if WSAStartup(MAKEWORD(2, 0), Wsa) <> 0 then exit; //Creamos el socket Sock_e:= socket(AF_INET, SOCK_STREAM,IPPROTO_TCP); if Sock_e <> INVALID_SOCKET then begin // Dirección IP y puerto ServerAddr.sin_family:= AF_INET; ServerAddr.sin_addr.s_addr:= INADDR_ANY; ServerAddr.sin_port:= htons(Port); // Asociamos el socket al puerto if bind(Sock_e, ServerAddr, sizeof(ServerAddr)) <> -1 then begin // Bucle principal del servidor // Este servidor nunca termina, siempre escuchará while true do begin // Escuchando puerto WriteLn('Escuchando por el puerto ', Port); if listen(Sock_e, 1) <> -1 then begin //hay una conexión entrante y la aceptamos Len:= sizeof(sockaddr_in); Sock_c:= accept(Sock_e, @ClientAddr, @Len); if Sock_c = INVALID_SOCKET then continue; windows.Beep(1000, 100); WriteLn('Shell inversa conectada'+#10+#10); // Bucle de comunicación repeat // Leemos los datos entrantes if (Recibe(Sock_c, Input) = 0) then break;//recibimos los datos que envie // Escribimos comandos ReadLn(Input); Input:= Input+#10; TimeOut:= 1000; setsockopt(Sock_c, SOL_SOCKET, SO_SNDTIMEO, PCHAR(@TimeOut), sizeof(TimeOut)); send(Sock_c, PCHAR(Input)^, Length(Input), 0); until false; windows.Beep(1000, 200); WriteLn(#10+'Cliente Desconectado'+#10); closesocket(Sock_c); end; // listen end; // While (true) closesocket(Sock_e); end; // if bind end; // if Sock_e WSACleanup; end.
Esto sólo pretende ser un juguete funcional y muy mejorable
El código de la shell inversa lo tenéis en la página de seoane
Subo los fuentes y binarios de la shell inversa modificada y del servidor.
Saludos.