Помнишь, как ты писал код, подменяющий API-вызовы в своем крутом трояне? Или хотя бы читал статью о том, как это делается? Дай угадаю — тебе не понравилось отсутствие хоть какого-нибудь инструментария от Microsoft? Так вот, хочу тебя обрадовать — теперь ситуация кардинально изменилась. Встречай Microsoft Detours!

 

WTF?

Microsoft Detours — проект, разрабатываемый в лабораториях Microsoft Research (хотя, судя по дате последнего обновления — уже не разрабатываемый), позволяющий перехватывать Win32 API-вызовы. 64 бита он тоже умеет, но.. за 10 килобаксов :). Да, коммерческая (и 64-битная) версия стоит именно столько, а вот для образовательных (хе-хе, конечно, конечно) целей он абсолютно бесплатен. Несмотря на то, что проект давненько не обновлялся, он содержит весь необходимый для перехвата вызовов функционал, и работает безглючно (по крайней мере, я каких-либо багов не заметил).

Скачать инсталлятор Detours можно на research.microsoft.com. По умолчанию он установится в Program FilesMicrosoft Research.

Если ты считаешь, что на этом установка окончена, ты глубоко заблуждаешься. Сначала нужно собрать бинарники самой библиотеки и примеры к ней — для этого в консоли VC++ надо выполнить make, и подождать, пока все это добро соберется. А вот ху[Артем, не используй это слово, у нас же приличный журнал — прим. ред.]. Как бы не так! Мелкомягкий компилятор, датируемый 2008-м годом, наотрез отказался его собирать. Поковырявшись самостоятельно, я решил погуглить. Оказалось, что Microsoft в курсе этой проблемы, но делать ничего не собирается. Почесав недолго репу, на detours я благополучно забил. Некоторое время спустя, поставив ради интереса 2005-ю студию, я решил попытать счастья снова — и опа, собралось! Так что учти, что для сборки сей замечательной библиотеки тебе понадобится Visual C++ 2005 (можно и express). Насчет 2010-й не в курсе — в то время ее не было, а сейчас нет желания иметь какие-то дела со студией вообще, поэтому весь код был написан в Visual C++ 2005/2008 (хотя, писать можно и в блокноте).

От detours нам понадобится заголовочный файл detours.h, а также библиотеки detoured.lib, detours.lib и detoured.dll. А писать мы с тобой будем соксификатор на SOCKS4 — программка полезная, отлично показывающая возможности Microsoft Detours, а заодно вспомним спецификации этого несложного протокола.

 

Как оно работает?

Принцип работы прост, как дырка от жо[Артем, опять ты за свое! — прим.ред.] — мы пишем DLL, в которой реализуем нашу версию вызова cool_call(), пишем приложение, которое запускает нужный нам софт с нужной библиотекой, и подсовывающий этой софтине наш API-вызов, устанавливая jump с адресом левого вызова в начало оригинального вызова. Для примера, запустим в дебаггере любое приложение, использующее, к примеру, функцию connect(), поставим бряк на эту функцию, при остановке выполнения нажмем на Go To Disassembly, и увидим:

<………>
00411573 call dword ptr [__imp__connect@12 (4183B0h)]
<………>

Перейдем по F10 до этой строчки, нажмем F11 и окажемся в
листинге этой функции:

<………>
71A94A07 mov edi,edi
71A94A09 push ebp

Знаешь, что такое mov edi, edi? Это, по сути, NOP, пропуск такта процессора, неиспользуемая инструкция. Сюда-то и вставляется jump на нашу функцию. Теперь запустим эту программу с «левым» вызовом, подсунутым ей Detours’ом. И что же мы видим?

<………>
71A94A07 jmp @ILT+715(_my_connect@12) (100112D0h)
<………>

Да-да, вместо NOP’а появился jump на адрес нашей реализации функции connect() =).

 

От простого к сложному

Напишем простую программку, которая будет запускать приложение с нашей библиотекой. Для запуска понадобится всего одна функция — DetourCreateProcessWithDll(), и мы должны передать 7 что-то значащих параметров, остальное, в лучших традициях WinAPI — NULL.

  • LPCTSTR lpApplicationName — полный путь к запускаемому приложению;
  • BOOL bInheritHandles — будет ли приложение наследовать хэндлы от нашего launcher’a — нам это не надо, поэтому — false;
  • DWORD dwCreationFlags — флаги запуска приложения.

Запускать его нужно в приостановленном режиме, поэтому CREATE_DEFAULT_ERROR_MODE | CREATE_SUSPENDED;

  • LPSTARTUPINFOW lpStartupInfo — указатель на структуру, содержащую информацию о запуске приложения;
  • LPPROCESS_INFORMATION lpProcessInformation — указатель на структуру, содержащую информацию о запущенном приложении;
  • LPCSTR lpDetouredDllPath — путь до динамической библиотеки detoured.dll;
  • LPCSTR lpDllName — путь до нашей DLL-ки;

В итоге код запуска приложения выглядит так:

bool res = DetourCreateProcessWithDll(L"F:\DetoursTest\Debug\DetoursTest.exe", NULL, NULL, NULL, false, dwFlags, NULL, NULL, &si, &pi, detouredName, dllName, NULL);

Не забудь обнулить структуры перед передачей в функцию, если собираешься их потом использовать! Если приложение запустилось нормально — нужно возобновить (в нашем случае — запустить) выполнение его главного потока функцией ResumeThread, передав ей в качестве параметра pi.hThread. Собственно, с «Запускатором», все.

 

