Grabduck

Мал, да удал: Trojan-Downloader.Win32.Tiny

:

Доброго дня всем.
В сегодняшнем выпуске много технических подробностей. Так что уж извините, если их слишком много :)

Письмо


Итак, начинаем. На входе у нас письмо с текстом «Journalist shot in Georgia! See attached video. Password is 123» и с приложенным файлом «Georgia.zip».

Для более или менее полного анализа нам понадобятся:
— Самый Лучший дизассемблер в мире IDA
— Неплохой Отладчик Windbg
— python 2.5 (строго говоря, подойдет любой, просто у меня 2.5)
— Visual Studio какая-нибудь и Microsoft SDK, чтобы собрать небольшую программу на с++.
— упаковщик программ upx
— ну и чего-то еще по мелочи.

Распаковываем присланный нам файл, и видим, что наши подозрения оправданы: видео там и рядом не ночевало.

joined.exe (md5:607af96b03addadf28cf9280701df191)
Dr.Web: Trojan.Packed.151
Kaspersky: Trojan-Downloader.Win32.Agent.abqe


Распаковка


Приступ первый. Сохраняем файл в безопасное место, снимаем с него права на выполнение, загружаем в ida. Сразу же ida ругается на IAT в нестандартной секции и предупреждает, что не все импортируемые функции будут видны, соглашаемся продолжать. После этого ida немного думает, произоводя дизассемблирование и анализ нашего файла, а, закончив, сама переходит в Entry Point.

Видим в окошке «Navigation» какие-то большие и сложные вычисления, а, нажав Ctrl-S, в открывшемся списке сегментов видим две записи: «UPX0» и «UPX1». И это отчетливо отдается у нас в могзу: «упакованно upx». Точнее, есть вероятность, что упакованно upx, и мы отправляемся эту возможность проверять.

D:\programs\upx>upx -t  D:\prog\virus\0001.Georgia-2008-08-25\joined.exe

testing D:\prog\virus\0001.Georgia-2008-08-25\joined.exe [OK]

То есть, upx опознал свой файл. Рапаковываем:
D:\programs\upx>upx -d  D:\prog\virus\0001.Georgia-2008-08-25\joined.exe
        File size         Ratio      Format      Name
   --------------------   ------   -----------   ----------
     10240 <-      7680   75.00%    win32/pe     joined.exe

Unpacked 1 file.

После чего, открываем распакованный файл в ida и бегло просматриваем.

Основная функция выглядит довольно просто: сначала производится некоторая настройка с использованием LoadLibrary и GetProcAddress, чтобы не светить используемые функции в импорте. После идет вызов EnumResourceNames, которая перечисляет все имена ресурсов. Что ж, ресурсы, это интересно, посмотрим, что находится в ресурсах нашего файла.

Открываем редактор ресурсов (у меня это старенький ResourceExplorer), и видим среди прочего отличный бинарный ресурс под названием «LOADER.19B099.EXE». Исполняемый файл внутри ресурса! Ну что, очень похоже, что основной вирус экспортирует вложеннный и запускает каким-то образом. Предлагаю пока отложить первый файл и поковырять этот бинарный ресурс.

Троян в трояне

Экспортируем ресурс в файл, смотрим, что говорят антивирусы.

resource-loader.19b099.exe (md5:ca843aed6a7910fcb36bd0146af990ca)
Dr.Web:  Trojan.Packed.151
Kaspersky: Trojan-Downloader.Win32.Tiny.bvi

(Обратите внимание, кстати, что докторвеб маркирует этот файл также, как и joined.exe).

Ну, во-первых, размер. 1.6 килобайт. Значит основательно работали напильником, заставляли компилятор оптимизировать и вообще всячески старались. Во-вторых, такой код можно запросто инъектировать в какой-нибудь другой процесс — если этот код, конечно, правильно написан. В третьих, никаких импортов вообще — еще один плюс к гипотезе шелл-кода. Ну и, в четвертых, тело «зашифровано». Вот что у нас творится в точке входа:

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

byte* data = [EIP], полученный хитрыими манипуляциями.
x = 0A9h;
while( data & 0xFFF )
{
	*data = *data ^ x;
	x--;
	data--;
}

