Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #134, ФЕВРАЛЬ 2010 г.

Взлом и анализ TDL3. Покоряем легендарный руткит

Артем Баранов (artembaranov@yandex.ru)


Как ты, я надеюсь, уже наслышан, под TDL3 понимают новое поколение руткитов tdss. Его отличают навороченные технологические фичи, которые оставили безоружными все антивирусы и почти все антируткиты. я покажу, как следует разбирать TDL3, в чем его технические особенности и почему антивирусы спасовали перед ним.

Первая информация о рутките появилась на rootkit.com в блоге diablonova (rootkit.com/blog.php?newsid=970) и содержала сведения о предыдущих версиях TDL, а также, собственно, о новой версии. Распространение руткита началось где-то с октября, тогда же и вышла упомянутая статья.

Бросалось в глаза, что руткит действует как файловый вирус, заражая драйвер-порта жесткого диска; обычно это atapi.sys, находящийся под драйвером класса disk.sys. При заражении размер драйвера не менялся, руткит перезаписывал часть ресурсов драйвера и устанавливал точку входа на себя. Это загрузчик, основная же часть руткита размещается в конце диска.

Заражая atapi.sys, руткит предоставлял себе возможность стартовать на самом раннем этапе загрузки ОС. Кроме того, большинство антируткитов даже не определят присутствие руткита в памяти. Перехват диспатч таблицы IRP в объекте-драйвера atapi осуществлен так: при чтении зараженного драйвера-порта руткит подсовывает оригинальный файл. Поэтому все AV шли лесом, а так как руткит работал на самом низком уровне, то даже в перспективе определить заражение файла смогут далеко не все антируткиты. Не говоря уже про лечение.

Спустя какое-то время аналитики DrWeb выпустили статью «От Backdoor.Tdss.565 и выше (aka TDL3)» (drweb.com/static/BackDoor.Tdss.565_(aka%20TDL3).pdf), которая раскрывает технические особенности TDL3, а также его последующих модификаций. Они же первые выпустили лечение этого руткита. Немного позднее вышла статья хакера thug4lif3 на rootkit.com (rootkit.com/newsread.php?newsid=979), также рассказывающая о внутреннем мире этого руткита.

В отличие от Rustock.C, который все это время держал лидерство среди самых высокотехнологичных руткитов, TDL3 вполне нормально работает на виртуальной машине и под отладчиком, а его код не обфусцирован и его вполне можно разобрать по винтикам под отладчиком. Однако технологическая начинка TDL3 может сбить с толку начинающих хакеров, ибо она весьма не проста, но очень оригинальна.
Получение семпла руткита тоже задача не из легких; я встречал ссылки на offensivecomputing.net и malwarebytes.org, так что, если там поискать, ссылочку вполне можно получить.

Начинаем копать

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

Нам понадобится виртуалка, желательно Windows XP SP3, настроенная для отладки, и Windbg, само собой, IDA для разбора. Загружаемся в обычной non-debug конфигурации, запускаем семпл. Ждем, пока дроппер исчезнет, затем перезагружаемся в debug-конфигурации и подключаем дебаггер. Если заражение прошло успешно, руткит выдаст в debug out строчку типа «This is your life, and it's ending one minute at a time». Полный набор предложений — смотри в статье аналитиков DrWeb.

Вообще-то, при запуске руткитов на виртуалке не мешает установить параметр Disable acceleration for binary translation в установках процессора виртуальной машины. Как оказалось, tdss.565 и без него нормально запускается.

Итак, первое что смотрим, — перехваты в диспатч-таблице драйвера-порта (на моей виртуалке это atapi.sys). Если сомневаешься в том, какой драйвер у тебя установлен, выполни команду !devstack \device\harddisk0\dr0. Самый нижний драйвер и есть драйвер-порта.

Получим его диспатч-функции (загрузив первоначально символы для драйвера, .reload atapi.sys).

Идем по этому адресу и видим:

kd> u f9756b3a l3
atapi!PortPassThroughZeroUnusedBuffers+0x34:
f9756b3a a10803dfff mov eax,dword ptr ds:[FFDF0308h]
f9756b3f ffa0fc000000 jmp dword ptr [eax+0FCh]
f9756b45 0000 add byte ptr [eax],al

