using System; using System.IO; using SabreTools.Hashing; using SabreTools.IO.Compression.Deflate; using SabreTools.IO.Extensions; using SabreTools.Numerics.Extensions; namespace SabreTools.Wrappers { public partial class WiseSectionHeader : IExtractable { /// public bool Extract(string outputDirectory, bool includeDebug) { // Extract the header-defined files bool extracted = ExtractHeaderDefinedFiles(outputDirectory, includeDebug); if (!extracted) { if (includeDebug) Console.Error.WriteLine("Could not extract header-defined files"); return false; } return true; } // Currently unaware of any NE samples. That said, as they wouldn't have a .WISE section, it's unclear how such // samples could be identified. /// /// Extract the predefined, static files defined in the header /// /// Output directory to write to /// True to include debug data, false otherwise /// True if the files extracted successfully, false otherwise private bool ExtractHeaderDefinedFiles(string outputDirectory, bool includeDebug) { lock (_dataSourceLock) { // Seek to the compressed data offset _dataSource.SeekIfPossible(CompressedDataOffset, SeekOrigin.Begin); bool successful = true; // Extract first executable, if it exists if (ExtractFile("FirstExecutable.exe", outputDirectory, FirstExecutableFileEntryLength, includeDebug) != ExtractionStatus.GOOD) successful = false; // Extract second executable, if it exists // If there's a size provided for the second executable but no size for the first executable, the size of // the second executable appears to be some unrelated value that's larger than the second executable // actually is. Currently unable to extract properly in these cases, as no header value in such installers // seems to actually correspond to the real size of the second executable. if (ExtractFile("SecondExecutable.exe", outputDirectory, SecondExecutableFileEntryLength, includeDebug) != ExtractionStatus.GOOD) successful = false; // Extract third executable, if it exists if (ExtractFile("ThirdExecutable.exe", outputDirectory, ThirdExecutableFileEntryLength, includeDebug) != ExtractionStatus.GOOD) successful = false; // Extract main MSI file if (ExtractFile("ExtractedMsi.msi", outputDirectory, MsiFileEntryLength, includeDebug) != ExtractionStatus.GOOD) { // Fallback- seek to the position that's the length of the MSI file entry from the end, then try and // extract from there. _dataSource.SeekIfPossible(-MsiFileEntryLength + 1, SeekOrigin.End); if (ExtractFile("ExtractedMsi.msi", outputDirectory, MsiFileEntryLength, includeDebug) != ExtractionStatus.GOOD) return false; // The fallback also failed. } return successful; } } /// /// Attempt to extract a file defined by a filename /// /// Output filename, null to auto-generate /// Output directory to write to /// Expected size of the file plus crc32 /// True to include debug data, false otherwise /// Extraction status representing the final state /// Assumes that the current stream position is the end of where the data lives private ExtractionStatus ExtractFile(string filename, string outputDirectory, uint entrySize, bool includeDebug) { if (includeDebug) Console.WriteLine($"Attempting to extract {filename}"); // Extract the file var destination = new MemoryStream(); ExtractionStatus status; if (!(Version is not null && Version[1] == 0x01)) status = ExtractStreamWithChecksum(destination, entrySize, includeDebug); else // hack for Codesited5.exe , very early and very strange. status = ExtractStreamWithoutChecksum(destination, entrySize, includeDebug); // If the extracted data is invalid if (status != ExtractionStatus.GOOD || destination is null) return status; // Ensure the full output directory exists filename = Path.Combine(outputDirectory, filename); var directoryName = Path.GetDirectoryName(filename); if (directoryName is not null && !Directory.Exists(directoryName)) Directory.CreateDirectory(directoryName); // Write the output file File.WriteAllBytes(filename, destination.ToArray()); return status; } /// /// Extract source data with a trailing CRC-32 checksum /// /// Stream where the file data will be written /// Expected size of the file plus crc32 /// True to include debug data, false otherwise /// private ExtractionStatus ExtractStreamWithChecksum(Stream destination, uint entrySize, bool includeDebug) { // Debug output if (includeDebug) Console.WriteLine($"Offset: {_dataSource.Position:X8}, Expected Read: {entrySize}, Expected Write:{entrySize - 4}"); // clamp to zero // Check the validity of the inputs if (entrySize == 0) { if (includeDebug) Console.Error.WriteLine("Not attempting to extract, expected to read 0 bytes"); return ExtractionStatus.GOOD; // If size is 0, then it shouldn't be extracted } else if (entrySize > (_dataSource.Length - _dataSource.Position)) { if (includeDebug) Console.Error.WriteLine($"Not attempting to extract, expected to read {entrySize} bytes but only {_dataSource.Position} bytes remain"); return ExtractionStatus.INVALID; } // Extract the file try { byte[] actual = _dataSource.ReadBytes((int)entrySize - 4); uint expectedCrc32 = _dataSource.ReadUInt32(); // Debug output if (includeDebug) Console.WriteLine($"Expected CRC-32: {expectedCrc32:X8}"); byte[]? hashBytes = HashTool.GetByteArrayHashArray(actual, HashType.CRC32); if (hashBytes is not null) { uint actualCrc32 = BitConverter.ToUInt32(hashBytes, 0); // Debug output if (includeDebug) Console.WriteLine($"Actual CRC-32: {actualCrc32:X8}"); if (expectedCrc32 != actualCrc32) { if (includeDebug) Console.Error.WriteLine("Mismatched CRC-32 values!"); return ExtractionStatus.BAD_CRC; } } destination.Write(actual, 0, actual.Length); return ExtractionStatus.GOOD; } catch { if (includeDebug) Console.Error.WriteLine("Could not extract"); return ExtractionStatus.FAIL; } } /// /// Extract source data without a trailing CRC-32 checksum /// /// Stream where the file data will be written /// Expected size of the file /// True to include debug data, false otherwise /// private ExtractionStatus ExtractStreamWithoutChecksum(Stream destination, uint entrySize, bool includeDebug) { // Debug output if (includeDebug) Console.WriteLine($"Offset: {_dataSource.Position:X8}, Expected Read: {entrySize}, Expected Write:{entrySize - 4}"); // Check the validity of the inputs if (entrySize == 0) { if (includeDebug) Console.Error.WriteLine("Not attempting to extract, expected to read 0 bytes"); return ExtractionStatus.GOOD; // If size is 0, then it shouldn't be extracted } else if (entrySize > (_dataSource.Length - _dataSource.Position)) { if (includeDebug) Console.Error.WriteLine($"Not attempting to extract, expected to read {entrySize} bytes but only {_dataSource.Position} bytes remain"); return ExtractionStatus.INVALID; } // Extract the file try { byte[] actual = _dataSource.ReadBytes((int)entrySize); // Debug output if (includeDebug) Console.WriteLine("No CRC-32!"); destination.Write(actual, 0, actual.Length); return ExtractionStatus.GOOD; } catch { if (includeDebug) Console.Error.WriteLine("Could not extract"); return ExtractionStatus.FAIL; } } } }