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


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

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

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

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

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

Дело в том, что многие сетевые адаптеры имеют гнездо для установки ПЗУ (постоянного запоминающего устройства, 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 END

Осталось обговорить еще одну тонкость. Каким образом обеспечить равенство нулю контрольной суммы? Ведь очевидно, что само собой это условие не будет выполняться. Идея проста — необходимо дополнить получившуюся сумму до нуля. Делается это размещением числа, равного разнице между 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

[C] Андрей Бордачев

 

Источник WASM.RU


Поделиться в соц сетях

Подписаться
Уведомить о
0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии

Есть идеи, замечания, предложения? Воспользуйтесь формой Обратная связь или отправьте сообщение по адресу replay@sciencestory.ru
© 2017 Истории науки. Информация на сайте опубликована в ознакомительных целях может иметь ограничение 18+