嗯...您如何定义“纯 .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。