Rustock.C - секретные техники анализа или заглядывая реверсеру через плечо

Автор: (c)Крис Касперски ака мыщъх

Легендарно-неуловимому rootkit'у Rustock.C посвящены десятки технических публикаций, детально описывающих, что именно он делает, но никто из реверсеров не говорит - как именно он это выяснил, какие инструменты и методики анализа использовались? Мыщъх предпринял попытку заполнить этот пробел, поделившись экзотическими блюдами хакерской кухни.

Введение

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

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

Rustock.C - словно проливной дождь в знойной пустыне. Это настоящий вызов, реально напрягающий мозги и на несколько дней (а то и недель) выбивающий хакера из круговорота повседневной суеты. Окружающий мир исчезает. Остается только монитор, клава, Русток и бесчисленное множество распечаток, осенним листопадом падающих на пол. Rustock.C затягивает, не отпуская даже во сне, заставляя хакера подскакивать среди ночи, лихорадочно опробуя только что вспыхнувшую идею, озарившую казалось совершенно неразрешимую проблему.

Детект виртуальных машин, куча антиотладочных приемов, многослойное шифрование, полиморфный код, жестокая обфускация, привязка к зараженной машине, повсюду нестандартные приемы с трюками, использующимися впервые. И все это происходит на самом низком уровне операционной системы в нулевом кольце с активным противодействием ядерным отладчикам и детекторам классических руткитов! К тому же, Rustock.C - это едва ли не единственный вирус, заражающий драйвера и умело обходящий брандмауэры и антивирусы! Короче, тут есть, на чем поработать и есть чему поучиться, совершенствуя свое мастерство!

А и Б сидели на трубе

Как и следует из его названия, Rustock.C - не первый в своем семействе. До него были версии A и B, построенные по тому же принципу, что и С, но гораздо хуже защищенные, а потому начинающим хакерам рекомендуется начинать свой путь именно с них. Когда версия B будет разобрана по винтикам и байтикам, почерк автора вируса станет знаком настолько, что "жуткий и ужасный" Rustock.C окажется не такой уж непроходимой проблемой. К слову сказать, у мыщъха имеется множество сэмплов, опознаваемых антивирусами как Rustock.C, но радикально отличающимися поведением расшифровщика третьего уровня (в частности, в некоторых сэмплах отсутствует привязка к чипсету, имеющаяся в других).

Также имеется версия E и еще куча других, однако анализировать всех их - смысла нет, поскольку вариации не так уж значительны и ничего нового мы не узнаем, а вот времени убьем изрядно.

В поисках Rustock'а

Забавно, но даже антивирусным компаниям пришлось всерьез напрячься, чтобы раздобыть живые образцы этого легендарного вируса и это с учетом распределенных систем для отлова малвари с кучей датчиков и сенсоров, рассредоточенных по всему миру, грабящих весь трафик и сохраняющих его длительное время для последующего анализа. Но датчики упорно молчали. Вируса не было. То есть, не то, чтобы совсем не было, но скудность собранного "урожая" вызывает смутные сомнения в цифрах, приводимых различными исследовательскими группами: сколько времени неуловимый Rustock.C жил и как много машин он заразил?

Где же все-таки брать образцы для анализа?! В антивирусные компании обращаться бесполезно. Все равно не дадут. Во всяком случае, через официальные каналы. А вот по дружбе... в обход всех должностных инструкций... Однако, для завязывания знакомств требуется время и если хакер по натуре человек не очень общительный и не вращается в индустрии безопасности, зная всех и каждого, ему придется грызть асфальт зубами или задействовать профессиональные социальные сети, крупнейшей из которых на данный момент является www.linkedin.com.

Мыщъх с алиской

Рисунок 1. Мыщъх с алиской на LinkedIn (алиска - вторая сверху, знает ассемблер и еще кучу языков, включая Перл, Питон, Си, Руби и...)

Грубо говоря, www.linkedin.com - это то же самое, что "Мой мир", только порядка на два круче и реализованный должным образом. В принципе, все социальные сети построены по общей схеме: "я", "мой друг" и "друг моих друзей", причем количество контактов при переходе от одной ступени к другой увеличивается в геометрической прогрессии! Имея десяток-другой знакомых первого уровня, через списки их контактов можно дотянуться практически до кого угодно, а дотянувшись - познакомится через общих друзей, используя их как залог своей лояльности, что мыщъх не пойдет и не начнет распространять полученный образец вируса налево и направо.

