public void ShouldFindDataNode() { var dnraw = TestSetup.Bcd.ReadBytesFromHive(0x0000000000001100, 8); var dn = new DataNode(dnraw, 0x0000000000000100); Check.That(dn).IsNotNull(); Check.That(dn.ToString()).IsNotEmpty(); Check.That(dn.Signature).IsEmpty(); }
private DataNode GetDataNodeFromOffset(long relativeOffset) { var dataLenBytes = ReadBytesFromHive(relativeOffset + 4096, 4); var dataLen = BitConverter.ToUInt32(dataLenBytes, 0); var size = (int) dataLen; size = Math.Abs(size); var dn = new DataNode(ReadBytesFromHive(relativeOffset + 4096, size), relativeOffset); return dn; }
/// <summary> /// Associates vk records with NK records and builds a hierarchy of nk records /// <remarks>Results of this method will be available in DeletedRegistryKeys</remarks> /// </summary> private void BuildDeletedRegistryKeys() { _logger.Info("Associating deleted keys and values..."); var unreferencedNKCells = CellRecords.Where(t => t.Value.IsReferenced == false && t.Value is NKCellRecord); var associatedVKRecordOffsets = new List<long>(); var _deletedRegistryKeys = new Dictionary<long, RegistryKey>(); //Phase one is to associate any value records with key records foreach (var unreferencedNkCell in unreferencedNKCells) { try { var nk = unreferencedNkCell.Value as NKCellRecord; _logger.Debug("Processing deleted nk record at absolute offset 0x{0:X}", nk.AbsoluteOffset); nk.IsDeleted = true; var regKey = new RegistryKey(nk, null) { KeyFlags = RegistryKey.KeyFlagsEnum.Deleted }; //some sanity checking on things if (regKey.NKRecord.Size < 0x50 + regKey.NKRecord.NameLength) { continue; } //Build ValueOffsets for this NKRecord if (regKey.NKRecord.ValueListCellIndex > 0) { //there are values for this key, so get the offsets so we can pull them next _logger.Debug("Processing deleted nk record values for nk at absolute offset 0x{0:X}", nk.AbsoluteOffset); DataNode offsetList = null; var size = ReadBytesFromHive(regKey.NKRecord.ValueListCellIndex + 4096, 4); var sizeNum = Math.Abs(BitConverter.ToUInt32(size, 0)); if (sizeNum > regKey.NKRecord.ValueListCount*4 + 4) { //ValueListCount is the number of offsets we should be looking for. they are 4 bytes long //If the size of the data record at regKey.NKRecord.ValueListCellIndex exceeds the total number of bytes plus the size (another 4 bytes), reset it to a more sane value to avoid crazy long reads sizeNum = regKey.NKRecord.ValueListCount*4 + 4; } try { var rawData = ReadBytesFromHive(regKey.NKRecord.ValueListCellIndex + 4096, (int) sizeNum); var dr = new DataNode(rawData, regKey.NKRecord.ValueListCellIndex); offsetList = dr; } catch (Exception) //ncrunch: no coverage { //ncrunch: no coverage //sometimes the data node doesn't have enough data to even do this, or its wrong data _logger.Warn( //ncrunch: no coverage "When getting values for nk record at absolute offset 0x{0:X}, not enough/invalid data was found at offset 0x{1:X}to look for value offsets. Value recovery is not possible", nk.AbsoluteOffset, regKey.NKRecord.ValueListCellIndex); } //ncrunch: no coverage if (offsetList != null) { _logger.Debug("Found offset list for nk at absolute offset 0x{0:X}. Processing.", nk.AbsoluteOffset); try { for (var i = 0; i < regKey.NKRecord.ValueListCount; i++) { //use i * 4 so we get 4, 8, 12, 16, etc var os = BitConverter.ToUInt32(offsetList.Data, i*4); regKey.NKRecord.ValueOffsets.Add(os); } } catch (Exception) //ncrunch: no coverage { //ncrunch: no coverage _logger.Warn( //ncrunch: no coverage "When getting value offsets for nk record at absolute offset 0x{0:X}, not enough data was found at offset 0x{1:X} to look for all value offsets. Only partial value recovery possible", nk.AbsoluteOffset, regKey.NKRecord.ValueListCellIndex); } //ncrunch: no coverage } } _logger.Debug("Looking for vk records for nk record at absolute offset 0x{0:X}", nk.AbsoluteOffset); //For each value offset, get the vk record if it exists, create a KeyValue, and assign it to the current RegistryKey foreach (var valueOffset in nk.ValueOffsets) { if (CellRecords.ContainsKey((long) valueOffset)) { _logger.Debug( "Found vk record at relative offset 0x{0:X} for nk record at absolute offset 0x{1:X}", valueOffset, nk.AbsoluteOffset); var val = CellRecords[(long) valueOffset] as VKCellRecord; //we have a value for this key if (val != null) { //if its an in use record AND referenced, warn if (val.IsFree == false && val.IsReferenced) { _logger.Warn( "When getting values for nk record at absolute offset 0x{0:X}, VK record at relative offset 0x{1:X} isn't free and is referenced by another nk record. Skipping!", nk.AbsoluteOffset, valueOffset); } else { associatedVKRecordOffsets.Add(val.RelativeOffset); var kv = new KeyValue(val); regKey.Values.Add(kv); _logger.Debug( "Added vk record at relative offset 0x{0:X} for nk record at absolute offset 0x{1:X}", valueOffset, nk.AbsoluteOffset); } } } else { _logger.Debug( "vk record at relative offset 0x{0:X} not found for nk record at absolute offset 0x{1:X}", valueOffset, nk.AbsoluteOffset); } } _logger.Debug( "Associated {0:N0} value(s) out of {1:N0} possible values for nk record at absolute offset 0x{2:X}", regKey.Values.Count, nk.ValueListCount, nk.AbsoluteOffset); _deletedRegistryKeys.Add(nk.RelativeOffset, regKey); } catch (Exception ex) //ncrunch: no coverage { //ncrunch: no coverage _logger.Error( //ncrunch: no coverage ex, $"Error while processing deleted nk record at absolute offset 0x{unreferencedNkCell.Value.AbsoluteOffset:X}"); } //ncrunch: no coverage } _logger.Debug("Building tree of key/subkeys for deleted keys"); //DeletedRegistryKeys now contains all deleted nk records and their associated values. //Phase 2 is to build a tree of key/subkeys var matchFound = true; while (matchFound) { var keysToRemove = new List<long>(); matchFound = false; foreach (var deletedRegistryKey in _deletedRegistryKeys) { if (_deletedRegistryKeys.ContainsKey(deletedRegistryKey.Value.NKRecord.ParentCellIndex)) { //deletedRegistryKey is a child of RegistryKey with relative offset ParentCellIndex //add the key as as subkey of its parent var parent = _deletedRegistryKeys[deletedRegistryKey.Value.NKRecord.ParentCellIndex]; _logger.Debug( "Found subkey at absolute offset 0x{0:X} for parent key at absolute offset 0x{1:X}", deletedRegistryKey.Value.NKRecord.AbsoluteOffset, parent.NKRecord.AbsoluteOffset); deletedRegistryKey.Value.KeyPath = $@"{parent.KeyPath}\{deletedRegistryKey.Value.KeyName}"; parent.SubKeys.Add(deletedRegistryKey.Value); //mark the subkey for deletion so we do not blow up the collection while iterating it keysToRemove.Add(deletedRegistryKey.Value.NKRecord.RelativeOffset); //reset this so the loop continutes matchFound = true; } } foreach (var l in keysToRemove) { //take out the key from main collection since we copied it above to its parent's subkey list _deletedRegistryKeys.Remove(l); } } _logger.Debug("Associating top level deleted keys to active Registry keys"); //Phase 3 is looking at top level keys from Phase 2 and seeing if any of those can be assigned to non-deleted keys in the main tree foreach (var deletedRegistryKey in _deletedRegistryKeys) { if (CellRecords.ContainsKey(deletedRegistryKey.Value.NKRecord.ParentCellIndex)) { //an parent key has been located, so get it var parentNk = CellRecords[deletedRegistryKey.Value.NKRecord.ParentCellIndex] as NKCellRecord; _logger.Debug( "Found possible parent key at absolute offset 0x{0:X} for deleted key at absolute offset 0x{1:X}", deletedRegistryKey.Value.NKRecord.ParentCellIndex + 0x1000, deletedRegistryKey.Value.NKRecord.AbsoluteOffset); if (parentNk == null) { //the data at that index is not an nkrecord continue; } if (parentNk.IsReferenced && parentNk.IsFree == false) { //parent exists in our primary tree, so get that key var pk = GetKey(deletedRegistryKey.Value.NKRecord.ParentCellIndex); _logger.Debug( "Copying subkey at absolute offset 0x{0:X} for parent key at absolute offset 0x{1:X}", deletedRegistryKey.Value.NKRecord.AbsoluteOffset, pk.NKRecord.AbsoluteOffset); deletedRegistryKey.Value.KeyPath = $@"{pk.KeyPath}\{deletedRegistryKey.Value.KeyName}"; deletedRegistryKey.Value.KeyFlags |= RegistryKey.KeyFlagsEnum.HasActiveParent; UpdateChildPaths(deletedRegistryKey.Value); //add a copy of deletedRegistryKey under its original parent pk.SubKeys.Add(deletedRegistryKey.Value); RelativeOffsetKeyMap.Add(deletedRegistryKey.Value.NKRecord.RelativeOffset, deletedRegistryKey.Value); if (KeyPathKeyMap.ContainsKey(deletedRegistryKey.Value.KeyPath.ToLowerInvariant()) == false) { KeyPathKeyMap.Add(deletedRegistryKey.Value.KeyPath.ToLowerInvariant(), deletedRegistryKey.Value); } _logger.Debug( "Associated deleted key at absolute offset 0x{0:X} to active parent key at absolute offset 0x{1:X}", deletedRegistryKey.Value.NKRecord.AbsoluteOffset, pk.NKRecord.AbsoluteOffset); } } } DeletedRegistryKeys = _deletedRegistryKeys.Values.ToList(); var unreferencedVk = CellRecords.Where(t => t.Value.IsReferenced == false && t.Value is VKCellRecord); foreach (var keyValuePair in unreferencedVk) { if (associatedVKRecordOffsets.Contains(keyValuePair.Key) == false) { var vk = keyValuePair.Value as VKCellRecord; var val = new KeyValue(vk); UnassociatedRegistryValues.Add(val); } } }
public List <IRecordBase> Process() { var records = new List <IRecordBase>(); //additional cell data starts 32 bytes (0x20) in var offsetInHbin = 0x20; _registryHive.TotalBytesRead += 0x20; while (offsetInHbin < Size) { var recordSize = BitConverter.ToUInt32(_rawBytes, offsetInHbin); var readSize = (int)recordSize; if (!_recoverDeleted && readSize > 0) { //since we do not want to get deleted stuff, if the cell size > 0, its free, so skip it offsetInHbin += readSize; continue; } // if we get a negative number here the record is allocated, but we cant read negative bytes, so get absolute value readSize = Math.Abs(readSize); // _registryHive.Logger.Trace( // $"Getting rawRecord at hbin relative offset 0x{offsetInHbin:X} (Absolute offset: 0x{offsetInHbin + RelativeOffset + 0x1000:X}). readsize: {readSize}"); var rawRecord = new ArraySegment <byte>(_rawBytes, offsetInHbin, readSize).ToArray(); _registryHive.TotalBytesRead += readSize; var cellSignature = Encoding.ASCII.GetString(rawRecord, 4, 2); var cellSignature2 = BitConverter.ToInt16(rawRecord, 4); if (cellSignature2 == LkSignature && rawRecord.Length < 0x50) { //this is not an ln, its data, so update the sig cellSignature2 = 0x0; } //ncrunch: no coverage start if (_registryHive.Logger.IsDebugEnabled) { var foundMatch = false; foundMatch = Regex.IsMatch(cellSignature, @"\A[a-z]{2}\z"); //only process records with 2 letter signatures. this avoids crazy output for data cells if (foundMatch) { // _registryHive.Logger.Trace( // $"Processing {cellSignature} record at hbin relative offset 0x{offsetInHbin:X} (Absolute offset: 0x{offsetInHbin + RelativeOffset + 0x1000:X})"); } else { // _registryHive.Logger.Trace( // $"Processing data record at hbin relative offset 0x{offsetInHbin:X} (Absolute offset: 0x{offsetInHbin + RelativeOffset + 0x1000:X})"); } } //ncrunch: no coverage end ICellTemplate cellRecord = null; IListTemplate listRecord = null; DataNode dataRecord = null; try { switch (cellSignature2) { case LfSignature: case LhSignature: listRecord = new LxListRecord(rawRecord, offsetInHbin + RelativeOffset); break; case LiSignature: listRecord = new LiListRecord(rawRecord, offsetInHbin + RelativeOffset); break; case RiSignature: listRecord = new RiListRecord(rawRecord, offsetInHbin + RelativeOffset); break; case DbSignature: listRecord = new DbListRecord(rawRecord, offsetInHbin + RelativeOffset); break; case LkSignature: cellRecord = new LkCellRecord(rawRecord, offsetInHbin + RelativeOffset); break; //ncrunch: no coverage case NkSignature: if (rawRecord.Length >= 0x30) // the minimum length for a recoverable record { cellRecord = new NkCellRecord(rawRecord.Length, offsetInHbin + RelativeOffset, _registryHive); } break; case SkSignature: if (rawRecord.Length >= 0x14) // the minimum length for a recoverable record { cellRecord = new SkCellRecord(rawRecord, offsetInHbin + RelativeOffset); } break; case VkSignature: if (rawRecord.Length >= 0x18) // the minimum length for a recoverable record { cellRecord = new VkCellRecord(rawRecord.Length, offsetInHbin + RelativeOffset, _minorVersion, _registryHive); } break; default: dataRecord = new DataNode(rawRecord, offsetInHbin + RelativeOffset); break; } } catch (Exception ex) { //check size and see if its free. if so, dont worry about it. too small to be of value, but store it somewhere else? var size = BitConverter.ToInt32(rawRecord, 0); if (size < 0) { //ncrunch: no coverage RegistryHive.HardParsingErrorsInternal += 1; //ncrunch: no coverage _registryHive.Logger.Error( //ncrunch: no coverage ex, $"Hard error processing record with cell signature {cellSignature} at Absolute Offset: 0x{offsetInHbin + RelativeOffset + 4096:X} with raw data: {BitConverter.ToString(rawRecord)}"); //TODO store it somewhere else as a placeholder if its in use. include relative offset and other critical stuff } //ncrunch: no coverage else { _registryHive.Logger.Warn( ex, $"Soft error processing record with cell signature {cellSignature} at Absolute Offset: 0x{offsetInHbin + RelativeOffset + 4096:X} with raw data: {BitConverter.ToString(rawRecord)}"); //This record is marked 'Free' so its not as important of an error RegistryHive.SoftParsingErrorsInternal += 1; } } List <IRecordBase> carvedRecords = null; if (cellRecord != null) { if (cellRecord.IsFree) { carvedRecords = ExtractRecordsFromSlack(cellRecord.RawBytes, cellRecord.RelativeOffset); } else { records.Add((IRecordBase)cellRecord); } } if (listRecord != null) { if (_recoverDeleted) { carvedRecords = ExtractRecordsFromSlack(listRecord.RawBytes, listRecord.RelativeOffset); } records.Add((IRecordBase)listRecord); } if (dataRecord != null && _recoverDeleted) { carvedRecords = ExtractRecordsFromSlack(dataRecord.RawBytes, dataRecord.RelativeOffset); } if (carvedRecords != null) { if (carvedRecords.Count > 0) { records.AddRange(carvedRecords); } } offsetInHbin += readSize; } _rawBytes = null; return(records); }
public List<IRecordBase> Process() { var records = new List<IRecordBase>(); //additional cell data starts 32 bytes (0x20) in var offsetInHbin = 0x20; _registryHive.TotalBytesRead += 0x20; while (offsetInHbin < Size) { var recordSize = BitConverter.ToUInt32(_rawBytes, offsetInHbin); var readSize = (int) recordSize; if (!_recoverDeleted && readSize > 0) { //since we do not want to get deleted stuff, if the cell size > 0, its free, so skip it offsetInHbin += readSize; continue; } // if we get a negative number here the record is allocated, but we cant read negative bytes, so get absolute value readSize = Math.Abs(readSize); var rawRecord = new ArraySegment<byte>(_rawBytes, offsetInHbin, readSize).ToArray();// new byte[readSize]; _registryHive.TotalBytesRead += readSize; var cellSignature = Encoding.ASCII.GetString(rawRecord, 4, 2); var cellSignature2 = BitConverter.ToInt16(rawRecord, 4); //ncrunch: no coverage start if (_registryHive._logger.IsDebugEnabled) { var foundMatch = false; foundMatch = Regex.IsMatch(cellSignature, @"\A[a-z]{2}\z"); //only process records with 2 letter signatures. this avoids crazy output for data cells if (foundMatch) { _registryHive._logger.Debug("Processing {0} record at hbin relative offset 0x{1:X} (Absolute offset: 0x{2:X})", cellSignature, offsetInHbin, offsetInHbin + RelativeOffset + 0x1000); } else { _registryHive._logger.Debug("Processing data record at hbin relative offset 0x{0:X} (Absolute offset: 0x{1:X})", offsetInHbin, offsetInHbin + RelativeOffset + 0x1000); } } //ncrunch: no coverage end ICellTemplate cellRecord = null; IListTemplate listRecord = null; DataNode dataRecord = null; try { switch (cellSignature2) { case LfSignature: case LhSignature: listRecord = new LxListRecord(rawRecord, offsetInHbin + RelativeOffset); break; case LiSignature: listRecord = new LIListRecord(rawRecord, offsetInHbin + RelativeOffset); break; case RiSignature: listRecord = new RIListRecord(rawRecord, offsetInHbin + RelativeOffset); break; case DbSignature: listRecord = new DBListRecord(rawRecord, offsetInHbin + RelativeOffset); break; case LkSignature: cellRecord = new LKCellRecord(rawRecord, offsetInHbin + RelativeOffset); break; //ncrunch: no coverage case NkSignature: if (rawRecord.Length >= 0x30) // the minimum length for a recoverable record { cellRecord = new NKCellRecord(rawRecord.Length, offsetInHbin + RelativeOffset, _registryHive); } break; case SkSignature: if (rawRecord.Length >= 0x14) // the minimum length for a recoverable record { cellRecord = new SKCellRecord(rawRecord, offsetInHbin + RelativeOffset); } break; case VkSignature: if (rawRecord.Length >= 0x18) // the minimum length for a recoverable record { cellRecord = new VKCellRecord(rawRecord.Length, offsetInHbin + RelativeOffset, _minorVersion, _registryHive); } break; default: dataRecord = new DataNode(rawRecord, offsetInHbin + RelativeOffset); break; } } catch (Exception ex) { //check size and see if its free. if so, dont worry about it. too small to be of value, but store it somewhere else? var size = BitConverter.ToInt32(rawRecord, 0); if (size < 0) { //ncrunch: no coverage RegistryHive._hardParsingErrors += 1; //ncrunch: no coverage _registryHive._logger.Error( //ncrunch: no coverage ex,$"Hard error processing record with cell signature {cellSignature} at Absolute Offset: 0x{offsetInHbin + RelativeOffset + 4096:X} with raw data: {BitConverter.ToString(rawRecord)}"); //TODO store it somewhere else as a placeholder if its in use. include relative offset and other critical stuff } //ncrunch: no coverage else { _registryHive._logger.Warn( ex,$"Soft error processing record with cell signature {cellSignature} at Absolute Offset: 0x{offsetInHbin + RelativeOffset + 4096:X} with raw data: {BitConverter.ToString(rawRecord)}"); //This record is marked 'Free' so its not as important of an error RegistryHive._softParsingErrors += 1; } } List<IRecordBase> carvedRecords = null; if (cellRecord != null) { if (cellRecord.IsFree) { carvedRecords = ExtractRecordsFromSlack(cellRecord.RawBytes, cellRecord.RelativeOffset); } else { records.Add((IRecordBase) cellRecord); } } if (listRecord != null) { if (_recoverDeleted) { carvedRecords = ExtractRecordsFromSlack(listRecord.RawBytes, listRecord.RelativeOffset); } records.Add((IRecordBase) listRecord); } if (dataRecord != null && _recoverDeleted) { carvedRecords = ExtractRecordsFromSlack(dataRecord.RawBytes, dataRecord.RelativeOffset); } if (carvedRecords != null) { if (carvedRecords.Count > 0) { records.AddRange(carvedRecords); } } offsetInHbin += readSize; } _rawBytes = null; return records; }