Неплохой способ, обманывающий антируткиты: TDL3 нашел зануленное место в конце секции и вписал туда переход на свой код (очевидно, это общая для всех IRP функция-обработчик руткита). Здесь мы впервые сталкиваемся с его главной структурой, которая адресуется от адреса 0xFFDF0308. Да-да, ты не ошибся, она находится в KUSER_SHARED_DATA, вернее, в ее неиспользуемой (читай, малоиспользуемой) области. Проведя ночь в отладчике, я расковырял ее важные части. Далее я буду часто ссылаться на эту структуру.

struct _TAIL_PARAM_BLOCK
{
PVOID pTailInMem; //+0, указатель на хвост в памяти
PVOID KernelBaseAddress; //+4, стартовый адрес ядра
PVOID MountedVFSDeviceObject; //+8, объект устройство atapi, к которому
//руткит примонтировал свою VFS
PVOID Unknown1; //+C
ULONG TailDiskOffsLow; //+10, ULONGLONG, смещение хвоста на диске
ULONG TailDiskOffsHigh; //+14
ULONG numOfValidSectorInHideArea; //+18, число секторов в скрываемой
//области
FAST_MUTEX FastMutex;//+1C
ULONG TailStartDiskSector;//+3C, стартовый LBA хвоста
ULONG HideAreaStartSector;//+40, стартовый LBA скрываемой области
UCHAR szBotId[36]; //+44, событие с таким именем создается в корне \
ULONG Unknown2; //+68
ULONG Unknown3; //+6C
ULONG Unknown4; //+70
ULONG Unknown5; //+74
ULONG Unknown6; //+78
ULONG Unknown7; //+7C
ULONG Unknown8; //+80
ULONG Unknown9; //+84
ULONG Unknown10;//+88
ULONG OrigAtapiFuncs[0x1C];//+8C, оригинальные диспатч-функции
//драйвера-порта
PVOID RootkitDispatchFunc; //+FC, главная диспатч-функция руткита
PVOID AtapiDriverObject; //+100
PVOID AtapiBootRootkitDevObj//+104, объект-устройство драйвера-порта
//для диска, на котором хранится этот
//драйвер
ULONG SectorSize;//+108
PVOID pKernel32_LoadLibraryExA;//+10c, для инжекции в процессы
ULONG cEntryInHideAreaTable; //+110, далее таблица скрываемых секторов
struct
{
ULONG SectorStart;
ULONG OffsFromSector;
ULONG RestoreDataSize;
PVOID pOrigData;
ULONG unknown;
} HideAreaEntry[7]; //+114
ULONG unknown11[45];//+1A0
WCHAR DirSignature[9];//+254, сигнатура виртуального каталога
WCHAR DirFullPath[30];//+266, полный путь к виртуальному каталогу
…
}

Внутри хвоста

Как ты понял, руткит состоит из двух частей: загрузчика, который находится в секции ресурсов, и хвоста, который включает в себя весь руткит-функционал. Задача загрузчика руткита — загрузить хвост в память и передать на него управление. Наша же задача — сдампить хвост в файл для последующего анализа в IDA. И здесь можно пойти двумя способами. Во-первых, можно сдампить руткит из памяти. Мы знаем структуру хвоста и что в его начале идет сигнатура TDL3, потом оригинальные ресурсы, а потом код. Берем значение RootkitDispatchFunc из основной структуры (у меня это 818e2e31) и выполняем команду поиска вниз по памяти сигнатуры «TDL3».

Теперь осталось сдампить 0x5E00 байт (столько байт загрузчик выделяет для хвоста), начиная со стартового адреса, командой .writemem D:\1.bin 818df000 l5e00. Второй способ: восстанавливаем оригинальные диспатч-функции atapi.sys, копируя их из поля OrigAtapiFuncs структуры руткита и далее дампим секторы с диска с помощью, например, DiskExplorer. Вынести хуки руткита можно одной командой – m, копирующей память из одного буфера в другой;

m poi(FFDF0308)+8C l70 81957548+38, где 81957548 – в моем случае адрес объекта-драйвера atapi.

После этого мы получаем доступ к зараженному atapi.sys на диске и скрываемой руткитом области на диске. Сектор, с которого начинается хвост, хранится в поле TailStartDiskSector. Точку входа в хвост легко определить, зареверсив инфицированный atapi.sys. Где-то в конце стартовой функции загрузчика видим код

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

Загрузив файл, мы сразу можем перейти к разбору основной функции-обработчику IRP в рутките. Делаем dd poi(FFDF0308)+fc l1 и затем по полученному адресу идем в иду, и превращаем код в функцию – клавиша P.

VFS руткита

Ты, должно быть, в недоумении, зачем руткиту перехватывать все диспатч-функции atapi, когда для скрытия секторов достаточно одной – IRP_MJ_INTERNAL_DEVICE_CONTROL (aka IRP_MJ_SCSI). Драйверы дисков не обрабатывают create/close запросы, потому что не работают с дескрипторами, как FSD, которым нужно инициализировать файловые объекты для открываемых файлов. Такие драйверы (портов жестких дисков) принимают только запросы на чтение/запись секторов и вспомогательные запросы от диспетчера PnP и связанные с питанием. Тот же atapi.sys в незараженном состоянии обрабатывает create/close запросы, просто возвращая STATUS_SUCCESS.

Дело в том, что руткит резервирует часть диска (в его конце) под область, где он будет хранить свои файлы, например, dll, которые он инжектит в процессы, а также сам хвост. Там он организует свою ФС, но самое интересное, что руткит монтирует ее на объект \Device\Ide\IdePortX, обрабатывая таким образом запросы на чтение/записи к файлам, которые поступают как из самого руткита, так и из его юзермодной части. Например, сам хвост может обращаться к своей ФС для чтения конфигурационного файла. VFS подробно расписана в white paper DrWeb, поэтому я не буду повторяться, а предоставляю тебе возможность изучить это самостоятельно.
Для обращения к файлам в разделе руткита нужно знать уникальную сигнатуру, которую он хранит по смещению 0x254 структуры (DirSignature). Полный же путь формируется как (DirFullPath) имя_драйвера_порта + сигнатура_каталога (на моей машине выглядит как \Device\Ide\IdePort1\riyuicvp).

В силу того, что руткит виртуализует доступ к своим файлам, Win32 API вполне способны открывать файлы типа \Device\Ide\IdePort1\riyuicvp\tdlwsp.dll. В одной из версий руткита, очевидно, произошла ошибка, и dll, которую руткит должен был удалить из PEBа, оказалась видна.

Техническая начинка

Теперь посмотрим на то, как хвост проводит подготовительные действия инициализации. После того, как руткит открыл драйвер-порта на диске, он ищет его объект-устройство с флагом FILE_DEVICE_CONTROLLER, к которому будет примонтирована его FS. Далее производит полную инициализацию своей структуры и заражает драйвер в памяти, кроме того, составляет карту секторов и смещений драйвера-порта, которые он должен скрыть (массив HideAreaEntry).

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

Например, ты нашел функцию завершения и хочешь узнать, в каком месте кода она регистрируется. В примере ее адрес 8198ACA2. Тогда 8198ACA2-8a243384=F774791E. Нажимаем <Alt-B> и вбиваем адрес в обратном порядке, то есть 1E 79 74 F7.

Функция же обработки всех IRP является достаточно простой, особенно если знать формат главной структуры руткита. Она выполняет несколько целей: контролирует скрытие данных раздела руткита, скрытие зараженного atapi и обрабатывает функции VFS.

Итак, если диапазоны адресов совпадают со скрываемыми, руткит устанавливает свою функцию завершения, в которой будет фильтровать данные. В функции завершения руткит вначале проверяет диапазон секторов для скрываемого раздела и, если есть перекрытие, зануляет данные в буфере.

Последние версии

Более поздние версии TDL3 дополнены функционалом, который позволяет обходить антивирусы и антируткиты соответственно. В версии, следующей за вышеописанной, используется несколько другая техника перехватов. Диспатч-таблица функций atapi остается нетронутой, но руткит создает fake-объект драйвера со своими диспатч-функциями и перезаписывает указатель в устройстве, которое обслуживает загрузочный диск, на свой драйвер.

Содержание