From 9921f02ceea011506193acfba190aa0840563423 Mon Sep 17 00:00:00 2001 From: Neil Simmons Date: Sun, 25 Oct 2020 11:31:49 -0400 Subject: [PATCH] changes to allow compatibility with ClrMdV2 --- ClrMD.Extensions/ClrDynamic.cs | 139 +++++++++++++++---- ClrMD.Extensions/ClrMD.Extensions.csproj | 2 +- ClrMD.Extensions/ClrMDExtensions.cs | 71 ++++++++-- ClrMD.Extensions/ClrMDSession.cs | 47 ++++--- ClrMD.Extensions/Obfuscation/Deobfuscator.cs | 4 +- 5 files changed, 203 insertions(+), 60 deletions(-) diff --git a/ClrMD.Extensions/ClrDynamic.cs b/ClrMD.Extensions/ClrDynamic.cs index 9cc4f4d..96abe84 100644 --- a/ClrMD.Extensions/ClrDynamic.cs +++ b/ClrMD.Extensions/ClrDynamic.cs @@ -5,6 +5,7 @@ using System.Dynamic; using System.Linq; using System.Net; +using System.Net.Http.Headers; using System.Text; using System.Text.RegularExpressions; using ClrMD.Extensions.LINQPad; @@ -60,7 +61,8 @@ public ClrDynamic this[int arrayIndex] if (!Type.IsArray) throw new InvalidOperationException(string.Format("Type '{0}' is not an array", Type.Name)); - int arrayLength = Type.GetArrayLength(Address); + //int arrayLength = Type.GetArrayLength(Address); + int arrayLength = Heap.GetObject(Address).AsArray().Length; if (arrayIndex >= arrayLength) throw new IndexOutOfRangeException(string.Format("Array index '{0}' is not between 0 and '{1}'", arrayIndex, arrayLength)); @@ -78,7 +80,8 @@ public int ArrayLength if (!Type.IsArray) throw new InvalidOperationException(string.Format("Type '{0}' is not an array", Type.Name)); - return Type.GetArrayLength(Address); + //return Type.GetArrayLength(Address); + return Heap.GetObject(Address).AsArray().Length; ; } } @@ -91,12 +94,14 @@ public object SimpleDisplayValue get { if (Type.IsEnum) - return Type.GetEnumName(SimpleValue) ?? SimpleValue.ToString(); + { + return Type.AsEnum().EnumerateValues().FirstOrDefault(f => Convert.ToInt64(f.Value) == Convert.ToInt64(SimpleValue)).Name ?? SimpleValue.ToString(); + } return SimpleValue ?? "{null}"; } } - public ulong Size => Type.GetSize(Address); + public ulong Size => Heap.GetObjectSize(Address, Type); public dynamic Dynamic => this; @@ -130,8 +135,11 @@ public bool IsUndefined() private static ClrInstanceField FindField(ObfuscatedField oField) { - TypeName delcaringType = ClrMDSession.Current.ObfuscateType(oField.DeclaringType); - var target = ClrMDSession.Current.Heap.GetTypeByName(delcaringType); + TypeName declaringType = ClrMDSession.Current.ObfuscateType(oField.DeclaringType); + + var heapTypes = ClrMDSession.Current.Heap.EnumerateTypes(); + + var target = heapTypes.First(type => type.Name == declaringType); return target.GetFieldByName(oField.ObfuscatedName); } @@ -239,10 +247,8 @@ public IEnumerable EnumerateReferences() public IEnumerable EnumerateReferencesAddress() { - List references = new List(); - - Type.EnumerateRefsOfObject(Address, (objRef, fieldOffset) => references.Add(objRef)); - return references; + var refs = Heap.GetObject(Address).EnumerateReferences().Select(r => r.Address); + return refs; } public IEnumerable EnumerateDictionaryValues() @@ -274,8 +280,8 @@ private ClrDynamic GetInnerObject(ulong pointer, ClrType type) if (type.IsObjectReference) { - Type.Heap.ReadPointer(pointer, out fieldAddress); - + Type.Heap.Runtime.DataTarget.DataReader.ReadPointer(pointer, out fieldAddress); + //Console.WriteLine(fieldAddress); if (!type.IsSealed && fieldAddress != NullAddress) actualType = type.Heap.GetSafeObjectType(fieldAddress); } @@ -284,18 +290,19 @@ private ClrDynamic GetInnerObject(ulong pointer, ClrType type) // Unfortunately, ClrType.GetValue for primitives assumes that the value is boxed, // we decrement PointerSize because it will be added when calling ClrType.GetValue. // ClrMD should be updated in a future version to include ClrType.GetValue(int interior). - fieldAddress = pointer - (ulong)type.Heap.PointerSize; + + fieldAddress = pointer - (ulong)type.Heap.Runtime.DataTarget.DataReader.PointerSize; } - else if (type.IsValueClass) + else if (type.IsValueType) { fieldAddress = pointer; } else { - throw new NotSupportedException(string.Format("Object type not supported '{0}'", type.Name)); + throw new NotSupportedException($"Object type not supported '{type.Name}'"); } - return new ClrDynamic(fieldAddress, actualType, !type.IsObjectReference); + return new ClrDynamic(fieldAddress, actualType,!type.IsObjectReference); } #region Operators @@ -305,7 +312,7 @@ public override bool Equals(object other) if (HasSimpleValue) { if (other is string && Type.IsEnum) - return Equals(other, Type.GetEnumName(SimpleValue)); + return Equals(other, Type.AsEnum().EnumerateValues().FirstOrDefault(f => Convert.ToInt64(f.Value) == Convert.ToInt64(SimpleValue)).Name); return Equals(other, SimpleValue); } @@ -460,7 +467,7 @@ public static explicit operator double(ClrDynamic obj) public static explicit operator string(ClrDynamic obj) { if (obj.Type.IsEnum) - return obj.Type.GetEnumName(obj.SimpleValue); + return obj.Type.AsEnum().EnumerateValues().FirstOrDefault(f => (int)f.Value == (int)obj.SimpleValue).Name; return (string)obj.SimpleValue; } @@ -670,10 +677,92 @@ public static object GetSimpleValue(ClrDynamic obj) ClrType type = obj.Type; ClrHeap heap = type.Heap; - if (type.IsPrimitive || type.IsString) - return type.GetValue(obj.Address); + ulong unboxedAddress = obj.Address + (ulong)heap.Runtime.DataTarget.DataReader.PointerSize; + + switch (type.ElementType) + { + case ClrElementType.String: + { + return heap.GetObject(obj.Address).AsString(); + } + + case ClrElementType.Boolean: + { + return heap.Runtime.DataTarget.DataReader.Read(unboxedAddress); + } + + case ClrElementType.Int8: + { + return heap.Runtime.DataTarget.DataReader.Read(unboxedAddress); + } + + case ClrElementType.Int16: + { + return heap.Runtime.DataTarget.DataReader.Read(unboxedAddress); + } + + case ClrElementType.Int32: + { + return heap.Runtime.DataTarget.DataReader.Read(unboxedAddress); + } + + case ClrElementType.Int64: + { + return heap.Runtime.DataTarget.DataReader.Read(unboxedAddress); + } + + case ClrElementType.UInt8: + { + return heap.Runtime.DataTarget.DataReader.Read(unboxedAddress); + } + case ClrElementType.UInt16: + { + return heap.Runtime.DataTarget.DataReader.Read(unboxedAddress); + } + + case ClrElementType.UInt32: + { + return heap.Runtime.DataTarget.DataReader.Read(unboxedAddress); + } + + case ClrElementType.UInt64: + { + return heap.Runtime.DataTarget.DataReader.Read(unboxedAddress); + } + + case ClrElementType.NativeInt: + { + heap.Runtime.DataTarget.DataReader.ReadPointer(unboxedAddress, out var value); + return (long)value; + } + + case ClrElementType.FunctionPointer: + case ClrElementType.Pointer: + case ClrElementType.NativeUInt: + { + heap.Runtime.DataTarget.DataReader.ReadPointer(unboxedAddress, out var value); + return value; + } + + case ClrElementType.Float: + { + return heap.Runtime.DataTarget.DataReader.Read(unboxedAddress); + } + + case ClrElementType.Double: + { + return heap.Runtime.DataTarget.DataReader.Read(unboxedAddress); + } + + case ClrElementType.Char: + { + return heap.Runtime.DataTarget.DataReader.Read(unboxedAddress); + } + + + } - ulong address = obj.IsInterior ? obj.Address : obj.Address + (ulong)heap.PointerSize; + ulong address = obj.IsInterior ? obj.Address : obj.Address + (ulong)heap.Runtime.DataTarget.DataReader.PointerSize; switch (type.Name) { @@ -715,7 +804,7 @@ public static string GetSimpleValueString(ClrDynamic obj) ClrType type = obj.Type; if (type != null && type.IsEnum) - return type.GetEnumName(value) ?? value.ToString(); + return type.AsEnum().EnumerateValues().FirstOrDefault(f => Convert.ToInt64(value) == Convert.ToInt64(f.Value)).Name ?? value.ToString(); DateTime? dateTime = value as DateTime?; if (dateTime != null) @@ -726,13 +815,13 @@ public static string GetSimpleValueString(ClrDynamic obj) private static byte[] ReadBuffer(ClrHeap heap, ulong address, int length) { - byte[] buffer = new byte[length]; - int byteRead = heap.ReadMemory(address, buffer, 0, buffer.Length); + Span buffer = new byte[length]; + int byteRead = heap.Runtime.DataTarget.DataReader.Read(address, buffer); if (byteRead != length) throw new InvalidOperationException(string.Format("Expected to read {0} bytes and actually read {1}", length, byteRead)); - return buffer; + return buffer.ToArray(); } private static DateTime GetDateTime(ulong dateData) diff --git a/ClrMD.Extensions/ClrMD.Extensions.csproj b/ClrMD.Extensions/ClrMD.Extensions.csproj index e6a55c9..14ed6ff 100644 --- a/ClrMD.Extensions/ClrMD.Extensions.csproj +++ b/ClrMD.Extensions/ClrMD.Extensions.csproj @@ -17,8 +17,8 @@ - + diff --git a/ClrMD.Extensions/ClrMDExtensions.cs b/ClrMD.Extensions/ClrMDExtensions.cs index c0a7c16..2d1eece 100644 --- a/ClrMD.Extensions/ClrMDExtensions.cs +++ b/ClrMD.Extensions/ClrMDExtensions.cs @@ -27,13 +27,13 @@ public static ClrDynamic GetDynamicObject(this ClrHeap heap, ulong address) public static IEnumerable EnumerateDynamicObjects(this ClrHeap heap) { - return from address in heap.EnumerateObjectAddresses() + return from address in heap.EnumerateObjects().Select(f => f.Address) select heap.GetDynamicObject(address); } public static IEnumerable EnumerateDynamicObjects(this ClrHeap heap, ClrType type) { - return from address in heap.EnumerateObjectAddresses() + return from address in heap.EnumerateObjects().Select(f => f.Address) let objectType = heap.GetSafeObjectType(address) where objectType == type select new ClrDynamic(address, type); @@ -59,7 +59,7 @@ public static IEnumerable EnumerateDynamicObjects(this ClrHeap heap, HashSet set = new HashSet(castedTypes); - return from address in heap.EnumerateObjectAddresses() + return from address in heap.EnumerateObjects().Select(f => f.Address) let type = heap.GetSafeObjectType(address) where set.Contains(type) select new ClrDynamic(address, type); @@ -67,10 +67,16 @@ where set.Contains(type) public static IEnumerable EnumerateDynamicObjects(this ClrHeap heap, string typeName) { + var heapTypes = heap.EnumerateTypes(); + //(from objects in heap.EnumerateObjects() + //group objects by objects.Type into tn + // select (tn.Key) + //); + if (!typeName.Contains("*")) { var type = - (from t in heap.EnumerateTypes() + (from t in heapTypes let deobfuscator = ClrMDSession.Current.GetTypeDeobfuscator(t) where deobfuscator.OriginalName == typeName select t).First(); @@ -82,7 +88,7 @@ public static IEnumerable EnumerateDynamicObjects(this ClrHeap heap, RegexOptions.Compiled | RegexOptions.IgnoreCase); var types = - from type in heap.EnumerateTypes() + from type in heapTypes let deobfuscator = ClrMDSession.Current.GetTypeDeobfuscator(type) where regex.IsMatch(deobfuscator.OriginalName) select type; @@ -107,10 +113,10 @@ public static List GetDetailedStackTrace(this ClrThread thread) { List stackframes = new List(); - List stackObjects = thread.EnumerateStackObjects().ToList(); + List stackObjects = thread.EnumerateStackRoots().ToList(); ulong lastAddress = 0; - foreach (ClrStackFrame frame in thread.StackTrace) + foreach (ClrStackFrame frame in thread.EnumerateStackTrace()) { ClrStackFrame f = frame; List objectsInFrame = stackObjects @@ -121,7 +127,7 @@ public static List GetDetailedStackTrace(this ClrThread thread) stackframes.Add(new StackFrameInfo { - Function = f.DisplayString, + Function = f.Method.Name, Objects = objectsInFrame }); @@ -134,10 +140,18 @@ public static List GetDetailedStackTrace(this ClrThread thread) public static string GetStackTrace(this ClrThread thread) { StringBuilder builder = new StringBuilder(); + int count = 0; + foreach (ClrStackFrame frame in thread.EnumerateStackTrace()) + { - foreach (ClrStackFrame frame in thread.StackTrace) - builder.AppendLine(frame.DisplayString); + if (frame != null) + builder.AppendLine($"{frame}"); + count++; + if (count == 100) break; + } + + string stack = builder.ToString(); return ClrMDSession.Current.DeobfuscateStack(stack); } @@ -171,6 +185,8 @@ public static ClrType GetDeclaringType(this ClrField field, ClrType containingTy return containingType; } + + } public class ClrObjectWrapper @@ -187,4 +203,39 @@ public ClrObjectWrapper(ClrDynamic item) Object = item; } } + + public static class EnumerateTypesExtension + { + /// + /// Enumerates types with constructed method tables in all modules. + /// + /// + /// + +//this is in clrmd master branch but didn't seem to be published in the nuget yet, so I added it here. + public static IEnumerable EnumerateTypes(this ClrHeap heap) + { + if (heap is null) + throw new ArgumentNullException(nameof(heap)); + + // The ClrHeap actually doesn't know anything about 'types' in the strictest sense, that's + // all tracked by the runtime. First, grab the runtime object: + + ClrRuntime runtime = heap.Runtime; + + // Now we loop through every module and grab every constructed MethodTable + foreach (ClrModule module in runtime.EnumerateModules()) + { + foreach ((ulong mt, int _) in module.EnumerateTypeDefToMethodTableMap()) + { + // Now try to construct a type for mt. This may fail if the type was only partially + // loaded, dump inconsistency, and in some odd corner cases like transparent proxies: + ClrType type = runtime.GetTypeByMethodTable(mt); + + if (type != null) + yield return type; + } + } + } + } } \ No newline at end of file diff --git a/ClrMD.Extensions/ClrMDSession.cs b/ClrMD.Extensions/ClrMDSession.cs index c6d2387..7ed2466 100644 --- a/ClrMD.Extensions/ClrMDSession.cs +++ b/ClrMD.Extensions/ClrMDSession.cs @@ -33,25 +33,19 @@ public class ClrMDSession : IDisposable public bool IsReferenceMappingCreated { get; private set; } internal ClrType ErrorType { get; private set; } - + private ClrMDSession(DataTarget target, string dacFile) { ClrMDSession.Detach(); - if (target.ClrVersions.Count == 0) + if (target.ClrVersions.Count() == 0) throw new ArgumentException("DataTarget has no clr loaded.", nameof(target)); Target = target; Runtime = dacFile == null ? target.ClrVersions[0].CreateRuntime() : target.ClrVersions[0].CreateRuntime(dacFile); Heap = Runtime.Heap; - //Temp hack until ErrorType is made public - var property = Heap.GetType().GetProperty("ErrorType", BindingFlags.Instance | BindingFlags.NonPublic); - - if (property == null) - throw new InvalidOperationException("Unable to find 'ErrorType' property on ClrHeap."); - - ErrorType = (ClrType)property.GetValue(Heap); + ErrorType = null; //I'm not sure if this is the right thing, but ErrorType doesn't exist in ClrMD 2.0 m_allObjects = CreateLazyAllObjects(); @@ -73,12 +67,12 @@ public static ClrMDSession LoadCrashDump(string dumpPath, string dacFile = null) Detach(); - DataTarget target = DataTarget.LoadCrashDump(dumpPath); + DataTarget target = DataTarget.LoadDump(dumpPath); try { - if (target.Architecture == Architecture.X86 && Environment.Is64BitProcess || - target.Architecture == Architecture.Amd64 && !Environment.Is64BitProcess) + if (target.DataReader.Architecture == Architecture.X86 && Environment.Is64BitProcess || + target.DataReader.Architecture == Architecture.Amd64 && !Environment.Is64BitProcess) { throw new InvalidOperationException("Mismatched architecture between this process and the target dump."); } @@ -93,27 +87,27 @@ public static ClrMDSession LoadCrashDump(string dumpPath, string dacFile = null) return new ClrMDSession(target, dacFile); } - public static ClrMDSession AttachToProcess(string processName, uint millisecondsTimeout = 5000, AttachFlag attachFlag = AttachFlag.Invasive) + public static ClrMDSession AttachToProcess(string processName) { Process p = Process.GetProcessesByName(processName).FirstOrDefault(); if (p == null) throw new ArgumentException("Process not found", "processName"); - return AttachToProcess(p, millisecondsTimeout, attachFlag); + return AttachToProcess(p); } - public static ClrMDSession AttachToProcess(int pid, uint millisecondsTimeout = 5000, AttachFlag attachFlag = AttachFlag.Invasive) + public static ClrMDSession AttachToProcess(int pid) { Process p = Process.GetProcessById(pid); if (p == null) throw new ArgumentException("Process not found", "pid"); - return AttachToProcess(p, millisecondsTimeout, attachFlag); + return AttachToProcess(p); } - public static ClrMDSession AttachToProcess(Process p, uint millisecondsTimeout = 5000, AttachFlag attachFlag = AttachFlag.Invasive) + public static ClrMDSession AttachToProcess(Process p) { if (s_currentSession != null && s_lastProcessId == p.Id) { @@ -123,7 +117,7 @@ public static ClrMDSession AttachToProcess(Process p, uint millisecondsTimeout = Detach(); - DataTarget target = DataTarget.AttachToProcess(p.Id, millisecondsTimeout, attachFlag); + DataTarget target = DataTarget.AttachToProcess(p.Id, true); s_lastProcessId = p.Id; return new ClrMDSession(target, null); } @@ -176,8 +170,16 @@ public IEnumerable EnumerateDynamicObjects(string typeName) var regex = new Regex($"^{Regex.Escape(typeName).Replace("\\*", ".*")}$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + + var heapTypes = Heap.EnumerateTypes(); + //(from objects in ClrMDSession.Current.Heap.EnumerateObjects() + //group objects by objects.Type into tn + //select (tn.Key) + //); + var types = - from type in Heap.EnumerateTypes() + from type in heapTypes let deobfuscator = GetTypeDeobfuscator(type) where regex.IsMatch(deobfuscator.OriginalName) select type; @@ -322,9 +324,10 @@ private static void TestInvalidComObjectException() { try { - byte[] dummy = new byte[8]; - int bytesRead; - s_currentSession.Runtime.ReadMemory(0, dummy, 8, out bytesRead); + Span dummy = new byte[8]; + //int bytesRead; + s_currentSession.Target.DataReader.Read(0,dummy); + //s_currentSession.Runtime.ReadMemory(0, dummy, 8, out bytesRead); } catch (System.Runtime.InteropServices.InvalidComObjectException ex) { diff --git a/ClrMD.Extensions/Obfuscation/Deobfuscator.cs b/ClrMD.Extensions/Obfuscation/Deobfuscator.cs index f0ae2a1..5481947 100644 --- a/ClrMD.Extensions/Obfuscation/Deobfuscator.cs +++ b/ClrMD.Extensions/Obfuscation/Deobfuscator.cs @@ -95,9 +95,9 @@ public ITypeDeobfuscator GetTypeDeobfuscator(ClrType type) if (m_typeLookup.TryGetValue(type, out result)) return result; - if (type.Module != null && type.Module.IsFile) + if (type.Module != null && type.Module.IsPEFile) { - string moduleName = Path.GetFileName(type.Module.FileName); + string moduleName = Path.GetFileName(type.Module.AssemblyName); var key = new TypeKey(moduleName, type.Name); m_obfuscationMap.TryGetValue(key, out result);