En esta ocasión precisamos un servidor que estará ejecutándose en la máquina remota y se encargará de la desaferentación (apagar el monitor, bloquear el teclado y bloquear el ratón) y un cliente que se encargará de enviar los comandos precisos para tales acciones. La conexión la he asegurado mediante protocolo TCP de forma que sabremos si el equipo remoto tiene el servidor en marcha y si recibe los comandos.
El Servidor de tan sólo 16K, carece de entorno gráfico, pasando desapercibido a la "víctima". El cliente tiene un entorno gráfico simple con los elementos justos para funcionar.
Quiero destacar que si se hacen pruebas en local, nos podemos quedar muy colgados con lo que he incorporado un sistema de seguridad para pruebas con un Timer que activará monitor, teclado y ratón en unos segundos siempre que estemos probando en local (IP = 127.0.0.1) pero no en remoto.
No hay mucho más que explicar, así que aquí va el corazón del código del cliente:
function TForm1.EnableMonitor(SHost: PCHAR; Enable: BOOL): BOOL; const SEnable = 'Enable'; SDisable = 'Disable'; var WSA: TWSADATA; Sock: TSOCKET; Host: PHostent; Remote: sockaddr_in; IP: ^Integer; Conex: integer; Msg: array[0..10] of CHAR; begin Label1.Caption:= 'Enviando'; Label1.Update; Result:= false; if Enable then lstrcpy(Msg, SEnable) else lstrcpy(Msg, SDisable); if(WSAStartup(MakeWord(2,2), WSA) = 0) then begin Host:= gethostbyname(SHost); if Host <> nil then begin Sock:= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(Sock <> INVALID_SOCKET) then begin IP:= @Host.h_addr_list^[0]; Remote.sin_family:= AF_INET; Remote.sin_addr.s_addr:= INADDR_ANY; Remote.sin_port:= htons(9999); Remote.sin_addr.S_addr:= IP^; ZeroMemory(@Remote.sin_zero, 8); //Intentamos establecer la conexión Conex:= connect(Sock, Remote, sizeof(Remote)); if (conex <> -1) then Result:= -1 <> send(Sock, Msg, lstrlen(Msg), 0); //Enviar comando ShutDown(Sock, SD_BOTH); CloseSocket(Sock); end; end; end; WSACleanUp; if Result then Label1.Caption:= 'Comando enviado' else Label1.Caption:= 'No se ha podido comunicar con Servidor'; end;
Y lo que sigue es el código completo del servidor:
program ServerMonitorPower; uses WinSock, MMSystem, Windows, Messages; var idThTurnOffMonitor: DWORD = 0; WHookKeyboard: HHOOK = 0; WHookMouse: HHOOK = 0; function ThTurnOffMonitor(): DWORD; stdcall var msg: TMsg; i: DWORD; begin // Monitor off repeat PeekMessage(msg,0,0,0, PM_REMOVE); Sleep(100); SendNotifyMessage(GetForegroundWindow(), WM_SYSCOMMAND, SC_MONITORPOWER, 2); DispatchMessage(msg); until (msg.Message = WM_QUIT); // Monitor on SendNotifyMessage(GetForegroundWindow(), WM_SYSCOMMAND, SC_MONITORPOWER, -1); for i:= 0 to 4 do begin mouse_event(MOUSEEVENTF_MOVE, 0, 10, 0, 0); Sleep(40); mouse_event(MOUSEEVENTF_MOVE, 0, DWORD(-10), 0, 0); end; Result:= 0; end; function KeyboardHook(Code, wParam, lParam: Integer): Integer; stdcall; begin Result:= -1; if Code <> HC_ACTION then Result:= CallNextHookEx(WHookKeyboard, Code, wParam, lParam); end; function MouseHook(Code, wParam, lParam: Integer): Integer; stdcall; begin Result:= -1; if Code <> HC_ACTION then Result:= CallNextHookEx(WHookMouse, Code, wParam, lParam); end; procedure CmdEnableMonitor(Enable: BOOL); begin if not Enable and (idThTurnOffMonitor = 0) then // Turn off monitor CloseHandle(CreateThread(nil, 0, @ThTurnOffMonitor, nil, 0, idThTurnOffMonitor)); // Hooking WHookKeyboard:= SetWindowsHookEx(13{WH_KEYBOARD_LL}, @KeyboardHook, HInstance, 0); WHookMouse:= SetWindowsHookEx(14{WH_MOUSE_LL}, @MouseHook, HInstance, 0); if Enable then begin // Turn on monitor PostThreadMessage(idThTurnOffMonitor, WM_QUIT, 0, 0); //UnHooling if WHookKeyboard <> 0 then UnhookWindowsHookEx(WHookKeyboard); if WHookMouse <> 0 then UnhookWindowsHookEx(WHookMouse); WHookMouse:= 0; WHookKeyboard:= 0; idThTurnOffMonitor:= 0; end; end; // El servidor propiamente dicho var WSA: TWSADATA; Sock_c: TSOCKET; Sock_e: TSOCKET; Local: sockaddr_in; Buffer: array[0..1024] of CHAR; Len: integer; begin idThTurnOffMonitor:= 0; if(WSAStartup(MakeWord(2,2), WSA) <> 0) then exit; Sock_e:= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(Sock_e <> INVALID_SOCKET) then begin local.sin_family:= AF_INET; local.sin_addr.s_addr:= INADDR_ANY; local.sin_port:= htons(9999); // Asociamos el socket al puerto if(bind(Sock_e, Local, sizeof(Local)) <> -1) then begin // escucha... while true do begin // Socket a la escucha if (listen(sock_e, 1) = -1) then exit; Len:= sizeof(Tsockaddr); // Aceptamos conexión Sock_c:= accept(Sock_e, PSOCKADDR(@Local), @Len); Len:= recv(Sock_c, Buffer, 1023, 0); //recibimos los datos que envie if (Len > 0) then //si seguimos conectados begin Buffer[Len]:= #0; //le ponemos el final de cadena if lstrcmpi(Buffer, 'Enable') = 0 then CmdEnableMonitor(true); if lstrcmpi(Buffer, 'Disable') = 0 then CmdEnableMonitor(false); end; closesocket(Sock_c); end; end; end; WSACleanUp; CmdEnableMonitor(true); end.
He de decir que un código parecido ya use en una aplicación de escritorio remoto que era algo más que eso, la idea era que el usuario no pudiera intervenir cuando esa app se usaba en modo escritorio remoto, porque el resto de sus virtudes eran transparentes al usuario.
También he encontrado efectos interesantes en las pruebas que he hecho basadas en el código que muestro, como el bloqueo total de monitor, teclado y ratón incluidas las célebres CTR-ALT-SUP, pero eso es otra historia que tendré que investigar más a fondo y con más tiempo, así como los efectos reales causados.
Para no dejaros a medias con el cliente, subo el proyecto completo y compilado.
Saludos.