Here, we will look at different ways of defining and setting memory fields.
- incbin - A NASM pseudo-instruction that includes everything from a file in the current location of the PE.
- File I/O - We'll go over the Win32 API functions for accessing files.
incbin is a pseudo-instruction, unlike %include, which is a preprocessor directive.
When you use %include, NASM includes all the code from the file specified.
incbin, instead of including all the code, includes all the contents from the file specified.
It's kind of like using 'db <all the contents from the file>' .
incbin - Example Program - The Code
This is the source code:
;; Define the externs. extern MessageBoxA extern ExitProcess ;; Construct the symbol import table. import MessageBoxA user32.dll import ExitProcess kernel32.dll ;; The code section; use 32-bit code. section .text use32 ;; ..start is where the program starts running. ..start: ;; Display a message box with title the_title and message msg. push dword 0 push dword the_title push dword msg push dword 0 call [MessageBoxA] ;; Exit, returning 0. push dword 0 call [ExitProcess] ;; The data section. section .data ;; Define the title. the_title db "Just A Test", 0 ;; Tell NASM that this is where msg is located. msg: ;; Then use binary include to include everything from file01.txt incbin "file01.txt" db 0 ;; The bss section. section .bss
And this should go into file01.txt :
Hello, how are you? Nice day, isn't it?
incbin - Example Program - The Output
You should get a message box with everything that's in file01.txt ;
Did we read from a file? No. NASM did the reading from file01.txt when it was assembling our program. So it's the same as:
msg: db "Hello, how are you? ", 13, 10, 13, 10, "Nice day, isn't it? "We just simply defined the data area at msg to whatever was inside file01.txt
So how do we read from a file?
ReadFile - Reading From A File
ReadFile() is the Win32 API function for reading from a file.
So let's look at the function parameters:
hFile - The handle to the file to write to.
lpBuffer - The pointer to the memory buffer where to save the read data.
nNumberOfBytesToRead - The number of bytes to read from the file.
lpNumberOfBytesRead - The pointer to an integer that receives the number of bytes actually read.
lpOverlapped - A pointer to an OVERLAPPED structure. This parameter is optional, depending on how the file was opened; we won't care about this parameter right now.
What? No filename? And what is this file handle?
Let's take a look at how to get a file handle.
OpenFile - Opening A File
The OpenFile() Win32 API function opens a file.
lpFileName - The pointer to a string that contains the name of the file to open.
lpReOpenBuff - The pointer to an OFSTRUCT structure.
uStyle - The action to take.
The values I use most often, for the third parameter, are:
OF_CREATE (0x1000) - Make a new file; if the file exists, truncate it to 0 bytes in size.
OF_READ (0x0000) - Open the file for reading.
OF_WRITE (0x0001) - Open the file for writing.
OF_READWRITE (0x0002) - Open the file for reading and writing.
For more information, visit OpenFile Function (Windows).
CreateFile - Another Method For Opening A File
CreateFile() is another Win32 API function for opening file (or other I/O device) handles.
- The pointer to a string with the filename.
- The desired file access rights constant (/s).
- The share mode flags for the file being opened or made.
- An optional pointer to a SECURITY_ATTRIBUTES structure.
- Creation disposition. An action to take when a file exists or does not exist.
- Flags and attributes. Specifies whether the file should be archive, read-only, hidden, etc.
- A valid handle to a template file that allows the GENERIC_READ access right. That file's attributes and extended attributes are used for the file being opened, if the file being opened is a new file.
The most common access rights are GENERIC_READ, for reading, or GENERIC_WRITE, for writing.
GENERIC_ALL (0x10000000) - Read, write, and execute access.
GENERIC_EXECUTE (0x20000000) - Execute access.
GENERIC_WRITE (0x40000000) - Write access.
GENERIC_READ (0x80000000) - Read access.
Share Mode Flags
FILE_SHARE_READ (0x01) - Other processes can read this file.
FILE_SHARE_WRITE (0x02) - Other processes can write to this file.
FILE_SHARE_DELETE (0x04) - Other processes can delete this file.
0 (0x00) - Other processes can't access this file.
CREATE_NEW (1) - Make a new file; fail if exists.
CREATE_ALWAYS (2) - Make a new file; if it exists, and if it is accessible, overwrite the file.
OPEN_EXISTING (3) - Open a file; fail, if not exists.
OPEN_ALWAYS (4) - Open a file; make a new file, if not exists.
TRUNCATE_EXISTING (5) - Open a file and truncate it, so that its size is 0 bytes; fail if not exists.
Flags and Attributes
FILE_ATTRIBUTE_NORMAL (0x80) - Normal attributes.
FILE_ATTRIBUTE_HIDDEN (0x02) - The file is hidden.
For more information about the CreateFile() function or its parameters, visit CreateFile Function (Windows).
When you're done using a file, always close it using the CloseHandle() Win32 API function.
CloseHandle - Closing A File
When you're done using the file, you can close the file using this function. This function is very simple and takes only one parameter; how hard is that?
- The handle to the file to close.
WriteFile - Writing To A File
The parameters of the WriteFile() Win32 API function are pretty much identical to the parameters of the ReadFile() Win32 API function; the only difference, as for the programmer's side, is that ReadFile() reads bytes from a file to memory, and WriteFile() writes bytes to a file from memory.
Windows also keeps track of where, in the file, the handle's pointer points to. A file pointer is kind of like a regular pointer that we use for referencing memory, but instead of pointing to a memory address, it has the offset into the file.
When you use ReadFile() or WriteFile(), you read or write starting at the current file pointer. The current file pointer is then appropriately modified, after the read or write operation.
For example, let's say the file pointer is at 20. You read 4 bytes. The file pointer is now at 24. Then you write 5 bytes. The file pointer is, at this point, at 29. And so on.
When you first open a file, the file pointer is normally at 0. So how do you change the file pointer without using read or write operations?
SetFilePointer - Setting The File Pointer
To set the file pointer for a file handle, you use the SetFilePointer Win32 API function.
- The handle of which to set the pointer.
- The distance to move the file pointer.
- Optional (can be NULL); the pointer to an integer (4 bytes) that has the high-order double-word of the distance to move the file pointer. If not NULL, the integer pointed to by this parameter also receives the high-order double-word of the new file pointer.
- The move method; where to start moving the file pointer from.
If the function succeeds, the return value is the low-order double-word of the new file pointer.
FILE_BEGIN (0) - Start moving from the beginning of the file.
FILE_CURRENT (1) - Start moving from the current file pointer position.
FILE_END (2) - Start moving from the end of the file.
For more reference to this function, visit SetFilePointer Function (Windows).
There is also a Win32 API function for getting the size of a file.
GetFileSize - Getting The Size Of A File
The GetFileSize() Win32 API function gets the size of a file. Since files can be larger than 4 GB in size, we might need a 64-bit value to know the file size.
- A handle to the file to get the size of.
- An optional (can be NULL) pointer to an integer that would receive the high-order double-word of the file size.
The return value is the low-order double-word of the file size.
For more reference on this function, visit GetFileSize Function (Windows).
So what if we wanted to truncate a file?
SetEndOfFile - Truncating The Rest Of The File
This Win32 API function sets the end-of-file mark at the current file pointer offset. That means if the file pointer was 15 then the file is now 15 bytes in size.
- The handle to the file.
Now that you know about Win32 API File I/O functions and NASM's syntax, we can proceed to writing the code for our next program.
Example Program - The Code
;; Define the externs. extern MessageBoxA extern ExitProcess extern OpenFile extern ReadFile extern WriteFile extern CloseHandle extern GetFileSize ;; Construct our symbol import table. import MessageBoxA user32.dll import ExitProcess kernel32.dll import OpenFile kernel32.dll import ReadFile kernel32.dll import WriteFile kernel32.dll import CloseHandle kernel32.dll import GetFileSize kernel32.dll ;; This goes into the code section; use 32-bit code. section .text use32 ;; This is where the program should start running. ..start: ;; Call the main() function. call main ;; Exit, returning whatever main() returned. push eax call [ExitProcess] main: ;; 12 bytes for local variable space and ;; 136 bytes for the OFSTRUCT structure; ;; that, together, would make 148 bytes. enter 148, 0 ;; Display a message box with msg as the message. push dword 0 push dword the_title push dword msg push dword 0 call [MessageBoxA] ;; Open a file for writing or for creating. lea ebx, [ebp-148] ;; The address of the OFSTRUCT structure. push dword 1 | 0x1000 ;; OF_WRITE | OF_CREATE push ebx push dword filename1 call [OpenFile] mov dword [ebp-4], eax ;; Save the file handle. ;; Write the message to the file. lea ebx, [ebp-8] ;; The number of bytes written will be saved there. push dword 0 push ebx push dword msg_stop - msg ;; Size of the message. push dword msg ;; The address of the message. push dword [ebp-4] ;; The handle to use for writing to file. call [WriteFile] ;; At this point, if we wanted to, we could check whether the number of ;; bytes actually written ([ebp-8]) equals the number of bytes intended ;; to write. If not equal, then some error must have occurred. ;; Close the file handle, because we're done using the file. push dword [ebp-4] ;; The file handle. call [CloseHandle] ;; Open another file, for reading-only this time. lea ebx, [ebp-148] ;; The OFSTRUCT structure. push dword 0 ;; OF_READ push ebx ;; The pointer to our OFSTRUCT structure. push dword filename2 ;; The name of the file to open. call [OpenFile] mov dword [ebp-4], eax ;; And we save the file handle. cmp eax, -1 ;; Check to see if the return is HFILE_ERROR (-1). jz .e1 ;; If equal - meaning OpenFile() failed - go to e1. ;; Get the size of the file. push dword 0 push dword [ebp-4] call [GetFileSize] mov dword [ebp-8], eax ;; Save the size of the file. ;; Check to make sure that the size of the file doesn't exceed the size of our buffer. cmp eax, 1024 jng .over1 ;; If the file size exceeds our buffer size, we'll either need to fail, or ;; we'll need to only read part of the file. But if you do fail in cases ;; like this, don't forget to close the file before exiting. mov dword [ebp-8], 1024 ;; We'll change that value to the size of our buffer. .over1: ;; We read the text from the file to our buffer. lea ebx, [ebp-12] ;; Where the number of bytes actually read would be saved. push dword 0 push ebx push dword [ebp-8] push dword buffer push dword [ebp-4] call [ReadFile] ;; We're done using the file, for the day, so we'll close the file handle. push dword [ebp-4] call [CloseHandle] ;; We'll NULL-terminate the data that we just read, just in case. mov eax, dword [ebp-12] ;; The number of bytes actually read. add eax, buffer ;; + the address of the buffer. mov ebx, eax ;; = the byte right after the buffer. mov byte [ebx], 0 ;; We don't actually have to; the data section should be 0-initialized, ;; so that when we read data to the data section, there should be NULL ;; right after the last byte of the data we just read. ;; Now we display a message box with the newly-read data. push dword 0 push dword the_title push dword buffer push dword 0 call [MessageBoxA] ;; We return 0. xor eax, eax leave ret .e1: ;; We still need to take care of error 1. ;; Display a message box with the error1 error message. push dword 0 push dword 0 ;; If you leave this parameter blank, a default title ;; of "Error" will be used. push dword msg_error1 push dword 0 call [MessageBoxA] ;; And we don't really have any open file handles or anything, so ;; we can exit now. ;; We'll return 1, to say that there was an error. mov eax, 1 leave ret section .data the_title db "File Incbin Program", 0 msg: incbin "msg.txt" msg_stop db 0 filename1 db "FileIncbin.txt", 0 filename2 db "msg2.txt", 0 msg_error1 db "Could not open file msg2.txt", 0 section .bss buffer resb 1024 padding_byte resb 1
Hello World! .....
Oh, hello again. So, did you write more programs? Well, anyway, gotta go now.
Example Program - The Output
You should get two message boxes for the output. The first one should contain the text from msg.txt and the second one should contain the text from msg2.txt; there should also appear another file in the folder the .exe is in; the file should have the same text as the first message box.
The text in the first message box is set by NASM when it assembles the program. Our program then prepares the text for and displays the second message box.
Here's what the output might look like:
Intro To Win32 Assembly, Using NASM
Application of Mouse and Keyboard Input
Using Memory Allocation and Alphabet Algorithm
OpenFile: OpenFile Function (Windows)
CreateFile: CreateFile Function (Windows)
SECURITY_ATTRIBUTES: SECURITY_ATTRIBUTES Structure (Windows)
SetFilePointer: SetFilePointer Function (Windows)
GetFileSize: GetFileSize Function (Windows)
Edited by RhetoricalRuvim, 20 August 2011 - 07:00 PM.