English 中文(简体)
How to crash the .NET common language runtime (CLR) in pure .net
原标题:

There is a similar question targeting the Java VM but I haven t found a question for .net (please close and mark as duplicate if I was missing something).

So - is it possible without nasty unmanaged interop? And with crashing I mean a real "xxx.exe has stopped working" not a StackOverflow- or OutOfMemoryException.

I think it is not possible, except when hitting a bug in the VM itself.

最佳回答

Well...how would you define "pure .NET"? I played with CLR2/delegate/GCHandle/array when I read the "how to crash JVM" post, and came up with something like this:

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

namespace TestCLR2Crash {
        static void Main( string[ ] args ) {
            // declare a delegate that refers to a static method,
            // in this case it s a static method generated from the
            // anonymous delegate.
            Action action = delegate( ) { };

            // "generate" code into an array of uint
            var fakeDelegate = new uint[ ] {
                // dummy values
                0x00000000, 0x00000000,
                // fake _methodPtrAux
                0x00000000,
                // native code/string
                0x6AEC8B55, 0x2FD9B8F5, 0xD0FF7C81, 0x006A006A,
                0x00E81F6A, 0x83000000, 0x50102404, 0x81CC5DBA,
                0x8BD2FF7C, 0x47C35DE5, 0x74656572, 0x73676E69,
                0x6F726620, 0x6567206D, 0x6172656E, 0x20646574,
                0x65646F63, 0x00000A21
            };

            // fill in the fake _methodPtrAux,
            // make it point to the code region in fakeDelegate
            var handle = GCHandle.Alloc( fakeDelegate, GCHandleType.Pinned );
            var addr = handle.AddrOfPinnedObject( );
            const int sizeOfUInt32 = sizeof( uint ); // 4
            const int indexOfCode = 3;
            fakeDelegate[ 2 ] = Convert.ToUInt32( addr.ToInt32( ) + sizeOfUInt32 * indexOfCode );

            var targetInfo = typeof( Action )
                .GetField( "_target", BindingFlags.NonPublic | BindingFlags.Instance );
            targetInfo.SetValue( action, fakeDelegate );
            action( );       // Greetings from generated code!
            Console.WriteLine( "Greetings from managed code!" );

            handle.Free( );
        }
    }
}

It s only known to work on 32-bit Windows XP with CLR2 on x86; and also known not to work with Vista and Windows 7 and the like, where DEP+ASLR is on by default.

The point of fun about the code above is that it didn t explicitly use unsafe code (although GCHandle.Alloc(..., GCHandleType.Pinned) demands security privilege), yet it manages to fake an array into a delegate instance, and calls into the x86 machine code within the array. The code itself is pure C#, if you don t count the embedded x86 code as some "foreign language" ;-) Basically it makes use of internal implementation of CLR2 s delegates on static methods, that a few private members of Delegate are actually internal pointers. I stuffed x86 code into an array, which is allocated on the managed heap. So in order for this to work, DEP must not be enabled, or we ll have to find some other way to get execution privilege on that memory page.

The x86 code is like this: (in pseudo-MASM syntax)

55              push ebp
8BEC            mov  ebp,esp
6A F5           push -0B                         ; /DevType = STD_OUTPUT_HANDLE
B8 D92F817C     mov  eax,KERNEL32.GetStdHandle   ; |
FFD0            call eax                         ; GetStdHandle
6A 00           push 0                           ; /pReserved = NULL
6A 00           push 0                           ; |pWritten = NULL
6A 1F           push 1F                          ; |CharsToWrite = 1F (31.)
E8 00000000     call <&next_instruction>         ; |
830424 10       add  dword ptr ss:[esp],10       ; |Buffer
50              push eax                         ; |hConsole
BA 5DCC817C     mov  edx,KERNEL32.WriteConsoleA  ; |
FFD2            call edx                         ; WriteConsoleA
8BE5            mov  esp,ebp
5D              pop  ebp
C3              ret

This is NOT a behavior specified by CLI, and won t work on other CLI implementations such as Mono. There are other ways to make the similar logic run on Mono though, already tried that on Ubuntu 9.04 w/ Mono 2.4 and worked.

I wrote a blog post about it here: http://rednaxelafx.javaeye.com/blog/461787

It s in Chinese, but there s plenty of code there which should explain what I did. Using the same trick, at the end of the blog post I showed a couple of examples how you could tweak the code above to make things go wrong, such as getting a SEHException.

问题回答

Without the need to use unsafe code or delegates (also if i must admit it is a very nice way to crash your CLR), you can use simple Marshal functions to force .NET to crash.

using System;
using System.Runtime.InteropServices;

namespace Crash
{
    class Program
    {
        static void Main(string[] args)
        {
            IntPtr p = Marshal.AllocHGlobal(1);
            for (int i = 0; i < 10000000; ++i)
            {
                p = new IntPtr(p.ToInt64() + 1);
                Marshal.WriteByte(p, 0xFF);
            }
        }
    }
}

Also this, using always GCHandle, will cause a memory access violation like error.

using System;
using System.Runtime.InteropServices;

namespace Crash
{
    class Program
    {
        static void Main(string[] args)
        {
            GCHandle.FromIntPtr(new IntPtr(32323));
        }
    }
}

I ve done that just today. I was testing a setup of a larger .net project. There was missing an assembly containing some interfaces and the exe just stops working. No exception was caught by the runtime.

You can be sure that there a even more bugs in the runtime - just count the millions of lines of code ...

