Τεχνικό Άρθρο

XOR obfuscation XLS του HotXLS: παράγωγο κλειδιού και XorRor

Το HotXLS γράφει workbooks Excel 5.0/95 (BIFF5) με XOR obfuscation που η Excel 16 ανοίγει μόνο όταν τρεις λεπτομέρειες ταιριάζουν ακριβώς με το [MS-OFFCRYPTO]: το κλειδί FILEPASS πρέπει να είναι CreateXorKey_Method1(password), το XOR array 16 byte πρέπει να χτίζεται με XorRor (περιστροφή δεξιά κατά ένα bit), και κάθε byte πρέπει να χρησιμοποιεί XorArrayIndex = (stream offset + record length) mod 16. Το HotXLS έφτιαξε το index στο v2.384.47 και το κλειδί και την περιστροφή στο v2.384.54. Πριν από αυτό, κάθε BIFF5 αρχείο με κωδικό που παρήγαγε άνοιγε μια χαρά στο HotXLS και απέτυχε στην Excel

Η τελευταία εκείνη πρόταση είναι όλη η ιστορία σε μικρότυπο. Ένας reader και ένας writer που μοιράζονται την ίδια λάθος ιδέα συμφωνούν μεταξύ τους τέλεια, οπότε τα round-trip tests μένουν πράσινα ενώ ο μόνος καταναλωτής που μετράει λέει όχι. Η Excel 16 είπε όχι δύο φορές, με δύο διαφορετικά μηνύματα, και κάθε μήνυμα έδειχνε σε διαφορετικό στρώμα του σχήματος. Αυτό το άρθρο διασχίζει εκείνα τα στρώματα με τη σειρά που τα ελέγχει η Excel, με λεπτομέρεια σε επίπεδο byte που σας χρησιμεύει είτε καλείτε το HotXLS είτε γράφετε δικό σας BIFF reader

Τι αποθηκεύει στην πραγματικότητα το BIFF XOR obfuscation;

Το BIFF XOR obfuscation αποθηκεύει μόνο δύο λέξεις 16 bit στο αρχείο, και όλα τα άλλα ξαναϋπολογίζονται από τον κωδικό. Η εγγραφή FILEPASS ($002F) κάθεται αμέσως μετά το workbook globals BOF, και σε BIFF5 αρχείο το body της είναι ακριβώς 4 bytes: το XOR key ακολουθούμενο από τον password verifier. Δεν υπάρχει salt, ούτε αναγνωριστικό αλγορίθμου, ούτε κρυπτογραφημένο verifier blob του είδους που κουβαλάνε τα σχήματα RC4 και AES

Από εκείνες τις δύο λέξεις ένας reader ξαναχτίζει τρία πράγματα:

  • Ο verifier, ένα hash 16 bit των bytes του κωδικού με XOR $CE4B. Η σύγκρισή του με την αποθηκευμένη λέξη είναι ο έλεγχος κωδικού, και ο μόνος
  • Το XOR key, μια τιμή 16 bit από το CreateXorKey_Method1 στο [MS-OFFCRYPTO] §2.3.7.2, που το οδηγούν δύο πίνακες σταθερών (InitialCode, 15 λέξεις, και XorMatrix, 105 λέξεις)
  • Το XOR array, 16 bytes από τα bytes του κωδικού συμπληρωμένα με σταθερό pad 16 byte, καθένα με XOR το χαμηλό byte του κλειδιού (ζυγές θέσεις) ή το υψηλό (μονές θέσεις), και μετά περιστραμμένα δεξιά κατά ένα bit

Τα record headers μένουν σε plain text, όπως και μια χούφτα ολόκληρες εγγραφές που το σχήμα εξαιρεί, μεταξύ των οποίων BOF, FILEPASS και INTERFACEHDR. Κάθε άλλο record body μετασχηματίζεται byte προς byte: περιστροφή αριστερά κατά 5 bits, μετά XOR με μια εγγραφή του array 16 byte. Η αποκρυπτογράφηση, που το [MS-OFFCRYPTO] §2.3.7.3 γράφει ως DecryptData_Method1, είναι ο καθρέφτης: πρώτα XOR, μετά περιστροφή δεξιά κατά 5

Στο HotXLS δεν αγγίζετε ποτέ τίποτα από αυτά απευθείας. Ορίστε έναν κωδικό, διαλέξτε το format, και το SaveAs εκπέμπει FILEPASS και μετασχηματίζει το stream:

