Windows System Software -- Consulting, Training, Development -- Unique Expertise, Guaranteed Results

Before Posting...
Please check out the Community Guidelines in the Announcements and Administration Category.

Why is ZwWriteFile return STATUS_INVALID_PARAMETER?

IsLookingForAnswersIsLookingForAnswers Member Posts: 5
edited March 22 in NTDEV

Dear all,

This is one of my very first drivers and first encounter with ZwCreatFile and ZwWriteFile. I have no clue why is it failing. I changed literally every possible variable but I don't get it. The ZwCreateFile() succeed but writing to the file fails with the status of invalid parameter.

What I tried:
declaring the buffer as a char array and char pointer (BUFFER[] and *BUFFER)
Changing wcslen to sizeof
Allocating space while creating with ZwCreateFile >> gave me access violation error
set ByteOffset in ZwCreateFile to NULL, 0, 512
Used same _**and **_different IO_STATUS_BLOCK for opening the file and writing to it

I really have no clue, and can smell that it will be a very stupid small mistake some where, but have spent 3 hours reading and trying with no vain so I thought I will create an account and post a question.

Also, I am using WinDbg and have followed the driver debugging tutorial in MSDN and they don't mention anything about figuring out which parameter is the invalid one. Is there a way to determine which parameter is the invalid one?

Thanks in advance!

Code:
OBJECT_ATTRIBUTES FileAttributes;
UNICODE_STRING FileName = RTL_CONSTANT_STRING(L"\DosDevices\C:\temp\example2.txt");
HANDLE fileHandle;
IO_STATUS_BLOCK IOStatusBlock;
IO_STATUS_BLOCK WriteIOsb;
NTSTATUS status;
WCHAR *BUFFER = L"HelloWorld!";
//ULONG strlen = RtlStringCbLengthW(&BUFFER, 20, NULL);
ULONG strlen = wcslen(BUFFER);
ULONG stLength = sizeof(BUFFER);

InitializeObjectAttributes(&FileAttributes, &FileName, OBJ_CASE_INSENSITIVE, NULL, NULL);


status = ZwCreateFile(&fileHandle, GENERIC_WRITE, &FileAttributes, &IOStatusBlock, 0, FILE_ATTRIBUTE_NORMAL, 0, FILE_SUPERSEDE, FILE_RANDOM_ACCESS, NULL, 0);

if (!NT_SUCCESS(status)) {
    DbgPrint("Failed to open the file handle. Error: %x", status);
    return status;
}

// status = ZwWriteFile(fileHandle, NULL, NULL, NULL, &IOStatusBlock, &BUFFER, wcslen(BUFFER), 0, NULL);

status = ZwWriteFile(fileHandle, NULL, NULL, NULL, &WriteIOsb, BUFFER, wcslen(BUFFER), 0, NULL);
if (!NT_SUCCESS(status)) {
    DbgPrint("Failed to write to the file handle. Error: %x", status);
    status = ZwClose(fileHandle);
    if (!NT_SUCCESS(status)) {
        DbgPrint("Failed to close the file handle. Error: %x", status);
    }
    return status;
}
ZwClose(fileHandle);

}

P.S. ANY code optimization is MORE THAN WELCOME since I am just beginning to learn! Also to put this code in context: this is executed in the DriverEntry after initializing the device. Perhaps not the best place but I am just experimenting and will create a user-mode application later on (so each function correspond appropriately to an IOCTL)

Comments

  • Tim_RobertsTim_Roberts Member - All Emails Posts: 13,002

    Both "wcslen" and "sizeof" are wrong, for different reasons. Did you print those values to check? You need to tell ZwWriteFile how many bytes you want to write. wcslen does not return bytes, it returns a character count. And "sizeof" is going to return the size of a pointer, which is 8 in a 64-bit driver. What you want is wcslen(BUFFER) * sizeof(WCHAR).

    What does the IO_STATUS_BLOCK Status value say? It's not necessarily the same as the returned status.

    Are you sure you want to write a Unicode string? Most tools can't handle Unicode files unless you prepend a Unicode BOM.

    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

  • IsLookingForAnswersIsLookingForAnswers Member Posts: 5

    @Tim_Roberts said:
    Both "wcslen" and "sizeof" are wrong, for different reasons. Did you print those values to check? You need to tell ZwWriteFile how many bytes you want to write. wcslen does not return bytes, it returns a character count. And "sizeof" is going to return the size of a pointer, which is 8 in a 64-bit driver. What you want is wcslen(BUFFER) * sizeof(WCHAR).

    What does the IO_STATUS_BLOCK Status value say? It's not necessarily the same as the returned status.

    Are you sure you want to write a Unicode string? Most tools can't handle Unicode files unless you prepend a Unicode BOM.

    Hello Tim and thank you for your very quick response.

    You are right, wcslen returns 0xb whereas wcslensizeof(wchar) returns 0x16. I did not know the difference. For the use of sizeof(), I used it as the following:
    BUFFER[] = L"HelloWorld!"
    then as a buffer size I used sizeof(BUFFER), which gave 0x18. Do you maybe know why sizeof(BUFFER) is larger than wclen
    sizeof(wchar)?

    For the IO_STATUS_VALUE of the zwWriteFile() I got two different values on two different situations:
    WCHAR *BUFFER = "HelloWorld!": 0n0
    WCHAR BUFFER[] = "HelloWorld!": 0n209

    Even after adapting your suggestion, I still get the same error: c000000d

  • IsLookingForAnswersIsLookingForAnswers Member Posts: 5

    Hello Tim and everyone reading this.

    The mistake that I made:
    I overlooked the fact that ByteOffset is a PLARGE_INTEGER and kept passing 0 to it.
    To solve this, I wrote the following
    LARGE_INTEGER byteOffset = {0};
    Then I passed &byteOffset as an argument.

    Another thing I did was not passing the buffer as PVOID, so my final call was:
    ZwWriteFile(fileHandle, NULL, NULL, NULL, &WriteIOsb, (PVOID)BUFFER, wcslen(BUFFER), &byteOffset, NULL);
    and it worked!

    Now I am trying to read from the same file but I don't receive anything (not an error, but I am not sure where to find the data). The way I do it:

    PVOID Buffer = ExAllocatePool(Buffer, 0x800);
    Then I do this call:
    status = ZwReadFile(fileHandle, NULL, NULL, NULL, &WriteIOsb, BUFFER, sizeof(BUFFER), &ByteOffset, NULL);
    But the data in buffer looks quite random.

    I also tried
    WCHAR BUFFER[512];
    status = ZwReadFile(fileHandle, NULL, NULL, NULL, &WriteIOsb, (PVOID)BUFFER, sizeof(BUFFER), &ByteOffset, NULL);
    but no luck. Here size of buffer is correct (0x800).

    I don't get an error, but not sure where to find the read data.

  • Tim_RobertsTim_Roberts Member - All Emails Posts: 13,002
    via Email
    IsLookingForAnswers wrote:
    > The mistake that I made:
    > I overlooked the fact that ByteOffset is a PLARGE_INTEGER and kept passing 0 to it.
    > To solve this, I wrote the following
    > LARGE_INTEGER byteOffset = {0};
    > Then I passed &byteOffset as an argument.

    That was the key.


    > Another thing I did was not passing the buffer as PVOID, so my final call was:

    Totally irrelevant.   If you're writing in C, any pointer can be passed
    where a void* is expected.  If you're writing in C++, your original code
    would have given a compile-time error.  If you didn't get a compile-time
    error, then it is working fine.


    > ZwWriteFile(fileHandle, NULL, NULL, NULL, &WriteIOsb, (PVOID)BUFFER, wcslen(BUFFER), &byteOffset, NULL);
    > and it worked!

    No, it didn't, because you are still saying "wcslen(BUFFER)" instead of
    "wcslen(BUFFER)*sizeof(WCHAR)".  You only wrote half as much as you
    think you did.  Did you look at the file?


    > then as a buffer size I used sizeof(BUFFER), which gave 0x18. Do you maybe know why sizeof(BUFFER) is larger than wclensizeof(wchar)?

    Yes.  When you initialize a string buffer like this:
        WCHAR BUFFER[] = L"HelloWorld!";
    the compiler makes the buffer 24 bytes long, even though there are only
    11 characters.  The final two bytes store the zero terminator that is
    present in all C strings.  You don't want to write the zero character to
    the file, so "sizeof" is the wrong answer.


    > Then I do this call:
    > status = ZwReadFile(fileHandle, NULL, NULL, NULL, &WriteIOsb, BUFFER, sizeof(BUFFER), &ByteOffset, NULL);
    > But the data in buffer looks quite random.

    Did you close and re-open the file?  You opened it with GENERIC_WRITE,
    so you don't have read permission.  Did you reset the ByteOffset value
    to 0?  The ZwWriteFile updates the file position to include your new
    data, and if you pass that value to ZwReadFile, it will be starting past
    the end of the file.

    All of which was in the documentation...

    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

  • IsLookingForAnswersIsLookingForAnswers Member Posts: 5

    @Tim_Roberts said:
    That was the key.

    Yes!

    Totally irrelevant.   If you're writing in C, any pointer can be passed
    where a void* is expected.  If you're writing in C++, your original code
    would have given a compile-time error.  If you didn't get a compile-time
    error, then it is working fine.

    I did not know that (Just finished C course at uni, didn't teach us that)!

    No, it didn't, because you are still saying "wcslen(BUFFER)" instead of
    "wcslen(BUFFER)*sizeof(WCHAR)".  You only wrote half as much as you
    think you did.  Did you look at the file?

    Ahahah you are absolutely right. I checked the file and the whole sentence was written because I used the trick you taught me (wcslen*sizeof).

    Yes.  When you initialize a string buffer like this:
        WCHAR BUFFER[] = L"HelloWorld!";
    the compiler makes the buffer 24 bytes long, even though there are only
    11 characters.  The final two bytes store the zero terminator that is
    present in all C strings.  You don't want to write the zero character to
    the file, so "sizeof" is the wrong answer.

    Okay that makes more sense now! I will avoid using sizeof() and will stick to wcslen*sizeof, is there an advantage of using RtlStringCbLengthW? From documentation:

    RtlStringCbLengthW and RtlStringCbLengthA return the current length of the string in bytes, not including those bytes used for the terminating null character.

    So is using the return value + 1 sizeof(WCHAR) is a good idea? Or should I stick to the method wcslensizeof(WCHAR)? I am just asking to learn best practice.

    Did you close and re-open the file?  You opened it with GENERIC_WRITE,
    so you don't have read permission.  Did you reset the ByteOffset value
    to 0?  The ZwWriteFile updates the file position to include your new
    data, and if you pass that value to ZwReadFile, it will be starting past
    the end of the file.

    Yes, I closed and re-opened the file (they are different methods now. Before I leave any method I close the file handle). I thought I don't need to reset the ByteOffset since I am closing and re-opening the file?

    Also, why is it possible to store data beyond EOF but not read them? (i.e. store and move to current EOF, write data, set EOF back to original using NtSetInformationFile, now there is some data beyond EOF that we cannot read). Or can we read by just doing the reverse (sorry for the newbie question!)

    All of which was in the documentation...

    Look, I have read NtWriteFile() and NtReadFile() for at least five times and still couldn't figure it out. I also Googled for more than an hour and it took me around 3 hours of trial and error and BSODs before I decide to sign up and ask. I am also reading a book and would love to hear your suggestions of resources to read/watch. You are absolutely right, after all, it was all in the documentation. But that P before LARGE_INTEGER is much more obvious and intuitive for experts than for newbies. It's gonna be a steep learning curve, and I am thankful for your help!

  • Tim_RobertsTim_Roberts Member - All Emails Posts: 13,002
    via Email
    IsLookingForAnswers wrote:
    >
    > Okay that makes more sense now! I will avoid using sizeof() and will stick to wcslen*sizeof, is there an advantage of using RtlStringCbLengthW?

    ;)  That's a religious argument.  There are those who argue that kernel
    string use should all come from the routines.  In MY
    opinion, if there is a standard C run-time library function that will do
    the job, use it.  So, it comes down to personal preference.


    > So is using the return value + 1 sizeof(WCHAR) is a good idea? Or should I stick to the method wcslensizeof(WCHAR)? I am just asking to learn best practice.

    No, you're still not grasping the difference here.  The string as stored
    in memory includes the zero byte/word, because that's what C does, but
    you DON'T want to write that zero-byte to the file. You want to write
    the characters, and ONLY the characters.  So, DON'T add one to the
    return value.  RtlStringCbLengthW returns exactly the same thing as
    wcslen()*sizeof(WCHAR).  Which one you use depends on personal preference.


    > Yes, I closed and re-opened the file (they are different methods now. Before I leave any method I close the file handle). I thought I don't need to reset the ByteOffset since I am closing and re-opening the file?

    Why not?  After your ZwWriteFile, your ByteOffset variable gets updated
    to include the data you just wrote, so it points just past the end of
    the file.  If you pass that value to ZwReadFile without changing it,
    it's going to read at the wrong place.

    This is quite different from user-mode I/O, where the runtime library
    keeps track of the file position for you.  In kernel, you need to think
    about the position for every operation.  If you're doing sequential I/O,
    ZwReadFile and ZwWriteFile will update the value on your behalf, but it
    is still under your ownership.  When you start over, YOU need to reset it.


    > Also, why is it possible to store data beyond EOF but not read them? (i.e. store and move to current EOF, write data, set EOF back to original using NtSetInformationFile, now there is some data beyond EOF that we cannot read). Or can we read by just doing the reverse (sorry for the newbie question!)

    No.  If you change the length using NtSetInformationFile, then the data
    beyond that point is gone, and cannot be recovered.  The file is
    permanently truncated.  When you write to a point beyond the end of the
    file, the EOF point is updated to accommodate that new data.  But
    reading never changes the file, including the length.  Reading beyond
    the end is an error.

    You might want to do some I/O experiments in user-mode to make sure
    you're clear on the concepts before you start delving into kernel-mode. 
    The fundamental concepts are the same but the cost of a mistake is much,
    much higher.


    > But that P before LARGE_INTEGER is much more obvious and intuitive for experts than for newbies. It's gonna be a steep learning curve, and I am thankful for your help!

    Maybe.  If you had any experience with user-mode Windows programming,
    you would recognize that PXXXX is the convention for a typedef meaning
    "pointer to an XXXX".  And if you are digging into kernel-mode Windows
    programming without being comfortable with user-mode Windows
    programming, then you are in for a very difficult road indeed.

    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

  • anton_bassovanton_bassov Member Posts: 5,004

    And if you are digging into kernel-mode Windows programming without being comfortable with user-mode Windows
    programming, then you are in for a very difficult road indeed.

    ...and a very obvious lack of understanding of C language, let alone of the most basic things like reading/writing a file, makes things worse for the OP by, probably, a couple of orders of magnitude. Judging from everything that the OP has said in so far, his C course in the uni was less than perfunctory - I bet the main emphasis was put on Java/.NET et al. A good thing is that, unlike "please guide me in the same" lot, he seems to be, indeed, willing to learn things......

    Anton Bassov

  • Peter_Viscarola_(OSR)Peter_Viscarola_(OSR) Administrator Posts: 7,251

    Free advice for the OP: Kernel mode programming on Windows is not the place to learn to program. And starting by trying to learn the native API, which is poorly documented and never put into a proper coherent architectural context, is a tough row to hoe even if you wanted to do it from user mode.

    You have been fortunate to have the abundant patience of Mr. Roberts smile on you here. He is an exception on this list. Review, please, the Community Rukes and Guidelines (which I assume you read before posting) ... particularly the secon on students, dilettantes and dabblers.

    Anyhow, I am truly pleased you got your questions sorted out.

    Peter
    .

    Peter Viscarola
    OSR
    @OSRDrivers

  • IsLookingForAnswersIsLookingForAnswers Member Posts: 5

    @Tim_Roberts said:

    Maybe.  If you had any experience with user-mode Windows programming,
    you would recognize that PXXXX is the convention for a typedef meaning
    "pointer to an XXXX".  And if you are digging into kernel-mode Windows
    programming without being comfortable with user-mode Windows
    programming, then you are in for a very difficult road indeed.

    True that, I believe I will be patient enough, and although it is difficult, I am enjoying it. I call it: sweet suffering.

    @Anton_Bassov said:
    ...and a very obvious lack of understanding of C language, let alone of the most basic things like reading/writing a file, makes things worse > for the OP by, probably, a couple of orders of magnitude. Judging from everything that the OP has said in so far, his C course in the uni was > less than perfunctory - I bet the main emphasis was put on Java/.NET et al. A good thing is that, unlike "please guide me in the same" lot, he > seems to be, indeed, willing to learn things......

    As painful as it is to admit, I agree haha. Our C course is 4 weeks of one lecture per week and two assignments. Our .Net and Java courses are +1 year long, but I don't find them interesting neither challenging. I am more into C and learning the windows driver development is what I am doing in my free time. I am willing to learn things, and at the moment I am reading the relevant parts of Art Baker The Windows NT Device Driver Book (1997, but that's what the university library offer! Also relevant parts because I am more into software drivers). I am MORE THAN happy to be referred to a book or video course (with a reasonable price for student? Video because I am not US situated) to assist my learning process. I saw Peter's book as well with excellent reviews, but I couldn't find it printed where I live.

    @Peter_Viscarola_(OSR) said:

    Free advice for the OP: Kernel mode programming on Windows is not the place to learn to program. And starting by trying to learn the
    native API, which is poorly documented and never put into a proper coherent architectural context, is a tough row to hoe even if you
    wanted to do it from user mode.
    You have been fortunate to have the abundant patience of Mr. Roberts smile on you here. He is an exception on this list. Review, please,
    the Community Rukes and Guidelines (which I assume you read before posting) ... particularly the secon on students, dilettantes and
    dabblers.
    Anyhow, I am truly pleased you got your questions sorted out.

    True that as well, but as I said to Tim I am enjoying it and it is the part drawing my attention the most at the moment. I do it in my free time and never regretted a minute.
    Tim's patience is well noticed and appreciated, I saw that in here as well: https://community.osr.com/discussion/266427/convert-large-integer-to-dword and I am very grateful for his patience and excellent explanation.

    I will be very careful in my future questions, I don't want to get bashed by other community members, and indeed before I post I search it online!

  • Peter_Viscarola_(OSR)Peter_Viscarola_(OSR) Administrator Posts: 7,251

    Ah! The “what’s a DWORD and what’s an ULONG” thread that wandered into much varied territory. That was a great thread, in which the multi-year conspiracy to annoy @anton_bassov was finally revealed.

    I enjoyed rereading that one again this morning.

    Peter

    Peter Viscarola
    OSR
    @OSRDrivers

  • anton_bassovanton_bassov Member Posts: 5,004

    Ah! The “what’s a DWORD and what’s an ULONG” thread that wandered into much varied territory. That was a great thread,
    in which the multi-year conspiracy to annoy @anton_bassov was finally revealed.

    I enjoyed rereading that one again this morning.

    Certainly, it was not a bad one, but concerning " wandering into much varied territory"""".......well, I would say that, in actuality, it stayed pretty well-focused, at least by my standards. After all, it does not go anywhere close to the epic "long rambling threads" that we used to have, does it......

    Anton Bassov

Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Upcoming OSR Seminars
Developing Minifilters 29 July 2019 OSR Seminar Space
Writing WDF Drivers 23 Sept 2019 OSR Seminar Space
Kernel Debugging 21 Oct 2019 OSR Seminar Space
Internals & Software Drivers 18 Nov 2019 Dulles, VA