Od środka

Ostatnio poznaliśmy sposób, w jaki przechowywana jest w pamięci każda linia BASIC-a. oraz co oznaczają takie znaki kontrolne, jak CHR$ 14 i CHR$ 8. W tym odcinku poznamy pozostałe znaki kontrolne. Trzy z nich dotyczą zmiany miejsca wydruku, sześć — zmiany atrybutów..

Pierwszym z nich jest CHR$ 6 — „COMMA CONTROL”, czyli przecinek kontrolny. Ma on takie samo działanie, jak przecinek separujący teksty w instrukcji PRINT, tzn. drukuje tyle spacji (ale zawsze co najmniej jedną), by znaleźć się w kolumnie 0 lub 16:

PRINT „1”, „2”

oraz PRINT „1 „ + CHR$ 6+”2”

mają identyczne znaczenie.

Podobnie, znak CHR$ 22 — „AT CTRL”, czyli AT kontrolne, pozwala przenosić pozycję wydruku w dowolne miejsce ekranu tak, jak AT w instrukcji PRINT. Po tym znaku muszą wystąpić dwa bajty, określające numer linii i numer kolumny, w której ma zostać umieszczony następny znak:

PRINT AT 10,7;”!” j

est równoznaczne z

PRINT CHR$ 22:CHRS 10;CHR$ 7;”!”.

Aby się przekonać, jak za pomocą tego znaku uniemożliwić prawidłowy listing programu, wpisz

np.:

 

 

10 RANDOMIZE USR 30000:  REM ---Nic nie widac!(6)

 

 

— po instrukcji REM wpisz trzy spacje, a po wykrzykniku — dwa przecinki kontrolne. Można je uzyskać bezpośrednio z klawiatury, wciskając kolejno klawisze: EXTEND (lub dwa SHIFT-y na raz) by uzyskać kursor „E”, a następnie klawisz „6” (kursor zmieni kolor na żółty) i DELETE — kursor przeskoczy do najbliższej połówki ekranu.

Po wpisaniu tej linii, wymieniamy te trzy spacje na znak AT 0,0 przez:

POKE 23774,22;POKE 23775,0: POKE 23776,0.

Spróbujmy teraz wylistować program. Na ekranie nie pojawia się tekst całej linii — początkowa jej część jest zakrywana przez napis znajdujący się za instrukcją REM i znakiem AT CTRL. Podobne kłopoty są gdy ściągniemy tę linię do edycji (klawisz EDIT).

Podane współrzędne w znaku AT CTRL powinny mieścić się na ekranie, tzn. numer linii nie może być większy niż 21, a numer kolumny — niż 31. Podanie większych wartości w przypadku PRINT lub LIST powoduje komunikat „Out of screen” i zaprzestanie dalszego drukowania. Jeśli zaś listing uzyskano przez wciśnięcie ENTER (automatyczny listing) — także nastąpi zakończenie listowania programu, a ponadto na dole ekranu znajdzie się migający znak zapytania — sygnał błędu. Jest to więc praktyczny sposób na uniemożliwienie wylistowania każdego programu.

Kolejnym znakiem kontrolnym jest CHR$23 — „TAB CTRL” — znak tabulacji poziomej. Po nim następują dwa bajty określające numer kolumny, do której ma zostać przeniesiona pozycja wydruku. Są one traktowane jako jedna, dwubajtowa liczba (pierwszy bajt mniej znaczący). Ponieważ są jednak tylko 32 kolumny, to jest ona brana modulo 32, czyli jej starszy bajt i trzy najstarsze bity młodszego bajtu są ignorowane. Drugą istotną sprawą jest to, że TAB przenosi pozycję wydruku przez drukowanie spacji — podobnie jak przecinek kontrolny, może być więc użyty do zakrywania znajdujących się już na ekranie tekstów.

Następną grupę znaków kontrolnych stanowią znaki zmieniające atrybuty. Są to:

 

