Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР 117, СЕНТЯБРЬ 2008 г.

Rustock.C – секретные техники анализа

Крис Касперски



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

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

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

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

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

В поисках Rustock'а

Забавно, но даже антивирусным компаниям пришлось всерьез напрячься, чтобы раздобыть живые образцы легендарного вируса. Где же брать образцы для анализа? В антивирусные компании обращаться бесполезно. Все равно не дадут. Во всяком случае, через официальные каналы. А вот по дружбе, в обход всех должностных инструкций... Тут можно задействовать профессиональные социальные сети, крупнейшей из которых на данный момент является www.linkedin.com. Грубо говоря, www.linkedin.com – то же самое, что «Мой мир», только порядка на два круче и реализованный должным образом. Имея десяток-другой знакомых первого уровня, через списки их контактов можно дотянуться практически до кого угодно, а дотянувшись — познакомиться через общих друзей. Используя их как залог своей лояльности, что я не пойду и не начну распространять полученный образец вируса налево и направо, вполне реально добиться своей цели.
На хакерских форумах ссылки на Rustock.C (выложенный на «Рапишиду») попадались не раз и не два, но сейчас все они битые, однако, если запастись терпением, то откопать живого зверька вполне можно.

На www.offensivecomputing.net (требуется регистрация) есть один экземпляр Rustock.C, однако – нет дропера. Чтобы заставить «зверька» заработать, придется конкретно напрячься. Хотя после небольшой доработки «напильником» он соглашается жить под VMWare и даже размножается (нужно только «отломать» процедуру детектирования, ну и, конечно, подобрать ключ привязки к машине, шифрующий основной код вируса).

Другой источник сэмплов — malwaredatabase.net/blog, откуда Rustock.C периодически то появляется, то исчезает. Также можно влиться в ряды распределенной сети «Malware Database Over Dropbox», где малварь хранится не на публичных серверах (которые закрываются так же стихийно, как и открываются), а на локальных жестких дисках членов сети. Короче говоря, это тот же самый eMule, только без координирующих серверов. Rustock.C там есть, в широком ассортименте сэмплов, добытых с различных компьютеров, что существенно упрощает анализ, поскольку мусорный код вычищается путем сравнивания нескольких образцов друг с другом. Для подключения к этому ресурсу необходимо быть принятым в ряды сообщества «Professional Reverse Engineers & Ethical Hackers» (ehre.collectivex.com). Там тоже требуется регистрация, причем регистрация премодерируемая, то есть координатор вправе немотивированно отказать. Лично у меня вступление в ряды заняло с неделю достаточно оживленной переписки, естественно, на английском — чес-слово, процедура устройства в антивирусную компанию с открытием доступа к коллекции вирусов отняла гораздо меньше времени.

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

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

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

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

.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

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

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

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

#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 rustockn");
if(!(f_out=fopen("rustock-c-un","wb")))return printf("-ERR:open rustock-unn");
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);
}

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

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

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

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

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

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

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

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

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

Обращения к отладочным регистрам — это еще мелочи. Очень быстро мы встречаем код, взаимодействующий с ядерной памятью. В частности, следующий фрагмент осуществляет разбор таблицы экспорта ntoskrnl.exe на предмет поиска необходимых вирусу функций. Как это он делает? Сначала что-то грузит из указателя, полученного из FS:[38h], где на прикладном уровне находится «количество критических секций», принадлежащих потоку (Count of owned critical sections). Но ведь Rustock.C отнюдь не на прикладном уровне работает! А ядро здесь держит Processor Control Region (или, сокращенно, PCR), по смещению 38h от начала которого лежит указатель на глобальную таблицу дескрипторов прерываний (IDT). «Смотрит» она непосредственно в ядро (если, конечно, ее никто не захучил). Как мы это узнали? Раскладка ядерной памяти хорошо описана в документации на SoftICE, а определения самих структур можно найти в NTDDK от Microsoft или обратиться к замечательному ресурсу «Windows Vista Kernel Structures», содержащему практически всю информацию о ядре Висты (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

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

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

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

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

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

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

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

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);
}

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

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

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);
}

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

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

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

Заключение

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

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

WWW

Сага о том, как сотрудники DrWeb ловили Неуловимого Джо, а потом поймали и прищемили: drweb.com/upload/a8601a8e66f6ff9a9c629c969482d292_1210059861_DDOCUMENTSArticales_PRDrWEB_Rustock_rus.pdf.
Животрепещущая история о том, как с вируса содрали первый слой упаковщика, чему страшно возрадовались, но застряли во втором, который не смогли пройти даже с помощью IDA-Pro: blog.threatexpert.com/2008/05/rustockc-unpacking-nested-doll.html.

Увлекательная детективная история с разоблачением... нет, не вируса, а его создателей, на роль которых выдвигаются парни с краклаба: rootkit.com/newsread.php?newsid=879.

Краткое, но самое техничное описание вируса из всех встреченных мною (с описанием прохождения всех уровней шифрования): eset.com/threat-center/blog/?p=127.
Техничный анализ предыдущей версии Rustock.C: offensivecomputing.net/?q=node/331.

Содержание
ttfb: 55.073976516724 ms