Ну ... как бы вы определили "чистый .NET"? Я поиграл с CLR2 / делегат / GCHandle / массив, когда я прочитал пост "как разбить JVM" и придумал что-то вроде этого:
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 с CLR2 на x86; а также известно, что он не работает с 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 с Mono 2.4 и работали.
Я написал в блоге об этом здесь: http://rednaxelafx.javaeye.com/blog/461787
Это на китайском языке, но там есть много кода, который должен объяснить, что я сделал. Используя тот же трюк, в конце поста в блоге я показал несколько примеров того, как можно настроить приведенный выше код, чтобы заставить его работать неправильно, например, получить исключение SEHException.