Как работать с защищёнными программами (тайники спектрума) 1990 г.

Глава 8. - использование системных процедур.


8. ИСПОЛЬЗОВАНИЕ СИСТЕМНЫХ ПРОЦЕДУР

Ранее были представлены процедуры из ПЗУ:
SAVE7BYTES и LOAD-BYTES. Рассмотрим как использовать эти
процедуры для защиты программ. Рассмотрим блоки машинного
кода, запускаемые нестандартно.

Первый способ- это прикрытие загрузчика блоком, который им
загружается. Такая защита применена, к примеру, в игре
ПАРАДИЗ-СИТИ. Проследим способ чтения этой программы, так,
чтобы она не стартовала автоматически. Начнем с бейсика.
Практически наиболее важной инструкцией является RANDOMIZE
USR. Лучше всего восстановить эту прцедуру с адреса запуска
RANDOMIZE USR (PEEK 2 3 6 4 7+2 5 6 * РЕЕК 23 62 8) , т.е. в нашем
случае с адреса 24130. Подобные процедуры используют из-
вестную нам процедуру 1366, считывающую блоки без заголовка.

Также и в этом случае, но перед ее вызовом с помощью коман-
ды LDIR процедура загрузки переносит сама себя в конец
памяти по адресу 63116 и переходит туда по команде JP:

потом считывается картинка, а затем главный блок данных:

потом считывается картинка, а затем главный блок данных:

Способ запуска считанной программы требует пояснения.
Как известно, каждая инструкция CALL заносит в машинный
стек адрес, с которого начинает работать программа после
выхода из подпрограммы. В этом загрузчике после выполнения
второй инструкции CALL 1366 в стек заносится адрес команды
за CALL. т.е. 63142,и процедура загрузки затирает сама себя,
так как считывает байты с магнитофона в ту область памяти,
где она была размешена. Важное значение имеет способ запус-
ка считанной программы. Процедура 1366 заканчивается инст-
рукцией RET. означающей переход по адресу , записанному в
стек, или, в нашем случае, по адресу 63142. В процессе счи-
тывания программы процедура, которая находилась там, была
замешена считанной программой, но микропроцессор этого не
замечает и возвращается по адресу, с которого выполнена
CALL 1366, не обращая внимания на то, что там находится
уже другая программа. Схематично это показано на рисунке:

Слева расписано содержимое памяти до, а справа- после
считывания программы. Команды,отмеченные " * " ложатся на
выполняемую программу.

Как распознать защиту такого рода и как ее обойти?
Начнем с бейсика. Считываем загрузчик (на ассемблере) и
определяем адрес окончания считанных блоков (добавляя к
адресу начала - регистр IX длину блока -регистр DE ). Если
какой-либо из блоков накрывает процедуру загрузки, то это
и означает, что программа считывается и загружается именно
таким способом. Дальше достаточно, опираясь на данные об
адресе и длине блока, написать короткую процедуру,
загружающую нужный нам блок кода, или подготовить
соответствующий заголовок, а затем с помощью CLEAR ADR
установить соответствующим образом машинный стек (так,
чтобы машинная программа не уничтожила стек), и, после
этого, считать программу. После выполнения в ней
необходимых изменений, ее можно записать на ленту, но в том
же виде, в каком был записан оригинал (длина блока должна
совпадать прежде всего ! ) . Если этот блок был без заголовка
( как в нашем случае) , то записываем его обычным SAVE ". . . "
CODE ... , но опуская заголовок, т.е. магнитофон включаем
только в перерыве между заголовком и блоком кода. Можно
также попробовать запустить считанный блок переходом на
требуемый адрес командой RANDOMIZE USR ... , но это не
всегда получается. В игре "ТРИ НЕДЕЛИ В ПАРАДИЗ СИТИ" этим
адресом будет 63142, и, можно убедиться - этот метод не
срабатывает.

Другим интересным способом запуска блоков машинного
кода является считывание программы в область машинного
стека. Таким способом можно запускать блоки машинного кода,
загружая их просто через LOAD " " CODE. Схематично этот
метод показан на рисунке:

