System assembly code change or "leak" .Net Framework 5.0

    Here I will demonstrate the opportunity, which in essence is a real hack. The question is, why might this be needed? In fact, there can be a huge number of goals for this. So, our task is to change the code of the mscorlib library so that all programs that use it receive these changes. Not a runtime, of course, but at the start (for runtime, you need to do other things, and here you need to make a reservation that these changes should not break the current library state). I took Mscorlib as an example, because everyone has it on the computer. But you can hack any other.

    We all know that in order to avoid “hell dll”, Microsoft, in addition to the usual versions and the name of the libraries, was given the opportunity to sign assemblies with a key whose public key guarantees that a particular assembly “came” from a particular developer, and not from some of another. Therefore, if for some good reason we want to change the code of an existing library so that it loads into a foreign process and the public key remains the same, we won’t succeed. Because we cannot sign it, we do not have a private key.

    Our mini goal is for the program to display the text on the console:





    Summary:
    1. Introduction
    2. Driver development
    3. Driver Development Conclusions
    4. Writing a Windows Service
    5. Modifying mscorlib.dll code
    6. Research results


    Introduction


    Today we will talk about assemblies located in the GAC and in the NAC. I found out that the .NET Framework is very sensitive to the GAC structure. He trusts her so much that he loads assemblies from there, without really checking the version number. Therefore, in order to make a substitution, we do not need so much: we need to write a kernel-level driver in order to redirect calls to certain files to another place. For this, I took advantage of the possibility of filesystem filters. The bottom line is simple: in order not to write a lot of code in the driver itself, a protocol is written between Kernel-space and user-space. From the side of user-space stands windows - service application. It communicates with the driver, gives it commands what to redirect to. And that in turn redirects. In order to change the code of an existing library, for example, mscorlib, you can use either Reflexil,

    Procedure:
    • We change the assembly (Reflexil or Mono :: Cecil) for the mscorlib assembly and get a modified assembly. For example, we embed logging in it;
    • We put the result in the tmp directory;
    • Add redirection "C: \ Windows \ Microsoft.NET \ assembly \ GAC_64 \ mscorlib \ v4.0_4.0.0.0__b77a5c561934e089 \ mscorlib.dll" to, for example, "C: \ tmp \ mscorlib64.dll" - a modified assembly.

    And we get at subsequent launches of all applications on the .net system library logging. For the sake of order, it is necessary that the driver also filter by process numbers, to whom exactly to give the "leftist", and to whom - the original library.


    Driver development


    First, install VirtualBox. We need it to debug the driver. We don’t want to restart every time our driver contacts the wrong addresses and crashes with an error (naturally, with a BSOD). We roll images of Windows XP and Windows 7, x86 and x64 onto virtual machines and remove snapshots so that we can roll back.

    Next, we install VisualDDK, WinDDK, and other infrastructure for developing drivers on the development machine.

    Next, we write the driver itself. I will not post full listings, but only the important parts. Let me make a reservation, we are writing Minifilter Filesystem Driver.

    1) Filter registration:
    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
    };
    


    Everything is simple here. We introduce structures for registering the filter driver. The filter will work on all volumes, and it will intercept the CreateFile operation (creating / opening a file)

    Initialization of the driver should also not cause questions for experienced people. I’ll just list what happens here:
    • driver is registered in the system
    • a notification is made that will notify us of new processes in the system
    • Communitation Port rises to communicate with the user level (3rd ring of protection, Windows applications), where a Windows service awaits us, which will be described below


    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;
    }
    


    The output from the filter is also simple:
    I ’ll just say that DeregisterFilter is engaged in the release of all resources. based on the set flags in DRIVER_DATA.Initialized (see code above)

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


    Further. Now we need to know what and where to redirect. Which file and where. To do this, I made a small protocol between kernel-mode and user-mode on the communication port, which we raised in DriverEntry.

    We define a set of commands:
    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;
    


    We determine the base structure of all teams:
    typedef struct {
        COMM_COMMAND Command;
        USHORT Data[];
    } COMM_MESSAGE, *PCOMM_MESSAGE;
    


    We determine the structure sent to the Windows service to notify of a new process in the system:
    typedef struct 
    {
        ULONG Pid;
    } COMM_PROCESS_ATTACHED, *PCOMM_PROCESS_ATTACHED;
    


    Define the response structure:
    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;
    


    We also introduce structures for storing data in the driver:

    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;
    


    Now that everything is defined, it is necessary to notify the service about this when starting a new process (and we are already intercepting it), which will give us a set of rules for redirecting files and folders depending on this process.

    I will not include the code of functions that clear memory, because it is not so interesting.
    When a function is called, it is passed the process that created it, the created one, and a flag whether the process is created or it dies. Those. The function notifies us of the creation and death of the process.

    Inside it, if the process is created, we notify the Windows service about this, passing the PID of the process. Therefore, the PID service finds a list of redirect rules and gives them to us as an answer:

    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;
    			}
    		}
    	}
    }
    


    The code below simply initializes the internal structures relative to the data that came from the Windows service:
    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;
    }
    


    And, lastly, the redirect rule search function. If the rule is found, returns STATUS_SUCCESS and the modified path:
    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 );
    	}
    }
    



    Driver Development Conclusions



    We have a driver that notifies an external service of the creation of new processes in the operating system. When a service receives information about a process, it decides whether to enable monitoring on it or not. If so, it also sends a list of redirect rules on the file system.


    Writing a Windows Service


    Again, I will not go into particular details. This is a regular service. The only feature is that he should expect commands from the driver and respond to them. We will be waiting with the help of IoCompletionPort. This mechanism requires (in general, it’s understandable why) that several threads hang on the port while waiting for I / O to complete. When the data arrives, one of the tasks wakes up and can process this data. In this task we will send a list of redirects.

    The code may be a little dirty, but oh well. The main thing is doing what you need:
    • FilterConnectCommunicationPort connects us to the driver
    • CreateIoCompletionPort creates i / o completion port, all links where to read about it are in the code
    • MessagingManagerThread - a class that controls the flow of message processing from the driver
    • Stop sends a completion command to all threads via io completion port, otherwise everything will hang


    	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 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; imini 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;
    		}
    	}
    


    And finally, the thread service class:
    	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 m_redirections;
    		private const int timeoutCompletionStatus = 5000;
    		/// 
    		/// Builds MessagingManagerThread object
    		/// 
    		/// I/O Completion Port Handle (Win32)
    		public MessagingManagerThread(IntPtr ioCompletionPortHandle, CancellationToken token, IntPtr filterPortHandle, Dictionary 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++;
    		}
    	}
    


    The configuration is as follows in a tricky way:
      var redir = new RedirectionsManager();
      redir.Add(typeof(Environment).Assembly.GetName(), "\\temp\\GAC32\\mscorlib.dll");
    

    This means that any call to the mscorlib.dll assembly in the GAC will lead to a redirect to the "C: \ temp \ GAC32 \ mscorlib.dll" folder for the 32-bit system. For 64-bit, do the same, but for a different folder.


    Modify mscorlib.dll


    To modify mscorlib, you can use .Net Reflector + Reflexil, old freeware versions.
    In them, I replaced System.Environment.get_Version () so that the version number is "5.0.40930.0"


    Checking Results


    For testing, we will create a .Net Framework 4.0 application and call it testredir , since our driver expects this process name.

    The text of the application is simple to impossibility

    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);
    		}
        } 
    }
    


    The output of the program: Note: It is necessary to change the code of system libraries carefully. For example, our example brings down a piece of software because it checks the version number on which it runs. A good example is the creation of a logger of internal events, which I am doing now. Sources:
    Ver: 5.0.40930.0!







    Also popular now: