📜 ⬆️ ⬇️

Changing the code of system assemblies or "leak". Net Framework 5.0

Here I will demonstrate the opportunity, which in its essence is a real hack. The question is why this may be needed? In fact, the goals for this can be a huge set. So our task is to change the code of the mscorlib library so that all the programs that use it get these changes. Not runtime, of course, but during the start (for runtime, you need to do other things, and here we must make a reservation that these changes should not break the current state of the library). 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 the “hell dll”, in addition to the regular versions and library names, Microsoft was given the opportunity to sign assemblies with a key, the public key of which guarantees that a specific assembly “came” from a specific developer, and not from other. Therefore, if for some reasonably bona fide reason we want to change the code of the existing library so that it is loaded into the foreign process and the public key remains the same, we will not succeed. Because we can not 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. Conclusions on driver development
  4. Writing a Windows service
  5. Code change mscorlib.dll
  6. Research results


Introduction


Today we will talk about assemblies that are in the GAC and in the NAC. I found out that the .NET Framework is very sensitive to the structure of the GAC. He trusts her so much that he loads the assemblies from there, without really checking the version number. Therefore, in order to make a substitution, we need not so much: we need to write a kernel-level driver in order to redirect calls to certain files to another place. I used the filesystem filters feature for this. The essence 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 user-space side, a windows - service application appears. It communicates with the driver, gives him commands, what to redirect. And that in turn redirects. In order to change the code of an existing library, for example, mscorlib, you can use either Reflexil or Mono :: Cecil.

Procedure:

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


Driver Development


To begin, install VirtualBox. We need it to debug the driver. We don’t want to restart every time our driver addresses the wrong addresses and crashes with an error (naturally, with a BSOD). On virtualki we roll images of Windows XP and Windows 7, x86 and x64 and remove snapshots so that there is much to roll back.

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

Next, write the driver itself. I will not post full listings, but only their important parts. I will make a reservation just write we 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 }; 


It's simple. Enter the structure for registering the filter driver. The filter will work on all volumes, and intercept the CreateFile operation (create / open file)

Driver initialization should also not cause questions from experienced people. Just list what happens here:


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


Exiting the filter is also simple:
I can only 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. What file and where. For this, I made a small protocol between the kernel-mode and user-mode via communication port, which we raised in DriverEntry

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 structure of the base of all commands:
 typedef struct { COMM_COMMAND Command; USHORT Data[]; } COMM_MESSAGE, *PCOMM_MESSAGE; 


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


We define the structure of the response:
 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, when everything is determined, it is necessary at the start of a new process (and we are already intercepting it) to notify about this service, which, as an answer, will give us a set of rules for redirecting files and folders depending on this process.

I will not include the code of memory-clearing functions, since it's not so interesting.
When a function is called, it is passed to the process that created it, created it and whether the process is created or it dies. Those. the function notifies us of the creation and death of the process.

Inside it, if a process is being created, we notify the Windows service of this by passing the PID of the process. Therefore, the PID service finds a list of the 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, finally, the search function redirect rules. If a 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(&current->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, &current->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, &current->NewName); *FileFound = ret; return STATUS_SUCCESS; } } current = current->Next; } } currentProcess = currentProcess->Next; } return STATUS_NOT_FOUND; } __finally { FltReleasePushLock( &DRIVER_DATA.Sync ); } } 



Conclusions on driver development



We have a driver that notifies the external service of creating new processes in the operating system. Service receiving information about the process decides whether to include monitoring on it or not. If so, also sends a list of redirection rules on the file system.


Writing a Windows service


Again, I will not go into any particular details. This is a regular service. The only feature is that he should expect from the driver commands and respond to them. We will wait using IoCompletionPort. This mechanism requires (in general, it is clear for what) that several threads are hanging while waiting for completion of I / O on the port. When the data comes, one of the tasks wakes up and can process this data. We in this task will send a list of redirects.

The code may be a little unclean, but oh well. The main thing is doing what is necessary:


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


And finally, the flow 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<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++; } } 


The setting is the following not tricky:
  var redir = new RedirectionsManager(); redir.Add(typeof(Environment).Assembly.GetName(), "\\temp\\GAC32\\mscorlib.dll"); 

This means that any access to the assembly mscorlib.dll in the GAC will result in a redirect to the folder “C: \ temp \ GAC32 \ mscorlib.dll” for a 32-bit system. For 64-bit, do the same, but for another folder.


Modify mscorlib.dll


To modify mscorlib, you can use .Net Reflector + Reflexil, the old freeware versions.
In them, I replaced System.Environment.get_Version () so that the version number was "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 such a process name.

The text of the application is simple to impossible

 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:
Ver: 5.0.40930.0!


The note:
Changing the system library code is necessary carefully. For example, our example crashes 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 now do

Sources:

Source: https://habr.com/ru/post/239005/


All Articles