Seconde partie de l'article consacré au défi de Championship Wrestling imaginé par les ACS Boys, Deny et The Gog's. Résumé de l'épisode précédent : on en a bavé mais on a enfin localisé la routine de décryptage qui se trouve en $6000. Nous avons aussi découvert où se trouve l'image encodée sur le disque. Et comble du raffinement, nous avons un peu bidouillé une copie nous permettant ainsi d’atterrir juste avant le décodage (ou presque) sans devoir tout retracer. Pour ceux n'ayant pas lu la première partie (ou s'étant endormis avant la fin), je vous invite à y jeter un œil tout de même avant d’enchaîner...
La première étape avant d'attaquer les choses sérieuses, sera de récupérer une image décodée du défi afin de pouvoir y apposer notre petite signature. Il serait bon également de sauver sur disque toute la routine de décodage car cela pourrait nous être utile plus tard (mais non je ne spoile pas !). Pour ce faire, nous allons repartir de notre disque modifié. Je rappelle que, quand il nous rend la main, nous sommes juste avant l'exécution de la routine en $1180, routine qui s'occupait de décrypter tout une portion de code et finissait par nous envoyer en $6000 en utilisant l'astuce de la pile et de la routine HOME.
Pour récupérer la routine, procédons ainsi :
1176 : 4C 59 FF ; on shunte l'appel à la routine HOME donc le saut en $6000 1180G ; on lance l'exécution pour obtenir le décryptage de la routine ; de décryptage (vous suivez ou bien ?) C600G ; on peut alors booter un DOS 3.3 (n'oubliez pas de changer de disque...) BSAVE ROUTINE DECODAGE,A$6000,L$500 ; on sauve la routine décryptée ! ; ne soyez pas pingre sur la portion à ; enregistrer car il faut aussi sauver ; la partie qui se trouve en $6400+ |
Pour obtenir l'image décryptée, cela commence pareil :
1176 : 4C 59 FF 1180G 6079 : 4C 59 FF ; on stoppe la routine juste après le décodage C600G ; boot d'un DOS 3.3 BSAVE IMAGE,A$2000,L$2000 ; sauvegarde de toute la Page Hires 1 |
Attention : si vous signez l'image en utilisant Blazzing Paddle par exemple, faites bien attention car l'image sera alors sauvegardée en 33 secteurs (et non 34). Il faudra bien veiller à mettre à zéro les octets entre $3FF8 et $3FFF (non sauvegardés par Blazzing Paddle car inutiles) avant de réencoder l'image. Nous avons besoin d'une Page Hires complète de $2000 octets car c'est ce que la routine de décodage attendra...
Ces étapes préliminaires effectuées, nous allons pouvoir entrer concrètement dans le vif du sujet et reprendre point par point les 3 phases de décodage survolées dans la première partie. Afin de simplifier le tout, nous allons nommer ces phases P,Q et R qui correspondront aux phases successives 1,2 et 3 exécutées lors du décodage. Pour chacune de ces trois phases de décodage, nous devrons générer un code inverse pour l'encodage. Nous obtiendrons alors les 3 phases d'encodage correspondantes que nous appellerons P',Q' et R'.
Première phase : P
6003- A9 00 LDA #$00 6005- 85 10 STA $10 ; compteur initialisé à 00 6007- A9 00 LDA #$00 6009- 85 0E STA $0E ; zone à traiter (buffer) 600B- A9 20 LDA #$20 600D- 85 0F STA $0F ; commence en $2000 ; décodage phase 1 : P => (Buffer) = (Buffer) EOR (Buffer+1) 600F- A2 20 LDX #$20 ; $20 pages mémoire ($20-$3F) 6011- A0 00 LDY #$00 6013- B1 0E LDA ($0E),Y ; A<-(Buff) 6015- C8 INY 6016- 51 0E EOR ($0E),Y ; A<- A EOR (Buff+1) 6018- 88 DEY 6019- 91 0E STA ($0E),Y ; A->(Buff) 601B- C8 INY 601C- D0 F5 BNE $6013 601E- E6 0F INC $0F ; 6020- CA DEX 6021- D0 EE BNE $6011 ; il reste des pages à traiter ? 6023- E6 10 INC $10 ; on incrémente le compteur 6025- AD F1 20 LDA $20F1 ; on récupère le contenu de $20F1 6028- 49 11 EOR #$11 ; est-il égal à #$11 ? 602A- D0 DB BNE $6007 ; non ? alors on refait un cycle complet 602C- A5 10 LDA $10 ; si oui 602E- C9 0A CMP #$0A ; on vérifie que le compteur est bien à 10 6030- D0 FE BNE $6030 ; sinon boucle infinie |
Petit aparté sur le EOR (Exclusive OR) : une des propriétés fondamentales de cet opérateur binaire est sa réversibilité : si on a A EOR B = C, alors C EOR B = A. Pour passer de C à A ou de A à C, il faut donc utiliser exactement le même argument B pour le EOR.
Pour traduire en français notre première phase de décodage, on dirait : on prend un octet d'une page mémoire donnée, on fait un EOR avec l'octet qui le suit immédiatement et on remet le tout à l'emplacement du premier. Chaque octet est donc dépendant de celui qui le suit immédiatement. Et ainsi de suite. Pour inverser la routine (donc encoder cette fois), il faudra toujours faire un EOR entre un octet et celui qui le suit mais en partant non plus du premier d'une page mémoire mais du dernier. Et remonter jusqu'au premier. En effet, le premier octet d'une page mémoire étant celui qui est décodé en premier, il est donc logique qu'il soit celui encodé en dernier. Et inversement, le dernier décodé (le $FF) devra être le premier encodé si on veut obtenir la routine inverse.
Il est également important de comprendre que les pages mémoire du buffer d'entrée (donc toute la zone de la Page graphique 1) sont indépendantes entre elles lors du décodage. Quand l'index Y devient supérieur à $FF, il repasse à $00 (c'est un registre 8 bits). Ce qui fait que le dernier octet d'une page mémoire donnée (exemple $21FF) sera décodé (EORé) avec le premier de la même page ($2100). Il n'y a pas d'interconnexion entre les pages mémoire. On pourra donc les traiter lors de l'encodage dans l'ordre croissant (comme pour le décodage) ou bien dans n'importe quel ordre. On va se simplifier la vie et reprendre l'ordre croissant.
Ce qui nous amène pour notre phase d'encodage P' (ceux ne pigeant pas pourquoi elle s'appelle P "prime" sont condamnés à tout relire !) à un code qui ressemble quasiment à celui du décodage, seule nuance, on part de l'octet $FF et on redescend vers $00 pour chaque page mémoire :
** ENCODAGE : PHASE P' ** LDA #$00 STA COMPT :B6 LDX #$20 STX ADDH LDA #$00 STA ADDB LDY #$FF ; on part de $FF :B5 LDA (ADDB),Y ; (Buff) INY EOR (ADDB),Y ; EOR (Buff + 1) DEY STA (ADDB),Y ; remis dans Buff DEY ; on décrémente CPY #$FF ; vers 00 (inclus) BNE :B5 INC ADDH DEX BNE :B5 INC COMPT LDA COMPT CMP #$0A BNE :B6 |
Nous incrémentons un compteur afin d'effectuer 10 fois le même cycle de codage . Rappelez-vous dans la routine de décodage, il était vérifié à la fin que l'octet en $20F1 correspondait bien à #$11 au bout de 10 exécutions de la même routine. Et là vous allez me dire : "oui mais le #$11 qui doit être en $20F1, il sort d'où ?"
Le décodage de l'image du défi contient trois phases successives P puis Q puis R (on va le savoir...). Pour encoder, il faut effectuer également 3 phases (P',Q' et R') mais dans l'ordre inverse : la première phase de décodage devra correspondre à notre dernière phase d'encodage, et la dernière de décodage à notre première partie du codage. Vous l'aurez compris, la seconde phase reste à la même place ! Nous aurons donc pour l'encodage trois phases dans cette ordre : R' puis Q' puis enfin P'. Le rapport avec le #$11 ? Et bien cet octet ($20F1) qui est vérifié dans la phase 1 du décodage devra donc être issu des phases d'encodage R' et Q'. Pour le moment, mettons juste dans un coin de notre cerveau qu'il y a une vérif qui traîne dans le coin...
Seconde phase : Q
; décodage phase 2 : Q => (A) EOR XX 6032- A9 00 LDA #$00 6034- 85 0F STA $0F 6036- A9 20 LDA #$20 ; zone à traiter commence en $2000 6038- 85 10 STA $10 ; $20 pages à traiter 603A- A2 20 LDX #$20 ; compteur de pages (qu'on va décrémenter) 603C- A0 00 LDY #$00 603E- B1 0F LDA ($0F),Y ; A<-(Buff) 6040- 48 PHA ; sauve A 6041- 98 TYA ; A<-Y 6042- 18 CLC 6043- 65 10 ADC $10 ; on lui additionne la page courante 6045- 48 PHA ; on sauve A' 6046- 8A TXA ; compteur dans A 6047- 6A ROR ; rotation droite 6048- 85 11 STA $11 ; sauve résultat dans TEMP 604A- 68 PLA ; on récupère le A' 604B- 2A ROL ; rotation gauche 604C- 2A ROL ; rotation gauche 604D- 2A ROL ; rotation gauche 604E- 45 11 EOR $11 ; EOR avec TEMP 6050- 85 11 STA $11 ; sauve TEMP 6052- 68 PLA ; on restaure A (c'est à dire (Buff)) 6053- 45 11 EOR $11 ; EOR avec TEMP 6055- 91 0F STA ($0F),Y ; sauve dans (Buff) 6057- 88 DEY ; 6058- D0 E4 BNE $603E ; on boucle pour faire toute la page mémoire 605A- E6 10 INC $10 ; on incrémente Buff 605C- CA DEX ; il reste des pages à traiter ? 605D- D0 DD BNE $603C ; oui on boucle |
Le petit rappel sur le EOR de tout à l'heure, va nous permettre de bien comprendre que cette phase Q de décodage, ne fait que prendre un A (issu du Buffer), calculer un B à partir de la page courante et du compteur, pour ensuite faire un A EOR B et générer notre fameux C. Pour encoder et revenir vers A, il faudra donc le C (on le connaît puisque ce sera un octet de l'image) et calculer le B de la même manière que la routine ci-dessus. Et oui de la même manière car il nous faut le même B ! Peu importe la façon alambiquée à coup de ROR et autre ROL utilisée par la routine de décodage, il suffit de reprendre exactement la même routine pour l'encodage ! Magique et pratique... Ce qui nous donne :
** ENCODAGE : PHASE Q' ** LDA #$00 STA ADDB LDA #$20 STA ADDH TAX :B4 LDY #$00 :B3 LDA (ADDB),Y PHA TYA CLC ADC ADDH PHA TXA ROR STA TEMP PLA ROL ROL ROL EOR TEMP STA TEMP PLA EOR TEMP STA (ADDB),Y DEY BNE :B3 INC ADDH DEX BNE :B4 |
Oui c'est exactement la même routine que celle du décodage. Et il va se passer la même chose pour la phase R, troisième et dernière que nous allons voir maintenant :
Troisième phase : R
; décodage phase 3 : R => A = A EOR (#$78 EOR Y) 605F- A9 00 LDA #$00 6061- 85 0E STA $0E 6063- A9 20 LDA #$20 6065- 85 0F STA $0F 6067- AA TAX 6068- A0 00 LDY #$00 606A- 98 TYA 606B- 49 78 EOR #$78 606D- 51 0E EOR ($0E),Y 606F- 91 0E STA ($0E),Y 6071- C8 INY 6072- D0 F6 BNE $606A 6074- E6 0F INC $0F 6076- CA DEX 6077- D0 EF BNE $6068 |
On retrouve encore une fois notre A EOR B = C avec un B qui sera fixe pour chaque paire A/C. Ici il est simplement calculé par rapport à la valeur de l'index Y. La routine d'encodage R' correspondante sera donc exactement la même encore une fois :
** ENCODAGE R'** LDA #$00 STA ADDB LDA #$20 STA ADDH TAX :B2 LDY #$00 :B1 TYA EOR #$78 EOR (ADDB),Y STA (ADDB),Y INY BNE :B1 INC ADDH DEX BNE :B2 |
Pour obtenir notre routine d'encodage finale, il faudra donc réunir les trois phases dans cet ordre : R' Q' P' qui s'effectueront successivement sur le même buffer. Chaque phase (ré)encryptant le résultat de la précédente (sauf pour R' évidemment qui partira, elle, d'une image décodée).
Hélas tout n'est pas encore terminé, car la routine de décodage contient aussi différentes vérifications nous conduisant chacune, si le résultat n'est pas celui escompté, dans le mur !
La première concerne donc le fameux #$11 qui doit être en $20F1 après les deux premières phases d'encodage (R' et Q'). Puisque ces deux phases effectuent chacune un EOR avec une valeur fixe pour l'octet en $20F1, on en déduit donc que ce #$11 dépendra directement (et uniquement) de l'octet de départ en $20F1 de notre image signée !
Pour bien se rendre compte de quoi on parle, sur cette capture d'écran, j'ai mis l'octet $20F1 à $FF. On obtient donc 7 bits consécutifs à 1 donc un petit trait blanc. C'est celui qui apparaît juste en-dessous et à droite du "TOUJOURS PERSONNE ?". Sur l'image originale, cet octet est à $80 (donc rien à l'écran).
Sur cette première version de l'image modifiée, sans même le faire exprès, l'octet $20F1 était inchangé. On obtenait donc #$11 lors de l'encodage après les phases R' et Q' et la vérification au décodage me laissait donc tranquillement passer...
Vous l'aurez compris, pour passer le check 1, une des solutions possibles est tout simplement de faire en sorte de ne rien écrire en $20F1. Simple et efficace...
La seconde vérification concerne la routine de décodage en elle-même :
608D- A0 00 LDY #$00 608F- B9 00 60 LDA $6000,Y 6092- 4D 8E 60 EOR $608E 6095- 8D 8E 60 STA $608E 6098- C8 INY 6099- C0 8E CPY #$8E 609B- D0 F2 BNE $608F 609D- C9 B8 CMP #$B8 ; check 2 (routine décodage) 609F- F0 03 BEQ $60A4 ; gentil garçon tu peux continuer! 60A1- 4C 00 C6 JMP $C600 ; bad boy = reboot |
Elle vérifie donc par un checksum si la section $6000-$608D est intacte. Et donc, si par exemple, nous n'avons pas modifié le #$11 de la vérif précédente qui est en plein dedans. Ce qui veut donc forcément dire que modifier le #$11 entrainera d'office une modification du #$B8 pour que le checksum ne nous arrête pas. Vous voyez que l'affaire se complique...
La troisième vérif toujours contenue dans la routine de décodage a lieu après l'exécution des trois phases et s'intéresse à la Page Hires 1. C'est donc une vérification de l'image décodée :
60A4- A0 00 LDY #$00 60A6- B9 00 20 LDA $2000,Y 60A9- 4D A5 60 EOR $60A5 60AC- 8D A5 60 STA $60A5 60AF- C8 INY 60B0- D0 F4 BNE $60A6 60B2- EE A8 60 INC $60A8 60B5- AD A8 60 LDA $60A8 60B8- C9 40 CMP #$40 60BA- D0 EA BNE $60A6 60BC- AD A5 60 LDA $60A5 60BF- C9 DE CMP #$DE ; check 3 (image décodée) 60C1- F0 03 BEQ $60C6 ; cours chercher bonheur ! 60C3- 4C 21 02 JMP $0221 ; bad boy = "ACS Lead Again !" |
Un checksum à base d'EOR (décidément...) est effectué sur toute la page graphique entre $2000 et $3FFF. Le résultat doit être égal à $DE sinon badaboom...
Or cette fois, peu importe la modification effectuée sur l'image, même un petit gribouillis dans un coin, entraînera une valeur différente pour le checksum (sauf pour les veinards qui tomberont par hasard sur la même).
Mais est-ce bien sûr ? Ne pourrait-on pas s'arranger pour retrouver $DE et ce, peu importe l'image et la signature ?
Rappel numéro 2 : EOR est une opération dite commutative : A EOR B = B EOR A et associative (A EOR B) EOR C = A EOR (B EOR C). Autre propriété intéressante : A EOR 00 = A.
Comment utiliser tout ceci pour le cas qui nous intéresse ? Vais-je arrêter de poser des questions dont je connais les réponses ?
Lors de la vérification, tous les octets entre $2000 et $3FFF vont être EORés les uns avec les autres. On aura donc : A EOR B EOR C EOR D .... EOR X = $DE, X étant le dernier octet vérifié à savoir celui en $3FFF. Vous comprenez maintenant pourquoi je vous avais demandé tout à l'heure de mettre des zéro entre $3FF8 et $3FFF.
Or l'associativité de EOR fait que l'on peut regrouper tous les octets AVANT celui en $3FFF par une valeur que nous appellerons Y. On a donc : A EOR B EOR C EOR D EOR E .... EOR W = Y (W étant l'octet en $3FFE).
Et Y EOR X = $DE ! Y va dépendre directement de notre propre image signée. Et on peut très facilement le calculer nous même en utilisant la même routine que le checksum made in ACS. Une fois Y trouvé, on pourra donc en déduire X car X = $DE EOR Y ! Et une fois X obtenu, i suffira alors de mettre cette valeur en $3FFF pour que le checksum final soit $DE peu importe notre image !
Et comme les octets à partir de $3FF8 jusqu'en $3FFF ne sont pas visibles à l'écran, on pourra donc mettre la valeur que l'on souhaite en $3FFF, l'image n'en sera pas altérée pour autant ! Classe n'est-ce pas ?
Mais ce n'est pas tout car il existe une quatrième vérification :
60CF- 20 20 64 JSR $6420 ; got to check 4 6420- 4C 88 64 JMP $6488 6488- A0 00 LDY #$00 648A- B9 00 60 LDA $6000,Y 648D- 4D 89 64 EOR $6489 6490- 8D 89 64 STA $6489 6493- C8 INY 6494- D0 F4 BNE $648A 6496- C9 04 CMP #$04 ; check 4 6498- F0 03 BEQ $649D ; good guy 649A- 6C F2 03 JMP ($03F2) ; reboot ! |
C'est de nouveau une vérification consistant à checker l'intégrité de la routine de décodage. Là, on ne fait plus dans la dentelle puisque c'est toute la page $60 qui est vérifiée. De nouveau, cette vérif posera problème si on tient absolument à modifier l'octet $20F1. Mais si on est malin, on le laissera à $80, sa valeur par défaut, et on éliminera d'un coup le problème des vérifications 1, 2 et 4 !
Voici le programme final d'encryptage de l'image que je vous propose. Il contient bien entendu les 3 phases d'encodage telles que décrites précédemment et dans le bon ordre (R' Q' P'). En début de routine, j'ai incorporé le calcul de checksum de l'image avant encodage afin de générer la valeur avec laquelle il faudra faire un EOR #$DE pour obtenir le contenu définitif de $3FFF. Vous remarquerez que ce checksum est calculé entre $2000 et $3FFF. N'oubliez pas de vérifier que votre image signée contient bien des $00 entre $3FF8 et $3FFF (et pas quelques $FF qui traînent). Le programme affiche également la valeur en $20F1 après les phases R' et Q'.
1 2 LST OFF 3 ORG $1000 4 5 ADDB EQU $18 6 ADDH EQU $19 7 TEMP EQU $1A 8 9 PRBYTE EQU $FDDA 10 COUT EQU $FDED 11 12 ** CALCUL CHECKSUM IMAGE 13 14 15 LDY #00 16 STY TEMP 17 18 BC1 LDA $2000,Y 19 EOR TEMP 20 STA TEMP 21 INY 22 BNE BC1 23 INC BC1+2 24 LDA BC1+2 25 CMP #$40 26 BNE BC1 27 28 LDA TEMP 29 JSR PRBYTE 30 31 LDA #$8D 32 JSR COUT 33 34 35 ** CODAGE PART I : R' 36 37 LDA #$00 38 STA ADDB 39 LDA #$20 40 STA ADDH 41 42 TAX 43 44 :B2 LDY #$00 45 :B1 TYA 46 EOR #$78 47 EOR (ADDB),Y 48 STA (ADDB),Y 49 INY 50 BNE :B1 51 52 INC ADDH 53 DEX 54 BNE :B2 55 56 57 ** CODAGE PART II : Q' 58 59 LDA #$00 60 STA ADDB 61 LDA #$20 62 STA ADDH 63 TAX 64 65 :B4 LDY #$00 66 67 :B3 LDA (ADDB),Y 68 PHA 69 TYA 70 CLC 71 ADC ADDH 72 PHA 73 TXA 74 ROR 75 STA TEMP 76 PLA 77 ROL 78 ROL 79 ROL 80 EOR TEMP 81 STA TEMP 82 PLA 83 EOR TEMP 84 STA (ADDB),Y 85 DEY 86 BNE :B3 87 88 INC ADDH 89 DEX 90 BNE :B4 91 92 ** CODAGE PART III : P' 93 94 LDA $20F1 95 JSR PRBYTE 96 97 LDA #$00 98 STA TEMP 99 100 :B6 LDX #$20 101 102 STX ADDH 103 LDA #$00 104 STA ADDB 105 106 107 LDY #$FF 108 109 :B5 LDA (ADDB),Y 110 INY 111 EOR (ADDB),Y 112 DEY 113 STA (ADDB),Y 114 DEY 115 CPY #$FF 116 BNE :B5 117 118 INC ADDH 119 DEX 120 BNE :B5 121 122 INC TEMP 123 LDA TEMP 124 CMP #$0A 125 BNE :B6 126 127 JMP $3D0 128 129 FIN RTS |
Il suffit donc de charger l'image signée en mémoire. De charger le programme ci-dessus, de l'exécuter ($1000G). De noter la première valeur retournée. Elle sera à EORer avec $DE pour obtenir le bon $3FFF. Il faudra bien sûr recharger l'image (car elle aura été encryptée entre temps), fixer le $3FFF et recharger la routine d'encodage (qui ne peut s’exécuter deux fois de suite) pour, enfin, lancer l'encryptage final. Tout ceci n'est pas très optimisé je vous l'accorde mais j'ai préféré tout mettre dans le même programme même si cela oblige à quelques acrobaties.
Exemple pour ma première image signée (qui n'écrase pas le $20F1) : le programme d'encodage retourne 9D et 11. Pour le #$11, on ne revient pas dessus... Le $9D correspond donc au checksum de la zone $2000-$3FFF. Pour obtenir la valeur à écrire en $3FFF, il suffit de faire : $9D EOR $DE soit $43. Pour info, vous pouvez mettre ce $43 sur n'importe quel octet entre $3FF8 et $3FFF (du moment que les autres sont à zéro). Quand vous relancez le programme d'encodage avec l'image signée (et le $3FFF modifié), il doit afficher maintenant DE et 11. Cette fois, tout est OK, l'image en Page 1 est celle à sauver sur disque !
Petit aperçu de l'image après encodage (ou avant décodage c'est selon...). Même signée et donc modifiée, vous devriez obtenir un truc à peu près ressemblant...
Il reste ensuite l'étape de l'écriture sur Championship Wrestling. Tout se passe en accès direct, Piste $1B / Secteur $00. Pour un total de 34 secteurs. L'intégralité des pistes $1B et $1C sera donc occupée par l'image.
Vu que le fameux #$11 n'a pas été modifié et le checksum image rectifié, tous les checks doivent donner leur feu vert et le défi est officiellement résolu... Faites quand même une copie avant le lancement final au cas où...
Bonus pour les fous...
Que se passe-t'il, par contre, si on n'est pas malin mais plutôt maso voire carrément vicieux et qu'on se débrouille pour que la signature écrase l'octet $20F1 (comme sur celle qui illustre le début de cette partie) ? Il faudra alors modifier la routine en $6000 pour y placer les bonnes valeurs sur les différentes vérifs rencontrées. Ceux ayant lu jusqu'au bout la partie I, doivent se souvenir que la routine en $6000 est elle-même cryptée et subit un double décodage avant d'être exécutée. Allez jeter un petit coup d'œil au code en $7000 par exemple et vous allez comprendre votre douleur...
Donc on résume : si on modifie l'image et l'octet $20F1, il faudra modifier la routine de décodage pour fixer les checks 1,2 et 4. Le check 3, lui, sera réglé par l'octet $3FFF que l'on disposera à la bonne valeur (comme expliqué précédemment). Or modifier ces 3 checks nous oblige à réencoder la routine de décryptage en elle-même pour l'écrire sur disque. Et là c'est l'usine à gaz assurée...
Il reste toutefois une autre possibilité : ne pas toucher à la routine de décodage telle qu'elle apparaît sur le disque mais la patcher une fois chargée (et décryptée) pour y modifier les 3 octets de comparaison des vérifs problématiques. Problème : où incorporer et comment exécuter ce patch ? Or ACS dans son envie de (trop) bien faire nous a laissé une petite porte entrouverte. Rappelez-vous le tout début de la routine de décodage en $6000 :
6000- 20 9B 02 JSR $029B ; <--- hihihi 029B- 8D E9 C0 STA $C0E9 ; allumage drive (fake) 029E- A9 02 LDA #$02 ; on empile 02A0- 48 PHA 02A1- A9 7F LDA #$7F ; $27F pour sauter en 02A3- 48 PHA ; $280 quand le RTS 02A4- 4C 58 FC JMP $FC58 ; de HOME sera exécuté 0280- A9 21 LDA #$21 ; on a déjà rencontré cette 0282- 8D F2 03 STA $03F2 ; routine qui dispose les vecteurs 0285- A9 02 LDA #$02 ; RESET et BRK 0287- 8D F3 03 STA $03F3 ; pour renvoyer vers $221 028A- 49 A5 EOR #$A5 ; 028C- 8D F4 03 STA $03F4 028F- A9 21 LDA #$21 0291- 8D F0 03 STA $03F0 0294- A9 02 LDA #$02 0296- 8D F1 03 STA $03F1 0299- 60 RTS ; retour à l'appelant |
La routine en $280, déjà rencontrée précédemment, est de nouveau appelée après le coup de l'allumage du drive. À ce sujet d'ailleurs, je pense qu'ACS brouille les pistes en allumant le drive pour essayer de faire croire que quelque chose se charge (alors qu'en fait notre image cryptée est déjà entièrement en mémoire). Pas mal le coup du vrai-faux accès disque !
L'idée ici, c'est d'utiliser la routine en $280 et de la modifier afin qu'elle patche la routine de décodage ! Elle est exécutée au moment idéal pour notre rustine. Et vu qu'il ne nous faut patcher que trois octets, on a largement la place de l'incorporer ici.
Est-ce pour autant que la routine en $280 est lisible sur le disque ? Hélas non, mais elle est cryptée d'une façon plutôt simple (et en une seule fois) contrairement à la routine en $6000.
La routine de décryptage d'ailleurs, la voici ou la revoici car nous l'avons déjà aperçue dans la partie I :
1100- A2 00 LDX #$00 1102- BD D0 11 LDA $11D0,X 1105- 85 0F STA $0F 1107- A9 FF LDA #$FF 1109- 49 FF EOR #$FF 110B- 85 0E STA $0E 110D- 85 10 STA $10 110F- BD E0 11 LDA $11E0,X 1112- 85 11 STA $11 1114- A0 00 LDY #$00 1116- 98 TYA 1117- 5D F0 11 EOR $11F0,X 111A- 51 0E EOR ($0E),Y 111C- 91 10 STA ($10),Y 111E- 88 DEY 111F- D0 F5 BNE $1116 1121- E8 INX 1122- E0 0C CPX #$0C 1124- F0 03 BEQ $1129 1126- 4C 02 11 JMP $1102 11D0- 12 13 14 15 16 17 18 19 1A 1B 1C 1D 00 11E0- 02 60 70 71 72 73 03 19 61 62 63 64 00 11F0- 01 45 D3 79 18 56 99 44 12 19 33 35 00 ** |
La page 2 est décryptée en même temps que les pages indexées en $11E0+ ($60, $70, $71 etc.).
Le décryptage est simple : on prend une valeur fixe suivant la page ($01 pour la page $02) et on fera un EOR entre cette valeur, chaque octet de la page et l'index de l'octet dans la page (ouf !).
Exemple concret pour les octets qui nous intéressent en $280+ :
$280 : octet final = A9. Octet sur disque = A9 EOR $01 (valeur fixe) EOR $80 (position de l'octet dans la page) = $28
$281 : octet final = 21. Octet sur disque = 21 EOR $01 (valeur fixe) EOR $81 (position de l'octet dans la page) = $A1
$282 : octet final = 8D. Octet sur disque = 8D EOR $01 (valeur fixe) EOR $82 (position de l'octet dans la page) = $0E
$283 : octet final = F2. Octet sur disque = F2 EOR $01 (valeur fixe) EOR $83 (position de l'octet dans la page) = $70
Nous n'avons pas calculé ces octets pour rien. Il nous suffit maintenant de faire une recherche Hexa sur le disque avec 28 A1 0E 70 pour localiser l'emplacement de la page $02 codée. Et on trouve le tout en T :$1A/S :$02/P :$80 !
Il nous reste maintenant à trouver les bonnes valeurs pour écrire notre patch. Pour ma seconde image, celle qui écrase le $20F1, le programme d'encodage retourne : FA et 16. Le $16 sera donc la valeur à intégrer dans la routine de décodage à la place du $11. Quant au $FA, il nous permet de calculer notre valeur à placer en $3FFF à savoir : $FA EOR $DE = $24
Il nous faut aussi trouver les valeurs pour les checks 2 et 4, ceux qui vérifient l'intégrité de la routine de décodage. Je vous avais conseillé (bien) plus haut de sauver sur disque cette routine. C'est maintenant qu'elle va nous servir !
Chargez là en mémoire ainsi que votre image encodée depuis le DOS. Nous partons à la pêche à la bonne valeur du check 2 :
29B : 60 ; on met un RTS en $29B (et oui il n'a y rien là) 6029 : 16 ; on met notre "nouvelle" valeur pour le "$11" 609D : 4C 59 FF ; pour récupérer la main juste après le check 2 ! 6000G ; on lance la routine qui va encrypter l'image en Page 1 608E ; on récupère la bonne valeur du check 2 ! |
De mon côté j'obtiens $BF. Rebootez votre disk DOS contenant votre image encodée et la routine de décodage (on doit repartir sur des bases propres), rechargez le tout et cette fois, on cherche la bonne valeur du check 4 :
29B : 60 ; on met un RTS en $29B 6029 : 16 ; on met notre bonne valeur pour le check 1 609E : BF ; on met la bonne valeur pour le check 2 6496 : 4C 59 FF ; pour récupérer la main juste après le check 4 ! 6000G ; on lance la routine qui va encrypter l'image en Page 1 6489 ; on récupère la bonne valeur du check 4 ! |
J'obtiens 03. Nous avons donc maintenant toutes les valeurs pour générer notre patch et la routine en $280 devient après modification :
0280- A9 16 LDA #$16 0282- 8D 29 60 STA $6029 ; pour le check 1 0285- A9 BF LDA #$BF 0287- 8D 9E 60 STA $609E ; pour le check 2 028A- 49 BC EOR #$BC ; -> A = 03 028C- 8D 97 64 STA $6497 ; pour le check 4 028F- A9 21 LDA #$21 ; ne change pas... 0291- 8D F0 03 STA $03F0 ; 0294- A9 02 LDA #$02 ; 0296- 8D F1 03 STA $03F1 ; 0299- 60 RTS |
Passons à la toute dernière étape : encoder ces modifications avant de les écrire sur disque. Le plus simple sera d'utiliser un éditeur de secteur pour placer directement les valeurs Piste$1A/Secteur$02 à partir de la position $80 une fois EORées. Pour l'encodage, on refait l'inverse de tout à l'heure avec nos nouvelles valeurs, ce qui donne pour mon cas perso :
P$80 : 28 96 (A9 EOR 80 EOR 01 = 28 / 16 EOR 81 EOR 01 = 96)
P$82 : 0E AB E5 (8D EOR 82 EOR 01 = 0E / 29 EOR 83 EOR 01 = AB / 60 EOR 83 EOR 01 = E5)
P$85 : 2D 38 (A9 EOR 85 EOR 01 = 2D / BF EOR 86 EOR 01 = 38)
P$87 : 0B 17 E8 (8D EOR 87 EOR 01 = 0B / 9E EOR 88 EOR 01 = 17 / 60 EOR 89 EOR 01 = E8
P$8A : C2 36 (49 EOR 8A EOR 01 = C2 / BC EOR 8B EOR 01 = 36)
P$8C : 00 1B EB (8D EOR 8C EOR 01 = 00 / 97 EOR 8D EOR 01 = 1B / 64 EOR 8E EOR 01 = EB)
Voilà, il ne reste plus qu'à tester et ça marche ! J'avais prévenu qu'il fallait être maso pour aller jusqu'ici...
Je ne sais pas pour vous, mais perso, je n'en peux plus donc je rends l'antenne sous vos applaudissements (ou vos ronflements). Comme d'habitude, voici le disque de travail contenant les divers trucs de cet article (le programme "Encode" avec son source, la "Routine Décodage" ainsi que les diverses images). Et bien sûr, un peu d'autosatisfaction avec les deux versions signées par mes soins de Championship Wrestling, la première allant au plus simple, la seconde dite masochiste !
Adieu !