【问题标题】:How to crash the .NET common language runtime (CLR) in pure .net如何在纯 .net 中使 .NET 公共语言运行时 (CLR) 崩溃
【发布时间】:2010-12-15 14:51:23
【问题描述】:

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

那么 - 没有讨厌的非托管互操作是否可能?崩溃是指真正的“xxx.exe 已停止工作”,而不是 StackOverflow 或 OutOfMemoryException。

我认为这是不可能的,除非遇到 VM 本身的错误。

【问题讨论】:

  • .NET CLR 的全部意义在于从计算机中抽象出来,将堆栈溢出和其他错误保留在 CLR 中,并且不会使您的物理计算机崩溃(即使使用有错误的操作系统)
  • 如果这样的事情是可能的,根据定义,它不属于“VM本身的错误”类别吗?
  • 是的.. 除非您正在谈论从 CLR 运行时中删除重要文件等
  • 我猜你可以对原始指针和通过使用GCHandle 固定对象获得的信息进行同样令人讨厌的操作。
  • GCHandle 的东西听起来很有趣。但是,它是否通过了“没有非托管互操作的纯 .net”的定义?

标签: .net security crash clr


【解决方案1】:

我知道您可以使用 .NET 使您的整个 PC 崩溃。但这涉及无限循环和实时进程优先级...

【讨论】:

  • 进程优先级 - 好主意。但是我们可以从进程内部改变进程优先级吗?或者我们可以触发另一个比我们的优先级更高的进程吗?好吧....让我们看看...
  • 刚刚尝试过:一个工作进程是无限循环和实例化对象,而它是由另一个进程使用 ProcessPriorityClass.RealTime 启动的。机器的性能肯定会下降,但不会崩溃。
  • 你可以使用 Process.GetCurrentProcess().ProcessPriority 或类似的东西来设置它。但我的结果好坏参半。例如,我自己的机器完全冻结了。我不知道这是否重要,但我总是以管理员身份登录。
  • ...每个 CPU 核心需要一个无限循环。
【解决方案2】:

Oren Eini 在 .net 框架中发现了一个导致“ExecutionEngineException”的错误 - 基本上是运行时崩溃。

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

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

尽管该错误处于“关闭”状态 - 它尚未修复。

【讨论】:

  • 错误报告已关闭,因为他找到了一种解决方法......所以 MS 关闭了报告并且从未修复过该问题,因为没有其他人遇到同样的问题。
  • ...问题本身就在黑暗中,没有看到他提供的解决方案。但是,这也将被归类为“CLR 中的错​​误”
【解决方案3】:

我今天才这样做。我正在测试一个更大的 .net 项目的设置。缺少包含一些接口的程序集,exe 停止工作。运行时没有捕获到异常。

您可以确定运行时中存在更多错误 - 只需计算数百万行代码...

【讨论】:

    【解决方案4】:

    我看到 Java 程序通过加载一个不存在的类并忽略 ClassNotFoundError 使 JVM 崩溃,然后继续执行,就好像什么都没发生一样。也许你可以在动态加载 .NET 类时做类似的事情。

    【讨论】:

    • 由于我们不能在 .net 中加载类,而只能加载程序集,所以情况有点不同。当我们未能加载程序集并吞下异常时,我们唯一可以尝试的就是通过反射找到一个类和/或方法。但最糟糕的是另一个异常,要么与反射本身有关(例如 TypeLoadException),要么在加载程序集后某些变量不为空时出现 NullReferenceException。总结一下:我认为在.net 中没有机会。如果你是对的,那么某些 JVM 可能在这方面有问题。
    • 它是一个 Sun JVM,但在几年前,可能大约是 2.x 版。
    【解决方案5】:

    嗯...您如何定义“纯 .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( );
            }
        }
    }
    

    只知道它可以在 x86 上使用 CLR2 的 32 位 Windows XP 上工作;并且还已知不适用于 Vista 和 Windows 7 等,默认情况下 DEP+ASLR 处于启用状态。

    上面代码的有趣之处在于它没有明确使用不安全代码(尽管 GCHandle.Alloc(..., GCHandleType.Pinned) 需要安全权限),但它设法将数组伪装成委托实例,并调用数组中的 x86 机器代码。代码本身是纯 C#,如果您不将嵌入式 x86 代码算作某种“外语”;-) 基本上,它利用了 CLR2 代理在静态方法上的内部实现,即 Delegate 的一些私有成员实际上是内部指针。我将 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 上运行,已经在 Ubuntu 9.04 w/Mono 2.4 上尝试过并且工作正常。

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

    它是中文的,但是那里有很多代码可以解释我所做的。使用相同的技巧,在博客文章的最后,我展示了几个示例,您可以如何调整上面的代码以使事情出错,例如获得 SEHException。

    【讨论】:

    • 我仍然不确定这是否是“纯 .net”——定义变得模糊。但是,这绝对是一个非常好的主意 +1!
    【解决方案6】:

    有一些 c# 代码虽然在技术上是正确的,但不会作为有效的 .net 程序运行。它与接口重载一个空方法有关,但我不记得了。

    【讨论】:

      【解决方案7】:

      您可以使用 /clr:pure 编译此代码,并使用链接器强制纯选项。

      但是,它会因运行时故障而崩溃;

      (1388.5e4):访问冲突 - 代码 c0000005(!!!第二次机会!!!) 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 * 警告:无法验证校验和 C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\eb4e1e70734f6efb9c7de7ec5f452c9e\mscorlib.ni.dll mscorlib_ni+0x9365c5: 6ae965c5 ff10
      调用 dword ptr [eax]
      ds:002b:8d00fea5=????????

      即使您使用 /clr:pure 或 /clr:safe 进行编译,并且图像代表它本身,它也只能在完全信任的情况下工作,因为验证器捕获了这个错误(编译器错误)。

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

      这是来自解析整个 PE 文件的 peverify 的输出,在运行时调用此方法之前不会检测到此错误,因为验证器与 JIT'er 一起工作并且对其验证很懒惰。

      [IL]:错误: [C:\用户\文件\文档\视觉 工作室 2010\Projects\testCli\bin\Release\PureSettings.dll : Settings.aUseTemplate::HasTemplate^>][off set 0x00000017][found ref 'Settings.RefType'][预期地址 of ref ] 堆栈上的意外类型。 1 验证 PureSettings.dll 的错误

      如果您可以在此处绕过验证程序,这将是 CLR 中的一个巨大 BUG,并让您从非特权进程执行代码。

      这是 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 或任何其他类型安全模式下运行,此代码将生成著名的“代码可能破坏运行时”异常。

      【讨论】:

        【解决方案8】:

        无需使用不安全的代码或委托(如果我必须承认这是使 CLR 崩溃的好方法),您可以使用简单的 Marshal 函数来强制 .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);
                    }
                }
            }
        }
        

        此外,始终使用 GCHandle 会导致内存访问冲突,例如错误。

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

        【讨论】:

        • 我喜欢最后一个,又短又甜。将它用于我的碰撞测试。
        【解决方案9】:

        我想我找到了另一种不涉及非托管代码的方法。但是,它使用 PInvoke。当我添加 MethodImpl(MethodImplOptions.Unmanaged) 时它起作用了。 我在 .Net Core 2.1 和 .Net Framework 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();
                }
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-05-25
          • 1970-01-01
          • 2011-05-20
          • 1970-01-01
          • 1970-01-01
          • 2016-03-22
          • 2013-05-18
          • 1970-01-01
          相关资源
          最近更新 更多