Программирование в машинных кодах и на языке ассемблера 1993 г.

Дополнение 1 - практические рекомендации по просмотру машинного кода фирменных игровых программ.


6. ПРАКТИЧЕСКИЕ РЕКОМЕНДАЦИИ ПО ПРОСМОТРУ МАШИННОГО КОДА ФИРМЕННЫХ ИГРОВЫХ ПРОГРАММ.

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

Сразу оговоримся, что рекомендации, которые мы здесь приведем, не являются строго однозначными и окончательными. Это только путеводитель. Возникли они как результат наших поисков и экспериментов, может быть Вы найдете другие, лучшие. Во всяком случае, с набором опыта Вы будете расширять и набор приемов. Может быть, Вы сами сможете поделиться своими приемами с нашими читателями.

Предположим, что Вы взяли игровую программу (незащищенную от просмотра) и с помощью, допустим, программы-копировщика выделили из нее машинокодовый блок. У разных программ он бывает разным, но допустим его размер примерно 4 0К.

Что будет, если Вы захотите только просмотреть его, например с помощью программы ДИСАССЕМБЛЕРа, скажем MONITOR или MONS . Если каждому байту Вы уделите хотя бы по 3 секунды своего внимания, то на просмотр всего блока у Вас уйдет более 30 часов напряженнейшей работы. В то же время, есть приемы, которые позволяют в течение 30...40 минут вчерне разобраться с устройством программы и дальнейшую работу производить более целенаправленно .

Самое лучшее, что можно сделать - это распечатать код программы на принтере. Уже за то время, пока он будет печататься, Вы сможете во многом разобраться. На худой конец напишите программу, которая будет распечатывать текст Вашего блока на экране. Главное требование, чтобы печать производилась в следующем формате строки: адрес, а за ним восемь байтов. Очень важно, чтобы их было именно восемь, это поможет при беглом просмотре кода многое открыть.

Давайте рассмотрим варианты того, что Вы при этом можете увидеть.

1. Если в тексте относительно часто встречаются коды CD (CALL NN) и FE (CP N) , то есть большая вероятность, что перед

Вами главный логический блок программы, осуществляющий увязку всех структур в единое целое. Он обычно невелик (до 1К) и может быть запрятан глубоко в тело программы.

Пример фрагмента программы "BOMB JACK". 49952 F6 E6 CD 38 EC CD 10 ED 49960 CD A0 ED CD EB D7 CD 5B 49968 CC CD 8F D9 CD A8 D6 3A

2. Если перед Вами сравнительно однородные значения кодов, но все же относительно чаще появляются коды 20, 21, 22, 2А, 32, 3А, 3Е, С9, то здесь скорее всего исполняемый машинный код, а именно - рабочие процедуры программы. Пример фрагмента:

55656

С8

С9

11

00

40

06

BF

ED

55664

53

E1

FD

21

E3

FD

14

7A

55672

E6

07

20

12

7B

C6

20

5F

3. Если в выводимых строках явно преобладают близкие по значению коды, лежащие в диапазоне от 30 до 7А, то с большой вероятностью можно сказать, что в этих областях памяти хранятся тексты сообщений, запросов к пользователю, экранных меню и т. п.

Пример фрагмента.

61616

6E

20

77

69

74

68

20

74

61624

68

65

20

42

65

73

74

10

61632

03

10

42

6F

6D

62

65

72

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

Возможен, конечно, вариант, что программист при стыковке всех блоков программы в единое целое просто оставил эту область в качестве "мусора".

63968 27 00 00 00 00 00 0A 00

63976 0A 00 10 27 00 00 00 00

63984 00 0A 00 0A 00 10 27 00

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

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

Фрагмент:

34888

F8

02

F8

03

00

FF

01

FC

34896

03

78

00

EF

FC

01

FE

00

34904

10

C6

87

30

CF

00

39

00

34912

FF

00

FF

00

FF

00

FF

00

5.2. Бывает , что экраны хранятся в стрингов, т.е. в строго заданном формате (скажем в стринге длиной 256 байтов каждый бит несет информацию о том, какой символ, где и как должен помещаться на экран) . Это бывает в программах с многочисленными экранами и псевдографикой. Например, MANIC

MINER, JET SET WILLY. В этом случае Вам поможет тот факт, что распечатку Вы ведете по восемь кодов в строке. Возникает ситуация, когда при просмотре строк по вертикали Вы можете уловить закономерности повторения одинаковых байтов.

По этому же принципу можно уловить и области, где в программах размещаются графические изображения спрайтов (движущихся объектов, персонажей и т.п.)

Фрагмент программы:

49424

78

87

38

78

38

38

78

38

49432

30

08

78

81

38

04

78

81

49440

38

05

78

81

38

04

78

81

49448

38

08

78

81

38

04

78

81

6. Распечатка

по восемь

кодов

в одну строку может Вам

по

определить

еще

одну

важную

область

в программе - где

про

грамма хранит шаблоны для нестандартного генератора шрифта. Часто перед Вами стоит задача переделать шрифт в программе на русский. Если программа использует стандартный шрифт "Спектру-ма", то Вам достаточно где-то загрузить русский шрифт и поместить указание на него в системные переменные (мы об этом писали в нашей книге "Большие возможности Вашего Спектрума"). Если же программа применяет свой нестандартный генератор шрифта, то это ничего не даст, т. к. она все равно выставит системную переменную CHARS так, чтобы она указывала на программный генератор шрифта. Поэтому Ваша задача - отыскать, где в программе находятся шаблоны символов шрифта и переделать их.

При распечатке по восемь кодов в строке, Вы явно увидите в одной из позиций столбец нулей, когда будут распечатываться шаблоны. Пример фрагмента программы:

00

36984

20

00

00

6E

CE

D6

E6

36992

EC

78

00

3C

7E

18

18

18

37000

3C

6E

00

3C

7E

06

0C

10

37008

3E

7C

00

3E

7C

08

1C

06

00

Вы, наверное, догадались, почему так происходит. Дело в том, что шаблон символа записывается восемью байтами, по одному байту на каждую строку шаблона сверху вниз. Поскольку верхняя строка почти всегда пустая (чтобы обеспечить минимальный интервал между строчками при печати), то каждый восьмой байт оказывается нулевым.

Дополнительным указанием на то, что здесь хранятся шаблоны символов, служит тот факт, что все байты в приведенном фрагменте - четные. И это тоже не случайно. Чтобы между буквами при печати был минимальный зазор, надо, чтобы правый крайний столбец шаблона был пустым. А правой позиции каждой строки шаблона соответствует нулевой бит. Если в байте нулевой бит выключен, то число четное, а если включен, то нечетное.

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

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

Например, если в БЕЙСИКе Вы встретите запись:

LET a = 15: GO TO a - это совсем не значит, что здесь выполняется переход к 15-й строке. Вас, может быть, обманывают. Дело в том, что формат БЕЙСИК-строки таков, что сначала число записывается в виде стринга, а затем в пятибайтной форме. Если Вы просмотрите БЕЙСИК-строку байт за байтом командой PEEK, то увидите, что каждое число, кроме номеров строк, повторено дважды. Так вот, первая запись говорит о том, что будет показано на экране, а вторая (интегральная) - что реально будет исполнено. Конечно, нормально эти записи соответствуют друг другу, но программист легко мог сделать, что на экране изображается 15, а в расчет идет что угодно другое. Не уловив такого ложного перехода в БЕЙСИК-строке, Вы потеряете нить и попадете в подготовленную ловушку.

Итак, все надо проверять, в том числе и БЕЙСИК-строки, желательно с помощью ДИСАССЕМБЛЕРа.

Все вышеприведенные фрагменты мы взяли из широкораспространенной программы BOMB JACK. Мы даже указывали адреса, где что содержится. Но не спешите заглядывать в машинный код этой программы. Вы ничего подобного там не увидите. Тот код, который содержится на ленте, не соответствует тому, который работает. Те адреса, куда загружается эта программа, не соответствуют тем, в которых она находится во время работы. Нестандартный загрузчик (он же, кстати, выдает на бордюр красивые цветные полосы при загрузке) выполняет раскодирование информации, поступающей от магнитофона и переброску всей программы в новое место после окончания загрузки. Защита не самая серьезная, но достаточная, чтобы испортить настроение начинающему.

Приведенные выше фрагменты взяты нами после того, как все ступени предохранения были сняты.

Разобравшись грубо со структурой программы, Вы можете начать ее анализировать. Отыщите главный логический блок. В нем Вы найдете массу обращений к подпрограммам:

CALL NN

CALL NN

CALL NN

Применяя дисассемблирующую программу, поставьте вместо CALL NN точку прерывания (BREAK POINT) и стартуйте проверяемую программу прямо из дисассемблера (JP ADDR). Программа начнет работать, дойдет до точки прерывания, остановится и вызовет дисассемблер. Снимите эту точку прерывания и поставьте другую в следующем CALL. Опять стартуйте проверяемую программу. Теперь она пройдет немного дальше, а Вы поймете, что делала та процедура, которую Вы перед этим отключали. Продолжайте в том же духе. Уже через два-три часа Вы будете знать назначение основных процедурных блоков исследуемой программы и сможете приступить к их детальному анализу.

