Introduction to MASM32
ASM is machine language that your computer reads with all computer programs. Every program that is compiled from any language such as C++ or Delphi is then converted to PC ASM through your compiler. PC ASM is what your CPU reads and it is quite difficult to read; however, with MASM32 you can code ASM in a similar fashion to C++ but without the complexity and hassle of PC ASM that your processor reads.
In ASM the PC has registers which are segments of memory that your CPU uses to handle memory. There is also stack data, and of course hard disk and RAM memory. The advantage of ASM is that it can work directly with memory and is considered the fastest type of code as long as it is coded correctly. By contrast, it also makes it a very difficult language, but we will try and simplify the process in this MASM32 Tutorial.
We won't delve into too much detail about MASM's internal workings and how ASM is interpreted by the computer, because the complexity can confuse many people. The basic understanding is that ASM commands are converted into simple electronic signals by your CPU that handle memory.
If you want to try out the examples in this tutorial, I recommend downloading RadAsm from http://www.radasm.com/ . Download the IDE pack, install it, and then download the Programming pack and install that too. Before you try and compile download the MASM32 compiler at the top of the page. Install MASM32 compiler, and then adjust your RadAsm settings so that RadAsm knows the locations of the MASM32 directories.
Here's the basic skeleton of a MASM program:
// the model of your program will usually be flat, stdcall
// There is stdcall syntax and C syntax of summoning functions
// the stdcall syntax means you call your program arguments from
// RIGHT to LEFT rather than LEFT to RIGHT which is the C syntax.
// This is your initialized variables that you will use.
// The format is: NameOfVariable db "Message",0
// the comma 0 is the null terminator indicating end of the message.
// This is your uninitialized variables that you will use.
// Constant variables example: BUFFER_SIZE equ 1024
// Your code and everything goes in here.
eax -> This is the standard register that you will mostly be using.
ecx -> This is a counter register which is used in loops.
; This is a copy function, it copies from:
; Either memory to register
; register to memory
; register to register
; IT CANNOT COPY FROM MEMORY TO MEMORY. so don't try mov hInst, hInst2
; because they are both variables.
; example: mov eax,hInst ;copies the value of hInst to the register eax.
; Calls a function with arguments
; calls a function you have to push the arguments beforehand.
; This pushes/adds the object into the stack
; This pops the object from the stack
; increments register
; decrements register
Two ways to call a function:
Standard Old way:
push offset ClassName
push offset AppName
; This is a C syntax call from Right to Left.
; Reverse the order to work with stdcall.
; But this is messy and not a great method.
invoke MessageBox,0,offset ClassName,offset Appname,MB_OK
; This is great! So how do we get the return value of this??
; Once you invoke this function the return value is stored in eax.
; mov RetValue,eax
; Now RetValue has the returned data, which you can check:
We just discussed a few ways to use ASM. Of course, there is many more properties, methods, and functions that exist in ASM, but these are the most common methods.
First Windows Program
Next we'll discuss how to create your first Windows Program in MASM32. This is going to look quite complex at first, but I assure you, it is extremely simple and I will explain each line. Do not be afraid of the length and size of the code, it will all make sense once you read through this tutorial. I chose this program, because it is quite a simple program, and yet has enough examples of every ASM concept.
The following program will kill the process notepad.exe if it is running. You can change it to whatever program by editing the db variables.
.586 is a later processor, but you can experiment trying to be compatible to older processors such as .386 or .486 instruction sets.
We included the windows library for windows functions, and various windows libraries that also have specific functions. The .inc extension is similar to .h in C++, which is a header or include file. The .lib extension indicates library files.
Your WinMain function
We create the prototype WinMain, which isn't defined yet, we just want to let the program know that we will be using the WinMain function later in the program. WinMain is a specific function that the Windows Operating System uses for most of your code.
ClassName db "MainWinClass",0
AppName db "Main Window",0
ProcessName db "notepad.exe",0
started db "KillProcess",0
startedtext db "KillProcess has started!",0
quittext db "KillProcess is suiciding!",0
successtext db "KillProcess has succeeded in killing notepad.exe!",0
failedtext db "KillProcess has failed and brought shame to our family!",0
hInstance HINSTANCE ?
CommandLine LPSTR ?
We express our data here. Usually these are constants and texts that we will be using throughout the program. The .data? variables are undeclared global (the whole program can see them) variables that we will use.
invoke GetModuleHandle, NULL
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
We start our main code, and we invoke or launch some standard windows functions and add the returned values to hInstance and CommandLine.
Shutting Off a Win32 Process
mov pe32.dwSize,SIZEOF PROCESSENTRY32
We declare some variables in our KillProcess process function. Which has an argument called lpszExecutable, of the type LPSTR (which is a type of pointer string in windows).
We declare some local variables using the keyword LOCAL, with types BOOL, PROCESSENTRY32, and HANDLE.
We use the mov command to move values of TRUE, or the SIZEOF PROCESSENTRY32 into their appropriate variables.
SIZEOF PROCESSENTRY32 is required because PROCESSENTRY32 is a structure and we want to declare the size of the structure we will be using in our functions. This is required by windows functions.
Many of the types and arguments you will see for each function are just peices of rules that Windows requires from the programmer in order to use certain functions. You can always look them up in MSDN, although you will have to convert them to ASM.
We invoke the function CreateToolhelp32Snapshot, with arguments TH32CS_SNAPPROCESS which is a definition number and 0. The value returned by CreateToolhelp32Snapshot is stored in eax register. So we mov the returned value into a variable we can use for later called hProcess and hProcesses.
Basic Task Manager Processes
invoke CompareString,LOCALE_USER_DEFAULT,NORM_IGNORECASE,ADDR pe32.szExeFile,-1,lpszExecutable,-1
invoke OpenProcess,PROCESS_TERMINATE,FALSE,ADDR pe32.th32ProcessID
invoke Process32Next,hProcesses,ADDR pe32
We invoke Process32First with the variable hProcesses, and we reference the pe32 structure we had started declaring. This is called a struct because it is a blob of data that contains many properties. Earlier we defined dwSize for pe32, since it now contains information we have to use a reference when showing it to other functions. We don't want to literally send the whole struct into the other function, we just want to reference it, so the function can see it and edit the variable/struct from inside the function.
After that, the code checks if eax exists, and then loops as long as bLoop is TRUE. We CompareString to find the program we are looking for, it continues to loop until it finds the program we're looking for in the process list, which is found from Process32 functions.
If the returned value of CompareString is 2, we call OpenProcess, and prepare to terminate it. If we can open this process, then we can go ahead and Terminate it.
If we successfully terminated the program we will equate bResult to true, so we will know later that we were successful.
After we end our if statements using endif, we use Process32Next to continue to the next Process. If there is a next process, we continue to loop using bLoop as true; if not, bLoop will be set as false and the loop will end.
We of course call CloseHandle to stop any memory leaks and clean up our mess. The method ret is used to indicate the end the KillProcess function.
Windows Process Handling
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.lpszClassName,OFFSET ClassName
invoke RegisterClassEx, addr wc
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
WinMain is a standard design of all Windows Main functions. The idea is to declare some information for the Windows Operating system, and register our program as a process in the operating system. We then show the window, and start the process loop that handles all incoming and outgoing messages of your program.
invoke MessageBox,0,OFFSET startedtext,OFFSET started,MB_OK
invoke KillProcess,OFFSET ProcessName
invoke MessageBox,0,OFFSET failedtext,OFFSET started,MB_OK
invoke MessageBox,0,OFFSET successtext,OFFSET started,MB_OK
.IF eax < 0
invoke MessageBox,0,OFFSET quittext,OFFSET started,MB_OK
The WndProc is a standard windows function which we use to handle all the messages that windows or other programs send us. If windows or a program, sends us the WM_DESTROY message, it means someone wants our program to end, and we should handle our shut down operations. If we receive WM_CREATE it means our program just started and we should create the GUI (Graphical User Interface) of the program if it has one.
Otherwise we just run the DefWindowProc (Default Window Process) if no message is being received.
In WM_CREATE we first define bResult as FALSE, so we know when it is true. We invoke a MessageBox to show the user and then launch our KillProcess function. We show a different message box on fail and on the success of KillProcess. We use GetAsyncKeyState,VK_ESCAPE and loop it to wait for the user to press his keyboard Escape key, if that is the case, we shut down our program by sending a message to ourselves called WM_DESTROY. We also show a small good bye message box too.
When you compile that program, you will understand it a little better. Of course, for many it will be confusing but playing around with the code will allow you to learn how ASM works and pretty soon you will understand it.
Also be advised, ASM is a very difficult language, so don't expect to understand it immediately, it may take some time to sink in.