← Декабрь 2000 → | ||||||
1
|
2
|
3
|
||||
---|---|---|---|---|---|---|
5
|
6
|
7
|
8
|
9
|
10
|
|
11
|
12
|
13
|
14
|
16
|
17
|
|
19
|
20
|
21
|
22
|
23
|
24
|
|
25
|
26
|
27
|
29
|
30
|
31
|
За последние 60 дней ни разу не выходила
Сайт рассылки:
http://rusfaq.ru
Открыта:
28-06-2000
Статистика
0 за неделю
Ассемблер? Это просто! Учимся программировать Выпуск N 016
Ели мясо мужики, Пивом запива-а-али. Что рассылка к ним пришла Они совсем не знали... Сегодня в рассылке: Друзья мои! У меня в почтовом ящике собралось много ваших писем. Извините, у меня просто сейчас нет времени отвечать на них. Очень надеюсь, что, разобравшись с делами, я всем напишу. Извините еще раз... __________ Старчиков Алексей ( vunder@spb.cityline.ru ) написал программу в помощь программистам на Ассемблерам. Как сообщает сам Алексей, "данная программа признана лучшей в номинации "Программное обеспечение под Windows". Вы можете скачать ее по адресу: http://www.oleg77.newmail.ru/Assembler. Однако Алексей просил пользователей данной программы сообщить ему координаты. Для дальнейшего изучения Ассемблера, нам нужно рассмотреть, как работает простейший отладчик (таковым является AFD. Было бы хорошо, если бы вы взяли его на нашем сайте и поэкспериментировали с ним). Интересно, а вы задумывались над тем, как отладчик выполняет программу пошагово? Можно ли его обмануть? Естественно, дорогие мои! Обмануть отладчик на Ассемблере очень просто. Мы уже рассматривали с вами два способа: 1. Перенести стек в тело программы; 2. Прочитать саму себя заново с диска. Однако, второй способ мы так и не разобрали. Почему же отладчик работал неверно, если мы читали саму себя в память? Давайте разберемся сперва с работой самого отладчика. Между прочим, это очень интересно. Прерывание 03. Вы уже знаете, что существует ряд прерываний, выполняющих те или иные функции при возникновении некоторых ситуации. Например:
В этом мы уже убедились из прошлого выпуска рассылки, где перехватывали перечисленные выше прерывания и "творили беспредел" с оболочками DOS. Вообще номера прерываний от 0 до 1Fh "обслуживаются" BIOS (ПЗУ). Все остальные доступны программисту или операционной системе. Например, MS-DOS использует номера от 20h до 2Fh (int 20h - выход из программы; int 21h - комплекс процедур и т.д.). Что значит "обслуживаются ПЗУ"? Это значит, что обработчики этих прерываний находятся в области BIOS (ПЗУ- Постоянное Запоминающее Устройство), в то время, как обработчики 20h - 2Fh и до 0FFh находятся в ОЗУ - Оперативное Запоминающее Устройство (т.е. та область памяти, которая теряется при выключении / перезагрузке компьютера в отличие от ПЗУ). Естественно, мы можем перехватить как прерывания ПЗУ (от 0 до 1Fh), так и все остальные, что мы уже делали. Прерывание с номером 3 примечательно тем, что:
Пояснение. Принудительно вызвать прерывание 03 - записать в любом месте нашей программы int 3. При отладке программы отладчик перехватывает 03h прерывание. Проще говоря, устанавливает вектор (адрес) данного прерывания на некую свою процедуру. Выполняя одну команду (когда пользователь нажимает клавиши F1 или F2), отладчик просто сохраняет следующий за текущей командой байт и вместо него вписывает 0CCh, т.е. int 3. Естественно, что на экран отладчик выводит данную инструкцию в ее нормальном, первозданном виде, а не int 3. Все просто смотрится на примере. Возьмем такую простейшую программу, которая выводит один символ 'Q' на экран в текущую позицию курсора: _______ cseg segment Begin: ret cseg ends _______ Вот как это выглядит в отладчике (только что загрузили ее под AFD):
Таблица N 1. Шаг первый: только что загрузились Примечание. Зеленым цветом (или символом '»') помечена текущая команда, т.е. команда, которая выполнится после нажатия клавиши F1/F2 в AFD. В колонке "Смещение" указываем, по какому смещению находится в памяти команда. В колонке "Инструкция Ассемблера" - реальные ассемблерные команды, расположенные по соответствующему смещению, а в колонке "Машинный код" - машинный код команды в шестнадцатеричной системе из колонки "Инструкция Ассемблера". Колонка "Что на экране" отражает то, что отладчик показывает нам на экране, а также оригинальный код команд. ________ К слову. Посмотреть машинные коды соответствующих команд Ассемблера можно в любом отладчике, а лучше - в Hacker's View. Реальные ассемблерные команды - команды, которые на самом деле находятся в памяти по соответствующему смещению (читайте дальше - все будет понятно). ________ Итак, запускаем нашу программу под отладчиком AFD. Команда mov ah,2 расположится по адресу 100h, а mov dl,'Q' - 102h (см. Таблицу N 1). Естественно, что отладчик сразу не даст программе работать, а просто загрузит ее в память и выведет на экран инструкции Ассемблера. Внимание! Пользователь нажимает F2. AFD запоминает один байт по адресу 102h (первый байт следующей за mov ah,2 команды. В нашем случае - 0B2h), записывает туда 0CCh, т.е. команду int 3 и выполняет инструкцию mov ah,2. После этого процессор выполнит не команду mov dl,'Q', а int 3 (вызывет прерывание 03, адрес которого указывает на определенную процедуру обработки отладчика AFD (отладчик-то перехватывает это прерывание сразу после загрузки!)). Вот, что получится, когда пользователь нажмет клавишу F2:
Таблица N 2. Нажали F2 первый раз. Что делает процедура обработки 03h прерывание? Допустим, текущая команда находится по адресу 0102h (см. Таблицу N 2). Пользователь нажимает F1/F2. Прерывание 03 делает следующее:
Вот, что получится, когда пользователь второй раз нажмет F2:
Таблица N 3. Нажали F2 второй раз. Обратите внимание, какой машинный код находится по адресу 0105h - 21C3h. Перед ним, по адресу 0104h, находится код 0CCh (int 3). Как вы уже поняли, команда Ассемблера может занимать один, два и более байт (например, mov ah,2 - 0B402h - два байта; ret - 0C3h - один байт). Если мы "вручную" в процессе работы рассматриваемой нами программы заменим 0B4h на, скажем, 0C3h, то вместо mov ah,2 мы получим ret (машинный код которой 0C3h). Но команда ret однобайтовая в отличие от mov ah,2. Куда же денется второй байт команды mov ah,2 (02h)? Процессор просто поймет 02 как add dh,[CD51+BP+SI]. Но т.к. данная команда занимает аж 4 байта, то процессор "присоединит" к 02h еще и 0B2h, 51h, 0CDh. Все. Код пошел путаться... Вот что происходит по смещению 105h в Таблице 3! Процессор распознал 21h как and. Но после and должны идти приемник и источник (например, and ax,11b). Следовательно, процессор возьмет следующий за 21h байт, которым будет 0C3h. 21C3h как раз и есть AND BX,AX! Похоже на кривое зеркало или "испорченный телефон" (игра такая была в моей молодости)... Отсюда и появляется по смещению 103h какой-то push cx в Таблице N 2. Вывод: если вы "на лету" меняете код своей же программы (по разным причинам), имейте в виду, что однобайтовую команду нужно менять на однобайтовую, двухбайтовую на двухбайтовую и т.д. Можно, конечно, менять две однобайтовые на одну двухбайтовую и пр. Можно даже, изменив всего один байт, сделать всю программу (или ее часть) другой. Т.е., например, в Hacker's View вы видите один код, а выполняет программа совсем другие действия. Но это вы еще поймете при экспериментах... Еще вопрос: почему же тогда отладчик работает нормально, "искриввив" код? Неужели вы не знаете? Int 3 ведь восстанавливает код, который был изменен! Получим так:
Таблица N 4. Нажали F2 третий раз. Хорошо получилось здесь: ret и int 3 занимают по одному байту. И последняя таблица:
Таблица N 5. Нажали F2 четвертый и последний раз. Пришли к тому, с чего начинали (Таблица N 1). Но не забываем про int 20h! Отладик пишет: "Program terminated OK". Всё! Способы обойти отладку программы Как же нам сделать так, чтобы AFD не смог производить отладку нашей программы (помимо двух способов, которые мы уже рассмотрели (см. начало настоящего выпуска))? Очень легко! Рассуждаем --> AFD перехватывает 3 прерывание --> AFD вставляет 0CCh (int 3) после каждой команды --> Процедура обрабоки 03 прерывания AFD постоянно останавливает нашу программу... Подумайте... Подсказка: нужно сделать что-то с обработчиком 03 прерывания... Подумайте, подумайте... Код команды iret - 0СFh... Думайте, думайте... Я пока за пивком сгоняю... ... "Ах, хорошо!" - сказал Олег, глотнув своей любимой "Балтики"... Надеюсь, что многие нашли не один, а несколько способов. Рассмотрим один. Нам нужно записать в самое начало обработчика 03 прерывания команду iret. Один из способов получения адреса обработчика того или иного прерывания нам известен: функция 35h прерывания 21h. Однако, можно получить и поменять адрес не прибегая к функциям 35h и 25h 21h-ого прерывания, хотя Microsoft делать так не советует. Но мы все равно рассмотрим. Все текущие адреса обработчиков прерываний находятся в так называемой таблице векторов прерываний, которая расположена по адресу 0000:0000h. Как вы знаете, адрес обработчика любого прерывания занимает 4 байта: сегмент (2 байта) и смещение (2 байта), причем первые два байта - это смещение, а вторые - сегмент (мы уже знаем, что все данные в компьютере храняться "задом наперед", включая расположение сегмент: смещение). Вспомните что возвращает функция 35h прерывания 21h. Т.о. адрес нулевого прерывания будет здесь: 0000:0000h, первого - 0000:0004h, второго - 0000:0008h и т.д. Вот пример чтения адреса обработчика 21h-ого прерывания из таблицы векторов прерываний: _____________ ... xor ax,ax mov bx,es:[21h*4] ;В BX - смещение ;Сохраним адрес 21h-ого на
будущее mov ah,2 ... Int_21h_offset dw ? ... _____________ Думаю, что пример не нуждается в пояснении. Однако, обратите внимание на следующие строки: mov bx,es:[21h*4] Ассемблер, в отличие от языков высокого уровня, вычислит выражение в квадратных скобках всего один раз. Т.е. процессор, выполняя приведенные выше строки, не будет постоянно вычислять, сколько будет 21h*4+2. А как такое возможно? Для начала вычислим сами: 21h*4+2=134 (86h). Подобные выражения, встречающиеся в программе, вычисляются на стадии ассемблирования, т.е. программой-ассемблером (MASM / TASM). Запустив программу под отладчиком, мы увидим, что строка mov es,es:[21h*4+2] будет отображаться как mov es,es:[86h]. Учитывая это, можно смело писать, например, так: mov ax,(23+5)*3 mov cx,34h+98/2 не беспокоясь за то, что такие строки будут постоянно вычисляться процессором, тем самым замедляя работу. А зачем это нужно? Неужели самому сложно высчитать и записать результат? Ответ прост: для удобства и наглядности в процессе написания программы. В нашем случае мы сразу видим, что в ES заносится сегмент 21h-прерывания: mov es,es:[21h*4+2] 21h - прерывание; +2 - сегмент. А если бы так было записано: mov es,es:[86h], то нам пришлось бы тогда постоянно высчитывать, вспоминать, что именно мы загружаем в ES. Можно, конечно, делать пометки в виде примечаний. Но это уж на ваше усмотрение: кому как удобней... Имейте только в виду, что выражения вида mov ax,[bx+di] остаются как есть! Откуда знать ассемблеру (MASM / TASM), какие значения находятся в регистрах BX и DI в момент ассемблирования? В данном выражении в AX поместится сумма чисел, которые в момент выполнения этой команды находятся в регистрах BX и DI. А зачем перед [21h*4+2] стоит регистр ES? Конечно, можно опустить ES, но тогда мы получим не адрес 21h-ого прерывания, а что-то совсем другое. ES указывает на то, что нужно загрузить слово в ES, которое расположено по смещению 21h*4+2, причем из сегмента не какого-нибудь, а именно из того, который указан в ES. Поэтому мы, собственно, и загружали в начале нуль в ES (а по какому сегменту находится таблица векторов прерываний? См. выше): xor ax,ax _____________ Давайте вернемся к отладчику. Итак, нам нужно занести в первый байт процедуры обработки прерывания 03 команду iret, тем самым "вырубив" отладчик. Прежде получим адрес обработчика прерывания 03: xor ax,ax mov bx,es:[03h*4] ;В BX - смещение Теперь занесем iret (машинный код iret - 0CFh) в самое начало обработчика: mov byte ptr es:[bx],0CFh Отладчик в нокауте! _________ Для того, чтобы лучше понять работу отладчика в файле-приложении найдите "!DEBUG.ASM". Что нужно сделать:
Вот вам и доказательства... ___________ Усвоив приведенный в данной рассылке материал, надеюсь, что вы без труда поймете, почему при перечитывании самих себя в память, отладчик работает неверно. Причем, что именно он делает? Вот еще таблица (если вы, конечно, не устали от них):
Здесь приведен кусок программы, которая считывает себя в память. Представьте, что полный код такой: ... mov ah,3Fh mov bx,Handle mov cx,offset Finish-100h mov dx,offset Begin int 21h ;Эта строка... ret ;и эта строка приведены в таблице. ... Внимательно посмотрите на колонки "Инструкция Ассемблера" и "Что на экране". Подумайте, что произойдет, если мы колонку "Что на экране" скопируем в колонку "Инструкция Ассемблера"? В таком случае просто затрется int 3 (0CCh). То же самое происходит, если мы читаем свою программу в память с диска. На диске ведь вместо int 3 будет ret! А как отладчик получит управление, если int 3 затирается? В приложении к данной рассылке вы найдете файлы типа Dbg16_01.asm. Посмотрите их, почитайте описания. Думаю, что многое станет понятным. Файл-приложение можно взять здесь: http://www.oleg77.newmail.ru/Assembler/Programs/Lessons/Debug16.rar С уважением,
|
http://subscribe.ru/
E-mail: ask@subscribe.ru |
|
В избранное | ||