.NET Core – Random IT Utensils https://blog.adamfurmanek.pl IT, operating systems, maths, and more. Sat, 09 Oct 2021 12:49:34 +0000 en-US hourly 1 https://wordpress.org/?v=6.6.2 Custom memory allocation in C# Part 18 — Hijacking methods on .NET 5 with modifying machine code https://blog.adamfurmanek.pl/2021/12/11/custom-memory-allocation-in-c-part-18/ https://blog.adamfurmanek.pl/2021/12/11/custom-memory-allocation-in-c-part-18/#respond Sat, 11 Dec 2021 09:00:40 +0000 https://blog.adamfurmanek.pl/?p=4272 Continue reading Custom memory allocation in C# Part 18 — Hijacking methods on .NET 5 with modifying machine code]]>

This is the eighteenth part of the Custom memory allocation series. For your convenience you can find other parts in the table of contents in Part 1 — Allocating object on a stack

Today we are going to see a rewritten way of hijacking method with machine code. It works in Windows and Linux, for both Debug and Release using .NET 5.

using System;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;

namespace MethodHijackerNetCore
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"Calling StaticString method before hacking:\t{TestClass.StaticString()}");
            HijackMethod(typeof(TestClass), nameof(TestClass.StaticString), typeof(Program), nameof(StaticStringHijacked));
            Console.WriteLine($"Calling StaticString method after hacking:\t{TestClass.StaticString()}");

            Console.WriteLine();

            var instance = new TestClass();
            Console.WriteLine($"Calling InstanceString method before hacking:\t{instance.InstanceString()}");
            HijackMethod(typeof(TestClass), nameof(TestClass.InstanceString), typeof(Program), nameof(InstanceStringHijacked));
            Console.WriteLine($"Calling InstanceString method after hacking:\t{instance.InstanceString()}");

            Console.WriteLine();

            Vector2 v = new Vector2(9.856331f, -2.2437377f);
            for (int i = 1; i <= 35 ; i++)
            {
                MultiTieredClass.Test(v, i);
                Thread.Sleep(100);
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public static string StaticStringHijacked()
        {
            return "Static string hijacked";
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public string InstanceStringHijacked()
        {
            return "Instance string hijacked";
        }

        public static void HijackMethod(Type sourceType, string sourceMethod, Type targetType, string targetMethod)
        {
            var source = sourceType.GetMethod(sourceMethod);
            var target = targetType.GetMethod(targetMethod);

            RuntimeHelpers.PrepareMethod(source.MethodHandle);
            RuntimeHelpers.PrepareMethod(target.MethodHandle);


            var offset = 2 * IntPtr.Size;
            IntPtr sourceAddress = Marshal.ReadIntPtr(source.MethodHandle.Value, offset);
            IntPtr targetAddress = Marshal.ReadIntPtr(target.MethodHandle.Value, offset);

            var is32Bit = IntPtr.Size == 4;
            byte[] instruction;

            if (is32Bit)
            {
                instruction = new byte[] {
                    0x68, // push <value>
                }
                 .Concat(BitConverter.GetBytes((int)targetAddress))
                 .Concat(new byte[] {
                    0xC3 //ret
                 }).ToArray();
            }
            else
            {
                instruction = new byte[] {
                    0x48, 0xB8 // mov rax <value>
                }
                .Concat(BitConverter.GetBytes((long)targetAddress))
                .Concat(new byte[] {
                    0x50, // push rax
                    0xC3  // ret
                }).ToArray();
            }

            Marshal.Copy(instruction, 0, sourceAddress, instruction.Length);
        }
    }

    class TestClass
    {
        [MethodImpl(MethodImplOptions.NoInlining)]
        public static string StaticString()
        {
            return "Static string";
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public string InstanceString()
        {
            return "Instance string";
        }
    }

    class MultiTieredClass
    {
        [MethodImpl(MethodImplOptions.NoInlining)]
        public static void Test(Vector2 v, int i)
        {
            v = Vector2.Normalize(v);
            Console.WriteLine($"Vector iteration {i:0000}:\t{v}\t{TestClass.StaticString()}");
        }
    }
}

Tested with .NET 5.0.102 on Windows and .NET 5.0.401 on WSL2 Ubuntu 20.04. This is the output:

Calling StaticString method before hacking:     Static string
Calling StaticString method after hacking:      Static string hijacked

Calling InstanceString method before hacking:   Instance string
Calling InstanceString method after hacking:    Instance string hijacked

Vector iteration 0001:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0002:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0003:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0004:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0005:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0006:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0007:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0008:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0009:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0010:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0011:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0012:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0013:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0014:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0015:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0016:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0017:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0018:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0019:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0020:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0021:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0022:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0023:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0024:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0025:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0026:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0027:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0028:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0029:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0030:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0031:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0032:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0033:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0034:  <0.97505456, -0.22196563>       Static string
Vector iteration 0035:  <0.97505456, -0.22196563>       Static string
Examine MethodDescriptor: 7FFA3ACF5218

So we clearly see that the hacking works and that after multitiered compilation kicks in it no longer calls method but inlines it.

]]>
https://blog.adamfurmanek.pl/2021/12/11/custom-memory-allocation-in-c-part-18/feed/ 0
Custom memory allocation in C# Part 17 — Hijacking methods on .NET 5 with modifying metadata curious thing https://blog.adamfurmanek.pl/2021/12/04/custom-memory-allocation-in-c-part-17/ https://blog.adamfurmanek.pl/2021/12/04/custom-memory-allocation-in-c-part-17/#respond Sat, 04 Dec 2021 09:00:48 +0000 https://blog.adamfurmanek.pl/?p=4270 Continue reading Custom memory allocation in C# Part 17 — Hijacking methods on .NET 5 with modifying metadata curious thing]]>

This is the seventeenth part of the Custom memory allocation series. For your convenience you can find other parts in the table of contents in Part 1 — Allocating object on a stack

I was rewriting my method hijacking samples to .NET 5 and I found an interesting behavior. Let’s take this code:

using System;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;

namespace OverridingSealedMethodNetCore
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"Calling StaticString method before hacking:\t{TestClass.StaticString()}");
            HijackMethod(typeof(TestClass), nameof(TestClass.StaticString), typeof(Program), nameof(StaticStringHijacked));
            Console.WriteLine($"Calling StaticString method after hacking:\t{TestClass.StaticString()}");

            Console.WriteLine();

            var instance = new TestClass();
            Console.WriteLine($"Calling InstanceString method before hacking:\t{instance.InstanceString()}");
            HijackMethod(typeof(TestClass), nameof(TestClass.InstanceString), typeof(Program), nameof(InstanceStringHijacked));
            Console.WriteLine($"Calling InstanceString method after hacking:\t{instance.InstanceString()}");

            Console.WriteLine();

            Vector2 v = new Vector2(9.856331f, -2.2437377f);
            for (int i = 1; i <= 35; i++)
            {
                MultiTieredClass.Test(v, i);
                Thread.Sleep(100);
            }
        }

        public static void HijackMethod(Type sourceType, string sourceMethod, Type targetType, string targetMethod)
        {
            // Get methods using reflection
            var source = sourceType.GetMethod(sourceMethod);
            var target = targetType.GetMethod(targetMethod);

            // Prepare methods to get machine code (not needed in this example, though)
            RuntimeHelpers.PrepareMethod(source.MethodHandle);
            RuntimeHelpers.PrepareMethod(target.MethodHandle);

            var sourceMethodDescriptorAddress = source.MethodHandle.Value;
            var targetMethodMachineCodeAddress = target.MethodHandle.GetFunctionPointer();

            // Pointer is two pointers from the beginning of the method descriptor
            Marshal.WriteIntPtr(sourceMethodDescriptorAddress, 2 * IntPtr.Size, targetMethodMachineCodeAddress);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public static string StaticStringHijacked()
        {
            return "Static string hijacked";
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public string InstanceStringHijacked()
        {
             return "Instance string hijacked";
        }
    }

    class TestClass
    {
        [MethodImpl(MethodImplOptions.NoInlining)]
        public static string StaticString()
        {
            return "Static string";
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public string InstanceString()
        {
            return "Instance string";
        }
    }

    class MultiTieredClass
    {
        [MethodImpl(MethodImplOptions.NoInlining)]
        public static void Test(Vector2 v, int i)
        {
            v = Vector2.Normalize(v);
            Console.WriteLine($"Vector iteration {i:0000}:\t{v}\t{TestClass.StaticString()}");
        }
    }
}

If you follow my blog then there is nothing new here. We try to hijack method by modifying its runtime metadata. The MultiTiered part is only to show recompilation of the code. I’m running this on W10 x64 in Release x64 mode and I’m getting this output:

Calling StaticString method before hacking:     Static string
Calling StaticString method after hacking:      Static string

Calling InstanceString method before hacking:   Instance string
Calling InstanceString method after hacking:    Instance string

Vector iteration 0001:  <0.9750545, -0.22196561>        Static string
Vector iteration 0002:  <0.9750545, -0.22196561>        Static string
Vector iteration 0003:  <0.9750545, -0.22196561>        Static string
Vector iteration 0004:  <0.9750545, -0.22196561>        Static string
Vector iteration 0005:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0006:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0007:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0008:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0009:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0010:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0011:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0012:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0013:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0014:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0015:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0016:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0017:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0018:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0019:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0020:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0021:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0022:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0023:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0024:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0025:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0026:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0027:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0028:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0029:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0030:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0031:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0032:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0033:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0034:  <0.9750545, -0.22196561>        Static string hijacked
Vector iteration 0035:  <0.97505456, -0.22196563>       Static string

And this is nice. Notice that first two lines of the output show that even though we hacked the method, we’re still not getting the new behavior. That’s first why.

Next, we see that we start calling the example of multitiered compilation method and first 4 instances are consistent. However, in fifth one we see that a hijacked method was called instead of the original one. That’s second why. This lasts until iteration 35 when multitiered compilation kicks in and recompiles things.

I don’t know the answer why it works this way but I presume there is this new code cache thing which was implemented around .NET Core 2.1 to support multitiered compilation. I may be wrong, though.

]]>
https://blog.adamfurmanek.pl/2021/12/04/custom-memory-allocation-in-c-part-17/feed/ 0
Custom memory allocation in C# Part 16 — Hijacking new on Linux with .NET 5 https://blog.adamfurmanek.pl/2021/09/25/custom-memory-allocation-in-c-part-16/ https://blog.adamfurmanek.pl/2021/09/25/custom-memory-allocation-in-c-part-16/#comments Sat, 25 Sep 2021 08:00:20 +0000 https://blog.adamfurmanek.pl/?p=4019 Continue reading Custom memory allocation in C# Part 16 — Hijacking new on Linux with .NET 5]]>

This is the sixteenth part of the Custom memory allocation series. For your convenience you can find other parts in the table of contents in Part 1 — Allocating object on a stack

I was recently asked if it’s possible to hijack the new operator in Linux. We’ve already seen that we can do it in both .NET Framework and .NET Core, in both x86_32 and x86_64, now it’s time to do it in .NET 5 on Linux.

Docker configuration

I’m going to use Ubuntu 20.04 on WSL2 running on Windows 10:

afish@ubuntu:/home/af$ uname -r
4.19.128-microsoft-standard

I’m going to use Docker for installing .NET and everything around. So I do this to create a container based on .NET 5:

docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --security-opt apparmor=unconfined --rm -i -v /home/afish/makeref:/makeref mcr.microsoft.com/dotnet/sdk:5.0 bash

You can see I’m mapping directory /home/afish/makeref and enabling some security flags to be able to debug application and modify page protection.

lldb and SOS

First thing I want to do is to install lldb and SOS:

apt-get update

Get:1 http://security.debian.org/debian-security buster/updates InRelease [65.4 kB]
Get:2 http://deb.debian.org/debian buster InRelease [121 kB]
Get:3 http://deb.debian.org/debian buster-updates InRelease [51.9 kB]
Get:4 http://security.debian.org/debian-security buster/updates/main amd64 Packages [258 kB]
Get:5 http://deb.debian.org/debian buster/main amd64 Packages [7907 kB]
Get:6 http://deb.debian.org/debian buster-updates/main amd64 Packages [7860 B]
Fetched 8412 kB in 2s (3467 kB/s)
Reading package lists...

apt-get install -y lldb

Reading package lists...
Building dependency tree...
Reading state information...
The following additional packages will be installed:
  binfmt-support bzip2 file libbsd0 libc-dev-bin libc6-dev libedit2 libffi-dev
  libgpm2 liblldb-7 libllvm7 libmagic-mgc libmagic1 libncurses-dev libncurses6
  libpipeline1 libpython-stdlib libpython2-stdlib libpython2.7
  libpython2.7-minimal libpython2.7-stdlib libreadline7 libsqlite3-0
  libtinfo-dev linux-libc-dev lldb-7 llvm-7 llvm-7-dev llvm-7-runtime lsb-base
  manpages manpages-dev mime-support python python-lldb-7 python-minimal
  python-six python2 python2-minimal python2.7 python2.7-minimal
  readline-common xz-utils
Suggested packages:
  bzip2-doc glibc-doc gpm ncurses-doc llvm-7-doc man-browser python-doc
  python-tk python2-doc python2.7-doc binutils readline-doc
The following NEW packages will be installed:
  binfmt-support bzip2 file libbsd0 libc-dev-bin libc6-dev libedit2 libffi-dev
  libgpm2 liblldb-7 libllvm7 libmagic-mgc libmagic1 libncurses-dev libncurses6
  libpipeline1 libpython-stdlib libpython2-stdlib libpython2.7
  libpython2.7-minimal libpython2.7-stdlib libreadline7 libsqlite3-0
  libtinfo-dev linux-libc-dev lldb lldb-7 llvm-7 llvm-7-dev llvm-7-runtime
  lsb-base manpages manpages-dev mime-support python python-lldb-7
  python-minimal python-six python2 python2-minimal python2.7
  python2.7-minimal readline-common xz-utils
