Skip to content

Commit

Permalink
This should fix the registry compare crash. (#42)
Browse files Browse the repository at this point in the history
* This should fix the registry compare crash.

* First trial version of rewritten registry collector.
  • Loading branch information
gfs committed Apr 3, 2019
1 parent 1e522fb commit 0b1adce
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 118 deletions.
7 changes: 3 additions & 4 deletions Cli/Program.cs
Expand Up @@ -603,7 +603,7 @@ private static int RunMonitorCommand(MonitorCommandOptions opts)
}
catch (Exception ex)
{
Logger.Instance.Error(ex, "Error collecting from {0}: {1}", c.GetType().Name, ex.Message);
Logger.Instance.Error(ex, "Error collecting from {0}: {1} {2}", c.GetType().Name, ex.Message, ex.StackTrace);
returnValue = 1;
}
}
Expand Down Expand Up @@ -839,7 +839,7 @@ public static int RunGuiMonitorCommand(MonitorCommandOptions opts)
}
catch (Exception ex)
{
Logger.Instance.Error(ex, "Error collecting from {0}: {1}", c.GetType().Name, ex.Message);
Logger.Instance.Error(ex, "Error collecting from {0}: {1} {2}", c.GetType().Name, ex.Message, ex.StackTrace);
}
}

Expand Down Expand Up @@ -999,8 +999,7 @@ public static int RunCollectCommand(CollectCommandOptions opts)
}
catch (Exception ex)
{
Logger.Instance.Info(ex.Message);
Logger.Instance.Error(ex, "Error collecting from {0}: {1}", c.GetType().Name, ex.Message);
Logger.Instance.Error(ex, "Error collecting from {0}: {1} {2}", c.GetType().Name, ex.Message, ex.StackTrace);
returnValue = 1;
}
Logger.Instance.Info("Completed: {0}", c.GetType().Name);
Expand Down
139 changes: 48 additions & 91 deletions Lib/Collectors/Registry/RegistryCollector.cs
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Threading.Tasks;
using AttackSurfaceAnalyzer.ObjectTypes;
using AttackSurfaceAnalyzer.Utils;
using Microsoft.Data.Sqlite;
Expand All @@ -27,7 +28,7 @@ public class RegistryCollector : BaseCollector
private Action<RegistryObject> customCrawlHandler = null;

private static readonly string SQL_TRUNCATE = "delete from file_system where run_id=@run_id";
private static readonly string SQL_INSERT = "insert into registry (run_id, row_key, key, value, contents, iskey, permissions, serialized) values (@run_id, @row_key, @key, @value, @contents, @iskey, @permissions, @serialized)";
private static readonly string SQL_INSERT = "insert into registry (run_id, row_key, key, value, subkeys, permissions, serialized) values (@run_id, @row_key, @key, @value, @subkeys, @permissions, @serialized)";

public RegistryCollector(string RunId) : this(RunId, DefaultHives, null) { }

Expand Down Expand Up @@ -67,102 +68,36 @@ public override bool CanRunOnPlatform()
public void Write(RegistryObject obj)
{
_numCollected++;

var cmd = new SqliteCommand(SQL_INSERT, DatabaseManager.Connection, DatabaseManager.Transaction);
cmd.Parameters.AddWithValue("@run_id", this.runId);
cmd.Parameters.AddWithValue("@row_key", CryptoHelpers.CreateHash(obj.ToString()));
cmd.Parameters.AddWithValue("@key", obj.Key.Name);
cmd.Parameters.AddWithValue("@value", obj.Value);
cmd.Parameters.AddWithValue("@contents", obj.Contents ?? "");
cmd.Parameters.AddWithValue("@iskey", obj.IsKey);
cmd.Parameters.AddWithValue("@permissions", obj.Key.GetAccessControl().GetSecurityDescriptorSddlForm(AccessControlSections.All));
cmd.Parameters.AddWithValue("@serialized", JsonConvert.SerializeObject(obj));

cmd.ExecuteNonQuery();

if (_numCollected % 1000 == 0)
{
DatabaseManager.Commit();
}

customCrawlHandler?.Invoke(obj);
}