Четверо носков

Теперь переходим к более сложному этапу — написанию самой DLL. Для соксификации приложения нам надо перехватить всегонавсего один вызов — всем известный connect(). Создавай в студии пустой проект — DLL.

Перед перехватом нужно сохранить адрес оригинальной функции, чтобы можно было ее вызвать:

int (WINAPI * real_connect) (SOCKET sock, const sockaddr *addr, int namelen) = connect;

После этого пишем свою функцию, полностью повторяющую оригинал. Обрати внимание — полностью повторяющую, то есть не только по параметрам, но и по соглашению о вызове! На эти грабли мне уже «посчастливилось» наступить, поэтому считаю своим долгом предупредить об этом тебя :).

Что мы должны сделать, чтобы соксифицировать приложение? Нам надо перехватить вызов функции connect(), и, вместо установления соединения с указанным сервером, соединиться с SOCKS-сервером, передать ему запрос на соединение, и вернуть получившийся дескриптор сокета приложению, которое запросило соединение. А приложение даже и не поймет, что работает через SOCKS-прокси, ибо протокол SOCKS прозрачен и для клиента, и для сервера.

Моя реализация этой функции проста до безобразия:

DLLEXPORT int WINAPI my_connect(SOCKET sock, const sockaddr *addr, int namelen)
{
return connectToSocks4(real_connect, real_send, real_recv, "68.102.100.62", 55465, (struct sockaddr_in *) addr);
}

connectToSocks4() — функция, устанавливающая соединение с SOCKS-сервером. На всякий случай, я установил хуки еще и на send() и recv(), хотя в конкретно данном случае это не требуется. Их реализацию приводить тут я не буду — она аналогична функции connect().

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

  • DetourRestoreAfterWith() — восстанавливает таблицу импорта приложения после его запуска;
  • DetourTransactionBegin() — вызывается перед установкой/снятием хука;
  • DetourUpdateThread() — инициализирует установку/снятие хука для указанного потока;
  • Подмену заданного вызова выполняет функция DetourAttach:DetourAttach(&(PVOID&)real_connect, my_connect);

После выполнения подмены нужно завершить транзакцию вызовом функции DetourTransactionCommit(). В случае, если нужно убрать хук, выполняются те же самые действия, только DetourAttach() заменяется на DetourDetach(). Теперь давай посмотрим спецификацию протокола SOCKS4.

После установки коннекта к SOCKS-серверу нужно отправить пакет с информацией о наших намерениях. Пакет выглядит так:

  • 1 байт — версия SOCKS, в случае четвертой версии протокола — 0x04;
  • 1 байт — команда (соединиться с сервером — 0x01, привязать порт — 0x02);
  • 2 байта — порт удаленного сервера (либо порт, который нужно открыть);
  • 4 байта — IP-адрес, к которому нужно присоединиться;
  • N+1 байт — C-строка длины N, содержащая идентификатор пользователя, завершенная нулевым байтом. N может быть равна нулю.

После этого сервер отправит нам пакет, в котором укажет, все ли его устраивает:

  • 1 байт — нулевой, игнорируетсфя;
  • 1 байт — результат:
    • 0x5a — все ок,
    • 0x5b — fail,
    • 0x5c — недоступен клиентский identd,
    • 0x5d — identd не смог опознать пользователя.
  • 2 байта — должны игнорироваться;
  • 4 байта — должны игнорироваться.

Пример «общения» клиента с сервером:

Клиент:

0x04 | 0x01 | 0x00 0x50 | 0x42 0x66 0x07 0x63 | 0x00

Сервер:

0x00 | 0x5a | 0xXX 0xXX | 0xXX 0xXX 0xXX 0xXX

Где 0xXX — случайное значение (эти байты должны игнорироваться)

После этого можно общаться с SOCKS-сервером, как с обыкновенным сервером, к которому мы и подключаемся. Вот как я реализовал подключение к SOCKS-у:

<… Вырезан код …>
char reply[8];
char packet[9];
packet[0] = 0x04;
packet[1] = 0x01;
packet[2] = r_host->sin_port / 0x100;
packet[3] = r_host->sin_port % 0x100;
packet[4] = r_host->sin_addr.S_un.S_un_b.s_b1;
packet[5] = r_host->sin_addr.S_un.S_un_b.s_b2;
packet[6] = r_host->sin_addr.S_un.S_un_b.s_b3;
packet[7] = r_host->sin_addr.S_un.S_un_b.s_b4;
packet[8] = 0x00;
r_send(sock, packet, 9, 0);
memset(reply, 0x00, 9);
int recvd = r_recv(sock, reply, 9, 0);
<… Вырезан код …>
return sock;

Не забудь — при коннекте к сокс-серверу нужно использовать оригинальную функцию connect(), а то получится бесконечная рекурсия.
На эти грабли я уже наступил, поэтому считаю своим долгом предупредить о них тебя.

 

Вердикт

Ну, вот и все — сегодня я научил тебя перехватывать API-вызовы абсолютно легальным, с точки зрения программирования, путем, используя костыль от самого Microsoft. Как всегда, не оставлю тебя без задания — слабо реализовать тут SOCKS5? (не бойся, пятый протокол ненамного сложнее, чем четвертый). Я надеюсь, ты понимаешь, что эту библиотеку можно использовать несколько в других, более «крупных», целях. Ну, ты меня понял :).

Только не забывай про законодательство, а то дяди в серой форме поставят вполне легальные хуки на все твои действия. Возможно, даже сроком в несколько лет.

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии