// // Copyright (c) 2008-2011, Kenneth Bell // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // namespace DiscUtils.Registry { using System; using System.Collections.Generic; using System.IO; using System.Security.AccessControl; /// /// A registry hive. /// public sealed class RegistryHive : IDisposable { private const long BinStart = 4 * Sizes.OneKiB; private Stream _fileStream; private Ownership _ownsStream; private HiveHeader _header; private List _bins; /// /// Initializes a new instance of the RegistryHive class. /// /// The stream containing the registry hive. /// /// The created object does not assume ownership of the stream. /// public RegistryHive(Stream hive) : this(hive, Ownership.None) { } /// /// Initializes a new instance of the RegistryHive class. /// /// The stream containing the registry hive. /// Whether the new object assumes object of the stream. public RegistryHive(Stream hive, Ownership ownership) { _fileStream = hive; _fileStream.Position = 0; _ownsStream = ownership; byte[] buffer = Utilities.ReadFully(_fileStream, HiveHeader.HeaderSize); _header = new HiveHeader(); _header.ReadFrom(buffer, 0); _bins = new List(); int pos = 0; while (pos < _header.Length) { _fileStream.Position = BinStart + pos; byte[] headerBuffer = Utilities.ReadFully(_fileStream, BinHeader.HeaderSize); BinHeader header = new BinHeader(); header.ReadFrom(headerBuffer, 0); _bins.Add(header); pos += header.BinSize; } } /// /// Gets the root key in the registry hive. /// public RegistryKey Root { get { return new RegistryKey(this, GetCell(_header.RootCell)); } } /// /// Creates a new (empty) registry hive. /// /// The stream to contain the new hive. /// The new hive. /// /// The returned object does not assume ownership of the stream. /// public static RegistryHive Create(Stream stream) { return Create(stream, Ownership.None); } /// /// Creates a new (empty) registry hive. /// /// The stream to contain the new hive. /// Whether the returned object owns the stream. /// The new hive. public static RegistryHive Create(Stream stream, Ownership ownership) { if (stream == null) { throw new ArgumentNullException("stream", "Attempt to create registry hive in null stream"); } // Construct a file with minimal structure - hive header, plus one (empty) bin BinHeader binHeader = new BinHeader(); binHeader.FileOffset = 0; binHeader.BinSize = (int)(4 * Sizes.OneKiB); HiveHeader hiveHeader = new HiveHeader(); hiveHeader.Length = binHeader.BinSize; stream.Position = 0; byte[] buffer = new byte[hiveHeader.Size]; hiveHeader.WriteTo(buffer, 0); stream.Write(buffer, 0, buffer.Length); buffer = new byte[binHeader.Size]; binHeader.WriteTo(buffer, 0); stream.Position = BinStart; stream.Write(buffer, 0, buffer.Length); buffer = new byte[4]; Utilities.WriteBytesLittleEndian(binHeader.BinSize - binHeader.Size, buffer, 0); stream.Write(buffer, 0, buffer.Length); // Make sure the file is initialized out to the end of the firs bin stream.Position = BinStart + binHeader.BinSize - 1; stream.WriteByte(0); // Temporary hive to perform construction of higher-level structures RegistryHive newHive = new RegistryHive(stream); KeyNodeCell rootCell = new KeyNodeCell("root", -1); rootCell.Flags = RegistryKeyFlags.Normal | RegistryKeyFlags.Root; newHive.UpdateCell(rootCell, true); RegistrySecurity sd = new RegistrySecurity(); sd.SetSecurityDescriptorSddlForm("O:BAG:BAD:PAI(A;;KA;;;SY)(A;CI;KA;;;BA)", AccessControlSections.All); SecurityCell secCell = new SecurityCell(sd); newHive.UpdateCell(secCell, true); secCell.NextIndex = secCell.Index; secCell.PreviousIndex = secCell.Index; newHive.UpdateCell(secCell, false); rootCell.SecurityIndex = secCell.Index; newHive.UpdateCell(rootCell, false); // Ref the root cell from the hive header hiveHeader.RootCell = rootCell.Index; buffer = new byte[hiveHeader.Size]; hiveHeader.WriteTo(buffer, 0); stream.Position = 0; stream.Write(buffer, 0, buffer.Length); // Finally, return the new hive return new RegistryHive(stream, ownership); } /// /// Creates a new (empty) registry hive. /// /// The file to create the new hive in. /// The new hive. public static RegistryHive Create(string path) { return Create(new FileStream(path, FileMode.Create, FileAccess.ReadWrite), Ownership.Dispose); } /// /// Disposes of this instance, freeing any underlying stream (if any). /// public void Dispose() { if (_fileStream != null && _ownsStream == Ownership.Dispose) { _fileStream.Dispose(); _fileStream = null; } } internal K GetCell(int index) where K : Cell { Bin bin = GetBin(index); if (bin != null) { return (K)bin.TryGetCell(index); } else { return null; } } internal void FreeCell(int index) { Bin bin = GetBin(index); if (bin != null) { bin.FreeCell(index); } } internal int UpdateCell(Cell cell, bool canRelocate) { if (cell.Index == -1 && canRelocate) { cell.Index = AllocateRawCell(cell.Size); } Bin bin = GetBin(cell.Index); if (bin != null) { if (bin.UpdateCell(cell)) { return cell.Index; } else if (canRelocate) { int oldCell = cell.Index; cell.Index = AllocateRawCell(cell.Size); bin = GetBin(cell.Index); if (!bin.UpdateCell(cell)) { cell.Index = oldCell; throw new RegistryCorruptException("Failed to migrate cell to new location"); } FreeCell(oldCell); return cell.Index; } else { throw new ArgumentException("Can't update cell, needs relocation but relocation disabled", "canRelocate"); } } else { throw new RegistryCorruptException("No bin found containing index: " + cell.Index); } } internal byte[] RawCellData(int index, int maxBytes) { Bin bin = GetBin(index); if (bin != null) { return bin.ReadRawCellData(index, maxBytes); } else { return null; } } internal bool WriteRawCellData(int index, byte[] data, int offset, int count) { Bin bin = GetBin(index); if (bin != null) { return bin.WriteRawCellData(index, data, offset, count); } else { throw new RegistryCorruptException("No bin found containing index: " + index); } } internal int AllocateRawCell(int capacity) { int minSize = Utilities.RoundUp(capacity + 4, 8); // Allow for size header and ensure multiple of 8 // Incredibly inefficient algorithm... foreach (var binHeader in _bins) { Bin bin = LoadBin(binHeader); int cellIndex = bin.AllocateCell(minSize); if (cellIndex >= 0) { return cellIndex; } } BinHeader newBinHeader = AllocateBin(minSize); Bin newBin = LoadBin(newBinHeader); return newBin.AllocateCell(minSize); } private BinHeader FindBin(int index) { int binsIdx = _bins.BinarySearch(null, new BinFinder(index)); if (binsIdx >= 0) { return _bins[binsIdx]; } return null; } private Bin GetBin(int cellIndex) { BinHeader binHeader = FindBin(cellIndex); if (binHeader != null) { return LoadBin(binHeader); } return null; } private Bin LoadBin(BinHeader binHeader) { _fileStream.Position = BinStart + binHeader.FileOffset; return new Bin(this, _fileStream); } private BinHeader AllocateBin(int minSize) { BinHeader lastBin = _bins[_bins.Count - 1]; BinHeader newBinHeader = new BinHeader(); newBinHeader.FileOffset = lastBin.FileOffset + lastBin.BinSize; newBinHeader.BinSize = Utilities.RoundUp(minSize + newBinHeader.Size, 4 * (int)Sizes.OneKiB); byte[] buffer = new byte[newBinHeader.Size]; newBinHeader.WriteTo(buffer, 0); _fileStream.Position = BinStart + newBinHeader.FileOffset; _fileStream.Write(buffer, 0, buffer.Length); byte[] cellHeader = new byte[4]; Utilities.WriteBytesLittleEndian(newBinHeader.BinSize - newBinHeader.Size, cellHeader, 0); _fileStream.Write(cellHeader, 0, 4); // Update hive with new length _header.Length = newBinHeader.FileOffset + newBinHeader.BinSize; _header.Timestamp = DateTime.UtcNow; _header.Sequence1++; _header.Sequence2++; _fileStream.Position = 0; byte[] hiveHeader = Utilities.ReadFully(_fileStream, _header.Size); _header.WriteTo(hiveHeader, 0); _fileStream.Position = 0; _fileStream.Write(hiveHeader, 0, hiveHeader.Length); // Make sure the file is initialized to desired position _fileStream.Position = BinStart + _header.Length - 1; _fileStream.WriteByte(0); _bins.Add(newBinHeader); return newBinHeader; } private class BinFinder : IComparer { private int _index; public BinFinder(int index) { _index = index; } #region IComparer Members public int Compare(BinHeader x, BinHeader y) { if (x.FileOffset + x.BinSize < _index) { return -1; } else if (x.FileOffset > _index) { return 1; } else { return 0; } } #endregion } } }