Указатель стека (регистр SP) принимает показанное на
рисунке состояние в процессе выполнения процедуры 1366
(вызванное из бейсика через LOAD""CODE ). Способ запуска
программы в целом прост. Адрес считывания блока рассчитан
так, что блок считывается на машинный стек именно с того
места, в котором находится адрес возврата из инструкции
LOAD""CODE ( он при этом равен значению системной перемен-
ной ERRSP-2) или прямо из процедуры LOAD-BYTES (равен
ERRSP-6 ). Тогда два первых байта программы содержат адрес
ее запуска. Этот способ очень похож на предыдущий,за исклю-
чением того, что там подменялась процедура загрузки, а
здесь - адрес возврата из этой процедуры, или просто адрес
возврата из инструкции LOAD. После считывания блока кода
микропроцессор считывает содержимое стека и переходит по
прочитанному адресу, который только что появился в памяти.
По этому адресу в программе находится начало процедуры
загрузки ее последующих блоков - что и показано на рисунке.

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

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

Теперь рассмотрим вопрос разделения на части блоков
длиной более 42К. Вмешательство в блоки такого типа основа-
но на разделении их на фрагменты так. чтобы в памяти еще
оставалось место для MONS-A или другого дизассемблера,
исправления этих фрагментов, а затем сборки их в целый блок
либо написания новой процедуры загрузки. Обычно достаточно
разделить блок на две части. Вначале необходимо из процедуры
загрузки, или, если ее нет. то из заголовка этого блока

получить длину блока и адрес его загрузки. Чтобы полу-
чить первую часть блока используем процедуру 13 6 6 но с
другими параметрами- не с теми, которых требовал разделенный
на части блок,просто задаем адрес, по которому хотим размес-
тить этот блок (выше RAMTOR), а также длину - примерно 16К,
несмотря на то, что блок этот значительно длиннее. Считаем
теперь этот блок через CАLL 1366 или CALL 2050, но во вто-
ром случае сообщение "ТАРЕ LOADING ERROR", которое появит-
ся, не даст нам никакой информации о верности считывания,
т. к . мы загружаем часть блока без контрольного байта,
находящегося в конце блока. Считанную таким образом первую
часть блока записываем на ленту и сразу же приступаем ко
второй части. Ее считывание сложнее ввиду ограничений по
памяти, но также во зможно. Вспомним, что в нашем компьютере
16К ПЗУ, запись в которые нево змо жна. Например, вызовем
процедуру 1366 с адресом чтения ра вным 0, и начальные 16К
будут просто потеряны, а в память ОЗУ считаются только
следующие 32К или меньше (т.е. оставшаяся свыше 16K часть
блока) . Считываемый блок разместится в ОЗУ с адреса 16384,
заходя на системные переменные длины блока и оставляя без
изменения лишь те байты, адреса которых больше длины блока.
Поэтому необходимо позаботиться о том, чтобы машинный стек,
а также написанную нами процедуру загрузки разместить в
конце памяти. Нужно также помнить о том, что система
бейсика будет уничтожена и записать сразу считанный блок на
ленту можно только процедурой, написанной на ассемблере.
Кроме того в промежутках времени между считыванием фрагмента
блока и его записью нельзя разблокировать прерывания, так
как они изменяют содержимое ячеек с адресами 23552-23560 ,
а также 23672-23673, а там находится считанн ый блок. Чтобы
выполнить это последнее условие, войдем в середину процедуры
1366, благодаря че му после считывания блока не будет
выполнена процедура 1343. Именно она еще и разблокирует
прерывания. С помощью CLEAR 64999 переносим машинный стек,
с адреса 65500 помещаем процедуру загрузки:

ORG 6 5 0 0 0;

LD IX,0; адрес считывания

LD DE,DL; длина блока

LD A,255; подготовка к считыванию

SCF; блока

INC D; таким способом

EX AF, AF; заменяем начало

DEC D; процедуры 1366

DI ; а затем входим

LD A, 15; в ее середину:

OUT (254) , A;

CALL 1378;

LD A, 0; черная рамка

JP 0, OK; будет означать

LD A, 7; верное считывание

OK OUT (254),A; белая - ошибочное

CZEKAJ LD A, 191; ожидаем нажатия

IN A, (254) ; "ENTER"
RRA;

JP 0, CZEKAJ;

LD IX,0; запись считанного

LD DE,DL-16 3 8 4; блока на

LD A,255; ленту

