| |||
[network & security news] [RSS & Twitter] [articles, programing info] [books] [links, soft & more...] [soft archive] | [home] |
Микстура против ЧИХ-а
|
Поле | Смещение | Длина | Назначение |
Magic | +00 | 4 | Сигнатура ‘PE\0\0’ (0x00004550) |
NumberOfSections | +06 | 2 | Количество секций (без "псевдосекции") |
SizeOfOptionalHeader | +14h | 2 | Размер переменной части заголовка |
AddressOfEntryPoint | +28h | 4 | Смещение точки входа в программу |
ImageBase | +34h | 4 | Базовый адрес загрузки программы |
SectionAlignment | +38h | 4 | Размер блока в памяти |
FileAlignment | +3Ch | 4 | Размер блока на диске |
Структура этого заголовка описана под именем _IMAGE_OPTIONAL_HEADER в файле WINNT.H, который можно обнаружить в составе таких продуктов, как Borland C/C++ v5.0, Microsoft Visual C/C++, Microsoft Windows 9X/NT/2000/XP DDK и пр.
Все секции (в том числе и "псевдосекция" заголовков) присутствуют и в программном файле, и в памяти, причем располагаются в одном и том же порядке. Отличие только в том, что каждая секция (в том числе и "псевдосекция" заголовков) занимает целое число блоков определенной длины, а размер этих блоков в общем случае различен для случая файла (см. поле FileAlignment, часто его значение равно 512) и для случая памяти (см. поле SecionAlignment, часто его значение равно 4096). Самая первая по счету секция (это "псевдосекция" заголовков) при загрузке в память получит линейный адрес ImageBase (часто его значение равно 400000h). Смещение первой исполняемой команды программы относительно ImageBase хранится в поле AddressOfEntryPoint.
Все секции (кроме "псевдосекции" заголовков) имеют имена. Программный код, как правило, хранится в секции с именем .text или CODE. Области переменных размещаются в секциях .data или DATA. Под текстовые и строковые константы обычно отводится секция .idata, под ресурсы Windows-приложения - секция .rsrc и т.п. Сразу после PE-заголовка (который состоит из постоянной части длиной 0x18 байтов и переменной части длиной SizeOfOptionalHeader байтов) размещается таблица описания всех секций (кроме "псевдосекции" заголовков). Она содержит NumberOfSections записей следующей структуры:
Поле | Смещ. | Длина | Назначение |
Name | +00h | 8 | Символьное имя секции |
VirtualSize | +0Сh | 4 | Реальное количество информации в секции |
VirtualAddress | +10h | 4 | Смещение секции в памяти от ImageBase |
SizeOfRawData | +14h | 4 | Размер, зарезервированный для секции на диске |
PointerToRawData | +18h | 4 | Смещение секции от начала файла |
Characteristics | +20h | 4 | Флаги свойств секции |
Подробней с этой структурой можно также ознакомиться, заглянув в файл WINNT.H, там она располагается под именем _IMAGE_SECTION_HEADER.
Обычно SizeOfRawData – это "круглое" число, кратное значению FileAlignment (часто это 512 байтов), а значение поля VirtualSize – число "не круглое", характеризующее реальную длину секции. Значит, между реальным концом области, содержащей "полезные" байты содержимого секции, и концом фрагмента дисковой памяти, зарезервированным за этой секцией, обычно присутствует неиспользуемый "зазор", величина которого может варьироваться от 1 до 511 байтов.
Продолжая глядеть на NOTEPAD.EXE при помощи HIEW, нажмем клавишу F8. Это позволит увидеть содержимое полей специфического PE-заголовка зараженной программы. Вслед за этим нажмем F6 и ознакомимся с таблицей программных секций. Сравнивая два файла, зараженный и незараженный, можно легко увидеть, что:
1) в PE-заголовке изменилось значение поля AddressOfEntryPoint:
Было | Стало | |
AddressOfEntryPoint | 0x10CC | 0x270 |
2) в таблице секций увеличились до предела значения полей VirtualSize для первых двух секций:
VirtualSize |
SizeOfRawData |
|||
Было | Стало | Было | Стало | |
.text | 0x3E9C | 0x4000 | 0x4000 | 0x4000 |
.data | 0x84C | 0x1000 | 0x1000 | 0x1000 |
Графически произошедшие изменения можно изобразить так:
Итак, вирус зарезервировал для себя место в "хвостах" нескольких секций и записал туда куски своего кода. Начальный фрагмент своего кода он поместил в неиспользуемую область в "псевдосекции" заголовков.
После этого вирус изменил точку входа в программу таким образом, что она стала указывать на его начальный фрагмент. Выясняется, что Windows при запуске зараженной программы не проверяет местоположение точки входа – ей абсолютно безразлично, находится ли она внутри секции с именем .text, или в какой-нибудь другой секции, или вообще вне секций. Поразительная беспечность!
В КАКИХ ОПЕРАЦИОННЫХ СИСТЕМАХ ВИРУС ЖИВЕТ?
В своей работе вирус использует ряд в общем-то документированных, но малоизвестных особенностей функционирования Windows.
Справка. Каждому запущенному на исполнение потоку Windows ставит в соответствие структуру данных под названием TIB (Thread Information Block – блок информации о потоке). Второе четырехбайтовое поле в этой структуре является линейным адресом текущего SEH (Structured Exception Handler – структурированного обработчика исключений), а первое – адресом следующего в длинной цепочке аналогичных же обработчиков. Обработчики активируются в момент возникновения исключительных ситуаций (например, если программа попытается выполнить недопустимую команду или обратиться в "запрещенный" район памяти).
В момент запуска потока структура TIB доступна по адресу FS:[0].
Для того, чтобы ответить на вопрос об "ареале" распространения вируса, потребуется дизассемблировать и внимательно исследовать его начало (первые несколько десятков байтов, на которые указывает AddressOfEntryPoint). Вот как выглядит этот фрагмент:
push ebp lea eax,[esp-0008] xor ebx,ebx xchg eax,fs:[ebx] call $+5 pop ebx lea ecx,[ebx+00042] ; Адрес нового SEH push ecx push eax |
После его выполнения район памяти, в котором расположен стек, будет содержать следующие данные:
Смещение | Значение | Примечание |
ESP | Ebp | |
ESP-4 | Адрес старого SEH | |
ESP-8 | Адрес нового SEH | Сюда указывает FS:[0] |
Поскольку стек "растет вниз", то достаточно перевернуть эту табличку "вверх тормашками" и увидеть, что фрагмент стековой памяти, начинающийся с адреса [esp+8], по своему содержимому очень напоминает начало какой-то TIB. Все правильно, именно в этой роли он в дальнейшем и будет использоваться!
Примечание. Может показаться странным, что автор вируса довольно замысловато манипулирует со стеком вместо того, чтобы просто сформировать нужную структуру данных в обычных переменных. Но не нужно забывать, что данный фрагмент вируса выполняется в области программы, которая не принадлежит ни одной из секций, и, следовательно, для нее запрещена запись в память.
Итак, первым делом CIH берет на себя обработку исключительных ситуаций. Зачем? Дело в том, что непосредственно вслед за этим он пытается модифицировать системную IDT (Interrupt Descriptor Table – таблицу дескрипторов прерываний) для того, чтобы взять на себя также еще и обработку прерывания номер 3.
push eax sidt [esp-0002] ; Адрес IDT - в стек pop ebx add ebx,01C cli ; Дескриптор 6-байтовый, и модифицируется он по частям mov ebp,[ebx] mov bp,[ebx-0004] ; Выполняется адресация на новый обработчик lea esi,[ecx+00012] push esi ; Вписывается 1-я половина адреса mov [ebx-0004],si shr esi,010 ; Затем вторая mov [ebx+00002],si pop esi |
Теперь достаточно инициировать вызов прерывания командой Int 3, и управление получит вирусный обработчик этого прерывания. Только вот выполняться этот обработчик будет уже в нулевом кольце защиты, а это значит, что вирус получит недоступные ему прежде системные привилегии.
Но сработает этот прием только в Windows 95/98/ME. А в операционных системах семейства Windows NT/2000 возникнет исключительная ситуация. Вот для чего вирус перехватывал обработчик исключений – чтобы зараженная программа при запуске из-под NT не вылетала, а корректно продолжала свою работу!
Итак, зараженная вирусом программа будет корректно работать в любой операционной системе, но размножаться (т.е. заражать другие программы) вирус способен только в Windows 9X.
КАК ВИРУС ВОЗВРАЩАЕТ УПРАВЛЕНИЕ ЗАРАЖЕННОЙ ПРОГРАММЕ?
Фрагмент возврата управления зараженной программе следует искать в вирусном обработчике исключений. Вот он:
; Старый SEH возвращается на прежнее место xor ebx,ebx mov eax,fs:[ebx] mov esp,[eax] pop fs:[ebx] pop eax pop ebp ; Возврат управления зараженной программе! push 00040278C retn |
Нетрудно сообразить, что 040278Сh – это и есть сохраненное внутри вируса значение старой точки входа в программу! Достаточно вычесть из него значение поля ImageBase и поместить на свое "законное" место в поле AdressOfEntryPoint. И вирус, формально оставшись внутри программы, никогда больше не получит управления!
Обратим внимание, что "заветное" двойное слово лежит по смещению +5Eh относительно точки входа в вирус. Собственно говоря, теперь мы уже можем написать свой собственный антивирус. Но восстановив точку входа, мы только обезвредим вирус. Ряд антивирусов (например, "Антивирус Касперского" с подключенной базой CIH-TRACE.AVC) способны обнаруживать такой "убитый", но "не похороненный" Win.CIH и устраивать по этому поводу небольшой "скандал".
Кроме того, коварные повадки "заразы" изучены еще не до конца. Поэтому продолжим анализ.
КАК ВИРУС ИЩЕТ ЦЕЛИ ДЛЯ ЗАРАЖЕНИЯ?
CIH относится к классу "резидентных" вирусов, хотя этот термин для многозадачных операционных систем и не имеет смысла. Это значит, что он "сидит" в памяти постоянно, следит за всеми вновь запускаемыми программами и заражает их.
"Слежку" за запускаемыми программами он осуществляет при помощи "секретного", очень скупо документированного фирмой Microsoft механизма обращений к глубинным компонентам ядра операционной системы – к VMM (Virtual Memory Manager – менеджеру виртуальных машин) и к драйверам виртуальных устройств.
Справка. Обращение к компонентам ядра Windows 9X (которое обычно выполняется не из прикладных программ, но из системных DLL) выглядят следующим образом:
int 20h dw ? ; Номер функции dw ? ; Код компонента, например: 1- VMM, 40h – IFSMgr, и пр. |
Необходимые параметры вызова при этом передаются через стек.
Обычно дизассемблеры подобный код отображают в виде высокоуровневых ассемблерных макросов VxDCall и VMMCall.
Этот код не является реентерабельным, поскольку ядро Windows в процессе выполнения подобного запроса искажает несколько байтов в точке, в которую предполагается возврат управления. Для системных DLL это несущественно, а перед вирусами (вернее, перед их авторами) встает ряд трудноразрешимых проблем по восстановлению первоначального кода.
Обращения прикладных и системных программ к файловой системе обслуживаются компонентом ядра под названием IFSMgr (Installed File System Manager - менеджер инсталлированной файловой системы), все эти обращения кодируются числом 40h. Среди нескольких десятков сервисных функций этого компонента интересно выделить:
Доступ к вышеописанным механизмам возможен только из нулевого кольца защиты.
Используя вызов InstallFileSystemApiHook, вирус берет на себя обработку обращений к файловой системе и ждет (проверяя каждый раз параметры вызова, сохраненные в стеке) запроса на открытие файла какой-либо программы (это обычно происходит при ее запуске):
; Обработчик уже занят заражением какого-либо файла? Test [esi], 001 Jne SkipInf ; Проверка стековых параметров вызова lea ebx,[esp+00028] cmp [ebx], 024 ; Открытие файла ? jne SkipInf ; Заражение ... SkipInf: |
Все это очень похоже на перехват каким-нибудь старинным резидентным вирусом прерывания 21h, только происходит теперь уже в 32-битовой многозадачной среде Windows.
КАК ВИРУС ОТЛИЧАЕТ УЖЕ ЗАРАЖЕННУЮ ПРОГРАММУ?
Вот маленький фрагмент вирусной процедуры, выполняющей заражение PE-файла:
Xor eax,eax Mov ah,0D6 ; Код операции чтения Mov ebp,eax Xor ecx,ecx Mov cl,004 ; Кол-во байтов Xor edx,edx Mov dl,03C ; Смещение в файле Call edi ; Читать Mov edx,[esi] Dec edx ; Смещение-1 |
Проанализировав его, легко понять, что вирус рассчитывает местоположение в файле для PE-заголовка потенциальной "жертвы" и проверяет первый байт перед сигнатурой ‘PE’. В "здоровой" программе этот байт обычно нулевой, а любое ненулевое значение является для вируса признаком зараженности.
Поэтому программу, зараженную вирусом CIH, целесообразно не восстанавливать полностью в исходном виде, но оставить на своем месте хотя бы признак зараженности. В следующий раз вирус эту программу не тронет.
КАК НАПИСАТЬ СВОЙ АНТИВИРУС?
Для того, чтобы "собирать" себя из отдельных "кусочков", вирусу, конечно же, требуется где-то запомнить местоположение этих "кусочков" и их длины. Табличку с этой информацией можно обнаружить в самом начале вирусного кода, непосредственно перед точкой входа в вирус. В зараженном файле NOTEPAD.EXE она выглядит так:
Table: Dd 0 ; Признак конца таблички Dd 000000F7h ; Длина в секции .data Dd 0040584Ch ; Позиция в секции .data Dd 00000164h ; Длина в секции .text Dd 00404E9Ch ; Позиция в секции .text Dd 00000190h ; Длина в псевдосекции заголовков ; Точка входа в вирус Start: Push ebp ... |
Убедимся, что вирус действительно читает и использует эту табличку в обратном порядке:
Lea eax,[esi-Start] ; Адрес начала таблички ... mov esi,eax More: Mov ecx,[eax-0004] ; Очередная длина Repe movsb Sub eax,008 ; Адрес очередного "кусочка" Mov esi,[eax] Or esi,esi ; Конец таблички? Je Enough Jmps More |
Итак, мы теперь владеем информацией, достаточной для того, чтобы восстановить зараженную программу в исходном виде:
Разумеется, прежде чем "лечить" программу, нужно предварительно убедиться в том, что она действительно "больна". "Чернобыльский" вирус не относится к классу полиморфных, хранит свой код в программе в неизменном виде и, значит, легко может быть обнаружен по сигнатуре – характерной для него и только для него последовательности байтов.
Не будем особо мудрствовать и возьмем в качестве сигнатуры 8 первых байтов вируса, начиная с точки входа.
/* Компилировать, например, в BC/C++ 5.0 */ #include <stdio.h> #include <winnt.h> #define SLEN 8 #define OK 0 #define BAD 1 IMAGE_NT_HEADERS32 pe; IMAGE_DOS_HEADER mz; IMAGE_SECTION_HEADER sh; /* "Проверялка" для Win95.CIH. © Климентьев К., Самара 2001 */ int CheckCIH( char *filename ) { int f, i; char buf[SLEN]; char Sign[SLEN] ={0x55, 0x8D, 0x44, 0x24, 0xF8, 0x33, 0xDB, 0x64}; f=open(filename, O_RDONLY|O_BINARY); read( f, &mz, sizeof(IMAGE_DOS_HEADER)); if ((mz.e_magic==0x5A4D)&&(mz.e_lfarlc>=0x40)) { lseek( f, mz.e_lfanew, SEEK_SET); read( f, &pe, sizeof(IMAGE_NT_HEADERS32)); if (pe.Signature==0x4550) { lseek( f, pe.OptionalHeader.AddressOfEntryPoint, SEEK_SET); read( f, buf, SLEN); close(f); for (i=0;i<SLEN;i++) if (buf[i] != Sign[i]) return OK; return BAD; } } close(f); return OK; } |
"Лечение" зараженного файла состоит из двух этапов. На первом мы разыскиваем внутри вируса и возвращаем на "законное место" внутри заголовка двойное слово – оригинальный адрес точки входа в программу. Собственно говоря, второй этап совершенно необязателен, т.к вирус уже обезврежен. Тем не менее, дабы "успокоить" некоторые особо бдительные антивирусы, дополнительно просканируем табличку вирусных фрагментов, рассчитаем их местоположение внутри файла и заполним их символом "звездочка".
/* "Лечилка" для Win95.CIH. © Климентьев К., Самара 2001 */ CureCIH( char *filename ) { Int f, i, k; Unsigned char c='*'; DWORD l, p, e, tmp; F=open(filename, O_RDWR|O_BINARY); /* Восстановление старой точки входа */ lseek(f,pe.OptionalHeader.AddressOfEntryPoint+0x5E,SEEK_SET); read(f, &e, 4); e -= pe.OptionalHeader.ImageBase; lseek(f,mz.e_lfanew+0x28,SEEK_SET); write(f, &e, 4); /* Удаление вируса из области заголовков */ lseek( f, pe.OptionalHeader.AddressOfEntryPoint-4, SEEK_SET); read( f, &l, 4); lseek(f, pe.OptionalHeader.AddressOfEntryPoint, SEEK_SET); for (k=0;k<(int)l;k++) write( f, &c, 1); /* Удаление фрагментов вируса из секций */ tmp = pe.OptionalHeader.AddressOfEntryPoint-12; while (1) { /* Чтение таблички описания вирусных фрагментов */ lseek(f, tmp, SEEK_SET); tmp-=8; read( f, &l, 4); /* Длина фрагмента */ read( f, &p, 4); /* Позиция фрагмента в памяти */ if (!p) goto finish; p -= pe.OptionalHeader.ImageBase; /* Сканирование таблицы секций */ lseek(f,mz.e_lfanew+0x18+pe.FileHeader.SizeOfOptionalHeader, SEEK_SET); for (i=0;i<pe.FileHeader.NumberOfSections;i++) { read( f, &sh, sizeof(IMAGE_SECTION_HEADER)); if ((p>sh.VirtualAddress)&& (p<sh.VirtualAddress+sh.Misc.VirtualSize)) { lseek(f, sh.PointerToRawData + (p-sh.VirtualAddress), SEEK_SET); for (k=0;k<(int)l;k++) write( f, &c, 1); goto done; } } done:; } finish: close(f); } |
Эти две процедуры, CureCIH() и CheckCIH() должны вызываться из некой программы примерно вот в таком стиле:
If (CheckCIH(filename)==BAD) CureCIH(filename); |
Напишите программу, обходящую дерево дисковых каталогов и проверяющую все встреченные EXE-файлы, самостоятельно. Это очень просто. Вот и все, антивирус для знаменитого "Чернобыльского" вируса готов!
ЗАКЛЮЧЕНИЕ
Итак, представители "электронной микрофауны" совсем не так сложны и непонятны, как могло бы показаться на первый взгляд. Более того, не все вирусописатели столь любознательны и трудолюбивы, как Чен Инг Хау: многие вирусы, в том числе и самые современные, устроены гораздо проще.
Так почему же так много людей, называющих себя "специалистами по программному обеспечению" и "программистами", не знают реальных повадок "электронной заразы", зато охотно верят безграмотным журналистским слухам и сплетням? Почему они не имеют (и не хотят иметь) представления о том, как примерно устроены, что могут и чего не могут делать антивирусные программы?
Конечно, данная статья не способна никоим образом повлиять на сложившуюся ситуацию, но какую-то крохотную подвижку в сознании этих людей она, хочется надеяться, произведет.
Следует упомянуть, что данная статья никогда не была бы не написана без ценных замечаний от участников группы NF.
Благодарю за внимание. С глубочайшим почтением и искреннейшей преданностию есмь, милостивые государи, Ваш покорный слуга
© Климентьев К.Е. aka DrMad: drmad@dr.com * http://drmad.chat.ru
Самара 2001
Все документы и программы на этом сайте собраны ТОЛЬКО для образовательных целей, мы
не отвечаем ни за какие последствия, которые имели место как следствие использования
этих материалов\программ. Вы используете все вышеперечисленное на свой страх и риск.
Любые материалы с этого сайта не могут быть скопированы без разрешения автора или
администрации.
[network & security news] [RSS & Twitter] [articles, programing info] [books] [links, soft & more...] [soft archive] | [home] |
2000-2015 © uinC Team |