代码之家  ›  专栏  ›  技术社区  ›  Marc Wittke

如何在纯.NET中崩溃.NET公共语言运行库(clr)

  •  13
  • Marc Wittke  · 技术社区  · 15 年前

    有一个类似的问题针对 Java VM 但是我没有找到.NET的问题(如果我遗漏了什么,请关闭并标记为副本)。

    那么-没有讨厌的非托管互操作是否可能?对于崩溃,我的意思是真正的“xxx.exe已经停止工作”,而不是stackoverflow或outofmemoryException。

    我认为这是不可能的,除非在虚拟机中碰到一个bug。

    9 回复  |  直到 6 年前
        1
  •  16
  •   RednaxelaFX    15 年前

    那么……你如何定义“纯.NET”?当我阅读“如何崩溃JVM”帖子时,我使用了clr2/delegate/gchandle/array,并提出了如下问题:

    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( );
            }
        }
    }
    

    它只在32位Windows XP上工作,在x86上使用CLR2;也不在Vista和Windows 7等系统上工作,默认情况下,DEP+ASLR处于打开状态。

    上面代码的有趣之处在于,它没有显式地使用不安全的代码(尽管gchandle.alloc(…,gchandleType.pinned)需要安全特权),但它设法将数组假设为委托实例,并调用数组中的x86计算机代码。如果您不将嵌入的x86代码算作某种“外语”,代码本身就是纯C-) 基本上,它利用了静态方法上CLR2委托的内部实现,委托的一些私有成员实际上是内部指针。我将x86代码填充到一个数组中,该数组在托管堆上分配。因此,为了使这一点起作用,必须不启用DEP,否则我们将不得不找到其他方法来获得该内存页上的执行特权。

    x86代码如下:(伪masm语法)

    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
    

    这不是cli指定的行为,不能在其他cli实现(如mono)上工作。不过,还有其他方法可以让类似的逻辑在Mono上运行,已经在Ubuntu9.04 w/Mono2.4上尝试过了,并且成功了。

    我在这里写了一篇关于它的博客: http://rednaxelafx.javaeye.com/blog/461787

    它是中文的,但是有很多代码可以解释我做了什么。使用同样的技巧,在博客文章的结尾,我展示了几个例子,如何调整上面的代码以使事情出错,例如获得一个sehexception。

        2
  •  3
  •   Jan    15 年前

    我今天就这么做了。我正在测试一个更大的.NET项目的设置。缺少一个包含某些接口的程序集,而exe仅停止工作。运行时未捕获异常。

    你可以确定在运行时会有更多的错误-只需计算数百万行代码…

        3
  •  2
  •   Adam    15 年前

    OrenEini在.NET框架中发现了一个导致“ExecutionEngineeException”的错误——基本上是运行时崩溃。

    您可以在此处阅读(Microsoft Connect):

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

    尽管bug处于“关闭”状态,但它还没有被修复。

        4
  •  2
  •   Salvatore Previti    13 年前

    不需要使用不安全的代码或委托(如果我必须承认这是一种非常好的方法来崩溃您的CLR),您可以使用简单的封送处理函数来强制.NET崩溃。

    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);
                }
            }
        }
    }
    

    此外,使用always gchandle,也会导致类似错误的内存访问冲突。

    using System;
    using System.Runtime.InteropServices;
    
    namespace Crash
    {
        class Program
        {
            static void Main(string[] args)
            {
                GCHandle.FromIntPtr(new IntPtr(32323));
            }
        }
    }
    
        5
  •  1
  •   David R Tribble    15 年前

    我见过Java程序通过加载一个不存在的类而忽略JVM而忽略 ClassNotFoundError ,然后继续执行,就像什么都没有发生一样。也许您可以在动态加载.NET类时执行类似的操作。

        6
  •  0
  •   RandomNickName42    14 年前

    您可以使用/clr:pure编译此代码,也可以使用链接器force pure选项。

    但是,它会崩溃,看起来像是运行时故障;

    (1388.5E4):访问违规-代码 C00 000(5)!!!!第二次机会!!!!) eax=8d00fea5 ebx=00000000 ecx=00253e50 edx=00253e50 esi=022ad3cc edi=00253e50 eip=6ae965c5 esp=0020edbc ebp=0020edc8 IOPL=0 nv上EI PL ZR NA PE NC cs=0023 ss=002b ds=002b es=002b fs=0053 GS=002B
    EFL=00010246 * 警告:无法验证的校验和 C:\windows\assembly\nativeimages_v4.0.30319_32\mscorlib\eb4e10734f6efb9c7de7ec5f452c9e\mscorlib.ni.dll mscorlib_ni+0x9365c5:6ae965c5 ff10
    调用dword ptr[eax]
    DS:002B:8D00FEA5=???????????

    即使您使用/clr:pure或/clr:safe进行编译,并且图像本身就是这样表示的,但是由于验证器捕获了这个bug(编译器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);
            }
        };
    }
    

    这是来自peverify的输出,它解析整个pe文件,直到运行时调用此方法时才会检测到此错误,因为验证器与jit'er一起工作,并且懒于验证。

    [IL]:错误: [C:\用户\文件\文档\可视 导播室 2010年\projects\testcli\bin\release\puresettings.dll : settings.austemplate::hastemplate^>[关闭设置0x0000017][找到引用 'settings.reftype'[预期地址 of ref]堆栈上的意外类型。 1个验证puresettings.dll的错误

    如果您可以绕过这里的验证器,这将是clr中的一个巨大错误,并让您从非私有进程执行代码。

    这是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
    

    错误在偏移位置,IL_19。

    如果您在CAS或任何其他类型安全模式下运行,则此代码将生成著名的“代码可能会使运行时去死化”异常。

        7
  •  0
  •   SENya    6 年前

    我想我找到了另一种不涉及非托管代码的方法。然而,它使用的是pinvoke。当我添加了MethodImpl(MethodImplOptions.Unmanaged)时,它工作正常。 我在.NET核心2.1和.NET框架4.7.2上测试了它。前者在vs调试期间崩溃,而后者则在异常时中断,并显示消息System.TypeLoadException在内部的某个地方被抛出。消息和错误代码与我从@salvatore previti答案中得到的相同。 我从命令行调用编译后的应用程序,得到错误退出代码-532462766。 代码如下:

    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();
            }
        }
    }
    
        8
  •  -1
  •   Vivelin    15 年前

    我知道你可以用.NET让你的整个电脑崩溃。但这涉及到一个无止境的循环和一个实时的进程优先级…

        9
  •  -1
  •   CodingBarfield    14 年前

    有些C代码虽然技术上正确,但不能作为有效的.NET程序运行。它与接口重载空方法有关,但我真的记不清了。

    推荐文章