После чего управление передается куда-то в середину распакованного кода.

Распаковка-2


Ну что же, раз файл сам себя распаковывает, пусть сам это и делает. Нам лишь будет нужно остановиться в правильном месте, чтобы увидеть распакованный код. Запускаем Windbg, жмем Ctrl-E (Open executable file), после этого дебагер останавливается. Это еще не точка входа, а лишь начало процесса. Выясняем точку входа (если мы вдруг забыли ее, когда разглядывали дизассемблированный код):
0:001> !dh 00400000 
File Type: EXECUTABLE IMAGE
...
OPTIONAL HEADER VALUES
     10B magic #
    7.10 linker version
     400 size of code
     200 size of initialized data
       0 size of uninitialized data
    132F address of entry point		
    1000 base of code

Переходим на нее:

0:000> g 40132F 

Нас интересует конечный этап расшифровки: переход на расшифрованный код, который происходит в строке:

.text:0040135D   push  offset dword_401135

Переходим сюда:
0:000> bp 0040135D

Жмем «g» (Go), и, не успеваем моргнуть глазом, как программа останавливается. Мы, кстати, можем увидеть прямо отсюда, как этот код выглядит теперь. По адресу, куда планируется переход, находится вот что:
0:000> uf 00401135
image00400000+0x1135:
00401135 55              push    ebp
00401136 8bec            mov     ebp,esp
00401138 81ec98060000    sub     esp,698h
0040113e 53              push    ebx

Ну, вот и прекрасно. Запускаем какой-нибудь LordPE, делаем дамп файла, и код в дизассемблере выглядит теперь более удобочитаемым. Файл размером не изменился, он по-прежнему крошечный. В нем всего три функции. Займемся их изучением.

Основная функциональность

Вот что в точке входа.

Особый интерес представляет связка такого вида:

mov     ecx, 0D82AFA6Fh
call    my_get_proc_address
...
call    eax             

Через ECX передается параметр в функцию my_get_proc_address (это я ее так назвал. В оригинале она называлась sub_00401135 :), а то, что она возвращает — это ни что иное, как адрес некой функции, на которую передается управление инструкцией call.

Что же делает функция my_get_proc_address?

(Кода там довольно много, те, кому не лень, посмотрят сами, см. ниже). Вкратце:

1. Получает указатель на PEB:

mov     eax, large fs:30h

Мы, кстати, тоже можем посмотреть, что там находится:

0:000> dt nt!_PEB
ntdll!_PEB
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar
   +0x002 BeingDebugged    : UChar
   +0x003 SpareBool        : UChar
   +0x004 Mutant           : Ptr32 Void
   +0x008 ImageBaseAddress : Ptr32 Void
   +0x00c Ldr              : Ptr32 _PEB_LDR_DATA
   +0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS
   +0x014 SubSystemData    : Ptr32 Void
...

2. Получает из PEB список загруженных модулей и проходится по нему. Мы тоже знаем, где лежит этот список:

0:000> dt -r1 nt!_PEB
ntdll!_PEB
...
   +0x00c Ldr              : Ptr32 _PEB_LDR_DATA
      +0x000 Length           : Uint4B
      +0x004 Initialized      : UChar
      +0x008 SsHandle         : Ptr32 Void
      +0x00c InLoadOrderModuleList : _LIST_ENTRY
      +0x014 InMemoryOrderModuleList : _LIST_ENTRY
      +0x01c InInitializationOrderModuleList : _LIST_ENTRY
...

3. Для каждого из этих модулей функция находит таблицу экспорта, «получает список» экспортируемых функций. Для каждой экспортируемой функции вычисляется некий хэш, который и сравнивается с тем самым странным числом, которое передается в функцию как агрумент.

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

Вот, значит эта самая функция, довольно простая. Ее мы и запрограмируем. Как на чем, на питоне! Он теперь везде, и в гугле его, говорят, любят.

#!bin/python
def rotl(x, n):
    return ((x << n) | (x >> (32-n))) % 2**32

