public void TestParseRC4CryptoAPIFilePassRecord() { byte[] rc4CryptoApiFilePassRecord = new byte[] { 0x2F, 0x00, 0xC8, 0x00, 0x01, 0x00, 0x04, 0x00, 0x02, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x68, 0x00, 0x00, 0x04, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4D, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6F, 0x00, 0x73, 0x00, 0x6F, 0x00, 0x66, 0x00, 0x74, 0x00, 0x20, 0x00, 0x45, 0x00, 0x6E, 0x00, 0x68, 0x00, 0x61, 0x00, 0x6E, 0x00, 0x63, 0x00, 0x65, 0x00, 0x64, 0x00, 0x20, 0x00, 0x43, 0x00, 0x72, 0x00, 0x79, 0x00, 0x70, 0x00, 0x74, 0x00, 0x6F, 0x00, 0x67, 0x00, 0x72, 0x00, 0x61, 0x00, 0x70, 0x00, 0x68, 0x00, 0x69, 0x00, 0x63, 0x00, 0x20, 0x00, 0x50, 0x00, 0x72, 0x00, 0x6F, 0x00, 0x76, 0x00, 0x69, 0x00, 0x64, 0x00, 0x65, 0x00, 0x72, 0x00, 0x20, 0x00, 0x76, 0x00, 0x31, 0x00, 0x2E, 0x00, 0x30, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x98, 0xC3, 0xA1, 0xA0, 0x9A, 0xC8, 0x92, 0x3D, 0x7A, 0x5D, 0x03, 0x30, 0x34, 0xE8, 0x67, 0x64, 0xA8, 0xA9, 0x29, 0xF0, 0xA6, 0xA8, 0xB6, 0xA1, 0x1F, 0x8C, 0x6F, 0x27, 0xB6, 0xA8, 0x82, 0xEB, 0x14, 0x00, 0x00, 0x00, 0xE8, 0xAF, 0x8B, 0x67, 0x4E, 0x3F, 0xA7, 0xA1, 0x39, 0x9D, 0x9F, 0x2D, 0xDC, 0xB5, 0x1A, 0x33, 0x5D, 0x8A, 0xB2, 0x69 }; VirtualStreamReader vsr = new VirtualStreamReader(new MemoryStream(rc4CryptoApiFilePassRecord)); FilePass filePassRecord = new FilePass(vsr, (RecordType)vsr.ReadUInt16(), vsr.ReadUInt16()); byte[] filePassBytes = filePassRecord.GetBytes(); Assert.AreEqual(rc4CryptoApiFilePassRecord.Length, filePassBytes.Length); Assert.IsFalse(filePassRecord.encryptionHeader.fDocProps); Assert.IsFalse(filePassRecord.encryptionHeader.fCryptoAPI); Assert.IsTrue(filePassRecord.encryptionHeader.fExternal); Assert.IsTrue(filePassRecord.encryptionHeader.fAES); for (int offset = 0; offset < rc4CryptoApiFilePassRecord.Length; offset += 1) { Assert.AreEqual(rc4CryptoApiFilePassRecord[offset], filePassBytes[offset]); } }
public byte[] TransformWorkbookBytes(byte[] bytes, ObfuscationMode mode, string password = XorObfuscation.DefaultPassword) { VirtualStreamReader vsr = new VirtualStreamReader(new MemoryStream(bytes)); MemoryStream ms = new MemoryStream(); BinaryWriter bw = new BinaryWriter(ms); while (vsr.BaseStream.Position < vsr.BaseStream.Length) { BiffHeader bh; bh.id = (RecordType)vsr.ReadUInt16(); //Handle case where RecordId is empty if (bh.id == 0) { break; } bh.length = vsr.ReadUInt16(); //Taken from https://social.msdn.microsoft.com/Forums/en-US/3dadbed3-0e68-4f11-8b43-3a2328d9ebd5/xls-xor-data-transformation-method-1 byte XorArrayIndex = (byte)((vsr.BaseStream.Position + bh.length) % 16); //We remove the FilePass Record for the decrypted document if (mode == ObfuscationMode.Decrypt && bh.id == RecordType.FilePass) { //Skip the remaining FilePass bytes ushort encryptionMode = vsr.ReadUInt16(); if (encryptionMode != 0) { throw new NotImplementedException("FilePass EncryptionMode of " + encryptionMode + " is unsupported."); } ushort key = vsr.ReadUInt16(); ushort verify = vsr.ReadUInt16(); ushort passwordVerify = CreatePasswordVerifier_Method1(password); if (verify != passwordVerify) { throw new ArgumentException( "Incorrect decryption password. Try bruteforcing the password with another tool."); } continue; } ; bw.Write(Convert.ToUInt16(bh.id)); bw.Write(Convert.ToUInt16(bh.length)); //If we're encrypting, then use the byte writer for our current position rather than the read stream if (mode == ObfuscationMode.Encrypt) { XorArrayIndex = (byte)((bw.BaseStream.Position + bh.length) % 16); } //Nothing to decrypt for 0 length if (bh.length == 0) { continue; } switch (bh.id) { case RecordType.BOF: case RecordType.FilePass: case RecordType.UsrExcl: case RecordType.FileLock: case RecordType.InterfaceHdr: case RecordType.RRDInfo: case RecordType.RRDHead: byte[] recordBytes = vsr.ReadBytes(bh.length); bw.Write(recordBytes); //If this is the first BOF record, we inject the appropriate FilePass record if (mode == ObfuscationMode.Encrypt && bh.id == RecordType.BOF && vsr.BaseStream.Position == (bh.length + 4)) { ushort key = CreateXorKey_Method1(password); ushort verify = CreatePasswordVerifier_Method1(password); FilePass filePass = new FilePass(key, verify); byte[] filePassBytes = filePass.GetBytes(); bw.Write(filePassBytes); } continue; case RecordType.BoundSheet8: //Special Case - don't encrypt/decrypt the lbPlyPos Field uint lbPlyPos = vsr.ReadUInt32(); //For encryption we need to adjust this by the added FilePass record length //Decryption we auto-fix afterwards, but encrypted entries don't auto-fix well // if (mode == ObfuscationMode.Encrypt) // { // lbPlyPos += 10; // } bw.Write(lbPlyPos); //Since we are skipping lbPlyPos, we need to update the XorArrayIndex offset as well XorArrayIndex = (byte)((XorArrayIndex + 4) % 16); byte[] remainingBytes = vsr.ReadBytes(bh.length - 4); if (mode == ObfuscationMode.Decrypt) { byte[] decryptedBytes = DecryptData_Method1(password, remainingBytes, XorArrayIndex); bw.Write(decryptedBytes); } else { byte[] encryptedBytes = EncryptData_Method1(password, remainingBytes, XorArrayIndex); bw.Write(encryptedBytes); } continue; default: byte[] preTransformBytes = vsr.ReadBytes(bh.length); if (mode == ObfuscationMode.Decrypt) { byte[] decBytes = DecryptData_Method1(password, preTransformBytes, XorArrayIndex); bw.Write(decBytes); } else { byte[] encBytes = EncryptData_Method1(password, preTransformBytes, XorArrayIndex); bw.Write(encBytes); } continue; } } return(bw.GetBytesWritten()); }