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 !