@@ -47,6 +47,88 @@ public void AppendLineNoNullChars(string value)
47
47
=> AppendLine ( value . Replace ( "\0 " , string . Empty ) ) ;
48
48
}
49
49
50
+ public static class UnmanagedMemHelper
51
+ {
52
+ /// <summary>
53
+ /// Windows API call used for memory range validation
54
+ /// </summary>
55
+ [ DllImport ( "kernel32.dll" ) ]
56
+ public static extern int VirtualQuery ( IntPtr lpAddress , out MEMORY_BASIC_INFORMATION lpBuffer , int dwLength ) ;
57
+
58
+ /// <summary>
59
+ /// Do our best to validate that the input memory address is actually a COM object
60
+ /// </summary>
61
+ /// <param name="comObjectPtr">the input memory address to check</param>
62
+ /// <returns>false means definitely not a valid COM object. true means _probably_ a valid COM object</returns>
63
+ public static bool ValidateComObject ( IntPtr comObjectPtr )
64
+ {
65
+ // Is it a valid memory address, with at least one accessible vTable ptr
66
+ if ( IsValidMemoryRange ( comObjectPtr , IntPtr . Size ) )
67
+ {
68
+ IntPtr vTablePtr = Marshal . ReadIntPtr ( comObjectPtr ) ;
69
+
70
+ // And for a COM object, we need a valid vtable, with at least 3 vTable entries (for IUnknown)
71
+ if ( IsValidMemoryRange ( vTablePtr , IntPtr . Size * 3 ) )
72
+ {
73
+ IntPtr firstvTableEntry = Marshal . ReadIntPtr ( vTablePtr ) ;
74
+
75
+ // And lets check the first vTable entry actually points to EXECUTABLE memory
76
+ // (we could check all 3 initial IUnknown entries, but we want to be reasonably
77
+ // efficient and we can never 100% guarantee our result anyway.)
78
+ if ( IsValidMemoryRange ( firstvTableEntry , 1 , checkIsExecutable : true ) )
79
+ {
80
+ // As best as we can tell, it looks to be a valid COM object
81
+ return true ;
82
+ }
83
+ }
84
+ }
85
+
86
+ // One of the validation checks failed. The COM object is definitely not a valid COM object.
87
+ return false ;
88
+ }
89
+
90
+ /// <summary>
91
+ /// Validate a memory address range
92
+ /// </summary>
93
+ /// <param name="memOffset">the input memory address to check</param>
94
+ /// <param name="size">the minimum size of data we are expecting to be available next to memOffset</param>
95
+ /// <param name="checkIsExecutable">optionally check if the memory address points to EXECUTABLE memory</param>
96
+ public static bool IsValidMemoryRange ( IntPtr memOffset , int size , bool checkIsExecutable = false )
97
+ {
98
+ if ( memOffset != IntPtr . Zero )
99
+ {
100
+ var memInfo = new MEMORY_BASIC_INFORMATION ( ) ;
101
+ var sizeOfMemInfo = Marshal . SizeOf ( memInfo ) ;
102
+
103
+ if ( VirtualQuery ( memOffset , out memInfo , sizeOfMemInfo ) == sizeOfMemInfo )
104
+ {
105
+ if ( ( ! memInfo . Protect . HasFlag ( ALLOCATION_PROTECTION . PAGE_NOACCESS ) ) &&
106
+ ( ! memInfo . Protect . HasFlag ( ALLOCATION_PROTECTION . PAGE_GUARD ) ) )
107
+ {
108
+ // We've confirmed the base memory address is valid, and is accessible.
109
+ // Finally just check the full address RANGE is also valid (i.e. the end point of the structure we're reading)
110
+ var validMemAddressEnd = memInfo . BaseAddress . ToInt64 ( ) + memInfo . RegionSize . ToInt64 ( ) ;
111
+ var endOfStructPtr = memOffset . ToInt64 ( ) + size ;
112
+ if ( endOfStructPtr <= validMemAddressEnd )
113
+ {
114
+ if ( checkIsExecutable )
115
+ {
116
+ // We've been asked to check if the memory address is marked as containing executable code
117
+ return memInfo . Protect . HasFlag ( ALLOCATION_PROTECTION . PAGE_EXECUTE ) ||
118
+ memInfo . Protect . HasFlag ( ALLOCATION_PROTECTION . PAGE_EXECUTE_READ ) ||
119
+ memInfo . Protect . HasFlag ( ALLOCATION_PROTECTION . PAGE_EXECUTE_READWRITE ) ||
120
+ memInfo . Protect . HasFlag ( ALLOCATION_PROTECTION . PAGE_EXECUTE_WRITECOPY ) ;
121
+ }
122
+ return true ;
123
+ }
124
+ }
125
+ }
126
+ }
127
+
128
+ return false ;
129
+ }
130
+ }
131
+
50
132
/// <summary>
51
133
/// Encapsulates reading unmanaged memory into managed structures
52
134
/// </summary>
@@ -64,7 +146,7 @@ public static T ReadComObjectStructure<T>(object comObj)
64
146
if ( Marshal . IsComObject ( comObj ) )
65
147
{
66
148
var referencesPtr = Marshal . GetIUnknownForObject ( comObj ) ;
67
- var retVal = StructHelper . ReadStructure < T > ( referencesPtr ) ;
149
+ var retVal = StructHelper . ReadStructureSafe < T > ( referencesPtr ) ;
68
150
Marshal . Release ( referencesPtr ) ;
69
151
return retVal ;
70
152
}
@@ -86,6 +168,7 @@ public static T ReadStructure<T>(IntPtr memAddress)
86
168
return ( T ) Marshal . PtrToStructure ( memAddress , typeof ( T ) ) ;
87
169
}
88
170
171
+
89
172
/// <summary>
90
173
/// Takes an unmanaged memory address and reads the unmanaged memory given by its pointer,
91
174
/// with memory address validation for added protection
@@ -95,8 +178,12 @@ public static T ReadStructure<T>(IntPtr memAddress)
95
178
/// <returns>the requested structure T</returns>
96
179
public static T ReadStructureSafe < T > ( IntPtr memAddress )
97
180
{
98
- if ( memAddress == IntPtr . Zero ) return default ( T ) ;
99
- return ( T ) Marshal . PtrToStructure ( memAddress , typeof ( T ) ) ;
181
+ if ( UnmanagedMemHelper . IsValidMemoryRange ( memAddress , Marshal . SizeOf ( typeof ( T ) ) ) )
182
+ {
183
+ return ( T ) Marshal . PtrToStructure ( memAddress , typeof ( T ) ) ;
184
+ }
185
+
186
+ return default ( T ) ;
100
187
}
101
188
}
102
189
@@ -1330,7 +1417,6 @@ public static TypeLibWrapper FromVBProject(IVBProject vbProject)
1330
1417
{
1331
1418
// Now we've got the references object, we can read the internal object structure to grab the ITypeLib
1332
1419
var internalReferencesObj = StructHelper . ReadComObjectStructure < VBEReferencesObj > ( references . Target ) ;
1333
-
1334
1420
return new TypeLibWrapper ( internalReferencesObj . TypeLib ) ;
1335
1421
}
1336
1422
}
@@ -1348,6 +1434,10 @@ private void InitCommon()
1348
1434
/// <param name="rawObjectPtr">The raw unamanaged ITypeLib pointer</param>
1349
1435
public TypeLibWrapper ( IntPtr rawObjectPtr )
1350
1436
{
1437
+ if ( ! UnmanagedMemHelper . ValidateComObject ( rawObjectPtr ) )
1438
+ {
1439
+ throw new ArgumentException ( "Expected COM object, but validation failed." ) ;
1440
+ } ;
1351
1441
target_ITypeLib = ( ComTypes . ITypeLib ) Marshal . GetObjectForIUnknown ( rawObjectPtr ) ;
1352
1442
Marshal . Release ( rawObjectPtr ) ; // target_ITypeLib holds a reference to this now
1353
1443
InitCommon ( ) ;
@@ -1663,7 +1753,7 @@ public VBETypeLibsAccessor(IVBE ide)
1663
1753
{
1664
1754
// Now we've got the references object, we can read the internal object structure to grab the ITypeLib
1665
1755
var internalReferencesObj = StructHelper . ReadComObjectStructure < VBEReferencesObj > ( references . Target ) ;
1666
-
1756
+
1667
1757
// Now we've got this one internalReferencesObj.typeLib, we can iterate through ALL loaded project TypeLibs
1668
1758
using ( var typeLibIterator = new VBETypeLibsIterator ( internalReferencesObj . TypeLib ) )
1669
1759
{
0 commit comments