private void AddSubKeysAndValues(RegistryKey key, string path)
{
_keys.Add(key);
var regObj = new RegistryObject(key, true);

Write(regObj);

// Write values under key and commit
foreach (var value in key.GetValueNames())
string hashSeed = String.Format("{0}{1}", obj.Key, JsonConvert.SerializeObject(obj));
using (var cmd = new SqliteCommand(SQL_INSERT, DatabaseManager.Connection, DatabaseManager.Transaction))
{
var Value = key.GetValue(value);
string str = "";

// This is okay. It is a zero-length value
if (Value == null)
cmd.Parameters.AddWithValue("@run_id", this.runId);
cmd.Parameters.AddWithValue("@row_key", CryptoHelpers.CreateHash(hashSeed));
cmd.Parameters.AddWithValue("@key", obj.Key);
cmd.Parameters.AddWithValue("@value", JsonConvert.SerializeObject(obj.Values));
cmd.Parameters.AddWithValue("@subkeys", JsonConvert.SerializeObject(obj.Subkeys));
cmd.Parameters.AddWithValue("@permissions", obj.Permissions);
cmd.Parameters.AddWithValue("@serialized", JsonConvert.SerializeObject(obj));
try
{
// We can leave this empty
cmd.ExecuteNonQuery();
}

else if (Value.ToString() == "System.Byte[]")
catch(Exception e)
{
str = Convert.ToBase64String((System.Byte[])Value);
Logger.Instance.Debug(e.GetType() + "thrown in registry collector");
}

else if (Value.ToString() == "System.String[]")
if (_numCollected % 1000 == 0)
{
str = "";
foreach (String st in (System.String[])Value)
{
str += st;
}
DatabaseManager.Commit();
}

else
{
if (Value.ToString() == Value.GetType().ToString())
{
Logger.Instance.Warn("Uh oh, this type isn't handled. " + Value.ToString());
}
str = Value.ToString();
}

regObj = new RegistryObject(key, value, str, false);
_values.Add(regObj);

Write(regObj);
}

// Do the same for all subkeys
foreach (var subkey in key.GetSubKeyNames())
{
try
{
var next = key.OpenSubKey(subkey, false);
AddSubKeysAndValues(next, path + subkey + "\\");
}
// These are expected as we are running as administrator, not System.
catch (System.Security.SecurityException e)
{
Logger.Instance.Debug(e.GetType() + " " + e.Message + " " + path + subkey);
}
// There seem to be some keys which are listed as existing by the APIs but don't actually exist.
// Unclear if these are just super transient keys or what the other cause might be.
// Since this isn't use actionable, also just supress these to the debug stream.
catch (System.IO.IOException e)
{
Logger.Instance.Debug(e.GetType() + " " + e.Message + " " + path + subkey);
}
catch (Exception e)
{
Logger.Instance.Info(e.GetType() + " " + e.Message + " " + path + subkey);
}
}
customCrawlHandler?.Invoke(obj);
}



public override void Execute()
{
Start();
Expand All @@ -173,12 +108,34 @@ public override void Execute()
}
Truncate(this.runId);

foreach (RegistryHive Hive in Hives)
{
Logger.Instance.Info("Starting on Hive: " + Hive);
var BaseKey = RegistryKey.OpenBaseKey(Hive, RegistryView.Default);
AddSubKeysAndValues(BaseKey, Hive + "\\");
}
Parallel.ForEach(Hives,
(hive =>
{
Logger.Instance.Debug("Starting " + hive.ToString());
var registryInfoEnumerable = RegistryWalker.WalkHive(hive);
try
{
Parallel.ForEach(registryInfoEnumerable,
(registryObject =>
{
try
{
Write(registryObject);
}
catch (Exception e)
{
Logger.Instance.Debug("Walk of {0} fziled", hive.ToString());
Logger.Instance.Debug(e.GetType());
}
}));
}
catch (Exception e)
{
Logger.Instance.Debug(e.GetType());
Logger.Instance.Debug(e.Message);
}
}));

DatabaseManager.Commit();
Stop();
}
Expand Down
9 changes: 4 additions & 5 deletions Lib/Collectors/Registry/RegistryCompare.cs
Expand Up @@ -11,10 +11,10 @@ namespace AttackSurfaceAnalyzer.Collectors.Registry
{
public class RegistryCompare : BaseCompare
{
private static readonly string SELECT_MODIFIED_SQL = @"select a.key as 'a_key', a.serialized as 'a_serialized', a.row_key as 'a_row_key', b.serialized as 'b_serialized', b.row_key as 'b_row_key' from registry a, registry b where a.run_id=@first_run_id and b.run_id=@second_run_id and a.key = b.key and a.value == b.value and a.iskey == b.iskey and (a.contents != b.contents or a.permissions != b.permissions)";
private static readonly string SELECT_MODIFIED_SQL = @"select a.key as 'a_key', a.serialized as 'a_serialized', a.row_key as 'a_row_key', b.serialized as 'b_serialized', b.row_key as 'b_row_key' from registry a, registry b where a.run_id=@first_run_id and b.run_id=@second_run_id and a.key = b.key and (a.row_key != b.row_key)";

private static readonly string SELECT_INSERTED_SQL = "select * from registry b where b.run_id = @second_run_id and key not in (select row_key from registry a where a.run_id = @first_run_id);";
private static readonly string SELECT_DELETED_SQL = "select * from registry a where a.run_id = @first_run_id and key not in (select row_key from registry b where b.run_id = @second_run_id);";
private static readonly string SELECT_INSERTED_SQL = "select * from registry b where b.run_id = @second_run_id and b.key not in (select key from registry a where a.run_id = @first_run_id);";
private static readonly string SELECT_DELETED_SQL = "select * from registry a where a.run_id = @first_run_id and a.key not in (select key from registry b where b.run_id = @second_run_id);";

public RegistryCompare()
{
Expand Down Expand Up @@ -79,8 +79,7 @@ public override void Compare(string firstRunId, string secondRunId)
var obj = new RegistryResult()
{
Base = JsonConvert.DeserializeObject<RegistryObject>(reader["serialized"].ToString()),
BaseRowKey = reader["a_row_key"].ToString(),
CompareRowKey = reader["b_row_key"].ToString(),
BaseRowKey = reader["row_key"].ToString(),
BaseRunId = firstRunId,
CompareRunId = secondRunId,
ResultType = RESULT_TYPE.REGISTRY,
Expand Down
77 changes: 63 additions & 14 deletions Lib/Objects/RegistryObject.cs
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Security.AccessControl;
using AttackSurfaceAnalyzer.Utils;
using Microsoft.Win32;
Expand All @@ -9,12 +11,11 @@ namespace AttackSurfaceAnalyzer.ObjectTypes
public class RegistryObject
{

public RegistryKey Key;

public string Path;
public bool IsKey;
public string Value;
public string Contents;
public string Key;
public Dictionary<string, string> Values;
public List<string> Subkeys;
public string Permissions;


public string RowKey
{
Expand All @@ -24,19 +25,67 @@ public string RowKey
}
}

public override string ToString()
private static List<string> GetSubkeys(RegistryKey key)
{
return string.Format("Key={0}, Value={1}, Contents={2}, IsKey={3}, Permissions={4}", Key.Name, Value, Contents, IsKey, Key.GetAccessControl().GetSecurityDescriptorSddlForm(AccessControlSections.All));
return new List<string>(key.GetSubKeyNames());
}

public RegistryObject(RegistryKey Key, bool isKey) : this(Key, "", "", isKey) { }
private static Dictionary<string, string> GetValues(RegistryKey key)
{
Dictionary<string, string> values = new Dictionary<string, string>();
// Write values under key and commit
foreach (var value in key.GetValueNames())
{
var Value = key.GetValue(value);
string str = "";

// This is okay. It is a zero-length value
if (Value == null)
{
// We can leave this empty
}

public RegistryObject(RegistryKey Key, string Value, string Contents, bool isKey)
else if (Value.ToString() == "System.Byte[]")
{
str = Convert.ToBase64String((System.Byte[])Value);
}

else if (Value.ToString() == "System.String[]")
{
str = "";
foreach (String st in (System.String[])Value)
{
str += st;
}
}

else
{
if (Value.ToString() == Value.GetType().ToString())
{
Logger.Instance.Warn("Uh oh, this type isn't handled. " + Value.ToString());
}
str = Value.ToString();
}
values.Add(value, str);
}
return values;
}

public RegistryObject(RegistryKey Key)
{
this.Key = Key;
this.Value = Value;
this.Contents = Contents;
this.IsKey = isKey;
this.Key = Key.Name;
this.Values = GetValues(Key);
this.Subkeys = GetSubkeys(Key);
this.Permissions = "";
try
{
Permissions = Key.GetAccessControl().GetSecurityDescriptorSddlForm(AccessControlSections.All);
}
catch(Exception e)
{
Logger.Instance.Debug(e.GetType() + " failed to get security descriptor for " + Key.Name);
}
}

public RegistryObject()
Expand Down
17 changes: 13 additions & 4 deletions Lib/Utils/DatabaseManager.cs
Expand Up @@ -17,7 +17,7 @@ public static class DatabaseManager
private static readonly string SQL_CREATE_OPEN_PORT_COLLECTION = "create table if not exists network_ports (run_id text, row_key text, family text, address text, type text, port int, process_name text, serialized text)";
private static readonly string SQL_CREATE_SERVICE_COLLECTION = "create table if not exists win_system_service (run_id text, row_key text, service_name text, display_name text, start_type text, current_state text, serialized text)";
private static readonly string SQL_CREATE_USER_COLLECTION = "create table if not exists user_account (run_id text, row_key text, account_type text, caption text, description text, disabled text, domain text, full_name text, install_date text, local_account text, lockout text, name text, password_changeable text, password_expires text, password_required text, sid text, uid text, gid text, inactive text, home_directory text, shell text, password_storage_algorithm text, properties text data_hash text, serialized text)";
private static readonly string SQL_CREATE_REGISTRY_COLLECTION = "create table if not exists registry (run_id text, row_key text, key text, value text, contents text, iskey text, permissions text, serialized text)";
private static readonly string SQL_CREATE_REGISTRY_COLLECTION = "create table if not exists registry (run_id text, row_key text, key text, value text, subkeys text, permissions text, serialized text)";
private static readonly string SQL_CREATE_CERTIFICATES_COLLECTION = "create table if not exists certificates (run_id text, row_key text, pkcs12 text, store_location text, store_name text, hash text, hash_plus_store text, cert text, cn text, serialized text)";

private static readonly string SQL_CREATE_COMPARE_RESULT_TABLE = "create table if not exists compared (base_run_id text, compare_run_id test, change_type int, base_row_key text, compare_row_key text, data_type int)";
Expand Down Expand Up @@ -114,11 +114,20 @@ public static SqliteTransaction Transaction

public static void Commit()
{
if (_transaction != null)
try
{
_transaction.Commit();
_transaction = null;
if (_transaction != null)
{
_transaction.Commit();
}
}
catch (Exception)
{
Logger.Instance.Debug("Commit collision");
}

_transaction = null;

}

private static string _SqliteFilename = "asa.sqlite";
Expand Down

0 comments on commit 0b1adce

Please sign in to comment.