CHR$ 16 — INK CTRL

CHR$ 17 — PAPER CTRL

CHR$ 18 — FLASH CTRL

CHR$ 19 — BRIGHT CTRL

CHR$ 20 — INVERSE CTRL

i CHR$ 21 — OVER CTRL.

 

Po każdym z tych znaków konieczny jest jeden bajt, precyzujący o jaki atrybut chodzi. Po znakach INK i PAPER mogą to być liczby 0 ... 9, po FLASH i BRIGHT: 0,1 i 8, po INVERSE i OVER: 0 i 1. Podanie innych wartości wywołuje komunikat „lnvalid colour” i oczywiście przerwanie listowania programu

Odśmiecając program zabezpieczony znakami kolorów kontrolnych ustalamy sobie np., że jeśli przeglądając treść programu napotkamy kod znaku PAPER CTRL, to wpisujemy w jego drugim bajcie wartość 0, jeśli INK CTRL — wartość 7, a w pozostałe znaki kontrolne kolorów — wartość 0. Poza tym usuwamy wszystkie przeszkadzające znaki BACKSPACE (CHR$ 8) przez zastąpienie ich spacjami (CHR$ 32). Podobnie likwidujemy znaki AT CTRL — wymieniamy za pomocą POKE-ów trzy bajty znaku na spacje. Po takiej korekcie program daje się już wylistować bez żadnych niespodzianek.

Jeśli chcesz się włamać do programu ładującego, nie musisz, a w zasadzie nie powinieneś go odśmiecać — ważne jest przecież to, aby dowiedzieć się co ten program robi, w jaki sposób ładuje do pamięci i uruchamia następne bloki, a nie aby robił to „ładnie” i był napisany czysto i przejrzyście. Jest to ważne tym bardziej, że zanim nie poznasz dokładnie programu, lepiej nie rób w nim żadnych zmian — jedno zabezpieczenie może być sprawdzane przez inne. Dlatego najlepszym sposobem złamania programu jest analiza jego działania krok po kroku, odczytując kolejne bajty pamięci:

 

0 BORDER 0: PAPER 0: INK 0: CLS: PRINT #0,"LOADING";: FOR n=0 TO 20 STEP 4: BEEP .2,n: NEXT n: LOAD ""CODE: PRINT AT 19,0;: LOAD ""CDDE: PRINT AT 19,0;: LOAD ""CODE: PRINT AT 19,0;: LOAD ""CODE: PRINT AT 19,0;: LOAD ""CODE: RANDOMI ZE USR 24064

Pamiętaj o właściwej interpretacji kolejnych bajtów: najpierw dwa bajty numeru linii, potem dwa bajty oznaczające jej długość (które mogą być fałszywe), a następnie tekst: instrukcja BASIC-a, potem jej parametry, za każdą liczbą CHR$ 14 i pięć bajtów zawierających wartość tej liczby. Za parametrami — dwukropek i następna instrukcja lub ENTER i nowa linia programu.

To by było na tyle, jeśli chodzi o znaki kontrolne. Jest jednak jeszcze jedna rzecz, którą trzeba wyjaśnić, abyś nie miał kłopotów z odczytywaniem BASIC-a. Chodzi o instrukcję DEF FN. Wpisz, a następnie obejrzyj dokładnie taką linię:

10 DEF FN a(a,b$,c)=a + c

Wydaje się, że powinna ona zająć w pamięci 19 bajtów (numer linii, jej długość, ENTER oraz 14 wpisanych znaków), ale tak nie jest. Interpreter po każdym parametrze funkcji umieścił znak CHR$ 14 i zarezerwował na coś następne pięć bajtów. Na co? — zaraz się przekonasz. Wpisz:

PRINT FN a (1 ,”123”,2)

