Все мы знаем, что для того, чтобы не было «ада dll», Microsoft помимо обычных версий и названия библиотек, дали возможность подписывать сборки ключом, public key которой гарантирует что конкретная сборка «пришла» от конкретного разработчика, а не от какого-то другого. Поэтому, если мы хотим по какой-то вполне добросовестной причине изменить код существующей библиотеки т.о., чтобы она загрузилась в чужой процесс и при этом ключик public key остался тем же, у нас этого не получится. Потому что мы не сможем ее подписать, у нас нет закрытого ключа.
Наша мини цель, чтобы программа вывела на консоль текст:
PS: это — исправленная версия старого кода
.
Краткое содержание:
- Введение
- Разработка драйвера
- Выводы по разработке драйвера
- Написание сервиса Windows
- Изменение кода mscorlib.dll
- Результаты исследования
Введение
Сегодня речь пойдет о сборках, находящихся в 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://fsfilters.blogspot.com/
- DDK Examples->mini filters->scanner
- msdn.microsoft.com/en-us/library/windows/desktop/aa365198(v=vs.85).aspx
- www.rsdn.ru/article/baseserv/threadpool.xml
ссылка на оригинал статьи http://habrahabr.ru/company/luxoft/blog/239005/
Добавить комментарий