uses
  SysUtils, lxHandle;

procedure SaveLegacyProtectedBook(const FileName: string);
var
  Wb: IXLSWorkbook;
begin
  Wb := TXLSWorkbook.Create;
  Wb.Sheets.Add.Name := 'Ledger';
  Wb.Sheets[1].Range['A1', 'A1'].Value := 'Account';
  Wb.Sheets[1].Range['B1', 'B1'].Value := 1250.75;

  // Το BIFF5 υποστηρίζει μόνο XOR obfuscation· το xletAuto θα το διάλεγε κι αυτό
  Wb.EncryptionType := xletXor;
  // Κρατήστε το ASCII και το πολύ 15 χαρακτήρες (βλ. παρακάτω)
  Wb.EncryptionPassword := 'secret';

  if Wb.SaveAs(FileName, xlExcel5) <> 1 then
    raise Exception.Create('BIFF5 save failed');
end;

Γιατί η Excel λέει ότι ο κωδικός είναι λάθος όταν ο verifier ταιριάζει;

Η Excel απορρίπτει τον κωδικό επειδή δεν εμπιστεύεται το αποθηκευμένο κλειδί: η Excel παράγει το κλειδί από τον πληκτρολογημένο κωδικό με CreateXorKey_Method1 και το συγκρίνει με τη λέξη κλειδί του FILEPASS, οπότε αρχείο με οποιοδήποτε άλλο κλειδί αποτυγχάνει τον έλεγχο κωδικού ακόμα κι όταν ο verifier είναι σωστός. Το specification περιγράφει το κλειδί ως έξοδο του κωδικού, όχι ως ελεύθερη παράμετρο, και η Excel 16 επιβάλλει εκείνο το διάβασμα

Ο writer του HotXLS πριν το v2.384.54 γέμιζε τη λέξη κλειδί με δύο τυχαία bytes. Στο χαρτί μοιάζει αβλαβές, αφού ο verifier είναι ο τεκμηριωμένος έλεγχος κωδικού και το array χτίζεται από ό,τι κλειδί δηλώνει το αρχείο. Το ίδιο το HotXLS διάβαζε εκείνα τα αρχεία χωρίς πρόβλημα, γιατί ο reader του έπαιρνε το κλειδί από το FILEPASS ως δεδομένο. Η Excel 16, με το ίδιο αρχείο και τον σωστό κωδικό, απάντησε ότι ο κωδικός δεν ήταν σωστός. Από το v2.384.54 το κλειδί παράγεται, οπότε το FILEPASS για τον κωδικό secret κρατά πάντα κλειδί $014D και verifier $DAA7, τιμές διασταυρωμένες με ανεξάρτητη υλοποίηση του specification

Διάγραμμα HotXLS της εγγραφής BIFF5 XOR FILEPASS που ελέγχει η Excel 16 στο άνοιγμα: το σκέτο body τεσσάρων byte κρατά το XOR key και τον password verifier, η Excel παράγει το κλειδί από τον πληκτρολογημένο κωδικό με CreateXorKey_Method1 και απορρίπτει τυχαίο κλειδί με σφάλμα κωδικού ακόμα κι όταν ο verifier ταιριάζει· το HotXLS αποθηκεύει το παραγόμενο κλειδί 014D για το secret
Το body του FILEPASS είναι μόνο δύο λέξεις, αλλά η Excel ξαναπαράγει το κλειδί από τον κωδικό σας και συγκρίνει· τυχαία γεμάτο κλειδί αποτυγχάνει τον έλεγχο ακόμα με σωστό verifier, γι’ αυτό το HotXLS το παράγει από το v2.384.54

Η ίδια η παραγωγή είναι σύντομη μόλις μπουν οι δύο πίνακες. Διασχίστε τον κωδικό προς τα πίσω, κοιτάξτε το bit 6 κάθε byte επτά φορές ενώ το μετατοπίζετε αριστερά, και κάντε XOR με μία εγγραφή XorMatrix κάθε φορά που το bit είναι 1. Το παρακάτω είναι σκίτσο αρχής που αναπαράγει τον αλγόριθμο του specification και ταιριάζει με την υλοποίηση του HotXLS· δεν είναι API του HotXLS:

// Σκίτσο αρχής του [MS-OFFCRYPTO] 2.3.7.2 CreateXorKey_Method1
// και CreateXorArray_Method1 (μόνο επεξήγηση, όχι API του HotXLS)
type
  TXorArray = array [0..15] of Byte;

function DemoCreateXorKey(const Password: AnsiString): Word;
const
  InitialCode: array [0..14] of Word = ($E1F0, $1D0F, $CC9C, $84C0, $110C,
    $0E10, $F1CE, $313E, $1872, $E139, $D40F, $84F9, $280C, $A96A, $4EC3);
  XorMatrix: array [0..104] of Word = (
    $AEFC, $4DD9, $9BB2, $2745, $4E8A, $9D14, $2A09,
    $7B61, $F6C2, $FDA5, $EB6B, $C6F7, $9DCF, $2BBF,
    $4563, $8AC6, $05AD, $0B5A, $16B4, $2D68, $5AD0,
    $0375, $06EA, $0DD4, $1BA8, $3750, $6EA0, $DD40,
    $D849, $A0B3, $5147, $A28E, $553D, $AA7A, $44D5,
    $6F45, $DE8A, $AD35, $4A4B, $9496, $390D, $721A,
    $EB23, $C667, $9CEF, $29FF, $53FE, $A7FC, $5FD9,
    $47D3, $8FA6, $0F6D, $1EDA, $3DB4, $7B68, $F6D0,
    $B861, $60E3, $C1C6, $93AD, $377B, $6EF6, $DDEC,
    $45A0, $8B40, $06A1, $0D42, $1A84, $3508, $6A10,
    $AA51, $4483, $8906, $022D, $045A, $08B4, $1168,
    $76B4, $ED68, $CAF1, $85C3, $1BA7, $374E, $6E9C,
    $3730, $6E60, $DCC0, $A9A1, $4363, $86C6, $1DAD,
    $3331, $6662, $CCC4, $89A9, $0373, $06E6, $0DCC,
    $1021, $2042, $4084, $8108, $1231, $2462, $48C4);
var
  Len, I, Bit, Element: Integer;
  Ch: Byte;
begin
  Result := 0;
  Len := Length(Password);
  if Len > 15 then
    Len := 15;                       // το κλειδί βλέπει μόνο 15 bytes
  if Len = 0 then
    Exit;
  Result := InitialCode[Len - 1];
  Element := $68;                    // τελευταία εγγραφή XorMatrix
  for I := Len downto 1 do
  begin
    Ch := Ord(Password[I]);
    for Bit := 1 to 7 do
    begin
      if (Ch and $40) <> 0 then
        Result := Result xor XorMatrix[Element];
      Ch := Byte(Ch shl 1);
      Dec(Element);
    end;
  end;
end;

function XorRor(B, KeyByte: Byte): Byte;
begin
  B := B xor KeyByte;
  Result := Byte((B shr 1) or (B shl 7));   // περιστροφή δεξιά κατά ένα bit
end;

procedure DemoCreateXorArray(const Password: AnsiString; out Arr: TXorArray);
const
  PadArray: TXorArray = ($BB, $FF, $FF, $BA, $FF, $FF, $B9, $80,
    $00, $BE, $0F, $00, $BF, $0F, $00, $00);
var
  Key: Word;
  Len, I: Integer;
begin
  Key := DemoCreateXorKey(Password);
  Len := Length(Password);
  if Len > 16 then
    Len := 16;
  for I := 0 to Len - 1 do
    Arr[I] := Ord(Password[I + 1]);
  for I := Len to 15 do
    Arr[I] := PadArray[I - Len];
  for I := 0 to 15 do
    if Odd(I) then
      Arr[I] := XorRor(Arr[I], Byte(Key shr 8))
    else
      Arr[I] := XorRor(Arr[I], Byte(Key and $FF));
end;

Για τον secret αυτό παράγει το array 1F 32 17 B9 14 BA 7B 7F 59 DD 59 7F 7A C0 A6 DF, χρήσιμο fixture αν τεστάρετε δικό σας reader