Как правило, Вы начинаете разбор готовой програмы с какой-то конкретной целью. В зависимости от того, какова Ваша цель, Вы можете применять какие-либо иные тактические приемы.

Если Вы хотите модернизировать программу потому, что количество попыток на ее прохождение Вам недостаточно, то Вы можете применять такую стратегию.

Допустим, Вам дано всего четыре попытки. Очевидно, где-то в программе есть переменная, и ей отведен адрес, в котором хранится количество оставшихся попыток. Найти этот адрес - задача непростая, но ведь как-то в начале работы там было установлено число 4. С большой вероятностью можно полагать, что оно устанавливалось сначала в аккумуляторе, а потом пересылалось в адрес. Если это так, то где-то в программе должна быть команда LD A,4 (машинный код 3E04) . Напишите несложную программу на БЕЙСИКе, которая просмотрит всю память исследуемой программы и выдаст Вам адреса, в которых она встретила сочетание кодов 3E и 04. Поверьте, их будет очень немного. Теперь с помощью POKE дайте в найденный адрес вместо 04 скажем 05 или 25. Посмотрите, что получилось . Если Вы работаете с дисассемблером, то он уже имеет команды на поиск заданного стринга и вся задача упрощается .

Если Вы ставите перед собой задачу адаптации программы на русский язык, то Вам надо прежде всего две вещи:

- найти, где в программе находится шрифт;

- найти, где в программе находятся тексты сообщений, меню и т. п.

Шрифт можно искать по какому-либо символу, например, по восклицательному знаку. Поскольку как бы шрифт ни изображался, восклицательный знак в основной своей части представляет вертикальную палочку, то в записи его шаблона должны присутствовать подряд по крайней мере три одинаковых байта.

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

Программу на поиск текстов сообщений также несложно написать. Логика ее работы может быть такой. Найти все случаи, когда подряд следуют пять байтов, каждый из которых больше 40 (28 HEX) , но меньше 122 (7A HEX) .

Задач перед Вами может стоять сколько угодно - от желания постичь премудрости машинного программирования до организации опроса по внешнему порту монетоприемника с целью выяснения пикантного вопроса "А не забыл ли заигравшийся клиент опустить очередные двадцать копеек?", и все, конечно, осветить невозможно. Думайте, ставьте тесты, анализируйте результаты и, насколько возможно, автоматизируйте свою работу. Поручайте компьютеру самому производить поиски, сравнения, ставить эксперименты.

Мы не даем тексты несложных поисковых программ, так как полагаем, что Вы легко их напишете сами, но на всякий случай, если у Вас нет под рукой удобного ДИСАССЕМБЛЕРа, даем листинг программы шестнадцатиричного вывода по восемь кодов в строке.

HEXOUT

10 INPUT "begin",beg

20 INPUT "end",end

30 DIM a$ ( 8 , 3) , DIM d (8)

40 FOR i=beg TO end STEP 8

50 LET b$=""

60 FOR k=1 TO 8

70 LET d(k) = PEEK(i+k-1)

80 GO SUB

90 LET b$=b$+a$(k)

100 NEXT k

110 GO SUB 1000

120 PRINT w$;b$

130 PRINT

140 NEXT i

500 LET h=INT (D (k) /16)

510 LET l = d ( k) -h* 1 6

520 LET h$ = CHR$ (h+48 + 7* (h>9) )

530 LET l$=CHR$ (l+48+7*(l>9))

540 LET a$ (k)=h$ + l$ + "_"

550 RETURN

000

LET

a1=

:INT ( i/16/ 16/1 6)

010

LET

re z

=i-a1

*16* 16*1 6

020

LET

a2=

: INT (

rez/16/16 )

030

LET

re z

= rez -

-a2*16*16

040

LET

a3=

: INT (rez/16)

050

LET

a4=

: rez-a3* 1 6

060

LET

m$ =

CHR$

(a1 + 48+7*

(a1>9)

060

LET

m$ =

CHR$

(a1 + 48+7*

(a1>9)

070

LET

n$=

CHR$

(a2 + 48+7*

(a1>9)

080

LET

p$ =

CHR$

(a3 + 48+7*

(a1>9)

090

LET

q$ =

CHR$

(a4 + 48+7*

(a1>9)

100

LET

w$ =

: a$ +

n$ + p$ +

q$ +

110

RETURN




СОДЕРЖАНИЕ:


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

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



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

Похожие статьи:
Вступление - история создания журнала.
Demo Party - результаты Millenium Party.
BBS - список станций BBS ZXNet.
От авторов - Virt group и газета ZX-News.
Письмо №282

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