Skip to content

Commit

Permalink
Update to calculate marvin hash for transaction logs to avoid replayi…
Browse files Browse the repository at this point in the history
…ng corrupt logs
  • Loading branch information
EricZimmerman committed Oct 16, 2018
1 parent 81033a6 commit 8f9d278
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 15 deletions.
11 changes: 9 additions & 2 deletions Registry.Test/Registry.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,24 @@
<TargetFrameworks>net461</TargetFrameworks>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net461|AnyCPU'">
<DebugType>full</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="NFluent" Version="2.4.0" />
<PackageReference Include="NLog" Version="4.5.10" />
<PackageReference Include="NUnit" Version="3.11.0" />
</ItemGroup>



<ItemGroup>
<ProjectReference Include="..\Registry\Registry.csproj" />
</ItemGroup>





</Project>

42 changes: 42 additions & 0 deletions Registry.Test/TestTransactionLogs.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,54 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using NLog;
using NLog.Config;
using NLog.Targets;
using NUnit.Framework;

namespace Registry.Test
{
public class TestTransactionLogs
{
[Test]
public void HiveTestAmcache()
{
var config = new LoggingConfiguration();
var loglevel = LogLevel.Trace;

const string layout = @"${message}";

var consoleTarget = new ColoredConsoleTarget();

config.AddTarget("console", consoleTarget);

consoleTarget.Layout = layout;

var rule1 = new LoggingRule("Console", loglevel, consoleTarget);
config.LoggingRules.Add(rule1);



LogManager.Configuration = config;


var hive = @"D:\temp\amcache\amcache.hve";
var hive1 = new RegistryHive(hive);

var log1 = $"{hive}.LOG1";
var log2 = $"{hive}.LOG2";

var logs = new List<string>();
logs.Add(log1);
logs.Add(log2);

var newb = hive1.ProcessTransactionLogs(logs);

var newName = hive + "_NONDIRTY";

File.WriteAllBytes(newName, newb);
}

[Test]
public void HiveTests()
{
Expand Down
14 changes: 8 additions & 6 deletions Registry.sln
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.22609.0
# Visual Studio 15
VisualStudioVersion = 15.0.28010.2046
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Registry", "Registry\Registry.csproj", "{5C40BE24-A89D-4973-B668-2D6E945CB047}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Registry", "Registry\Registry.csproj", "{5C40BE24-A89D-4973-B668-2D6E945CB047}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExampleApp", "ExampleApp\ExampleApp.csproj", "{59EA257D-E4A2-4820-B911-1D6A3C8C70F9}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExampleApp", "ExampleApp\ExampleApp.csproj", "{59EA257D-E4A2-4820-B911-1D6A3C8C70F9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Registry.Test", "Registry.Test\Registry.Test.csproj", "{0E97C9AC-C1FD-482F-A0DC-548F8402562A}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Registry.Test", "Registry.Test\Registry.Test.csproj", "{0E97C9AC-C1FD-482F-A0DC-548F8402562A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -20,7 +20,6 @@ Global
{5C40BE24-A89D-4973-B668-2D6E945CB047}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5C40BE24-A89D-4973-B668-2D6E945CB047}.Release|Any CPU.Build.0 = Release|Any CPU
{59EA257D-E4A2-4820-B911-1D6A3C8C70F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{59EA257D-E4A2-4820-B911-1D6A3C8C70F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{59EA257D-E4A2-4820-B911-1D6A3C8C70F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{59EA257D-E4A2-4820-B911-1D6A3C8C70F9}.Release|Any CPU.Build.0 = Release|Any CPU
{0E97C9AC-C1FD-482F-A0DC-548F8402562A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
Expand All @@ -31,4 +30,7 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3A6C6CBA-DC68-4707-9519-DC2268422BF7}
EndGlobalSection
EndGlobal
3 changes: 3 additions & 0 deletions Registry/Abstractions/RegistryKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ public string GetRegFormat(HiveTypeEnum hiveType)
case HiveTypeEnum.Components:
keyBase = "HKEY_CURRENT_USER\\COMPONENTS";
break;
case HiveTypeEnum.Amcache:
keyBase = "";
break;

default:
keyBase = "HKEY_CURRENT_USER\\UNKNOWN_BASEPATH";
Expand Down
3 changes: 2 additions & 1 deletion Registry/IRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public enum HiveTypeEnum
[Description("USRCLASS")] UsrClass = 6,
[Description("COMPONENTS")] Components = 7,
[Description("DRIVERS")] Drivers = 8,
[Description("BCD")] Bcd = 8
[Description("BCD")] Bcd = 8,
[Description("AMCACHE")] Amcache = 9
}
}
132 changes: 132 additions & 0 deletions Registry/Other/Marvin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace Registry.Other
{
internal static class Marvin
{
/// <summary>
/// Convenience method to compute a Marvin hash and collapse it into a 32-bit hash.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ComputeHash32(ref byte data, int count, ulong seed)
{
long hash64 = ComputeHash(ref data, count, seed);
return ((int)(hash64 >> 32)) ^ (int)hash64;
}

/// <summary>
/// Computes a 64-hash using the Marvin algorithm.
/// </summary>
public static long ComputeHash(ref byte data, int count, ulong seed)
{
uint ucount = (uint)count;
uint p0 = (uint)seed;
uint p1 = (uint)(seed >> 32);

int byteOffset = 0; // declared as signed int so we don't have to cast everywhere (it's passed to Unsafe.Add() and used for nothing else.)

while (ucount >= 8)
{
p0 += Unsafe.As<byte, uint>(ref Unsafe.Add(ref data, byteOffset));
Block(ref p0, ref p1);

p0 += Unsafe.As<byte, uint>(ref Unsafe.Add(ref data, byteOffset + 4));
Block(ref p0, ref p1);

byteOffset += 8;
ucount -= 8;
}

switch (ucount)
{
case 4:
p0 += Unsafe.As<byte, uint>(ref Unsafe.Add(ref data, byteOffset));
Block(ref p0, ref p1);
goto case 0;

case 0:
p0 += 0x80u;
break;

case 5:
p0 += Unsafe.As<byte, uint>(ref Unsafe.Add(ref data, byteOffset));
byteOffset += 4;
Block(ref p0, ref p1);
goto case 1;

case 1:
p0 += 0x8000u | Unsafe.Add(ref data, byteOffset);
break;

case 6:
p0 += Unsafe.As<byte, uint>(ref Unsafe.Add(ref data, byteOffset));
byteOffset += 4;
Block(ref p0, ref p1);
goto case 2;

case 2:
p0 += 0x800000u | Unsafe.As<byte, ushort>(ref Unsafe.Add(ref data, byteOffset));
break;

case 7:
p0 += Unsafe.As<byte, uint>(ref Unsafe.Add(ref data, byteOffset));
byteOffset += 4;
Block(ref p0, ref p1);
goto case 3;

case 3:
p0 += 0x80000000u | (((uint)(Unsafe.Add(ref data, byteOffset + 2))) << 16)| (uint)(Unsafe.As<byte, ushort>(ref Unsafe.Add(ref data, byteOffset)));
break;

default:
Debug.Fail("Should not get here.");
break;
}

Block(ref p0, ref p1);
Block(ref p0, ref p1);

return (((long)p1) << 32) | p0;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Block(ref uint rp0, ref uint rp1)
{
uint p0 = rp0;
uint p1 = rp1;

p1 ^= p0;
p0 = _rotl(p0, 20);

p0 += p1;
p1 = _rotl(p1, 9);

p1 ^= p0;
p0 = _rotl(p0, 27);

p0 += p1;
p1 = _rotl(p1, 19);

rp0 = p0;
rp1 = p1;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint _rotl(uint value, int shift)
{
// This is expected to be optimized into a single rol (or ror with negated shift value) instruction
return (value << shift) | (value >> (32 - shift));
}

public static ulong DefaultSeed => s_defaultSeed;

private static ulong s_defaultSeed = 0x82EF4D887A4E55C5;

}
}
10 changes: 10 additions & 0 deletions Registry/Registry.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,19 @@
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
<DebugType>full</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net461|AnyCPU'">
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="NFluent" Version="2.4.0" />
<PackageReference Include="NLog" Version="4.5.10" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.3.0" />
</ItemGroup>

</Project>
3 changes: 3 additions & 0 deletions Registry/RegistryBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ internal void Initialize()
case "bcd":
HiveType = HiveTypeEnum.Bcd;
break;
case "amcache.hve":
HiveType = HiveTypeEnum.Amcache;
break;
default:
HiveType = HiveTypeEnum.Other;
break;
Expand Down
12 changes: 10 additions & 2 deletions Registry/TransactionLog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ private void Initialize()
case "bcd":
HiveType = HiveTypeEnum.Bcd;
break;
case "amcache.hve":
HiveType = HiveTypeEnum.Amcache;
break;
default:
HiveType = HiveTypeEnum.Other;
break;
Expand All @@ -164,7 +167,7 @@ public bool ParseLog()
throw new Exception("ParseLog already called");
}

var index = 0x200; //data starts at offset 500 decimal
var index = 0x200; //data starts at offset 512 decimal

while (index < FileBytes.Length)
{
Expand Down Expand Up @@ -197,13 +200,18 @@ public bool ParseLog()
/// </summary>
/// <param name="hiveBytes"></param>
/// <remarks>This method does nothing to determine IF the data should be overwritten</remarks>
/// <returns>Byte array containing the updaated hive</returns>
/// <returns>Byte array containing the updated hive</returns>
public byte[] UpdateHiveBytes(byte[] hiveBytes)
{
const int baseOffset = 0x1000; //hbins start at 4096 bytes

foreach (var transactionLogEntry in TransactionLogEntries)
{
if (transactionLogEntry.HasValidHashes() == false)
{
Logger.Debug($"Skipping transaction log entry (Hash verification failed): {transactionLogEntry}");
continue;
}
Logger.Debug($"Processing log entry: {transactionLogEntry}");
foreach (var dirtyPage in transactionLogEntry.DirtyPages)
{
Expand Down
Loading

0 comments on commit 8f9d278

Please sign in to comment.