minifilter header hiding and abstraction violation

Hello OSR-list,

I have a pre-existing file of test data. This file is 0x800 bytes long.

I want to skip 0x200 bytes from the start of the file during read so that userspace sees an apparent/virtual file that begins at offset 0x200 and has a length of 0x600.

My sole requirement is to handle userspace reading the file sequentially using CreateFile ReadFile CloseHandle with FILE_ATTRIBUTE_NORMAL or FILE_FLAG_NO_BUFFERING.
I have no other requirements at this stage.

In an attempt to satisfy this requirement I add 0x200 to ‘Data->Iopb->Parameters.Read.ByteOffset.QuadPart’ during uncached reads.
Then the apparent file begins at offset 0x200 (good news) but still has a length of 0x800 and has 0x200 zeros on the end (bad news).

The read that populates the cache transfers 0x600 bytes.
The read that eventually populates the user buffer from the cache transfers 0x800 bytes.
Where did the extra 0x200 bytes come from?
It seems that something is directly acquiring the actual file length from FSRTL_COMMON_FCB_HEADER.FileSize. I think it should use IRP_MJ_QUERY_INFORMATION but that never occurs in the trace below so it would not help to intercept it. (indeed in would not help to intercept any IRP that does not occur in the trace of my sole requirement)

I have confirmed that this happens because because I can hack it to “work” by subtracting 0x200 from FSRTL_COMMON_FCB_HEADER.FileSize in post-create then add it back on pre-read and subtract it again on post-read such that, when this field is accessed directly the accessor receives the apparent/virtual length of 0x600 but the read itself still sees the actual length of 0x800. This hack would not multithread though (if two threads are between pre and post read).
Anyway, I’d expect not to need any hacks and to just intercept IRP_MJ_QUERY_INFORMATION but, like I said, it is never called in the trace below. As far as I can see nothing is called that could discover the length in a way that I could intercept.

If something is going around the minfilter abstraction and just accessing FileObject fields directly then, as a minifilter author, what can I do?

Thanks in advance for any assistance.

David

Pre IRP_MJ_READ (start populating userspace buffer)
…Data = 84574068
…Buffer = 0012DD08
…FltObjects->FileObject = 84569968
…FltObjects->FileObject->FsContext = A1181C58
…FLT_IS_IRP_OPERATION(Data) = 1
…FltObjects->FileObject->Flags = 40002
…Data->Iopb->IrpFlags = 60900
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 2000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 0

Pre IRP_MJ_READ (start populating the cache)
…Data = 859642F0
…Buffer = 805C1000
…FltObjects->FileObject = 84569968
…FltObjects->FileObject->FsContext = A1181C58
…FLT_IS_IRP_OPERATION(Data) = 1
…FltObjects->FileObject->Flags = 40002
…Data->Iopb->IrpFlags = 60403
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 1000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 0
…Doing Data->Iopb->Parameters.Read.ByteOffset.QuadPart += 0x200
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 200
…Doing FltSetCallbackDataDirty(Data)

Post IRP_MJ_READ (done populating the cache)
…Data = 859642F0
…Buffer = 805C1000
…FltObjects->FileObject = 84569968
…FltObjects->FileObject->FsContext = A1181C58
…FLT_IS_IRP_OPERATION(Data) = 1
…FltObjects->FileObject->Flags = 40002
…Data->Iopb->IrpFlags = 60403
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 1000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 0
…Data->IoStatus.Status = 0
…Data->IoStatus.Information = 600
…Contents of Buffer = 02000202020402060208020a020c020e02100212021402160218021a021c021e…then 5e0 bytes more…

Post IRP_MJ_READ (done populating userspace buffer)
…Data = 84574068
…Buffer = 0012DD08
…FltObjects->FileObject = 84569968
…FltObjects->FileObject->FsContext = A1181C58
…FLT_IS_IRP_OPERATION(Data) = 1
…FltObjects->FileObject->Flags = c0002
…Data->Iopb->IrpFlags = 60900
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 2000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 0
…Data->IoStatus.Status = 0
…Data->IoStatus.Information = 800
…Contents of Buffer = 02000202020402060208020a020c020e02100212021402160218021a021c021e…then 7e0 bytes more…

Pre IRP_MJ_READ
…Data = 84574068
…Buffer = 0012DD08
…FltObjects->FileObject = 84569968
…FltObjects->FileObject->FsContext = A1181C58
…FLT_IS_IRP_OPERATION(Data) = 0
…FltObjects->FileObject->Flags = c0002
…Data->Iopb->IrpFlags = 0
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 2000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 800

Pre IRP_MJ_FAST_IO_CHECK_IF_POSSIBLE

Post IRP_MJ_FAST_IO_CHECK_IF_POSSIBLE

Post IRP_MJ_READ
…Data = 84574068
…Buffer = 0012DD08
…FltObjects->FileObject = 84569968
…FltObjects->FileObject->FsContext = A1181C58
…FLT_IS_IRP_OPERATION(Data) = 0
…FltObjects->FileObject->Flags = c0002
…Data->Iopb->IrpFlags = 0
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 2000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 800
…Data->IoStatus.Status = c0000011
…Data->IoStatus.Information = 0
…Contents of Buffer =

Pre IRP_MJ_CLEANUP

Post IRP_MJ_CLEANUP

You must be leaking this information via either the file_info query or a
directory enumeration … to user mode the request for size information
is application dependent and can come in through either pathway,
sometimes both. As for the section getting created, this will come
through a file_info_query.

So from what you are saying, the user mode read is requesting 0x800
bytes of data. If you look at the file in Explorer what is the size of
the file?

I would recommend getting a trace using FileSpy and seeing where you are
leaking the size information. There are tons of entries in this forum
about this exact problem, answered many times. It can be done, it’s just
non-trivial to get 100% correct.

Pete

On 9/22/2014 8:10 AM, xxxxx@nney.com wrote:

Hello OSR-list,

I have a pre-existing file of test data. This file is 0x800 bytes long.

I want to skip 0x200 bytes from the start of the file during read so that userspace sees an apparent/virtual file that begins at offset 0x200 and has a length of 0x600.

My sole requirement is to handle userspace reading the file sequentially using CreateFile ReadFile CloseHandle with FILE_ATTRIBUTE_NORMAL or FILE_FLAG_NO_BUFFERING.
I have no other requirements at this stage.

In an attempt to satisfy this requirement I add 0x200 to ‘Data->Iopb->Parameters.Read.ByteOffset.QuadPart’ during uncached reads.
Then the apparent file begins at offset 0x200 (good news) but still has a length of 0x800 and has 0x200 zeros on the end (bad news).

The read that populates the cache transfers 0x600 bytes.
The read that eventually populates the user buffer from the cache transfers 0x800 bytes.
Where did the extra 0x200 bytes come from?
It seems that something is directly acquiring the actual file length from FSRTL_COMMON_FCB_HEADER.FileSize. I think it should use IRP_MJ_QUERY_INFORMATION but that never occurs in the trace below so it would not help to intercept it. (indeed in would not help to intercept any IRP that does not occur in the trace of my sole requirement)

I have confirmed that this happens because because I can hack it to “work” by subtracting 0x200 from FSRTL_COMMON_FCB_HEADER.FileSize in post-create then add it back on pre-read and subtract it again on post-read such that, when this field is accessed directly the accessor receives the apparent/virtual length of 0x600 but the read itself still sees the actual length of 0x800. This hack would not multithread though (if two threads are between pre and post read).
Anyway, I’d expect not to need any hacks and to just intercept IRP_MJ_QUERY_INFORMATION but, like I said, it is never called in the trace below. As far as I can see nothing is called that could discover the length in a way that I could intercept.

If something is going around the minfilter abstraction and just accessing FileObject fields directly then, as a minifilter author, what can I do?

Thanks in advance for any assistance.

David

Pre IRP_MJ_READ (start populating userspace buffer)
…Data = 84574068
…Buffer = 0012DD08
…FltObjects->FileObject = 84569968
…FltObjects->FileObject->FsContext = A1181C58
…FLT_IS_IRP_OPERATION(Data) = 1
…FltObjects->FileObject->Flags = 40002
…Data->Iopb->IrpFlags = 60900
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 2000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 0

Pre IRP_MJ_READ (start populating the cache)
…Data = 859642F0
…Buffer = 805C1000
…FltObjects->FileObject = 84569968
…FltObjects->FileObject->FsContext = A1181C58
…FLT_IS_IRP_OPERATION(Data) = 1
…FltObjects->FileObject->Flags = 40002
…Data->Iopb->IrpFlags = 60403
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 1000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 0
…Doing Data->Iopb->Parameters.Read.ByteOffset.QuadPart += 0x200
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 200
…Doing FltSetCallbackDataDirty(Data)

Post IRP_MJ_READ (done populating the cache)
…Data = 859642F0
…Buffer = 805C1000
…FltObjects->FileObject = 84569968
…FltObjects->FileObject->FsContext = A1181C58
…FLT_IS_IRP_OPERATION(Data) = 1
…FltObjects->FileObject->Flags = 40002
…Data->Iopb->IrpFlags = 60403
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 1000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 0
…Data->IoStatus.Status = 0
…Data->IoStatus.Information = 600
…Contents of Buffer = 02000202020402060208020a020c020e02100212021402160218021a021c021e…then 5e0 bytes more…

Post IRP_MJ_READ (done populating userspace buffer)
…Data = 84574068
…Buffer = 0012DD08
…FltObjects->FileObject = 84569968
…FltObjects->FileObject->FsContext = A1181C58
…FLT_IS_IRP_OPERATION(Data) = 1
…FltObjects->FileObject->Flags = c0002
…Data->Iopb->IrpFlags = 60900
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 2000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 0
…Data->IoStatus.Status = 0
…Data->IoStatus.Information = 800
…Contents of Buffer = 02000202020402060208020a020c020e02100212021402160218021a021c021e…then 7e0 bytes more…

Pre IRP_MJ_READ
…Data = 84574068
…Buffer = 0012DD08
…FltObjects->FileObject = 84569968
…FltObjects->FileObject->FsContext = A1181C58
…FLT_IS_IRP_OPERATION(Data) = 0
…FltObjects->FileObject->Flags = c0002
…Data->Iopb->IrpFlags = 0
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 2000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 800

Pre IRP_MJ_FAST_IO_CHECK_IF_POSSIBLE

Post IRP_MJ_FAST_IO_CHECK_IF_POSSIBLE

Post IRP_MJ_READ
…Data = 84574068
…Buffer = 0012DD08
…FltObjects->FileObject = 84569968
…FltObjects->FileObject->FsContext = A1181C58
…FLT_IS_IRP_OPERATION(Data) = 0
…FltObjects->FileObject->Flags = c0002
…Data->Iopb->IrpFlags = 0
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 2000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 800
…Data->IoStatus.Status = c0000011
…Data->IoStatus.Information = 0
…Contents of Buffer =
>
> Pre IRP_MJ_CLEANUP
>
> Post IRP_MJ_CLEANUP
>
>
> —
> NTFSD is sponsored by OSR
>
> OSR is hiring!! Info at http://www.osr.com/careers
>
> For our schedule of debugging and file system seminars visit:
> http://www.osr.com/seminars
>
> To unsubscribe, visit the List Server section of OSR Online at http://www.osronline.com/page.cfm?name=ListServer


Kernel Drivers
Windows File System and Device Driver Consulting
www.KernelDrivers.com
866.263.9295

Hello Peter,

Thank you for your response. I’ll try to address your points …

The file has an actual on disk length of 0x800. At this stage my sole requirement is that CreateFile ReadFile CloseHandle reads the file sequentially and skips the first 0x200 bytes (so only sees the last 0x600 bytes).

So from what you are saying, the user mode read is requesting 0x800
bytes of data.

Sorry if I was not clear about this bit. My user mode application just does CreateFile ReadFile (a block of 8K) CloseHandle. The 8K request can be seen in the posted trace as ‘Data->Iopb->Parameters.Read.Length = 2000’ for the pre and post of “populating the user mode buffer”. It can also be seen in “ToRead 2000” in the FileSpy trace below. Here is the user mode code …

// ReadMyFile.exe
HANDLE handle = CreateFileRetry(“HideHeader123.bin”, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, flags, NULL);
for (;:wink: {
UCHAR buffer[1024*8];
ULONG read;
if (!ReadFile(handle, buffer, sizeof(buffer), &read, NULL)) {
// … handle the error …
}
if (read==0) break;
// … dump the data …
}
CloseHandle(handle);

It just reads all the data that it can in that loop that iterates twice. The first iteration reads 0x800 out of 0x2000. The data received is offset 0x200-0x800 from the actual file (good!) but followed by 0x200 zeros (bad!). The second iteration reads 0 bytes out of 0x2000 so breaks out of the loop. It does not call anything other than CreateFile ReadFile CloseHandle. It does not enumerate directories or query the file length. It only knows the file length by virtue of getting less bytes back than it asks for. I don’t presently need 100% correct deception. My sole requirement is to deceive ReadMyFile.exe and no other program (at this stage).

You must be leaking this information via either the file_info query or a
directory enumeration.

My minifilter intercepts logs and passes through IRPs that I am not changing. This means that the trace I posted contains a complete list of everything worth intercepting (on the basis that things that do not happen are not worth intercepting). For example, IRP_MJ_QUERY_INFORMATION and IRP_MJ_DIRECTORY_CONTROL are not present in the trace. I could add code to process them but that code would not be run. I think the FileSpy trace below supports this idea.

The on disk length can be seen in a directory listing as 0x800. I have never made this listing so, even if Windows were caching the length somewhere, it would not have seen it.

I would recommend getting a trace using FileSpy and seeing where you are
leaking the size information.

Here is the complete FileSpy trace:
(I’ve flattened it out rather just pasting the tab-delimited stuff here)

#: 1, Time sent: 10:26:49.428
Process: ReadMyFile.exe
Type: IRP
Request: IRP_MJ_CREATE
IRP Flags: 00000884
Fo Flags: 00000002
Path: \Device\Mup\david\HideHeader123.bin
Status: STATUS_SUCCESS
More Info: FILE_OPEN CreOpts: 00000060 Access: 0012019F Share: 0 Attrib: 00000080 Result: FILE_OPENED

#: 3
Time sent: 10:26:49.428
Process: ReadMyFile.exe
Type: IRP
Request: IRP_MJ_READ
IRP Flags: 00060900
Fo Flags: 00040002
Path: \Device\Mup\david\HideHeader123.bin
Status: STATUS_SUCCESS
More Info: Offset 00000000-00000000 ToRead 2000 Read 800

#: 2
Time sent: 10:26:49.444
Process: ReadMyFile.exe
Type: IRP
Request: IRP_MJ_READ
IRP Flags: 00060403
Fo Flags: 00040002
Path: \Device\Mup\david\HideHeader123.bin
Status: STATUS_SUCCESS
More Info: Offset 00000000-00000000 ToRead 1000 Read 600

#: 5
Time sent: 10:26:49.553
Process: ReadMyFile.exe
Type: FastIO
Request: FASTIO_READ
Fo Flags: 000C0002
Path: \Device\Mup\david\HideHeader123.bin
Status: STATUS_END_OF_FILE
More Info: Offset 00000000-00000800 ToRead 2000 Read 0

#: 4
Time sent: 10:26:49.569
Process: ReadMyFile.exe
Type: FastIO
Request: FASTIO_CHECK_IF_POSSIBLE
Fo Flags: 000C0002
Path: \Device\Mup\david\HideHeader123.bin
Status: STATUS_SUCCESS

#: 6
Time sent: 10:26:49.584
Process: ReadMyFile.exe
Type: IRP
Request: IRP_MJ_CLEANUP
IRP Flags: 00000404
Fo Flags: 000C0002
Path: \Device\Mup\david\HideHeader123.bin
Status: STATUS_SUCCESS

My ReadMyFile.exe waits for a keypress before reading again. Every time it produces this trace.
This trace matches well with the trace that my minifilter produced which I included in my original post. In fact, I think that trace was clearer because it showed the pre and post of each IRP where FileSpy doesn’t get that level of detail.

It can be done, it’s just non-trivial to get 100% correct.

In an extreme-programming style I’m trying to “Do The Simplest Thing That Could Possibly Work” and that is why I’ve set myself such a minimal sole requirement namely: hide the first 0x200 bytes of HideHeader123.bin from ReadMyFile.exe. The simplest thing does not work so I do not want to proceed onto more complicated things yet.

I can come back when I’ve intercepted IRP_MJ_QUERY_INFORMATION and IRP_MJ_DIRECTORY_CONTROL. The reason I have not done this yet is that I believe that code would not be run during ReadMyFile.exe.

Sorry for the long post but I wanted to address all your points in detail.

I hope that provides adequate clarification. Thank you for your interest and time.

All the best,

David

Validate your beliefs as to if they are true or false. Its easy in this
case to validate if they are true or false.
On Sep 23, 2014 7:10 AM, wrote:

> Hello Peter,
>
> Thank you for your response. I’ll try to address your points …
>
> The file has an actual on disk length of 0x800. At this stage my sole
> requirement is that CreateFile ReadFile CloseHandle reads the file
> sequentially and skips the first 0x200 bytes (so only sees the last 0x600
> bytes).
>
> > So from what you are saying, the user mode read is requesting 0x800
> > bytes of data.
>
> Sorry if I was not clear about this bit. My user mode application just
> does CreateFile ReadFile (a block of 8K) CloseHandle. The 8K request can be
> seen in the posted trace as ‘Data->Iopb->Parameters.Read.Length = 2000’ for
> the pre and post of “populating the user mode buffer”. It can also be seen
> in “ToRead 2000” in the FileSpy trace below. Here is the user mode code …
>
> // ReadMyFile.exe
> HANDLE handle = CreateFileRetry(“HideHeader123.bin”,
> GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, flags, NULL);
> for (;:wink: {
> UCHAR buffer[1024*8];
> ULONG read;
> if (!ReadFile(handle, buffer,
> sizeof(buffer), &read, NULL)) {
> // … handle the error …
> }
> if (read==0) break;
> // … dump the data …
> }
> CloseHandle(handle);
>
> It just reads all the data that it can in that loop that iterates twice.
> The first iteration reads 0x800 out of 0x2000. The data received is offset
> 0x200-0x800 from the actual file (good!) but followed by 0x200 zeros
> (bad!). The second iteration reads 0 bytes out of 0x2000 so breaks out of
> the loop. It does not call anything other than CreateFile ReadFile
> CloseHandle. It does not enumerate directories or query the file length. It
> only knows the file length by virtue of getting less bytes back than it
> asks for. I don’t presently need 100% correct deception. My sole
> requirement is to deceive ReadMyFile.exe and no other program (at this
> stage).
>
> > You must be leaking this information via either the file_info query or a
> > directory enumeration.
>
> My minifilter intercepts logs and passes through IRPs that I am not
> changing. This means that the trace I posted contains a complete list of
> everything worth intercepting (on the basis that things that do not happen
> are not worth intercepting). For example, IRP_MJ_QUERY_INFORMATION and
> IRP_MJ_DIRECTORY_CONTROL are not present in the trace. I could add code to
> process them but that code would not be run. I think the FileSpy trace
> below supports this idea.
>
> The on disk length can be seen in a directory listing as 0x800. I have
> never made this listing so, even if Windows were caching the length
> somewhere, it would not have seen it.
>
> > I would recommend getting a trace using FileSpy and seeing where you are
> > leaking the size information.
>
> Here is the complete FileSpy trace:
> (I’ve flattened it out rather just pasting the tab-delimited stuff here)
>
> #: 1, Time sent: 10:26:49.428
> Process: ReadMyFile.exe
> Type: IRP
> Request: IRP_MJ_CREATE
> IRP Flags: 00000884
> Fo Flags: 00000002
> Path: \Device\Mup\david\HideHeader123.bin
> Status: STATUS_SUCCESS
> More Info: FILE_OPEN CreOpts: 00000060 Access: 0012019F
> Share: 0 Attrib: 00000080 Result: FILE_OPENED
>
> #: 3
> Time sent: 10:26:49.428
> Process: ReadMyFile.exe
> Type: IRP
> Request: IRP_MJ_READ
> IRP Flags: 00060900
> Fo Flags: 00040002
> Path: \Device\Mup\david\HideHeader123.bin
> Status: STATUS_SUCCESS
> More Info: Offset 00000000-00000000 ToRead 2000 Read 800
>
> #: 2
> Time sent: 10:26:49.444
> Process: ReadMyFile.exe
> Type: IRP
> Request: IRP_MJ_READ
> IRP Flags: 00060403
> Fo Flags: 00040002
> Path: \Device\Mup\david\HideHeader123.bin
> Status: STATUS_SUCCESS
> More Info: Offset 00000000-00000000 ToRead 1000 Read 600
>
> #: 5
> Time sent: 10:26:49.553
> Process: ReadMyFile.exe
> Type: FastIO
> Request: FASTIO_READ
> Fo Flags: 000C0002
> Path: \Device\Mup\david\HideHeader123.bin
> Status: STATUS_END_OF_FILE
> More Info: Offset 00000000-00000800 ToRead 2000 Read 0
>
> #: 4
> Time sent: 10:26:49.569
> Process: ReadMyFile.exe
> Type: FastIO
> Request: FASTIO_CHECK_IF_POSSIBLE
> Fo Flags: 000C0002
> Path: \Device\Mup\david\HideHeader123.bin
> Status: STATUS_SUCCESS
>
> #: 6
> Time sent: 10:26:49.584
> Process: ReadMyFile.exe
> Type: IRP
> Request: IRP_MJ_CLEANUP
> IRP Flags: 00000404
> Fo Flags: 000C0002
> Path: \Device\Mup\david\HideHeader123.bin
> Status: STATUS_SUCCESS
>
> My ReadMyFile.exe waits for a keypress before reading again. Every time it
> produces this trace.
> This trace matches well with the trace that my minifilter produced which I
> included in my original post. In fact, I think that trace was clearer
> because it showed the pre and post of each IRP where FileSpy doesn’t get
> that level of detail.
>
> > It can be done, it’s just non-trivial to get 100% correct.
>
> In an extreme-programming style I’m trying to “Do The Simplest Thing That
> Could Possibly Work” and that is why I’ve set myself such a minimal sole
> requirement namely: hide the first 0x200 bytes of HideHeader123.bin from
> ReadMyFile.exe. The simplest thing does not work so I do not want to
> proceed onto more complicated things yet.
>
> I can come back when I’ve intercepted IRP_MJ_QUERY_INFORMATION and
> IRP_MJ_DIRECTORY_CONTROL. The reason I have not done this yet is that I
> believe that code would not be run during ReadMyFile.exe.
>
> Sorry for the long post but I wanted to address all your points in detail.
>
> I hope that provides adequate clarification. Thank you for your interest
> and time.
>
> All the best,
>
> David
>
>
> —
> NTFSD is sponsored by OSR
>
> OSR is hiring!! Info at http://www.osr.com/careers
>
> For our schedule of debugging and file system seminars visit:
> http://www.osr.com/seminars
>
> To unsubscribe, visit the List Server section of OSR Online at
> http://www.osronline.com/page.cfm?name=ListServer
>

Are you handling the caching interface? Meaning that in #3 below for the
cached read, are you calling CcCopyRead()? Or are you passing this down
to the underling file system? If not then you will need to adjust the
returned length of the read in #3, on the completion processing, to be
0x600 bytes in length. Otherwise the file system will return the full
length of the read which is 0x800 bytes in length.

To do this correctly, you will need to handle caching on the file where
you are hiding the header or hacks like the above will need to be performed.

Pete

On 9/23/2014 5:10 AM, xxxxx@nney.com wrote:

Hello Peter,

Thank you for your response. I’ll try to address your points …

The file has an actual on disk length of 0x800. At this stage my sole requirement is that CreateFile ReadFile CloseHandle reads the file sequentially and skips the first 0x200 bytes (so only sees the last 0x600 bytes).

> So from what you are saying, the user mode read is requesting 0x800
> bytes of data.
Sorry if I was not clear about this bit. My user mode application just does CreateFile ReadFile (a block of 8K) CloseHandle. The 8K request can be seen in the posted trace as ‘Data->Iopb->Parameters.Read.Length = 2000’ for the pre and post of “populating the user mode buffer”. It can also be seen in “ToRead 2000” in the FileSpy trace below. Here is the user mode code …

// ReadMyFile.exe
HANDLE handle = CreateFileRetry(“HideHeader123.bin”, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, flags, NULL);
for (;:wink: {
UCHAR buffer[1024*8];
ULONG read;
if (!ReadFile(handle, buffer, sizeof(buffer), &read, NULL)) {
// … handle the error …
}
if (read==0) break;
// … dump the data …
}
CloseHandle(handle);

It just reads all the data that it can in that loop that iterates twice. The first iteration reads 0x800 out of 0x2000. The data received is offset 0x200-0x800 from the actual file (good!) but followed by 0x200 zeros (bad!). The second iteration reads 0 bytes out of 0x2000 so breaks out of the loop. It does not call anything other than CreateFile ReadFile CloseHandle. It does not enumerate directories or query the file length. It only knows the file length by virtue of getting less bytes back than it asks for. I don’t presently need 100% correct deception. My sole requirement is to deceive ReadMyFile.exe and no other program (at this stage).

> You must be leaking this information via either the file_info query or a
> directory enumeration.
My minifilter intercepts logs and passes through IRPs that I am not changing. This means that the trace I posted contains a complete list of everything worth intercepting (on the basis that things that do not happen are not worth intercepting). For example, IRP_MJ_QUERY_INFORMATION and IRP_MJ_DIRECTORY_CONTROL are not present in the trace. I could add code to process them but that code would not be run. I think the FileSpy trace below supports this idea.

The on disk length can be seen in a directory listing as 0x800. I have never made this listing so, even if Windows were caching the length somewhere, it would not have seen it.

> I would recommend getting a trace using FileSpy and seeing where you are
> leaking the size information.
Here is the complete FileSpy trace:
(I’ve flattened it out rather just pasting the tab-delimited stuff here)

#: 1, Time sent: 10:26:49.428
Process: ReadMyFile.exe
Type: IRP
Request: IRP_MJ_CREATE
IRP Flags: 00000884
Fo Flags: 00000002
Path: \Device\Mup\david\HideHeader123.bin
Status: STATUS_SUCCESS
More Info: FILE_OPEN CreOpts: 00000060 Access: 0012019F Share: 0 Attrib: 00000080 Result: FILE_OPENED

#: 3
Time sent: 10:26:49.428
Process: ReadMyFile.exe
Type: IRP
Request: IRP_MJ_READ
IRP Flags: 00060900
Fo Flags: 00040002
Path: \Device\Mup\david\HideHeader123.bin
Status: STATUS_SUCCESS
More Info: Offset 00000000-00000000 ToRead 2000 Read 800

#: 2
Time sent: 10:26:49.444
Process: ReadMyFile.exe
Type: IRP
Request: IRP_MJ_READ
IRP Flags: 00060403
Fo Flags: 00040002
Path: \Device\Mup\david\HideHeader123.bin
Status: STATUS_SUCCESS
More Info: Offset 00000000-00000000 ToRead 1000 Read 600

#: 5
Time sent: 10:26:49.553
Process: ReadMyFile.exe
Type: FastIO
Request: FASTIO_READ
Fo Flags: 000C0002
Path: \Device\Mup\david\HideHeader123.bin
Status: STATUS_END_OF_FILE
More Info: Offset 00000000-00000800 ToRead 2000 Read 0

#: 4
Time sent: 10:26:49.569
Process: ReadMyFile.exe
Type: FastIO
Request: FASTIO_CHECK_IF_POSSIBLE
Fo Flags: 000C0002
Path: \Device\Mup\david\HideHeader123.bin
Status: STATUS_SUCCESS

#: 6
Time sent: 10:26:49.584
Process: ReadMyFile.exe
Type: IRP
Request: IRP_MJ_CLEANUP
IRP Flags: 00000404
Fo Flags: 000C0002
Path: \Device\Mup\david\HideHeader123.bin
Status: STATUS_SUCCESS

My ReadMyFile.exe waits for a keypress before reading again. Every time it produces this trace.
This trace matches well with the trace that my minifilter produced which I included in my original post. In fact, I think that trace was clearer because it showed the pre and post of each IRP where FileSpy doesn’t get that level of detail.

> It can be done, it’s just non-trivial to get 100% correct.
In an extreme-programming style I’m trying to “Do The Simplest Thing That Could Possibly Work” and that is why I’ve set myself such a minimal sole requirement namely: hide the first 0x200 bytes of HideHeader123.bin from ReadMyFile.exe. The simplest thing does not work so I do not want to proceed onto more complicated things yet.

I can come back when I’ve intercepted IRP_MJ_QUERY_INFORMATION and IRP_MJ_DIRECTORY_CONTROL. The reason I have not done this yet is that I believe that code would not be run during ReadMyFile.exe.

Sorry for the long post but I wanted to address all your points in detail.

I hope that provides adequate clarification. Thank you for your interest and time.

All the best,

David


NTFSD is sponsored by OSR

OSR is hiring!! Info at http://www.osr.com/careers

For our schedule of debugging and file system seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at http://www.osronline.com/page.cfm?name=ListServer


Kernel Drivers
Windows File System and Device Driver Consulting
www.KernelDrivers.com
866.263.9295

Can you confirm that you are intercepting the fast I/O read? The fact you show a call to IsFastIoPossible suggests you aren’t.

At a minimum, you’d need to handle this by moving the file offset forward 0x200 bytes BEFORE it gets sent down to the underlying FSD. Otherwise, it’s going to copy 0x800 bytes into the buffer.

Tony
OSR

Hi Peter,

Thank you. That makes things much clearer.

To confirm my understanding: the actual length of 0x800 leaks out in this situation because the underlying filesystem calls CcInitializeCacheMap passing the actual length of 0x800. I can see this kind of thing happening in the fastfat example (e.g. read.c). Is this correct? Assuming so:

Yes, I let the cached read fall through by returning FLT_PREOP_SUCCESS_WITH_CALLBACK. If the cache is 0x800 bytes long then the read will return 0x800 bytes. This no longer seems surprising to me.

The trace below still shows the problem under discussion. But based on my understanding, I have since written a hack into cached post read (#D below) to truncate IoStatus.Information when needed. This works well - but it is a hack.

I’d much rather do it properly with CcCopyRead, as you suggest, but this presently alludes me. At #A I cannot use CcCopyRead as the PrivateCacheMap is NULL. I do not think I can do CcInitializeCacheMap myself as I’m not a filesystem and don’t have any CACHE_MANAGER_CALLBACKS to offer. Yet, by #D the read has finished and I am left having to hack. I can stick with my hack but if you could shed some more light on the CcCopyRead technique that would be wonderful.

Hi Tony,

I think that, the way I’m doing this, the cache doesn’t contain the first 0x200 bytes of the file so I do not believe I need add to offsets on any cached execution path. My problem so far has been that the cache is still 0x800 bytes long (not 0x600 as I’d prefer) so it has 0x200 zeros on the end. I think, this is because it was sized that way by the underlying filesystem that knows the actual file length.

Thank you to you both for your assistance.

All the best,

David

#A
Pre IRP_MJ_READ
…Data = 857180C8
…Buffer = 0012DCC8
…FltObjects->FileObject = 85A00588
…FltObjects->FileObject->FsContext = A1BC9C58
…FLT_IS_IRP_OPERATION(Data) = 1
…FltObjects->FileObject->Flags = 40002
…Data->Iopb->IrpFlags = 60900
…BooleanFlagOn(IRP_NOCACHE, Data->Iopb->IrpFlags) = 0
…FltObjects->FileObject->PrivateCacheMap = 00000000
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 2000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 0

#B
Pre IRP_MJ_READ
…Data = 859E2DC8
…Buffer = 8A9E7000
…FltObjects->FileObject = 85A00588
…FltObjects->FileObject->FsContext = A1BC9C58
…FLT_IS_IRP_OPERATION(Data) = 1
…FltObjects->FileObject->Flags = 40002
…Data->Iopb->IrpFlags = 60403
…BooleanFlagOn(IRP_NOCACHE, Data->Iopb->IrpFlags) = 1
…FltObjects->FileObject->PrivateCacheMap = 8592B298
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 1000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 0
…Doing Data->Iopb->Parameters.Read.ByteOffset.QuadPart += 0x200
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 200
…Doing FltSetCallbackDataDirty(Data)

#C
Post IRP_MJ_READ
…Data = 859E2DC8
…Buffer = 8A9E7000
…FltObjects->FileObject = 85A00588
…FltObjects->FileObject->FsContext = A1BC9C58
…FLT_IS_IRP_OPERATION(Data) = 1
…FltObjects->FileObject->Flags = 40002
…Data->Iopb->IrpFlags = 60403
…BooleanFlagOn(IRP_NOCACHE, Data->Iopb->IrpFlags) = 1
…FltObjects->FileObject->PrivateCacheMap = 8592B298
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 1000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 0
…Data->IoStatus.Status = 0
…Data->IoStatus.Information = 600
…Contents of Buffer = 02000202020402060208020a020c020e02100212021402160218021a021c021e…then 5e0 bytes more…

#D
Post IRP_MJ_READ
…Data = 857180C8
…Buffer = 0012DCC8
…FltObjects->FileObject = 85A00588
…FltObjects->FileObject->FsContext = A1BC9C58
…FLT_IS_IRP_OPERATION(Data) = 1
…FltObjects->FileObject->Flags = c0002
…Data->Iopb->IrpFlags = 60900
…BooleanFlagOn(IRP_NOCACHE, Data->Iopb->IrpFlags) = 0
…FltObjects->FileObject->PrivateCacheMap = 8592B298
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 2000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 0
…Data->IoStatus.Status = 0
…Data->IoStatus.Information = 800
…Contents of Buffer = 02000202020402060208020a020c020e02100212021402160218021a021c021e…then 7e0 bytes more…

#E
Pre IRP_MJ_READ
…Data = 857180C8
…Buffer = 0012DCC8
…FltObjects->FileObject = 85A00588
…FltObjects->FileObject->FsContext = A1BC9C58
…FLT_IS_IRP_OPERATION(Data) = 0
…FltObjects->FileObject->Flags = c0002
…Data->Iopb->IrpFlags = 0
…BooleanFlagOn(IRP_NOCACHE, Data->Iopb->IrpFlags) = 0
…FltObjects->FileObject->PrivateCacheMap = 8592B298
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 2000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 800

#F
Pre IRP_MJ_FAST_IO_CHECK_IF_POSSIBLE

#G
Post IRP_MJ_FAST_IO_CHECK_IF_POSSIBLE

#H
Post IRP_MJ_READ
…Data = 857180C8
…Buffer = 0012DCC8
…FltObjects->FileObject = 85A00588
…FltObjects->FileObject->FsContext = A1BC9C58
…FLT_IS_IRP_OPERATION(Data) = 0
…FltObjects->FileObject->Flags = c0002
…Data->Iopb->IrpFlags = 0
…BooleanFlagOn(IRP_NOCACHE, Data->Iopb->IrpFlags) = 0
…FltObjects->FileObject->PrivateCacheMap = 8592B298
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 2000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 800
…Data->IoStatus.Status = c0000011
…Data->IoStatus.Information = 0
…Contents of Buffer =

Pre IRP_MJ_CLEANUP

Post IRP_MJ_CLEANUP

Pre IRP_MJ_ACQUIRE_FOR_CC_FLUSH

Post IRP_MJ_ACQUIRE_FOR_CC_FLUSH

Pre IRP_MJ_RELEASE_FOR_CC_FLUSH

Post IRP_MJ_RELEASE_FOR_CC_FLUSH

Pre IRP_MJ_CLOSE

Taking ownership of the file objects requires you to go the long,
arduous journey of implementing a layered file system. This is what is
required to manage the cache interface for the files … it will get you
to a clean, robust solution but will take you at least 6+ months to get
things working at a base level and longer from there. To go the route
you are going, that is faking sizes to the upper layers, you need to
implement the file info and directory enum requests, changing file sizes
in those calls; removing the header length on queries, adding it on sets.

There are lots of posts on faking file size information in this forum …

Pete

On 9/25/2014 4:27 AM, xxxxx@nney.com wrote:

Hi Peter,

Thank you. That makes things much clearer.

To confirm my understanding: the actual length of 0x800 leaks out in this situation because the underlying filesystem calls CcInitializeCacheMap passing the actual length of 0x800. I can see this kind of thing happening in the fastfat example (e.g. read.c). Is this correct? Assuming so:

Yes, I let the cached read fall through by returning FLT_PREOP_SUCCESS_WITH_CALLBACK. If the cache is 0x800 bytes long then the read will return 0x800 bytes. This no longer seems surprising to me.

The trace below still shows the problem under discussion. But based on my understanding, I have since written a hack into cached post read (#D below) to truncate IoStatus.Information when needed. This works well - but it is a hack.

I’d much rather do it properly with CcCopyRead, as you suggest, but this presently alludes me. At #A I cannot use CcCopyRead as the PrivateCacheMap is NULL. I do not think I can do CcInitializeCacheMap myself as I’m not a filesystem and don’t have any CACHE_MANAGER_CALLBACKS to offer. Yet, by #D the read has finished and I am left having to hack. I can stick with my hack but if you could shed some more light on the CcCopyRead technique that would be wonderful.

Hi Tony,

I think that, the way I’m doing this, the cache doesn’t contain the first 0x200 bytes of the file so I do not believe I need add to offsets on any cached execution path. My problem so far has been that the cache is still 0x800 bytes long (not 0x600 as I’d prefer) so it has 0x200 zeros on the end. I think, this is because it was sized that way by the underlying filesystem that knows the actual file length.

Thank you to you both for your assistance.

All the best,

David

#A
Pre IRP_MJ_READ
…Data = 857180C8
…Buffer = 0012DCC8
…FltObjects->FileObject = 85A00588
…FltObjects->FileObject->FsContext = A1BC9C58
…FLT_IS_IRP_OPERATION(Data) = 1
…FltObjects->FileObject->Flags = 40002
…Data->Iopb->IrpFlags = 60900
…BooleanFlagOn(IRP_NOCACHE, Data->Iopb->IrpFlags) = 0
…FltObjects->FileObject->PrivateCacheMap = 00000000
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 2000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 0

#B
Pre IRP_MJ_READ
…Data = 859E2DC8
…Buffer = 8A9E7000
…FltObjects->FileObject = 85A00588
…FltObjects->FileObject->FsContext = A1BC9C58
…FLT_IS_IRP_OPERATION(Data) = 1
…FltObjects->FileObject->Flags = 40002
…Data->Iopb->IrpFlags = 60403
…BooleanFlagOn(IRP_NOCACHE, Data->Iopb->IrpFlags) = 1
…FltObjects->FileObject->PrivateCacheMap = 8592B298
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 1000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 0
…Doing Data->Iopb->Parameters.Read.ByteOffset.QuadPart += 0x200
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 200
…Doing FltSetCallbackDataDirty(Data)

#C
Post IRP_MJ_READ
…Data = 859E2DC8
…Buffer = 8A9E7000
…FltObjects->FileObject = 85A00588
…FltObjects->FileObject->FsContext = A1BC9C58
…FLT_IS_IRP_OPERATION(Data) = 1
…FltObjects->FileObject->Flags = 40002
…Data->Iopb->IrpFlags = 60403
…BooleanFlagOn(IRP_NOCACHE, Data->Iopb->IrpFlags) = 1
…FltObjects->FileObject->PrivateCacheMap = 8592B298
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 1000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 0
…Data->IoStatus.Status = 0
…Data->IoStatus.Information = 600
…Contents of Buffer = 02000202020402060208020a020c020e02100212021402160218021a021c021e…then 5e0 bytes more…

#D
Post IRP_MJ_READ
…Data = 857180C8
…Buffer = 0012DCC8
…FltObjects->FileObject = 85A00588
…FltObjects->FileObject->FsContext = A1BC9C58
…FLT_IS_IRP_OPERATION(Data) = 1
…FltObjects->FileObject->Flags = c0002
…Data->Iopb->IrpFlags = 60900
…BooleanFlagOn(IRP_NOCACHE, Data->Iopb->IrpFlags) = 0
…FltObjects->FileObject->PrivateCacheMap = 8592B298
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 2000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 0
…Data->IoStatus.Status = 0
…Data->IoStatus.Information = 800
…Contents of Buffer = 02000202020402060208020a020c020e02100212021402160218021a021c021e…then 7e0 bytes more…

#E
Pre IRP_MJ_READ
…Data = 857180C8
…Buffer = 0012DCC8
…FltObjects->FileObject = 85A00588
…FltObjects->FileObject->FsContext = A1BC9C58
…FLT_IS_IRP_OPERATION(Data) = 0
…FltObjects->FileObject->Flags = c0002
…Data->Iopb->IrpFlags = 0
…BooleanFlagOn(IRP_NOCACHE, Data->Iopb->IrpFlags) = 0
…FltObjects->FileObject->PrivateCacheMap = 8592B298
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 2000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 800

#F
Pre IRP_MJ_FAST_IO_CHECK_IF_POSSIBLE

#G
Post IRP_MJ_FAST_IO_CHECK_IF_POSSIBLE

#H
Post IRP_MJ_READ
…Data = 857180C8
…Buffer = 0012DCC8
…FltObjects->FileObject = 85A00588
…FltObjects->FileObject->FsContext = A1BC9C58
…FLT_IS_IRP_OPERATION(Data) = 0
…FltObjects->FileObject->Flags = c0002
…Data->Iopb->IrpFlags = 0
…BooleanFlagOn(IRP_NOCACHE, Data->Iopb->IrpFlags) = 0
…FltObjects->FileObject->PrivateCacheMap = 8592B298
…Data->Iopb->OperationFlags = 0
…Data->Iopb->Parameters.Read.Length = 2000
…Data->Iopb->Parameters.Read.ByteOffset.QuadPart = 800
…Data->IoStatus.Status = c0000011
…Data->IoStatus.Information = 0
…Contents of Buffer =
>
> Pre IRP_MJ_CLEANUP
>
> Post IRP_MJ_CLEANUP
>
> Pre IRP_MJ_ACQUIRE_FOR_CC_FLUSH
>
> Post IRP_MJ_ACQUIRE_FOR_CC_FLUSH
>
> Pre IRP_MJ_RELEASE_FOR_CC_FLUSH
>
> Post IRP_MJ_RELEASE_FOR_CC_FLUSH
>
> Pre IRP_MJ_CLOSE
>
>
>
> —
> NTFSD is sponsored by OSR
>
> OSR is hiring!! Info at http://www.osr.com/careers
>
> For our schedule of debugging and file system seminars visit:
> http://www.osr.com/seminars
>
> To unsubscribe, visit the List Server section of OSR Online at http://www.osronline.com/page.cfm?name=ListServer


Kernel Drivers
Windows File System and Device Driver Consulting
www.KernelDrivers.com
866.263.9295