Статьи по Assembler

       

Как изменить способности компьютера


Автор: Бордачев Андрей Юльевич (glareboa@mail.ru)

С другими интересами автора вы можете познакомиться здесь.

Статья была впервые опубликована в журнале Библиотека информационной технологии (БИТ), N 10 за 1994 год.

Огромное количество микросхем от "простеньких", выполняющих логические операции типа И,ИЛИ,НЕ и др., до сверхинтегрированных контроллеров в которые "впихнута" чуть ли не вся машина (ПЭВМ). Всевозможные дополнительные платы (адаптеры, контроллеры), винчестеры и т.д. Все это призвано к жизни и к взаимодействию с целью облегчить существование своим хозяевам (пользователям, программистам).

Что же, картина получается непростая. Но говорить о сложности устройства компьютера, о сложности его частей, о сложности процессов их взаимодействия излишне - это и так понятно.

Если Вы дочитали до этого места то, может быть, подумали: а к чему это автор намекает на всякие сложности, очевидные же вещи. Если появился вопрос - дадим на него ответ. Любой человек, имевший возможность работать с компьютером, и пользователь, и программист, в один прекрасный день думает: а хорошо бы, если бы компьютер ("ящик") умел делать еще и то-то, и так-то. Установить бы дополнительную "платочку" в компьютер и получить желаемое.

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

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

Дело в том, что многие сетевые адаптеры имеют гнездо для установки ПЗУ (постоянного запоминающего устройства, rom, read only memory), позволяющего при наличии локальной сети осуществлять удаленную загрузку операционной системы. Но, как показала практика, в большинстве случаев эта возможность не используется. Если в это гнездо вместо микросхемы со старой ПЗУ (если она там вообще есть) установить новую, со своей программой, то компьютер обретет новые дополнительные свойства. Например, это может быть программа, защищающая компьютер от несанкционированного доступа, или антивирусная защита (монитор, ревизор и т.п.). Так что учиться схемотехнике не придется. Можно сразу приступить к разработке программы для ПЗУ.


Но в чем же здесь преимущество? Одни только сложности. Вынимай микросхему, ищи новою. Программу нужно еще как-то поместить в ПЗУ. Да и вообще, будет ли она выполняться? Кто или что сделает так, чтобы эта программа получила управление?

Вот именно об этом "механизме", заложенном в BIOS и обеспечивающем возможность выполнения программ, "прошитых" (так называют программы, записанные в ПЗУ с помощью программаторов) в ПЗУ, а также о том, каким образом необходимо оформлять эту программу, и хотелось рассказать заинтересованным читателям.

Отметим при этом, что преимущество программы, размещенной в ПЗУ, заключается в том, что она неуничтожаема и отработает еще задолго до того, как начнут свою работу по загрузке операционной системы с диска модули MAIN BOOT и BOOT. И хотя эти части системы наиболее часто подвержены атакам вирусов, мы хозяева положения и можем блокировать любую атаку или же осуществить восстановительные работы.



волшебная возможность bios

Каким же образом машина узнает, что ей необходимо выполнить еще одну дополнительную программу? Задавшись вопросом, начинаем искать ответ. К счастью есть палочка-выручалочка - листинг BIOS, поставляемый в комплекте (за отдельную плату) с "персоналками". В этом листинге содержится информация, раскрывающая тайны функционирования машины. В частности, и ответ на наш вопрос.

Оказывается, что среди прочих важных дел, совершаемых подпрограммами BIOS, есть дело, заключающееся в проверке присутствия ПЗУ в адресном пространстве с С800:0 до E000:0.

Это пространство сканируется с шагом в 2048 байт на предмет наличия "подписи" 0AA55H. Но ведь может быть и совпадение. Чтобы не ошибиться, после того, как найдена "подпись", производится расчет контрольной суммы методом сложения по модулю 100H (сложение побайтно без учета переноса). Результат при этом должен получиться равным нулю. Но сколько же байтов необходимо просуммировать? Эта информация содержится в одном байте, следующем за подписью. Причем чтобы узнать размер ПЗУ в байтах, необходимо умножить это число на 200H.



Если все эти условия выполняются, BIOS считает, что перед ним ПЗУ, оформленное по правилам, и совершает последний шаг - передает управление на смещение +3 (отсчет начинается с нуля) относительно найденного сегмента. Следовательно, там должна располагаться первая выполняемая команда нашей программы. Обычно это команда jmp. Совершив все необходимое, наша программа должна вернуть управление обратно в BIOS с тем, чтобы та продолжила поиск аналогичных ПЗУ. Следовательно, наша программа должна заканчиваться командой retf.

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

;#################################################################### ; Поиск дополнительной ROM в области памяти C800->E000 в блоках по 2K ; Модуль ROM должен иметь подпись '55AA' в первых двух байтах и ; индикатор размера ((размер в байтах)/512) в третьем байте. ; Исполняемый код должен начинаться с четвертого байта. ;//////////////////////////////////////////////////////////////////// ROM_SCAN: ;начало фрагмента кода BIOS STI ;разрешить прерывания MOV DX,0C800H ;начало сканирования ROM_SCAN2: MOV DS,DX ;ds - сегмент очередного ROM-модуля SUB BX,BX ;bx - указатель в ROM-модуле MOV AX,[BX] ;взять первое слово в модуле CMP AX,0AA55H ;подпись найдена? JNZ NEXT_ROM ; нет - перейти к следующему базовому адресу ROM CALL ROM_CHECK ; да - проверить контрольную сумму JMP ARE_WE_DONE ;перейти к проверке конца области дополнительной ROM NEXT_ROM: ;следующий ROM-модуль ADD DX,0080H ; позиционировать новый модуль через 2K байт (как сегмент) ARE_WE_DONE: ;проверка конца области дополнительной ROM CMP DX,0E000H ;достигнут адрес E0000? JL ROM_SCAN2 ; нет - перейти к новой проверке подписи ;//////////////////////////////////////////////////////////////////// ;Последующий код не относится к теме статьи ;... ;... ;... ;#################################################################### ;Эта подпрограмма вычисляет контрольную сумму дополнительных ;модулей ROM и, если она верна, вызывает программу из модуля ;(При входе регистр ES указывает на сегмент данных BIOS) ;//////////////////////////////////////////////////////////////////// ROM_CHECK PROC NEAR ;вычислить число байт для сканирования SUB AH,AH ;ax - индикатор размера модуля MOV AL,[BX+2] MOV CL,09H ;ax=ax*512 SHL AX,CL MOV CX,AX ;cx - счетчик байт для сканирования ;вычислить указатель на следующий модуль и поместить его в dx PUSH CX MOV CX,4 SHR AX,CL ADD DX,AX POP CX ;вычислить контрольную сумму модуля CALL ROM_CHECKSUM JZ ROM_CHECK_1 CALL ROM_ERR ;контрольная сумма на совпала! JMP ROM_CHECK_END ;---- Вызов программы, находящейся в ПЗУ ---------------- ROM_CHECK_1: PUSH DX ;сохранить текущее значение указателя MOV ES:IO_ROM_INIT,0003H ;подготовить смещение MOV ES:IO_ROM_SEG,DS ;подготовить сегмент CALL DWORD PTR ES:IO_ROM_INIT ;вызвать программу POP DX ;восстановить указатель ;---------------------------------------------------------- ROM_CHECK_END: RET ;завершить работу ROM_CHECK ENDP ;=========================================== ;Подпрограмма вычисления контрольной суммы ;=========================================== ROM_CHECKSUM PROC NEAR XOR AL,AL NEXT_BYTE: ADD AL,[BX] INC BX LOOP NEXT_BYTE OR AL,AL ;сумма равна нулю? RET ROM_CHECKSUM ENDP



Осталось обговорить еще одну тонкость. Каким образом обеспечить равенство нулю контрольной суммы? Ведь очевидно, что само собой это условие не будет выполняться. Идея проста - необходимо дополнить получившуюся сумму до нуля. Делается это размещением числа, равного разнице между 100H и получившейся контрольной суммой в байте (перед расчетом он должен быть равен нулю), следующем за последней командой программы. Отметим также, что расчет контрольной суммы ведется по количеству байт, равных размеру ПЗУ, а не по размеру получившегося кода программы.

После сказанного можно отобразить структуру программы следующим образом:

смещениекодкомментарий
+00 db 55h,0AAh подпись
+02 db (?) индикатор размера модуля
+03 db 0EAh,(?),(?) передача управления коду (metka)
+06 ;... константы программы
+?? metka:
;...
код программы
+?? retf возврат управления BIOS
+?? db (?) дополнение контрольной суммы до 100h
Знаки вопроса означают, что конкретные значения будут известны только после написания программы.

