Глава
7
КСОРКА
(обещанное
мной продолжение, написанное специально для этого издания)
Это
странное, даже неприятное слово было придумано мною несколько лет назад и,
говорят, прижилось. В этой главе я попытаюсь объяснить, что оно значит, и если
Вы поймете, какой страшный смысл оно таит, то свою задачу я выполню.
Итак,
поехали. В предыдущих главах уже приводились кое-какие элементарные загрузчики
в кодах. Но, как и простые бейсик-загрузчики, легкие защиты в кодах встречаются
не так часто, как хотелось бы. Все, кому не лень, пытаются более или менее
успешно закрыть свои загрузчики от посторонних глаз, и эти ухищрения создают определенные
сложности при переделке программ на диск.
Рассмотрим
простенькую программку, которая, будучи запущена, оставит от вполне связного
текста загрузчика нечто совершенно неудобоваримое:
LD HL,START ;адрес начала загрузчика
LD BC,LENGTH ;длина загрузчика
LOOP LD A,(HL) ;загрузка в А из памяти
XOR
176 ;изменение байта в А
LD (HL),А ;запись его обратно
INC HL ;увеличение указателя
DEC BC ;увеличение счетчика
LD А,В ;счетчик
OR С ; равен 0?
JR NZ,LOOP ;если нет, то на LOOP
Предположим,
что с адреса START у нас находится загрузчик длиной LENGTH байт. Если разобраться,
как работает приведенный фрагмент,
* Для
начала можно посоветовать книгу [1], где подробно описан пакет DEVPAC —
программы GENS4 и MONS4.
то очевидно, что
после его выполнения никакой дизассемблер не поможет нам прочитать содержимое
загрузчика до тех пор, пока мы не выполним этот фрагмент еще раз.
Полагаю,
что уже кое-что становится понятным. В частности, ясно, куда пропадают загрузчики
— да никуда, просто их авторы вставляют в них куски вроде приведенного. Когда
же программа стартует, она сначала «расксоривается», а потом запускается.
Возможно, уже стало понятно происхождение слов «ксорка», «заксорен», «расксоривается»
и т. п. Дело вот в чем: излюбленной командой для заксоривания является XOR,
так как ее повторное выполнение (с тем же параметром) приводит к тому, что
операнду, с которым она работает, возвращается прежнее значение (которое он
имел до первого выполнения XOR). Без этой команды, похоже, не обходится
ни одна более или менее приличная защита.
Как
же бороться с этим наваждением? Тут, пожалуй, можно дать, разве что, несколько
теоретических советов, не более, — фантазия у людей весьма богатая, а когда эти
люди еще и программисты...
Итак,
способ первый. Он наиболее прост, но часто оказывается самым эффективным. Для
его применения Вам нужно, естественно, хорошо разобраться с каким-нибудь монитором-отладчиком.
Из ранее упомянутых отладчиков наиболее удобным для хакерских дел я считаю
Mon2, a MONS4 более пригоден для отладки программ. Оба они имеют режимы
трассировки, и часто бывает достаточно найти входную точку загрузчика, точнее —
ксорки, и протрассировать ксорку от начала до конца. В результате Вы получите
вполне приличный листинг загрузчика и, полагаю, еще раз перечитаете предыдущие
главы этой брошюры. Не стоит бояться двух, пяти, ста вложенных ксорок (это не
шутка, защита типа Alcatraz protection, например, содержит их около трехсот!).
Если в ксорках не используются какие-нибудь специальные «извраты», то они могут
быть с легкостью раскручены.
Но,
вероятно, Вы уже догадываетесь, что не все так просто в этой жизни. Есть масса
ухищрений, которые мешают трассировать программы из отладчиков. Основные
попробую перечислить.
1.
Использование недокументированных команд. Как известно, в Z80 есть некоторое
количество команд, не описанных ни в каких фирменных справочниках. Даже если Вы
сами хорошо представляете, что эти команды означают, то отладчику от этого
легче не становится, и он либо «затыкается», либо игнорирует их, либо еще
что-нибудь придумывает, чтобы отмазаться.
2.
Использование регистра R, специального регистра регенерации динамической
памяти, содержимое которого увеличивается на единицу после выполнения каждой
команды (точнее, очередного цикла) микропроцессора. Обычно этот регистр
используется разве что для генерации случайных чисел, однако ловкие
программисты придумали, как его применить. Например, так:
LD HL,START
LD BC,LENGTH
XOR А ;обнуление
LD R,A ;регистра R
LOOP LD A,R ;получение очередного значения R
XOR (HL) ;заксоривание
LD (HL),А ;запись заксоренного значения
INC HL
DEC BC
LD А,В
OR С
JR NZ,LOOP
В
результате выполнения этого фрагмента в памяти образуется еще больший бардак,
чем после ксорки, описанной в начале главы, но при повторном его выполнении все
восстанавливается. Вам же мешает только одно: когда Вы трассируете этот участок
отладчиком, то выполнение одной команды отлаживаемой программы сопровождается
выполнением сотен или тысяч команд самого отладчика. Понятно, что в результате
живущий своею жизнью регистр регенерации памяти принимает совсем не то
значение, которое от него ожидали, и в памяти вместо загрузчика образуется
полный хаос. Должен заметить, что это — хроническая болезнь отладчиков: для
Speccy я не встречал еще ни одного, который бы правильно обращался с регистром
R.
3.
Использование второго режима прерываний. Этот очень экзотический способ защиты
от трассировки встречается крайне редко из-за чрезвычайной сложности его
реализации, так что я даже не буду на нем останавливаться.
Рано
или поздно Вы столкнетесь с загрузчиком (точнее, с ксоркой), который нельзя
расксорить, используя отладчик в пошаговом режиме. Что же можно предпринять?
Если быть до конца откровенным, то Вам может помочь только Ваша фантазия: пусть
она окажется богаче фантазии автора защиты. Однако для затравки попробую дать
несколько советов. Возьмем приведенную выше ксорку, несколько ее видоизменив:
LD HL,START
LD B,LENGTH
XOR А
LD
R,A
LOOP LD A,R
XOR (HL)
LD (HL),A
INC HL
DJNZ LOOP
JP OUTTHERE
Здесь
можно применить одно простое средство: поставить точку останова после команды
перехода к циклу (DJZN). В данном случае мы можем себе это позволить,
так как после команды перехода к циклу стоит команда JP. В принципе, на
этом месте может стоять любая осмысленная команда, которая сама не участвует в
ксорке и не заксорена.
Но посмотрите, что получится, если в нашем примере адреса будут расположены
следующим образом:
...
LD HL,START
LD B,LENGTH
XOR А
LD R,A
LOOP LD A,R
XOR (HL)
LD (HL),A
INC HL
DJNZ LOOP
START ...
Поставленная
по адресу START точка останова будет испорчена уже после выполнения
первых шагов ксорки, и, разумеется, ни в какой отладчик Вы уже не вернетесь. За
этим необходимо следить самым тщательным образом.
После
следующей фразы, возможно, меня назовут отступником, но я все же ее произнесу:
от многих сложностей Вам поможет избавиться... кнопка Magic. Это, конечно, не
значит, что я изменил своим принципам, это значит только, что кнопка Magic —
отличное средство бороться с ксорками.
Дело
в том, что когда Вы обычным образом запустите заксоренный загрузчик, он
расксорится в памяти и начнет выполнять свои прямые обязанности, то есть
загружать программу. Если в этот момент нажать кнопку Magic, то на диск
запишется файл, содержащий копию памяти.
В
нем, среди прочего мусора, Вы сможете отыскать расксоренный загрузчик.
Запустите программу Disk Doctor (или аналогичную) и потихоньку изучайте этот
файл, которым кнопка Magic загадила Ваш диск...
Тут,
ребята, я не могу сказать ничего более конкретного, кроме того, что Вы будете
иметь сорок восемь килобайт «нечта», из которого малопонятным путем надо будет
вытянуть загрузчик. По сути, нужно решить две задачи: первую — определить, где
загрузчик Находится (это довольно просто) и вторую — найти его входную точку.
Вот здесь могут возникнуть вопросы. Однако, если Вы тщательно проработали
ксорку до того, как нажали кнопку Magic, то количество вопросов наверняка
уменьшится.
Я не
полный идиот и отдаю себе отчет в том, что сказанное не может претендовать на
полноту, но поверьте, объяснять на бумаге то, что понимается не разумом, а
воспринимается интуитивно, крайне тяжело. Поэтому эта глава не содержит
конкретных советов, а, скорее, намекает на возможные решения.
Для
того, чтобы Вы получили более яркое (или хотя бы какое-нибудь) представление о
возможных вариантах ксорок и способах борьбы с ними, я попробую описать
несколько наиболее выдающихся из известных мне.
Защита типа Alcatraz protection
Это
самая навороченная защита, которую я встречал. Программы, закрытые ею
(например, Infiltrator, Rygar) не копируются стандартными копировщиками,
применен ускоренный формат записи на ленту и ряд других ухищрений, но нам
интересно не это, а то, какие в ней применены ксорки. А их там порядка 300,
причем попадаются как и самые идиотично простые, подобно приведенной в начале
главы, так и ксорки с использованием недокументированных команд, регистра регенерации,
стека и т. п. Самым же интересным является то, что сам загрузчик загружает
заксоренный файл, причем файл содержит новый загрузчик, который загружается
поверх старого, модифицируя при этом аргументы ксорки. И так три раза. В общем,
тяжело для раскрытия, хотя и не особенно трудно для понимания.
Снять
эту защиту честным путем мне не удалось, помогла только кнопка Magic, чего и
Вам желаю.
Да,
забыл сказать, что заставка в этой защите загружалась настолько красиво...
Защита типа Speedlock protection
Чрезвычайно
распространенная защита (встречалась, например, в Freddy Hardest, Tanx и в
десятках двух программ).
Полагаю, что примерно 70—80 процентов программ снабжались ею (речь,
разумеется, идет об оригинальных фирменных программах, облаченных в красивые
разноцветные упаковки и снабженных описанием, а не о
BillGilbert-S.S.Captain-JackO'Lantern-NicolasRodionov'вских взломах).
Защита,
как впрочем и все защиты, была предназначена для ограничения распространения программ,
то есть программы, закрытые ею, не копировались.
Мне
попадалось несколько модификаций Speedlock'a. Наиболее талантливым, на мой
взгляд, был первый вариант (разработан в 1984 году, автор, кажется, David
Aubrey Jones), о нем я расскажу подробнее. Остальные варианты были разработаны
уже другими авторами и вряд ли заслуживают внимания с точки зрения ксорок, хотя
внешне они работали довольно забавно.
Для
1984 года в Speedlock'е применялись просто гениальные штучки. Я весьма
благодарен ее автору, так как для меня эта защита была школой, пройдя которую,
я понял очень многое. К сожалению, вряд ли Вам удастся столкнуться со Speedlock'oM,
но если Вы захотите пройти школу «молодого бойца», то могу порекомендовать
повозиться с моей программкой Disk Control Utility. Начинать лучше с версий не
позже 2.12, в них использована ксорка, написанная мною году в 87, если не раньше,
а она лишь жалкая пародия на Speedlock. В версиях позже 2.12 ксорка написана в
1992 году, а это уже не для «молодых бойцов», лучше попрактиковаться на чем-нибудь
попроще.
Итак,
более подробно о Speedlock'e. Листинг бейсик-загрузчика выглядел весьма разумно
и понятно, а именно, примерно так:
О
Speedlock protection
После
первичной обработки можно было выудить около десятка строк с нулевым номером, в
каждой из которых было несколько операторов РОКЕ. Других операторов не
встречалось. Бейсик, разумеется, заканчивался мусором, то есть загрузчиком в
кодах.
Операторы
РОКЕ выполняли следующее: переустанавливали системную переменную ERR_SP
(адрес возврата по ошибке) так, что как только интерпретатор выполнял всю
разумную часть бейсик-программы и сталкивался с неразумной, то он тут же
вылетал, но не в процедуру обработки ошибки с целью написать сообщение Nonsense
in Basic, a на загрузчик в кодах, точнее, на раскрутку ксорки.
Раскрутка
ксорки в Speedlock'e для неподготовленного человека выглядит довольно забавно,
но и бессмысленно: байт пятьсот каких то пересылок между регистрами, сложений,
вычитаний, логических операций, манипулирования со стеком. Причем ни одного
оператора, претендующего на то, чтобы завершить цикл ксорки. После этих пятисот
байт непонятно чего следовало еще несколько байт непонятно чего. Эти несколько
байт выглядели примерно так:
LOOP LD A,R
XOR (HL)
LD (HL),A
LDI
DEC SP
DEC SP
RET PE
DEC SP
DEC SP
RET PO
и дальше какой-то
бред. Как выяснилось при ближайшем рассмотрении, это, собственно, и есть тело
ксорки, то есть цикла раскрутки. На первый взгляд трудно увидеть в этом цикл,
однако нужно принять во внимание содержимое стека в момент попадания на адрес LOOP.
На вершине стека был записан адрес LOOP (если быть более точным, то не
на вершине, а на два байта ниже), затем адрес следующей ксорки (которая раскручивалась
этой). Таким образом, после выполнения операций
DEC SP
DEC SP
счетчик стека
устанавливался на указатель на адрес LOOP, a
RET РЕ
выполнял возврат
по этому адресу в случае, если после выполнения
LDI
регистр ВС не
равен нулю, то есть необходимо продолжить цикл. Затем, после расксоривания,
указатель стека снова дважды уменьшается на единицу, устанавливаясь на LOOP,
а команда RET РЕ снова «возвращает» на LOOP. На всякий случай
напомню, что команда LDI делает следующее:
LD (DE),(HL) ;такой мнемоники в Z80 нет, но
;чтобы описать LDI
;ее использовать можно, так
;как делает она именно это
INC
HL
INС DE
DEC BC
Причем,
в зависимости от состояния ВС (равно или не равно нулю), соответствующим
образом устанавливается флаг процессора Parity, этим и объясняется
использование команд RET PO и RET РЕ.
Когда
же цикл завершается, то программа переходит ко второй группе операторов DEC
SP, выполняет их и оператором RET PO «возвращается» на следующую
расксорку.
Следующая
расксорка выглядит примерно также, затем следует еще парочка попроще.
Вот
такие дела.
___________________
Теперь
попробую придумать ксорку с использованием стека. Ну, например, так:
...
LD SP,END ; начало заксоренной области + 2
LOOP LD BC,LENGTH
LD A,R
POP HL ;фактически: LD H,(SP-1)
;LD L,(SP-2)
;DEC SP
;DEC SP
XOR A,H, ;последовательная ксорка Н и L
LD H,A
LD A,R
XOR L,A
PUSH HL ;фактически: LD (SP),L
;LD(SP+1),H
;...INC SP
;...INC SP
POP HL ;фактически: INC SP
;INC SP
DEC BC ;дальше ясно
DJNZ LOOP
...
Кстати,
не так плохо получилось. Дополнительные комментарии, на мой взгляд, излишни.
Можно несколько изменить эту ксорку:
...
LD SP,END ; начало заксоренной области + 2
LOOP LD BC,LENGTH
LD A,R
POP HL ; фактически: LD H,(SP-1)
; LD L,(SP-2)
; DEC SP
; DEC SP
XOR A,H ; ксорка Н
LD H,A
PUSH HL ; фактически: LD (SP),L
; LD(SP+1),H
; ...INC SP
; ...INC SP
INC SP увеличение счетчика стека на 1
DEC BC дальше ясно
DJNZ LOOP
...
Этот
вариант будет работать несколько медленнее (впрочем, это вряд ли имеет
принципиальное значение) и менее извращен: за каждый цикл ксорится один байт, а
в первом случае за каждый цикл ксорится два байта и используются разные
смещения регистра R.
__________________
Теперь
несколько слов об использовании недокументированных команд. Довольно полное их
описание можно найти в книге [ 1 ]. Я же остановлюсь на описании наиболее
распространенных из них, а именно командах, работающих с частями индексных
регистров IX и IY как с 8-разрядными регистрами общего назначения. На самом
деле все не так страшно, и, столкнувшись впервые с такими командами в Speedlock'e,
я понял, где собака зарыта, затратив совсем немного умственных усилий.
Как
известно, доступ к регистрам IX и IY осуществляется теми же кодами операций,
что и к регистру HL, за одним лишь исключением — команды работы с регистрами IX
и IY префиксируются байтами 221(DDh) и 253(FDh) соответственно. И если в
фирменных руководствах есть описания только таких команд как LD IY,0, то
это еще не значит, что нет команды LD IYh,0*.
Документированную
команду LD IY,0 можно представить следующим образом:
DEFB 253 ; префикс регистра IY
LD HL,0
По
аналогии с командой LD IY,0 недокументированную команду LD IYh,0 можно записать
в таком виде:
DEFB 253
LD Н,0
Ассемблеры
(например, GENS4), как и отладчики, недокументированные команды не понимают, но
используя в них приведенную запись команды LD IYh,0, можно внедрять
недокументированные команды в программы.___________________________________________________________
* Так
как запись команды LD I,0 неоднозначна, части индексных регистров обозначаются
IXh и IX1 (старший и младший байты регистра IX) и IYh, IYl (старший и младший
байты регистра IY).
___________________
Ну вот пока
и все. Мне остается только надеяться, что изложенное в этой брошюре будет тем
толчком, который приведет Вас к истинным высотам; что очень быстро, адаптировав
сотню-другую программ, Вы приобретете бесценный опыт, и начнете сами писать
оригинальные программы и придумывать к ним красивые защиты.