0 upgraded, 44 newly installed, 0 to remove and 1 not upgraded.
Need to get 71.3 MB of archives.
After this operation, 369 MB of additional disk space will be used.
Get:1 http://deb.debian.org/debian buster/main amd64 libpython2.7-minimal amd64 2.7.16-2+deb10u1 [395 kB]
Get:2 http://deb.debian.org/debian buster/main amd64 python2.7-minimal amd64 2.7.16-2+deb10u1 [1369 kB]
Get:3 http://deb.debian.org/debian buster/main amd64 python2-minimal amd64 2.7.16-1 [41.4 kB]
Get:4 http://deb.debian.org/debian buster/main amd64 python-minimal amd64 2.7.16-1 [21.0 kB]
Get:5 http://deb.debian.org/debian buster/main amd64 mime-support all 3.62 [37.2 kB]
Get:6 http://deb.debian.org/debian buster/main amd64 readline-common all 7.0-5 [70.6 kB]
Get:7 http://deb.debian.org/debian buster/main amd64 libreadline7 amd64 7.0-5 [151 kB]
Get:8 http://deb.debian.org/debian buster/main amd64 libsqlite3-0 amd64 3.27.2-3+deb10u1 [641 kB]
Get:9 http://deb.debian.org/debian buster/main amd64 libpython2.7-stdlib amd64 2.7.16-2+deb10u1 [1912 kB]
Get:10 http://deb.debian.org/debian buster/main amd64 python2.7 amd64 2.7.16-2+deb10u1 [305 kB]
Get:11 http://deb.debian.org/debian buster/main amd64 libpython2-stdlib amd64 2.7.16-1 [20.8 kB]
Get:12 http://deb.debian.org/debian buster/main amd64 libpython-stdlib amd64 2.7.16-1 [20.8 kB]
Get:13 http://deb.debian.org/debian buster/main amd64 python2 amd64 2.7.16-1 [41.6 kB]
Get:14 http://deb.debian.org/debian buster/main amd64 python amd64 2.7.16-1 [22.8 kB]
Get:15 http://deb.debian.org/debian buster/main amd64 bzip2 amd64 1.0.6-9.2~deb10u1 [48.4 kB]
Get:16 http://deb.debian.org/debian buster/main amd64 libmagic-mgc amd64 1:5.35-4+deb10u1 [242 kB]
Get:17 http://deb.debian.org/debian buster/main amd64 libmagic1 amd64 1:5.35-4+deb10u1 [117 kB]
Get:18 http://deb.debian.org/debian buster/main amd64 file amd64 1:5.35-4+deb10u1 [66.4 kB]
Get:19 http://deb.debian.org/debian buster/main amd64 manpages all 4.16-2 [1295 kB]
Get:20 http://deb.debian.org/debian buster/main amd64 xz-utils amd64 5.2.4-1 [183 kB]
Get:21 http://deb.debian.org/debian buster/main amd64 libpipeline1 amd64 1.5.1-2 [31.2 kB]
Get:22 http://deb.debian.org/debian buster/main amd64 lsb-base all 10.2019051400 [28.4 kB]
Get:23 http://deb.debian.org/debian buster/main amd64 binfmt-support amd64 2.2.0-2 [70.0 kB]
Get:24 http://deb.debian.org/debian buster/main amd64 libbsd0 amd64 0.9.1-2 [99.5 kB]
Get:25 http://deb.debian.org/debian buster/main amd64 libc-dev-bin amd64 2.28-10 [275 kB]
Get:26 http://deb.debian.org/debian buster/main amd64 linux-libc-dev amd64 4.19.160-2 [1416 kB]
Get:27 http://deb.debian.org/debian buster/main amd64 libc6-dev amd64 2.28-10 [2691 kB]
Get:28 http://deb.debian.org/debian buster/main amd64 libedit2 amd64 3.1-20181209-1 [94.0 kB]
Get:29 http://deb.debian.org/debian buster/main amd64 libffi-dev amd64 3.2.1-9 [156 kB]
Get:30 http://deb.debian.org/debian buster/main amd64 libgpm2 amd64 1.20.7-5 [35.1 kB]
Get:31 http://deb.debian.org/debian buster/main amd64 libllvm7 amd64 1:7.0.1-8+deb10u2 [13.1 MB]
Get:32 http://deb.debian.org/debian buster/main amd64 libncurses6 amd64 6.1+20181013-2+deb10u2 [102 kB]
Get:33 http://deb.debian.org/debian buster/main amd64 libpython2.7 amd64 2.7.16-2+deb10u1 [1036 kB]
Get:34 http://deb.debian.org/debian buster/main amd64 liblldb-7 amd64 1:7.0.1-8+deb10u2 [7938 kB]
Get:35 http://deb.debian.org/debian buster/main amd64 libncurses-dev amd64 6.1+20181013-2+deb10u2 [333 kB]
Get:36 http://deb.debian.org/debian buster/main amd64 libtinfo-dev amd64 6.1+20181013-2+deb10u2 [940 B]
Get:37 http://deb.debian.org/debian buster/main amd64 llvm-7-runtime amd64 1:7.0.1-8+deb10u2 [190 kB]
Get:38 http://deb.debian.org/debian buster/main amd64 llvm-7 amd64 1:7.0.1-8+deb10u2 [4554 kB]
Get:39 http://deb.debian.org/debian buster/main amd64 llvm-7-dev amd64 1:7.0.1-8+deb10u2 [21.3 MB]
Get:40 http://deb.debian.org/debian buster/main amd64 python-six all 1.12.0-1 [15.7 kB]
Get:41 http://deb.debian.org/debian buster/main amd64 python-lldb-7 amd64 1:7.0.1-8+deb10u2 [122 kB]
Get:42 http://deb.debian.org/debian buster/main amd64 lldb-7 amd64 1:7.0.1-8+deb10u2 [8459 kB]
Get:43 http://deb.debian.org/debian buster/main amd64 lldb amd64 1:7.0-47 [7176 B]
Get:44 http://deb.debian.org/debian buster/main amd64 manpages-dev all 4.16-2 [2232 kB]
debconf: delaying package configuration, since apt-utils is not installed
Fetched 71.3 MB in 2s (29.9 MB/s)
Selecting previously unselected package libpython2.7-minimal:amd64.
(Reading database ... 9877 files and directories currently installed.)
Preparing to unpack .../00-libpython2.7-minimal_2.7.16-2+deb10u1_amd64.deb ...
Unpacking libpython2.7-minimal:amd64 (2.7.16-2+deb10u1) ...
Selecting previously unselected package python2.7-minimal.
Preparing to unpack .../01-python2.7-minimal_2.7.16-2+deb10u1_amd64.deb ...
Unpacking python2.7-minimal (2.7.16-2+deb10u1) ...
Selecting previously unselected package python2-minimal.
Preparing to unpack .../02-python2-minimal_2.7.16-1_amd64.deb ...
Unpacking python2-minimal (2.7.16-1) ...
Selecting previously unselected package python-minimal.
Preparing to unpack .../03-python-minimal_2.7.16-1_amd64.deb ...
Unpacking python-minimal (2.7.16-1) ...
Selecting previously unselected package mime-support.
Preparing to unpack .../04-mime-support_3.62_all.deb ...
Unpacking mime-support (3.62) ...
Selecting previously unselected package readline-common.
Preparing to unpack .../05-readline-common_7.0-5_all.deb ...
Unpacking readline-common (7.0-5) ...
Selecting previously unselected package libreadline7:amd64.
Preparing to unpack .../06-libreadline7_7.0-5_amd64.deb ...
Unpacking libreadline7:amd64 (7.0-5) ...
Selecting previously unselected package libsqlite3-0:amd64.
Preparing to unpack .../07-libsqlite3-0_3.27.2-3+deb10u1_amd64.deb ...
Unpacking libsqlite3-0:amd64 (3.27.2-3+deb10u1) ...
Selecting previously unselected package libpython2.7-stdlib:amd64.
Preparing to unpack .../08-libpython2.7-stdlib_2.7.16-2+deb10u1_amd64.deb ...
Unpacking libpython2.7-stdlib:amd64 (2.7.16-2+deb10u1) ...
Selecting previously unselected package python2.7.
Preparing to unpack .../09-python2.7_2.7.16-2+deb10u1_amd64.deb ...
Unpacking python2.7 (2.7.16-2+deb10u1) ...
Selecting previously unselected package libpython2-stdlib:amd64.
Preparing to unpack .../10-libpython2-stdlib_2.7.16-1_amd64.deb ...
Unpacking libpython2-stdlib:amd64 (2.7.16-1) ...
Selecting previously unselected package libpython-stdlib:amd64.
Preparing to unpack .../11-libpython-stdlib_2.7.16-1_amd64.deb ...
Unpacking libpython-stdlib:amd64 (2.7.16-1) ...
Setting up libpython2.7-minimal:amd64 (2.7.16-2+deb10u1) ...
Setting up python2.7-minimal (2.7.16-2+deb10u1) ...
Linking and byte-compiling packages for runtime python2.7...
Setting up python2-minimal (2.7.16-1) ...
Selecting previously unselected package python2.
(Reading database ... 10694 files and directories currently installed.)
Preparing to unpack .../python2_2.7.16-1_amd64.deb ...
Unpacking python2 (2.7.16-1) ...
Setting up python-minimal (2.7.16-1) ...
Selecting previously unselected package python.
(Reading database ... 10727 files and directories currently installed.)
Preparing to unpack .../00-python_2.7.16-1_amd64.deb ...
Unpacking python (2.7.16-1) ...
Selecting previously unselected package bzip2.
Preparing to unpack .../01-bzip2_1.0.6-9.2~deb10u1_amd64.deb ...
Unpacking bzip2 (1.0.6-9.2~deb10u1) ...
Selecting previously unselected package libmagic-mgc.
Preparing to unpack .../02-libmagic-mgc_1%3a5.35-4+deb10u1_amd64.deb ...
Unpacking libmagic-mgc (1:5.35-4+deb10u1) ...
Selecting previously unselected package libmagic1:amd64.
Preparing to unpack .../03-libmagic1_1%3a5.35-4+deb10u1_amd64.deb ...
Unpacking libmagic1:amd64 (1:5.35-4+deb10u1) ...
Selecting previously unselected package file.
Preparing to unpack .../04-file_1%3a5.35-4+deb10u1_amd64.deb ...
Unpacking file (1:5.35-4+deb10u1) ...
Selecting previously unselected package manpages.
Preparing to unpack .../05-manpages_4.16-2_all.deb ...
Unpacking manpages (4.16-2) ...
Selecting previously unselected package xz-utils.
Preparing to unpack .../06-xz-utils_5.2.4-1_amd64.deb ...
Unpacking xz-utils (5.2.4-1) ...
Selecting previously unselected package libpipeline1:amd64.
Preparing to unpack .../07-libpipeline1_1.5.1-2_amd64.deb ...
Unpacking libpipeline1:amd64 (1.5.1-2) ...
Selecting previously unselected package lsb-base.
Preparing to unpack .../08-lsb-base_10.2019051400_all.deb ...
Unpacking lsb-base (10.2019051400) ...
Selecting previously unselected package binfmt-support.
Preparing to unpack .../09-binfmt-support_2.2.0-2_amd64.deb ...
Unpacking binfmt-support (2.2.0-2) ...
Selecting previously unselected package libbsd0:amd64.
Preparing to unpack .../10-libbsd0_0.9.1-2_amd64.deb ...
Unpacking libbsd0:amd64 (0.9.1-2) ...
Selecting previously unselected package libc-dev-bin.
Preparing to unpack .../11-libc-dev-bin_2.28-10_amd64.deb ...
Unpacking libc-dev-bin (2.28-10) ...
Selecting previously unselected package linux-libc-dev:amd64.
Preparing to unpack .../12-linux-libc-dev_4.19.160-2_amd64.deb ...
Unpacking linux-libc-dev:amd64 (4.19.160-2) ...
Selecting previously unselected package libc6-dev:amd64.
Preparing to unpack .../13-libc6-dev_2.28-10_amd64.deb ...
Unpacking libc6-dev:amd64 (2.28-10) ...
Selecting previously unselected package libedit2:amd64.
Preparing to unpack .../14-libedit2_3.1-20181209-1_amd64.deb ...
Unpacking libedit2:amd64 (3.1-20181209-1) ...
Selecting previously unselected package libffi-dev:amd64.
Preparing to unpack .../15-libffi-dev_3.2.1-9_amd64.deb ...
Unpacking libffi-dev:amd64 (3.2.1-9) ...
Selecting previously unselected package libgpm2:amd64.
Preparing to unpack .../16-libgpm2_1.20.7-5_amd64.deb ...
Unpacking libgpm2:amd64 (1.20.7-5) ...
Selecting previously unselected package libllvm7:amd64.
Preparing to unpack .../17-libllvm7_1%3a7.0.1-8+deb10u2_amd64.deb ...
Unpacking libllvm7:amd64 (1:7.0.1-8+deb10u2) ...
Selecting previously unselected package libncurses6:amd64.
Preparing to unpack .../18-libncurses6_6.1+20181013-2+deb10u2_amd64.deb ...
Unpacking libncurses6:amd64 (6.1+20181013-2+deb10u2) ...
dSelecting previously unselected package libpython2.7:amd64.
Preparing to unpack .../19-libpython2.7_2.7.16-2+deb10u1_amd64.deb ...
Unpacking libpython2.7:amd64 (2.7.16-2+deb10u1) ...
irSelecting previously unselected package liblldb-7.
Preparing to unpack .../20-liblldb-7_1%3a7.0.1-8+deb10u2_amd64.deb ...
Unpacking liblldb-7 (1:7.0.1-8+deb10u2) ...
Selecting previously unselected package libncurses-dev:amd64.
Preparing to unpack .../21-libncurses-dev_6.1+20181013-2+deb10u2_amd64.deb ...
Unpacking libncurses-dev:amd64 (6.1+20181013-2+deb10u2) ...
Selecting previously unselected package libtinfo-dev:amd64.
Preparing to unpack .../22-libtinfo-dev_6.1+20181013-2+deb10u2_amd64.deb ...
Unpacking libtinfo-dev:amd64 (6.1+20181013-2+deb10u2) ...
Selecting previously unselected package llvm-7-runtime.
Preparing to unpack .../23-llvm-7-runtime_1%3a7.0.1-8+deb10u2_amd64.deb ...
Unpacking llvm-7-runtime (1:7.0.1-8+deb10u2) ...
Selecting previously unselected package llvm-7.
Preparing to unpack .../24-llvm-7_1%3a7.0.1-8+deb10u2_amd64.deb ...
Unpacking llvm-7 (1:7.0.1-8+deb10u2) ...
Selecting previously unselected package llvm-7-dev.
Preparing to unpack .../25-llvm-7-dev_1%3a7.0.1-8+deb10u2_amd64.deb ...
Unpacking llvm-7-dev (1:7.0.1-8+deb10u2) ...
Selecting previously unselected package python-six.
Preparing to unpack .../26-python-six_1.12.0-1_all.deb ...
Unpacking python-six (1.12.0-1) ...
Selecting previously unselected package python-lldb-7.
Preparing to unpack .../27-python-lldb-7_1%3a7.0.1-8+deb10u2_amd64.deb ...
Unpacking python-lldb-7 (1:7.0.1-8+deb10u2) ...
Selecting previously unselected package lldb-7.
Preparing to unpack .../28-lldb-7_1%3a7.0.1-8+deb10u2_amd64.deb ...
Unpacking lldb-7 (1:7.0.1-8+deb10u2) ...
Selecting previously unselected package lldb.
Preparing to unpack .../29-lldb_1%3a7.0-47_amd64.deb ...
Unpacking lldb (1:7.0-47) ...
Selecting previously unselected package manpages-dev.
Preparing to unpack .../30-manpages-dev_4.16-2_all.deb ...
Unpacking manpages-dev (4.16-2) ...
Setting up libpipeline1:amd64 (1.5.1-2) ...
Setting up lsb-base (10.2019051400) ...
Setting up libgpm2:amd64 (1.20.7-5) ...
Setting up mime-support (3.62) ...
Setting up libmagic-mgc (1:5.35-4+deb10u1) ...
Setting up manpages (4.16-2) ...
Setting up libsqlite3-0:amd64 (3.27.2-3+deb10u1) ...
Setting up libmagic1:amd64 (1:5.35-4+deb10u1) ...
Setting up linux-libc-dev:amd64 (4.19.160-2) ...
Setting up file (1:5.35-4+deb10u1) ...
Setting up bzip2 (1.0.6-9.2~deb10u1) ...
Setting up libffi-dev:amd64 (3.2.1-9) ...
Setting up libncurses6:amd64 (6.1+20181013-2+deb10u2) ...
Setting up xz-utils (5.2.4-1) ...
update-alternatives: using /usr/bin/xz to provide /usr/bin/lzma (lzma) in auto mode
update-alternatives: warning: skip creation of /usr/share/man/man1/lzma.1.gz because associated file /usr/share/man/man1/xz.1.gz (of link group lzma) doesn't exist
update-alternatives: warning: skip creation of /usr/share/man/man1/unlzma.1.gz because associated file /usr/share/man/man1/unxz.1.gz (of link group lzma) doesn't exist
update-alternatives: warning: skip creation of /usr/share/man/man1/lzcat.1.gz because associated file /usr/share/man/man1/xzcat.1.gz (of link group lzma) doesn't exist
update-alternatives: warning: skip creation of /usr/share/man/man1/lzmore.1.gz because associated file /usr/share/man/man1/xzmore.1.gz (of link group lzma) doesn't exist
update-alternatives: warning: skip creation of /usr/share/man/man1/lzless.1.gz because associated file /usr/share/man/man1/xzless.1.gz (of link group lzma) doesn't exist
update-alternatives: warning: skip creation of /usr/share/man/man1/lzdiff.1.gz because associated file /usr/share/man/man1/xzdiff.1.gz (of link group lzma) doesn't exist
update-alternatives: warning: skip creation of /usr/share/man/man1/lzcmp.1.gz because associated file /usr/share/man/man1/xzcmp.1.gz (of link group lzma) doesn't exist
update-alternatives: warning: skip creation of /usr/share/man/man1/lzgrep.1.gz because associated file /usr/share/man/man1/xzgrep.1.gz (of link group lzma) doesn't exist
update-alternatives: warning: skip creation of /usr/share/man/man1/lzegrep.1.gz because associated file /usr/share/man/man1/xzegrep.1.gz (of link group lzma) doesn't exist
update-alternatives: warning: skip creation of /usr/share/man/man1/lzfgrep.1.gz because associated file /usr/share/man/man1/xzfgrep.1.gz (of link group lzma) doesn't exist
Setting up binfmt-support (2.2.0-2) ...
invoke-rc.d: could not determine current runlevel
invoke-rc.d: policy-rc.d denied execution of start.
Setting up libc-dev-bin (2.28-10) ...
Setting up libbsd0:amd64 (0.9.1-2) ...
Setting up readline-common (7.0-5) ...
Setting up libreadline7:amd64 (7.0-5) ...
Setting up manpages-dev (4.16-2) ...
Setting up libedit2:amd64 (3.1-20181209-1) ...
Setting up libpython2.7-stdlib:amd64 (2.7.16-2+deb10u1) ...
Setting up libllvm7:amd64 (1:7.0.1-8+deb10u2) ...
Setting up libc6-dev:amd64 (2.28-10) ...
Setting up libpython2.7:amd64 (2.7.16-2+deb10u1) ...
Setting up libncurses-dev:amd64 (6.1+20181013-2+deb10u2) ...
Setting up llvm-7-runtime (1:7.0.1-8+deb10u2) ...
Setting up python2.7 (2.7.16-2+deb10u1) ...
Setting up llvm-7 (1:7.0.1-8+deb10u2) ...
Setting up libpython2-stdlib:amd64 (2.7.16-1) ...
Setting up python2 (2.7.16-1) ...
Setting up libpython-stdlib:amd64 (2.7.16-1) ...
Setting up python (2.7.16-1) ...
Setting up libtinfo-dev:amd64 (6.1+20181013-2+deb10u2) ...
Setting up liblldb-7 (1:7.0.1-8+deb10u2) ...
Setting up llvm-7-dev (1:7.0.1-8+deb10u2) ...
Setting up python-six (1.12.0-1) ...
Setting up python-lldb-7 (1:7.0.1-8+deb10u2) ...
Setting up lldb-7 (1:7.0.1-8+deb10u2) ...
Setting up lldb (1:7.0-47) ...
Processing triggers for libc-bin (2.28-10) ...

dotnet tool install --global dotnet-sos

Tools directory '/root/.dotnet/tools' is not currently on the PATH environment variable.
If you are using bash, you can add it to your profile by running the following command:

cat << \EOF >> ~/.bash_profile
# Add .NET Core SDK tools
export PATH="$PATH:/root/.dotnet/tools"
EOF

You can add it to the current session by running the following command:

export PATH="$PATH:/root/.dotnet/tools"

You can invoke the tool using the following command: dotnet-sos
Tool 'dotnet-sos' (version '5.0.160202') was successfully installed.

/root/.dotnet/tools/dotnet-sos install

Installing SOS to /root/.dotnet/sos from /root/.dotnet/tools/.store/dotnet-sos/5.0.160202/dotnet-sos/5.0.160202/tools/netcoreapp2.1/any/linux-x64
Creating installation directory...
Copying files...
Creating new /root/.lldbinit file - LLDB will load SOS automatically at startup
SOS install succeeded

Project configuration

Okay. Now we can create new project with dotnet new console -lang C# makeref, enter the directory and modify the csproj file:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>

</Project>

Notice that I added AllowUnsafeBlocks to enable unsafe code (which we already know is not needed but just to keep it simple).

Application

Finally, the application code:

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Reflection;


namespace HijackingNewOperatorNetCore
{
    class Program
    {
        static void Main(string[] args)
        {
            var allocator = new GenericMemoryAllocator();

            // Allocate object through allocator
            var customlyAlocated = allocator.Allocate<TestClass>();
            // Allocate ordinary object
            var ordinary = new object();

            // Hijack method and allocate object
            HijackNew();
            System.Diagnostics.Debugger.Break();
            var hijacked = new object();

            // Observe that hijacked objects are in generation 2
            Console.WriteLine($"Object customly allocated by hand: {GC.GetGeneration(customlyAlocated)}");
            Console.WriteLine($"Object created normally: {GC.GetGeneration(ordinary)}");
            Console.WriteLine($"Object with hijacked newobj: {GC.GetGeneration(hijacked)}");
        }

        public static void HijackNew()
        {
            var methodHandle = typeof(GenericMemoryAllocator).GetMethod(nameof(GenericMemoryAllocator.RawAllocate)).MethodHandle;
            RuntimeHelpers.PrepareMethod(methodHandle);

            var myAllocAddress = Marshal.ReadIntPtr(methodHandle.Value, 8);
            var defaultAllocAddress = GenericMemoryAllocator.GetAllocMethodAddress();


            int offset = (int)((long)myAllocAddress - defaultAllocAddress - 4 - 1); // 4 bytes for relative address and one byte for opcode
            byte[] instruction = {
                0xE9, // Long jump instruction
                (byte)(offset & 0xFF),
                (byte)((offset >> 8) & 0xFF),
                (byte)((offset >> 16) & 0xFF),
                (byte)((offset >> 24) & 0xFF)
            };

            GenericMemoryAllocator.UnlockPage((IntPtr)defaultAllocAddress);
            Marshal.Copy(instruction, 0, (IntPtr)defaultAllocAddress, instruction.Length);
        }
    }

    class TestClass
    {
        public int a, b, c, d;
    }
}

namespace HijackingNewOperatorNetCore
{
    class GenericMemoryAllocator
    {
        public T Allocate<T>()
        {
            var methodTable = typeof(T).TypeHandle.Value; // Get handle to the method table
            RawAllocate(methodTable); // Allocate the object and set the field, also JIT-compile the method
            return (T)Dummy;
        }

        // Method needs to be static in order to maintain the calling convention
        public static unsafe IntPtr RawAllocate(IntPtr methodTable)
        {
            // Calculate the object size by extracting it from method table and dividing by int size.
            // We assume that the size starts 4 bytes after the beginning of method table (works from .NET 3.5 to .NET Core 3.1)
            int objectSize = Marshal.ReadInt32(methodTable, 4) / sizeof(int);
            // Skip sizeof(int) bytes for syncblock
            _currentOffset++;
            // Write the address to method table
            Memory[_currentOffset] = methodTable;

            // Get the handle for the newly created object
            TypedReference newObjectReference = __makeref(Dummy);
            // Get the handle for the memory
            TypedReference memoryReference = __makeref(Memory);
            // Calculate the address of  the spawned object. We need to add 2 since we need to skip the method table of the array and the array size
            var spawnedObjectAddress = *(IntPtr*)*(IntPtr*)&memoryReference + (_currentOffset + 2) * sizeof(IntPtr);

            // Modify the handle for the new object using the address of the existing memory
            *(IntPtr*)*(IntPtr*)&newObjectReference = spawnedObjectAddress;

            // Move within the memory
            _currentOffset += objectSize;

            return *(IntPtr*)*(IntPtr*)&newObjectReference;
        }

        // Fields needs to be static in order to be accessible from RawAllocate
        private static bool Is64 = IntPtr.Size == sizeof(long);
        // Array big enough to be stored in Generation 2
        private static IntPtr[] Memory = new IntPtr[102400];
        private static int _currentOffset;
        private static object Dummy = new object();

        // This method is used to find the address of the CLR allocation function
        [MethodImpl(MethodImplOptions.NoOptimization)]
        private void CreateObject()
        {
            new object();
        }

        public static long GetAllocMethodAddress()
        {
            // Get the handle to the method creating the object
            var methodHandle = typeof(GenericMemoryAllocator).GetMethod(nameof(CreateObject), BindingFlags.NonPublic | BindingFlags.Instance).MethodHandle;

            // JIT-compile methods
            RuntimeHelpers.PrepareMethod(methodHandle);

            // Get the address of the jitted method
            IntPtr methodAddress = Marshal.ReadIntPtr(methodHandle.Value, 16);

            // Call to internal function differs between architectures, builds etc
            int offset = 51;

            // Read the jump offset
            int jumpOffset = 0;
            for (int i = 1; i < 5; ++i)
            {
                jumpOffset = jumpOffset + (Marshal.ReadByte(methodAddress, offset + i) << (i - 1) * 8);
            }
            // Calculate the absolute address
            long absoluteAddress = (long)methodAddress + offset + jumpOffset + 1 + 4; // 1 byte for jmp instruction, 4 bytes for relative address

            return absoluteAddress;
        }

        // Method to unlock the page for executing
        [DllImport("libc", SetLastError = true)]
        static extern int mprotect(IntPtr lpAddress, uint dwSize, uint flags);

        // Unlocks the page for executing
        public static void UnlockPage(IntPtr address)
        {
              long newAddress = ((long)address) & (long)(~0 << 12);
              IntPtr na = (IntPtr)newAddress;
              long length = ((long)address) + 6 - newAddress;
              // 1 for read, 2 for write, 4 for execute
              mprotect(na, (uint)length, 1 | 2 | 4);
        }
    }
}

This should look familiar to you. There are three main differences from Windows solution.

First, the CreateObject method in line 107 is now assembled differently. Machine code looks like this (and let’s see lldb in action at the same time):

dotnet --version

5.0.101

dotnet build

Microsoft (R) Build Engine version 16.8.0+126527ff1 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  All projects are up-to-date for restore.
/makeref/makeref/Program.cs(56,26): warning CS0649: Field 'TestClass.c' is never assigned to, and will always have its default value 0 [/makeref/makeref/makeref.csproj]
/makeref/makeref/Program.cs(56,23): warning CS0649: Field 'TestClass.b' is never assigned to, and will always have its default value 0 [/makeref/makeref/makeref.csproj]
/makeref/makeref/Program.cs(56,20): warning CS0649: Field 'TestClass.a' is never assigned to, and will always have its default value 0 [/makeref/makeref/makeref.csproj]
/makeref/makeref/Program.cs(56,29): warning CS0649: Field 'TestClass.d' is never assigned to, and will always have its default value 0 [/makeref/makeref/makeref.csproj]
  makeref -> /makeref/makeref/bin/Debug/net5.0/makeref.dll

Build succeeded.

/makeref/makeref/Program.cs(56,26): warning CS0649: Field 'TestClass.c' is never assigned to, and will always have its default value 0 [/makeref/makeref/makeref.csproj]
/makeref/makeref/Program.cs(56,23): warning CS0649: Field 'TestClass.b' is never assigned to, and will always have its default value 0 [/makeref/makeref/makeref.csproj]
/makeref/makeref/Program.cs(56,20): warning CS0649: Field 'TestClass.a' is never assigned to, and will always have its default value 0 [/makeref/makeref/makeref.csproj]
/makeref/makeref/Program.cs(56,29): warning CS0649: Field 'TestClass.d' is never assigned to, and will always have its default value 0 [/makeref/makeref/makeref.csproj]
    4 Warning(s)
    0 Error(s)

Time Elapsed 00:00:04.77

lldb bin/Debug/net5.0/makeref

(lldb) target create "bin/Debug/net5.0/makeref"
Current executable set to 'bin/Debug/net5.0/makeref' (x86_64).

r

(lldb) r
Process 1181 launched: '/makeref/makeref/bin/Debug/net5.0/makeref' (x86_64)
Process 1181 stopped
* thread #1, name = 'makeref', stop reason = signal SIGTRAP
    frame #0: 0x00007ffff73ee1ed libcoreclr.so`___lldb_unnamed_symbol15306$$libcoreclr.so + 1
libcoreclr.so`___lldb_unnamed_symbol15306$$libcoreclr.so:
->  0x7ffff73ee1ed <+1>: retq
    0x7ffff73ee1ee <+2>: nop

libcoreclr.so`___lldb_unnamed_symbol15307$$libcoreclr.so:
    0x7ffff73ee1f0 <+0>: pushq  %rbp
    0x7ffff73ee1f1 <+1>: movq   0xd8(%rdi), %r12

sos Name2EE makeref.dll HijackingNewOperatorNetCore.GenericMemoryAllocator.CreateObject

(lldb) sos Name2EE makeref.dll HijackingNewOperatorNetCore.GenericMemoryAllocator.CreateObject
Module:      00007fff7de42788
Assembly:    makeref.dll
Token:       0000000006000007
MethodDesc:  00007fff7deb6ea0
Name:        HijackingNewOperatorNetCore.GenericMemoryAllocator.CreateObject()
JITTED Code Address: 00007fff7dda9030

sos u 00007fff7dda9030

(lldb) sos u 00007fff7dda9030
Normal JIT generated code
HijackingNewOperatorNetCore.GenericMemoryAllocator.CreateObject()
ilAddr is 00007FFFF3D4F463 pImport is 00000000014B3BF0
Begin 00007FFF7DDA9030, size 4d

/makeref/makeref/Program.cs @ 113:
>>> 00007fff7dda9030 55                   push    rbp
00007fff7dda9031 4883ec10             sub     rsp, 0x10
00007fff7dda9035 488d6c2410           lea     rbp, [rsp + 0x10]
00007fff7dda903a 33c0                 xor     eax, eax
00007fff7dda903c 488945f0             mov     qword ptr [rbp - 0x10], rax
00007fff7dda9040 48897df8             mov     qword ptr [rbp - 0x8], rdi
00007fff7dda9044 48b8082ce47dff7f0000 movabs  rax, 0x7fff7de42c08
00007fff7dda904e 833800               cmp     dword ptr [rax], 0x0
00007fff7dda9051 7405                 je      0x7fff7dda9058
00007fff7dda9053 e828213879           call    0x7ffff712b180 (JitHelp: CORINFO_HELP_DBG_IS_JUST_MY_CODE)
00007fff7dda9058 90                   nop

/makeref/makeref/Program.cs @ 114:
00007fff7dda9059 48bf000cd67dff7f0000 movabs  rdi, 0x7fff7dd60c00
00007fff7dda9063 e8c8953779           call    0x7ffff7122630 (HijackingNewOperatorNetCore.GenericMemoryAllocator.RawAllocate(IntPtr), mdToken: 0000000006000006)
00007fff7dda9068 488945f0             mov     qword ptr [rbp - 0x10], rax
00007fff7dda906c 488b7df0             mov     rdi, qword ptr [rbp - 0x10]
00007fff7dda9070 e81370feff           call    0x7fff7dd90088 (System.Object..ctor(), mdToken: 000000000600045E)
00007fff7dda9075 90                   nop

/makeref/makeref/Program.cs @ 115:
00007fff7dda9076 90                   nop
00007fff7dda9077 488d6500             lea     rsp, [rbp]
00007fff7dda907b 5d                   pop     rbp
00007fff7dda907c c3                   ret

If you count all bytes you’ll find out that the offset is now 51.

Second difference is the method descriptor. Function address used to be 8 bytes from the beginning, now it’s 16 (in line 121):

memory read -count 64 00007fff7deb6ea0

(lldb) memory read -count 64 00007fff7deb6ea0
0x7fff7deb6ea0: 07 00 08 03 08 00 28 00 28 5b da 7d ff 7f 00 00  ......(.([.}....
0x7fff7deb6eb0: 30 90 da 7d ff 7f 00 00 08 00 0b 03 09 00 a8 00  0..}............
0x7fff7deb6ec0: 30 5b da 7d ff 7f 00 00 80 8a da 7d ff 7f 00 00  0[.}.......}....
0x7fff7deb6ed0: 09 00 0e 03 0a 00 8a 00 00 00 00 00 00 00 00 00  ................

Finally, we cannot use VirtualProtectEx anymore as we’re on Linux. We need to go with mprotect:

[DllImport("libc", SetLastError = true)]
static extern int mprotect(IntPtr lpAddress, uint dwSize, uint flags);

public static void UnlockPage(IntPtr address)
{
	  long newAddress = ((long)address) & (long)(~0 << 12);
	  IntPtr na = (IntPtr)newAddress;
	  long length = ((long)address) + 6 - newAddress;
	  // 1 for read, 2 for write, 4 for execute
	  mprotect(na, (uint)length, 1 | 2 | 4);
}