Если ваша программа должна работать только на этапе старта компьютера, то ее переменные вы можете размещать в любой области conventional ОЗУ выше той, которая отведена под переменные BIOS (то есть в диапазоне сегментных адресов 0050:0000...9000:FFFF) - на этом этапе указанный диапазон свободен. Гораздо сложнее ситуация, когда ваша программа должна работать после того, как операционная система загружена: например, обслуживать прерывания или предоставлять свои процедуры для вызова обычными программами. В этом случае вам придется предусмотреть механизм, исключающий конфликты использования памяти.

В заключение - две программы.

;############################################################ ;Пример программы рассчитывающей контрольную сумму, ;вычисляющей дополняющий байт и формирующей обрабатываемую ;программу в виде файла на диске. ;============================================================ TITLE ComputeCRC INCLUDE MACRO.DEF LENGTHROM EQU 2000H ;Размер ПЗУ в байтах (8192) CODE SEGMENT BYTE PUBLIC ASSUME CS:CODE,DS:CODE ORG 100H START: JMP SHORT BEGIN ;Имя обрабатываемой программы FileName: DB 'FileName.Ext',0 BEGIN: ;Открыть файл для чтения MOV AX,3D02H MOV DX,OFFSET FileName INT 21H JNC M1 JMP ER M1: ;Прочитать файл в конец программы MOV BX,AX MOV AX,3F00H MOV CX,LENGTHROM MOV DX,OFFSET LastByte INT 21H JNC M2 JMP ER M2: ;Запомнить количество прочитанных байт MOV CX,AX ;Установить указатель позиции в файле на начало MOV AX,4200H PUSHR CX,DX ;PUSHR,POPR - макросы описаны в MACRO.DEF MOV CX,0 ;см.статью Макросы First и Second



MOV DX,0 INT 21H POPR CX,DX JC ER PUSHR AX,BX,CX,DX,DI ; Рассчитать контрольную сумму PUSHR CX XOR AX,AX MOV BX,OFFSET LastByte M3: ADD AL,[BX] INC BX LOOP M3 ;Вычислить дополняющий байт MOV CX,100H SUB CX,AX MOV AX,CX POPR CX ;Настроить указатель на начало обрабатываемой программы PUSHR AX MOV DI,CX ADD DI,OFFSET LastByte-1 ;Найти место в обрабатываемой программе для записи вычисленного байта MOV AX,0 STD REPNE SCASB INC DI POPR AX ;Записать его туда MOV [DI],AL POPR AX,BX,CX,DX,DI ;Записать получившийся модуль на диск MOV AX,4000H INT 21H ;Ошибки не обрабатываем т.к. программа проста ER: ;Закрыть файл MOV AX,3E00H INT 21H ;Нормальное завершение программы MOV AX,4C00H INT 21H LastByte: CODE ENDS END START

В качестве примера, реализующего структуру, описанную выше, приведена программа-скелет, способная стать основой для написания ваших программ:

;############################################################ ;Пример "скелета" программы для записи в ПЗУ ;============================================================ TITLE BiosPassword LENGTHROM EQU 2000H ;Размер ПЗУ в байтах (8192) CODE SEGMENT BYTE PUBLIC ASSUME CS:CODE,DS:CODE ORG 0 START: DB 55h DB 0AAh ;Размер ПЗУ по модулю 200H DB LENGTHROM SHR 9 ;Первая выполняемая команда JMP BEGIN ;--------------------------------- Данные ---- CP1 DB 14,'Copyright (C) ' CP2 DB 18,'by Bordachev A.Y. ' CP3 DB 18,'ver. 1.00-93/03/31' UNFACE DB 4,':-( ' FACE DB 4,';-) ' BIOS DB 5,'BIOS ' PROMPT DB 9,'PASSWORD:' ERR1 DB 4,'ERR1' ;----------------------- Начало программы ---- BEGIN: ;Не забудьте настроить и другие регистры если необходимо MOV AX,CS MOV DS,AX ;--------------------------------------------- ;- Здесь располагается код Вашей программы. - ;--------------------------------------------- ;Вернуть управление вызвавшей программе (BIOS) RETF ;Сюда запишем дополняющий байт DB (0) CodeEnd: ;Все что ниже, заполним кодом 0FFH ;так как в чистом ПЗУ обычно содержится ;именно этот код. DB (LENGTHROM-(OFFSET CodeEnd-OFFSET START)) DUP (0FFH) LastByte: CODE ENDS END START

Читайте также другие статьи Андрея Бордачева: О формате PCX и Макросы First и Second.


Содержание раздела