Oren Eini discovered a bug in the .net framework that caused an ExecutionEngineException - basically a crash of the runtime.

You can read about it here (Microsoft Connect):

https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=384781

And despite the closed state of the bug - it s not fixed yet.

I ve seen Java programs crash the JVM by loading a non-existent class and ignoring ClassNotFoundError, then continue executing as if nothing happened. Perhaps you could do something similar when dynamically loading a .NET class.

You can compile this code with /clr:pure and have the linker force pure option as well.

However, it crashes with what looks like a runtime failure;

(1388.5e4): Access violation - code c0000005 (!!! second chance !!!) eax=8d00fea5 ebx=00000000 ecx=00253e50 edx=00253e50 esi=022ad3cc edi=00253e50 eip=6ae965c5 esp=0020edbc ebp=0020edc8 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b
efl=00010246 * WARNING: Unable to verify checksum for C:WindowsassemblyNativeImages_v4.0.30319_32mscorlibeb4e1e70734f6efb9c7de7ec5f452c9emscorlib.ni.dll mscorlib_ni+0x9365c5: 6ae965c5 ff10
call dword ptr [eax]
ds:002b:8d00fea5=????????

EVEN though you compile with /clr:pure or /clr:safe, and the image represents it self as such, it will only work in full trust due to the verifier catching this bug (compiler bug).

namespace Settings
{
    public ref class RefType 
    {    
    public: 
        unsigned int    I; 
        String^ S;    
        unsigned long   L;
    };
    public ref class aUseTemplate
    {
    public:
        void CallTemplate()
        {
            array<RefType^>^ refarr = gcnew array<RefType^>(20);
            for(int i=0; i < 20; i++)
            {
                RefType^ rt = gcnew RefType();
                rt->I = 0x42424242;
                rt->L = 0x33333333;
                refarr[i] = rt;
            }

            HasTemplate(refarr);
        }
        template<typename T> void HasTemplate(T input)
        {
            for each(T% x in input)
                Console::WriteLine(x);
        }
    };
}

This is from the output of peverify, which parses the entire PE file, this error is not detected until the runtime calls into this method, as the verifier works with the JIT er and is lazy about it s verification.

[IL]: Error: [C:UsersfilesDocumentsVisual Studio 2010Projects estCliinReleasePureSettings.dll : Settings.aUseTemplate::HasTemplate^>][off set 0x00000017][found ref Settings.RefType ][expected address of ref ] Unexpected type on the stack. 1 Error(s) Verifying PureSettings.dll

If you could bypass the verifier here, this would be a HUGE BUG in the CLR and give you code execution from an unprivilaged process.

Here s the MSIL;

  IL_000d:  bge.s      IL_0021
  IL_000f:  ldloc.1
  IL_0010:  ldloc.0
  IL_0011:  ldelem.ref
  IL_0012:  castclass  Settings.RefType
  IL_0017:  stloc.2
  IL_0018:  ldloc.2
  IL_0019:  ldind.ref  
  IL_001a:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_001f:  br.s       IL_0006
  IL_0021:  ret

The bug is at offset, IL_19.

If you are running under CAS or any other type-safe mode, this code will generate the famous "code may destabalize the runtime" exception.

I think I have found another way which doesn t involve unmanaged code. However, it uses PInvoke. It worked when I added MethodImpl(MethodImplOptions.Unmanaged). I tested it on .Net Core 2.1 and on .Net Framework 4.7.2. The former crashed during the VS debug immediately, for the latter the debugger broke on exception with the message that the System.TypeLoadException is thrown somewhere in internals. The message and error code are the same as the ones I ve got from the @Salvatore Previti answer. I called the compiled app from the command line and get error exit code -532462766. The code is below:

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

namespace ConsoleApp1
{
    class Program
    {
        [MethodImpl(MethodImplOptions.Unmanaged)]
        [DllImport("user32.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
        public static extern void Foo();

        public static void Main(string[] args)
        {
            try
            {
                Foo();
            }
            catch (Exception e)
            {
            }

            Console.ReadLine();
        }
    }
}

I know you can crash your entire PC with .NET. But that involves an endless loop and a RealTime process priority...

There is some c# code that while technically correct won t run as a valid .net program. It has something todo with interface overloading an empty method but I can t really remember.





相关问题
Manually implementing high performance algorithms in .NET

As a learning experience I recently tried implementing Quicksort with 3 way partitioning in C#. Apart from needing to add an extra range check on the left/right variables before the recursive call, ...

Anyone feel like passing it forward?

I m the only developer in my company, and am getting along well as an autodidact, but I know I m missing out on the education one gets from working with and having code reviewed by more senior devs. ...

How do I compare two decimals to 10 decimal places?

I m using decimal type (.net), and I want to see if two numbers are equal. But I only want to be accurate to 10 decimal places. For example take these three numbers. I want them all to be equal. 0....

Exception practices when creating a SynchronizationContext?

I m creating an STA version of the SynchronizationContext for use in Windows Workflow 4.0. I m wondering what to do about exceptions when Post-ing callbacks. The SynchronizationContext can be used ...

Show running instance in single instance application

I am building an application with C#. I managed to turn this into a single instance application by checking if the same process is already running. Process[] pname = Process.GetProcessesByName("...

How to combine DataTrigger and EventTrigger?

NOTE I have asked the related question (with an accepted answer): How to combine DataTrigger and Trigger? I think I need to combine an EventTrigger and a DataTrigger to achieve what I m after: when ...

热门标签