mprotect requires the address to be aligned to a page boundary (which is 4096 bytes on my machine) so I clear lowest 12 bits (line 6 in the listing above). Next, I calculate new offset of the method (I’m actually not sure if that’s needed). Finally, I enable all permissions for the page in line 10.

And just for the sake of completeness, final output:

dotnet run

Object customly allocated by hand: 2
Object created normally: 0
Object with hijacked newobj: 2

Final notes

As you can see, there is no magic in this approach, it’s just a bunch of bytes which we can modify in the same way as long as we’re on the same architecture. However, keep in mind the following:

  • I do not recommend using this in production code. I do use things like these in real applications but this is always risky and requires good understanding of all internals
  • This is just one of multiple allocation methods provided by .NET. If you want it to be “production ready” then you need to update all of them
  • Since you override the method globally, you can’t control easily when it’s called. In other words, .NET will use your logic as well so you need to take care of all memory management (or do some fancy juggling to call .NET methods when you actually need to allocate some new memory)
  • Keep in mind that .NET scans the heap and requires it to be parseable. Be careful with what you allocate and how. Also, make sure your objects are pinned or that you have good concurrency management (since GC can kick in anytime and move objects around)

Have fun!

]]>
https://blog.adamfurmanek.pl/2021/09/25/custom-memory-allocation-in-c-part-16/feed/ 1
.NET Inside Out Part 24 – Synchronous waiting for the Task in the same frame https://blog.adamfurmanek.pl/2020/08/29/net-inside-out-part-24/ https://blog.adamfurmanek.pl/2020/08/29/net-inside-out-part-24/#respond Sat, 29 Aug 2020 08:00:40 +0000 https://blog.adamfurmanek.pl/?p=3442 Continue reading .NET Inside Out Part 24 – Synchronous waiting for the Task in the same frame]]>

This is the twentieth fourth 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#

Let’s take this code:

using System;
using System.Threading;
using System.Threading.Tasks;
					
public class Program
{
	public static void Main()
	{
		Task.Factory.StartNew(Handle);
	}
	
	public static void Handle(){
		Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
		Task.Run(() => {
			Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
			Thread.Sleep(1000);
		}).Wait();
	}
}

and compare it with this one:

using System;
using System.Threading;
using System.Threading.Tasks;
					
public class Program
{
	public static void Main()
	{
		Task.Factory.StartNew(Handle);
	}
	
	public static void Handle(){
		Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
		var tcs = new TaskCompletionSource<bool>();
		Task.Run(() => {
			Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
			Thread.Sleep(1000);
			tcs.SetResult(true);
		});
		tcs.Task.Wait();
	}
}

They look very similar, however, they give different outputs. First code runs both tasks (Handle and one sleeping) on the same thread:

11
11

while the latter runs on different threads:

10
12

Why? That’s because scheduler tries to detect a situation when we are waiting on the task immediately (first code) and run it inline. In the second code this cannot be detected as we wait for “different” task.

]]>
https://blog.adamfurmanek.pl/2020/08/29/net-inside-out-part-24/feed/ 0
.NET Inside Out Part 23 – Machine code address of any .NET Core method https://blog.adamfurmanek.pl/2020/08/22/net-inside-out-part-23/ https://blog.adamfurmanek.pl/2020/08/22/net-inside-out-part-23/#respond Sat, 22 Aug 2020 08:00:48 +0000 https://blog.adamfurmanek.pl/?p=3433 Continue reading .NET Inside Out Part 23 – Machine code address of any .NET Core method]]>

This is the twentieth third 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#

.NET Core introduced tiered compilation and reworked AOT compilation. Previously, we could get address of machine code by calling GetFunctionPointer on a method handle. However, it didn’t work for NGEN-ed methods and doesn’t work for ReadyToRun code or tiered methods. How to do it?

If you go through coreclr repository you’ll find this method:

PCODE MethodDesc::GetNativeCode()

If we call it from C# we’ll be able to get the machine code address. Let’s do it:

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace MachineCodeAddress
{
    delegate int CallMethodReturnPointer();

    class Program
    {
        static void Main(string[] args)
        {
            // Run as x86
            // .load C:\users\afish\desktop\tools\sos\x86\sos.dll
            // x coreclr!MethodDesc::GetNativeCode
            // Gives
            // 79c6be0c          coreclr!MethodDesc::GetNativeCode (void)
            // Replace address below with real one (or automate extracting it from coreclr.pdb + coreclr location in runtime)
            var methodDescriptorGetNativeCode_nativeAddress = 0x7c747dbb;
            var methodHandle = typeof(FileStream).GetMethod("WriteFileNative", BindingFlags.NonPublic | BindingFlags.Instance).MethodHandle;

            RuntimeHelpers.PrepareMethod(methodHandle);
            var mainHandle = (int)methodHandle.Value;
            Console.ReadLine();

            var getCodeAddress_code = new byte[]
            {
                0xb9, (byte)(mainHandle & 0xFF), (byte)((mainHandle >> 8) & 0xFF), (byte)((mainHandle >> 16) & 0xFF), (byte)((mainHandle >> 24) & 0xFF), // b9 44 33 22 11 mov ecx,0x11223344
                0x68 , (byte)(methodDescriptorGetNativeCode_nativeAddress & 0xFF), (byte)((methodDescriptorGetNativeCode_nativeAddress >> 8) & 0xFF), (byte)((methodDescriptorGetNativeCode_nativeAddress >> 16) & 0xFF), (byte)((methodDescriptorGetNativeCode_nativeAddress >> 24) & 0xFF),// 68 bb 7d 74 7c push   0x7c747dbb
                0xc3, // c3 ret
                0xc3, // c3 ret
            };
            CallMethodReturnPointer getCodeAddress = FuncGenerator.Generate<CallMethodReturnPointer>(getCodeAddress_code);
            var codeAddress = getCodeAddress();

            // These two should be different for ReadyToRun code
            Console.WriteLine($"CodeAddress: {codeAddress.ToString("X")}");
            Console.WriteLine($"FunctionPointer: {methodHandle.GetFunctionPointer().ToString("X")}");
            Console.ReadLine();
        }
    }

    // Flags for VirtualProtect method
    public enum Protection
    {
        PAGE_NOACCESS = 0x01,
        PAGE_READONLY = 0x02,
        PAGE_READWRITE = 0x04,
        PAGE_WRITECOPY = 0x08,
        PAGE_EXECUTE = 0x10,
        PAGE_EXECUTE_READ = 0x20,
        PAGE_EXECUTE_READWRITE = 0x40,
        PAGE_EXECUTE_WRITECOPY = 0x80,
        PAGE_GUARD = 0x100,
        PAGE_NOCACHE = 0x200,
        PAGE_WRITECOMBINE = 0x400
    }

    public class FuncGenerator
    {
        // Method to unlock page for executing
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect);

        // Unlocks page for executing
        private static void UnlockPage(int address)
        {
            uint old;
            VirtualProtect((IntPtr)address, 6, (uint)Protection.PAGE_EXECUTE_READWRITE, out old);
        }

        // Some internal storage for pinning
        private static IList<object> memory = new List<object>();
        private static IList<GCHandle> handles = new List<GCHandle>();

        // Pins array with code and returns address to the beginning of the array
        private static IntPtr Pin(object data)
        {
            memory.Add(data);
            var handle = GCHandle.Alloc(data);
            handles.Add(handle);

            return Marshal.ReadIntPtr(GCHandle.ToIntPtr(handle));
        }

        // Returns delegate of type T using class U for stubbing
        public static T Generate<T>(byte[] data)
        {
            // Address of machine code in array
            // We omit first 8 bytes (array type and size)
            var arrayCodeAddress = ((int)Pin(data)) + 8;

            // Unlock page so we can execute code from it
            UnlockPage(arrayCodeAddress);
            Console.WriteLine("Machine code in array address: " + arrayCodeAddress.ToString("X"));

            // Returns delegate of correct type so we have runtime type checking
            return (T)(object)Marshal.GetDelegateForFunctionPointer<T>((IntPtr)arrayCodeAddress);
        }
    }
}

General idea is we want to generate some machine code to call the method directly. To do that, we use .NET Inside Out Part 9 — Generating Func from a bunch of bytes in C# revisited approach. In lines 31-34 we pass the method descriptor via ecx register (the this pointer). Then use the trick to push address and return to it which is effectively a call to an absolute address. Finally, we return the value of the eax register. Rest of the code is explained in other part.

You can run this on some ReadyToRun method (like in the example) and see that value returned by internal method differs from the value returned by .NET API.

It works, however, we need to know the physical address of the MethodDesc::GetNativeCode() method. For the purpose of this demo I just extracted it with WinDBG, however, you can automate it in many ways. Keep in mind that the address will change between applications and system restarts.

]]>
https://blog.adamfurmanek.pl/2020/08/22/net-inside-out-part-23/feed/ 0
Custom memory allocation in C# Part 10 — Hijacking new in .NET Core https://blog.adamfurmanek.pl/2018/09/22/custom-memory-allocation-in-c-part-10/ https://blog.adamfurmanek.pl/2018/09/22/custom-memory-allocation-in-c-part-10/#comments Sat, 22 Sep 2018 08:00:25 +0000 https://blog.adamfurmanek.pl/?p=2599 Continue reading Custom memory allocation in C# Part 10 — Hijacking new in .NET Core]]>

This is the tenth part of the Custom memory allocation series. For your convenience you can find other parts in the table of contents in Part 1 — Allocating object on a stack

I had .NET Core 2.1.103 x64 installed and I wanted to hijack new with the code from the last post. Unfortunately, it crashed because of very silly thing. Let’s see:

0:003> .loadby sos coreclr

0:003> !name2ee * Program
Module:      00007ffb6db41000
Assembly:    System.Private.CoreLib.dll
--------------------------------------
Module:      00007ffb0ed04580
Assembly:    GenericUnsafeAlloc_core.dll
--------------------------------------
Module:      00007ffb0ed04df0
Assembly:    System.Runtime.dll
--------------------------------------
Module:      00007ffb0ed05bb8
Assembly:    System.Console.dll
--------------------------------------
Module:      00007ffb0ed06ef0
Assembly:    System.Runtime.InteropServices.dll
--------------------------------------
Module:      00007ffb0ed07b90
Assembly:    System.Threading.dll
--------------------------------------
Module:      00007ffb0ed08500
Assembly:    System.Runtime.Extensions.dll

0:003> !dumpmodule -mt 00007ffb0ed04580
Name:       C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc_core\bin\Debug\netcoreapp2.0\GenericUnsafeAlloc_core.dll
Attributes: PEFile SupportsUpdateableMethods
Assembly:   000002803d1bbc60
LoaderHeap:              0000000000000000
TypeDefToMethodTableMap: 00007ffb0ed003a8
TypeRefToMethodTableMap: 00007ffb0ed003d8
MethodDefToDescMap:      00007ffb0ed004d0
FieldDefToDescMap:       00007ffb0ed00540
MemberRefToDescMap:      0000000000000000
FileReferencesMap:       00007ffb0ed00600
AssemblyReferencesMap:   00007ffb0ed00608
MetaData start address:  0000028001ca23a4 (3732 bytes)

Types defined in this module

              MT          TypeDef Name
------------------------------------------------------------------------------
00007ffb0ed05858 0x02000003 GenericUnsafeAlloc_core.GenericMemoryAllocator
00007ffb0ed05688 0x02000004 GenericUnsafeAlloc_core.Program
00007ffb0ed05950 0x02000005 GenericUnsafeAlloc_core.TestClass

Types referenced in this module

              MT            TypeRef Name
------------------------------------------------------------------------------
00007ffb6e570410 0x02000010 System.Object
00007ffb6e5705c0 0x02000012 System.RuntimeTypeHandle
00007ffb6e5745a0 0x02000013 System.RuntimeMethodHandle
00007ffb6e586dc8 0x02000014 System.IntPtr
00007ffb6e570690 0x02000015 System.Type
00007ffb6e571de8 0x02000016 System.Runtime.InteropServices.Marshal
00007ffb6e5740d0 0x02000019 System.Reflection.MethodBase
00007ffb6e573af0 0x0200001a System.Runtime.CompilerServices.RuntimeHelpers
00007ffb6e56fcb0 0x0200001b System.Byte
00007ffb0ed06d50 0x0200001c System.Console
00007ffb6e5745f0 0x0200001d System.GC
00007ffb6e571968 0x0200001e System.Int32

0:003> !dumpmt -md 00007ffb0ed05858 
EEClass:         00007ffb0eea1100
Module:          00007ffb0ed04580
Name:            GenericUnsafeAlloc_core.GenericMemoryAllocator
mdToken:         0000000002000003
File:            C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc_core\bin\Debug\netcoreapp2.0\GenericUnsafeAlloc_core.dll
BaseSize:        0x18
ComponentSize:   0x0
Slots in VTable: 14
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc Table
           Entry       MethodDesc    JIT Name
00007ffb6e182020 00007ffb6dce0988 PreJIT System.Object.ToString()
00007ffb6e182040 00007ffb6dce0990 PreJIT System.Object.Equals(System.Object)
00007ffb6e182090 00007ffb6dce09b8 PreJIT System.Object.GetHashCode()
00007ffb6e1820a0 00007ffb6dce09d8 PreJIT System.Object.Finalize()
00007ffb0ee21e00 00007ffb0ed05840    JIT GenericUnsafeAlloc_core.GenericMemoryAllocator..cctor()
00007ffb0ee21a30 00007ffb0ed05838    JIT GenericUnsafeAlloc_core.GenericMemoryAllocator..ctor()
00007ffb0ee210f0 00007ffb0ed057d8   NONE GenericUnsafeAlloc_core.GenericMemoryAllocator.Allocate()
00007ffb0ee210c0 00007ffb0ed05770   NONE GenericUnsafeAlloc_core.GenericMemoryAllocator.VirtualProtect(IntPtr, UInt32, UInt32, UInt32 ByRef)
00007ffb0ee222f0 00007ffb0ed057b8    JIT GenericUnsafeAlloc_core.GenericMemoryAllocator.UnlockPage(IntPtr)
00007ffb0ee210e0 00007ffb0ed057c8   NONE GenericUnsafeAlloc_core.GenericMemoryAllocator.GetReferenceAsPointer(System.Object)
00007ffb0ee21bd0 00007ffb0ed057f8    JIT GenericUnsafeAlloc_core.GenericMemoryAllocator.RawAllocate(IntPtr)
00007ffb0ee22290 00007ffb0ed05808   NONE GenericUnsafeAlloc_core.GenericMemoryAllocator.CreateObject()
00007ffb0ee220f0 00007ffb0ed05818    JIT GenericUnsafeAlloc_core.GenericMemoryAllocator.GetAllocMethodAddress()
00007ffb0ee21ed0 00007ffb0ed05828    JIT GenericUnsafeAlloc_core.GenericMemoryAllocator.HijackNew()

Yes, CreateObject is not jitted even though PrepareMethod was called. Quick look at GH and we have the following. So I updated the SDK and could carry on:

0:003> !dumpmt -md 00007ffb0ed05688 
EEClass:         00007ffb0eea1088
Module:          00007ffb0ed04580
Name:            GenericUnsafeAlloc_core.Program
mdToken:         0000000002000004
File:            C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc_core\bin\Debug\netcoreapp2.0\GenericUnsafeAlloc_core.dll
BaseSize:        0x18
ComponentSize:   0x0
Slots in VTable: 6
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc Table
           Entry       MethodDesc    JIT Name
00007ffb6e182020 00007ffb6dce0988 PreJIT System.Object.ToString()
00007ffb6e182040 00007ffb6dce0990 PreJIT System.Object.Equals(System.Object)
00007ffb6e182090 00007ffb6dce09b8 PreJIT System.Object.GetHashCode()
00007ffb6e1820a0 00007ffb6dce09d8 PreJIT System.Object.Finalize()
00007ffb0ee210a0 00007ffb0ed05680   NONE GenericUnsafeAlloc_core.Program..ctor()
00007ffb0ee21810 00007ffb0ed05670    JIT GenericUnsafeAlloc_core.Program.Main()

0:003> !U 00007ffb0ee21810 
Normal JIT generated code
GenericUnsafeAlloc_core.Program.Main()
Begin 00007ffb0ee21810, size 207

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc_core\Program.cs @ 163:
>>> 00007ffb`0ee21810 55              push    rbp
00007ffb`0ee21811 57              push    rdi
00007ffb`0ee21812 4881ecb8000000  sub     rsp,0B8h
00007ffb`0ee21819 488dac24c0000000 lea     rbp,[rsp+0C0h]
00007ffb`0ee21821 488dbd6cffffff  lea     rdi,[rbp-94h]
00007ffb`0ee21828 b923000000      mov     ecx,23h
00007ffb`0ee2182d 33c0            xor     eax,eax
00007ffb`0ee2182f f3ab            rep stos dword ptr [rdi]
00007ffb`0ee21831 833dc831eeff00  cmp     dword ptr [00007ffb`0ed04a00],0
00007ffb`0ee21838 7405            je      00007ffb`0ee2183f
00007ffb`0ee2183a e801bfc45f      call    coreclr!JIT_DbgIsJustMyCode (00007ffb`6ea6d740)
00007ffb`0ee2183f 90              nop

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc_core\Program.cs @ 165:
00007ffb`0ee21840 48b95858d00efb7f0000 mov rcx,7FFB0ED05858h (MT: GenericUnsafeAlloc_core.GenericMemoryAllocator)
00007ffb`0ee2184a e851e0ad5f      call    coreclr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`6e8ff8a0)
00007ffb`0ee2184f 488945d0        mov     qword ptr [rbp-30h],rax
00007ffb`0ee21853 488b4dd0        mov     rcx,qword ptr [rbp-30h]
00007ffb`0ee21857 e8e4f8ffff      call    00007ffb`0ee21140 (GenericUnsafeAlloc_core.GenericMemoryAllocator..ctor(), mdToken: 0000000006000009)
00007ffb`0ee2185c 488b4dd0        mov     rcx,qword ptr [rbp-30h]
00007ffb`0ee21860 48894df0        mov     qword ptr [rbp-10h],rcx

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc_core\Program.cs @ 166:
00007ffb`0ee21864 488b4df0        mov     rcx,qword ptr [rbp-10h]
00007ffb`0ee21868 48ba885ad00efb7f0000 mov rdx,7FFB0ED05A88h (MD: GenericUnsafeAlloc_core.GenericMemoryAllocator.Allocate[[GenericUnsafeAlloc_core.TestClass, GenericUnsafeAlloc_core]]())
00007ffb`0ee21872 3909            cmp     dword ptr [rcx],ecx
00007ffb`0ee21874 e8f7f8ffff      call    00007ffb`0ee21170 (GenericUnsafeAlloc_core.GenericMemoryAllocator.Allocate[[System.__Canon, System.Private.CoreLib]](), mdToken: 0000000006000004)
00007ffb`0ee21879 488945c8        mov     qword ptr [rbp-38h],rax
00007ffb`0ee2187d 488b4dc8        mov     rcx,qword ptr [rbp-38h]
00007ffb`0ee21881 48894de8        mov     qword ptr [rbp-18h],rcx

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc_core\Program.cs @ 169:
00007ffb`0ee21885 48b91004576efb7f0000 mov rcx,offset System_Private_CoreLib+0xa30410 (00007ffb`6e570410) (MT: System.Object)
00007ffb`0ee2188f e80ce0ad5f      call    coreclr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`6e8ff8a0)
00007ffb`0ee21894 488945c0        mov     qword ptr [rbp-40h],rax
00007ffb`0ee21898 488b4dc0        mov     rcx,qword ptr [rbp-40h]
00007ffb`0ee2189c e86f07365f      call    System_Private_CoreLib+0x642010 (00007ffb`6e182010) (System.Object..ctor(), mdToken: 0000000006000191)
00007ffb`0ee218a1 488b45c0        mov     rax,qword ptr [rbp-40h]
00007ffb`0ee218a5 488945e0        mov     qword ptr [rbp-20h],rax

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc_core\Program.cs @ 172:
00007ffb`0ee218a9 e882f8ffff      call    00007ffb`0ee21130 (GenericUnsafeAlloc_core.GenericMemoryAllocator.HijackNew(), mdToken: 0000000006000008)
00007ffb`0ee218ae 90              nop

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc_core\Program.cs @ 173:
00007ffb`0ee218af e81cfeffff      call    00007ffb`0ee216d0 (System.Console.ReadLine(), mdToken: 0000000006000075)
00007ffb`0ee218b4 488945b8        mov     qword ptr [rbp-48h],rax
00007ffb`0ee218b8 90              nop

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc_core\Program.cs @ 174:
00007ffb`0ee218b9 48b91004576efb7f0000 mov rcx,offset System_Private_CoreLib+0xa30410 (00007ffb`6e570410) (MT: System.Object)
00007ffb`0ee218c3 e8d8dfad5f      call    coreclr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`6e8ff8a0)
00007ffb`0ee218c8 488945b0        mov     qword ptr [rbp-50h],rax
00007ffb`0ee218cc 488b4db0        mov     rcx,qword ptr [rbp-50h]
00007ffb`0ee218d0 e83b07365f      call    System_Private_CoreLib+0x642010 (00007ffb`6e182010) (System.Object..ctor(), mdToken: 0000000006000191)
00007ffb`0ee218d5 488b4db0        mov     rcx,qword ptr [rbp-50h]
00007ffb`0ee218d9 48894dd8        mov     qword ptr [rbp-28h],rcx

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc_core\Program.cs @ 177:
00007ffb`0ee218dd 48b96830cf1180020000 mov rcx,28011CF3068h
00007ffb`0ee218e7 488b09          mov     rcx,qword ptr [rcx]
00007ffb`0ee218ea 48894da8        mov     qword ptr [rbp-58h],rcx
00007ffb`0ee218ee 488b4df0        mov     rcx,qword ptr [rbp-10h]
00007ffb`0ee218f2 e8f957ad5f      call    coreclr!GCInterface::GetGeneration (00007ffb`6e8f70f0)
00007ffb`0ee218f7 8945a4          mov     dword ptr [rbp-5Ch],eax
00007ffb`0ee218fa 48b96819576efb7f0000 mov rcx,offset System_Private_CoreLib+0xa31968 (00007ffb`6e571968) (MT: System.Int32)
00007ffb`0ee21904 e897dfad5f      call    coreclr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`6e8ff8a0)
00007ffb`0ee21909 48894598        mov     qword ptr [rbp-68h],rax
00007ffb`0ee2190d 488b5598        mov     rdx,qword ptr [rbp-68h]
00007ffb`0ee21911 8b4da4          mov     ecx,dword ptr [rbp-5Ch]
00007ffb`0ee21914 894a08          mov     dword ptr [rdx+8],ecx
00007ffb`0ee21917 488b5598        mov     rdx,qword ptr [rbp-68h]
00007ffb`0ee2191b 488b4da8        mov     rcx,qword ptr [rbp-58h]
00007ffb`0ee2191f e824feffff      call    00007ffb`0ee21748 (System.Console.WriteLine(System.String, System.Object), mdToken: 0000000006000084)
00007ffb`0ee21924 90              nop

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc_core\Program.cs @ 178:
00007ffb`0ee21925 48b97030cf1180020000 mov rcx,28011CF3070h
00007ffb`0ee2192f 488b09          mov     rcx,qword ptr [rcx]
00007ffb`0ee21932 48894d90        mov     qword ptr [rbp-70h],rcx
00007ffb`0ee21936 488b4de8        mov     rcx,qword ptr [rbp-18h]
00007ffb`0ee2193a e8b157ad5f      call    coreclr!GCInterface::GetGeneration (00007ffb`6e8f70f0)
00007ffb`0ee2193f 89458c          mov     dword ptr [rbp-74h],eax
00007ffb`0ee21942 48b96819576efb7f0000 mov rcx,offset System_Private_CoreLib+0xa31968 (00007ffb`6e571968) (MT: System.Int32)
00007ffb`0ee2194c e84fdfad5f      call    coreclr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`6e8ff8a0)
00007ffb`0ee21951 48894598        mov     qword ptr [rbp-68h],rax
00007ffb`0ee21955 488b5598        mov     rdx,qword ptr [rbp-68h]
00007ffb`0ee21959 8b4d8c          mov     ecx,dword ptr [rbp-74h]
00007ffb`0ee2195c 894a08          mov     dword ptr [rdx+8],ecx
00007ffb`0ee2195f 488b5598        mov     rdx,qword ptr [rbp-68h]
00007ffb`0ee21963 488b4d90        mov     rcx,qword ptr [rbp-70h]
00007ffb`0ee21967 e8dcfdffff      call    00007ffb`0ee21748 (System.Console.WriteLine(System.String, System.Object), mdToken: 0000000006000084)
00007ffb`0ee2196c 90              nop

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc_core\Program.cs @ 179:
00007ffb`0ee2196d 48b97830cf1180020000 mov rcx,28011CF3078h
00007ffb`0ee21977 488b09          mov     rcx,qword ptr [rcx]
00007ffb`0ee2197a 48894d80        mov     qword ptr [rbp-80h],rcx
00007ffb`0ee2197e 488b4de0        mov     rcx,qword ptr [rbp-20h]
00007ffb`0ee21982 e86957ad5f      call    coreclr!GCInterface::GetGeneration (00007ffb`6e8f70f0)
00007ffb`0ee21987 89857cffffff    mov     dword ptr [rbp-84h],eax
00007ffb`0ee2198d 48b96819576efb7f0000 mov rcx,offset System_Private_CoreLib+0xa31968 (00007ffb`6e571968) (MT: System.Int32)
00007ffb`0ee21997 e804dfad5f      call    coreclr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`6e8ff8a0)
00007ffb`0ee2199c 48894598        mov     qword ptr [rbp-68h],rax
00007ffb`0ee219a0 488b5598        mov     rdx,qword ptr [rbp-68h]
00007ffb`0ee219a4 8b8d7cffffff    mov     ecx,dword ptr [rbp-84h]
00007ffb`0ee219aa 894a08          mov     dword ptr [rdx+8],ecx
00007ffb`0ee219ad 488b5598        mov     rdx,qword ptr [rbp-68h]
00007ffb`0ee219b1 488b4d80        mov     rcx,qword ptr [rbp-80h]
00007ffb`0ee219b5 e88efdffff      call    00007ffb`0ee21748 (System.Console.WriteLine(System.String, System.Object), mdToken: 0000000006000084)
00007ffb`0ee219ba 90              nop

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc_core\Program.cs @ 180:
00007ffb`0ee219bb 48b98030cf1180020000 mov rcx,28011CF3080h
00007ffb`0ee219c5 488b09          mov     rcx,qword ptr [rcx]
00007ffb`0ee219c8 48898d70ffffff  mov     qword ptr [rbp-90h],rcx
00007ffb`0ee219cf 488b4dd8        mov     rcx,qword ptr [rbp-28h]
00007ffb`0ee219d3 e81857ad5f      call    coreclr!GCInterface::GetGeneration (00007ffb`6e8f70f0)
00007ffb`0ee219d8 89856cffffff    mov     dword ptr [rbp-94h],eax
00007ffb`0ee219de 48b96819576efb7f0000 mov rcx,offset System_Private_CoreLib+0xa31968 (00007ffb`6e571968) (MT: System.Int32)
00007ffb`0ee219e8 e8b3dead5f      call    coreclr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`6e8ff8a0)
00007ffb`0ee219ed 48894598        mov     qword ptr [rbp-68h],rax
00007ffb`0ee219f1 488b5598        mov     rdx,qword ptr [rbp-68h]
00007ffb`0ee219f5 8b8d6cffffff    mov     ecx,dword ptr [rbp-94h]
00007ffb`0ee219fb 894a08          mov     dword ptr [rdx+8],ecx
00007ffb`0ee219fe 488b5598        mov     rdx,qword ptr [rbp-68h]
00007ffb`0ee21a02 488b8d70ffffff  mov     rcx,qword ptr [rbp-90h]
00007ffb`0ee21a09 e83afdffff      call    00007ffb`0ee21748 (System.Console.WriteLine(System.String, System.Object), mdToken: 0000000006000084)
00007ffb`0ee21a0e 90              nop

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc_core\Program.cs @ 181:
00007ffb`0ee21a0f 90              nop
00007ffb`0ee21a10 488d65f8        lea     rsp,[rbp-8]
00007ffb`0ee21a14 5f              pop     rdi
00007ffb`0ee21a15 5d              pop     rbp
00007ffb`0ee21a16 c3              ret

Now the allocator is called coreclr!JIT_TrialAllocSFastMP_InlineGetThread Let’s see:

0:003> !U 00007ffb`6e8ff8a0
Unmanaged code
>>> E:\A\_work\13\s\src\vm\amd64\JitHelpers_InlineGetThread.asm:40
00007ffb`6e8ff8a0 e92b2352a0          mov     edx,dword ptr [rcx+4]

Well, that was not very helpful, apparently something is wrong with WinDBG. Anyway, after updating .NET Core everything works like a charm. Tested with Windows 10 x64 1703, .NET Core 2.1.301 x64, compiled as .NET Core 2.1 for Debug Any CPU (works for Release as well).

]]>
https://blog.adamfurmanek.pl/2018/09/22/custom-memory-allocation-in-c-part-10/feed/ 1
Custom memory allocation in C# Part 9 — Hijacking new in x64 https://blog.adamfurmanek.pl/2018/09/15/custom-memory-allocation-in-c-part-9/ https://blog.adamfurmanek.pl/2018/09/15/custom-memory-allocation-in-c-part-9/#comments Sat, 15 Sep 2018 08:00:20 +0000 https://blog.adamfurmanek.pl/?p=2597 Continue reading Custom memory allocation in C# Part 9 — Hijacking new in x64]]>

This is the ninth part of the Custom memory allocation series. For your convenience you can find other parts in the table of contents in Part 1 — Allocating object on a stack

We already hijacked new in C#. Last time we did this for 32-bit applications, today we will modify the code to support x64. Let’s go.

Object allocation

Let’s start with object allocation. We had the helper method (which was not used anyway) for getting pointer to the object. Just for keeping everything clean, we can fix it:

private unsafe IntPtr* GetReferenceAsPointer(object obj)
{
	// The order of memory is:
	// [raw bytes] <- reference <- __makeref(reference) <- &__makeref(reference)
	var managedPointerToRawMemory = obj; // This is reference
	TypedReference managedPointerToPointerToRawMemory = __makeref(managedPointerToRawMemory); // This is pointer to reference, we need to cast it to correct type, but we cannot do it directly and we need to get address of it
	var pointerToPointerToPointerToRawMemory = (IntPtr*)&managedPointerToPointerToRawMemory; // We get an address
	var pointerToPointerToRawMemory = (IntPtr*)*pointerToPointerToPointerToRawMemory; // And we dereference it, this is __makeref(reference)
	var pointerToRawMemory = (IntPtr*)*pointerToPointerToRawMemory; //This is reference
	return pointerToRawMemory;
}

No big changes, just replaced int with IntPtr.

Now, let’s modify RawAllocate:

private static bool Is64 = IntPtr.Size == sizeof(long);
private static IntPtr[] Memory = new IntPtr[102400]; // Array big enough to be stored in Generation 2

// Method needs to be static in order to maintain calling convention
public static unsafe IntPtr RawAllocate(IntPtr methodTable)
{
	int objectSize = Marshal.ReadInt32(methodTable, 4) / sizeof(int); // Calculate the object size by extracting it from method table and dividing by int size. We assume that the size starts 4 bytes after the beginning of method table (works on .NET 4 and .NET 3.5)
	_currentOffset++; // Skip sizeof(int) bytes for syncblock
	Memory[_currentOffset] = methodTable; // Write address to method table

	TypedReference newObjectReference = __makeref(Dummy); // Get handle for newly created object
	TypedReference memoryReference = __makeref(Memory); // Get handle for memory
	var spawnedObjectAddress = *(IntPtr*)*(IntPtr*)&memoryReference + (_currentOffset + 2) * sizeof(IntPtr); // Calculate the address of spawned object. We need to add 2 since we need to skip methodtable of array and methodtable of elements type

	*(IntPtr*)*(IntPtr*)&newObjectReference = spawnedObjectAddress; // Modify handle for new object using address of existing memory

	_currentOffset += objectSize; // Move within the memory

	return *(IntPtr*)*(IntPtr*)&newObjectReference;
}

