Fluffy adventures in the macigal world of IT. Also, discounted graphic designer course available.

Zombiföldről - visszafejtve

2012-05-13

Egy ismerősömtől kaptam egy levelet, hogy segítsek egy BME-VIK gólyatábor feladat megoldásában, mivel korábban többször foglalkoztam Commodore 64 programozással, és érdekelt, hogy a VIK-es kollégák milyen programot írtak C64-re, ezért teljesen visszafejtettem a kiadott programot.

A történet szerint zombik ellen kell harcolnunk, és szükségrogünk van egy titkos kódra. A program a képernyő felső sorába egy scroller van, amiben a küldetés feladatait sorolja, alsó sorban pedig egy sprite, amin a titkos kód található, amit a keret eltakar. Ránézésre egyszerű kis programocska, de nézzünk bele, mi is van benne pontosan, és milyen megoldásokat alkalmaztak.

Forrás: http://golya.sch.bme.hu/rejtvenyek_zombifoldrol_2____commodore Már nem elérhető az eredeti helyen.

Kezdő lépések

Akkor lássunk is neki, ezúttal modern eszközök felhasználásával. A boncoláshoz a Vice monitorját használom fel, illetve az ICU64 nevű progit, ami láthatóvá teszi valós időben a gép memóriáját.

Először illene tudni, hogy a program a memória melyik területén helyezkedik majd el, a betöltést követően. Ezt nem nehéz megmondani. Minden C64 futtatható bináris első két byteja a kezdőcímet tartalmazza, ahova betölti a lemezről Kernal (nem kernel ) a kódot. Mivel programunk a RUN parancsra le is fut, ezért ennek a címnek a basic terület elejére kell hogy mutasson, ami a $0800 = 2048 (továbbiakban $xxxx formátumban írom le) címtől kezdődik. Ha ezen a címen $00 van, akkor a Kernal az ezt követő kódot basic kódként fogja kezelni, tehát a fileunk a $0801 címtől kell hogy betöltődjön, de azért nézzünk bele egy HEX editorral:

1
0000: 01 08

így is van. A fileunkból ez a két byte “kiesik” így a 2048 byteos hossz 2046-ra ($03FE) csökken. A kezdőcímből és a hosszból tudjuk, hogy a kódunk $0801 és $0BFF között lesz (A továbbiakban a listázást mégis $0800-ról kezdem majd)

Minden program, amit RUN paranccsal szeretnénk futtatni, annak a basic tárterület elejétől a basic interpreter által értelmezhető basic kódnak kell lennie. Még akkor is, ha a basicben egyetlen SYS parancs található, amivel a megadott címtől kezdődő gépi kód hajtódik végre. Ebből következik, hogy a LIST parancs kiadására, látni kellene valamilyen basic kódot.


Hopp, nem így történt. VIK-es kollégáink valamilyen listázás elleni védelmet alkalmaztak, vagy azért hogy jobban nézzen ki a lista, vagy azért, hogy elrejtsék a gépi kód kezdőcímét. Akkor nézzünk bele a BASIC ram területbe, mi található ott.

1
2
3
4
>C:0800 00 1e 08 00 00 9e 32 34 ......24
>C:0808 37 34 3a 8f 22 8d 91 2d 74:."..-
>C:0810 42 4d 45 2d 56 49 4b 20 BME-VIK
>C:0818 32 30 31 31 2d 00 00 00 2011-...

Nos, lássuk hogy is kell mindezt értelmezni, ezúttal $0800-tól:

1
2
3
4
5
6
7
8
9
10
0800: 00 Basic fejléc
0801: 1e 08 Következo sor kezdocíme (link)
0803: 00 00 Sorszám (16 biten)
0805: 9e Basic token
0806: 32 34 37 34 Basic utasítás argumentumai (ascii-ba)
080a: 3a A basic utasításokat elválasztó kettospont
080b: 8f Következo basic token
080c: 22 8d 91 ... Argumentumok
081d: 00 Sor vége
081e: 00 00 Következo sor kezdocíme

Vegyük szépen sorba. A sorok egy láncolt listához hasonló (de nem teljesen az, mert köztes sor beszúrásakor átrendezi) struktúrában foglalnak helyet; illetve csak listázáskor használja az interpreter. A linket követően a sorszám, majd a sor tartalma. Minden sor végét egy nulla jelöl. Minden basic utasítást rövidít le.

Ezek alapján a basic sorunk valahogy így fest:


Itt látható már, hogy a basic kódba a megjegyzés utasítást (REM) követő három karakter felel a “védelemért”, az inverz M és Q két vezérlőkarakter (CBM karaktertábla szerint). Ezek a vezérlő karakterek ún. idézőjel-módba karakterláncként építhetők be a kódba, későbbi felhasználás (print) végett. Ha ebből a módból sikerül “megszökni”, akkor a list parancs kírás közbe “végrehajtja” ezeket. Az inverz M sortörést produkál, az inverz Q pedig egy kurzor fel, így a -BME-VIK-2011- karaktersor felülírja listázáskor az előzőleg kiírt 0 sys2474:REM" részt.

Ezután már a kezdőcímet követve végig kell menni egy disassemberrel a memóraképen. Vice-ban van beépített monitor, ami képes mindenféle mellékhatás nélkül a memóriába látni.

Teljes disassembly

Végül itt a teljes disassemblelt kód:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
; BME-VIK gólyafeladat
; FULL DISASSEMBLY
; BASIC LOADER
>C:0800 00 1e 08 00 00 9e 32 34 ......24
>C:0808 37 34 3a 8f 22 8d 91 2d 74:."..-
>C:0810 42 4d 45 2d 56 49 4b 20 BME-VIK
>C:0818 32 30 31 31 2d 00 00 00 2011-...
; entry point (2474 = $09aa)
; MAIN
.C:09aa 20 63 0B JSR $0B63 ; call subroutine 1
.C:09ad 20 FD 0A JSR $0AFD ; call subroutine 2
.C:09b0 60 RTS ; return (to basic)
; IRQ routine
.C:09b1 20 C2 09 JSR $09C2 ; call scroller subroutine
.C:09b4 EE 00 D0 INC $D000 ; increase sprite #0 X coord (#bit 0-7)
.C:09b7 D0 03 BNE $09BC ; branch while overflow occurs via INC
.C:09b9 EE 10 D0 INC $D010 ; increase sprite #bit 8
.C:09bc 0E 19 D0 ASL $D019 ; acknowledge raster IRQ
.C:09bf 4C 31 EA JMP $EA31 ; jump back to kernal
; scroller
.C:09c2 8D F5 09 STA $09F5 ; push X, Y regs and A to local vars (@ line $09f4)
.C:09c5 8E F7 09 STX $09F7
.C:09c8 8C F9 09 STY $09F9
.C:09cb AE FB 09 LDX $09FB ; inc local var 1 @09FB - delay
.C:09ce E8 INX
.C:09cf 8E FB 09 STX $09FB
.C:09d2 E0 08 CPX #$08
.C:09d4 D0 1E BNE $09F4 ; if (X != 8) branch
.C:09d6 A2 00 LDX #$00
.C:09d8 8E FB 09 STX $09FB ; local var 1
.C:09db A0 00 LDY #$00
.C:09dd AE FC 09 LDX $09FC ; local var 2 - text pointer
.C:09e0 BD FD 09 LDA $09FD,X ; text data
.C:09e3 E8 INX
.C:09e4 99 00 04 STA $0400,Y ; copy to screen
.C:09e7 A9 01 LDA #$01 ; fill color memory
.C:09e9 99 00 D8 STA $D800,Y
.C:09ec C8 INY
.C:09ed C0 28 CPY #$28
.C:09ef D0 EF BNE $09E0 ; loop while (y != 40)
.C:09f1 EE FC 09 INC $09FC ; inc text pointer
.C:09f4 A9 00 LDA #$00 ; pop X, Y regs and A from local vars (set @ line $09c2)
.C:09f6 A2 00 LDX #$00
.C:09f8 A0 00 LDY #$00
.C:09fa 60 RTS ; return
; local vars ($09fb-$09fc) + scroller text
>C:09fb 00 00 20 20 20 20 20 20 .. ; " "
>C:0a03 20 20 20 20 20 20 20 20 ; " "
>C:0a0b 20 20 20 20 20 20 20 20 ; " "
>C:0a13 20 20 20 20 20 20 20 20 ; " "
>C:0a1b 20 20 20 20 20 20 20 20 ; " "
>C:0a23 20 20 01 0e 14 09 2d 1a ....-. ; " ANTI-Z"
>C:0a2b 0f 0d 02 09 05 20 02 15 ..... .. ; "OMBIE BU"
>C:0a33 0e 0b 05 12 20 03 0f 0d .... ... ; "NKER COM"
>C:0a3b 0d 01 0e 04 20 01 13 13 .... ... ; "MAND ASS"
>C:0a43 09 07 0e 0d 05 0e 14 3a .......: ; "IGNMENT:"
>C:0a4b 20 20 20 31 2e 20 05 12 1. .. ; " 1. ER"
>C:0a53 01 04 09 03 01 14 05 20 ....... ; "DANCIATE"
>C:0a5b 01 0e 19 20 01 0e 04 20 ... ... ; "ANY AND "
>C:0a63 01 0c 0c 20 08 0f 13 14 ... .... ; "ALL HOST"
>C:0a6b 09 0c 05 20 06 0f 12 03 ... .... ; "ILE FORC"
>C:0a73 05 13 2e 20 20 32 2e 20 ... 2. ; "ES. 2. "
>C:0a7b 12 05 03 0f 16 05 12 20 ....... ; "RECOVER "
>C:0a83 01 0c 0c 20 01 16 01 09 ... .... ; "ALL AVAI"
>C:0a8b 0c 01 02 0c 05 20 13 15 ..... .. ; "LABLE SU"
>C:0a93 10 10 0c 09 05 13 2e 20 ....... ; "PPLIES. "
>C:0a9b 20 20 33 2e 20 03 0f 0d 3. ... ; " 3. COM"
>C:0aa3 10 0c 05 14 05 20 13 05 ..... .. ; "PLETE SE"
>C:0aab 03 12 05 14 20 0d 09 13 .... ... ; "CRET MIS"
>C:0ab3 13 09 0f 0e 2e 20 20 34 ..... 4 ; "SION. 4"
>C:0abb 2e 20 13 14 01 19 20 01 . .... . ; ". STAY A"
>C:0ac3 0c 09 16 05 2c 20 12 05 ...., .. ; "LIVE, RE"
>C:0acb 10 0f 12 14 20 02 01 03 .... ... ; "PORT BAC"
>C:0ad3 0b 20 14 0f 20 02 01 13 . .. ... ; "K TO BAS"
>C:0adb 05 20 01 0e 04 20 04 0f . ... .. ; "E AND DO"
>C:0ae3 0e 27 14 20 07 05 14 20 .'. ... ; "N'T GET "
>C:0aeb 08 15 12 14 21 20 20 20 ....! ; "HURT! "
>C:0af3 07 0f 0f 04 20 0c 15 03 .... ... ; "GOOD LUC"
>C:0afb 0b 21 .! ; "K!"
; SUBROUTINE 2
; interruption setup
.C:0afd 78 SEI ; set interrupt bit
.C:0afe A9 7F LDA #$7F ; Set interrupt controls
.C:0b00 8D 0D DC STA $DC0D ; Set IRQ
.C:0b03 8D 0D DD STA $DD0D ; Set NMI (WTF)
.C:0b06 AD 1A D0 LDA $D01A ; Enable raster interruption
.C:0b09 09 01 ORA #$01 ; bitmask 000001
.C:0b0b 8D 1A D0 STA $D01A
.C:0b0e AD 11 D0 LDA $D011 ; Rasterline to generate interruption
.C:0b11 29 7F AND #$7F ; bitmask %01111111
.C:0b13 8D 11 D0 STA $D011
.C:0b16 A9 01 LDA #$01 ; Set the interruption on raster line $01
.C:0b18 8D 12 D0 STA $D012
.C:0b1b A9 B1 LDA #$B1 ; Set IRQ vector (lo)
.C:0b1d 8D 14 03 STA $0314
.C:0b20 A9 09 LDA #$09 ; (hi)
.C:0b22 8D 15 03 STA $0315 ; IRQ at $09B1
.C:0b25 58 CLI ; clear interrupt bit
.C:0b26 60 RTS ; return
; SUBROUTINE 3 - memcpy
.C:0b27 8C 44 0B STY $0B44 ; set comparison val @ line $0b43 to Y reg.
.C:0b2a A0 00 LDY #$00 ; clear Y
.C:0b2c E0 00 CPX #$00
.C:0b2e F0 0E BEQ $0B3E ; if (X == 0) branch
.C:0b30 B1 FB LDA ($FB),Y ; copy ($FB)+y to ($FD)+y
.C:0b32 91 FD STA ($FD),Y
.C:0b34 C8 INY ; inc Y
.C:0b35 D0 F9 BNE $0B30 ; loop
.C:0b37 E6 FE INC $FE ; inc $FE (hi byte of src)
.C:0b39 E6 FC INC $FC ; inc $FC (hi byte of dest)
.C:0b3b CA DEX ; dec X
.C:0b3c D0 F2 BNE $0B30 ; loop while (X != 0)
.C:0b3e B1 FB LDA ($FB),Y ; copy ($FB)+y to ($FD)+y
.C:0b40 91 FD STA ($FD),Y
.C:0b42 C8 INY
.C:0b43 C0 00 CPY #$00 ; compare (were set @ line $0b27)
.C:0b45 D0 F7 BNE $0B3E ; loop
.C:0b47 60 RTS ; return
; SUBROUTINE 4 - memset
.C:0b48 8C 5F 0B STY $0B5F ; set cmp. val @ line $0b5e
.C:0b4b A0 00 LDY #$00
.C:0b4d E0 00 CPX #$00
.C:0b4f F0 0A BEQ $0B5B ; if (X == 0) branch
.C:0b51 91 FD STA ($FD),Y ; set ($FD)+y to ACC
.C:0b53 C8 INY
.C:0b54 D0 FB BNE $0B51 ; loop
.C:0b56 E6 FE INC $FE ; inc $FE (hi byte of dst)
.C:0b58 CA DEX
.C:0b59 D0 F6 BNE $0B51 ; loop
.C:0b5b 91 FD STA ($FD),Y
.C:0b5d C8 INY
.C:0b5e C0 00 CPY #$00
.C:0b60 D0 F9 BNE $0B5B ; loop
.C:0b62 60 RTS ; return
; SUBROUTINE 1
.C:0b63 A9 01 LDA #$01 ; set sprite #0 X-coord #bit 8
.C:0b65 8D 10 D0 STA $D010
.C:0b68 A9 00 LDA #$00
.C:0b6a 8D 17 D0 STA $D017 ; reset sprite double height bits
.C:0b6d 8D 1B D0 STA $D01B ; reset sprite priority bits
.C:0b70 8D 1C D0 STA $D01C ; reset sprite multicolor bits
.C:0b73 8D 1D D0 STA $D01D ; reset sprite double bits
.C:0b76 A9 60 LDA #$60 ; set sprite #0 X-coord
.C:0b78 8D 00 D0 STA $D000
.C:0b7b A9 EE LDA #$EE ; set sprite #0 Y-coord
.C:0b7d 8D 01 D0 STA $D001
.C:0b80 A9 01 LDA #$01 ; set sprite #0 color
.C:0b82 8D 27 D0 STA $D027
.C:0b85 A9 FF LDA #$FF ; set sprite data pointer
.C:0b87 8D F8 07 STA $07F8
.C:0b8a A5 FB LDA $FB ; push zero page to stack
.C:0b8c 48 PHA
.C:0b8d A5 FC LDA $FC
.C:0b8f 48 PHA
.C:0b90 A5 FD LDA $FD
.C:0b92 48 PHA
.C:0b93 A5 FE LDA $FE
.C:0b95 48 PHA
.C:0b96 A9 BF LDA #$BF ; copy source (lo)
.C:0b98 85 FB STA $FB
.C:0b9a A9 0B LDA #$0B ; copy source (hi) - sprite data $0bbf
.C:0b9c 85 FC STA $FC
.C:0b9e A9 C0 LDA #$C0 ; copy dest. (lo)
.C:0ba0 85 FD STA $FD
.C:0ba2 A9 3F LDA #$3F ; copy dest. (hi) - sprite data $3fc0
.C:0ba4 85 FE STA $FE
.C:0ba6 A4 3F LDY $3F ; legth (lo)
.C:0ba8 A6 40 LDX $40 ; legth (hi)
.C:0baa 20 27 0B JSR $0B27 ; call subroutine 3
.C:0bad 68 PLA ; pop zero page from stack
.C:0bae 85 FE STA $FE
.C:0bb0 68 PLA
.C:0bb1 85 FD STA $FD
.C:0bb3 68 PLA
.C:0bb4 85 FC STA $FC
.C:0bb6 68 PLA
.C:0bb7 85 FB STA $FB
.C:0bb9 A9 01 LDA #$01 ; Enable sprite #0
.C:0bbb 8D 15 D0 STA $D015
.C:0bbe 60 RTS ; return
; sprite data
>C:0bbf 70 00 04 d0 00 04 80 00 p.......
>C:0bc7 26 e5 76 54 37 45 74 64 &.vT7Etd
>C:0bcf 64 44 c2 30 22 10 01 00 dD.0"...
>C:0bd7 10 01 00 7c 07 c0 38 03 ...|..8.
>C:0bdf 80 10 01 00 00 00 00 e1 ........
>C:0be7 22 e2 93 36 b5 92 be 95 "..6....
>C:0bef e2 aa b5 a2 a2 e5 a7 a2 ........
>C:0bf7 b5 94 a2 95 94 a2 f2 00 ........

Varázslat

A feladat kiírásában szerepelt az is, hogy lehet írni egy aprócska basic programot a basic indító és a gépikód közé, amivel varázslatos dolgokat lehet művelni az alsó sorban kavaró sprite-tal:

1
2
3
4
5
1a$="78:200;=17109=<;09>8>005=0?5:9078=?;09584<4>104<1:10:032<<12=0=0?;:00988="
2b$="0?=:=16=0:>?;098>16=0:03:<<12=0=0?;:00:88=0?=8=16=0<:8>?;09>000=005:2074<"
3c$="=809410":k=4096
4d$=a$+b$+c$:fOi=0to105:a=16*(aS(mI(d$,1+2*i,1))-48)+(aS(mI(d$,2*i+2,1))-48)
5pOi+k,a:nE:sYk