На http://www.offensivecomputing.net (требуется регистрация) есть один экземпляр Rustock.C, однако нет дропера и чтобы заставить "зверька" заработать, придется конкретно напрячь свой хвост (хотя после небольшой доработки "напильником" он соглашается жить под VM Ware и даже размножается, нужно только "отломать" процедуру детектирования, ну и, конечно, подобрать ключ привязки к машине, шифрующий основной код вируса).

www.offensivecomputing.net

Рисунок 2. www.offensivecomputing.net - огромная коллекция малвари на любой вкус.

Другой источник сэмплов - http://malwaredatabase.net/blog/, где Rustock.C периодически то появляется, то исчезает. А еще можно влиться в ряды распределенной сети "Malware Database Over Dropbox", где малварь хранится не на публичных серверах (которые закрываются также стихийно, как и открываются), а на локальных жестких дисках членов сети. Короче говоря, это тот же самый eMule, только без координирующих серверов. На компьютер устанавливается специальный клиент и файл, положенный в определенную папку, немедленно становится доступным всем остальным членам сети.

Rustock.C там есть, причем в широком ассортименте сэмплов, добытых с различных компьютеров, что существенно упрощает анализ, поскольку мусорный код вычищается путем сравнивания нескольких образцов друг с другом.

Для подключения к этому ресурсу необходимо быть принятым в ряды сообщества "Professional Reverse Engineers & Ethical Hackers" - http://ehre.collectivex.com/ (требуется регистрация, причем регистрация премодерируемая, т.е. координатор вправе немотивированно отказать, лично у меня вступление в ряды заняло с неделю достаточно оживленной переписки, естественно, на английском, чес-слово - процедура устройства в антивирусную компанию с открытием доступа к коллекции вирусов отняла у мыщъха гораздо меньше времени).

ehre.collectivex.com

Рисунок 3. ehre.collectivex.com - сообщество профессиональных реверсеров и хакеров, живущих по понятиям.

На хакерских форумах ссылки на Rustock.C (выложенный на "Рапишиду") попадались не раз и не два, но сейчас все они битые, однако если запастись терпением, то откопать живого зверька вполне можно.

Затерянный в недрах кода

Добытый образец Rustock.C грузим в HIEW, IDA-Pro или Ольгу. Стоп! А Ольга тут причем? Ведь Rustock.C заражает драйвера режима ядра, а Ольга работает в прикладном режиме, что, впрочем, совсем не мешает ей грузить драйвер как DLL в Ring 3, где, конечно, драйвер работать не будет, но первый уровень шифровки из трех снимается Ольгой на счет "раз", а вот со вторым уже возникают практически непреодолимые трудности. Но не будем забегать вперед.

Rustock.C в Ольге

Рисунок 4. Rustock.C в Ольге.

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

.00010200: 60            pushad
.00010201: B9D5030000    mov   ecx,0000003D5  ---v (1)
.00010206: 31DB          xor   ebx,ebx
.00010208: 31D2          xor   edx,edx
.0001020A: 81C331085F7D  add   ebx,07D5F0831
.00010210: 83D200        adc   edx,000
.00010213: 49            dec   ecx
.00010214: 75F4          jne   .00001020A  ---^ (2)
.00010216: BE33020100    mov   esi,000010233  ---v (3)
.0001021B: 89F7          mov   edi,esi
.0001021D: B905DF0000    mov   ecx,00000DF05  ---v (4)
.00010222: 89D8          mov   eax,ebx
.00010224: C1E803        shr   eax,003
.00010227: 01C2          add   edx,eax
.00010229: 87DA          xchg  ebx,edx
.0001022B: AD            lodsd
.0001022C: 29D8          sub   eax,ebx
.0001022E: AB            stosd
.0001022F: 49            dec   ecx
.00010230: 75F0          jne   .000010222  ---^ (5)
.00010232: 61            popad

Листинг 1. Первый уровень шифровки.

За концом расшифровщика следует "мусорный" код, который, собственно, и расшифровывается. Достаточно установить точку останова на команду POPAD, нажать <F9> (Run) и... первого слоя шифровки как не бывало. Можно смело сохранять дамп.

Решение номер два. Написать скрипт для IDA-Pro, расшифровывая код прямо в дизассемблере (плюс исчезают проблемы с возможными ошибками сохранения дампа). Способ надежный, но мыщъх - зверь ленивый и потому просто рипнул оригинальный код, засунул его в ассемблерную вставку на Си, дописал еще несколько строк, расходующихся на файловый ввод/вывод, в результате чего через пару минут появился статический расшифровщик (см. листинг 2).

#define BASE 0x00010000

decrypt(char *p)
{
        __asm // ripped code
        {
                mov        ecx, 0000003D5h
                xor        ebx, ebx
                xor        edx, edx
                ...
                mov        esi, [p]
                add        esi, 233h
                ...
                jnz        short loc_10222
        } // end of rip
}

main()
{
        FILE *f_in, *f_out; int base = BASE; char *p; fpos_t pos;
        if (!(f_in = fopen("rustock-c", "rb"))) return printf("-ERR:open rustock\n");
        if (!(f_out = fopen("rustock-c-un", "wb"))) return printf("-ERR:open rustock-un\n");
        fseek(f_in, 0, SEEK_END); fgetpos(f_in, &pos); fseek(f_in, 0, SEEK_SET);
        p = (char *) malloc((size_t)pos); fread(p, 1, (int) pos, f_in);
        decrypt(p); fwrite(p, 1, (int)pos, f_out); fclose(f_out); fclose(f_in);
}

Листинг 2. Статический расшифровщик первого уровня с рипнутым кодом.

В оригинальном коде расшифровщика потребовалось заменить всего одну строку (в листинге 1 она по адресу 00010216): MOV ESI,000010233h -> MOV ESI, [p]/ADD ESI, 233h, где p - указатель на блок памяти, в который загружен расшифровываемый файл, а 233h - смещение первого зашифрованного байта, следующего непосредственно за командой POPAD. Подобный прием (рипанье кода) - весьма эффективный способ для борьбы даже с навороченными шифровщиками, правда, если код шифровщика разбросан по десяткам функций, "размазанных" по всей программе, рипанье существенно усложняется и такие защиты уже предпочтительнее снимать в отладчике.

Первый уровень шифровки снят

Рисунок 5. Первый уровень шифровки снят, а за ним второй! Голубым выделен код расшифровщика, серым - то, что он расшифровывает.

Расшифрованный Rustoc-C-unpack загружаем в IDA-Pro и смотрим на панель навигатора (см. рис. 5), где синим цветом показан код, а серым - данные. То есть, никакие это, конечно, не данные, а основное вирусное тело. Зашифрованное, разумеется. Расшифровщик второго уровня занимает сравнительно небольшую часть, сбившуюся в левый угол, однако не стоит надеяться, что он дастся нам также легко, как и предыдущий!

Функции расшифровщика второго уровня

Рисунок 6. Функции расшифровщика второго уровня, переплетенные тесным клубком.

Попытка визуализации расшифровщика второго уровня вгоняет IDA-Pro в глубокую задумчивость, после чего она отображает жуткое хитросплетение графов, похожее на паутину, сотканную обкуренным пауком (см. рис. 6).

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

Фрагмент блок-схемы

Рисунок 7. Фрагмент блок-схемы расшифровщика второго уровня.

Просматривая код распаковщика второго уровня, мы натыкаемся на кучу привилегированных команд, включающих в себя и обращение к отладочным регистрам, что на прикладном уровне не трассируется в принципе, вызывая исключение:

.00010C17: 0F21C0            mov   eax,dr0
.00010C1A: E845340000        call  .000014064  ---v (2)
.00010C1F: 0F21C8            mov   eax,dr1
.00010C22: E83D340000        call  .000014064  ---v (3)

Листинг 3. Привилегированные машинные команды в расшифровщике второго уровня.

Следовательно, мы должны либо модифицировать код, переписав его так, чтобы он работал в Ring 3 без нарушения функционала, либо же воспользоваться эмулятором типа x86emu (Plug-in для IDA-Pro), который лучше всего брать прямо с CVS (https://sourceforge.net/projects/ida-x86emu/), где находится самая свежая версия с кучей фиксов, сильно отличающаяся от последнего официального релиза (см. рис. 8).

Впрочем, x86emu эмулирует ограниченный набор инструкций/регистров и потому без ручной работы здесь не обойтись. Как вариант, можно попробовать BOCHS (со встроенным отладчиком), но BOCHS очень медленно работает, а с популярными отладчиками ядерного уровня Rustock.C ведет отчаянную войну и потому вовсе не факт, что "живая" отладка приведет нас к цели быстрее эмулятора.

Эмулятор x86emu

Рисунок 8. Эмулятор x86emu на CVS с последними фиксами.

Но обращения к отладочным регистрам - это мелочи. Очень быстро мы встречаем код, взаимодействующий с ядерной памятью, в частности - следующий фрагмент (см. листинг 4) осуществляет разбор таблицы экспорта ntoskrnl.exe на предмет поиска необходимых вирусу функций. Как это он делает?! Сначала что-то грузит из указателя, полученного из FS:[38h], где на прикладном уровне находится кол-во критических секций, принадлежащих потоку (TIB->Count of owned critical sections), что не дружит со здравым смыслом.

Но ведь Rustock.C отнюдь не на прикладном уровне работает! А ядро здесь держит Processor Control Region (или, сокращенно, _KPCR), по смещению 38h от начала которого лежит указатель на глобальную таблицу дескрипторов прерываний (IDT), "смотрящую" непосредственно в ядро (если, конечно, ее никто не захачил).

Как мы это узнали?! Раскладка ядерной памяти хорошо описана в документации на Soft-Ice, а определения самих структур можно найти в NTDDK от Microsoft или обратиться к замечательному ресурсу "Windows Vista Kernel Structures", содержащего практически всю информацию о ядре Висты (http://www.nirsoft.net/kernel_struct/vista/index.html).

000138C4       mov    eax, large fs:38h    ; _KPCR->IDT;
000138CA       add    eax, 4
000138CD       mov    eax, [eax]
000138CF       xor    al, al
000138D1       stc
000138D2       jb     loc_10C00
00010C00       sub    eax, 5A9D558h
00010C05       sub    eax, 0FA562BA8h
00010C0A       cmp    word ptr [eax], 'ZM'
00010C0F       pushf
00010C10       call   sub_13667
...
00010E31       cmp    dword ptr [eax+ebx], 'EP'
00010E38       pushf
00010E39       call   sub_14484

Листинг 4. Прямой поиск ядра в памяти.

Кстати говоря, Lukasz Kwiatek, также исследовавший Rustock.C, приводит очень похожий, но подозрительно "вылизанный" код (см. листинг 7, http://www.eset.com/threat-center/blog/?p=127), что наводит на определенные размышления - либо он дербанил другую версию, либо же прогнал код через деобфускатор.

00000261         mov   eax, dword ptr fs:38
00000267         mov   eax, [eax+4]
0000026D         xor   al, al
0000026F         sub   eax, 100h
00000275         cmp   word ptr [eax], 'ZM'
0000027A         jnz   loc_26F
00000280         mov   bx, [eax+3Ch]
00000284         and   ebx, 0FFFFh
0000028A         cmp   dword ptr [eax+ebx], 'EP'
00000291         jnz   loc_26F

Листинг 5. Прямой поиск ядра в памяти в варианте от Lukasz Kwiatek'a.

Возникает резонный вопрос: как жить дальше и что с этим делать?! Спускаться в ядро как-то не хочется. И правильно! Поднять ядро на прикладной уровень намного быстрее, да и надежнее! IDA-Pro позволяет грузить намного более одного файла одновременно, что осуществляется посредством вызова функции load_nonbinary_file(), доступной из plug-in'ов, но отсутствующей в пользовательском интерфейсе.

Ок, пишем plug-in, грузящий любые библиотеки и драйвера, какие мы только захотим (включая ядро операционной системы), после чего останется только присобачить несложный эмулятор окружения ядра (чтобы в селекторе FS был не мусор, а валидные данные) и можно смело продолжать эмуляцию посредством x86emu.

Внешний вид эмулятора x86emu

Рисунок 9. Внешний вид эмулятора x86emu.

Ключевой фрагмент plug-in'а, работающий на версиях IDA-Pro вплоть до 4.7 включительно, приведен ниже (см. листинг 6):

void idaapi run(int arg)
{
        load_info_t *ld;
        warning("plugin \"dual-load\" is called!");

        ld = build_loaders_list("KERNEL32.DLL");
        load_nonbinary_file("KERNEL32.DLL","KERNEL32.DLL",".",
                NEF_SEGS|NEF_RSCS|NEF_NAME|NEF_IMPS|NEF_CODE,ld);

        load_nonbinary_file("NTDLL.DLL","NTDLL.DLL",".",
                NEF_SEGS|NEF_RSCS|NEF_NAME|NEF_IMPS|NEF_CODE,ld);
        qfree(ld);
}

Листинг 6. Загрузка нескольких файлов в одну базу IDA-Pro 4.7.

Начиная с IDA-Pro 4.8, прототип функции load_nonbinary_file() был злостно изменен Ильфаком без всякой заботы об обратной совместимости и старые plug-in'ы перестали работать, однако небольшая косметическая операция (см. листинг 7) спасает операцию!

void idaapi run(int arg)
{
        load_info_t *ld;
        warning("plugin \"dual-load\" is called!");

        /* NOTE: KERNEL32.DLL and NTDLL.DLL has to be in the current directory!!! */
        linput_t *p = open_linput("KERNEL32.DLL",false); // fix
        ld = build_loaders_list(p);
        load_nonbinary_file("KERNEL32.DLL", p, ".",
                        NEF_SEGS | NEF_RSCS | NEF_NAME | NEF_IMPS | NEF_CODE, ld);
        close_linput(p);
}

Листинг 7. Загрузка нескольких файлов в одну базу IDA-Pro 4.8+.

С загруженным ядром расшифровщик второго уровня снимается в IDA-Pro на ура и мы попадаем в... третий. А вот в нем... нас ждет настоящий "подарок" судьбы, ставящий в тупик и высаживающий на измену. Вирус, обращаясь к PCI-шине, извлекает оттуда параметры моста "PCI/ISA", формируя RC4-ключ на основе Device ID и Vendor ID, перебрать которые тупым Brute-Force совершенно нереально. Да и ненужно!!!

Роковая ошибка создателя Rustock.C заключается в том, что производителей чипсетов (где, собственно говоря, и находится обозначенный мост) не так уж и много. Просто идем на любую достаточно полную онлайновую базу PCI-устройств (например, www.pcidatabase.com), даем ей запрос, после чего осуществляем элегантный перебор на небольшой выборке.

Все! С падением последнего бастиона с вирусом можно делать все, что угодно. В частности, отломав детектор VM Ware (которая определяется через IDT), запустить его в среде виртуальной машины, наблюдая за изменениями в памяти и файловой системе. Никакие маскировочные приемы не помогут против посекторного сравнения образов виртуального жесткого диска до и после заражения. То же самое относится и к дампам памяти. Вторая роковая ошибка создателя Rustock.С - отсутствие перехвата функции KeBugCheckEx, которая, собственно, и сбрасывает дамп на диск.

Заключение

А вот некоторые вообще не заморачиваются с ручной распаковкой и эмуляцией, запуская вируса под доброкачественным виртуализатором, с которым Rustock.C никак не сражается. Помимо SEYE-эмулятора (недоступного широким массам), вполне сгодится и BOCHS, в котором (с учетом наличия исходных текстов) ничего не стоит подделать Device ID и Vendor ID, правда мы должны заранее знать, что за чипсет установлен на зараженной машине. Естественно, пользы (познавательного плана) в подобном способе запуска вируса немного. Нормальные вирусы вообще-то и без танцев с бубном запускаются.

Наибольший интерес представляет именно скрупулезный анализ вируса и используемых им приемов, многие их которых стоит взять на вооружение, если не на свое, так хоть на чужое, в смысле - приготовиться к появлению "зверьков", оборудованных модулями, выдранными из Rustock.C и, возможно, основательно доработанными.

Ссылки по теме