Once again, we remove unnecessary casting to int and use IntPtr all the way.

Hijacking new

Now we need to modify method getting address of .NET allocation method:

private static long GetAllocMethodAddress()
{
	// Get handle to method creating object
	var methodHandle = typeof(GenericMemoryAllocator).GetMethod(nameof(CreateObject), BindingFlags.NonPublic | BindingFlags.Instance).MethodHandle;

	// JIT methods
	RuntimeHelpers.PrepareMethod(methodHandle);

	// Get address of jitted method
	IntPtr methodAddress = Marshal.ReadIntPtr(methodHandle.Value, 8);

	// Call to internal function differs between builds
#if DEBUG
	int offset = Is64 ? 0x2D : 0x22;
#else
	int offset = Is64 ? 0x1A : 0xE;
#endif
	
	// Read jump offset
	int jumpOffset = 0;
	for(int i = 1; i < 5; ++i)
	{
		jumpOffset = jumpOffset + (Marshal.ReadByte(methodAddress, offset + i) << (i-1)*8);
	}

	// Calculate absolute address
	long absoluteAddress = (long)methodAddress + offset + jumpOffset + 1 + 4; // 1 byte for jmp instruction, 4 bytes for relative address

	return absoluteAddress;
}

We replace integers with long integers, add two more offsets (for x64), and change casting to long instead of int. We can use the same code for x86 now as we can always cast to the wider types.

Last part, hijacking the operator:

public static void HijackNew()
{
	var methodHandle = typeof(GenericMemoryAllocator).GetMethod(nameof(RawAllocate)).MethodHandle;
	RuntimeHelpers.PrepareMethod(methodHandle);

	var myAllocAddress = Marshal.ReadIntPtr(methodHandle.Value, 8);
	var defaultAllocAddress = GetAllocMethodAddress();


	int offset = (int)((long)myAllocAddress - defaultAllocAddress - 4 - 1); // 4 bytes for relative address and one byte for opcode
	byte[] instruction = {
		0xE9, // Long jump instruction
		(byte)(offset & 0xFF),
		(byte)((offset >> 8) & 0xFF),
		(byte)((offset >> 16) & 0xFF),
		(byte)((offset >> 24) & 0xFF)
	};

	UnlockPage((IntPtr)defaultAllocAddress);
	Marshal.Copy(instruction, 0, (IntPtr)defaultAllocAddress, instruction.Length);
}

Different cast for offset calculation, nothing else. We rely on the method placement when subtracting addresses, if the methods would be in different order, we would need to do the absolute jump (e.g., via push + ret trick).

You should also notice call to UnlockPage which we do in the following way:

public enum Protection
{
	PAGE_NOACCESS = 0x01,
	PAGE_READONLY = 0x02,
	PAGE_READWRITE = 0x04,
	PAGE_WRITECOPY = 0x08,
	PAGE_EXECUTE = 0x10,
	PAGE_EXECUTE_READ = 0x20,
	PAGE_EXECUTE_READWRITE = 0x40,
	PAGE_EXECUTE_WRITECOPY = 0x80,
	PAGE_GUARD = 0x100,
	PAGE_NOCACHE = 0x200,
	PAGE_WRITECOMBINE = 0x400
}


[DllImport("kernel32.dll", SetLastError = true)]
static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect);

// Unlocks page for executing
private static void UnlockPage(IntPtr address)
{
	uint old;
	VirtualProtect(address, 6, (uint)Protection.PAGE_EXECUTE_READWRITE, out old);
}

Testing

Just for the sake of completeness, here is the debugging session for doing the same manually:

Getting .NET allocation method:

0:003> !name2ee * Program
Module:      00007ffbaf491000
Assembly:    mscorlib.dll
--------------------------------------
Module:      00007ffb51944110
Assembly:    GenericUnsafeAlloc.exe

0:003> !dumpmodule -mt 00007ffb51944110
Name:       C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\bin\x64\Debug\GenericUnsafeAlloc.exe
Attributes: PEFile SupportsUpdateableMethods
Assembly:   000001fbfa6f1a60
LoaderHeap:              0000000000000000
TypeDefToMethodTableMap: 00007ffb51940070
TypeRefToMethodTableMap: 00007ffb519400a0
MethodDefToDescMap:      00007ffb519401b8
FieldDefToDescMap:       00007ffb51940228
MemberRefToDescMap:      0000000000000000
FileReferencesMap:       00007ffb519402e8
AssemblyReferencesMap:   00007ffb519402f0
MetaData start address:  000001fbfa562384 (3872 bytes)

Types defined in this module

              MT          TypeDef Name
------------------------------------------------------------------------------
00007ffb51945bf0 0x02000003 GenericUnsafeAlloc.GenericMemoryAllocator
00007ffb51945a20 0x02000004 GenericUnsafeAlloc.Program
00007ffb51945ce8 0x02000005 GenericUnsafeAlloc.TestClass

Types referenced in this module

              MT            TypeRef Name
------------------------------------------------------------------------------
00007ffbafb270c0 0x02000014 System.Object
00007ffbafb28d30 0x02000016 System.RuntimeTypeHandle
00007ffbafb46980 0x02000018 System.IntPtr
00007ffbafb28778 0x02000019 System.Type
00007ffbafb464b0 0x0200001a System.Runtime.InteropServices.Marshal
00007ffbafb610a8 0x02000020 System.Console
00007ffbafb2a490 0x02000021 System.GC
00007ffbafb29510 0x02000022 System.Int32

0:003> !dumpmt -md 00007ffb51945a20 
EEClass:         00007ffb51942748
Module:          00007ffb51944110
Name:            GenericUnsafeAlloc.Program
mdToken:         0000000002000004
File:            C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\bin\x64\Debug\GenericUnsafeAlloc.exe
BaseSize:        0x18
ComponentSize:   0x0
Slots in VTable: 6
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc Table
           Entry       MethodDesc    JIT Name
00007ffbaf93b1f0 00007ffbaf497538 PreJIT System.Object.ToString()
00007ffbaf93fd90 00007ffbaf497540 PreJIT System.Object.Equals(System.Object)
00007ffbaf961dc0 00007ffbaf497568 PreJIT System.Object.GetHashCode()
00007ffbaf93ce50 00007ffbaf497580 PreJIT System.Object.Finalize()
00007ffb51a50080 00007ffb51945a18   NONE GenericUnsafeAlloc.Program..ctor()
00007ffb51a50480 00007ffb51945a08    JIT GenericUnsafeAlloc.Program.Main()

0:003> !U 00007ffb51a50480 
Normal JIT generated code
GenericUnsafeAlloc.Program.Main()
Begin 00007ffb51a50480, size 265
*** WARNING: Unable to verify checksum for C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\bin\x64\Debug\GenericUnsafeAlloc.exe

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 162:
>>> 00007ffb`51a50480 55              push    rbp
00007ffb`51a50481 57              push    rdi
00007ffb`51a50482 4881ece8000000  sub     rsp,0E8h
00007ffb`51a50489 488dac24f0000000 lea     rbp,[rsp+0F0h]
00007ffb`51a50491 488dbd38ffffff  lea     rdi,[rbp-0C8h]
00007ffb`51a50498 b930000000      mov     ecx,30h
00007ffb`51a5049d 33c0            xor     eax,eax
00007ffb`51a5049f f3ab            rep stos dword ptr [rdi]
00007ffb`51a504a1 833de840efff00  cmp     dword ptr [00007ffb`51944590],0
00007ffb`51a504a8 7405            je      00007ffb`51a504af
00007ffb`51a504aa e8e1e7a95f      call    clr!JIT_DbgIsJustMyCode (00007ffb`b14eec90)
00007ffb`51a504af 90              nop

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 164:
00007ffb`51a504b0 48b9f05b9451fb7f0000 mov rcx,7FFB51945BF0h (MT: GenericUnsafeAlloc.GenericMemoryAllocator)
00007ffb`51a504ba e85120605f      call    clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`b1052510)
00007ffb`51a504bf 488945c0        mov     qword ptr [rbp-40h],rax
00007ffb`51a504c3 488b4dc0        mov     rcx,qword ptr [rbp-40h]
00007ffb`51a504c7 e854fcffff      call    00007ffb`51a50120 (GenericUnsafeAlloc.GenericMemoryAllocator..ctor(), mdToken: 0000000006000009)
00007ffb`51a504cc 488b4dc0        mov     rcx,qword ptr [rbp-40h]
00007ffb`51a504d0 48894de0        mov     qword ptr [rbp-20h],rcx

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 165:
00007ffb`51a504d4 488b4de0        mov     rcx,qword ptr [rbp-20h]
00007ffb`51a504d8 48ba205e9451fb7f0000 mov rdx,7FFB51945E20h (MD: GenericUnsafeAlloc.GenericMemoryAllocator.Allocate[[GenericUnsafeAlloc.TestClass, GenericUnsafeAlloc]]())
00007ffb`51a504e2 3909            cmp     dword ptr [rcx],ecx
00007ffb`51a504e4 e867fcffff      call    00007ffb`51a50150 (GenericUnsafeAlloc.GenericMemoryAllocator.Allocate[[System.__Canon, mscorlib]](), mdToken: 0000000006000004)
00007ffb`51a504e9 488945b8        mov     qword ptr [rbp-48h],rax
00007ffb`51a504ed 488b4db8        mov     rcx,qword ptr [rbp-48h]
00007ffb`51a504f1 48894dd8        mov     qword ptr [rbp-28h],rcx

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 168:
00007ffb`51a504f5 48b9c070b2affb7f0000 mov rcx,offset mscorlib_ni+0x6970c0 (00007ffb`afb270c0) (MT: System.Object)
00007ffb`51a504ff e80c20605f      call    clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`b1052510)
00007ffb`51a50504 488945b0        mov     qword ptr [rbp-50h],rax
00007ffb`51a50508 488b4db0        mov     rcx,qword ptr [rbp-50h]
00007ffb`51a5050c e80fd0ee5d      call    mscorlib_ni+0x4ad520 (00007ffb`af93d520) (System.Object..ctor(), mdToken: 0000000006000221)
00007ffb`51a50511 488b45b0        mov     rax,qword ptr [rbp-50h]
00007ffb`51a50515 488945d0        mov     qword ptr [rbp-30h],rax

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 172:
00007ffb`51a50519 e8326d795e      call    mscorlib_ni+0xd57250 (00007ffb`b01e7250) (System.Console.ReadLine(), mdToken: 0000000006000b56)
00007ffb`51a5051e 488945a8        mov     qword ptr [rbp-58h],rax
00007ffb`51a50522 90              nop

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 173:
00007ffb`51a50523 48b9c070b2affb7f0000 mov rcx,offset mscorlib_ni+0x6970c0 (00007ffb`afb270c0) (MT: System.Object)
00007ffb`51a5052d e8de1f605f      call    clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`b1052510)
00007ffb`51a50532 488945a0        mov     qword ptr [rbp-60h],rax
00007ffb`51a50536 488b4da0        mov     rcx,qword ptr [rbp-60h]
00007ffb`51a5053a e8e1cfee5d      call    mscorlib_ni+0x4ad520 (00007ffb`af93d520) (System.Object..ctor(), mdToken: 0000000006000221)
00007ffb`51a5053f 488b4da0        mov     rcx,qword ptr [rbp-60h]
00007ffb`51a50543 48894dc8        mov     qword ptr [rbp-38h],rcx

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 176:
00007ffb`51a50547 48b9d8350090fb010000 mov rcx,1FB900035D8h
00007ffb`51a50551 488b09          mov     rcx,qword ptr [rcx]
00007ffb`51a50554 48894d98        mov     qword ptr [rbp-68h],rcx
00007ffb`51a50558 488b4de0        mov     rcx,qword ptr [rbp-20h]
00007ffb`51a5055c e8afd9a65f      call    clr!GCInterface::GetGeneration (00007ffb`b14bdf10)
00007ffb`51a50561 8945f4          mov     dword ptr [rbp-0Ch],eax
00007ffb`51a50564 48b91095b2affb7f0000 mov rcx,offset mscorlib_ni+0x699510 (00007ffb`afb29510) (MT: System.Int32)
00007ffb`51a5056e e89d1f605f      call    clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`b1052510)
00007ffb`51a50573 48894590        mov     qword ptr [rbp-70h],rax
00007ffb`51a50577 488b4d98        mov     rcx,qword ptr [rbp-68h]
00007ffb`51a5057b 48898d70ffffff  mov     qword ptr [rbp-90h],rcx
00007ffb`51a50582 488b4d90        mov     rcx,qword ptr [rbp-70h]
00007ffb`51a50586 8b55f4          mov     edx,dword ptr [rbp-0Ch]
00007ffb`51a50589 895108          mov     dword ptr [rcx+8],edx
00007ffb`51a5058c 488b4d90        mov     rcx,qword ptr [rbp-70h]
00007ffb`51a50590 48898d68ffffff  mov     qword ptr [rbp-98h],rcx
00007ffb`51a50597 488b8d70ffffff  mov     rcx,qword ptr [rbp-90h]
00007ffb`51a5059e 488b9568ffffff  mov     rdx,qword ptr [rbp-98h]
00007ffb`51a505a5 e8268ffa5d      call    mscorlib_ni+0x5694d0 (00007ffb`af9f94d0) (System.Console.WriteLine(System.String, System.Object), mdToken: 0000000006000b65)
00007ffb`51a505aa 90              nop

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 177:
00007ffb`51a505ab 48b9e0350090fb010000 mov rcx,1FB900035E0h
00007ffb`51a505b5 488b09          mov     rcx,qword ptr [rcx]
00007ffb`51a505b8 48894d88        mov     qword ptr [rbp-78h],rcx
00007ffb`51a505bc 488b4dd8        mov     rcx,qword ptr [rbp-28h]
00007ffb`51a505c0 e84bd9a65f      call    clr!GCInterface::GetGeneration (00007ffb`b14bdf10)
00007ffb`51a505c5 8945f0          mov     dword ptr [rbp-10h],eax
00007ffb`51a505c8 48b91095b2affb7f0000 mov rcx,offset mscorlib_ni+0x699510 (00007ffb`afb29510) (MT: System.Int32)
00007ffb`51a505d2 e8391f605f      call    clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`b1052510)
00007ffb`51a505d7 48894590        mov     qword ptr [rbp-70h],rax
00007ffb`51a505db 488b4d88        mov     rcx,qword ptr [rbp-78h]
00007ffb`51a505df 48898d60ffffff  mov     qword ptr [rbp-0A0h],rcx
00007ffb`51a505e6 488b4d90        mov     rcx,qword ptr [rbp-70h]
00007ffb`51a505ea 8b55f0          mov     edx,dword ptr [rbp-10h]
00007ffb`51a505ed 895108          mov     dword ptr [rcx+8],edx
00007ffb`51a505f0 488b4d90        mov     rcx,qword ptr [rbp-70h]
00007ffb`51a505f4 48898d58ffffff  mov     qword ptr [rbp-0A8h],rcx
00007ffb`51a505fb 488b8d60ffffff  mov     rcx,qword ptr [rbp-0A0h]
00007ffb`51a50602 488b9558ffffff  mov     rdx,qword ptr [rbp-0A8h]
00007ffb`51a50609 e8c28efa5d      call    mscorlib_ni+0x5694d0 (00007ffb`af9f94d0) (System.Console.WriteLine(System.String, System.Object), mdToken: 0000000006000b65)
00007ffb`51a5060e 90              nop

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 178:
00007ffb`51a5060f 48b9e8350090fb010000 mov rcx,1FB900035E8h
00007ffb`51a50619 488b09          mov     rcx,qword ptr [rcx]
00007ffb`51a5061c 48894d80        mov     qword ptr [rbp-80h],rcx
00007ffb`51a50620 488b4dd0        mov     rcx,qword ptr [rbp-30h]
00007ffb`51a50624 e8e7d8a65f      call    clr!GCInterface::GetGeneration (00007ffb`b14bdf10)
00007ffb`51a50629 8945ec          mov     dword ptr [rbp-14h],eax
00007ffb`51a5062c 48b91095b2affb7f0000 mov rcx,offset mscorlib_ni+0x699510 (00007ffb`afb29510) (MT: System.Int32)
00007ffb`51a50636 e8d51e605f      call    clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`b1052510)
00007ffb`51a5063b 48894590        mov     qword ptr [rbp-70h],rax
00007ffb`51a5063f 488b4d80        mov     rcx,qword ptr [rbp-80h]
00007ffb`51a50643 48898d50ffffff  mov     qword ptr [rbp-0B0h],rcx
00007ffb`51a5064a 488b4d90        mov     rcx,qword ptr [rbp-70h]
00007ffb`51a5064e 8b55ec          mov     edx,dword ptr [rbp-14h]
00007ffb`51a50651 895108          mov     dword ptr [rcx+8],edx
00007ffb`51a50654 488b4d90        mov     rcx,qword ptr [rbp-70h]
00007ffb`51a50658 48898d48ffffff  mov     qword ptr [rbp-0B8h],rcx
00007ffb`51a5065f 488b8d50ffffff  mov     rcx,qword ptr [rbp-0B0h]
00007ffb`51a50666 488b9548ffffff  mov     rdx,qword ptr [rbp-0B8h]
00007ffb`51a5066d e85e8efa5d      call    mscorlib_ni+0x5694d0 (00007ffb`af9f94d0) (System.Console.WriteLine(System.String, System.Object), mdToken: 0000000006000b65)
00007ffb`51a50672 90              nop

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 179:
00007ffb`51a50673 48b9f0350090fb010000 mov rcx,1FB900035F0h
00007ffb`51a5067d 488b09          mov     rcx,qword ptr [rcx]
00007ffb`51a50680 48898d78ffffff  mov     qword ptr [rbp-88h],rcx
00007ffb`51a50687 488b4dc8        mov     rcx,qword ptr [rbp-38h]
00007ffb`51a5068b e880d8a65f      call    clr!GCInterface::GetGeneration (00007ffb`b14bdf10)
00007ffb`51a50690 8945e8          mov     dword ptr [rbp-18h],eax
00007ffb`51a50693 48b91095b2affb7f0000 mov rcx,offset mscorlib_ni+0x699510 (00007ffb`afb29510) (MT: System.Int32)
00007ffb`51a5069d e86e1e605f      call    clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`b1052510)
00007ffb`51a506a2 48894590        mov     qword ptr [rbp-70h],rax
00007ffb`51a506a6 488b8d78ffffff  mov     rcx,qword ptr [rbp-88h]
00007ffb`51a506ad 48898d40ffffff  mov     qword ptr [rbp-0C0h],rcx
00007ffb`51a506b4 488b4d90        mov     rcx,qword ptr [rbp-70h]
00007ffb`51a506b8 8b55e8          mov     edx,dword ptr [rbp-18h]
00007ffb`51a506bb 895108          mov     dword ptr [rcx+8],edx
00007ffb`51a506be 488b4d90        mov     rcx,qword ptr [rbp-70h]
00007ffb`51a506c2 48898d38ffffff  mov     qword ptr [rbp-0C8h],rcx
00007ffb`51a506c9 488b8d40ffffff  mov     rcx,qword ptr [rbp-0C0h]
00007ffb`51a506d0 488b9538ffffff  mov     rdx,qword ptr [rbp-0C8h]
00007ffb`51a506d7 e8f48dfa5d      call    mscorlib_ni+0x5694d0 (00007ffb`af9f94d0) (System.Console.WriteLine(System.String, System.Object), mdToken: 0000000006000b65)
00007ffb`51a506dc 90              nop

C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\Program.cs @ 180:
00007ffb`51a506dd 90              nop
00007ffb`51a506de 488d65f8        lea     rsp,[rbp-8]
00007ffb`51a506e2 5f              pop     rdi
00007ffb`51a506e3 5d              pop     rbp
00007ffb`51a506e4 c3              ret

0:003> !U 00007ffb`b1052510
Unmanaged code
00007ffb`b1052510 8b5104          mov     edx,dword ptr [rcx+4]
00007ffb`b1052513 654c8b1c2598140000 mov   r11,qword ptr gs:[1498h]
00007ffb`b105251c 4d8b5370        mov     r10,qword ptr [r11+70h]
00007ffb`b1052520 498b4368        mov     rax,qword ptr [r11+68h]
00007ffb`b1052524 4803d0          add     rdx,rax
00007ffb`b1052527 493bd2          cmp     rdx,r10
00007ffb`b105252a 7708            ja      clr!JIT_TrialAllocSFastMP_InlineGetThread__PatchTLSOffset+0x21 (00007ffb`b1052534)
00007ffb`b105252c 49895368        mov     qword ptr [r11+68h],rdx
00007ffb`b1052530 488908          mov     qword ptr [rax],rcx
00007ffb`b1052533 c3              ret

As we can see, now we are calling different method clr!JIT_TrialAllocSFastMP_InlineGetThread (00007ffb`b1052510)

Let’s grab our allocator:

0:003> !dumpmt -md 00007ffb51945bf0 
EEClass:         00007ffb51a91020
Module:          00007ffb51944110
Name:            GenericUnsafeAlloc.GenericMemoryAllocator
mdToken:         0000000002000003
File:            C:\Users\adafurma\Desktop\msp_windowsinternals\GenericUnsafeAlloc\bin\x64\Debug\GenericUnsafeAlloc.exe
BaseSize:        0x18
ComponentSize:   0x0
Slots in VTable: 14
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc Table
           Entry       MethodDesc    JIT Name
00007ffbaf93b1f0 00007ffbaf497538 PreJIT System.Object.ToString()
00007ffbaf93fd90 00007ffbaf497540 PreJIT System.Object.Equals(System.Object)
00007ffbaf961dc0 00007ffbaf497568 PreJIT System.Object.GetHashCode()
00007ffbaf93ce50 00007ffbaf497580 PreJIT System.Object.Finalize()
00007ffb51a50700 00007ffb51945bd8    JIT GenericUnsafeAlloc.GenericMemoryAllocator..cctor()
00007ffb51a507d0 00007ffb51945bd0    JIT GenericUnsafeAlloc.GenericMemoryAllocator..ctor()
00007ffb51a500d0 00007ffb51945b70   NONE GenericUnsafeAlloc.GenericMemoryAllocator.Allocate()
00007ffb51a500a0 00007ffb51945b08   NONE GenericUnsafeAlloc.GenericMemoryAllocator.VirtualProtect(IntPtr, UInt32, UInt32, UInt32 ByRef)
00007ffb51a500b0 00007ffb51945b50   NONE GenericUnsafeAlloc.GenericMemoryAllocator.UnlockPage(IntPtr)
00007ffb51a500c0 00007ffb51945b60   NONE GenericUnsafeAlloc.GenericMemoryAllocator.GetReferenceAsPointer(System.Object)
00007ffb51a50970 00007ffb51945b90    JIT GenericUnsafeAlloc.GenericMemoryAllocator.RawAllocate(IntPtr)
00007ffb51a500f0 00007ffb51945ba0   NONE GenericUnsafeAlloc.GenericMemoryAllocator.CreateObject()
00007ffb51a50100 00007ffb51945bb0   NONE GenericUnsafeAlloc.GenericMemoryAllocator.GetAllocMethodAddress()
00007ffb51a50110 00007ffb51945bc0   NONE GenericUnsafeAlloc.GenericMemoryAllocator.HijackNew()

And now modify the allocation:

0:003> a 0x00007ffb`b1052510
00007ffb`b1052510 jmp 0x51a50970 
jmp 0x51a50970 
00007ffb`b1052515

Let’s verify:

0:003> !U 00007ffb`b1052510
Unmanaged code
00007ffb`b1052510 e95be49fa0      jmp     00007ffb`51a50970
00007ffb`b1052515 8b1c2598140000  mov     ebx,dword ptr [1498h]
00007ffb`b105251c 4d8b5370        mov     r10,qword ptr [r11+70h]
00007ffb`b1052520 498b4368        mov     rax,qword ptr [r11+68h]
00007ffb`b1052524 4803d0          add     rdx,rax
00007ffb`b1052527 493bd2          cmp     rdx,r10
00007ffb`b105252a 7708            ja      clr!JIT_TrialAllocSFastMP_InlineGetThread__PatchTLSOffset+0x21 (00007ffb`b1052534)
00007ffb`b105252c 49895368        mov     qword ptr [r11+68h],rdx
00007ffb`b1052530 488908          mov     qword ptr [rax],rcx
00007ffb`b1052533 c3              ret

Jump is in place, we are good.

This code works for x86 and x64, both Debug and Release modes. Tested with Windows 10 x64 1703 and .NET Framework 4.5.2.

]]>
https://blog.adamfurmanek.pl/2018/09/15/custom-memory-allocation-in-c-part-9/feed/ 1
Custom memory allocation in C# Part 8 — Unsafe list in .NET Core x64 https://blog.adamfurmanek.pl/2018/06/23/custom-memory-allocation-in-c-part-8/ https://blog.adamfurmanek.pl/2018/06/23/custom-memory-allocation-in-c-part-8/#comments Sat, 23 Jun 2018 08:00:29 +0000 https://blog.adamfurmanek.pl/?p=2451 Continue reading Custom memory allocation in C# Part 8 — Unsafe list in .NET Core x64]]>

This is the eighth part of the Custom memory allocation series. For your convenience you can find other parts in the table of contents in Part 1 — Allocating object on a stack

Today we are going to revisit an Unsafe list example using .NET Core. Since I use x64 edition, I need to adjust a code using pointers:

public void Add(T item)
{
	_currentIndex++;

	unsafe
	{
		TypedReference reference = __makeref(item);
		long* itemAddress = (long*) (*(long*) *(long*) &reference - 8);

		for (long i = 1; i < _elementSize; ++i)
		{
			_storage[_currentIndex*_elementSize + i] = *itemAddress;
			itemAddress = itemAddress + 1;
		}

		reference = __makeref(_storage);
		_storage[_currentIndex*_elementSize] = *(long*) *(long*) &reference + _currentIndex*_elementSize*8 + 32;
		_elements[_currentIndex] = GetInternal(_currentIndex);
	}
}

Changes are rather simple — one just needs to replace integers with longs and multiply all the offsets by two.

class Poco
{
	public long Field1;
	public long Field2;
	public long Field3;
	public long Field4;
	public long Field5;
	public long Field6;
	public long Field7;
	public long Field8;
	public long Field9;
	public long Field10;
	public long Field11;
	public long Field12;
	public long Field13;
	public long Field14;
	public long Field15;
	public long Field16;
}

Here I just use long fields to make the code simpler. Of course one can detect the size of the object and work with integers as well.

Here are the results:

Results

Unsafe list is still faster.

]]>
https://blog.adamfurmanek.pl/2018/06/23/custom-memory-allocation-in-c-part-8/feed/ 1
Reordered code after compilation https://blog.adamfurmanek.pl/2018/06/16/reordered-code-after-compilation/ https://blog.adamfurmanek.pl/2018/06/16/reordered-code-after-compilation/#respond Sat, 16 Jun 2018 08:00:59 +0000 https://blog.adamfurmanek.pl/?p=2449 Continue reading Reordered code after compilation]]> Can one line of code in C# JIT compile into two different blocks of code in the same program? This sounds a little tricky but in fact is very easy and happens pretty often. Consider the following code:

using System;

namespace Reordering
{
    class Program
    {
        static void Main(string[] args)
        {
            for(int i = 0; i < 10; ++i)
            {
                Console.WriteLine("Iteration");
            }

            Console.ReadLine();
        }
    }
}

Now let’s see the loop in the machine code:

0:003> .loadby sos coreclr

0:003> !name2ee * Program
Module:      00007fff93581000
Assembly:    System.Private.CoreLib.dll
--------------------------------------
Module:      00007fff3e914d48
Assembly:    Reordering.dll
--------------------------------------
Module:      00007fff3e915588
Assembly:    System.Runtime.dll
--------------------------------------
Module:      00007fff3e915ea8
Assembly:    System.Console.dll
--------------------------------------
Module:      00007fff3e9170e8
Assembly:    System.Threading.dll
--------------------------------------
Module:      00007fff3e917a68
Assembly:    System.Runtime.Extensions.dll

0:003> !dumpmodule -mt 00007fff3e914d48
Name:       C:\Users\user\Reordering\bin\Debug\netcoreapp2.0\Reordering.dll
Attributes: PEFile SupportsUpdateableMethods
Assembly:   000002ac372c1050
LoaderHeap:              0000000000000000
TypeDefToMethodTableMap: 00007fff3e9104b0
TypeRefToMethodTableMap: 00007fff3e9104c8
MethodDefToDescMap:      00007fff3e910538
FieldDefToDescMap:       00007fff3e910550
MemberRefToDescMap:      0000000000000000
FileReferencesMap:       00007fff3e910560
AssemblyReferencesMap:   00007fff3e910568
MetaData start address:  000002ac1d5b208c (1284 bytes)

Types defined in this module

              MT          TypeDef Name
------------------------------------------------------------------------------
00007fff3e915d28 0x02000002 Reordering.Program

Types referenced in this module

              MT            TypeRef Name
------------------------------------------------------------------------------
00007fff93edb2e0 0x0200000c System.Object
00007fff3e916f50 0x0200000d System.Console

0:003> !dumpmt -md 00007fff3e915d28 
EEClass:         00007fff3ea61098
Module:          00007fff3e914d48
Name:            Reordering.Program
mdToken:         0000000002000002
File:            C:\Users\user\Reordering\bin\Debug\netcoreapp2.0\Reordering.dll
BaseSize:        0x18
ComponentSize:   0x0
Slots in VTable: 6
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc Table
           Entry       MethodDesc    JIT Name
00007fff93b100b0 00007fff9370b780 PreJIT System.Object.ToString()
00007fff93b100d0 00007fff9370b788 PreJIT System.Object.Equals(System.Object)
00007fff93b10120 00007fff9370b7b0 PreJIT System.Object.GetHashCode()
00007fff93b10130 00007fff9370b7d0 PreJIT System.Object.Finalize()
00007fff3ea70080 00007fff3e915d20   NONE Reordering.Program..ctor()
00007fff3ea70480 00007fff3e915d10    JIT Reordering.Program.Main(System.String[])

0:003> !U 00007fff3e915d10    
Normal JIT generated code
Reordering.Program.Main(System.String[])
Begin 00007fff3ea70480, size 80

C:\Users\user\Reordering\Program.cs @ 8:
00007fff`3ea70480 55              push    rbp
00007fff`3ea70481 57              push    rdi
00007fff`3ea70482 56              push    rsi
00007fff`3ea70483 4883ec30        sub     rsp,30h
00007fff`3ea70487 488d6c2440      lea     rbp,[rsp+40h]
00007fff`3ea7048c 488bf1          mov     rsi,rcx
00007fff`3ea7048f 488d7de0        lea     rdi,[rbp-20h]
00007fff`3ea70493 b904000000      mov     ecx,4
00007fff`3ea70498 33c0            xor     eax,eax
00007fff`3ea7049a f3ab            rep stos dword ptr [rdi]
00007fff`3ea7049c 488bce          mov     rcx,rsi
00007fff`3ea7049f 48894d10        mov     qword ptr [rbp+10h],rcx
00007fff`3ea704a3 833d1e4deaff00  cmp     dword ptr [00007fff`3e9151c8],0
00007fff`3ea704aa 7405            je      00007fff`3ea704b1
00007fff`3ea704ac e80f34c35f      call    coreclr!JIT_DbgIsJustMyCode (00007fff`9e6a38c0)
00007fff`3ea704b1 90              nop

C:\Users\user\Reordering\Program.cs @ 9:
00007fff`3ea704b2 33c9            xor     ecx,ecx
00007fff`3ea704b4 894dec          mov     dword ptr [rbp-14h],ecx
00007fff`3ea704b7 90              nop
00007fff`3ea704b8 eb1d            jmp     00007fff`3ea704d7

