CLR – Random IT Utensils https://blog.adamfurmanek.pl IT, operating systems, maths, and more. Sat, 20 Feb 2021 09:00:23 +0000 en-US hourly 1 https://wordpress.org/?v=6.6.2 .NET Inside Out Part 26 – Multiple identity inheritance in C# https://blog.adamfurmanek.pl/2021/02/20/net-inside-out-part-26/ https://blog.adamfurmanek.pl/2021/02/20/net-inside-out-part-26/#respond Sat, 20 Feb 2021 09:00:23 +0000 https://blog.adamfurmanek.pl/?p=3767 Continue reading .NET Inside Out Part 26 – Multiple identity inheritance in C#]]>

This is the twentieth sixth part of the .NET Inside Out series. For your convenience you can find other parts in the table of contents in Part 1 – Virtual and non-virtual calls in C#

We know C# has multiple signature (interface) and implementation inheritance. The latter doesn’t support full polymorphic invocations, though, but we already fixed it. We also know how to emulate state inheritance in Java and that can be almost directly translated to C#. Today we’ll see how to hack multiple identity inheritance in C#.

Word of warning: this is super hacky and requires a lot of attention (just like most things on this blog, though).

Let’s start with the following classes:

class Base1
{
	public int field;

	public void PrintInt()
	{
		Console.WriteLine(field);
	}
}

class Base2
{
	public float field;

	public void PrintFloat()
	{
		Console.WriteLine(field);
	}
}

class Base3
{
	public short field1;
	public short field2;

	public void PrintFields()
	{
		Console.WriteLine(field1);
		Console.WriteLine(field2);
	}
}

class Base4
{
	public string field;

	public void PrintString()
	{
		Console.WriteLine(field);
	}
}

They have the same physical structure in .NET Framework 4.5 on x86 architecture. Each instance has sync block (4 bytes), method handle (4 bytes), fields occupying 4 bytes (either one field like integer/float/string or two fields taking 2 bytes each). We’d like to create a class which can behave like any of these, just like with inheritance.

The idea is simple: we’ll create a fake class with matching size and holders for fields of each base class. We’ll dynamically change type as needed (morph the instance) and save/restore fields.

First, we need to have holders for base instances:

public class CurrentState
{
	public Dictionary holders;
	public Type lastRealType;

	public CurrentState(params object[] inheritedTypes)
	{
		holders = inheritedTypes.ToDictionary(t => t.GetType(), t => t);
	}
}

Now we need to have an interface to access the state in whichever type we are now:

interface MultipleBase
{
	CurrentState CurrentState();
}

Now we need to create subclasses with the state:

class FakeChild1 : Base1, MultipleBase
{
	// Types don't matter, size does
	public CurrentState currentState;

	public CurrentState CurrentState() => currentState;
}

class FakeChild2 : Base2, MultipleBase
{
	// Types don't matter, size does
	public CurrentState currentState;

	public CurrentState CurrentState() => currentState;
}

class FakeChild3 : Base3, MultipleBase
{
	// Types don't matter, size does
	public CurrentState currentState;

	public CurrentState CurrentState() => currentState;
}

class FakeChild4 : Base4, MultipleBase
{
	// Types don't matter, size does
	public CurrentState currentState;

	public CurrentState CurrentState() => currentState;
}

Notice how each subclass inherits the fields from the base class and also adds one more field for the state. Also, we inherit so we can use the subclass as a base class as needed.

Now it’s the time for the morphing logic:

static class MultipleBaseExtensions
{
	public static RealType Morph(this MultipleBase self) where FakeType : class where RealType : class
	{
		object holder;
		var currentState = self.CurrentState();
		var lastRealType = currentState.lastRealType;

		if (lastRealType != null)
		{
			holder = currentState.holders[lastRealType];

			foreach (var field in lastRealType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
			{
				field.SetValue(holder, field.GetValue(self));
			}
		}

		ChangeType(typeof(FakeType), self);
		holder = currentState.holders[typeof(RealType)];
		foreach (var field in typeof(RealType).GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
		{
			field.SetValue(self, field.GetValue(holder));
		}

		currentState.lastRealType = typeof(RealType);
		return (RealType)self;
	}

	private static void ChangeType(Type t, MultipleBase self)
	{
		unsafe
		{
			TypedReference selfReference = __makeref(self);
			*(IntPtr*)*(IntPtr*)*(IntPtr*)&selfReference = t.TypeHandle.Value;
		}
	}
}

We extract the current type (after last morphing) and we save all the fields on the side to the specific holder. Then, we change the type (morph) and then restore fields for new type using holder instance. We change the type by modifying the method handle in place.

It’s crucial here to understand that we assume that all instances have the same size and that the currentState field is always in the same place. We need to have the same size in each of the fake subclasses to support proper heap scanning. Otherwise GC will crash. We need to have currentState field in the same place otherwise we won’t find it after morphing.

Now the demo:

MultipleBase child = new FakeChild1
{
	currentState = new CurrentState(new Base1(), new Base2(), new Base3(), new Base4())
};

So we start with instance of any fake subclass and create holders as needed. Next, we morph and modify fields:

Console.WriteLine("Base1");
Base1 base1 = child.Morph();
base1.field = 123;
base1.PrintInt();
Console.WriteLine();

Console.WriteLine("Base2");
Base2 base2 = child.Morph();
base2.field = 456.0f;
base2.PrintFloat();
Console.WriteLine();

Console.WriteLine("Base3");
Base3 base3 = child.Morph();
base3.field1 = 789;
base3.field2 = 987;
base3.PrintFields();
Console.WriteLine();

Console.WriteLine("Base4");
Base4 base4 = child.Morph();
base4.field = "Abrakadabra";
base4.PrintString();
Console.WriteLine();

Console.WriteLine("Base3 again");
base3 = child.Morph();
base3.PrintFields();
Console.WriteLine();

Console.WriteLine("Base2 again");
base2 = child.Morph();
base2.PrintFloat();
Console.WriteLine();

Console.WriteLine("Base1 again");
base1 = child.Morph();
base1.PrintInt();

Output:

Base1
123

Base2
456

Base3
789
987

Base4
Abrakadabra

Base3 again
789
987

Base2 again
456

Base1 again
123

So you can see that we can morph the instance and change fields, and then we can morph back and restore fields. Obviously, multithreading scenario here would be pretty tricky. However, we sort of hacked the instance to support multiple base classes in a sort of generic way.

]]>
https://blog.adamfurmanek.pl/2021/02/20/net-inside-out-part-26/feed/ 0
.NET Inside Out Part 18 — Handling StackOverflowException with custom CLR host https://blog.adamfurmanek.pl/2020/06/06/net-inside-out-part-18/ https://blog.adamfurmanek.pl/2020/06/06/net-inside-out-part-18/#comments Sat, 06 Jun 2020 08:00:28 +0000 https://blog.adamfurmanek.pl/?p=3337 Continue reading .NET Inside Out Part 18 — Handling StackOverflowException with custom CLR host]]>

This is the eighteenth part of the .NET Inside Out series. For your convenience you can find other parts in the table of contents in Part 1 – Virtual and non-virtual calls in C#

We saw in Part 8 — Handling Stack Overflow Exception in C# with VEH how to handle SOE in C#. Obviously, that method is very unsafe and shouldn’t be used in production. But what can we do if we really need to handle SOE?

The answer is — nothing. .NET will always kill the CLR, we cannot stop that, we cannot do anything about that. We can try handling the situation to log errors but it is very dangerous and there is no reliable way to do so. Let’s try, at least.

Let’s begin with the following application in C#:

using System;

namespace StackOverflowGenerator
{
    class Program
    {
        public static void Main(string[] args)
        {
        }

        public static int Start(string args)
        {
            Console.WriteLine("Looping: " + args);
            Start(args);
            return 0;
        }
    }
}

Nothing big, we just loop and print the argument. The thing here is we cannot run this application just like that. To handle StackOverflowException we need to use custom CLR loader. So let’s use this C++ code:

#include <metahost.h>
#include <string>

#pragma comment(lib, "mscoree.lib")

#import "mscorlib.tlb" raw_interfaces_only				\
    high_property_prefixes("_get","_put","_putref")		\
    rename("ReportEvent", "InteropServices_ReportEvent")

using namespace mscorlib;
using namespace std;

LONG WINAPI VehExceptionHandler(_EXCEPTION_POINTERS* pExceptionInfo) {
	printf("%x\n", pExceptionInfo->ExceptionRecord->ExceptionCode);
	//0xE053534F - Soft SOE
	//0xE0434352 - CLR Exception
	//0xE06D7363 - C++ Exception
	if(pExceptionInfo->ExceptionRecord->ExceptionCode == 0xE053534f) {
		puts("SOE!");
	}

	return EXCEPTION_CONTINUE_SEARCH;
}

int main()
{
	HRESULT hr;
	ICLRMetaHost *pMetaHost = NULL;
	ICLRRuntimeInfo *pRuntimeInfo = NULL;
	ICLRRuntimeHost *pClrRuntimeHost = NULL;

	// build runtime
	hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
	hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&pRuntimeInfo));
	hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_PPV_ARGS(&pClrRuntimeHost));

	// configure SOE handling
	ICLRControl *pCLRControl = NULL;
	pClrRuntimeHost->GetCLRControl(&pCLRControl);

	ICLRPolicyManager* pCLRPolicyManager = NULL;
	hr = pCLRControl->GetCLRManager(IID_ICLRPolicyManager, (PVOID*)&pCLRPolicyManager);
	hr = pCLRPolicyManager->SetActionOnFailure(FAIL_StackOverflow, eRudeUnloadAppDomain);

	// start runtime
	hr = pClrRuntimeHost->Start();

	// add VEH
	AddVectoredExceptionHandler(1, VehExceptionHandler);

	// execute managed assembly
	DWORD pReturnValue;
	hr = pClrRuntimeHost->ExecuteInDefaultAppDomain(
		L"..\\StackOverflowGenerator\\bin\\Debug\\StackOverflowGenerator.exe",
		L"StackOverflowGenerator.Program",
		L"Start",
		L"Argument",
		&pReturnValue);

	puts("I'm done");
}

Let’s go through this line by line. Initially we just import some libraries to have an access to CLR methods. Next, in lines 27-35 we just load .NET 4.5 (it’s still based on version 4). You can load different .NET here (for instance version 2).

Next, in line 43 we tell .NET to unload application domain in case of SOE. The name says eRudeUnloadAppDomain which should suggest you that this is not nice.

Next, in line 46 we start the runtime and in line 49 we add VEH handler to report the SOE.

Handler is in lines 13-22. If you run this application you see the following output:

Looping: Argument
...
Looping: Argument
e053534f
SOE!
e0434352
e06d7363
I'm done

There are some more exceptions afterwards related to closing the application but they are not interesting. You can see multiple exceptions being reported. These are:

0xE053534F - Soft SOE
0xE0434352 - CLR Exception
0xE06D7363 - C++ Exception

What is “Soft SOE”? There are actually two types of StackOverflowException. One is reported by MMU and has code 0xC00000FD. The other one is thrown by .NET when CLR realizes there is not enough stack, and the exception code is 0xE053534F.

So we can see that we cannot handle SOE but we can at least run some code when it happens and not kill the application. If you comment out line 43 then the application will get terminated as soon as SOE is thrown.

However, this is very tricky. Notice that we do printf and puts in VEH handler. Is it safe? Well, not really. Let’s modify the code:

public static int Start(string args)
{
	Start(args);
	return 0;
}

And now our application crashes and VEH “is not called”. It is called, in fact, but it allocates some memory on the stack and crashes the process. Let’s change it to

LONG WINAPI VehExceptionHandler(_EXCEPTION_POINTERS* pExceptionInfo) {
	return EXCEPTION_CONTINUE_SEARCH;
}

And now we get this output:

Process is terminated due to StackOverflowException.

If you attach the debugger you can actually see that this time we get

Exception thrown at 0x00270448 in StackOverflowCatcherViaShim.exe: 0xC00000FD: Stack overflow (parameters: 0x00000001, 0x002F2FFC).

To sum up:

  • You cannot stop .NET application from terminating in case of SOE (unless you do the magic from Part 8)
  • You can stop the process from terminating (and continue running in native space)
  • You can execute code on SOE (soft and hard) but it is unreliable (and very dangerous)

So what should you do in reality? If you really need to handle SOE then it seems like you could set some flag atomically in VEH and pause the thread. Then, some other thread could observe the flag, dump the memory when needed, and clean up. You probably cannot do anything on the thread with insufficient stack as it is easy to crash the process.

]]>
https://blog.adamfurmanek.pl/2020/06/06/net-inside-out-part-18/feed/ 1
.NET Inside Out Part 17 — Abusing types to serialize non-serializable type https://blog.adamfurmanek.pl/2020/04/18/net-inside-out-part-17/ https://blog.adamfurmanek.pl/2020/04/18/net-inside-out-part-17/#comments Sat, 18 Apr 2020 08:00:05 +0000 https://blog.adamfurmanek.pl/?p=3280 Continue reading .NET Inside Out Part 17 — Abusing types to serialize non-serializable type]]>

This is the seventeenth part of the .NET Inside Out series. For your convenience you can find other parts in the table of contents in Part 1 – Virtual and non-virtual calls in C#

Last time we saw how to abuse type system. Today we are going to do this to serialize non-serializable type (kind of).

Let’s go with this:

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace SerializeNonSerializable
{
    [Serializable]
    public class Root
    {
        public int RootField { get; set; }
        public NonSerializableChild Child { get; set; }

        public void Print()
        {
            Console.WriteLine($"Root.Print {RootField}");
            Magic(Child);
        }

        public void Magic(NonSerializableChild child)
        {
            Console.WriteLine($"Delegating to child {child.GetType()}");
            child.Print();
        }
    }
    
    public class NonSerializableChild
    {
        public int ChildField { get; set; }

        public void Print()
        {
            Console.WriteLine($"NonSerializableChild.Print {ChildField}");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Root obj = new Root
            {
                RootField = 5,
                Child = new NonSerializableChild
                {
                    ChildField = 10
                }
            };

            obj.Print();

            IFormatter formatter = new BinaryFormatter();
            Stream stream = new MemoryStream();
            formatter.Serialize(stream, obj);
            stream.Close();
        }
    }
}

Output:

Root.Print 5
Delegating to child SerializeNonSerializable.NonSerializableChild
NonSerializableChild.Print 10

Unhandled Exception: System.Runtime.Serialization.SerializationException: Type 'SerializeNonSerializable.NonSerializableChild' in Assembly 'SerializeNonSerializable, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.
   at System.Runtime.Serialization.FormatterServices.InternalGetSerializableMembers(RuntimeType type)
   at System.Runtime.Serialization.FormatterServices.<>c__DisplayClass9_0.<GetSerializableMembers>b__0(MemberHolder _)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at System.Runtime.Serialization.FormatterServices.GetSerializableMembers(Type type, StreamingContext context)
   at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitMemberInfo()
   at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitSerialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriterobjectWriter, SerializationBinder binder)
   at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Write(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo)
   at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Serialize(Object graph, Header[] inHeaders, __BinaryWriter serWriter, Boolean fCheck)
   at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph, Header[] headers, Boolean fCheck)
   at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph)
   at SerializeNonSerializable.Program.Main(String[] args) in C:\Users\afish\Desktop\msp_windowsinternals\SerializeNonSerializable\Program.cs:line 83

Nothing surprising. We have a root object with non-serializable child and we get exception when we try serializing it.

Let’s now do similar thing as last time. Let’s create a mirror type with same binary structure but serializable:

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace SerializeNonSerializable
{
    [Serializable]
    public class Root
    {
        public int RootField { get; set; }
        public NonSerializableChild Child { get; set; }

        public void Print()
        {
            Console.WriteLine($"Root.Print {RootField}");
            Magic(Child);
        }

        public void Magic(NonSerializableChild child)
        {
            Console.WriteLine($"Delegating to child {child.GetType()}");
            child.Print();
        }
    }
    
    public class NonSerializableChild
    {
        public int ChildField { get; set; }

        public void Print()
        {
            Console.WriteLine($"NonSerializableChild.Print {ChildField}");
        }
    }

    [Serializable]
    public class SerializableChild
    {
        public int ChildField { get; set; }

        public void Print()
        {
            Console.WriteLine($"SerializableChild.Print {ChildField}");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Root obj = new Root
            {
                RootField = 5,
                Child = new NonSerializableChild
                {
                    ChildField = 10
                }
            };

            var replacedChild = new SerializableChild
            {
                ChildField = 10
            };

            obj.Print();

            unsafe
            {
                TypedReference typedReference = __makeref(obj);
                int* rootAddress = (int*)(*(int*)*(int*)&typedReference - 4);
                
                typedReference = __makeref(replacedChild);
                int* replacedAddress = (int*)(*(int*)*(int*)&typedReference - 4);

                *(rootAddress + 2) = ((int)replacedAddress) + 4;
            }

            obj.Print();

            IFormatter formatter = new BinaryFormatter();
            Stream stream = new MemoryStream();
            formatter.Serialize(stream, obj);
            stream.Close();
        }
    }
}

Output:

Root.Print 5
Delegating to child SerializeNonSerializable.NonSerializableChild
NonSerializableChild.Print 10
Root.Print 5
Delegating to child SerializeNonSerializable.SerializableChild
NonSerializableChild.Print 10

Okay, so we replace the instance with manual memory manipulation. We then call method on the root to delegate to child which is now of different type. However, methods are called correctly (as last time) and we can now serialize the tree.

So we can see that in theory we can rebuild the graph on the side, add attributes where needed and serialize everything. To deserialize we would need to do the opposite — read the graph and then create mirror objects of original types.

]]>
https://blog.adamfurmanek.pl/2020/04/18/net-inside-out-part-17/feed/ 1
.NET Inside Out Part 16 — Abusing type system https://blog.adamfurmanek.pl/2020/04/11/net-inside-out-part-16/ https://blog.adamfurmanek.pl/2020/04/11/net-inside-out-part-16/#comments Sat, 11 Apr 2020 08:00:58 +0000 https://blog.adamfurmanek.pl/?p=3273 Continue reading .NET Inside Out Part 16 — Abusing type system]]>

This is the sixteenth part of the .NET Inside Out series. For your convenience you can find other parts in the table of contents in Part 1 – Virtual and non-virtual calls in C#

Today we are going to play with type system to see if we can break it. Let’s take this code:

using System;

namespace AbusingTypeSystem
{
    public class A
    {
        public void Print()
        {
            Console.WriteLine("A.Print");
            Console.WriteLine(this.GetType());
        }
    }

    public class B
    {
        public void Print()
        {
            Console.WriteLine("B.Print");
            Console.WriteLine(this.GetType());
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MethodHijacker.MethodHijacker.HijackMethod(typeof(Program).GetMethod(nameof(PrintWithA)), typeof(Program).GetMethod(nameof(PrintWithB)));

            A a = new A();
            PrintWithA(a);
        }

        public static void PrintWithA(A a)
        {
            Console.WriteLine("Printing with A");
            a.Print();
        }

        public static void PrintWithB(B b)
        {
            Console.WriteLine("Printing with B");
            b.Print();
        }
    }
}

We create two classes with the same method signatures and different implementation. Next, we have two methods calling Print on types A or B respectively. We then hijack one of the method to point to the other. We call it with instance of type A and this is the output:

Printing with B
B.Print
AbusingTypeSystem.A

So we can see that we are going through method for B but with instance of type A.

So let’s see what happened. We started in PrintWithA with instance of type A. We then jump to PrintWithB. We print to the console and call method Print on type B. However, .NET doesn’t check the type here and just calls the method! In B.Print we print to the console and show the instance type which is A.

What happens if we change Print methods to be virtual? Let’s see:

using System;

namespace AbusingTypeSystem
{
    public class A
    {
        public virtual void Print()
        {
            Console.WriteLine("A.Print");
            Console.WriteLine(this.GetType());
        }
    }

    public class B
    {
        public virtual void Print()
        {
            Console.WriteLine("B.Print");
            Console.WriteLine(this.GetType());
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MethodHijacker.MethodHijacker.HijackMethod(typeof(Program).GetMethod(nameof(PrintWithA)), typeof(Program).GetMethod(nameof(PrintWithB)));

            A a = new A();
            PrintWithA(a);
        }

        public static void PrintWithA(A a)
        {
            Console.WriteLine("Printing with A");
            a.Print();
        }

        public static void PrintWithB(B b)
        {
            Console.WriteLine("Printing with B");
            b.Print();
        }
    }
}

Printing with B
A.Print
AbusingTypeSystem.A

So there is no difference, even though virtual method uses callvirt instruction under the hood it still doesn’t check the type. However, this time we have called method Print from type A, thanks to dynamic dispatch. Now let’s see this:

using System;

namespace AbusingTypeSystem
{
    public class A
    {
        public void Print()
        {
            Console.WriteLine("A.Print");
            Console.WriteLine(this.GetType());
        }
    }

    public class B
    {
        public void Print()
        {
            Console.WriteLine("B.Print");
            Console.WriteLine(this.GetType());
        }

        public void Print2()
        {
            Console.WriteLine("B.Print2");
            Console.WriteLine(this.GetType());
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MethodHijacker.MethodHijacker.HijackMethod(typeof(Program).GetMethod(nameof(PrintWithA)), typeof(Program).GetMethod(nameof(PrintWithB)));

            A a = new A();
            PrintWithA(a);
        }

        public static void PrintWithA(A a)
        {
            Console.WriteLine("Printing with A");
            a.Print();
        }

        public static void PrintWithB(B b)
        {
            Console.WriteLine("Printing with B");
            b.Print();
            b.Print2();
        }
    }
}

This time we add another method Print2 to the B type. The method does exactly the same as Print. Notice, it is not virtual. Output:

Printing with B
B.Print
AbusingTypeSystem.A
B.Print2
AbusingTypeSystem.A

So it still works correctly. But let’s now change the method to virtual:

using System;

namespace AbusingTypeSystem
{
    public class A
    {
        public void Print()
        {
            Console.WriteLine("A.Print");
            Console.WriteLine(this.GetType());
        }
    }

    public class B
    {
        public void Print()
        {
            Console.WriteLine("B.Print");
            Console.WriteLine(this.GetType());
        }

        public virtual void Print2()
        {
            Console.WriteLine("B.Print2");
            Console.WriteLine(this.GetType());
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MethodHijacker.MethodHijacker.HijackMethod(typeof(Program).GetMethod(nameof(PrintWithA)), typeof(Program).GetMethod(nameof(PrintWithB)));

            A a = new A();
            PrintWithA(a);
        }

        public static void PrintWithA(A a)
        {
            Console.WriteLine("Printing with A");
            a.Print();
        }

        public static void PrintWithB(B b)
        {
            Console.WriteLine("Printing with B");
            b.Print();
            b.Print2();
        }
    }
}

Output:

Printing with B
B.Print
AbusingTypeSystem.A

Unhandled Exception: System.AccessViolationException: Attempted to read or write
 protected memory. This is often an indication that other memory is corrupt.

Why does it crash now? The difference is callvirt instruction. It does the polymorphic dispatch based on the type so it needs to go through the method table. However, there is no method for type A but .NET doesn’t check it. It just reads the memory and jumps to some invalid location.

Takeaway? You can play with types but you need to be very careful.

]]>
https://blog.adamfurmanek.pl/2020/04/11/net-inside-out-part-16/feed/ 1