diff --git a/Function/BNGetStringRecognizerList.cs b/Function/BNGetStringRecognizerList.cs index 62e3e3e..33c4a72 100644 --- a/Function/BNGetStringRecognizerList.cs +++ b/Function/BNGetStringRecognizerList.cs @@ -19,7 +19,7 @@ internal static partial class NativeMethods internal static extern IntPtr BNGetStringRecognizerList( // size_t* count - IntPtr count + out ulong count ); } } diff --git a/Function/BNRegisterStringRecognizer.cs b/Function/BNRegisterStringRecognizer.cs index e3f7434..7a383a0 100644 --- a/Function/BNRegisterStringRecognizer.cs +++ b/Function/BNRegisterStringRecognizer.cs @@ -23,7 +23,7 @@ internal static extern IntPtr BNRegisterStringRecognizer( [MarshalAs(UnmanagedType.LPUTF8Str)] string name , // BNCustomStringRecognizer* recognizer - IntPtr recognizer + in BNCustomStringRecognizer recognizer ); } } diff --git a/Function/BNStringRecognizerRecognizeConstant.cs b/Function/BNStringRecognizerRecognizeConstant.cs index c6636f7..144306c 100644 --- a/Function/BNStringRecognizerRecognizeConstant.cs +++ b/Function/BNStringRecognizerRecognizeConstant.cs @@ -35,7 +35,7 @@ internal static extern bool BNStringRecognizerRecognizeConstant( long val , // BNDerivedString* out - IntPtr @out + out BNDerivedString @out ); } } diff --git a/Function/BNStringRecognizerRecognizeConstantPointer.cs b/Function/BNStringRecognizerRecognizeConstantPointer.cs index 5e1414e..6210a79 100644 --- a/Function/BNStringRecognizerRecognizeConstantPointer.cs +++ b/Function/BNStringRecognizerRecognizeConstantPointer.cs @@ -35,7 +35,7 @@ internal static extern bool BNStringRecognizerRecognizeConstantPointer( long val , // BNDerivedString* out - IntPtr @out + out BNDerivedString @out ); } } diff --git a/Function/BNStringRecognizerRecognizeExternPointer.cs b/Function/BNStringRecognizerRecognizeExternPointer.cs index dbf0b8e..f40d3bb 100644 --- a/Function/BNStringRecognizerRecognizeExternPointer.cs +++ b/Function/BNStringRecognizerRecognizeExternPointer.cs @@ -38,7 +38,7 @@ internal static extern bool BNStringRecognizerRecognizeExternPointer( ulong offset , // BNDerivedString* out - IntPtr @out + out BNDerivedString @out ); } } diff --git a/Function/BNStringRecognizerRecognizeImport.cs b/Function/BNStringRecognizerRecognizeImport.cs index bb2a9e6..52061c6 100644 --- a/Function/BNStringRecognizerRecognizeImport.cs +++ b/Function/BNStringRecognizerRecognizeImport.cs @@ -35,7 +35,7 @@ internal static extern bool BNStringRecognizerRecognizeImport( long val , // BNDerivedString* out - IntPtr @out + out BNDerivedString @out ); } } diff --git a/NavigationExtensions.cs b/NavigationExtensions.cs index 8875071..689f35f 100644 --- a/NavigationExtensions.cs +++ b/NavigationExtensions.cs @@ -80,6 +80,8 @@ public sealed class DerivedString public ulong Length { get; } + public CustomStringType? CustomType { get; } + /// /// Constructs a value-only DerivedString with no location, mirroring Python /// DerivedString(value=..., location=None, custom_type=None) for the common case of @@ -87,7 +89,24 @@ public sealed class DerivedString /// . /// public DerivedString(string value) - : this(value, false, 0, 0UL, 0UL) + : this(value, false, 0, 0UL, 0UL, null) + { + } + + /// Constructs a derived string with an explicit source location and type. + public DerivedString( + string value, + DerivedStringLocationType locationType, + ulong address, + ulong length, + CustomStringType? customType = null) + : this(value, true, (int)locationType, address, length, customType) + { + } + + /// Constructs a value-only derived string with an explicit custom type. + public DerivedString(string value, CustomStringType customType) + : this(value, false, 0, 0UL, 0UL, customType) { } @@ -96,13 +115,15 @@ private DerivedString( bool locationValid, int locationType, ulong addr, - ulong len) + ulong len, + CustomStringType? customType) { this.Value = value; this.LocationValid = locationValid; this.LocationType = (DerivedStringLocationType)locationType; this.Address = addr; this.Length = len; + this.CustomType = customType; } // Reads one BNDerivedString out of a by-value native array. Must run before the array @@ -117,7 +138,8 @@ internal static DerivedString FromNative(BNDerivedString raw) raw.locationValid, raw.location.locationType, raw.location.addr, - raw.location.len + raw.location.len, + CustomStringType.FromHandle(raw.customType) ); } @@ -125,9 +147,9 @@ internal static DerivedString FromNative(BNDerivedString raw) // (BNSetHighLevelILDerivedStringReferenceForExpr). The Value text is rebuilt as a fresh // BNStringRef* because this projection reads eagerly and does not retain the original core // handle. The caller owns that BNStringRef* and must free it (BNFreeStringRef) once the - // core has consumed the struct: core copies/add-refs the string, mirroring Python's - // owned=False borrow. custom_type is not surfaced by this read-only projection (Python - // CustomStringType handle is dropped), so it is left null. + // core has consumed the struct: core copies/add-refs the string for setter calls, while + // recognizer callbacks transfer the owned reference to the core caller. The custom type + // is a static core handle and is passed through without an additional reference. internal BNDerivedString ToNativeEx() { ulong byteCount = (ulong)Encoding.UTF8.GetByteCount(this.Value); @@ -147,7 +169,9 @@ internal BNDerivedString ToNativeEx() native.location.len = this.Length; } - native.customType = IntPtr.Zero; + native.customType = null == this.CustomType + ? IntPtr.Zero + : this.CustomType.DangerousGetHandle(); return native; } diff --git a/StringRecognizer.cs b/StringRecognizer.cs new file mode 100644 index 0000000..d58e8e9 --- /dev/null +++ b/StringRecognizer.cs @@ -0,0 +1,631 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace BinaryNinja +{ + internal static partial class NativeDelegates + { + [UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + internal delegate bool BNStringRecognizerIsValidForType( + IntPtr context, + IntPtr function, + IntPtr type + ); + + [UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + internal delegate bool BNStringRecognizerRecognizeConstant( + IntPtr context, + IntPtr function, + UIntPtr expressionIndex, + IntPtr type, + long value, + IntPtr result + ); + + [UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + internal delegate bool BNStringRecognizerRecognizeExternPointer( + IntPtr context, + IntPtr function, + UIntPtr expressionIndex, + IntPtr type, + long value, + ulong offset, + IntPtr result + ); + } + + [StructLayout(LayoutKind.Sequential)] + internal struct BNCustomStringRecognizer + { + internal IntPtr context; + + internal IntPtr isValidForType; + + internal IntPtr recognizeConstant; + + internal IntPtr recognizeConstantPointer; + + internal IntPtr recognizeExternPointer; + + internal IntPtr recognizeImport; + } + + /// + /// Recognizes derived strings represented by high-level IL constants and pointer expressions. + /// + public class StringRecognizer : AbstractSafeHandle + { + private enum RecognitionKind + { + Constant, + ConstantPointer, + ExternPointer, + Import + } + + private static readonly object registrationLock = new object(); + + private static readonly List registeredRecognizers = + new List(); + + private readonly string? registrationName; + + private bool isRegistered; + + private NativeDelegates.BNStringRecognizerIsValidForType? isValidForTypeCallback; + + private NativeDelegates.BNStringRecognizerRecognizeConstant? recognizeConstantCallback; + + private NativeDelegates.BNStringRecognizerRecognizeConstant? recognizeConstantPointerCallback; + + private NativeDelegates.BNStringRecognizerRecognizeExternPointer? recognizeExternPointerCallback; + + private NativeDelegates.BNStringRecognizerRecognizeConstant? recognizeImportCallback; + + /// Creates an unregistered custom string recognizer. + protected StringRecognizer(string name) + : base(false) + { + if (null == name) + { + throw new ArgumentNullException(nameof(name)); + } + + this.registrationName = name; + } + + private StringRecognizer(IntPtr handle) + : base(handle, false) + { + } + + /// Gets the registered recognizer name. + public string Name + { + get + { + if (this.IsInvalid) + { + return this.registrationName ?? string.Empty; + } + + return UnsafeUtils.TakeUtf8String( + NativeMethods.BNGetStringRecognizerName(this.handle) + ); + } + } + + /// Registers this custom recognizer and roots its callbacks for core use. + public void Register() + { + if (this.isRegistered || !this.IsInvalid) + { + throw new InvalidOperationException("The string recognizer is already registered."); + } + + this.isValidForTypeCallback = + new NativeDelegates.BNStringRecognizerIsValidForType(this.InvokeIsValidForType); + this.recognizeConstantCallback = + new NativeDelegates.BNStringRecognizerRecognizeConstant(this.InvokeRecognizeConstant); + this.recognizeConstantPointerCallback = + new NativeDelegates.BNStringRecognizerRecognizeConstant( + this.InvokeRecognizeConstantPointer + ); + this.recognizeExternPointerCallback = + new NativeDelegates.BNStringRecognizerRecognizeExternPointer( + this.InvokeRecognizeExternPointer + ); + this.recognizeImportCallback = + new NativeDelegates.BNStringRecognizerRecognizeConstant(this.InvokeRecognizeImport); + + BNCustomStringRecognizer callbacks = new BNCustomStringRecognizer(); + callbacks.context = IntPtr.Zero; + callbacks.isValidForType = Marshal.GetFunctionPointerForDelegate( + this.isValidForTypeCallback + ); + callbacks.recognizeConstant = Marshal.GetFunctionPointerForDelegate( + this.recognizeConstantCallback + ); + callbacks.recognizeConstantPointer = Marshal.GetFunctionPointerForDelegate( + this.recognizeConstantPointerCallback + ); + callbacks.recognizeExternPointer = Marshal.GetFunctionPointerForDelegate( + this.recognizeExternPointerCallback + ); + callbacks.recognizeImport = Marshal.GetFunctionPointerForDelegate( + this.recognizeImportCallback + ); + + IntPtr handle = NativeMethods.BNRegisterStringRecognizer( + this.registrationName ?? string.Empty, + in callbacks + ); + if (IntPtr.Zero == handle) + { + throw new InvalidOperationException("The core rejected the string recognizer."); + } + + this.SetHandle(handle); + this.isRegistered = true; + lock (StringRecognizer.registrationLock) + { + StringRecognizer.registeredRecognizers.Add(this); + } + } + + /// Finds a registered recognizer by name. + public static StringRecognizer? FromName(string name) + { + if (null == name) + { + throw new ArgumentNullException(nameof(name)); + } + + IntPtr handle = NativeMethods.BNGetStringRecognizerByName(name); + if (IntPtr.Zero == handle) + { + return null; + } + + return new CoreStringRecognizer(handle); + } + + /// Gets every recognizer registered with the core. + public static StringRecognizer[] GetRecognizers() + { + IntPtr recognizers = NativeMethods.BNGetStringRecognizerList(out ulong count); + return UnsafeUtils.TakeHandleArray( + recognizers, + count, + StringRecognizer.CreateCoreRecognizer, + NativeMethods.BNFreeStringRecognizerList + ); + } + + private static StringRecognizer CreateCoreRecognizer(IntPtr handle) + { + if (IntPtr.Zero == handle) + { + throw new ArgumentNullException(nameof(handle)); + } + + return new CoreStringRecognizer(handle); + } + + /// Determines whether this recognizer accepts expressions of the given type. + public virtual bool IsValidForType(HighLevelILFunction function, BinaryNinja.Type type) + { + return true; + } + + public virtual DerivedString? RecognizeConstant( + HighLevelILInstruction instruction, + BinaryNinja.Type type, + long value + ) + { + return null; + } + + public virtual DerivedString? RecognizeConstantPointer( + HighLevelILInstruction instruction, + BinaryNinja.Type type, + long value + ) + { + return null; + } + + public virtual DerivedString? RecognizeExternPointer( + HighLevelILInstruction instruction, + BinaryNinja.Type type, + long value, + ulong offset + ) + { + return null; + } + + public virtual DerivedString? RecognizeImport( + HighLevelILInstruction instruction, + BinaryNinja.Type type, + long value + ) + { + return null; + } + + private bool InvokeIsValidForType(IntPtr context, IntPtr functionHandle, IntPtr typeHandle) + { + try + { + using (HighLevelILFunction function = + HighLevelILFunction.MustNewFromHandle(functionHandle)) + using (BinaryNinja.Type type = BinaryNinja.Type.MustNewFromHandle(typeHandle)) + { + return this.IsValidForType(function, type); + } + } + catch (Exception exception) + { + Core.LogError("Unhandled exception in StringRecognizer.IsValidForType: {0}", exception); + return false; + } + } + + private bool InvokeRecognizeConstant( + IntPtr context, + IntPtr function, + UIntPtr expressionIndex, + IntPtr type, + long value, + IntPtr result + ) + { + return this.InvokeRecognition( + RecognitionKind.Constant, + function, + expressionIndex, + type, + value, + 0, + result + ); + } + + private bool InvokeRecognizeConstantPointer( + IntPtr context, + IntPtr function, + UIntPtr expressionIndex, + IntPtr type, + long value, + IntPtr result + ) + { + return this.InvokeRecognition( + RecognitionKind.ConstantPointer, + function, + expressionIndex, + type, + value, + 0, + result + ); + } + + private bool InvokeRecognizeExternPointer( + IntPtr context, + IntPtr function, + UIntPtr expressionIndex, + IntPtr type, + long value, + ulong offset, + IntPtr result + ) + { + return this.InvokeRecognition( + RecognitionKind.ExternPointer, + function, + expressionIndex, + type, + value, + offset, + result + ); + } + + private bool InvokeRecognizeImport( + IntPtr context, + IntPtr function, + UIntPtr expressionIndex, + IntPtr type, + long value, + IntPtr result + ) + { + return this.InvokeRecognition( + RecognitionKind.Import, + function, + expressionIndex, + type, + value, + 0, + result + ); + } + + private bool InvokeRecognition( + RecognitionKind kind, + IntPtr functionHandle, + UIntPtr expressionIndex, + IntPtr typeHandle, + long value, + ulong offset, + IntPtr resultPointer + ) + { + try + { + using (HighLevelILFunction function = + HighLevelILFunction.MustNewFromHandle(functionHandle)) + using (BinaryNinja.Type type = BinaryNinja.Type.MustNewFromHandle(typeHandle)) + { + HighLevelILInstruction instruction = function.MustGetExpression( + (HighLevelILExpressionIndex)expressionIndex.ToUInt64() + ); + DerivedString? recognized; + + switch (kind) + { + case RecognitionKind.Constant: + recognized = this.RecognizeConstant(instruction, type, value); + break; + case RecognitionKind.ConstantPointer: + recognized = this.RecognizeConstantPointer(instruction, type, value); + break; + case RecognitionKind.ExternPointer: + recognized = this.RecognizeExternPointer( + instruction, + type, + value, + offset + ); + break; + case RecognitionKind.Import: + recognized = this.RecognizeImport(instruction, type, value); + break; + default: + throw new ArgumentOutOfRangeException(nameof(kind)); + } + + return StringRecognizer.WriteResult(recognized, resultPointer); + } + } + catch (Exception exception) + { + Core.LogError("Unhandled exception in StringRecognizer recognition callback: {0}", exception); + return false; + } + } + + private static bool WriteResult(DerivedString? result, IntPtr resultPointer) + { + if (null == result) + { + return false; + } + + BNDerivedString native = result.ToNativeEx(); + try + { + Marshal.StructureToPtr(native, resultPointer, false); + return true; + } + catch + { + if (IntPtr.Zero != native.value) + { + NativeMethods.BNFreeStringRef(native.value); + } + + throw; + } + } + + private sealed class CoreStringRecognizer : StringRecognizer + { + internal CoreStringRecognizer(IntPtr handle) + : base(handle) + { + } + + public override bool IsValidForType( + HighLevelILFunction function, + BinaryNinja.Type type + ) + { + StringRecognizer.ValidateArguments(function, type); + return NativeMethods.BNIsStringRecognizerValidForType( + this.handle, + function.DangerousGetHandle(), + type.DangerousGetHandle() + ); + } + + public override DerivedString? RecognizeConstant( + HighLevelILInstruction instruction, + BinaryNinja.Type type, + long value + ) + { + return this.Recognize( + RecognitionKind.Constant, + instruction, + type, + value, + 0 + ); + } + + public override DerivedString? RecognizeConstantPointer( + HighLevelILInstruction instruction, + BinaryNinja.Type type, + long value + ) + { + return this.Recognize( + RecognitionKind.ConstantPointer, + instruction, + type, + value, + 0 + ); + } + + public override DerivedString? RecognizeExternPointer( + HighLevelILInstruction instruction, + BinaryNinja.Type type, + long value, + ulong offset + ) + { + return this.Recognize( + RecognitionKind.ExternPointer, + instruction, + type, + value, + offset + ); + } + + public override DerivedString? RecognizeImport( + HighLevelILInstruction instruction, + BinaryNinja.Type type, + long value + ) + { + return this.Recognize( + RecognitionKind.Import, + instruction, + type, + value, + 0 + ); + } + + private DerivedString? Recognize( + RecognitionKind kind, + HighLevelILInstruction instruction, + BinaryNinja.Type type, + long value, + ulong offset + ) + { + StringRecognizer.ValidateArguments(instruction, type); + UIntPtr expressionIndex = (UIntPtr)(ulong)instruction.ExpressionIndex; + BNDerivedString result; + bool recognized; + + switch (kind) + { + case RecognitionKind.Constant: + recognized = NativeMethods.BNStringRecognizerRecognizeConstant( + this.handle, + instruction.Function.DangerousGetHandle(), + expressionIndex, + type.DangerousGetHandle(), + value, + out result + ); + break; + case RecognitionKind.ConstantPointer: + recognized = NativeMethods.BNStringRecognizerRecognizeConstantPointer( + this.handle, + instruction.Function.DangerousGetHandle(), + expressionIndex, + type.DangerousGetHandle(), + value, + out result + ); + break; + case RecognitionKind.ExternPointer: + recognized = NativeMethods.BNStringRecognizerRecognizeExternPointer( + this.handle, + instruction.Function.DangerousGetHandle(), + expressionIndex, + type.DangerousGetHandle(), + value, + offset, + out result + ); + break; + case RecognitionKind.Import: + recognized = NativeMethods.BNStringRecognizerRecognizeImport( + this.handle, + instruction.Function.DangerousGetHandle(), + expressionIndex, + type.DangerousGetHandle(), + value, + out result + ); + break; + default: + throw new ArgumentOutOfRangeException(nameof(kind)); + } + + if (!recognized) + { + return null; + } + + try + { + return DerivedString.FromNative(result); + } + finally + { + if (IntPtr.Zero != result.value) + { + NativeMethods.BNFreeStringRef(result.value); + } + } + } + } + + private static void ValidateArguments( + HighLevelILFunction function, + BinaryNinja.Type type + ) + { + if (null == function) + { + throw new ArgumentNullException(nameof(function)); + } + + if (null == type) + { + throw new ArgumentNullException(nameof(type)); + } + } + + private static void ValidateArguments( + HighLevelILInstruction instruction, + BinaryNinja.Type type + ) + { + if (null == instruction) + { + throw new ArgumentNullException(nameof(instruction)); + } + + if (null == type) + { + throw new ArgumentNullException(nameof(type)); + } + } + } +}