Διάγραμμα HotXLS της κατασκευής XOR array για BIFF5 obfuscation: δεκαέξι bytes με σπόρο τον κωδικό και σταθερό pad κάνουν XOR με το χαμηλό byte του κλειδιού στις ζυγές θέσεις και το υψηλό στις μονές, μετά περιστρέφονται δεξιά κατά ένα bit με XorRor, παράγοντας το fixture 1F 32 17 B9 για τον κωδικό secret
Τα bytes του array βγαίνουν από τον κωδικό, το pad και τα δύο bytes του κλειδιού, με μία περιστροφή στο τέλος· το rotate left 2 δούλεψε μόνο όταν ο μετασχηματισμός byte έτρεχε με την αντίστροφη σειρά, και η Excel ακολουθεί τη σειρά του specification

Γιατί το σωστό κλειδί παράγει ακόμα κατεστραμμένο αρχείο;

Σωστό κλειδί παράγει ακόμα κατεστραμμένο αρχείο όταν το XOR array περιστρέφεται ανάποδα: το [MS-OFFCRYPTO] ορίζει το βήμα array ως XorRor, περιστροφή δεξιά κατά ένα bit, και ένα array περιστραμμένο αριστερά κατά δύο αποκρυπτογραφεί κάθε record body σε θόρυβο. Η διόρθωση του κλειδιού έσπρωξε την Excel 16 πέρα από την προτροπή κωδικού και κατευθείαν σε διαφορετικό σφάλμα, μια αναφορά ότι το αρχείο έχει πρόβλημα και δεν μπορεί να ανοίξει

Ο παλιός κώδικας του HotXLS περιστρεφόσε κάθε byte του array αριστερά κατά 2 bits, μια μορφή που κυκλοφορεί σε αρκετές υλοποιήσεις BIFF. Επειδή το HotXLS χρησιμοποιούσε την ίδια περιστροφή και στις δύο πλευρές, ο δικός του reader δεν το πρόσεξε ποτέ. Η Excel 16 δεν σώζει πλέον αρχεία Excel 5.0/95, και δεν προσφέρει XOR όταν σώζει BIFF8, οπότε δεν υπήρχε εγγενές δείγμα Excel για diff. Η απόδειξη έπρεπε να έρθει από την άλλη κατεύθυνση: γράψε ένα plain-text BIFF5 stream, κωδικοποίησέ το ξανά με οκτώ τρόπους, και άφησε την Excel 16 να ανοίξει κάθε παραλλαγή. Οι οκτώ παραλλαγές σταυρώνονταν σε τρεις ανεξάρτητες επιλογές:

ΕπιλογήΕπιλογή AΕπιλογή B
Περιστροφή arrayXorRor (rotate right 1)Περιστροφή αριστερά 2
Index array(offset + record length) mod 16offset mod 16
Σειρά μετασχηματισμού byteΠεριστροφή αριστερά 5, μετά XORXOR, μετά περιστροφή αριστερά 5

Η Excel 16 άνοιξε ακριβώς δύο από τις οκτώ: XorRor με rotate-then-XOR και το index record-length, και μία παραλλαγή που απλώς φαίνεται διαφορετική. Το rotate-left-2 με XOR-then-rotate και το ίδιο index είναι η ίδια συνάρτηση μεταμφιεσμένη. Η περιστροφή κατανέμεται πάνω στο XOR, οπότε το rol5(p xor rol2(b)) ισούται με rol5(p) xor rol7(b), και σε τιμή 8 bit μια περιστροφή αριστερά κατά 7 είναι περιστροφή δεξιά κατά 1. Με λίγα λόγια, rol5 ∘ rol2 = ror1, γι’ αυτό το array rotate-left-2 φαίνεται πειστικό μεμονωμένα: είναι σωστό μόνο μαζί με την αντίστροφη σειρά μετασχηματισμού. Ζευγάρι με τη σειρά του specification, καταστρέφει κάθε μετασχηματισμένο byte

Το ίδιο πείραμα έκλεισε και δεύτερη ερώτηση. Οι παραλλαγές που έριξναν το record length από το index απέτυχαν όλες, που επιβεβαίωσε τον κανόνα index που το HotXLS είχε υιοθετήσει ένα release νωρίτερα βασισμένο μόνο στο κείμενο του specification

Πώς υπολογίζεται το XorArrayIndex για κάθε byte;

