HotXLS kirjoittaa Excel 5.0/95 -työkirjoja (BIFF5) XOR-obfuskaatiolla, ja Excel 16 avaa ne vain, kun kolme yksityiskohtaa vastaa [MS-OFFCRYPTO]a täsmälleen: FILEPASS-avain on oltava CreateXorKey_Method1(password), 16-tavuinen XOR-taulukko on rakennettava XorRorilla (kierto oikealle yhden bitin verran) ja jokaisen tavun on käytettävä arvoa XorArrayIndex = (streamin siirtymä + tietueen pituus) mod 16. HotXLS sai indeksin kohdalleen versiossa v2.384.47 ja avaimen ja kierron versiossa v2.384.54. Sitä ennen jokainen sen tuottama salasanasuojattu BIFF5-tiedosto avautui HotXLS:ssä hyvin ja epäonnistui Excelissä
Tuossa viimeisessä lauseessa on koko tarina pienoiskoossa. Lukija ja kirjoittaja, joilla on sama väärä ajatus, sopivat toisiinsa täydellisesti, joten kierrostestit pysyvät vihreinä, kun ainoa merkittävä kuluttaja sanoo ei. Excel 16 sanoi kahdesti ei, kahdella eri viestillä, ja kumpikin viesti osoitti skeeman eri kerrosta. Tämä artikkeli kulkee kerrosten läpi siinä järjestyksessä, jossa Excel ne tarkistaa, tavutason yksityiskohtineen, joista on hyötyä olitpa sitten kutsumassa HotXLS:ää tai kirjoittamassa omaa BIFF-lukijaasi
Mitä BIFF XOR -obfuskaatio oikeasti tallentaa?
BIFF XOR -obfuskaatio tallentaa tiedostoon vain kaksi 16-bittistä sanaa, ja kaikki muu lasketaan uudelleen salasanasta. FILEPASS-tietue ($002F) istuu heti työkirjan globals-BOF:n perässä, ja BIFF5-tiedostossa sen runko on täsmälleen 4 tavua: XOR-avain, jota seuraa salasanan verifier. Suolaa ei ole, algoritmitunnistetta ei ole, eikä RC4- ja AES-skeemien kaltaista salattua verifier-lohkoa ole
Näistä kahdesta sanasta lukija rakentaa kolme asiaa uudelleen:
- verifierin, salasanatavujen 16-bittisen hashin, joka on XORattu arvolla
$CE4B. Sen vertaaminen tallennettuun sanaan on salasantarkistus, ja ainoa sellainen - XOR-avaimen, 16-bittisen arvon funktiosta
CreateXorKey_Method1kohdassa [MS-OFFCRYPTO] §2.3.7.2, jota kaksi vakiotaulukkoa ohjaa (InitialCode, 15 sanaa, jaXorMatrix, 105 sanaa) - XOR-taulukon, 16 tavua, jotka muodostuvat salasanatavuista täydennettynä kiinteällä 16-tavuisella padilla, kukin XORattuna matalaan avaintavuun (parilliset paikat) tai korkeaan avaintavuun (parittomat paikat), ja sitten kierrettynä oikealle yhden bitin verran
Tietueiden otsikot pysyvät selvätekstinä, samoin muutama kokonainen tietue, jonka skeema vapauttaa, niiden joukossa BOF, FILEPASS ja INTERFACEHDR. Jokainen muu tietuerunko muunnetaan tavu kerrallaan: kierrä vasemmalle 5 bittiä, sitten XOR yhdellä 16-tavaisen taulukon alkiolla. Salauksen purku, jonka [MS-OFFCRYPTO] §2.3.7.3 esittää nimellä DecryptData_Method1, on peilikuva: ensin XOR, sitten kierto oikealle 5
HotXLS:ssä sinun ei tarvitse koskettaa mitään näistä suoraan. Aseta salasana, valitse muoto, ja SaveAs emittoi FILEPASSin ja muuntaa streamin:
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 tukee vain XOR-obfuskaatiota; xletAuto valitsisi senkin
Wb.EncryptionType := xletXor;
// Pidä ASCII:na ja enintään 15 merkkiä (katso alta)
Wb.EncryptionPassword := 'secret';
if Wb.SaveAs(FileName, xlExcel5) <> 1 then
raise Exception.Create('BIFF5 save failed');
end;
Miksi Excel sanoo salasanan väärin, vaikka verifier täsmää?
Excel hylkää salasanan, koska se ei luota tallennettuun avaimeen: Excel johtaa avaimen kirjoitetusta salasanasta funktiolla CreateXorKey_Method1 ja vertaa sitä FILEPASSin avainsanaan, joten tiedosto, jonka avain on jotain muuta, kaatuu salasantarkistukseen, vaikka verifier olisi oikein. Spesifikaatio kuvaa avaimen salasanan tuotokseksi, ei vapaaksi parametriksi, ja Excel 16 valvoo tuota tulkintaa
HotXLS:n kirjoittaja täytti avainsanan kahdella satunnaisella tavulla ennen versiota v2.384.54. Se näyttää paperilla harmittomalta, sillä verifier on dokumentoitu salasantarkistus ja taulukko rakennetaan sillä avaimella, jonka tiedosto julistaa. HotXLS itse luki kyseiset tiedostot ongelmitta, sillä sen lukija otti avaimen FILEPASSista annettuna. Excel 16, sille annettuna sama tiedosto ja oikea salasana, vastasi, ettei salasana ollut oikein. Versiosta v2.384.54 alkaen avain johtuu, joten FILEPASS salasanalle secret sisältää aina avaimen $014D ja verifierin $DAA7, arvot, jotka ristiintarkistettiin spesifikaation riippumattomalla toteutuksella
Itse johtaminen on lyhyt, kun kaksi taulukkoa ovat paikoillaan. Kulje salasana takaperin, katso jokaisen tavun bittiä 6 seitsemän kertaa siirtäen sitä vasemmalle ja XORaa mukaan yksi XorMatrix-alkio aina, kun bitti on asetettu. Seuraava on periaatepiirros, joka toistaa spesifikaation algoritmin ja vastaa HotXLS:n toteutusta; se ei ole HotXLS:n API:
// Periaatepiirros kohdan [MS-OFFCRYPTO] 2.3.7.2 funktiosta CreateXorKey_Method1
// ja funktiosta CreateXorArray_Method1 (vain havainnollistus, ei HotXLS:n API)
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; // avain näkee vain 15 tavua
if Len = 0 then
Exit;
Result := InitialCode[Len - 1];
Element := $68; // viimeinen XorMatrix-alkio
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)); // kierto oikealle yhden bitin verran
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;
Salasanalle secret tämä tuottaa taulukon 1F 32 17 B9 14 BA 7B 7F 59 DD 59 7F 7A C0 A6 DF, kätevä testidata, jos testaat omaa lukijaasi
Miksi oikea avain tuottaa silti vaurioituneen tiedoston?
Oikea avain tuottaa yhä vaurioituneen tiedoston, kun XOR-taulukko kierretään väärään suuntaan: [MS-OFFCRYPTO] määrittelee taulukkovaiheen funktioksi XorRor, kierto oikealle yhden bitin verran, ja vasemmalle kahdella kierretty taulukko purkaa jokaisen tietuerungon kohinaksi. Avaimen korjaus vei Excel 16:n salasanakehotteen ohi suoraan toiseen virheeseen, ilmoitukseen siitä, että tiedostossa on ongelma eikä sitä voi avata
Vanha HotXLS-koodi kierrätti jokaisen taulukon tavun vasemmalle 2 bittiä, muoto, joka kiertää useissa BIFF-toteutuksissa. Koska HotXLS käytti samaa kiertoa molemmin puolin, sen oma lukija ei koskaan huomannut mitään. Excel 16 ei enää osaa tallentaa Excel 5.0/95 -tiedostoja, eikä se tarjoa XOR:ia tallentaessaan BIFF8:aa, joten natiivia Excel-näytettä ei ollut, johon verrata. Todisteiden oli tultava toiseen suuntaan: kirjoita yksi selvätekstinen BIFF5-stream, koodaa se uudelleen kahdeksalla tavalla ja anna Excel 16:n avata kukin variantti. Kahdeksan varianttia ristitti kolme riippumatonta valintaa:
| Valinta | Vaihtoehto A | Vaihtoehto B |
|---|---|---|
| Taulukon kierto | XorRor (kierto oikealle 1) | Kierto vasemmalle 2 |
| Taulukon indeksi | (siirtymä + tietueen pituus) mod 16 | siirtymä mod 16 |
| Tavumuunnoksen järjestys | Kierto vasemmalle 5, sitten XOR | XOR, sitten kierto vasemmalle 5 |
Excel 16 avasi täsmälleen kaksi kahdeksasta: XorRor kiertä-sitten-XORaa-järjestyksellä ja tietueen pituuden indeksillä, sekä yhden variantin, joka vain näyttää erilaiselta. Kierto-vasemmalle-2 XOR-ensin-järjestyksellä ja samalla indeksillä on sama funktio naamioituneena. Kierto distribuoituu XOR:n yli, joten rol5(p xor rol2(b)) on yhtä suuri kuin rol5(p) xor rol7(b), ja 8-bittisellä arvolla kierto vasemmalle 7 on kierto oikealle 1. Lyhyesti, rol5 ∘ rol2 = ror1, mistä syystä kierto-vasemmalle-2-taulukko näyttää eristettynä uskottavalta: se on oikein vain yhdessä vastakkaisen muunnosjärjestyksen kanssa. Spesifikaation järjestykseen paritettuna se turmelee jokaisen muunnetun tavun
Sama koe ratkaisi toisenkin kysymyksen. Variantit, jotka pudottivat tietueen pituuden indeksistä, kaatuivat kaikki, mikä vahvisti indeksisäännön, jonka HotXLS oli omaksunut jakso aiemmin pelkän spesifikaatiotekstin nojalla
Miten XorArrayIndex lasketaan jokaiselle tavulle?
Tavun XorArrayIndex on sen siirtymä työkirjan streamissa plus sen koko tietuedatan pituus, mod 16. Indeksi alkaa siis jokaista tietuetta kohden tietueesta riippuvasta arvosta ja kasvaa yhdellä tavua kohden sen sisällä. Spesifikaation pseudokoodi nimeää syötteet FileOffset ja Data.Length, mikä on helppo lukea väärin pelkäksi tietueen aloitussiirtymäksi, ja juuri tuo väärinlukema oli se, mitä HotXLS toimitti versioon v2.384.47 asti
Kolme yksityiskohtaa ratkaisevat, osuvatko indeksisi yksiin Excelin kanssa:
- 4-tavuista tietueen otsikkoa ei koskaan muunneta, mutta se silti vie streamin paikkoja, joten tietueen ensimmäinen runkotavu istuu kohdassa otsikon siirtymä + 4
- pituustermi on koko tietuedatan pituus, ei varsinaisesti muunnettujen tavujen määrä
- BOUNDSHEET on osittain selvätekstiä: sen ensimmäiset 4 tavua,
lbPlyPos, taulukon BOF:n streamin siirtymä, pysyvät luettavina, jotta jäsentäjä löytää taulukot. Kyseiset 4 tavua ohitetaan muunnoksessa mutta lasketaan silti sekä siirtymään että tietueen pituuteen
Yhdistettynä tietuekohtainen muunnos on muutaman rivin mittainen. Taas kerran: tämä on säännön piirros, ei jotain, mitä sinun tarvitsee kutsua:
function Rol8(B: Byte; N: Integer): Byte;
begin
Result := Byte((B shl N) or (B shr (8 - N)));
end;
// Obfuskoi yhden tietuerungon paikallaan. BodyPos on Body[0]:n streamin
// siirtymä, eli tietueen otsikon siirtymä + 4. PlainPrefix on 4
// BOUNDSHEETille, koko pituus BOF / FILEPASSille, 0 useimmille tietueille
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;
// Lukeminen on peilikuva: B := Body[I] xor Arr[...];
// sitten kierto oikealle 5, eli Rol8(B, 3)
Ennen versiota v2.384.47 HotXLS:n lukija laski indeksin pelkästä streamin sijainnista ja kirjoittaja käytti selvätekstistä tavusiirtymää. Molemmat ohittivat tietueen pituuden, joten taas kerran kaksi puolta sopivat keskenään ja kumman muun kanssa. Riippumaton dekooderi luki version v2.384.47 tuotoksen oikein ja vanhemman tuotoksen roskana, ja kahdeksan variantin Excel 16 -testi vahvisti myöhemmin säännön todellista kohdetta vastaan
Mitä tapahtuu vanhempien HotXLS-versioiden kirjoittamille XOR-tiedostoille?
HotXLS jatkaa omien v2.384.54:ää aiempien XOR-tiedostojensa lukemista tarkistamalla FILEPASS-avaimen: kun tallennettu avain on yhtä suuri kuin salasanasta johdettu avain, lukija rakentaa spesifikaation XorRor-taulukon, ja kun se poikkeaa, lukija käsittelee tiedostoa vanhempana HotXLS-tiedostona ja rakentaa kierto-vasemmalle-2-taulukon uudelleen. Excelin kirjoittamat tiedostot kantavat aina johdettua avainta, joten ne kulkevat aina spesifikaation polkua
Testi on heuristiikka, jolla on tarkka epäonnistumisaste. Vanha tiedosto, jonka satunnainen avain sattui olemaan yhtä suuri kuin johdettu avain, luettaisiin väärällä taulukolla, ja sen todennäköisyys on yksi 65 536:sta. Varapolku kattaa vain taulukon kierron; indeksisääntöä ei vaihdeta, joten sen pelastamat tiedostot ovat v2.384.47:n ja v2.384.53:n välissä kirjoitetut. Jos sinulla on yhä BIFF5 XOR -tiedostoja kyseiseltä aikaväliltä, avaa ne nykyisellä HotXLS:llä ja tallenna ne uudelleen, jotta saat tiedoston, jonka Excel hyväksyy
Kaksi salasanayksityiskohtaa koskee jokaista tiedostoa, vanhaa tai uutta:
- Pituus.
CreateXorKey_Method1lukee vain ensimmäiset 15 salasanatavua, mikä on spesifikaation raja. HotXLS soveltaa kyseistä kattoa avaimeen ja pitää verifierin ja taulukon niissä tavanomaisissa koko pituuden ja 16 tavun säännöissä, yhdenmukaisesti molemmin puolin. Excel itse kieltäytyy yli 15 merkkiä pitkistä salasanoista tälle muodolle, joten käsittele lukua 15 todellisena maksimina - Merkistö. HotXLS muuntaa salasanan tavuiksi järjestelmän ANSI-koodisivun kautta. Spesifikaatio kuvaa kunkin UTF-16-merkin matalan tavun ottamista, mikä täsmää ASCII:lle. Ilman Excel-näytteitä, jotka on suojattu ei-ASCII-salasanalla, ei ole totuusarvoa lopuille, joten pidä kiinni ASCII-salasanoista XOR-tiedostoissa
Lukupuolella TXLSWorkbook.OnPassword antaa sinun kysyä salasanaa, kun Open kohtaa FILEPASS-tietueen. Tapahtuma on TXLSPasswordEvent, jossa on var PassWord: WideString ja var Retry: Boolean; aseta Retry arvoon True yrittääksesi uudelleen, enintään kolme yritystä:
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: salasana vaaditaan mutta ei annettu; -1005: väärä salasana
if Rc <> 1 then
raise Exception.CreateFmt('Cannot open %s (code %d)', [FileName, Rc]);
ShowMessage(VarToStr(Wb.Sheets[1].Range['A1', 'A1'].Value));
end;
Jos tunnet salasanan jo valmiiksi, Open(FileName, APassWord) ohittaa tapahtuman kokonaan
Onko XOR-obfuskaatio riittävän turvallinen mihinkään?
BIFF XOR -obfuskaatio ei ole salausta eikä suojaa mitään motivoitunutta lukijaa vastaan. Salasantarkistus on 16-bittinen verifier, avain on 16 bittiä, ja 16-tavuinen taulukko toistuu koko streamin yli, joten ennakoitavat BIFF-tietueiden sisällöt paljastavat taulukon tavut ilman mitään salasanaa. HotXLS kirjoittaa XOR:in vain siksi, että Excel 5.0/95 -tiedostoilla ei ole muuta vaihtoehtoa, ja syy tuottaa tällaisia tiedostoja tänään on legacy-kuluttaja, joka ei osaa lukea mitään uudempaa
Klassinen moottori valitsee skeeman TXLSWorkbook.EncryptionTypen kautta, ja yhdistelmä tallennusmuodon kanssa tarkistetaan tiukasti:
xletAuto(oletus) kirjoittaa RC4 CryptoAPI:n muodollexlExcel97ja XOR:n muodollexlExcel5, vastaavasti kuin Excel itse kirjoitti kullekin muodollexletXorkelpaa vain BIFF5:lle; muodollaxlExcel97tallennus nostaa poikkeuksen hiljaisen paluun sijaanxletRC4jaxletRC4CryptoAPIovat vain BIFF8:aa, ja niiden pyytäminen BIFF5-tallennuksessa nostaa myös poikkeuksen
RC4 on myös vanhentunut, ja sen yhteentoimivuuden yksityiskohdat on käsitelty artikkelissa miksi Excel hylkää salatun työkirjan oikealla salasanalla. Jos vastaanottaja osaa lukea XLSX:ää, käytä sen sijaan XLSX-moottoria: TXLSXWorkbook.SaveAsEncryptedAgile kirjoittaa Agile Encryptionin (SHA-512-salasanahash 100 000 iteraation spin countilla ja AES-256-CBC), muodon, jonka Excel 2010 ja uudemmat kirjoittavat oletuksena, kun taas SaveAsEncrypted kirjoittaa vanhemman AES-128 Standard Encryptionin. Kahden väliset kompromissit ovat artikkelissa XLSX-tiedostojen salaus AES:llä Delphissä, ja lukupuoli on käsitelty artikkelissa Agile-salattujen Excel-tiedostojen lukeminen HotXLS:llä
Pikaopas: BIFF XOR -obfuskaatio, jonka Excel 16 hyväksyy
- FILEPASS (
$002F) seuraa globals-BOF:ia; BIFF5:ssä sen runko on 4 tavua: avain, sitten verifier - Avain =
CreateXorKey_Method1(password)kohdan [MS-OFFCRYPTO] §2.3.7.2 mukaan, ei koskaan satunnainen; salasanallesecretse on$014D - Taulukko = salasanatavut + pad, XOR matala avaintavu parillisissa paikoissa ja korkea avaintavu parittomissa paikoissa, sitten XorRor (kierto oikealle 1)
- Salaa tavu: kierto vasemmalle 5, sitten XOR; pura: XOR, sitten kierto oikealle 5 (§2.3.7.3)
- XorArrayIndex = (tavun streamin siirtymä + tietuedatan pituus) mod 16; otsikot ja selvätekstiset etuliitteet lasketaan siirtymään
- BOUNDSHEET pitää ensimmäiset 4 tavuaan selvätekstinä; BOF, FILEPASS ja INTERFACEHDR pysyvät kokonaan selvätekstinä
- Salasanat: ASCII, enintään 15 merkkiä
- HotXLS: indeksi korjattu versiossa v2.384.47, avain ja XorRor korjattu versiossa v2.384.54, vanhemmat HotXLS:n XOR-tiedostot havaitaan avainristiriidasta
- Tositoimensuojaan käytä vähintään BIFF8 RC4 CryptoAPI:a tai XLSX Agile Encryptionia
HotXLS hoitaa BIFF5- ja BIFF8-salasanasuojaukset, XLSX:n Standard- ja Agile Encryptionin sekä lukupuolen salasanacallbackin yhdestä Delphi- ja C++Builder-kirjastosta, ja yllä olevat yhteentoimivuuden yksityiskohdat on hoidettu puolestasi. Tutustu HotXLS Delphi -laskentataulukkokomponenttiin saadaksesi versiot, alustat ja kokeilulatauksen