Les défis se suivent mais ne se ressemblent pas ! On retrouve pourtant ici dans le 5ème (et hélas dernier) épisode de la saga QTiPS in the Computer World, un nouveau challenge concocté par le duo infernal QTiPS et Captain Crack mais sous la forme plus classique cette fois de l'image Hires à signer.
Vu que c'est un Fast Boot, que l'image s'affiche juste après celle de présentation, on va bien évidemment Boot Tracer pour localiser les routines intéressantes. Comme ce n'est pas le premier défi ni le premier Boot Trace qu'on réalise ensemble, je vais donc accélérer un peu les choses en allant directement aux adresses essentielles, mais pas d'inquiétude, vous aurez tout de même droit à ma verve intarissable... la preuve !
C'est parti pour un Boot Trace on ne peut plus classique :
9600<C600.C700M 96F8: 4C 59 FF 9600G 801L ... On repère tout de suite le JMP $B600 en $887 On modifie alors notre Boot 0 relogé pour l'intercepter : 96F8 : 4C 00 97 9700 : A9 59 8D 88 08 A9 FF 8D 89 08 4C 01 08 9600G B600L On remarque cette fois le JMP $69D1 en $B60E On remodifie une nouvelle fois notre Boot : 9700 : A9 10 8D 88 08 A9 97 8D 89 08 4C 01 08 9710 : A9 59 8D 0F B6 A9 FF 8D 10 B6 4C 00 B6 9600G |
Nous arrivons ici aux premières choses intéressantes :
69D1L 69D1- A0 00 LDY #$00 69D3- B9 00 60 LDA $6000,Y 69D6- 99 00 10 STA $1000,Y 69D9- C8 INY 69DA- D0 F7 BNE $69D3 69DC- EE D5 69 INC $69D5 69DF- EE D8 69 INC $69D8 69E2- AD D5 69 LDA $69D5 69E5- C9 6A CMP #$6A 69E7- D0 E8 BNE $69D1 69E9- 20 00 6A JSR $6A00 ; 69EC- AD 50 C0 LDA $C050 ; mode GFX 69EF- AD 52 C0 LDA $C052 ; plein écran 69F2- AD 54 C0 LDA $C054 ; page 1 69F5- AD 57 C0 LDA $C057 ; Hires 69F8- 20 00 10 JSR $1000 ; 69FB- 4C 1A B6 JMP $B61A ; |
Le début de la routine ne concerne que du déplacement de données, on s'en fout un peu. Par contre plus intéressant le JSR $1000 suivi du JMP $B61A. Tout ce qui est en B6xx concerne probablement la RWTS du Fast Boot. On va donc modifier ce JMP $B61A par un break ($00) ou un JMP $FF69 pour reprendre la main et voir à quel moment on arrive. Et on lance le tout par 69D1G !
On voit l'image de présentation et la musique d'intro se fait entendre. Si on appuie sur une touche, on récupère la main. Comme on a remarqué (car bien entendu, vous aurez, de vous-même, commencé par l'indispensable phase "observation" dont je parle souvent) qu'entre l'image d'intro et l'affichage de celle du défi, s'effectuait un chargement (probablement de l'image du défi en elle-même), on en déduit que le JMP $B61A doit s'occuper du chargement de notre défi.
Voyons voir ça :
*B61AL B61A- 20 71 BA JSR $BA71 B61D- A9 FF LDA #$FF B61F- EA NOP B620- 10 FB BPL $B61D B622- 8D 10 C0 STA $C010 B625- A0 02 LDY #$02 B627- D9 E0 BB CMP $BBE0,Y B62A- F0 05 BEQ $B631 B62C- 88 DEY B62D- 10 F8 BPL $B627 B62F- 30 EC BMI $B61D B631- AD E9 C0 LDA $C0E9 ; drive on ! B634- B9 00 BB LDA $BB00,Y B637- 8D 4F BA STA $BA4F B63A- B9 20 BB LDA $BB20,Y B63D- 8D 4B BA STA $BA4B B640- B9 40 BB LDA $BB40,Y B643- 8D 4C BA STA $BA4C B646- B9 60 BB LDA $BB60,Y B649- 8D 4D BA STA $BA4D B64C- B9 80 BB LDA $BB80,Y B64F- 8D 4E BA STA $BA4E B652- B9 A0 BB LDA $BBA0,Y B655- 8D 6B B6 STA $B66B ; modification du JMP $FF69 B658- B9 C0 BB LDA $BBC0,Y B65B- 8D 6C B6 STA $B66C ; modification du JMP $FF69 B65E- EA NOP B65F- EA NOP B660- EA NOP B661- 20 6D B6 JSR $B66D B664- 8D E8 C0 STA $C0E8 ; drive off B667- EA NOP B668- EA NOP B669- EA NOP B66A- 4C 69 FF JMP $FF69 ; modifié plus haut |
On voit des NOP un peu partout et un début de routine un peu chaotique. Pourquoi ? Recyclage (c'était déjà à la mode) ! Et oui QTiPS et Captain Crack ont sans doute utilisé un Fast Boot prévu avec un menu de sélection (pour lancer différents programmes). Ici ils ont shunté à l'arrache toute cette partie pour ne garder que ce qui les intéresse eux : le lancement de la suite du DISK. Mais cela tombe bien car ils nous ont offert de la place. Pourquoi voudrait-on de la place ? Et bien tout simplement pour modifier le disk directement au DiskFixer par exemple et poser un JMP $FF69 qui nous rendra la main. Ceci permet d'éviter de devoir tout se retaper pour arriver jusque là (notamment toute la partie Boot Trace). Et de pouvoir revenir directement à un point essentiel du défi même si l'on fait une fausse manip ou si on plante la bécane (et oui ça arrive...).
On recherche donc au DISKFiXER la suite HEXA : EA EA EA 4C 59 FF que l'on va trouver Track $00/Sector $0E/Byte $67. Et on modifie les 3 EA par un joli 4C 59 FF.
On peut donc ainsi directement lancer le Disk et il nous rend la main au même point que tout à l'heure sans avoir à refaire toute la partie laborieuse de Boot Tracing. Hey les gars, on n'est pas à l'usine ! On a le droit de prendre son temps pour faire un défi. Si vous croyez que je fais tout d'une traite, vous rêvez ! La modification directe d'un Disk permet de retrouver ses petits même après une nuit de sommeil ! Évidemment pour ceux qui travaillent sous émulateur, il y a encore plus pratique avec la sauvegarde de l'état de la bécane à un instant T. Mais pour la résolution d'un défi, je préfère rester dans les conditions les plus proches de la "réalité". Donc on ne touche pas aux petits plus que peut apporter l'émulateur et encore moins à la partie Debugger par exemple d'AppleWin (là ça serait à mon sens carrément tricher car on aurait accès à des fonctionnalités non disponibles à l'époque). On travaille à l'ancienne sinon cela n'a aucun sens. Ha, on me signale dans l'oreillette que de résoudre des défis vieux de 25 ans cela n'en a déjà pas beaucoup... C'est sans doute vrai mais quitte à faire les choses, autant les faire correctement et jusqu'au bout ! Donc on continue :
On relance notre QTiPS 5 (modifié). On assiste à la présentation avec la musique, on appuie sur une touche et ... on récupère la main en mode TEXT ! On peut déjà en profiter pour voir ce qui se passe en Page Hires : on tape donc directement : C050 (mode GFX), C057 (Hires) et C055 (page 2) , la page d'intro étant toujours en page 1 (C054). Rien n'a encore été chargé visiblement. On repasse en texte par Co51 et C054 (pour la page 1 si on était en 2). Et on vérifie ce qu'on a maintenant en $B66A (rappelez-vous on avait un JMP $FF69 évidemment bidon tout à l'heure).
Surprise ! On a maintenant un joli JMP $6000 en $B66A. Allons voir ça :
*6000L 6000- A0 0D LDY #$0D 6002- B9 00 60 LDA $6000,Y 6005- 49 A0 EOR #$A0 ; "décryptage" (Page $60) 6007- 99 00 60 STA $6000,Y 600A- C8 INY 600B- D0 F5 BNE $6002 600D- 0D 49 60 ORA $6049 6010- 00 BRK 6011- A0 19 LDY #$19 6013- 78 SEI 6014- C0 2D CPY #$2D 6016- EB ??? 6017- 1A ??? 6018- 19 48 C0 ORA $C048,Y 601B- 2D EC 1A AND $1AEC 601E- 09 A0 ORA #$A0 6020- 2D ED 1A AND $1AED 6023- 0D E9 C0 ORA $C0E9 6026- 4A LSR 6027- 2D EE 1A AND $1AEE |
Pas la peine d'aller plus loin, on voit que les premières lignes décryptent la suite qui s'exécute immédiatement derrière la routine de décryptage. Problème : comment récupérer la main ici après le décryptage mais avant l'exécution de la suite ?
Facile, on va ruser. On va faire en sorte que le premier octet de la suite décryptée soit à $00 (soit l'instruction BREAK). Pour ce faire, on remplace donc le $0D en 600D par un $A0. Explications : lors du décryptage sera effectué pour cet octet (en $600D donc) : $A0 EOR $A0 soit $00 ! Et on garde dans un coin de notre tête que $0D EOR $A0 (le décryptage normal) aurait dû générer un $AD en $600D ! Je vous préviens d'avance, si les EOR vous filent le migraine (ou la gerbe), vous pouvez déjà laisser tomber car on va en bouffer tout du long...
*600D : A0 ; on met un $A0 pour générer un $00 lors du décryptage (EOR) *6000G ; on exécute (donc on décrypte). Arrivé au BREAK on récupère la main *600D : AD ; on remet la valeur qui aurait dû apparaître lors du décryptage *600DL ; on peut voir la suite (et l'exécuter normalement) 600D- AD E9 C0 LDA $C0E9 ; drive on ! 6010- A0 00 LDY #$00 6012- B9 D8 60 LDA $60D8,Y ; table pistes $13/$14 6015- 8D 4B BA STA $BA4B ; 6018- B9 E8 60 LDA $60E8,Y ; table secteurs. Attention ! 601B- 8D 4C BA STA $BA4C 601E- A9 00 LDA #$00 ; 6020- 8D 4D BA STA $BA4D 6023- AD 49 60 LDA $6049 ; $40 6026- EA NOP 6027- 8D 4E BA STA $BA4E 602A- A9 02 LDA #$02 ; 2 secteurs 602C- 8D 4F BA STA $BA4F 602F- 8C 48 60 STY $6048 6032- 20 6D B6 JSR $B66D ; lecture ! 6035- AD 49 60 LDA $6049 6038- 18 CLC 6039- 69 02 ADC #$02 603B- 8D 49 60 STA $6049 603E- AC 48 60 LDY $6048 6041- C8 INY 6042- C0 10 CPY #$10 6044- D0 CC BNE $6012 6046- F0 02 BEQ $604A 6048- 00 BRK 6049- 40 RTI 604A- EA NOP 604B- A0 00 LDY #$00 604D- 98 TYA 604E- 99 00 60 STA $6000,Y ; nettoyage ?! 6051- C8 INY 6052- C0 4A CPY #$4A 6054- D0 F7 BNE $604D 6056- A9 10 LDA #$10 6058- 99 22 60 STA $6022,Y ; soit 4A+6022 = $606C 605B- 99 1B 60 STA $601B,Y ; soit 4A+601B = $6065 605E- A9 60 LDA #$60 6060- 48 PHA ; on empile $60 6061- A9 71 LDA #$71 6063- 48 PHA ; on empile $71 6064- AD 00 40 LDA $4000 ; devient LDA $4010 6067- 49 3F EOR #$3F (0) 6069- 48 PHA ; on empile le résultat 606A- C8 INY 606B- AD 00 40 LDA $4000 ; devient LDA $4010 606E- 49 E4 EOR #$E4 (0) 6070- 48 PHA ; on empile le résultat 6071- 60 RTS ; le RTS qui va dépiler les deux valeurs précédentes ! |
La première partie de la routine va lire l'image du défi ! Elle se trouve à partir de la piste $13, secteur $00 et va se charger en $4000+ (logique puisqu'elle sera Page 2). On voit que la routine lit 2 secteurs à la fois et ce $10 fois consécutives (soit $20, on a bien notre image complète).
Sauf que la lecture ne va pas être séquentielle (comme tout FastBoot qui se respecte) pour nous compliquer un peu la tâche. En effet la table en $60E8 renvoie une organisation quelque peu alambiquée (je commence déjà à détester QTiPS et Captain Crack...). Pour recopier notre image signée sur le DISK en lieu et place de l'originale, il faudra respecter cet agencement.
Suite de la routine : après un peu de nettoyage, elle va modifier deux adresses pour générer deux LDA $4010. Là encore il va falloir s'y faire. Dans tout le défi, le code se modifie par petites touches (généralement en transformant des adresses). Il faudra donc bien faire attention et ne pas s'arrêter à une première lecture.
On reprend : le contenu de $4010 va être EORé (désolé pour ce néologisme franglotechnicogeek) pour générer deux valeurs qui seront ensuite empilées. Et dépilées dans la foulée quand la routine va atteindre le RTS. On a donc une sorte de JMP camouflé qui utilise la pile. Je rappelle pour info qu'en $4000+ (Hires/Page2) se trouvera notre image du défi (encore cryptée) qui aura été chargée. C'est à dire que la routine va utiliser un octet de l'image (contenu de $4010) pour permettre au programme de continuer ! Extrêmement malin car toute modification de l'image entraînera une modification (après cryptage) de l'octet en $4010 et donc de l'adresse générée... Beau plantage en perspective si on ne modifie pas les EOR en $6067 et 606E pour recalculer les bonnes adresses.
Pour le moment ce qui nous intéresse, c'est de faire en sorte que l'image cryptée soit chargée et de récupérer la main juste après, donc avant d'arriver en $604A :
On remplace par exemple le BEQ par un BREAK en $6046 ($6046 : 00). Et on relance par 600DG. Le drive s'allume, charge l'image et on récupère la main. Note : Le drive reste allumé. Si on est curieux, on peut aller voir ce qu'il y a en page 2 Hires ($C050/C055) : c'est bien à priori une image cryptée qui s'y trouve ! Après plusieurs défis de ce type, on reconnaît une image cryptée quand même non ? ! On en profite pour tout de suite regarder le contenu $4010, soit $5E. On peut donc calculer les deux octets qui seront empilés :
$5E EOR $3F = $61
$5E EOR $E4 = $BA
Donc une fois atteint le RTS, il y aura dépilement de $BA puis de $61, et le registre PC (qui contient l'instruction à exécuter) deviendra $61BA+1 soit $61BB (le +1, c'est l'instruction RTS elle-même qui l'applique). Notre RTS équivaut donc à une JMP $61BB. Mais qu'avons-nous en $61BB ?
*61BBL 61BB- A1 A1 LDA ($A1,X) 61BD- A1 A1 LDA ($A1,X) 61BF- A1 A1 LDA ($A1,X) 61C1- A1 A1 LDA ($A1,X) 61C3- A1 A1 LDA ($A1,X) 61C5- A1 A1 LDA ($A1,X) 61C7- A1 A1 LDA ($A1,X) 61C9- A1 A1 LDA ($A1,X) 61CB- A1 A1 LDA ($A1,X) 61CD- A1 A1 LDA ($A1,X) 61CF- A1 A1 LDA ($A1,X) 61D1- A1 A1 LDA ($A1,X) 61D3- A1 A1 LDA ($A1,X) 61D5- A1 A1 LDA ($A1,X) 61D7- A1 A1 LDA ($A1,X) 61D9- A1 A1 LDA ($A1,X) 61DB- A1 A1 LDA ($A1,X) 61DD- A1 A1 LDA ($A1,X) 61DF- A1 A1 LDA ($A1,X) 61E1- A1 A1 LDA ($A1,X) 61E3- A1 A1 LDA ($A1,X) 61E5- ED 44 C0 SBC $C044 61E8- A1 A1 LDA ($A1,X) 61EA- A1 A1 LDA ($A1,X) 61EC- A1 A1 LDA ($A1,X) 61EE- A1 A1 LDA ($A1,X) 61F0- A0 EF LDY #$EF ; on aurait dû atterrir ici ! 61F2- B9 00 61 LDA $6100,Y ; pour décrypter 61F5- 49 A1 EOR #$A1 ; par un EOR 61F7- 99 00 61 STA $6100,Y ; ce qu'il y a en $6100+ 61FA- 88 DEY 61FB- D0 F5 BNE $61F2 61FD- 4C 00 61 JMP $6100 ; et pouvoir y sauter après ! |
Scoop : je pense qu'ici on est face à un gros bug dans le défi car il est évident qu'en $61BB, il n'y a rien d'intéressant et d'utile. En fait il était sûrement prévu que le RTS (grâce à la pile) saute en $61F0. On dirait bien que QTiPS (ou Captain Crack) a fait une petit erreur pour la valeur du EOR avant d'empiler l'adresse. Mais par chance, le code depuis $61BB jusqu'à $61F0 étant totalement inoffensif, on arrive quand même à bon port et le reste peut s'exécuter normalement ! Alors Bug ou Feature (pour brouiller les pistes) ? La question est posée. Quant à nous, on se souvient juste qu'on doit arriver en $61F0 (sans bug) ou $61BB (avec).
Sinon la petite routine en elle-même décrypte une portion de code et y saute. Une fois décrypté (il suffit de remplacer le JMP $6100 par un $00 et d'exécuter : $61F0G), on obtient en $6100 :
*6100L 6100- A2 00 LDX #$00 6102- 86 08 STX $08 6104- 86 09 STX $09 6106- 86 0A STX $0A 6108- 86 06 STX $06 610A- A9 40 LDA #$40 610C- 85 07 STA $07 610E- A0 00 LDY #$00 6110- 84 0B STY $0B 6112- B1 06 LDA ($06),Y ; $4000-$5FFF 6114- 18 CLC 6115- 65 08 ADC $08 6117- 45 0B EOR $0B 6119- 90 07 BCC $6122 611B- 18 CLC 611C- E6 09 INC $09 611E- D0 02 BNE $6122 6120- E6 0A INC $0A 6122- 85 08 STA $08 6124- C8 INY 6125- D0 E9 BNE $6110 6127- E6 07 INC $07 6129- A5 07 LDA $07 612B- C9 60 CMP #$60 612D- D0 DF BNE $610E 612F- 60 RTS ; JMP caché ! |
On a ici une sorte de checksum qui va générer à partir des données entre $4000 et $5FFF (donc l'image du défi qui est toujours cryptée je vous le rappelle) trois valeurs (contenues en $08, $09 et $0A). Et cette routine se termine par un nouveau RTS...
Vous vous souvenez que tout à l'heure, la routine avait empilé #$60 et #$71 avant d'empiler les deux valeurs EORé (bah oui, il faut aussi regarder les commentaires que je mets dans le code). Ces dernières ayant été dépilées (pour le fameux JMP en $61BB), ce RTS ci va dépiler à son tour les deux autres et sauter à l'adresse obtenue ! Soit $6071+1 = $6072. Et on continue donc :
*6072L 6072- 2C E8 C0 BIT $C0E8 ; tiens le drive s'arrête enfin ! 6075- A5 08 LDA $08 6077- 38 SEC 6078- 65 09 ADC $09 607A- 49 4C EOR #$4C ; valeur du checksum (1) $4C 607C- D0 13 BNE $6091 ; m'a tout l'air d'un BAD GUY ! 607E- 18 CLC 607F- 69 30 ADC #$30 ; 6081- 8D 8C 60 STA $608C 6084- A5 0A LDA $0A 6086- 49 70 EOR #$70 ; (1) $70 6088- 8D 8D 60 STA $608D 608B- 20 00 60 JSR $6000 ; modifié juste au-dessus en $6130! 608E- 4C F0 62 JMP $62F0 ; suite (et oui ce n'est pas fini !) 6091- A9 15 LDA #$15 ; on prépare le chargement 6093- 8D 4B BA STA $BA4B 6096- A9 00 LDA #$00 6098- 8D 4C BA STA $BA4C 609B- 8D 4D BA STA $BA4D 609E- A9 40 LDA #$40 60A0- 8D 4E BA STA $BA4E 60A3- A9 20 LDA #$20 60A5- 8D 4F BA STA $BA4F 60A8- 2C E9 C0 BIT $C0E9 60AB- 20 6D B6 JSR $B66D ; lecture ! 60AE- 2C E8 C0 BIT $C0E8 60B1- AD 50 C0 LDA $C050 60B4- AD 52 C0 LDA $C052 60B7- AD 57 C0 LDA $C057 60BA- AD 55 C0 LDA $C055 60BD- 4C B1 60 JMP $60B1 ; et on boucle sur l'affichage |
Ici on a la petite routine qui va utiliser le "checksum" précédent. Les contenus de $08 et $09 sont ajoutés (+1 pour la retenue) et tout ce petit monde doit donner #$4C sinon on saute en $6091 et c'est fini pour nous ! Mais on utilise encore le résultat pour modifier le JSR $6000 bidon avec l'aide du troisième larron c'est à dire le contenu de $0A ! Il faudra donc modifier aussi cette partie (c'est à dire la valeur des EOR en $607A et $6086) pour obtenir à partir de notre image cryptée à nous les bonnes valeurs. Quelles sont-elles d'ailleurs ces bonnes valeurs ? Bah pour remplacer le #$4C, il faudra attendre d'avoir utilisé le checksum sur notre propre image. Par contre, on peut déjà savoir quelle devra être l'adresse du JSR à obtenir !
Comment ?
Et bien on va exécuter le checksum (en $6100) :
6100G (il va nous générer les bonnes valeurs pour $08,$09 et $0A). On n'a même pas besoin de s'occuper de récupérer la main puisque le RTS va nous la rendre. N'oubliez pas que là, on a totalement shunté le déroulement normal du défi. Voyons voir ce qu'on obtient pour nos valeurs checksum :
$08 : $61 / $09 : $EA / $0A : $11
Ok à partir de là, on peut donc tout de suite savoir ce qu'est censé devenir le JSR $6000 en $608B.
A) ($08) + ($09) + 1 = $61 + $EA + 1 = $14C. Comme on ne s'occupe que de l'octet de poids faible, on obtient bien $4C !
Et $4C EOR $4C = $00 ! Forcément... On y ajoute maintenant le $30 et on obtient : $30 (logique) !
B) ($0A) EOR $70 = $11 EOR $70 = $61 .
On sait donc que le JSR $6000 en $608B sera transformé en JSR $6130. Il ne reste plus qu'à voir ce qu'on a en $6130 :
*6130L 6130- A2 00 LDX #$00 6132- 86 06 STX $06 6134- A9 40 LDA #$40 6136- 85 07 STA $07 6138- A0 00 LDY #$00 613A- B1 06 LDA ($06),Y 613C- 5D 5B 61 EOR $615B,X 613F- 91 06 STA ($06),Y 6141- C8 INY 6142- D0 F6 BNE $613A 6144- A0 00 LDY #$00 6146- B1 06 LDA ($06),Y 6148- C8 INY 6149- 51 06 EOR ($06),Y 614B- 88 DEY 614C- 91 06 STA ($06),Y 614E- C8 INY 614F- D0 F5 BNE $6146 6151- E8 INX 6152- E6 07 INC $07 6154- A5 07 LDA $07 6156- C9 60 CMP #$60 6158- D0 DE BNE $6138 615A- 60 RTS 615B- C3 ??? 615C- C1 D0 CMP ($D0,X) 615E- D4 ??? 615F- C1 C9 CMP ($C9,X) 6161- CE A0 C3 DEC $C3A0 6164- D2 ??? 6165- C1 C3 CMP ($C3,X) 6167- CB ??? 6168- A0 C6 LDY #$C6 616A- D2 ??? 616B- CF ??? 616C- CD A0 C3 CMP $C3A0 616F- D2 ??? 6170- C1 C3 CMP ($C3,X) 6172- CB ??? 6173- A0 CD LDY #$CD 6175- CF ??? 6176- CE D3 D4 DEC $D4D3 6179- C5 D2 CMP $D2 617B- E6 ??? 617C- 00 BRK |
Cette routine n'est rien de plus que la routine de décryptage de l'image mes amis ! Enfin !
Le décodage n'est pas très complexe. On a un double EOR pour chaque adresse mémoire de l'image :
- d'abords un EOR avec une valeur issue d'une table. Cette valeur change tous les $100 octets (donc à chaque page mémoire). On a donc une table de 20 valeurs qui commence en $615B. On pourra pour le cryptage réutiliser cette même table vu qu'un EOR est réversible.
- ensuite est effectué un classique EOR entre une adresse mémoire et la suivante. Une façon de lier les valeurs mémoire entre elles. En modifier une, entraînera forcément la modification de toutes les autres qui suivront.
Note : Dans l'optique de la résolution du défi, il serait de bon ton, arrivé à ce stade, d'exécuter la routine pour récupérer ainsi une image décryptée qui servira de base pour notre signature.
On reprend le suivi du code : une fois la routine de décryptage exécutée (par le JSR $6130), on saute en $62F0 :
*62F0L 62F0- A0 70 LDY #$70 62F2- B9 00 62 LDA $6200,Y 62F5- 49 A2 EOR #$A2 ; (décryptage page $62) 62F7- 99 00 62 STA $6200,Y 62FA- 88 DEY 62FB- D0 F5 BNE $62F2 62FD- 4C 00 62 JMP $6200 |
Nouveau décryptage d'une portion de code ! Et oui car contrairement à la plupart des défis de l'époque, une fois l'image affichée, ce n'est pas encore fini... Les auteurs (fourbes) ont ajouté des vérifications également sur l'image décryptée :
*6200L 6200- A0 00 LDY #$00 6202- A9 00 LDA #$00 6204- 99 00 60 STA $6000,Y ; encore un peu de nettoyage... 6207- C8 INY 6208- D0 F8 BNE $6202 620A- EE 06 62 INC $6206 620D- AD 06 62 LDA $6206 6210- C9 62 CMP #$62 6212- D0 EC BNE $6200 6214- F0 04 BEQ $621A 6216- 50 52 BVC $626A 6218- 57 ??? 6219- 55 ??? 621A- A2 00 LDX #$00 ; nouveau checksum 621C- 86 08 STX $08 621E- 86 09 STX $09 6220- 86 0A STX $0A 6222- 86 06 STX $06 6224- A9 40 LDA #$40 6226- 85 07 STA $07 6228- A0 00 LDY #$00 622A- 84 0B STY $0B 622C- B1 06 LDA ($06),Y ; $4000+ 622E- 18 CLC 622F- 65 08 ADC $08 6231- 45 0B EOR $0B 6233- 90 07 BCC $623C 6235- 18 CLC 6236- E6 09 INC $09 6238- D0 02 BNE $623C 623A- E6 0A INC $0A 623C- 85 08 STA $08 623E- C8 INY 623F- D0 E9 BNE $622A 6241- E6 07 INC $07 6243- A5 07 LDA $07 6245- C9 60 CMP #$60 6247- D0 DF BNE $6228 6249- A5 09 LDA $09 624B- 38 SEC 624C- 65 08 ADC $08 624E- 45 0A EOR $0A 6250- 49 D8 EOR #$D8 ; (2) $D8 6252- 8D 61 62 STA $6261 ; 6255- 8D 5E 62 STA $625E ; 6258- A0 00 LDY #$00 625A- B9 00 43 LDA $4300,Y 625D- 59 00 63 EOR $6300,Y ; modifié en $6380 (3) $6380-$63FF 6260- 99 00 63 STA $6300,Y ; modifié en $6380 6263- C8 INY 6264- C0 80 CPY #$80 6266- D0 F2 BNE $625A 6268- 6C 5E 62 JMP ($625E) ; = JMP $6380 |
Même principe que tout à l'heure, la routine génère 3 valeurs (en $08, $09 et $0A) qui vont servir à modifier les adresses utilisées par le EOR en 625D. EOR qui dans le même temps utilise une partie de l'image pour décoder du code ! Évidemment, si l'image est modifiée, le code le sera aussi. Il faudra en tenir compte ! On finit enfin par sauter en $6380 :
*6380L 6380- A0 A0 LDY #$A0 6382- B9 60 4F LDA $4F60,Y ; encore une vérif d'une portion de l'image 6385- D9 00 63 CMP $6300,Y ; qui doit correspondre avec 6388- D0 13 BNE $639D ; ce qui est en $63A0-$63FF ! (4) $63A0-$63FF 638A- C8 INY 638B- D0 F5 BNE $6382 638D- A0 7F LDY #$7F 638F- B9 00 63 LDA $6300,Y ; nouveau décryptage ($6300-$637F) 6392- 49 A3 EOR #$A3 ; (5 *) 6394- 99 00 63 STA $6300,Y ; de code 6397- 88 DEY 6398- D0 F5 BNE $638F 639A- 4C 30 63 JMP $6330 ; et saut vers lui ! 639D- 4C 90 62 JMP $6290 , BAD GUY ! |
On a une nouvelle vérification ici d'une partie de l'image en $4F60+$A0=$5000+. Là encore, il faudra prévoir de modifier les données pour qu'elles correspondent avec notre image signée. Sortie de routine par un saut en $6330 :
*6330L 6330- A0 62 LDY #$62 6332- A9 00 LDA #$00 6334- 99 00 63 STA $6300,Y ; nouveau nettoyage par le vide 6337- C8 INY 6338- EA NOP 6339- EA NOP 633A- D0 F6 BNE $6332 633C- A0 00 LDY #$00 633E- 98 TYA 633F- 99 00 62 STA $6200,Y ; idem 6342- C8 INY 6343- D0 F9 BNE $633E 6345- A0 89 LDY #$89 6347- A9 00 LDA #$00 6349- 99 00 BA STA $BA00,Y 634C- C8 INY 634D- C0 91 CPY #$91 634F- D0 F6 BNE $6347 6351- 20 71 BA JSR $BA71 ; 6354- A0 30 LDY #$30 6356- 98 TYA 6357- 99 00 63 STA $6300,Y ; on nettoie encore ! 635A- C8 INY 635B- C0 50 CPY #$50 635D- D0 F7 BNE $6356 635F- 4C 00 63 JMP $6300 ; saut en $6300 |
Une petite routine intermédiaire qui fait du nettoyage (décidément) et prépare la suite. On en sort en sautant en $6300 :
*6300L 6300- AD 50 C0 LDA $C050 6303- AD 52 C0 LDA $C052 6306- AD 54 C0 LDA $C054 6309- AD 57 C0 LDA $C057 630C- 2C 10 C0 BIT $C010 630F- AD 00 C0 LDA $C000 6312- EA NOP 6313- EA NOP 6314- A0 02 LDY #$02 6316- B9 2D 63 LDA $632D,Y 6319- 99 1D B6 STA $B61D,Y ; met LDA $C000 en $B61D 631C- 88 DEY 631D- 10 F7 BPL $6316 631F- A9 55 LDA #$55 ; $C054 devient $C055 en $B614 6321- 8D 15 B6 STA $B615 ; ce qui prépare l'affichage page 2... 6324- 20 58 FC JSR $FC58 6327- 2C 10 C0 BIT $C010 632A- 4C 11 B6 JMP $B611 ; suite ! 632D- AD 00 C0 LDA $C000 |
Toute cette partie modifie la routine de FastBoot vue plus haut pour la préparer pour la fin du défi en lui-même (affichage/test clavier) et ensuite enchaîner sur la suite du loading du disk ! Une fois que tout est prêt, on y saute (vers $B611) :
*B611L B611- 8D 52 C0 STA $C052 B614- 8D 55 C0 STA $C055 ; page 2 ! L'image s'affiche enfin B617- 8D 57 C0 STA $C057 B61A- 20 71 BA JSR $BA71 B61D- AD 00 C0 LDA $C000 ; appuie sur une touche ? B620- 10 FB BPL $B61D ; non on boucle B622- 8D 10 C0 STA $C010 ; oui, on réinitialise clavier B625- A0 02 LDY #$02 B627- D9 E0 BB CMP $BBE0,Y ; touche ESPACE ? B62A- F0 05 BEQ $B631 B62C- 88 DEY B62D- 10 F8 BPL $B627 B62F- 30 EC BMI $B61D B631- AD E9 C0 LDA $C0E9 ; on continue et on charge la suite... B634- B9 00 BB LDA $BB00,Y B637- 8D 4F BA STA $BA4F B63A- B9 20 BB LDA $BB20,Y B63D- 8D 4B BA STA $BA4B B640- B9 40 BB LDA $BB40,Y B643- 8D 4C BA STA $BA4C B646- B9 60 BB LDA $BB60,Y B649- 8D 4D BA STA $BA4D B64C- B9 80 BB LDA $BB80,Y B64F- 8D 4E BA STA $BA4E B652- B9 A0 BB LDA $BBA0,Y B655- 8D 6B B6 STA $B66B B658- B9 C0 BB LDA $BBC0,Y B65B- 8D 6C B6 STA $B66C |
On s'aperçoit que ce que l'on prenait pour une routine un peu chaotique plus haut, c'est à dire du FastBoot Multi-Programmes modifié à l'arrache, était en fait parfaitement maîtrisé. On repasse sur la même routine, modifiée en cours de route pour d'une part afficher notre image défi (Hires page 2 au lieu de page 1) et effectuer le test clavier (qui avait été supprimé pour le premier passage). Excellent travail ! Et mes plus plates excuses aux auteurs...
Voilà, la partie analyse est terminée. À nous de jouer maintenant :
Première chose à faire : programmer la routine de cryptage qui fera exactement l'inverse (forcément) de celle de décryptage. Voici le code et je me suis bien évidemment inspiré de la routine originale :
1 ORG $1000 2 3 LDA #$00 4 STA $06 5 LDA #$5F 6 STA $07 7 LDX #$20 8 9 BP LDY #$FF 10 B1 LDA ($06),Y 11 INY 12 EOR ($06),Y 13 DEY 14 STA ($06),Y 15 DEY 16 CPY #$FF 17 BNE B1 18 19 20 LDY #$00 21 B2 LDA ($06),Y 22 EOR TABLE,X 23 STA ($06),Y 24 INY 25 BNE B2 26 27 DEX 28 DEC $07 29 LDA $07 30 CMP #$3F 31 BNE BP 32 RTS 33 34 TABLE HEX C3,C1,D0,D4,C1,C9,CE,A0,C3,D2,C1,C3,CB,A0,C6,D2 35 HEX CF,CD,A0,C3,D2,C1,C3,CB,A0,CD,CF,CE,D3,D4,C5,D2 |
Pas grand chose à en dire, on va évidemment dans le sens inverse (on part de $5FFF pour arriver à $4000) et on effectue d'abord le EOR Y+1 avant le EOR avec la table (qui est exactement la même que pour le décodage). Attention ce code n'est pas relogeable (à cause de la table justement).
Maintenant, il va falloir jongler avec les checksums et leurs résultats. On ne va pas agir directement sur la routine des checksums. On les laisse faire leur boulot et générer à chaque fois les trois valeurs clés (contenus de $08,$09,$0A). Par contre, il faudra modifier les valeurs des EOR afin d'obtenir le résultat attendu.
Les plus perspicaces auront noté que j'ai laissé au fur et à mesure de mes commentaires de code des notes numérotées (*) que l'on reprend ici et qui concernent explicitement les parties à modifier pour contrer les checksums :
Les checks pré-décryptage à modifier :
(0) : Pas véritablement un checksum mais juste une vérification de l'adresse $4010 et deux EOR faits à partir de son contenu pour générer le vrai/faux JMP en $61BB par l'intermédiaire du couple (maudit) RTS/Pile.
(1) : $607A : EOR #$4C. Il faudra modifier le $4C pour obtenir $00 à la suite de cet EOR.
(1) : $6086 : EOR #$70. Il faudra modifier le $70 pour obtenir $61 à la suite de cet EOR.
Les checks post-décryptage :
(2) : $6250 : EOR #$D8. Il faudra modifier le $D8 pour obtenir $80 à la suite du EOR.
(3)/(4)/(5) : Toute la page $63 ($6300-$63FF) sera à modifier et à recopier sur disque. Elle est cryptée ainsi :
- de $6300 à $637F : EOR avec #$A3
- de $6380 à $63FF : EOR avec ce qui est en $4300+
(avec de $63A0 à $63FF : copie directe de ce qui est en $5000+)
Bon maintenant concrètement que fait-on ?
- Première chose : signer l'image décryptée que l'on a récupérée précédemment. J'ai utilisé pour ce faire Blazzing Paddle et avec un joystick tremblotant, croyez-moi l'exploit n'est pas mince même si la signature est moche...
- On encrypte notre image à l'aide du code fourni (programme ENCODE sur la disquette de travail).
- On écrit l'image sur notre copie de QTIP 5 modifiée (ajout du 4C 59 FF en T$00/S$0E/B$67 voir en début de session). Où ? Et bien on se souvient qu'au tout début, on avait repéré qu'elle était sauvegardée à partir de la piste $13/ secteur $00 jusqu'en piste $14, secteur $0F. Sauf que, comme déjà signalé plus haut, la sauvegarde n'étant pas séquentielle, l'écriture ne devra pas l'être non plus... On va utiliser le programme DIRECT (lui aussi dispo sur le working disk...yeah man in english !) en se basant sur la table ci-dessous (attention ça va être long et chiant à faire...). On écrit à chaque fois $200 octets (2 secteurs), sens ascendant :
- Piste $13 / Secteur $00 / Buffer $4000
- Piste $13 / Secteur $0E / Buffer $4200
- Piste $13 / Secteur $02 / Buffer $4400
- Piste $13 / Secteur $0C / Buffer $4600
- Piste $13 / Secteur $04 / Buffer $4800
- Piste $13 / Secteur $0A / Buffer $4A00
- Piste $13 / Secteur $06 / Buffer $4C00
- Piste $13 / Secteur $08 / Buffer $4E00
- Piste $14 / Secteur $0E / Buffer $5000
- Piste $14 / Secteur $00 / Buffer $5200
- Piste $14 / Secteur $0C / Buffer $5400
- Piste $14 / Secteur $02 / Buffer $5600
- Piste $14 / Secteur $0A / Buffer $5800
- Piste $14 / Secteur $04 / Buffer $5A00
- Piste $14 / Secteur $08 / Buffer $5C00
- Piste $14 / Secteur $06 / Buffer $5E00
- Ouf oui c'était bien pénible à faire j'en ai conscience. Les plus balaises écriront un programme pour faire ça auto mais perso, j'ai évalué que ma fainéantise serait largement gagnante en utilisant 16 fois de suite DIRECT plutôt que d'en développer une version spéciale...
- On prend notre disque et on le boot pour récupérer la main au moment où il jumpait en $6000 après le chargement de l'intro (grâce à la modification effectuée). L'idée c'est de suivre la progression du programme pour repérer les bonnes valeurs à modifier et contrer, dans un premier temps, les checksums pré-décryptage. Attention, attachez vos ceintures, prenez un Aspro, une profonde inspiration et c'est parti :
- Check (0) : on a vu qu'il fallait à la suite du RTS en $6071 arriver en $61BB (finalement, même si c'est un bug, on le laisse tel quel, l'objectif des défis étant de toucher au minimum au code et au déroulement du programme original).
Sur mon image signée et recryptée, en $4010 j'ai $5C. Il suffit donc de faire $5C EOR $BA = $E6 et $5C EOR $61 = $3D. On va donc devoir remplacer en $6067, le EOR #$3F par un EOR #$3D et en $606E le EOR #$E4 par un $EOR #$E6. - Pour trouver l'emplacement, on recherche la suite : E9 9F E8 68 qui correspond au code en $6067 avant décryptage.
Après une petite recherche avec DISKFIXER, on remplace donc T$12/S$00/B$68 : $9F par $9D (=$3D EOR $A0) et T$12/S$00/B$6F : $44 par $46 (=$E6 EOR $A0). Pourquoi les EOR $A0 avant l'écriture sur DISK ? On n'oublie pas que chaque page $6x du défi a son propre cryptage. En page $60 c'est par un EOR $A0. Il faut donc faire l'opération inverse (en l’occurrence la même...) pour nos valeurs à écrire. - Check (1) : on trace jusqu'à arriver à l'exécution du EOR en $607A. On peut mettre un BREAK dessus une fois qu'il a été décrypté.
- Arrivé à ce niveau, on récupère les valeurs générées par la routine checksum, soit le contenu de $08 (pour moi $44), $09 ($0A) et $0A ($11). Notre EOR devra donc être : $44+$0A+1 = $4F ! Et pour mettre le bon octet sur DISK , on fait $4F EOR $A0 (toujours le cryptage page $60) soit : $EF. Sur le DISK de QTiPS, on cherche donc avec DISKFIXER la suite E9 EC 70 B3 et une fois trouvée (en T$12/S$00/B$7A) on remplace le $EC par $EF. On doit également trouver la valeur qui EORée (notez l'accord !) avec le contenu de $0A (soit $11) donnera #$61. On fait donc : $61 EOR $11 = $70. Celui-là ne change donc pas. On avait déjà $70... Savourez ce moment de pur bonheur !
- Check (2) : maintenant, on passe à l'EOR #$D8 en $6250, premier "contrôle" post-décryptage. Refaites tout le chemin jusqu'à arriver en $62F0, partie qui va décrypter ce qui est en $6200+ avec un EOR #$A2 (cryptage page $62 cette fois). Un fois décrypté, on BREAK en $6250 par exemple et on vérifie les valeurs générées par le nouveau checksum dans $08, $09 et $0A. J'obtiens respectivement $94, $90, $02 (avec mon image signée qui à ce moment là est décryptée...suivez un peu...) ! On fait donc : $94+$90+1 = $125 soit $25 (sur un octet) et $25 EOR $02 = $27. On sait qu'à la sortie du EOR en $6250 on doit obtenir $80, donc notre valeur doit être : $80 EOR $27 = $A7 (et donc non plus $D8). Pour trouver la valeur à remplacer sur le DISK, on cherche EB 7A 2F C3 C0 et on remplace le $7A par $05 (soit $A7 EOR $A2... et oui on n'oublie que la page $62 est cryptée sur le DISK par un EOR #$A2). On trouve tout ça sur DISK en T$12 /S$02/B$50.
- Check (3)(4)(5) : dernière partie, la page $63 qu'il faut totalement reformer avant de la recopier sur DISK. Encore une fois on se débrouille pour arriver en $6268 (c'est à dire juste avant le saut en $6380 qui va commencer à décrypter la page $63). On peut sauver cette partie (en rebootant un DISK 3.3 par C600G) pour la modifier plus tard tranquillement ou on peut tout faire dans la foulée. L'image décryptée doit être bien évidemment en mémoire (je dis ça pour ceux qui voudraient faire ça le lendemain). Et on va également utiliser la routine qui est en $6258 (qui fait le EOR). Nous aussi on recycle... Là encore, ou on sauve cette petite routine (en ajoutant un RTS à la fin) pour l'utiliser plus tard, ou on le fait maintenant. Je rappelle, pour que tout soit clair, ce que fait le programme à la page $63 (la pauvre) :
- de $6300 à $637F : EOR avec #$A3.
- de $6380 à $63FF : EOR avec ce qui est en $4300+.
- de $63A0 à $63FF : copie direct de ce qui est en $5000+.
- Pour générer notre page $63 on devra donc faire dans l'ordre :
- $63A0<$5000.$5060M (move notre partie d'image vers la zone $63A0)
- $6258G (en n'oubliant pas de mettre un RTS en $6268) : on recrypte ainsi par le EOR la zone $6380-$63FF.
- on ne touche à rien entre $6300 et $637F. Cela n'a pas encore été décrypté ! Et on en a justement besoin crypté pour mettre sur DISK.
- On peut enfin sur un disque de travail sauver toute la zone $6300-$63FF ainsi constituée : ]BSAVE Z6300,A$6300,L$100
- On en profite pour repérer les premiers octets qui sont en $6300+ soit AD F3 63 0E que l'on cherchera sur DISK avec DiskFixer pour localiser où se trouve cette page. On trouve tout ça en : T$12/S$03. Il ne reste plus qu'à écrire avec DIRECT (encore lui) ce secteur sur le DISK à partir du fichier que l'on a sauvegardé juste au-dessus (buffer $6300 / taille : $100).
- Normalement, on est censé, arrivé ici, avoir un DISK de QTIP 5 signé et presque fonctionnel. Presque car, ayant modifié le boot pour récupérer la main avant le JSR $6000, on n'oublie pas de remettre les 3 NOP ($EA) en T$00/S$0E/B$64 à la place de notre patch (4C 59 FF).
- Voilà normalement c'est fini... ! Vous pouvez décrocher vos ceintures, ranger les sacs vomitifs non utilisés (s'il en reste...) et reprendre enfin à une activité moderne !
Le petit mot de la fin : j'ai attaqué ce défi en croyant n'en faire qu'une bouchée. Mais plus j'avançais et plus je m'apercevais qu'il était plutôt vicieux avec quelques bonnes idées (les check post-décryptage ou la sauvegarde de l'image sur le disque par exemple). Il n'est pas vraiment complexe à résoudre mais les multiples EOR à effectuer sont une vraie gymnastique cérébrale et ne pas perdre le fil demande un minimum de rigueur. Pas sûr que l'on soit encore nombreux à avoir envie de se prendre la tête là-dessus... Mais s'il ne doit en rester qu'un...
Comme d'habitude, vous pouvez directement récupérer QTiPS 5 signé par mes soins ainsi que le disque de travail qui contient plusieurs petites choses utiles !