К. Касперски - Техника оптимизации программ, Эффективное использование памяти (1127752), страница 88
Текст из файла (страница 88)
Эффективность ассемблера здесь вторична, главное — максимально запутать взлом- шика. Компиляторы же генерируют достаточно предсказуемый код, и почерк каждого из них профессиональным хакерам хорошо известен. Достоинства ассемблера в том, что он не ограничивает полет фантазии и позволяет воплошать в жизнь практически любые идеи. Полиморфный, шифрованный, самомодифицируюшийся код, антиотладочные и антидизасемблерные приемы— этот список можно продолжать бесконечно. Целесообразность использования тех или иных защитных механизмов — тема другого разговора, здесь же мы будем обсуждать лишь пути их реализации. Ассемблерные трюки — вообше "больная" тема, однако следует различать трюк как таковой (оригинальная идея и/или нетрадиционный прием программирования) и недокумевтированные возможности процессора и операционной системы. Трюки, при грамотном подходе к ним, вполне безобидны и никаких проблем не создают.
Классический пример трюка — расшифровка программы одноразовым блокнотом, возврашаемым функцией хеес. ПоСКОЛЬКУ ФУНКЦИЯ 00 ВСЕГДа ВОЗВРаШаЕт ОДНУ И тУ жЕ ПОСЛЕДОВатЕЛЬНОСтЬ, она идеально подходит для динамической шифровки/расшифровки программы. Если дизассемблер не сумеет распознать функцию 0 в откомпилированной программе, то хакер "ногу сломит", пока не догадается: как устроена и работает такая защита. Какие проблемы может создать этот трюк? Совершенно верно — никаких. А вот пример "грязного хака", основанного на недокументированных возможностях: в %юбое 95 регион адресного пространства от ОхС0000000 до ОхЕ0000000, храняший низкоуровневые компоненты системы, свободно доступен прикладным приложениям, что очень облегчает борьбу с отладчиками и всякими мониторами. Правда, под %шдо4ч )х)Т первая же попытка абрашения к этой области приводит к генерации исключения с последуюшим Машинная оптимизация закрытием приложения-нарушителя.
В результате конечный пользователь теряет возможность запускать защищенную программу под %(идой ХТ. Вот за это многие и не любят ассемблер. Но, позвольте, разве ж ассемблер виноват? Не используйте недокументированных особенностей (а если уж совсем невтерпеж, то используйте их с умом) — и проблем ни у кого не будет! В последнее время, кстати, наметилась устойчивая тенденция к отказу от ассемблера даже в защитных механизмах. Действительно, многие трюки замечательно реализуются и на языках высокого уровня. В частности, динамическую расшифровку кода (равно как и исполнение кода в стеке) можно реализовать и на "чистом" С/С++, достаточно лишь получить указатель на функцию (С это позволяет), после чего с ее содержимым можно делать все, что угодно. И вовсе не обязательно для этого спускаться на уровень "чистого" ассемблера.
Также язык высокого уровня облегчает написание полиморфных генераторов и виртуальных машин (машин Тьюринга, сетей Петри, стрелок Пирса и т. д.). Единственное, что нельзя на нем реализовать— так это самомодифицирующийся код. Вернее, можно, но с жесткой привязкой к конкретному компилятору (ибо необходимо знать: как и во что транслируется каждая строка), а подобная практика — дурной тон. Привязываться ни к чему и никогда не стоит, к тому же трудозатраты при создании самомодифицирующегося кода на языке высокого уровня намного выше, чем на ассемблере. Тем не менее, возможность создания защит на "чистых" С или С+-ь многих хакеров старого поколения просто "корежит", — они и слышать об этом не хотят (автор, кстати, сам такой). Что поделаешь! Традиции и привычки— "штучки упрямые".
Ну, а красивое программирование на "чистом" ассемблере, понимаете? А создание зашит непосредственно в машинных кодах вызывает ничем не передаваемое удовлетворение, по своему эмоциональному накалу сравнимое разве что с оргазмом. Это — программирование ради программирования, нацеленное не на конечный результат, а на сам процесс его достижения. Не могу удержаться, чтобы не процитировать: '~?ля некоторых людей программирование является такой зке внутренней потребностью, подобно тому, как коровы дают молоко, или писатели стремятся писать", — Николай Безруков. Программирование на ассемблере как особый род творчества Компьютер уже давно перестал быть вычислительной машиной для небольшой горстки избранных и с каждым днем он все стремительнее и стремительнее превращается в пылесос.
Ну, или что-то очень на него похожее. Современные программисты, абстрагировавшись от "железа" и даже от самих вычислительных алгоритмов, видят перед собой лишь мышь да визуальную 450 Глава 4 панель с компонентами. Написать программу стало так же легко, как сварить пакетный суп.
Конечно, свои положительные моменты в этом есть, но существует определенная категория людей, для которых жизнь — это ни на секунду не прекращающийся поиск и преодоление сложностей (ни слова про гамак и ласты!). Если задуматься: какую практическую ценность несет в себе, ну, скажем, покорение горных вершин? Ведь гораздо комфортнее и куда с меньшим риском к ним можно добраться и на вертолете. Увы! Чем легче достается — тем меньше удовлетворения оно приносит. Визуальное программирование слишком просто, чтобы быть по-настоящему интересным.
С другой стороны, чем выше уровень языка, тем больше приходится соблюдать предписаний и тем меньше остается возможности для самовыражения. А художники как раз и отличаются от окружающих тем, что в каждой работе передают свое видение мира, частицу своего "Я". Интерес к ассемблеру, часто доходящий до фанатизма, как раз и объясняется тем, что ассемблер — лучшее средство "пощупать железо" компьютера; это превосходная арена лля интеллектуальной борьбы и, наконец, — великолепный способ с пользой и интересом скоротать свободное от работы время. Существует огромное множество ассемблерных головоломок — от "написать программу на байт короче, чем у соседа", до "создать самообучающуюся шахматную игру, занимающую не более 2 Кбайт". На ассемблере пишутся многие "демки", на нем же создаются "крякмисы" (в дословном переводе "взломай меня").
Никто не спорит, что все, перечисленное выше, можно реализовать и на языках высокого уровня, причем за несравнимо более короткое время при не сильно худшей эффективности. Да! Можно! Но неинтересно. Мы, комсомольцы, видите ли, без "ласт и гамака любить не можем". Поэтому (и это очень важно!), если вы встретите человека, беззаветно преданного ассемблеру и презирающего языки высокого уровня, не спешите "ломать пальцы о клавиатуру", переубеждая его в обратном. Девять из десяти — ассемблер любят, но не за достоинства, а, напротив, за отсутствие таковых (если понимать под "достоинствами" удобства цивилизации). Один из десяти — просто "выпендривается" и программирует на ассемблере, чтобы продемонстрировать окружающим свою "крутость".
Тогда тем более не стоит его переубеждать — с возрастом "само рассосется". Заключение Вердикт. Ассемблер жил, ассемблер жив, ассемблер будет жить! Наблюдаемое засилье высокоуровневых языков и визуальных средств разработки— явление временное. Это — затишье перед бурей.
А буря грянет — в этом можете не сомневаться. Не сегодня, так завтра перед программистами встанут новые задачи, подчистую "съедающие" все вычислительные мощности и требующие еше. Главное — быть готовым к этому и вовремя предложить Машинная оптимизация Программируйте! Удачи вам и побольше сложностей от жизни! Исходные тексты тестовых примеров тога снес1 с ору(1пс *вгс, гпг *наг, тпг и) гпг а; 1пт Г; 11 [п<1) геспгп; // нечего копироватвц // повпементное копирование массива Гог [а=о;а<п;а++) пег[а)=его[а!) ГЕГПГП4 авп сруртос РОЯН 5511 / * РОЯН ЕО1) РОЯН ЕСХ4 */ сохраняем регистры ЕЯ1,[Е5Р+4+3*4) ; вгс ЕО1,[ЕЯР+8+3*4) 4 Нвс ЕСХ,[ЕЯР+8+4+3*4) моу моч моу свои знания, умения и навыки, дождавшись момента острой нехватки ассемблерных специалистов.
(Мимоходом: программисты, знающие практически забытый ныне Гопгап, с руками отрываются на Запад, ибо там половина научных приложений написана на языке Рог[гап, но ныне их некому сопровождать — старые кадры уходят на пенсию, а новое поколение выбирает пепСи). Если же вы органически не приемлете наживу и бизнес, — программируйте на ассемблере из спортивного интереса. Последние поколения процессоров Реп[ваш в этом отношении — просто клад, и там, поверьте, есть чему поучиться! 452 Глава 4 копируем одной командой! ВЕР ИЧБО РОР ЕСХ ; /* РОР ЕО1 восстанавливаем регистры РОР ЕБ1 ; */ гег выходим авш ору епар ,Р~8~~~~~~~~;4~~Е Епт сбес1 с ш1п~1па *ягс, 1па п~ 1па а; Рпа Г.; 11 (п<2) ге<игл -11 // Не среди чего искать минимум! // Присваиваем первому элементу маааива статуа // "условна наименьшего" а=ага~О~ 1 // Если такой, кто будет меньше нашего "меньшего"2 // есть да, то предать статус еыу.
бог(а=1!а<о;а++~ 11 1Г>ягс(а1) Г.=яка!а)! гегпгп с! аяш яолргос РОБН РОБН ЕО1 ЕЯ1,~ЕЯР+Ят41 ; ягс ЕОХ, ~ЕБРтв+83 / и ИОЧ ЕОХ, 2 Яехгб сохраняем регистры еать ареди чего накаты нет элементов для поиска Машинная оптимизация 453 Моч ЕАХ, (Е51( 1 поисваиваем первому элементу статус "условно наименьшего" етог: начало цикла в ЕО1 — очередной элемент 1 если кто еще меньшет ЕО1, (Е51( ЕАХ, ЕО1 Впехг ЕАХ, ЕО1 СМР если нет, — следушщий злемвнт передать статус МОЧ фпехг: Зехгг: РОР ЕО1 РОР Е51 восстанавливаем регистры гег ааш кцп впар 'ф~~~Ъ~МЕ4:6:-'(МАМ~~(~(а(;ЩофйфФфД(>(Ж(Чйффф~ - т Х:.~(ф((:;:ФРФас::Й'-" чогб саес1 с аогг(гпг *вгс, 1пг и( гпг а( гпг г( гп(.
гб (п<2( гесцгп( // Меньше двух элементов сортировать нельзя! ао( // у«танавдиваем флаг сортировки в ноль // Перебираем все элементы один за другим Еог (а.=1( а<п( а+я( // если следуюме1 элемент меньше предыцущего, // меняем их местами и устанавливаем флаг // сортировки в единицу (асс[а-1(>*го(а]( г=згс (а-1(1 згс(а-1(=вгс(а( АОО 551, Е ОЕС ЕОХ ЗН2 81ог перейти к следующему элементу уменьшить счетчик цикла на 1 повторять цикл пока ЕОХ > б Глава 4 454 згс)а) / В4 1=14 // повторять сортировку до тех пор, пока не дождемся // первого "чистого" прохода, т.е. прохода беэ изменений ) кбь1е)т)4 азщ зогт МОЧ есть что сортировать? сортировать нечего, на выход РОББ Ея т РОББ ЕВР сохраняем регистры РОББ ЕВХ Бкйь1е: основной цикл сортировки ЕЯ1, )ЕБР44+4*З) ЕОХ,)ЕБР+Вь4*3) МОЧ МОЧ ЕВР, ЕВР ХОН Ббог: цикл перебора элементов ЕАХ := згс ЕЛХ, )ЕБ1) ЕВХ, )ЕБ1+4) МОЧ ЕВХ := згсь1 Сравнить ЕЛХ и ЕВХ ЕРЛ, ЕВХ Бпехт Рог Если ЕЛХ > ЕВХ, перейти к следующему элементу, установить флаг изменений ь "нечистый" проход ЕВР, ЕВХ [ЕБ1Ь4), ЕЛХ )В61),ЕВХ МОЧ Впехг Бог: згс+=-14 АОО ОЕС ЕОХ Втог ргос ЕОХ, )ЕВРЕЕВ) ЕОХ, 2 ИехБС 4 згс и / ь: 0 иначе обменять элементы местами уменьшить счетчик цикла перебирать элементы, пока счетчик не равен нулю 455 Машинная оптимизация бфгеу-флаг установлент ЕВР,ЕВР Вньтге сортировать гока не будет чисто евх РОР ЕВР Восстановить регистры РОР ез1 РОР аехфс: Выход тес ави вовс есор РЕС1 01)ОР РОТШ, РАС1АсчТ МЕЬЮКА РОТЕгЧТЕЕР Хочу закончить свою книгу.