Το XorArrayIndex για ένα byte είναι το offset του στο workbook stream συν το μήκος ολόκληρου του record data στο οποίο ανήκει, mod 16. Το index άρα ξεκινά από τιμή ανάλογη της εγγραφής σε κάθε εγγραφή και αυξάνεται κατά ένα ανά byte μέσα της. Το pseudocode του specification ονομάζει τις εισόδους FileOffset και Data.Length, που διαβάζεται εύκολα λάθος ως το σκέτο offset αρχής της εγγραφής, και εκείνη η παρανόηση είναι ακριβώς όσα μετέδιδε το HotXLS μέχρι το v2.384.47

Τρεις λεπτομέρειες αποφασίζουν αν τα indices σας ευθυγραμμίζονται με την Excel:

  • Το record header 4 byte δεν μετασχηματίζεται ποτέ, αλλά εξακολουθεί να καταλαμβάνει θέσεις stream, οπότε το πρώτο body byte μιας εγγραφής κάθεται στο header offset + 4
  • Ο όρος μήκους είναι το πλήρες record data length, όχι ο αριθμός των bytes που μετασχηματίστηκαν στην πραγματικότητα
  • Το BOUNDSHEET είναι εν μέρει σκέτο: τα πρώτα 4 bytes του, το lbPlyPos, το stream offset του sheet BOF, μένουν αναγνώσιμα ώστε ένας parser να εντοπίζει sheets. Εκείνα τα 4 bytes προσπερνιούνται από τον μετασχηματισμό αλλά μετρούν και στο offset και στο record length
Διάγραμμα HotXLS του κανόνα XorArrayIndex για BIFF5 XOR obfuscation: κάθε body byte χρησιμοποιεί το stream offset συν το πλήρες record data length modulo 16, το σκέτο header τεσσάρων byte και το πρόθεμα BOUNDSHEET lbPlyPos μετρούν στο offset, και η παραβίαση του όρου record length ήταν η αστοχία που διόρθωσε το HotXLS στο v2.384.47
Το array index ξεκινά ξανά μία φορά ανά εγγραφή, όχι μία ανά stream· το header και τυχόν σκέτο πρόθεμα καταλαμβάνουν θέσεις, το record data length τροφοδοτεί το modulo, και τα δύο μισά του παλιού HotXLS συμφωνούσαν στον λάθος τύπο

Βαλμένα μαζί, ο ανά εγγραφή μετασχηματισμός είναι λίγες γραμμές. Και πάλι, αυτό είναι σκίτσο του κανόνα, όχι κάτι που χρειάζεται να καλέσετε:

function Rol8(B: Byte; N: Integer): Byte;
begin
  Result := Byte((B shl N) or (B shr (8 - N)));
end;

// Κρυπτογράφησε ένα record body στη θέση του. Το BodyPos είναι το stream offset
// του Body[0], δηλαδή header offset + 4. Το PlainPrefix είναι 4 για
// BOUNDSHEET, το πλήρες μήκος για BOF / FILEPASS, 0 για τις περισσότερες εγγραφές
procedure DemoObfuscateRecord(var Body: array of Byte; RecordLength: Word;
  PlainPrefix: Integer; BodyPos: LongWord; const Arr: TXorArray);
var
  I: Integer;
begin
  for I := PlainPrefix to RecordLength - 1 do
    Body[I] := Rol8(Body[I], 5) xor
      Arr[(BodyPos + LongWord(I) + RecordLength) mod 16];
end;

// Το διάβασμα είναι ο καθρέφτης: B := Body[I] xor Arr[...];
// μετά περιστροφή δεξιά κατά 5, δηλαδή Rol8(B, 3)

Πριν το v2.384.47 ο reader του HotXLS υπολόγιζε το index από τη θέση του stream σκέτη και ο writer χρησιμοποιούσε το σκέτο byte offset. Και τα δύο αγνοούσαν το record length, οπότε ξανά τα δύο μισά συμφωνούσαν μεταξύ τους και με κανέναν άλλον. Ένας decoder γραμμένος ανεξάρτητα διάβασε σωστά την έξοδο του v2.384.47 και την παλαιότερη έξοδο ως σκουπίδια, και το test οκτώ παραλλαγών στην Excel 16 επιβεβαίωσε αργότερα τον κανόνα απέναντι στον πραγματικό στόχο

Τι γίνεται με τα XOR αρχεία που έγραψαν παλαιότερες εκδόσεις HotXLS;

