Изменение кода .net framework библиотек или «утечка» .Net Framework 5.0

от автора

Здесь я продемонстрирую возможность, которая по своей сути — самый настоящий хак. Вопрос, зачем это может понадобиться? На самом деле целей для этого может быть огромное множество. Итак наша задача — изменить код библиотеки mscorlib таким образом, чтобы все программы, которые ей пользуются, получили эти изменения. Не рантайм, конечно, а во время старта (для runtime необходимо проделать другие вещи, и тут надо оговориться что изменения эти не должны поломать текущие состояния библиотеки). Mscorlib я взял как пример, потому что она есть у всех на компьютере. Но можно хакать любую другую.

Все мы знаем, что для того, чтобы не было «ада dll», Microsoft помимо обычных версий и названия библиотек, дали возможность подписывать сборки ключом, public key которой гарантирует что конкретная сборка «пришла» от конкретного разработчика, а не от какого-то другого. Поэтому, если мы хотим по какой-то вполне добросовестной причине изменить код существующей библиотеки т.о., чтобы она загрузилась в чужой процесс и при этом ключик public key остался тем же, у нас этого не получится. Потому что мы не сможем ее подписать, у нас нет закрытого ключа.

Наша мини цель, чтобы программа вывела на консоль текст:

PS: это — исправленная версия старого кода
.

Краткое содержание:

  1. Введение
  2. Разработка драйвера
  3. Выводы по разработке драйвера
  4. Написание сервиса Windows
  5. Изменение кода mscorlib.dll
  6. Результаты исследования

Введение

Сегодня речь пойдет о сборках, находящихся в GAC и в NAC. Я выяснил, что .NET Framework очень чутко относится к структуре GAC. Он доверяет ей настолько, что загружает оттуда сборки, не особо проверяя номер версии. Потому для того чтобы сделать подмену нам надо не так много: нам необходимо написать драйвер уровня ядра для того чтобы перенаправлять обращения к определенным файлам в другое место. Я для этого воспользовался возможностью filesystem filters. Суть проста: для того чтобы не писать много кода в самом драйвере, пишется протокол между Kernel-space и user-space. Со стороны user-space выступает windows — service приложение. Оно общается с драйвером, дает ему команды, что куда перенаправлять. А тот в свою очередь перенаправляет. Для того чтобы менять код существующей библиотеки, например, mscorlib, можно воспользоваться либо Reflexil, либо Mono::Cecil.

Порядок действий:

  • Меняем сборку (Reflexil или Mono::Cecil) по сборке mscorlib и получаем видоизмененную сборку. Например, встраиваем в нее логгирование
  • Кладем результат в tmp каталог
  • Добавляем redirection «C:\Windows\Microsoft.NET\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll» на, например, «C:\tmp\mscorlib64.dll» — видоизмененная сборка

И мы получаем при последующих запусках всех приложений на .net логгирования системной библиотеки. Для порядка, необходимо чтобы драйвер фильтровал также по номерам процессов, кому конкретно давать «левак», а кому — оригинальную библиотеку.

Разработка драйвера

Для начала устанавливаем VirtualBox. Он нам понадобится чтобы отлаживать драйвер. Нам же не хочется перезапускаться каждый раз, когда наш драйвер будет обращаться не по тем адресам и вылетать с ошибкой (естественно, с BSOD’ом). На виртуалки накатываем образы Windows XP и Windows 7, x86 и x64 и снимаем снапшоты, чтобы было куда откатываться.

Далее устанавливаем на девелоперскую машину VisualDDK, WinDDK, и прочую инфраструктуру для разработки драйверов.

Далее пишем сам драйвер. Я не буду выкладывать полные листинги, а только важные их части. Оговорюсь только что пишем мы Minifilter Filesystem Driver.

1) Регистрация фильтра:

