Изменение кода системных сборок или «утечка» .Net Framework 5.0

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

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

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






    Краткое содержание:
    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!


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

    Источники:
    Luxoft 113,85
    Компания
    Поделиться публикацией
    Комментарии 15
    • +3
      До завтрашнего дня обещаюсь выложить код на пробу =)
      • 0
        а не появились ли где сорцы на гитхабе?
      • 0
        На самом деле целей для этого может быть огромное множество.

        А можно несколько примеров? Ну кроме
        пример валит часть программного обеспечения, поскольку оно проверяет номер версии, на которой запускается.

        хочется понять какие еще плюсы…
        • +7
          Пример… Например, встраивание в некий софт, который не предоставляет логов для какого-то функционала, но их снять по какой-то причине надо.

          Или надо пропатчить сборку так чтобы та в GAC попала, а ключа для подписи новой сборки нету =)

          Как и прошлая моя статья, эта — результат внезапного решения копать и изучать, потому области применения — скорее следствие а не причина =)
        • 0
          Как по мне, так эта статья более практична, чем предыдушая (первое применение, что напрашивается — реверс инжиниринг).
          Но на вторую за день статью, лично моя реакция — похоже у человека появилась уйма свободного времени.

          Без обид, но была бы она третья — вспомнил бы пословицу про кота.
          • +4
            Эти статьи — результат просиживания дома по вечерам в отдалчике, иногда до часу ночи, а в выходные и до трех в течении (суммарно) месяцев 3-4-х. А вообще — часто времени вообще не хватало и были пробелы в несколько месяцев. Так что началось все года два назад, и только недавно все довел до ума. Сами статьи написались тоже не сразу =)
            • 0
              Ну вот, а я уже обзавидоваться успел )
              • +6
                Судя по вашему профилю, для 10-летнего, вы весьма смышленый малый :)
              • 0
                Вспоминаю свою собственную молодость. Правда все уже прошло
            • 0
              В Windows 7 и выше ужесточили проверку подписи драйверов, поэтому писать их можно для внутреннего пользования. А хотелось бы, чтобы драйвера можно было применить и в проектах, которые пишутся не только для себя.
              • 0
                Такой трюк — просто кладезь для библиотеки прототипирования, используемой в unit тестировании. Тот же TypeMock может успешно подменять только несколько классов из mscorlib (DateTime, File и FileStream), а тут потенциал вообще безграничный. Если довести до ума, может получится продукт, не имеющий аналогов. С библиотекой, которая умеет заменять заглушками любой системный вызов, можно будет писать такие тесты, о которых сейчас можно только мечтать. Автор, советую не бросать наработки и продолжать в этом направлении.
                • 0
                  Потому что мы не сможем ее подписать, у нас нет закрытого ключа.

                  Есть стандартный способ правки чужих сборок. Достаточно пересобрать сборку со старым public key (через delay signing) и отключить для нее strong name verification (запуком sn -Vr *,publickey).
                  • 0
                    Для логирования еще можно использовать Profiling API msdn.microsoft.com/en-us/library/bb384493(v=vs.110).aspx

                    Но вот изменять поведение приложения с его помощью уже не получится.
                    • 0
                      Можно править сборки и на лету, не пересобирая. Подобная техника лежит в основе нашего продукта КриптоПро .NET. Основная трудность в последнее время — поиск нужных мест в рантайме .NET, очень уж часто меняются компиляторы.

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое