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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
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:
1 2 3 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
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(); } } } |
1 2 3 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
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:
1 2 3 4 5 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
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:
1 2 3 4 5 6 |
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.