def hash(s):
    x = 0
    for i in s:
        x = (rotl(x, 5) ^ ord(i)) % 2**32
    return x

Питон своей поддержкой больших чисел нам мешает, поэтому эмулируем 32-битовую арифметику, беря модуль по 2 32.

Значит вспоминаем, о чем мы говорили раньше: «для всех экспортируемых функций всех модулей». Но, постой-ка, скажут внимательные читатели. Ведь шелкод ничего не импортирует! Попался! и довольно потирают руки :) Но есть в исполняемых файлах windows такая извечная русская печаль: создавая новый win32 процесс из PE-файла, загрузчик всегда загружает в него две стандартные библиотеки: ntdll и kernel32. Вот у нас уже и есть список из двух модулей.

Хорошая новость заключается в том, что мы тоже можем получить имена функций, которых экспортируются этими библиотеками! Старой-престарой утилитой dumpbin. И cygwin, конечно же, иметь под рукой будет неплохо.

dumpbin /exports C:\WINDOWS\system32\kernel32.dll > krnl-exp 
gawk '{print $4}' krnl-exp > krnl-exp1 

Ну, и тоже самое для ntdll. Затем в редакторе убираем остатки заголовка и итога, который генерит dumpbin, получая список в итоге. Натравляем наш питон-скрипт на эти файлы, получаем, что например «0xd82afa6f» означает «LdrLoadDll» — о-очень слабо документированная функция, которой пользуется сам загрузчик. Однако, ее название говорит само за себя — она загружает библиотеку в адресное пространство процесса. Как мы уже видели раньше, в дизассемблированном коде фигурирует строка «ole32.dll», да, точно, это именно ее и загружает зловред функцией LdrLoadDll.

Проделываем пассы дампбином и над ol32.dll, находим нашим доморощенным брутфорсом все «секретные» функции (в порядке вызова).

0xd82afa6f — «LdrLoadDll»
0x872b8a64 — «GetTempPathW»
0x4b99cb08 — «CoInitializeEx»
0x938ed88c — «CoInitializeSecurity»
0x7279d017 — «CoCreateInstance»
0xbde95c09 — «GetTempFileNameW»
0x050798d0 — «Sleep»
0x4b09a52a — «CreateProcessW»
0x0f12e6f0 — «CoUninitialize»
0xe3db70a7 — «ExitProcess»

Делаем в уме небольшое описание этого процесса: «Загружаем ole32 (LdrLoadDll), инициализируем COM (CoInitialize*), создаем COM-объект (CoCreateInstance), полчаем название временного файла (GetTemp*), ждем у моря погоды (Sleep), создаем процесс (CreateProcessW), выходим (все остальное). Брр, аж мороз по коже, как методично и равномерно нас собираются заразить какой-то гадостью.

Вобщем, самая большая неприятность — это CoCreateInstance и последующий CreateProcess. Потому как вызовы методов ком-объекта делаются динамически, и эти методы совершенно не поименованы. То есть, мы, конечно, можем узнать, что там вызывается, но это будет геморрой… А что поделать.

Что же за объект создает этот гад? В CoCreateInstance передаются два Guid'а — в коде они выглядят так:

.text:00401000 rclsid dd 4991D34Bh, 429180A1h, 2833B683h, 97906B36h
.text:00401010 riid dd 5CE34C0Dh, 4C1F0DC9h, 0A1DA7C89h, 7CEE8CB7h

Из-за особенностей хранения данных, „user-friendly“ guid выглядит „перемешанным“ например, если в памяти массив {5CE34C0Dh, 4C1F0DC9h, 0A1DA7C89h, 7CEE8CB7h}, обозначает {5CE34C0D-0DC9-4C1F-897C-DAA1B78CEE7C}. Ну, уже кое-что. Автор шелкода включил захардкоданный guid, значит он уверен, что на большинстве машин это сработает. Ищем по своему реестру это идентификаторы и получаем:

{5CE34C0D-0DC9-4C1F-897C-DAA1B78CEE7C} 
InProcServer32 = C:\WINDOWS\system32\qmgrprxy.dll = Background Intelligent Transfer Service Proxy
{4991d34b-80a1-4291-83b6-3328366b9097}
Background Intelligent Transfer Control Class 1.0