Το HotXLS εξακολουθεί να διαβάζει τα δικά του XOR αρχεία πριν το v2.384.54 ελέγχοντας το κλειδί FILEPASS: όταν το αποθηκευμένο κλειδί ισούται με το κλειδί που παράγεται από τον κωδικό, ο reader χτίζει το array XorRor του specification, και όταν διαφέρει, ο reader μεταχειρίζεται το αρχείο ως παλαιότερο αρχείο HotXLS και ξαναχτίζει το array rotate-left-2. Τα αρχεία γραμμένα από Excel κουβαλάνε πάντα το παραγόμενο κλειδί, οπότε παίρνουν πάντα τη διαδρομή του specification

Ο έλεγχος είναι ευρετική με ακριβές ποσοστό αστοχίας. Παλιό αρχείο του οποίου το τυχαίο κλειδί έτυχε να ισούται με το παραγόμενο θα διαβαζόταν με λάθος array, και η πιθανότητα είναι 1 στις 65.536. Το fallback καλύπτει μόνο την περιστροφή του array· ο κανόνας index δεν αλλάζει, οπότε τα αρχεία που σώζει είναι όσα γράφτηκαν μεταξύ v2.384.47 και v2.384.53. Αν ακόμα κρατάτε BIFF5 XOR αρχεία από εκείνο το παράθυρο, ανοίξτε τα με το τρέχον HotXLS και σώστε τα ξανά για να πάρετε αρχείο που η Excel δέχεται

Δύο λεπτομέρειες κωδικού ισχύουν για κάθε αρχείο, παλιό ή νέο:

  • Μήκος. Το CreateXorKey_Method1 διαβάζει μόνο τα πρώτα 15 bytes του κωδικού, που είναι το όριο του specification. Το HotXLS εφαρμόζει εκείνο το πλαφόν στο κλειδί και κρατά τον verifier και το array στους συνηθισμένους κανόνες τους, πλήρους μήκους και 16 byte, συνεπώς και στις δύο πλευρές. Η ίδια η Excel αρνείται κωδικούς πάνω από 15 χαρακτήρες γι’ αυτό το format, οπότε να θεωρείτε το 15 το πραγματικό μέγιστο
  • Σύνολο χαρακτήρων. Το HotXLS μετατρέπει τον κωδικό σε bytes μέσω της ANSI code page του συστήματος. Το specification περιγράφει να παίρνεις το low byte κάθε χαρακτήρα UTF-16, που συμφωνεί για ASCII. Χωρίς δείγματα Excel προστατευμένα με μη-ASCII κωδικούς δεν υπάρχει ground truth για τα υπόλοιπα, οπότε μείνετε σε ASCII κωδικούς για XOR αρχεία

Στην πλευρά ανάγνωσης, το TXLSWorkbook.OnPassword σας αφήνει να ζητήσετε κωδικό όταν το Open συναντά εγγραφή FILEPASS. Το event είναι TXLSPasswordEvent με var PassWord: WideString και var Retry: Boolean; βάλτε Retry σε True για να ξαναδοκιμάσετε, έως τρεις επαναλήψεις:

procedure TImportForm.WorkbookPassword(Sender: TObject;
  var PassWord: WideString; var Retry: Boolean);
var
  S: string;
begin
  S := '';
  Retry := InputQuery('Protected workbook', 'Password:', S);
  PassWord := S;
end;

procedure TImportForm.ImportLegacyFile(const FileName: string);
var
  Wb: IXLSWorkbook;
  Rc: Integer;
begin
  Wb := TXLSWorkbook.Create;
  Wb.OnPassword := WorkbookPassword;
  Rc := Wb.Open(FileName);
  // -1003: χρειάζεται κωδικός αλλά δεν δόθηκε· -1005: λάθος κωδικός
  if Rc <> 1 then
    raise Exception.CreateFmt('Cannot open %s (code %d)', [FileName, Rc]);
  ShowMessage(VarToStr(Wb.Sheets[1].Range['A1', 'A1'].Value));
end;

Αν ξέρετε ήδη τον κωδικό, το Open(FileName, APassWord) προσπερνά τελείως το event

Είναι το XOR obfuscation αρκετά ασφαλές για οτιδήποτε;