i ponownie dokładnie obejrzyj zawartość pamięci od adresu 23755. Po pierwszym parametrze w definicji funkcji dalej znajduje się CHR$ 14, ale po nim znalazły się kolejno: 0, 0, 1, 0, 0 co w pięciobajto wym zapisie oznacza liczbę 1. Podobnie, po trzecim parametrze funkcji znajduje się CHR$ 14 i bajty oznaczające liczbę 2. Po parametrze b$ także znalazła się wartość użytego parametru: CHR$ 14 i pięć bajtów, które kolejno oznaczają: pierwszy nie jest dla nas ważny, drugi i trzeci to adres, pod którym znajdował się łańcuch „123” (wywołanie funkcji nastąpiło w trybie bezpośrednim, więc adres ten dotyczy obszaru edycji linii BASIC-a), a bajty czwarty i piąty to długość łańcucha — w naszym przypadku wynosi ona 3 znaki.

Pamiętaj o tym odczytując BASIC przez PEEK a nie LIST. Zdarza się czasem, że w tych właśnie bajtach zarezerwowanych na rzeczywiste wartości funkcji ukryte są zabezpieczenia warunkujące działanie programu lub nawet program maszynowy ładujący następne bloki (np. Beta, Basic 1.0).

Na zakończenie parę słów o programach ładujących — tzw. loaderach. Ich zadaniem jest wczytanie i uruchomienie wszystkich bloków składających się na program. Zazwyczaj robią to w sposób maksymalnie utrudniający zrozumienie ich działania — tak, aby uruchomienie programu w inny sposób niż przez tego ładowacza (czyli w praktyce — włamanie się do niego) było niemożliwe. Robią to w mniej lub bardziej wyrafinowany sposób. Spójrzmy na ładowacze stosowane w większości produktów firmy ULTIMATE (np. Atic Atac, Knightlore, Pentagram, Night Shade itp.). Wyglądają one mniej więcej tak:

 

FOR n=23755 TO PEEK 23627+256*PEEK 23628: PRINT n;" ";PEEK n,CHR$ PEEK n AND PEEK n>31: NEXT n

 

Za takim ładowaczem znajduje się na taśmie pięć następnych bloków: ekran oraz zakodowany główny blok programu, a za nimi trzy króciutkie bloki zabezpieczające program: jednobajtowy (kod instrukcji JP (HL) ), kilkunastobajtowy (jest to procedura odkodowująca cały program), oraz ostatni — dwubajtowy, ładowany pod adres 23672, czyli do zmiennej FRAMES. Zawartość tej zmiennej jest zwiększona o 1 co 1/50 sekundy. W programie maszynowym uruchomionym przez RANDOMIZE USR 24064 wartość ta jest sprawdzana i jeśli jest inna, niż być powinna (co oznacza, że w międzyczasie, już po załadowaniu, program był zatrzymany na jakiś czas), następuje wyzerowanie komputera. Włamanie się do tego typu programów jest proste. Wystarczy załadować wszystkie bloki za wyjątkiem ostatniego, a po wylistowaniu programu, czy zrobieniu w nim odpowiednich zmian (np. wpisaniu POKE-ów unieśmiertelniających grę) wystarczy tylko wpisać LOAD „” CODE: RANDOMIZE USR 24064 (ale koniecznie w jednej linii, rozdzielając instrukcje dwukropkiem), by uruchomić grę.

Oddzielną sprawa jest odkodowanie programu, czyli uruchomienie procedurki odkodowującej w taki sposób, by po zakończeniu działania wróciła do BASIC-a. Czytelnikom znającym asembler nie powinno to sprawić kłopotu, jednak ze względu na powszechność stosowania tego typu zabezpieczeń, szczególnie w programach użytkowych (np. Art Studio czy The Last Word), wrócimy jescze do tego tematu.

A za miesiąc zakończymy sprawę programów ładujących, tzn. ich BASIC-owej części i już na dobre zajmiemy się asemblerem.

 

Tomasz Surmacz, Robert Dudzik