Зловещая тень „разумного“ сервиса закачки файлов маячит за этими идентификаторами. Background Intelligent Transfer Service, или в просторечии, BITS, — это, кстати, та самая штука, которая качает из интернета апдейты для windows. Короче, есть подозрение, что таким образом создается ком-объект, который с помощью BITS качает к нам на машину другой, более опасный вирус. Но это пока только догадки, и мы отправляемся в мснд за документацией по BITS.

Покопавшись немного, находим небольшой пример кода в Connecting to the BITS Service.

hr = CoCreateInstance(__uuidof(BackgroundCopyManager), NULL,
                        CLSCTX_LOCAL_SERVER,
                        __uuidof(IBackgroundCopyManager),
                        (void**) &g_pbcm);

Компилируем, дизассемблируем, смотрим, что получилось в нашей демо-программе:

lea     eax, [ebp+ppv]
push    eax             ; ppv
push    offset dword_408140 ; riid
push    4               ; dwClsContext
push    0               ; pUnkOuter
push    offset dword_408130 ; rclsid
call    ds:CoCreateInstance
...
.rdata:00408130 ; CLSID dword_408130
.rdata:00408130 dword_408130 dd 4991D34Bh,429180A1h,2833B683h,97906B36h; 0
.rdata:00408140 ; IID dword_408140
.rdata:00408140 dword_408140 dd 5CE34C0Dh,4C1F0DC9h,0A1DA7C89h,7CEE8CB7h; 0

Собственно, что и требовалось доказать. Те самые Guid'ы! Пытаясь выяснить, что имено скачивается в помошью BITS, видим, что имя файла „зашифровано“.

Опять быстренько пишем скрипт на питоне, который расшифровывает нам эту строку:

def xor(enc, key):
    res = ''
    for x in enc:
        y = (x ^ key) % 255
        if y == 0:
            break
        key = (key + 1) % 2**32;
        res = res + (chr(y))
    return res

… и получаем результат, потрясающий нас до глубины души!!!


http://reddii.org/traffic/all/files/filebyaka.exe 

(Внимание! По указанному адрему до сих пор находится вирус. Попытка открыть этот файл может окончиться фатально для вас и вашей машины).

Название говорит само за себя. Это бяка, и еше какая!

filebyaka.exe (md5:fc85dab5849416f8796b799fc209395a)
Dr.Web: Trojan.Packed.619
Kaspersky: Backdoor.Win32.Agent.ajd

Собственно, анализ этой бяки вы можете найти по ссылке xanalysis.blogspot.com, которую подсказали в комментариях.

Все, что происходит дальше, мы можем предсказать, даже не возвращаясь в дебагер. Создается задание (Job) для BITS, запускается закачка. Потом каждые 10 милисекунд проверяется статус задачи. Когда закачка завершается, скачанный файл запускается на выполнение. Фух, потора килобайта, а столько возни!

Итого


„Большой“ троянский конь внутри себя как ресурс содержит „маленького“ троянского жеребенка. Этот вирус, всего в полтора килобайта размером (это две страницы текста у меня в редакторе), умеет скачивать и запускать на выполнение другой, более опасный вирус. Сделан он в стиле шелкода, прямо „по учебнику“. Б ольшая часть используемых техник подробно рассмотрена в статье Evolving Shell Code. Троян делает небольшой финт ушами, и вместо реализации сетевых соединений, http протокола, и проч., просто использует сервис windows под названием Background intelligent transfer service (именно это позволяет уменьшить размер кода). Системные функции вызываются не по прямым адресам, а функции, которые используются для работы с файлами, типа CreateFile, не вызываются вообще. Есть большое сомнение, что какой-либо из существующих антивирусов способен опознать такую зловредную функциональность одним только эвристическим анализом. Скачивает эта крошка файл с грациозным названем filebyaka.exe, что говорит о сам знаете каких корнях.

Файлы, использованные при анализе, можно найти по этому адресу

На этой оптимистичной ноте прощаюсь с вами. Не запускайте незнакомые файлы. Берегите себя!