CALL 1218; а также

LD HL,64999; инициализация

JP 4 633; системы бейсика .

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

10 CLEAR 64999

20 INPUT "DLINA BLOKA?"; DL:RANDOMIZE DL

:LET X=PEEK 23670: LET Y=PEEK 23071: LET S=0

30 FOR N=65000 TO 65054: READ A:LET S=S+A
: POKE N, A : NEXT N

40 IF S <>5254+2*(X+Y)-64 THEN
PRINT "ОШИБКА В ДАННЫХ": STOP

5 0 PRINT "ВСЕ ДАННЫЕ О KEY, ВКЛЮЧИ МАГНИТОФОН"

60 RANDOMIZE USR 65500

7 0 DATA 221, 33 , 0, 0, 17, X, Y, 62, 2, 55 , 55, 20 , 8, 21, 2 43 , 62,

15,211,254

8 0 DATA 2 05,98,5,62,0,56,2,62,7,211,254,62,191,219,

254, 31,56

9 0 DATA 249, 221,33,0,0, 17, X, Y-64, 62, 255,205, 194,4,33,

231, 253 , 202 , 25, 18

Рассмотрим, как разделить блок. Установим ленту на блоке
кода, который нужно разделить. Если он имел заголовок, то
его опускаем. Запускаем процедуру и включаем магнитофон, в
определенный момент времени увидим, что программа считывает-
ся на экран - так и должно быть. После загрузки блока цвет
рамки говорит о правильности считывания: черная рамка-
все в порядке, белая - была ошибка. Вставим в магнитофон
другую кассету, включаем запись и нажимаем "ENTER"
Программа запишется на ленту, потом процедура вернется в
бейсик, инициализируя систему сообщением "(0) 1982..."
но не очищая память (уничтожается только область от начала
экрана до, примерно, 2 4 000 адреса) . Теперь подготавливая
заголовок или записывая коротенькую процедуру. можно
прочесть полученный блок под любой адрес.

Чтобы запустить измененную программу придется немного

потрудиться и собрать разделенную программу или написать
для нее новую процедуру загрузки. Если программа заполняла
полностью 48К памяти ОЗУ, возможен только второй метод.

Если надо соединить блоки - достаточно написать проце-
дуру, похожую на разделяющую, но такую, которая считывает
первый блок под адрес 163 84, второй - сразу за ним, а
затем запишет их вместе как один блок.

6 этом разделе был описан способ запуска блоков машин-
ного кода путем считывания а область машинного стека. Для
запуска такого блока достаточно ввести инструкцию
LOAD""CODE, которая и запустит его.Чтобы убедиться в этом
на практике, запустим программу, приведенную ниже:

10 CLEAR 65361: LET S=0

20 FOR N=65362 ТО 65395: READ A

:РОКЕ N,A: LET S = S+A: NEXT N
З0 IF S<>3437 THEN PRINT "ОШИБКА В СТРОКЕ DATA"
: стоп

4 0 SAVE "BEEP" CODE 6 53 62,3 4
50 DATA 88,255,3,19,0,62,221,2,

29,6,8, 197, 17,30,0,33,0,8,43, 124, 18
60 DATA 181,3,33,0,8,43,124,18,

1,32,251,193, 16,235 221,225,201

Она запишет на ленте короткий блок машинного кода,
который будет запускаться самостоятельно. После записи
этого блока освободим память с помощью RANDOHIZE USR 0
или RESET (по возможности, нужно переставить RAMTOR в
нормальное значение с помощью CLEAR 65367 ) и прочтем
его, введя LOAD""CODE. Задача этого блока - проинформи-
ровать, что он запустился - что и делается несколькими
звуковыми сигналами, которые появятся сразу после
считывания программы, как только с рамки исчезнут
гранатово - желтые полосы.




  Оставте Ваш отзыв:

  НИК/ИМЯ
  ПОЧТА (шифруется)
  КОД



Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
Литстраничка - Литературные миниатюры от читателей Optron.
Фантазия - рассказ "Резервация".
Тема - Russian ZX: история создания Российского спектрума.
Всякая всячина - Чёрная ворона.
MY SPEKK - В этой рубрикe нaдa рaскaзывaть про... прaвильно, спeктруmы.

В этот день...   24 ноября