Το BIFF XOR obfuscation δεν είναι κρυπτογράφηση και δεν προστατεύει τίποτα από έναν αποφασισμένο αναγνώστη. Ο έλεγχος κωδικού είναι verifier 16 bit, το κλειδί είναι 16 bits, και το array 16 byte επαναλαμβάνεται σε όλο το stream, οπότε προβλέψιμα περιεχόμενα εγγραφών BIFF εκθέτουν bytes του array χωρίς κανέναν κωδικό. Το HotXLS γράφει XOR μόνο επειδή τα αρχεία Excel 5.0/95 δεν έχουν άλλη επιλογή, και ο λόγος να παράγετε τέτοια αρχεία σήμερα είναι ένας legacy καταναλωτής που δεν διαβάζει τίποτα νεότερο

Η classic engine διαλέγει το σχήμα μέσω TXLSWorkbook.EncryptionType, και ο συνδυασμός με το save format ελέγχεται αυστηρά:

  • xletAuto (προεπιλογή) γράφει RC4 CryptoAPI για xlExcel97 και XOR για xlExcel5, ό,τι έγραφε η ίδια η Excel για κάθε format
  • xletXor είναι έγκυρο μόνο για BIFF5· με xlExcel97 η αποθήκευση πετάει exception αντί να πέσει σιωπηλά σε άλλο σχήμα
  • xletRC4 και xletRC4CryptoAPI είναι μόνο BIFF8, και το να τα ζητήσετε σε αποθήκευση BIFF5 πετάει επίσης exception

Το RC4 είναι κι αυτό παλιό, και οι λεπτομέρειες διαλειτουργικότητάς του καλύπτονται στο γιατί η Excel απορρίπτει κρυπτογραφημένο workbook με σωστό κωδικό. Αν ο παραλήπτης διαβάζει XLSX, χρησιμοποιήστε τη XLSX engine: το TXLSXWorkbook.SaveAsEncryptedAgile γράφει Agile Encryption (hashing κωδικού SHA-512 με spin count 100.000 επαναλήψεων και AES-256-CBC), το format που γράφουν εξ ορισμού η Excel 2010 και νεότερες, ενώ το SaveAsEncrypted γράφει την παλαιότερη AES-128 Standard Encryption. Τα trade-offs ανάμεσά τους είναι στο κρυπτογράφηση XLSX αρχείων με AES στο Delphi, και η πλευρά ανάγνωσης καλύπτεται στο ανάγνωση Agile-κρυπτογραφημένων αρχείων Excel με το HotXLS

Σύντομη αναφορά: BIFF XOR obfuscation που δέχεται η Excel 16

  • FILEPASS ($002F) ακολουθεί το globals BOF· σε BIFF5 το body του είναι 4 bytes: κλειδί, μετά verifier
  • Κλειδί = CreateXorKey_Method1(password) κατά [MS-OFFCRYPTO] §2.3.7.2, ποτέ τυχαίο· για secret είναι $014D
  • Array = bytes κωδικού + pad, XOR το χαμηλό byte κλειδιού σε ζυγές θέσεις και το υψηλό σε μονές, μετά XorRor (rotate right 1)
  • Κρυπτογράφησε byte: rotate left 5, μετά XOR· αποκρυπτογράφησε: XOR, μετά rotate right 5 (§2.3.7.3)
  • XorArrayIndex = (byte stream offset + record data length) mod 16· τα headers και τα σκέτα προθέματα μετρούν στο offset
  • Το BOUNDSHEET κρατά τα πρώτα 4 bytes σκέτα· BOF, FILEPASS και INTERFACEHDR μένουν τελείως σκέτα
  • Κωδικοί: ASCII, το πολύ 15 χαρακτήρες
  • HotXLS: index διορθωμένο στο v2.384.47, κλειδί και XorRor διορθωμένα στο v2.384.54, παλαιότερα XOR αρχεία HotXLS αναγνωρίζονται από ασυμφωνία κλειδιού
  • Για πραγματική προστασία χρησιμοποιήστε τουλάχιστον BIFF8 RC4 CryptoAPI, ή XLSX Agile Encryption

Το HotXLS διαχειρίζεται προστασία κωδικού BIFF5 και BIFF8, XLSX Standard και Agile Encryption, και το password callback της ανάγνωσης από μία βιβλιοθήκη Delphi και C++Builder, με τις λεπτομέρειες διαλειτουργικότητας παραπάνω τακτοποιημένες για εσάς. Δείτε το HotXLS Delphi spreadsheet component για εκδόσεις, platforms και δοκιμαστική λήψη