const FLT_OPERATION_REGISTRATION Callbacks[] = {  	{ IRP_MJ_CREATE,								 	  0,											 	  PbPreOperationCreateCallback, 	  PbPostOperationCreateCallback },  	{ IRP_MJ_NETWORK_QUERY_OPEN, 	  0, 	  PbPreOperationNetworkQueryOpenCallback, 	  NULL }, 	 	{ IRP_MJ_OPERATION_END } };  CONST FLT_REGISTRATION FilterRegistration = {      sizeof( FLT_REGISTRATION ),         //  Size     FLT_REGISTRATION_VERSION,           //  Version     0,                                  //  Flags      NULL,                               //  Context     Callbacks,                          //  Operation callbacks      PtFilterUnload,                     //  FilterUnload      PtInstanceSetup,                    //  InstanceSetup     PtInstanceQueryTeardown,            //  InstanceQueryTeardown     PtInstanceTeardownStart,            //  InstanceTeardownStart     PtInstanceTeardownComplete,         //  InstanceTeardownComplete  	PtGenerateFileName,                 //  GenerateFileName     PtNormalizeNameComponent            //  NormalizeNameComponent }; 

Тут все просто. Вводим структуры для регистрации драйвера фильтра. Фильтр будет работать на всех volumes, и перехватывать операцию CreateFile (создание/открытие файла)

Инициализация драйвера также не должна вызывать вопросов у опытных людей. Просто перечислю, что тут происходит:

  • драйвер регистрируется в системе
  • делается нотификация, которая будет нас уведомлять о новых процессах в системе
  • поднимается Communitation Port для общения с пользовательским уровнем (3 кольцо защиты, приложения Windows), где нас ожидает сервис Windows, который будет описан ниже
CPP_DRIVER_ENTRY (     __in PDRIVER_OBJECT DriverObject,     __in PUNICODE_STRING RegistryPath     )  { 	/* locals */     NTSTATUS             status; 	OBJECT_ATTRIBUTES    attr; 	UNICODE_STRING       portName; 		PSECURITY_DESCRIPTOR securityDescriptor;  	/* unused */     UNREFERENCED_PARAMETER( RegistryPath );  	/* code */ 	__try {  		// Set DRIVER_DATA to zeroes 		memset(&DRIVER_DATA, 0, sizeof(DRIVER_DATA));  		/* Setup process creation callback */ 		status = Xu(&PsProcessNotify, FALSE); 		if( !NT_SUCCESS( status )) __leave;  		SetFlag(DRIVER_DATA.initialized, FILTER_SUBSYSTEM_PROCESS);  		// Get exported OS functions 		DECLARE_CONST_UNICODE_STRING( Func1, L"IoReplaceFileObjectName" );				// Win7+ 		DRIVER_DATA.pfnIoReplaceFileObjectName = (PIOREPLACEFILEOBJECTNAME) MmGetSystemRoutineAddress((PUNICODE_STRING) &Func1 );  		// Register filter 		status =  FltRegisterFilter( DriverObject, 									&FilterRegistration, 									&DRIVER_DATA.fltHandle ); 		if ( !NT_SUCCESS( status )) __leave;  		SetFlag(DRIVER_DATA.initialized, FILTER_SUBSYSTEM_DRIVER);  		FltInitializePushLock(&DRIVER_DATA.Sync); 		SetFlag(DRIVER_DATA.initialized, FILTER_SUBSYSTEM_LOCK);  		// Setup security descriptor 		status = FltBuildDefaultSecurityDescriptor( &securityDescriptor, FLT_PORT_ALL_ACCESS ); 		if ( !NT_SUCCESS( status )) __leave;  		RtlInitUnicodeString( &portName, PbCommPortName );  		InitializeObjectAttributes( &attr, &portName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, securityDescriptor ); 		status = FltCreateCommunicationPort( DRIVER_DATA.fltHandle, &DRIVER_DATA.Connection.ServerPort, &attr, NULL, CommPortConnect, CommPortDisconnect, CommPortMessageNotify, 1 ); 		if ( !NT_SUCCESS( status )) __leave;  		SetFlag(DRIVER_DATA.initialized, FILTER_SUBSYSTEM_COMPORT);  		//  Free the security descriptor in all cases. It is not needed once the call to FltCreateCommunicationPort( ) is made. 		FltFreeSecurityDescriptor( securityDescriptor ); 		if ( !NT_SUCCESS( status )) __leave; 		 		//  Start filtering i/o 		status = FltStartFiltering( DRIVER_DATA.fltHandle );  		if ( !NT_SUCCESS( status )) __leave; 		 	 		ASSERT( NT_SUCCESS( status ) );  		DRIVER_DATA.State = DRIVER_STATE_STARTED;  	} __finally  	{ 		if(!NT_SUCCESS( status )) 		{ 			DeregisterFilter(); 		}     }  	return status; } 

Выход из фильтра также прост:
Скажу только что DeregisterFilter занимается освобождением всех ресурсов. основываясь на выставленных флагах в DRIVER_DATA.Initialized (см. код выше)

NTSTATUS  PtFilterUnload(     __in FLT_FILTER_UNLOAD_FLAGS Flags     ) {     UNREFERENCED_PARAMETER( Flags );      PAGED_CODE();      DeregisterFilter(); 	     DbgPrint("PoliciesSandbox!PtFilterUnload: Entered\n");      return STATUS_SUCCESS; } 

Далее. Теперь нам необходимо знать что и куда перенаправлять. Какой файл и куда. Для этого я сделал небольшой протокольчик между kernel-mode и user-mode по communication port, оторый мы подняли в DriverEntry

Определяем набор команд:

typedef enum {  	// From verson = v1.0 	// From User-space to Kernel-space 	GetVersion, 		 	GetFilesystemRedirections, 	AddFilesystemByPIDRedirection, 	RemoveFilesystemByPIDRedirection, 		 	GetRegistryRedirections, 	AddRegistryByPIDRedirection, 	RemoveRegistryByPIDRedirection, 		 	// From Kernel-space to User-space 	ProcessAttached, 		 	CancelIOCompletionPort  	// Version v2.0 is here } COMM_COMMAND; 

Определяем структуру базы всех команд:

typedef struct {     COMM_COMMAND Command;     USHORT Data[]; } COMM_MESSAGE, *PCOMM_MESSAGE; 

Определяем структуру, посылаемую сервису Windows для уведомления о новом процессе в системе:

typedef struct  {     ULONG Pid; } COMM_PROCESS_ATTACHED, *PCOMM_PROCESS_ATTACHED; 

Опеделяем структуру-ответ:

typedef struct {	 	USHORT NeedToMonitor; 	USHORT IsCompleted;          // true, if this packet contains all redirections, needed by driver. Otherwice, driver needs to ask more 	USHORT PairsCount;           // redirections count. Actually, count of null-terminated strings in Data 	 	struct {                     // positions of redirections to make searching fast  		USHORT From; 		USHORT To; 	} Positions[64];   	WCHAR  Data[]; } COMM_FS_REDIRECTIONS, *PCOMM_FS_REDIRECTIONS; 

Также вводим структуры для хранения данных в драйвере:

typedef struct _MAPPING_ENTRY {      UNICODE_STRING OldName;      UNICODE_STRING NewName;      _MAPPING_ENTRY *Next;  } MAPPING_ENTRY, *PMAPPING_ENTRY;  typedef struct _PROCESSES_MAP_ENTRY { 	 	ULONG Pid; 	PMAPPING_ENTRY entries; 	_PROCESSES_MAP_ENTRY *Prev, *Next; } PROCESSES_MAP_ENTRY, *PPROCESSES_MAP_ENTRY; 

Теперь, когда все определено, необходимо при старте нового процесса (а мы уже его перехватываем) оповестить об этом сервис, который в качестве ответа отдаст нам набор правил редиректа файлов и папок в зависимости от этого процесса.

Я не буду включать код функций, очищающих память, т.к. это не так интересно.
При вызове функции ей передается создавший процесс, созданный и флаг, создан ли процесс либо он умирает. Т.е. функция оповещает нас и о создании и о смерти процесса.

Внутри ее, если процесс создается, мы оповещаем об этом сервис Windows, передавая PID процесса. По этому PID сервис находит у себя список правил редиректа и отдает их нам в качестве ответа:

VOID PsProcessNotify (     IN HANDLE  ParentId,     IN HANDLE  ProcessId,     IN BOOLEAN  Create     ) { 	NTSTATUS status;  	if( HandleToULong( ProcessId ) <= 4) return;  	/* Check exsisting data */  	if(Create) 	{ 		LARGE_INTEGER liTimeout; 		DbgPrint("Process created: %x", ProcessId); 		liTimeout.QuadPart = -((LONGLONG)1 * 10 * 1000 * 1000); 		ULONG SenderBufferLength = MESSAGE_BUFFER_SIZE; 		ULONG ReplyLength = MESSAGE_BUFFER_SIZE; 		PCOMM_MESSAGE message = (PCOMM_MESSAGE)myNonPagedAlloc(MESSAGE_BUFFER_SIZE); 		message->Command = ProcessAttached; 		((PCOMM_PROCESS_ATTACHED)(&message->Data[0]))->Pid = HandleToULong(ProcessId); 		status = FltSendMessage(DRIVER_DATA.fltHandle, &DRIVER_DATA.Connection.ClientPort,  			                   message, SenderBufferLength, message, &ReplyLength,  							   &liTimeout);  		if(ReplyLength > 0) 		{ 			PCOMM_FS_REDIRECTIONS Redirections = (PCOMM_FS_REDIRECTIONS)&message->Data[0]; 			DbgPrint("Recieved reply from user-mode: NeedToMonitor = %s\n", Redirections->NeedToMonitor ? "TRUE" : "FALSE" ); 			if(Redirections->NeedToMonitor) 			{ 				PbRepInitializeMapping(HandleToULong(ProcessId), Redirections); 			} else { 				DbgPrint("Thread %d not needed to be monitored. Skipping.", HandleToULong(ProcessId)); 			} 		} 		 		if(!message) myFree(message); 	} 	else 	{ 		DbgPrint("Process destroyed: %x\n", ProcessId);  		PPROCESSES_MAP_ENTRY entry = DRIVER_DATA.Mapping; 		while( entry != NULL ) 		{ 			if(entry->Pid == HandleToULong(ProcessId)) 			{ 				PbRepDeleteMapping(entry); 				break; 			} 		} 	} } 

Код, представленный ниже просто инициализирует внутренние структуры относительно тех данных, которые пришли от сервиса Windows:

NTSTATUS PbRepInitializeMapping( __in ULONG pid, __in PCOMM_FS_REDIRECTIONS Redirections ) {     NTSTATUS status = STATUS_SUCCESS;  	FltAcquirePushLockExclusive( &DRIVER_DATA.Sync );  	__try 	{ 		DbgPrint("PlociesSandbox!PbRepInitializeMapping: redirections count: %d\n", Redirections->PairsCount);  		PMAPPING_ENTRY current = NULL;  		// Lookup PID in map 		PPROCESSES_MAP_ENTRY currentProcess = DRIVER_DATA.Mapping; 		while(currentProcess != NULL) 		{ 			if(currentProcess->Pid == pid) 			{			 				DbgPrint("PlociesSandbox!PbRepInitializeMapping: Already initialized; skipping"); 				return STATUS_SUCCESS; 			} 			currentProcess = currentProcess->Next; 		}  		currentProcess = (PPROCESSES_MAP_ENTRY)myNonPagedAlloc(sizeof(PROCESSES_MAP_ENTRY)); 		currentProcess->Pid = pid; 		currentProcess->Next = DRIVER_DATA.Mapping; 		currentProcess->Prev = NULL; 		if(DRIVER_DATA.Mapping != NULL) DRIVER_DATA.Mapping->Prev = currentProcess; 		DRIVER_DATA.Mapping = currentProcess;  		for(int i=0; i < Redirections->PairsCount; i++) 		{ 			// Copying a pair of pathes From->To to internal mapping structure 			int FromLen = wcslen(&Redirections->Data[Redirections->Positions[i].From]); 			int ToLen   = wcslen(&Redirections->Data[Redirections->Positions[i].To]); 			PMAPPING_ENTRY mappingEntry = (PMAPPING_ENTRY)myAlloc(NonPagedPool, sizeof(MAPPING_ENTRY));  			mappingEntry->OldName.Buffer = (WCHAR*)myAlloc(NonPagedPool, (FromLen + 1) * sizeof(WCHAR)); 			wcscpy(mappingEntry->OldName.Buffer, &Redirections->Data[Redirections->Positions[i].From]); 			mappingEntry->OldName.Length = mappingEntry->OldName.MaximumLength = wcslen(mappingEntry->OldName.Buffer) * sizeof(WCHAR);  			mappingEntry->NewName.Buffer = (WCHAR*)myAlloc(NonPagedPool, (ToLen + 1) * sizeof(WCHAR)); 			wcscpy(mappingEntry->NewName.Buffer, &Redirections->Data[Redirections->Positions[i].To]); 			mappingEntry->NewName.Length = mappingEntry->NewName.MaximumLength = wcslen(mappingEntry->NewName.Buffer) * sizeof(WCHAR);  			if(current == NULL) 			{ 				current = mappingEntry; 				currentProcess->entries = current; 			} else { 				current->Next = mappingEntry; 				current = mappingEntry; 			}  			current->Next = NULL; 		}  	} __finally {  		FltReleasePushLock( &DRIVER_DATA.Sync );  	} 	DbgPrint("PlociesSandbox!PbRepInitializeMapping: done\n");  	return status; } 

И, последнее, функция поиска правила редиректа. Если правило найдено, возвращает STATUS_SUCCESS и видоизмененный путь:

bool PbIsFolder(PUNICODE_STRING path) { 	return path->Buffer[path->Length/2 - 1] == L'\\'; }  NTSTATUS PbLookupRedirection(__in PUNICODE_STRING FilePath, __out PUNICODE_STRING *FileFound) { 	// Possible redirections: 	// Full change:    \Device\HarddiskVolume2\InternalPath\Path\To\Some\File.Ext -> \Device\.\Temporary\File.ext 	// Partial change: \Device\HarddiskVolume2\InternalPath\ -> \Device\HarddiskVolume2\Temporary\ 	//   in this case all pathes, starts with ..\InternalPath should be changed. For ex.: 	//     \Device\HarddiskVolume2\InternalPath\Some\Path\To\File.Ext -> \Device\HarddiskVolume2\Temporary\Some\Path\To\File.Ext 	ULONG Pid = HandleToULong(PsGetCurrentProcessId()); 	FltAcquirePushLockShared( &DRIVER_DATA.Sync ); 	__try  	{ 		PPROCESSES_MAP_ENTRY currentProcess = DRIVER_DATA.Mapping; 		while(currentProcess != NULL) 		{ 			if(currentProcess->Pid == Pid) 			{ 				PMAPPING_ENTRY current = currentProcess->entries; 				while(current != NULL) 				{ 					if(PbIsFolder(¤t->OldName)) 					{ 						// Folders prefixes are identical, please note that all lengthes are double-sized 						if(wcsncmp(current->OldName.Buffer, FilePath->Buffer, current->OldName.Length / 2) == NULL) 						{ 							int newlength = (FilePath->Length - current->OldName.Length) + current->NewName.Length; 							PUNICODE_STRING ret = PbAllocUnicodeString(newlength + 2); 							RtlCopyUnicodeString(ret, ¤t->NewName); 							RtlCopyMemory( Add2Ptr(ret->Buffer, ret->Length), 										   Add2Ptr(FilePath->Buffer, current->OldName.Length), 										   (FilePath->Length - current->OldName.Length) + 2);  							ret->Length = wcslen(ret->Buffer) * 2; 							*FileFound = ret; 							return STATUS_SUCCESS; 						} 					} else { 						if(wcscmp(current->OldName.Buffer, FilePath->Buffer) == NULL) 						{ 							PUNICODE_STRING ret = PbAllocUnicodeString(current->NewName.Length + 2); 							RtlCopyUnicodeString(ret, ¤t->NewName); 							*FileFound = ret; 							return STATUS_SUCCESS; 						} 					} 					current = current->Next; 				} 			} 			currentProcess = currentProcess->Next; 		} 		return STATUS_NOT_FOUND; 	} __finally  	{ 		FltReleasePushLock( &DRIVER_DATA.Sync ); 	} }  

Выводы по разработке драйвера

У нас есть драйвер, который оповещает внешний сервис о создании новых процессов в операционной системе. Сервис получая информацию о процессе принимает решение о том, включать на нем мониторинг или нет. Если да, передает также список правил редиректов на файловой системе.

Написание сервиса Windows

Опять же, я не буду вдаваться в особые подробности. Это обычный сервис. Единственная особенность заключается в том что он должен ожидать от драйвера команд и отвечать на них. Ожидать мы будем при помощи IoCompletionPort. Этот механизм требует (в общем понятно для чего), чтобы на ожидании завершения ввода/вывода по порту висело несколько потоков. Когда придут данные, одна из задач просыпается и может эти данные обработать. Мы в этой задаче будем отсылать список редиректов.

Код может быть немного нечистый, ну да ладно. Главное делает что надо:

  • FilterConnectCommunicationPort соединяет нас с драйвером
  • CreateIoCompletionPort создает i/o completion port, все ссылки, где о нем почитать есть в коде
  • MessagingManagerThread — класс, управляющий потоком обработки сообщений от драйвера
  • Остановка (Stop) посылает всем потокам команду завершения через io completion port, иначе все будет висеть
	public unsafe class MessagingManager 	{ 		private const string FilterCommunicationPortName = "\\PoliciesInjectorCom0"; 		private IntPtr m_filterPortHandle;         private IntPtr m_ioCompletionPortHandle;         private IntPtr[] m_buffers;         private MessagingManagerThread[] m_threads;         private CancellationTokenSource m_cancellationToken;          		public unsafe MessagingManager(Dictionary<string,string> redirections) 		{ 			uint hr = WinApi.FilterConnectCommunicationPort(FilterCommunicationPortName, 0, IntPtr.Zero, 0, IntPtr.Zero, out m_filterPortHandle); 			if(hr != 0x0) 			{ 				throw WinApi.CreateWin32Exception(String.Format("Cannot connect to driver via '{0}' port", FilterCommunicationPortName)); 			} 			Console.WriteLine("Connected to {0}", FilterCommunicationPortName); 			 			// For more info, ENG: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365198(v=vs.85).aspx 			// For more info, RUS: http://www.rsdn.ru/article/baseserv/threadpool.xml 			  			int threadsCount = Environment.ProcessorCount * 2; 			m_ioCompletionPortHandle = WinApi.CreateIoCompletionPort(m_filterPortHandle, IntPtr.Zero, IntPtr.Zero, threadsCount); 			if(m_ioCompletionPortHandle == IntPtr.Zero) 			{ 				throw WinApi.CreateWin32Exception("Cannot create I/O Completeon port"); 			} 			 			// Make thread for each processor and threads cancellation token 			m_threads = new MessagingManagerThread[threadsCount];             m_buffers = new IntPtr[threadsCount]; 			m_cancellationToken = new CancellationTokenSource(); 			Console.WriteLine("Number of threads to monitor: {0}", threadsCount); 				 			for(int i=0; i<m_threads.Length; i++) 			{ 				m_threads[i] = new MessagingManagerThread(m_ioCompletionPortHandle, m_cancellationToken.Token, m_filterPortHandle, redirections); 				unsafe                 {                     m_buffers[i] = Marshal.AllocHGlobal( sizeof(F2U.IncomingMessagePacket) );                     WinApi.RtlZeroMemory( m_buffers[i], sizeof(F2U.IncomingMessagePacket) );                     var buffer = (F2U.IncomingMessagePacket *) m_buffers[i];  					//  Make street magic (see DDK Examples->mini filters->scanner)                     hr = WinApi.FilterGetMessage( m_filterPortHandle, out buffer->Header, Marshal.SizeOf(typeof(F2U.IncomingMessagePacket)),                                                     ref buffer->Overlapped );                      if ( hr != WinApi.HRESULT_FROM_WIN32( WinApi.ERROR_IO_PENDING ) )                     {                     	throw WinApi.CreateWin32Exception( String.Format("Cannot get filter message. 0x{0:X}", hr ));                     }                 } 			} 		} 		 		public unsafe bool Stop() 		{ 			bool successfull = true; 			m_cancellationToken.Cancel();  			Console.WriteLine("Starting to cancel I/O.");             if (!WinApi.CancelIoEx(m_filterPortHandle, IntPtr.Zero))             {             	var errstr = String.Format("Cannot cancel I/O operations (0x{0:X}).", Marshal.GetLastWin32Error());             	Console.WriteLine(errstr);             	successfull = false;             	//	throw WinApi.CreateWin32Exception(errstr);             } 			 			foreach(var thread in m_threads.AsEnumerable()) 			{ 				var overlapped = new F2U.IncomingMessagePacket(); 				IntPtr *completionKey; 				overlapped.Message.Command = Command.CancelIOCompletionPort; 				WinApi.PostQueuedCompletionStatus(m_ioCompletionPortHandle, (uint)sizeof(F2U.IncomingMessagePacket), out completionKey, (NativeOverlapped *) &overlapped);				 			} 			 			foreach(var thread in m_threads.AsEnumerable()) 			{ 				if(!thread.WaitHandle.WaitOne(2000)) 				{ 					Console.WriteLine("Failed while waiting for thread {0} is stopped", thread.ThreadId); 					successfull = false; 					// TODO: kill thread and report confusing bug 				} 			} 			 			return successfull; 		} 	} 

И, наконец, класс обслуживания потока:

	public class MessagingManagerThread 	{ 		private ManualResetEvent  m_resetEvent; 		private IntPtr            m_ioCompletionPortHandle; 		private IntPtr            m_filterPortHandle; 		private CancellationToken m_cancelToken; 		private Thread            m_thread; 		private Int32             m_threadId; 		private Dictionary<string,string> m_redirections;  		private const int timeoutCompletionStatus = 5000; 		 		/// <summary> 		/// Builds MessagingManagerThread object 		/// </summary> 		/// <param name="handle">I/O Completion Port Handle (Win32)</param> 		public MessagingManagerThread(IntPtr ioCompletionPortHandle, CancellationToken token, IntPtr filterPortHandle, Dictionary<string,string> redirections) 		{ 			m_ioCompletionPortHandle = ioCompletionPortHandle; 			m_filterPortHandle = filterPortHandle; 			m_cancelToken = token; 			m_redirections = redirections; 			m_resetEvent = new ManualResetEvent(false); 			m_thread = new Thread( Start ); 			m_thread.Start(); 		} 		 		public WaitHandle WaitHandle  		{ 			get { return m_resetEvent; } 		} 		 		public Int32 ThreadId 		{ 			get { return m_threadId; } 		}  		public unsafe void Start() 		{ 			try  			{ 				// Get current thread id (unmanaged) 				m_threadId = WinApi.GetCurrentThreadId(); 				Console.WriteLine("Monitoring thread {0} is started", m_threadId); 				// Messages processing queue 				while(true) 				{ 					// If cancellation requested, we should to leave thread 					if(m_cancelToken.IsCancellationRequested) 					{ 						Console.WriteLine("Cancellation on thread {0} is requested", m_threadId); 						return; 					} 					 					// Otherwise, we should read completion port  					uint numberOfBytesTransferred; 					UIntPtr lpCompletionKey; 					NativeOverlapped* lpOverlapped; 					 					Console.WriteLine("Starting waiting for message at {0}... ", m_threadId); 					if(!WinApi.GetQueuedCompletionStatus(m_ioCompletionPortHandle, out numberOfBytesTransferred, out lpCompletionKey, out lpOverlapped, -1)) 					{ 						// Something wrong happend 						var error = Marshal.GetLastWin32Error(); 						if(error == WinApi.ERROR_OPERATION_ABORTED) 						{ 							return;	 						} 						if(error == WinApi.WAIT_TIMEOUT)  						{ 							Console.WriteLine("Time out {0}", m_threadId); 							continue; 						} 						Console.WriteLine("WinApi.GetQueuedCompletionStatus returns error code: {0:X}", error); 						throw WinApi.CreateWin32Exception("GetQueuedCompletionStatus", (uint)error); 					} 					 					Console.WriteLine("GetQueuedCompletionStatus finished successfully, Message is recieved"); 					 					// Message recieved 					var request = (F2U.IncomingMessagePacket *)lpOverlapped; 					if(request->Message.Command == Command.ProcessAttached) 					{ 						Run_ProcessAttached(request); 					} else  					if(request->Message.Command == Command.CancelIOCompletionPort) 					{ 						Console.WriteLine("Thread destroying requested");					 					} 					                     // Queue a new request completion.                     WinApi.RtlZeroMemory( (IntPtr) request, Marshal.SizeOf(request->GetType()));                     uint hr = WinApi.FilterGetMessage( m_filterPortHandle, out request->Header, Marshal.SizeOf(request->GetType()),                                                     ref request->Overlapped );                      if (hr != WinApi.HRESULT_FROM_WIN32(WinApi.ERROR_IO_PENDING))                     {                         throw WinApi.CreateWin32Exception( "Cannot get filter message", hr );                     }				 				} 			} finally  			{ 				// Send signal to owner 				m_resetEvent.Set(); 			} 		} 		 		private unsafe void Run_ProcessAttached(F2U.IncomingMessagePacket *data) 		{ 			var pid = ((F2U.ProcessAttached *)data->Message.Data)->ProcessId; 			Console.WriteLine("Incoming request for PID = {0}", pid); 			 			var reply = new IncomingMessagePacketReply(); 			reply.Header.NtStatus = 0; 			reply.Header.MessageId = data->Header.MessageId; 			reply.Message.Command = Command.ProcessAttached; 			int size= Message.MAX_DATA_SIZE; 			 			var pAttachedReply = ((ProcessAttachedReply *)(&reply.Message.Data[0])); 			pAttachedReply->NeedToMonitor=1; 			Process process = null; 			try { 				process = Process.GetProcessById(pid); 				Console.WriteLine("Retrieved name by pid: {0}; Managed: ???", process.ProcessName); 			} catch(ArgumentException ex) 			{ 				pAttachedReply->NeedToMonitor=0; 			} 			 			if(!process.ProcessName.Contains("testredir")) 				pAttachedReply->NeedToMonitor = 0; 			 			if(pAttachedReply->NeedToMonitor==1) 			{ 				int pos = 0, index = 0; 				Console.WriteLine("Redirections registered: {0}", m_redirections.Count); 				foreach(var redirection in m_redirections ) 				{ 					Console.WriteLine(" -- Trying to add redirection: \n    {0}\n    {1}", redirection.Key, redirection.Value); 					unchecked { 						((ProcessAttachedReply_Positions *)&pAttachedReply->Positions[index])->From = (ushort)pos; 						AppendStringToArray(&pAttachedReply->Data, redirection.Key, ref pos); 						((ProcessAttachedReply_Positions *)&pAttachedReply->Positions[index])->To = (ushort)pos; 						AppendStringToArray(&pAttachedReply->Data, redirection.Value, ref pos); 						index++; 					} 				} 				pAttachedReply->PairsCount = (ushort)m_redirections.Count; 			} 			uint status = WinApi.FilterReplyMessage(m_filterPortHandle, ref reply.Header, (int)size); 			 			Console.WriteLine("Making reply: Command = ProcessAttached, NeedToMonitor = True, Size = {0}, ReplyStat=0x{1:X}", size, status); 		} 		 		private unsafe void AppendStringToArray(ushort *arr, string str, ref int position) 		{ 			foreach(var ch in str) 			{ 				arr[position] = ch; 				position++; 			} 			arr[position]=0; 			position++; 		} 	} 

Настройка происходит следующим не хитрым образом:

  var redir = new RedirectionsManager();   redir.Add(typeof(Environment).Assembly.GetName(), "\\temp\\GAC32\\mscorlib.dll"); 

Это значит что любое обращение к сборке mscorlib.dll в GAC приведет к редиректу в папку «C:\temp\GAC32\mscorlib.dll» для 32-х разрядной системы. Для 64-х разрядной проделайте тоже самое, но для другой папки.

Модифицируем mscorlib.dll

Для модификации mscorlib можно воспользоваться .Net Reflector + Reflexil, старыми freeware версиями.
В них я заменил System.Environment.get_Version() так, чтобы номер версии был «5.0.40930.0»

Проверка результатов

Для тестирования создадим .Net Framework 4.0 приложение и назовем его testredir, поскольку наш драйвер ожидает именно такое имя процесса.

Текст приложения прост до невозможности

using System;  namespace testredir  {     public static class testredir     { 		public static void Main(string[] args) 		{ 			Console.WriteLine("Ver: {0}!", System.Environment.Version);			 			Console.Write("Press any key to continue . . . "); 			Console.ReadKey(true); 		}     }  } 

Вывод программы:
Ver: 5.0.40930.0!

Заметка:
Изменять код системных библиотек необходимо аккуратно. Например, наш пример валит часть программного обеспечения, поскольку оно проверяет номер версии, на которой запускается. Хорошим примером может послужить создание логгера внутренних событий, чем я сейчас и занимаюсь

Источники:

ссылка на оригинал статьи http://habrahabr.ru/company/luxoft/blog/239005/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *