1

Ultimate task that I am trying to achieve is to read zone information from batch file that I downloaded from the internet. My implementation is based on this SO answer and on some additional resources such as MSDN documentation of UnmanagedMemoryStream. Below you can see what I was able to came up with, however, that code simply throws NullReferenceException on ReadByte and I have absolutely no idea why. Ideas?

private static void Main(string[] args)
{
    var mainStream = NativeMethods.CreateFileW(
    "<path to batch file directory>any.bat:Zone.Identifier",
    NativeConstants.GENERIC_READ,
    NativeConstants.FILE_SHARE_READ,
    IntPtr.Zero,
    NativeConstants.OPEN_EXISTING,
    0,
    IntPtr.Zero);
    unsafe
    {
        using (var memoryStream = new UnmanagedMemoryStream((byte*)mainStream.ToPointer(), 1, 1, FileAccess.Read))
        {
            var zoneInfo = memoryStream.ReadByte();
        }
    }
}

public partial class NativeMethods
{
    /// Return Type: HANDLE->void*
    ///lpFileName: LPCWSTR->WCHAR*
    ///dwDesiredAccess: DWORD->unsigned int
    ///dwShareMode: DWORD->unsigned int
    ///lpSecurityAttributes: LPSECURITY_ATTRIBUTES->_SECURITY_ATTRIBUTES*
    ///dwCreationDisposition: DWORD->unsigned int
    ///dwFlagsAndAttributes: DWORD->unsigned int
    ///hTemplateFile: HANDLE->void*
    [CLSCompliantAttribute(false)]
    [DllImport("kernel32.dll", EntryPoint = "CreateFileW")]
    public static extern System.IntPtr CreateFileW(
        [InAttribute()] [MarshalAsAttribute(UnmanagedType.LPWStr)] string lpFileName,
        uint dwDesiredAccess,
        uint dwShareMode,
        [InAttribute()] System.IntPtr lpSecurityAttributes,
        uint dwCreationDisposition,
        uint dwFlagsAndAttributes,
        [InAttribute()] System.IntPtr hTemplateFile
    );
}

[CLSCompliantAttribute(false)]
public partial class NativeConstants
{
    /// GENERIC_WRITE -> (0x40000000L)
    public const uint GENERIC_WRITE = 1073741824;

    /// GENERIC_READ -> (0x80000000L)
    public const uint GENERIC_READ = 2147483648;

    /// FILE_SHARE_READ -> 0x00000001
    public const uint FILE_SHARE_READ = 1;

    /// OPEN_EXISTING -> 3
    public const uint OPEN_EXISTING = 3;
}

Some notes:

  • mainStream value is valid, it is not -1
  • memoryStream acts like everything is just fine, canRead returns true etc.

To reproduce this issue, i.e. to get yourself some testing batch file, you can for example create file "any.bat", upload it to e.g. Google drive and download it. That way the zone information should be added into this file and on top of that the value in Zone.Identifier should be set to 3, which stads for ZONE_INTERNET.

Stack trace does not reveal anything interesting IMHO:

at System.IO.UnmanagedMemoryStream.ReadByte() at ConsoleApplication1.Program.Main(String[] args) in c:\Users\MH185162\Documents\Visual Studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\Program.cs:line 41 at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args) at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart()

Community
  • 1
  • 1
Michal Hosala
  • 5,570
  • 1
  • 22
  • 49

1 Answers1

1

CreateFileW does not return a pointer to some memory location, it returns a handle. To work with that, you need FileStream, not UnmanagedMemoryStream:

using (var stream = new FileStream(mainStream, FileAccess.Read))

Though this overload of the constructor is obsolete, you should use SafeFileHandle instead of IntPtr (and make sure to Dispose it):

[CLSCompliant(false)]
[DllImport("kernel32.dll", EntryPoint = "CreateFileW")]
public static extern SafeFileHandle CreateFileW(
    [In] [MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
    uint dwDesiredAccess,
    uint dwShareMode,
    [In] IntPtr lpSecurityAttributes,
    uint dwCreationDisposition,
    uint dwFlagsAndAttributes,
    [In] IntPtr hTemplateFile
);

Also, I don't know anything about Zone.Identifier, but for me the stream doesn't contain a single character, like you seem to be expecting, it contains:

[ZoneTransfer]
ZoneId=3

To get to that, you can wrap the FileStream in a StreamReader. So, the whole code to get the above string is:

using (var streamHandle = NativeMethods.CreateFileW(
    @"<path to batch file directory>any.bat:Zone.Identifier",
    NativeConstants.GENERIC_READ, NativeConstants.FILE_SHARE_READ,
    IntPtr.Zero, NativeConstants.OPEN_EXISTING, 0, IntPtr.Zero))
using (var stream = new FileStream(streamHandle, FileAccess.Read))
using (var reader = new StreamReader(stream))
{
    Console.WriteLine(reader.ReadToEnd());
}
svick
  • 236,525
  • 50
  • 385
  • 514
  • Well I actually didnt know what is inside Zone.Identifier, I was simply trying to first read at least "something". Anyway, thanks for the answer I will review it once I am back on PC. Also I know that CreateFileW doesnt return pointer, I thought it is obvious as I am calling ToPointer method. Taking that into account, do you still think that my approach is wrong? – Michal Hosala Sep 26 '14 at 16:54
  • Yes, it's completely wrong. `IntPtr.ToPointer()` just converts the type from `IntPtr` to `void*` (which can then be casted to `byte*`), it doesn't change the value itself. It certainly doesn't read from a handle into memory, or anything like that. – svick Sep 26 '14 at 17:01
  • But isn't that exactly what I want..? From [`IntPtr` MSDN page](http://msdn.microsoft.com/en-us/library/system.intptr(v=vs.110).aspx): "A platform-specific type that is used to represent a pointer or a **handle**.". Then later in regards of `ToPointer` method: "Converts the value of this instance to a pointer to an unspecified type.".. So, I hope you kind of understand my doubts about my approach being _completely_ wrong. – Michal Hosala Sep 26 '14 at 17:09
  • No, it's not. Using `IntPtr` to represent a handle is acceptable (even if dangerous) approach. But calling `ToPointer()` on such value doesn't make any sense. For example for me, `CreateFileW` returns 0x378. But that's just a handle (basically a numeric id), it doesn't mean that you'll find anything useful at the memory address 0x378. Which is what calling `ToPointer()` and then dereferencing that pointer (which you do indirectly through `UnmanagedMemoryStream`) does. – svick Sep 26 '14 at 17:24
  • I finally reviewed your answer and it is just fine, thanks for your patience, now I see that my solution was wrong due to my faulty expectations. – Michal Hosala Sep 27 '14 at 18:53