C:\Users\user\Reordering\Program.cs @ 10:
00007fff`3ea704ba 90              nop

C:\Users\user\Reordering\Program.cs @ 11:
00007fff`3ea704bb 48b96830f42eac020000 mov rcx,2AC2EF43068h
00007fff`3ea704c5 488b09          mov     rcx,qword ptr [rcx]
00007fff`3ea704c8 e863feffff      call    00007fff`3ea70330
00007fff`3ea704cd 90              nop

C:\Users\user\Reordering\Program.cs @ 12:
00007fff`3ea704ce 90              nop

C:\Users\user\Reordering\Program.cs @ 9:
00007fff`3ea704cf 8b45ec          mov     eax,dword ptr [rbp-14h]
00007fff`3ea704d2 ffc0            inc     eax
00007fff`3ea704d4 8945ec          mov     dword ptr [rbp-14h],eax
00007fff`3ea704d7 8b4dec          mov     ecx,dword ptr [rbp-14h]
00007fff`3ea704da 83f90a          cmp     ecx,0Ah
00007fff`3ea704dd 0f9cc1          setl    cl
00007fff`3ea704e0 0fb6c9          movzx   ecx,cl
00007fff`3ea704e3 894de8          mov     dword ptr [rbp-18h],ecx
00007fff`3ea704e6 8b4de8          mov     ecx,dword ptr [rbp-18h]
00007fff`3ea704e9 85c9            test    ecx,ecx
00007fff`3ea704eb 75cd            jne     00007fff`3ea704ba

C:\Users\user\Reordering\Program.cs @ 14:
00007fff`3ea704ed e8cefdffff      call    00007fff`3ea702c0 (System.Console.ReadLine(), mdToken: 0000000006000075)
00007fff`3ea704f2 488945e0        mov     qword ptr [rbp-20h],rax
00007fff`3ea704f6 90              nop

C:\Users\user\Reordering\Program.cs @ 15:
00007fff`3ea704f7 90              nop
00007fff`3ea704f8 488d65f0        lea     rsp,[rbp-10h]
00007fff`3ea704fc 5e              pop     rsi
00007fff`3ea704fd 5f              pop     rdi
00007fff`3ea704fe 5d              pop     rbp
00007fff`3ea704ff c3              ret

You can see that C:\Users\user\Reordering\Program.cs @ 9: appears twice. This is just a loop header which in fact has two separate parts.

]]>
https://blog.adamfurmanek.pl/2018/06/16/reordered-code-after-compilation/feed/ 0
Custom memory allocation in C# Part 7 — Stack allocation once again https://blog.adamfurmanek.pl/2018/06/09/custom-memory-allocation-in-c-part-7/ https://blog.adamfurmanek.pl/2018/06/09/custom-memory-allocation-in-c-part-7/#comments Sat, 09 Jun 2018 08:00:47 +0000 https://blog.adamfurmanek.pl/?p=2446 Continue reading Custom memory allocation in C# Part 7 — Stack allocation once again]]>

This is the seventh part of the Custom memory allocation series. For your convenience you can find other parts in the table of contents in Part 1 — Allocating object on a stack

We know how to allocate object on a stack, however, we did this in .NET Framework. Can we do the same in .NET Core? Let’s see. I am using .NET Core 2.1.103 x64 on W10 Enterprise x64.

Code

Since I am using an 64-bit SDK, I need to change the code. Below is the modified version:

// This application stores managed object on stack
// Windbg internals:
/*
Load SOS:
.loadby sos coreclr

Select proper thread:
~0s

Present all values:
!clrstack -i -a

Tested with .NET Core 2.1.103 x64 on W10 Enterprise x64
*/

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Makeref_core
{
    class Poco
    {
        public int Field;

        public void WriteField()
        {
            Console.WriteLine(Field);
        }

        public override string ToString()
        {
            return Field.ToString();
        }

        ~Poco()
        {
            Console.WriteLine("Cleaning: " + Field);
        }
    }

    class Program
    {
        static void Main()
        {
            #region Copy

            Poco heapPoco = new Poco
            {
                Field = 5
            };
            Poco originalPoco = heapPoco;

            unsafe
            {
                var space = stackalloc ulong[10];
                for (int i = 0; i < 10; ++i)
                {
                    space[i] = 0xBADF00D;
                }

                TypedReference typedReference = __makeref(heapPoco);
                ulong* pocoAddress = (ulong*)(*(ulong*)*(ulong*)&typedReference - 8);

                Console.WriteLine($"Got address of an EE method: {((ulong)(pocoAddress + 1)).ToString("X")} Value: {(*(pocoAddress + 1)).ToString("X")}");
                Console.ReadLine();


                for (int i = 0; i < 3; ++i)
                {
                    Console.WriteLine($"Address {((ulong)pocoAddress).ToString("X")} Value {(*pocoAddress).ToString("X")}");
                    space[i] = *pocoAddress;
                    pocoAddress = pocoAddress + 1;
                }

                Console.WriteLine("Rewritten values");
                Console.ReadLine();

                *(ulong*)*(ulong*)&typedReference = (ulong)&space[1];

                Console.WriteLine($"Set reference to the stack: {((ulong)&space[1]).ToString("X")}");
                Console.ReadLine();

                Poco stackPoco = __refvalue(typedReference, Poco);
                //Poco stackPoco = heapPoco; - This is the same
                #endregion

                #region Write
                Console.WriteLine("Got poco on stack. Displaying values:");
                Console.Write("Heap: ");
                originalPoco.WriteField();
                Console.Write("Stack: ");
                stackPoco.WriteField();
                Console.WriteLine();

                Console.WriteLine("Modifying values using references. Displaying values:");
                originalPoco.Field = 555;
                stackPoco.Field = 444;
                Console.Write("Heap: ");
                originalPoco.WriteField();
                Console.Write("Stack: ");
                stackPoco.WriteField();
                Console.WriteLine();

                Console.WriteLine("Modifying values using array on a stack. Displaying values:");
                space[2] = 333;
                Console.Write("Heap: ");
                originalPoco.WriteField();
                Console.Write("Stack: ");
                stackPoco.WriteField();
                Console.ReadLine();

                Console.WriteLine("Calling virtual method:");
                Console.WriteLine("Heap: " + originalPoco.ToString());
                Console.WriteLine("Stack: " + stackPoco.ToString());
                Console.ReadLine();

                Console.WriteLine("Displaying object types:");
                Console.WriteLine(originalPoco.GetType());
                Console.WriteLine(stackPoco.GetType());
                Console.ReadLine();

                Console.WriteLine("Displaying GC generations:");
                Console.WriteLine(GC.GetGeneration(originalPoco));
                Console.WriteLine(GC.GetGeneration(stackPoco));
                Console.ReadLine();

                Console.WriteLine("Threading:");
                Console.WriteLine($"Taking lock in base: {DateTime.Now}");

                lock (stackPoco)
                {
                    Console.WriteLine($"Lock in base aquired: {DateTime.Now}");

                    Task.Run(() =>
                    {
                        Console.WriteLine($"Taking lock in child: {DateTime.Now}");
                        lock (stackPoco)
                        {
                            Console.WriteLine($"Lock in child aquired: {DateTime.Now}");
                        }
                        Console.WriteLine($"Lock in child released: {DateTime.Now}");
                    });

                    Thread.Sleep(2000);
                }

                Console.WriteLine($"Lock in base released: {DateTime.Now}");
                Console.ReadLine();

                //GC.ReRegisterForFinalize(stackPoco);// - bad idea

                Console.WriteLine("End");
            }
            #endregion
        }
    }
}

It is basically the same code as in Part 1, however, instead of casting pointers to int I use ulong type. ALso, the syncblock is now 8 bytes before the metadata.

WinDBG

Let’s see the WinDBG session. We start the application, wait for first ReadLine and attach the debugger:

0:003> .loadby sos coreclr
0:003> ~0s
ntdll!NtReadFile+0x14:
00007fff`e6545464 c3              ret
0:000> !clrstack -a -i
Loaded C:\tmp\mscordbi.dll\5A81EF0016d000\mscordbi.dll
Loaded C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.0.6\mscordaccore.dll



Dumping managed stack and managed variables using ICorDebug.
=============================================================================
Child SP         IP               Call Site
000000d38d57d858 00007fffe6545464 [NativeStackFrame]
000000d38d57d8e0 00007fff3f07214f 000000d38d57d910 (null) [Managed to Unmanaged transition: 000000d38d57d910]
000000d38d57d9a0 00007fffb2e28f65 [DEFAULT] I4 System.ConsolePal+WindowsConsoleStream.ReadFileNative(I,SZArray UI1,I4,I4,Boolean,ByRef I4,Boolean) (C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.0.6\System.Console.dll)

PARAMETERS:
  + (Error 0x80131304 retrieving parameter 'hFile')
  + (Error 0x80131304 retrieving parameter 'bytes')
  + (Error 0x80131304 retrieving parameter 'offset')
  + (Error 0x80131304 retrieving parameter 'count')
  + (Error 0x80131304 retrieving parameter 'isPipe')
  + (Error 0x80131304 retrieving parameter 'bytesRead')
  + (Error 0x80131304 retrieving parameter 'useFileAPIs')

LOCALS:
  + (Error 0x80004005 retrieving local variable 'readSuccess')
  + (Error 0x80004005 retrieving local variable 'errorCode')
  + (Error 0x80004005 retrieving local variable 'p')
  + (Error 0x80004005 retrieving local variable 'local_3')
  + (Error 0x80004005 retrieving local variable 'charsRead')

000000d38d57da00 00007fffb2e28db3 [DEFAULT] [hasThis] I4 System.ConsolePal+WindowsConsoleStream.Read(SZArray UI1,I4,I4) (C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.0.6\System.Console.dll)

PARAMETERS:
  + (Error 0x80131304 retrieving parameter 'this')
  + (Error 0x80131304 retrieving parameter 'buffer')
  + (Error 0x80131304 retrieving parameter 'offset')
  + (Error 0x80131304 retrieving parameter 'count')

LOCALS:
  + (Error 0x80004005 retrieving local variable 'bytesRead')
  + (Error 0x80004005 retrieving local variable 'errCode')

000000d38d57da70 00007fffa3ccdc6d [DEFAULT] [hasThis] I4 System.IO.StreamReader.ReadBuffer() (C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.0.6\System.Runtime.Extensions.dll)

PARAMETERS:
  + System.IO.StreamReader this @ 0x22b678659f0

LOCALS:
  + (Error 0x80004005 retrieving local variable 'len')
  + (Error 0x80004005 retrieving local variable 'local_1')

000000d38d57dac0 00007fffa3cce04a [DEFAULT] [hasThis] String System.IO.StreamReader.ReadLine() (C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.0.6\System.Runtime.Extensions.dll)

PARAMETERS:
  + System.IO.StreamReader this @ 0x22b678659f0

LOCALS:
  + (Error 0x80004005 retrieving local variable 'sb')
  + (Error 0x80004005 retrieving local variable 'i')
  + (Error 0x80004005 retrieving local variable 'ch')
  + (Error 0x80004005 retrieving local variable 's')

000000d38d57db10 00007fffb2e2d517 [DEFAULT] [hasThis] String System.IO.SyncTextReader.ReadLine() (C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.0.6\System.Console.dll)

PARAMETERS:
  + System.IO.SyncTextReader this @ 0x22b678663b0

LOCALS:
  + (Error 0x80004005 retrieving local variable 'local_0')
  + (Error 0x80004005 retrieving local variable 'local_1')
  + (Error 0x80004005 retrieving local variable 'local_2')

000000d38d57db60 00007fffb2e252fa [DEFAULT] String System.Console.ReadLine() (C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.0.6\System.Console.dll)

PARAMETERS: (none)

LOCALS: (none)

000000d38d57db90 00007fff3f0709b8 [DEFAULT] Void Makeref_core.Program.Main() (C:\Users\user\Desktop\msp_windowsinternals\Makeref_core\bin\Debug\netcoreapp2.0\Makeref_core.dl)

PARAMETERS: (none)

LOCALS:
  + Makeref_core.Poco heapPoco @ 0x22b67863840
  + Makeref_core.Poco originalPoco @ 0x22b67863840
  + Makeref_core.Program+c__DisplayClass0_0 CS$8__locals0 @ 0x22b67863858
  + unsigned long* space  = 195948557
  + typedbyref typedReference @ 0x22b67863840
  + unsigned long* pocoAddress  = 0
  + int i  = 10
  + (Error 0x80004005 retrieving local variable 'local_7')
  + (Error 0x80004005 retrieving local variable 'local_8')
  + int i  = 0
  + (Error 0x80004005 retrieving local variable 'local_10')
  + (Error 0x80004005 retrieving local variable 'local_11')
  + (Error 0x80004005 retrieving local variable 'local_12')

000000d38d57de00 00007fff9eb735f3 [NativeStackFrame]
Stack walk complete.
=============================================================================

Nothing special here, let’s examine the stack:

0:000> dd 000000d38d57db90 
000000d3`8d57db90  67865760 0000022b 00000000 00000000
000000d3`8d57dba0  6786575c 0000022b 8d57d970 000000d3
000000d3`8d57dbb0  0badf00d 00000000 0badf00d 00000000
000000d3`8d57dbc0  0badf00d 00000000 0badf00d 00000000
000000d3`8d57dbd0  0badf00d 00000000 0badf00d 00000000
000000d3`8d57dbe0  0badf00d 00000000 0badf00d 00000000
000000d3`8d57dbf0  0badf00d 00000000 0badf00d 00000000
000000d3`8d57dc00  8d57dbe0 000000d3 9ea4eeeb 00007fff

We can see the 0badf00d markers. Let’s continue the application, and after one step check the stack again:

0:003> dd 000000d38d57db90 
000000d3`8d57db90  67865760 0000022b 00000000 00000000
000000d3`8d57dba0  6786575c 0000022b 8d57d970 000000d3
000000d3`8d57dbb0  00000000 00000000 3ef15e20 00007fff
000000d3`8d57dbc0  00000005 00000000 0badf00d 00000000
000000d3`8d57dbd0  0badf00d 00000000 0badf00d 00000000
000000d3`8d57dbe0  0badf00d 00000000 0badf00d 00000000
000000d3`8d57dbf0  0badf00d 00000000 0badf00d 00000000
000000d3`8d57dc00  8d57dbe0 000000d3 9ea4eeeb 00007fff

Yes, this looks correct. Let’s verify with the object metadata directly:

0:003> !do 0x22b67863840
Name:        Makeref_core.Poco
MethodTable: 00007fff3ef15e20
EEClass:     00007fff3f062110
Size:        24(0x18) bytes
File:        C:\Users\user\Desktop\msp_windowsinternals\Makeref_core\bin\Debug\netcoreapp2.0\Makeref_core.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007fff978b6648  4000001        8         System.Int32  1 instance                5 Field

The MethodTable part is correct so the whole object is copied to the stack correctly. We can verify this differently:

0:003> !do 000000d3`8d57dbb8
Name:        Makeref_core.Poco
MethodTable: 00007fff3ef15e20
EEClass:     00007fff3f062110
Size:        24(0x18) bytes
File:        C:\Users\user\Desktop\msp_windowsinternals\Makeref_core\bin\Debug\netcoreapp2.0\Makeref_core.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007fff978b6648  4000001        8         System.Int32  1 instance                5 Field

So we dump the object directly from the stack and it works.

Summary

There was no TypedReference in .NET Core for a long time. Right now it is available so we can toy with memory and check what has been changed.

]]>
https://blog.adamfurmanek.pl/2018/06/09/custom-memory-allocation-in-c-part-7/feed/ 1