{"id":384788,"date":"2024-06-29T05:43:38","date_gmt":"2024-06-29T05:43:38","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=384788"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=384788","title":{"rendered":"<span>NTFS Reparse Points<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">Hi, Habr. Here I have prepared for you a small guide about NTFS Reparse points (hereinafter RP). This article is for those who are just starting to dive into the Windows kernel drivers development. In the beginning, I will explain the theory with examples, then I will give an interesting task to solve.<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/webt\/6a\/gf\/4b\/6agf4bcaset4np-2znvb5r9taqi.jpeg\" data-src=\"https:\/\/habrastorage.org\/webt\/6a\/gf\/4b\/6agf4bcaset4np-2znvb5r9taqi.jpeg\" data-blurred=\"true\"\/><br \/>  <a name=\"habracut\"><\/a><br \/>  RP is one of the key features of the NTFS file system, which can be useful in solving backup and recovery tasks. As a result, Acronis is very interested in this technology.<\/p>\n<h2>Useful links<\/h2>\n<p>  If you want to figure out the topic yourself, check out these resources. The theoretical part that you will see next is a summary of materials from this list.<\/p>\n<ul>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/windows\/win32\/fileio\/reparse-points\">Official documentation from MSDN<\/a><\/li>\n<li>P. Yosifovich, A. Ionescu, M. Russinovich and D. Solomon, Windows Internals. Part 2, 6th Edition.<\/li>\n<li><a href=\"http:\/\/hex.pp.ua\/reparse-point-custom.php\">A short and convenient guide (sorry only in russian)<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/aleksk\/LazyCopy\">Very cool project that uses RP<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/Dabudabot\/injection-monitor\">The solution to the problem<\/a> that we will analyze below<\/li>\n<\/ul>\n<p>  <\/p>\n<h2>A bit of theory<\/h2>\n<p>  A Reparse Point (RP) is an object of a given size with programmer-defined data and a unique tag. The custom object represented by the structure <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/ddi\/ntifs\/ns-ntifs-_reparse_guid_data_buffer\">REPARSE_GUID_DATA_BUFFER<\/a>.<\/p>\n<pre><code class=\"cpp\">typedef struct _REPARSE_GUID_DATA_BUFFER {   ULONG  ReparseTag;   USHORT ReparseDataLength;   USHORT Reserved;   GUID   ReparseGuid;   struct {     UCHAR DataBuffer[1];   } GenericReparseBuffer; } REPARSE_GUID_DATA_BUFFER, *PREPARSE_GUID_DATA_BUFFER; <\/code><\/pre>\n<p>  The RP data block size is up to 16 kilobytes.<\/p>\n<p>  <b>ReparseTag<\/b> \u2014 32-bit tag<br \/>  <b>ReparseDataLength<\/b> \u2014 data size<br \/>  <b>DataBuffer<\/b> \u2014 pointer to user data<\/p>\n<p>  RPs provided by Microsoft is represented by the structure <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/ddi\/ntifs\/ns-ntifs-_reparse_data_buffer\">REPARSE_DATA_BUFFER<\/a>. It should not be used for custom RPs.<\/p>\n<p>  The format of the tag:<\/p>\n<div style=\"text-align:center;\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/hm\/g9\/he\/hmg9heytt6cnxcconetjpodxg9g.png\" data-src=\"https:\/\/habrastorage.org\/webt\/hm\/g9\/he\/hmg9heytt6cnxcconetjpodxg9g.png\"\/><\/div>\n<p>  <b>M<\/b> \u2014 Reserved bit by Microsoft; If this bit is set, then the tag was developed by Microsoft.<br \/>  <b>L<\/b> \u2014 Delay bit; If this bit is set, then the data referenced by the RP is located on the medium with a slow response speed and a long data output delay.<br \/>  <b>R<\/b> \u2014 Reserved bit;<br \/>  <b>N<\/b> \u2014 Name change bit; If this bit is set, then the file or directory represents another named entity in the file system.<br \/>  <b>Tag value<\/b> \u2014 Must be requested from Microsoft;<\/p>\n<p>  Each time the application creates or deletes an RP, NTFS updates the <i>\\\\$Extend\\\\$Reparse<\/i> metadata file where RP records are stored. This centralized storage allows any application to sort and efficiently search for the desired object.<\/p>\n<p>  Using RP the Windows provides support for symbolic links, remote storage systems, and mount points for volumes and directories.<\/p>\n<p>  By the way, hard links in Windows are not an actual object, but simply a synonym to the same file on disk. These are not separate filesystem objects, but simply another file name in the file location table. This is how hard links differ from symbolic links.<\/p>\n<p>  To use RP, we need to write:<\/p>\n<ul>\n<li>A small application with the privileges <b>SE_BACKUP_NAME<\/b> or <b>SE_RESTORE_NAME<\/b>, which will create a file containing the RP structure, set the required <b>ReparseTag<\/b> field and fill the <b>DataBuffer<\/b><\/li>\n<li>A kernel-mode driver that will read buffer data and handle calls to this file.<\/li>\n<\/ul>\n<p>  <\/p>\n<h2>Creating our own file with RP<\/h2>\n<p>  1. Acquiring the necessary privileges<\/p>\n<pre><code class=\"cpp\">void GetPrivilege(LPCTSTR priv) { HANDLE hToken; TOKEN_PRIVILEGES tp; OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &amp;hToken); LookupPrivilegeValue(NULL, priv, &amp;tp.Privileges[0].Luid); tp.PrivilegeCount = 1; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hToken, FALSE, &amp;tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL); CloseHandle(hToken); }  GetPrivilege(SE_BACKUP_NAME); GetPrivilege(SE_RESTORE_NAME); GetPrivilege(SE_CREATE_SYMBOLIC_LINK_NAME); <\/code><\/pre>\n<p>  2. Preparing the structure <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/ddi\/ntifs\/ns-ntifs-_reparse_guid_data_buffer\">REPARSE_GUID_DATA_BUFFER<\/a>. In our example, we will write a simple string <i> \u201cMy reparse data\u201d <\/i> to the RP data, but it can be more valuable data.<\/p>\n<pre><code class=\"cpp\">TCHAR data[] = _T(\"My reparse data\"); BYTE reparseBuffer[sizeof(REPARSE_GUID_DATA_BUFFER) + sizeof(data)]; PREPARSE_GUID_DATA_BUFFER rd = (PREPARSE_GUID_DATA_BUFFER) reparseBuffer;  ZeroMemory(reparseBuffer, sizeof(REPARSE_GUID_DATA_BUFFER) + sizeof(data));  \/\/ {07A869CB-F647-451F-840D-964A3AF8C0B6} static const GUID my_guid = { 0x7a869cb, 0xf647, 0x451f, { 0x84, 0xd, 0x96, 0x4a, 0x3a, 0xf8, 0xc0, 0xb6 }};  rd->ReparseTag = 0xFF00; rd->ReparseDataLength = sizeof(data); rd->Reserved = 0; rd->ReparseGuid = my_guid; memcpy(rd->GenericReparseBuffer.DataBuffer, &amp;data, sizeof(data)); <\/code><\/pre>\n<p>  3. Creating the file.<\/p>\n<pre><code class=\"cpp\">LPCTSTR name = _T(\"TestReparseFile\");  _tprintf(_T(\"Creating empty file\\n\")); HANDLE hFile = CreateFile(name, GENERIC_READ | GENERIC_WRITE,  0, NULL, CREATE_NEW,  FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); if (INVALID_HANDLE_VALUE == hFile) { _tprintf(_T(\"Failed to create file\\n\")); return -1; } <\/code><\/pre>\n<p>  4. Filling our file with our structure, using the function <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows\/win32\/api\/ioapiset\/nf-ioapiset-deviceiocontrol\">DeviceIoControl<\/a> with parameter <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows\/win32\/api\/winioctl\/ni-winioctl-fsctl_set_reparse_point\">FSCTL_SET_REPARSE_POINT<\/a>.<\/p>\n<pre><code class=\"cpp\">_tprintf(_T(\"Creating reparse\\n\")); if (!DeviceIoControl(hFile, FSCTL_SET_REPARSE_POINT, rd, rd->ReparseDataLength + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE, NULL, 0, &amp;dwLen, NULL)) { CloseHandle(hFile); DeleteFile(name);  _tprintf(_T(\"Failed to create reparse\\n\")); return -1; }  CloseHandle(hFile); <\/code><\/pre>\n<p>  Full source code of this app could be found <a href=\"https:\/\/gist.github.com\/Dabudabot\/573fbe79cddbf86f71e3a5396d43b35d\">here<\/a>. <\/p>\n<p>  After build and run we get the file. Utility <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-server\/administration\/windows-commands\/fsutil\">fsutil<\/a> could help to look into the file we created and make sure that our data is in place.<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/mt\/zn\/2-\/mtzn2-einprbpe9lyirflde36bm.png\" data-src=\"https:\/\/habrastorage.org\/webt\/mt\/zn\/2-\/mtzn2-einprbpe9lyirflde36bm.png\"\/><\/p>\n<h2>Processing the RP<\/h2>\n<p>  It&#8217;s time to look at this file from the kernel side. I will not go into the details of the mini-filter driver development. There is a good explanation in the official documentation from Microsoft with <a href=\"https:\/\/github.com\/microsoft\/Windows-driver-samples\/tree\/master\/filesys\/miniFilter\">code examples<\/a>. Instead we&#8217;ll take a look at the <i>post callback<\/i> method. <\/p>\n<p>  We need to re-request <b>IRP<\/b> with the <b>FILE_OPEN_REPARSE_POINT<\/b> parameter. To do this, we&#8217;ll call <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/ddi\/fltkernel\/nf-fltkernel-fltreissuesynchronousio\">FltReissueSynchronousIo<\/a>. This function will repeat the request, but with updated <b>Create.Options<\/b> field.<\/p>\n<p>  Inside the structure <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/ddi\/fltkernel\/ns-fltkernel-_flt_callback_data\">PFLT_CALLBACK_DATA<\/a> there is a <b>TagData<\/b>. If we call <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/ddi\/fltkernel\/nf-fltkernel-fltfscontrolfile\">FltFsControlFile<\/a> with <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/ifs\/fsctl-get-reparse-point\">FSCTL_GET_REPARSE_POINT<\/a> parameter, we will get our buffer with data.<\/p>\n<pre><code class=\"cpp\">\/\/ todo we need to check is this actually our tag if (Data->TagData != NULL)  { if ((Data->Iopb->Parameters.Create.Options &amp; FILE_OPEN_REPARSE_POINT) != FILE_OPEN_REPARSE_POINT)     {       Data->Iopb->Parameters.Create.Options |= FILE_OPEN_REPARSE_POINT;        FltSetCallbackDataDirty(Data);       FltReissueSynchronousIo(FltObjects->Instance, Data);     }      status = FltFsControlFile(       FltObjects->Instance,        FltObjects->FileObject,        FSCTL_GET_REPARSE_POINT,        NULL,        0,        reparseData,       reparseDataLength,       NULL     ); } <\/code><\/pre>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/4x\/ev\/s8\/4xevs8qfb3xqaruyy94c_kickk8.png\" data-src=\"https:\/\/habrastorage.org\/webt\/4x\/ev\/s8\/4xevs8qfb3xqaruyy94c_kickk8.png\"\/><\/p>\n<p>  Then we can use this data depending on the task. We can re-request the <b>IRP<\/b>. Or initiate a completely new request. For example, the <a href=\"https:\/\/github.com\/aleksk\/LazyCopy\/tree\/master\/Driver\/LazyCopyDriver\">LazyCopy<\/a> project stores the path to the original file in the RP data. The author does not start copying when the file is opened, but only re-saves the data from RP into the <i>stream context<\/i> of this file. Data starts to be copied the moment the file is read or written. Here are the highlights of his project:<\/p>\n<pre><code class=\"cpp\">\/\/ Operations.c - PostCreateOperationCallback  NT_IF_FAIL_LEAVE(LcGetReparsePointData(FltObjects, &amp;fileSize, &amp;remotePath, &amp;useCustomHandler));  NT_IF_FAIL_LEAVE(LcFindOrCreateStreamContext(Data, TRUE, &amp;fileSize, &amp;remotePath, useCustomHandler, &amp;streamContext, &amp;contextCreated));  \/\/ Operations.c - PreReadWriteOperationCallback  status = LcGetStreamContext(Data, &amp;context);  NT_IF_FAIL_LEAVE(LcGetFileLock(&amp;nameInfo->Name, &amp;fileLockEvent));  NT_IF_FAIL_LEAVE(LcFetchRemoteFile(FltObjects, &amp;context->RemoteFilePath, &amp;nameInfo->Name, context->UseCustomHandler, &amp;bytesFetched));  NT_IF_FAIL_LEAVE(LcUntagFile(FltObjects, &amp;nameInfo->Name)); NT_IF_FAIL_LEAVE(FltDeleteStreamContext(FltObjects->Instance, FltObjects->FileObject, NULL)); <\/code><\/pre>\n<p>  RPs have a wide range of uses and open up many possibilities for solving various problems. We will analyze one of them by solving the following task.<\/p>\n<h2>The task<\/h2>\n<p>  The game <i>Half-life<\/i> can run in two modes: <i>software mode<\/i> and <i>hardware mode<\/i>, which differ in the way graphics are rendered in the game. After digging in a little with <i>IDA Pro<\/i>, we can see that the modes differ by the loaded library <b>sw.dll<\/b> or <b>hw.dll<\/b> using method <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows\/win32\/api\/libloaderapi\/nf-libloaderapi-loadlibrarya\">LoadLibrary<\/a>.<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/i-\/8c\/2c\/i-8c2cwohldfxbmvmbbjqvc0ivg.png\" data-src=\"https:\/\/habrastorage.org\/webt\/i-\/8c\/2c\/i-8c2cwohldfxbmvmbbjqvc0ivg.png\"\/><\/p>\n<p>  Depending on the input arguments (for example, <i>\u201c-soft\u201d<\/i>), this or that string is selected and passed to the function call <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows\/win32\/api\/libloaderapi\/nf-libloaderapi-loadlibrarya\">LoadLibrary<\/a>.<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/4n\/mi\/1n\/4nmi1nc6gwymw2fqmgmrqxh2kem.png\" data-src=\"https:\/\/habrastorage.org\/webt\/4n\/mi\/1n\/4nmi1nc6gwymw2fqmgmrqxh2kem.png\"\/><\/p>\n<p>  The goal of the task is to load the game only in <i>hardware mode<\/i> without the user noticing it. Ideally, so that the user does not even realize that regardless of his choice, that the game is loaded in <i>hardware mode<\/i>.<\/p>\n<p>  Of course, it would be possible to patch the executable file or replace the dll file, or even just copy <b>hw.dll<\/b> and rename the copy to <b>sw.dll<\/b>, but we are not looking for easy ways. Plus, if the game is updated or reinstalled, the effect will disappear.<\/p>\n<h2>The solution<\/h2>\n<p>  I suggest the following solution: write a small mini-filter driver. It will run continuously and will not be affected by reinstalling and updating the game. Let&#8217;s register the driver for the <b>IRP_MJ_CREATE<\/b> operation, because every time the executable calls <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows\/win32\/api\/libloaderapi\/nf-libloaderapi-loadlibrarya\">LoadLibrary<\/a>, it essentially opens the library file. As soon as we notice that the game process is trying to open the <b>sw.dll<\/b> library, we will return the <b>STATUS_REPARSE<\/b> status and ask to repeat the request, but this time to open <b>hw.dll<\/b>. Result: <i>the library we need was opened, even though the user-space asked for another one<\/i>.<\/p>\n<p>  First of all, we need to understand what process is trying to open the library, because we need to turn our trick only for the game process. To do this, right in <b>DriverEntry<\/b> we will need to call <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/ddi\/ntddk\/nf-ntddk-pssetcreateprocessnotifyroutine\">PsSetCreateProcessNotifyRoutine<\/a> and register a method that will be called every time a new process appears in the system.<\/p>\n<pre><code class=\"cpp\">NT_IF_FAIL_LEAVE(PsSetCreateProcessNotifyRoutine(IMCreateProcessNotifyRoutine, FALSE)); <\/code><\/pre>\n<p>  In this method, we must get the name of the executable file. We could use the function <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows\/win32\/procthread\/zwqueryinformationprocess\">ZwQueryInformationProcess<\/a>. <\/p>\n<pre><code class=\"cpp\">NT_IF_FAIL_LEAVE(PsLookupProcessByProcessId(ProcessId, &amp;eProcess));  NT_IF_FAIL_LEAVE(ObOpenObjectByPointer(eProcess, OBJ_KERNEL_HANDLE, NULL, 0, 0, KernelMode, &amp;hProcess));  NT_IF_FAIL_LEAVE(ZwQueryInformationProcess(hProcess,                                                ProcessImageFileName,                                                buffer,                                                returnedLength,                                                &amp;returnedLength)); <\/code><\/pre>\n<p>  If the name matches the target name, in our case it is <b>hl.exe<\/b>, we have to save its <b>PID<\/b>.<\/p>\n<pre><code class=\"cpp\">target = &amp;Globals.TargetProcessInfo[i]; if (RtlCompareUnicodeString(&amp;processNameInfo->Name, &amp;target->TargetName, TRUE) == 0) {       target->NameInfo = processNameInfo;       target->isActive = TRUE;       target->ProcessId = ProcessId;        LOG((\"[IM] Found process creation: %wZ\\n\", &amp;processNameInfo->Name)); } <\/code><\/pre>\n<p>  So, now we have the <b>PID<\/b> of the process of our game saved in the global object. We can move to <i>pre create callback<\/i>. There we should get the name of the file that is trying to be opened. <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/ddi\/fltkernel\/nf-fltkernel-fltgetfilenameinformation\">FltGetFileNameInformation<\/a> will help us with this. This function cannot be called at the <b>DPC<\/b> interrupt level (read more in <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/kernel\/managing-hardware-priorities\">about IRQL<\/a>), however, we are going to make a call at <i>pre create<\/i>, which guarantees us a level not higher than <b>APC<\/b>.<\/p>\n<pre><code class=\"cpp\">status = FltGetFileNameInformation(Data, FLT_FILE_NAME_OPENED | FLT_FILE_NAME_QUERY_FILESYSTEM_ONLY | FLT_FILE_NAME_ALLOW_QUERY_ON_REPARSE, &amp;fileNameInfo); <\/code><\/pre>\n<p>  Then, if our name is <b>sw.dll<\/b>, then we need to replace it in <b>FileObject<\/b> with <b>hw.dll<\/b>. And return the status <b>STATUS_REPARSE<\/b>.<\/p>\n<pre><code class=\"cpp\">\/\/ may be it is sw if (RtlCompareUnicodeString(&amp;FileNameInfo->Name, &amp;strSw, TRUE) == 0) { \/\/ concat NT_IF_FAIL_LEAVE(IMConcatStrings(&amp;replacement, &amp;FileNameInfo->ParentDir, &amp;strHw));  \/\/ then need to change NT_IF_FAIL_LEAVE(IoReplaceFileObjectName(FileObject, replacement.Buffer, replacement.Length)); }  Data->IoStatus.Status = STATUS_REPARSE; Data->IoStatus.Information = IO_REPARSE; return FLT_PREOP_COMPLETE; <\/code><\/pre>\n<p>  Of course, the implementation of the project as a whole is a bit more complex, but I tried to reveal the main points. The whole project with details is <a href=\"https:\/\/github.com\/Dabudabot\/injection-monitor\">here<\/a>.<\/p>\n<h2>Testing the solution<\/h2>\n<p>  To simplify our test runs, instead of a game, we will run a small application and libraries with the following content:<\/p>\n<pre><code class=\"cpp\">\/\/ testapp.exe #include \"TestHeader.h\"  int main() { TestFunction(); return 0; }  \/\/ testdll0.dll #include \"..\/include\/TestHeader.h\" #include &lt;iostream>  \/\/ This is an example of an exported function. int TestFunction() { std::cout &lt;&lt; \"hello from test dll 0\" &lt;&lt; std::endl; return 0; }  \/\/ testdll1.dll #include \"..\/include\/TestHeader.h\" #include &lt;iostream>  \/\/ This is an example of an exported function. int TestFunction() { std::cout &lt;&lt; \"hello from test dll 1\" &lt;&lt; std::endl; return 0; } <\/code><\/pre>\n<p>  Let&#8217;s build <i>testapp.exe<\/i> and link with <i>testdll0.dll<\/i>, copy them to the virtual machine, and also prepare <i>testdll1.dll<\/i>. The task of our driver will be to replace <i>testdll0<\/i> with <i>testdll1<\/i>. We will understand that we have succeeded if we see the message <i>\u201chello from test dll 1\u201d<\/i> in the console instead of <i>\u201chello from test dll 0\u201d<\/i>. Let&#8217;s run it without a driver to make sure our test application works correctly:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/2f\/pb\/s3\/2fpbs3nxq09mlwteb21jai1gs1a.png\" data-src=\"https:\/\/habrastorage.org\/webt\/2f\/pb\/s3\/2fpbs3nxq09mlwteb21jai1gs1a.png\"\/><\/p>\n<p>  Now let&#8217;s install and run the driver:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/zo\/-v\/nv\/zo-vnvel8jiu0iydirsic6u785c.png\" data-src=\"https:\/\/habrastorage.org\/webt\/zo\/-v\/nv\/zo-vnvel8jiu0iydirsic6u785c.png\"\/><\/p>\n<p>  Running the same application, we will get a completely different output to the console since another library was loaded:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/l6\/f6\/b9\/l6f6b9bxmcx_hy3henexq-ph98m.png\" data-src=\"https:\/\/habrastorage.org\/webt\/l6\/f6\/b9\/l6f6b9bxmcx_hy3henexq-ph98m.png\"\/><\/p>\n<p>  Plus, in the application written for the driver, we see logs that say that we really caught our open request and replaced one file with another. The test was successful, it&#8217;s time to test the solution on the game itself.<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/github.com\/Dabudabot\/injection-monitor\/blob\/master\/docs\/demo3.gif?raw=true\"\/><\/p>\n<p>  I hope it was helpful and interesting.<\/p><\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/536018\/\"> https:\/\/habr.com\/ru\/articles\/536018\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">Hi, Habr. Here I have prepared for you a small guide about NTFS Reparse points (hereinafter RP). This article is for those who are just starting to dive into the Windows kernel drivers development. In the beginning, I will explain the theory with examples, then I will give an interesting task to solve.<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/webt\/6a\/gf\/4b\/6agf4bcaset4np-2znvb5r9taqi.jpeg\" data-src=\"https:\/\/habrastorage.org\/webt\/6a\/gf\/4b\/6agf4bcaset4np-2znvb5r9taqi.jpeg\" data-blurred=\"true\"\/>  <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-384788","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/384788","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=384788"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/384788\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=384788"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=384788"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=384788"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}