Assembly Programming Journal 2
Assembly Programming Journal 2
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::. Dec 98/Jan 99
:::\_____\::::::::::. Issue 2
::::::::::::::::::::::.........................................................
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
[email protected]
T A B L E O F C O N T E N T S
----------------------------------------------------------------------
Introduction...................................................mammon_
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::..............................................INTRODUCTION
by mammon_
Wow! This issue is huge. More than twice the size of the last; maybe it is time
to go monthly...
This issue has as its theme --such as it were-- the use of popular free- and
shareware assemblers. It began with my needing to write a GAS intro to
accompany my X-Windows article; shortly thereafter, Linuxjr emailed me the
benefits of his university training with his A86 tutorial (beginners: this is
for you! Linuxjr explains *everything*). I then appealed to Gij to allow me to
incorporate his Nasm 'Quick-Start' guiide, which I have used often...he posted
the condition that I edit it heavily ;)
I would like to draw your attention first to our new column: Assembly Language
Snippets. Originally this was an idea which I and a few others had; however, I
never received any contributions for the 'Snippets' section. Then I received an
email from Troy with the first one... I pulled the rest out of my various asm
sources and voila, a new column was born. This is something that is fully open
to contributions; asm snippets --and we will need lots-- may be emailed to
[email protected] or [email protected], or they may be posted to the
Message Board at http://pluto.beseen.com/boardroom/q/19784/
Basic format should be:
;Name: Name to title you with
;Routine Title: Name to title the snippet with
;Summary: One-Line Description
;Comaptibility Specific Assemblers or OSes this works with
;Notes: Any extra notes you have
--Code--
I should point out here that freeservers.com is not very reliable; thus the
APJ home page is inaccessible more often than not. For this reason I have set
up a mirror on my own page, at http://www.eccentrica.org/Mammon/APJ/index.html
As for this issue's articles, we once again have two fine Win32 asm tutorials
by Iczelion, who maintains an excellent page at http://iczelion.cjb.net (with
a Win32 asm message board!). Ghirribizzo has supplied his fun Key Generator
Competition results (I can't say I was surprised when I saw the winner ;).
As for the issue challenge, XBios2 did not provide me with one for next issue,
so I used one from a text I found on the Internet somewhere... he has been
emailed the text and can try to beat it ;) Also, I am going to be setting up a
page for reader responses to the Issue Challenges -- readers can anticipate the
solutions before each issue comes out, or try and best the solution afterwards.
Submissions can be sent to the same places as the Snippets.
Author Bio's? I know mainstream mags do this-- if you want one, send one. I'll
tack it onto the end of the article ... anything within reason: URL, email,
hobbies, perversions, favorite drink, favorite linux distro, etc.
Next Issue: How many articles on Code Optimization can I get? That would make a
great theme (with the foundation laid this issue)--anything from code theory to
PentiumII-specific optimizations would be welcome. Prospective articles, send
to me or post on the MB...no topic is unacceptable unless you can in no way
possible relate it to assembly language.
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
Keygen Coding Competition
by Ghiribizzo
Introduction
------------
The competition was to write the smallest key generator for the simple serial
scheme I wrote as a trainer for newbies. I had a few reasons for starting this
competition:
To give the newbies a chance to participate in a competition
To give old hands the chance to brush up on their assembly skills
To promote tight assembly coding
To demonstrate the various different methods used to improve efficiency in
coding
Well, I'm back from my short European jaunt and the competition is now closed.
I have greatly enjoyed the entire competition, from the coding of the crackme
and the chats with various crackers on IRC through to deciding the winner and
writing this document.
The rules of the competition required that some standard interface text be
included which strongly urged the use of service 9 interrupt 21h - though this
would probably be used in any case - and discouraged blank screens and other
unfriendly UIs from being used to save bytes. Also, the rules specified a range
of input to be handled smaller than the possible 256 maximum. Due to the simple
nature of the serial scheme, this meant that the lookup table could immediately
be stripped down to the input range.
The Entrants
------------
The following entrants have been included because they illustrate the different
ideas and methods used to reach the common goal of reducing code size. I didn't
realise that so many crackers would use the precomputed table method. Perhaps
word got out during IRC chats and everybody started using them? In any case,
this didnt reduce the size cutting war as precomputation had its own routines
that needed to be optimised.
From the size I got from this keygen, I tried to guess a required key input
range to put this size between thestraight table precomputation and the packed
table precomputation.
One thing to note is how I ended the program. I was quite surprised by the fact
that nobody else seemed to know that you could quit com programs with a ret
instruction. Further size savings can be made by using Bbs trick of keeping DH
and also by tweaking the generator to fix some of the bitstreams produced to
give us the bits we need and save later processing.
The coding is very simple - almost seems as if Cruehead was typing the steps
going through his head straight onto the keyboard (perhaps he was?) the
resulting code is consequently very easy to understand and follow.
Honourable Mentions
-------------------
Special mention given to Trykka who managed to deduce how the look-up table was
created - but never sent in an entry!
The Winner
----------
Well it looks like Spyder is the winner by quite a large margin. Incidentally,
I have just made a quick check that the keygens work. You might be able to bump
yourself up on the scale by picking holes in the other keygens :-)
Rankings
--------
__Keygen______Size________Author______
kgen.com 211 Spyder
kg.com 224 Ghiribizzo (alpha)
kg10.com 230 Bb
kg9.com 233 Bb
kg6.com 239 Bb
kgvoid.com 247 VoidLord
kgcrue.com 255 Cruehead (alpha)
kg5.com 256 Bb
kgt.com 529 Serial Scheme
Final Words
-----------
There have been some excellent ideas in the keygens. However, none of the
keygens are as small as they could be. They all have some scope for improvement.
By combining some of the ideas given in the above keygens, we could create a
new smaller keygen. It will be interesting to see what the smallest possible
keygen would look like.
I hope that everyone who has taken part in the competition, or who has followed
it, has gained something from it. I hope that there will be more entries for
the next competition!
; Spyders Keygen==============================================================
; Ghiribizzo's Key Generator Competition entry by Spyder
; Sheesh you get assembler source and you want comments?
; Only one nibble of each byte in the original key table holds useful
; information. Only key table entries in the range 20..0x7F and 0x0D are
; needed - those 60 nibbles are packed into a 30 byte table, 0x0D is handled
; as a special case.
; The rest is just space concious assembler with a few wrinkles to save
; bytes. I worry I may have missed some pattern in the key table, could it
; be generated or derived? Otherwise I'm pretty happy with the result.
.286
seg000 segment byte public 'CODE'
assume cs:seg000
org 100h
assume es:nothing, ss:nothing, ds:seg000
public start
start proc near
mov ah, 9
mov dx, offset StartMsg
int 21h ; Sign on
mov ah, 0Ah
mov dx, offset Buffer
int 21h ; Get name
mov si, offset BufferCont ; Set up for loop
mov di, offset Serial
mov bx, offset Key - 10h
xor ax,ax
mov cx,10h
loop1:
lodsb
; cmp al,0dh ; don't need this because we arranged the data
; jnz skip0 ; before the key table to give the right code
; mov al,'p' ; for this out of range case
skip0:
sar al,1
xlat
jc skip1
sar al,4
skip1:
and al,0fh
add al,'0'
cmp al,'9'
jle skip2
add al,7
skip2:
stosb
loop loop1
movsw
movsb
mov ah,9
mov dx,offset SerialMsg
int 21h
int 20h
start endp
Buffer db 11h ;
db 0 ;
BufferCont db 'm'
db 'k'
db '3'
db ' '
db '"'
db '!'
db '['
db ']'
db 'n'
db 's'
db ')'
db '%'
db '3'
db 'x'
db '#'
db '0'
db 0dh, 0ah, '$'
StartMsg db 0dh,0ah,'OCU Keggen #1 ',0feh,' ----- spyder ----- 1998',0dh,0ah
db 0dh,0ah,'Enter Name : $'
db 0 ; A crucial spacer
Key db 075h, 041h, 062h, 0FDh, 0FDh, 0D1h, 055h, 085h
db 06Fh, 02Eh, 060h, 019h, 034h, 00Fh, 01Bh, 0D0h
db 0C6h, 0C5h, 09Eh, 093h, 06Ch, 075h, 06Dh, 0E6h
db 02Dh, 017h, 090h, 01Bh, 0FCh, 042h, 015h, 074h
db 0D2h, 022h, 00Ch, 040h, 0DDh, 039h, 0DCh, 086h
db 018h, 0A7h, 04Fh, 0EAh, 06Dh, 0CAh, 0A9h, 0C7h
SerialMsg db 0dh,0ah,'Serial Number: '
Serial:
seg000 ends
end start
; VoidLords Keygen============================================================
; OCU Keygen #1 | VoidLord 1998
; Category: newbie (this is my first keygen)
; Solution:
; for the every possible input char (20h-7fh) the "serial" char is stored in the
; Table. Since the output chars can only be 0-9 and A-F, we can store two chars
; in one byte, reducing the table size to 48 bytes.
seg000 segment byte public 'CODE'
assume cs:seg000
org 100h
assume es:nothing, ss:nothing, ds:seg000
start proc near
mov ah, 9 ; DOS - Write starting message
lea dx, StartMsg
int 21h
mov ah, 0ah ; DOS - read Name
lea dx, Serial
int 21h
xor ax, ax
xor bx, bx
loop1:
mov al, [Serial2+bx] ; the output will be in the same buffer
cmp al, 0dh ; end of input string (odh) ?
jne no_cr
mov [Serial2+bx], '1' ; the output char will be '1'
jmp finish ; the remaining chars are OK already
no_cr:
push bx ; now we should translate the namechar
sub al, 20h ; to the serial number char, using the
mov bx,ax ; lookup Table
shr bx,1 ; we have two chars in one byte in the Table
and al, 1
jnz odd ; is this char "even or odd" ?
mov al, [Table1+bx]
and al, 0fh ; if even, use the lower 4 bits
jmp end_l
odd:
mov al, [Table1+bx]
mov cl, 4
shr al, cl ; if odd, use the higher 4 bits
end_l:
pop bx ; translate the number to the hex char
cmp al, 10 ; is it digit 0-9 or letter A-F
jl digit
add al, 7 ; if letter, add 7
digit:
add al, '0' ; if digit, just add '0'
mov [Serial2+bx], al
inc bx ; process next input char
cmp bx, 10h
jl loop1
finish:
mov Serial, ':' ; complete the output string
mov Serial+1,' '
mov ah, 9 ; DOS - Print solution
lea dx, SerialMsg
int 21h
mov ah, 4Ch ; DOS - QUIT with EXIT
int 21h
start endp
StartMsg db 0dh,0ah,'OCU Keygen #1 ',0feh
db ' ----- VoidLord ----- 1998'
db 0dh, 0ah, 0dh,0ah,'Enter Name : $'
SerialMsg db 0dh,0ah,'Serial Number'
Serial db 11h, 0
Serial2 db 67, 57, 69, 55, 52, 53, 50, 53, 56, 55
db 68, 50, 69, 54, 49, 54, 0dh, 0ah, '$'
Table1 db 87, 20, 38, 223, 223, 29, 85, 88, 246, 226
db 6, 145, 67, 240, 177, 13, 108, 92, 233, 57
db 198, 87, 214, 110, 210, 113, 9, 177, 207, 36
db 81, 71, 45, 34, 192, 4, 221, 147, 205, 104
db 129, 122, 244, 174, 214, 172, 154, 124
seg000 ends
end start
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
How to Use A86 for Beginners
by Linuxjr
Requirements:
-Basic Dos knowledge like copying and renaming files and such
I am writing this paper for I find plenty of tutorials and books all about
assembly and how to write programs and how to do loops, if/else statments,
etc... But one thing I did not see plenty of is tutorials on how to set up the
assembler of choice that you grow fond of, for instance nasm, a86, tasm, masm
GAS, etc.
So I am writing about a86 and I'm using my college notes and experience I
learned from my Assembly class. I hope this will help you enjoy a86 and
encourage you to learn how to manage up to x286 opcodes and 16-bit code
before you start tackling with 32bit and Windows programming in assembler.
This is a sort of warning that you will only be able to write DOS programs
but you have to learn how to crawl before you can walk, and you have to learn
how to walk before you can run. I hope to show you how to set up a86, how
to write a few simple programs with the template I use, and how to do some
basic stuff in assembler.
I took a college course on Assembly a couple of months ago, and I was happy to
learn the internals of the system and how to manipulate the registers for some
awesome results. The assembler that we used was a86 by Eric Isaacson. This
is a shareware program, meaning you get to play with it before buying it. To
get this assembler go to - http://www.eji.com/a86/ - and you will see where to
download the programs. It is in a zip file and you just unzip it with your
favorite program like winzip or pkunzip. You should also download d86, the
debugger, for use with your a86 programs. Once you downloaded them, unzip the
files to a directory such as c:\a86, or even put on a floppy disk if you are
worried about space.
Getting Started
---------------
Let's get into it: you've got the assembler and the debugger, what next? First
of all, we have to make a text file since all asm source code is nothing but a
plain text code that has a bunch of operands and functions to do what you want
your program to do.
; PROGRAM :
;
; AUTHOR :
;
; PURPOSE :
;
; PROGRAM OUTLINE :
;
;============================== EXTERN ===============================
;=========================== STACK SEGMENT ===========================
sseg segment para stack 'STACK'
dseg ends
Now we have a template to use, and this is just one out of many templates you
can make for your assembly programs. Now let's begin to have fun. These few
programs will get us going for a basic feel of how to set up a basic hello
program.
The file template.asm is that a template which contains all the necessary
pieces of a program, except for the actual program itself. Make a copy of the
template.asm file, and name it something appropriate: message.asm is a good
choice. The file extension must be ASM. You will edit the new file to create
your first program. DO NOT EDIT template.asm itself!!!! You will use this
template file as the start of your assembly programs so it should not be
alterated(until you get advanced enough to play around with it ;-).
We will be using EDIT in a dos box as our editor, though you can use notepad or
Ultraedit to edit your assembly files as well.
The Comments
All of the progras that you will write should have a descriptive set of header
comments at the top. Any text AFTER a semicolon is considered a comment. The
top of your new program file should already have the basic outline for this
comment. Edit in your message.asm file to have something like this :
This is just an example to help you know what you want to do, and to have a
reference if you were to walk away from a project for a year or so...the header
will make a nice reminder of what you were trying to get this program to do.
dseg ends
Change this part of the code so that the message to be printed is defined.
The result will look like:
dseg ends
The HEX values are the two-byte sequence for a DOS newline(CR-LF). The first
characters of "0DH" and "0AH" is ZERO, no capital O. Note that there is NO
semicolon before "message". Do not allow this part to break over two lines.
THE Code -
Now locate the part of the code where the program code goes. It should look
like this:
;========================Main Program================================
;
program proc far ; Actual program code is completely
; contained in the FAR procedure
; named PROGRAM
exit:
mov ax,4c00h ; terminate program execution and
int 21h ; transfer control to DOS
All of the code for your program Should REPLACE the comment:
"Rest of Main Program code goes here".
Here is the code you will use to print out the message:
This code just calls the DOS Interrupt used to print strings to the screen.
Interrupt 21H is a general starting point for many useful DOS calls. The
sub-function used to print strings is Function 09H; this value must be loaded
into the AH register before calling. Also, Int 21H Function 09H requires the
address of the message be placed in the DX register. The above code performs
these two initialization tasks, and then calls the interrupt.
Take careful note of the semicolons which start the comments. Also, do not
alter any of the other part of the code.
These were the only two changes you needed to make.
:------------------------------ASM86-----------------------------------
@echo off
REM This is a simple batch file to use a86 and link:
if exist %1.asm GOTO FOUND
echo %0 ERROR : %1.asm -- FILE NOT FOUND
echo Usage: %0 file [link-file]
GOTO STOP
: FOUND
:-- Assemble the program
echo a86 +O +S +E %1.asm
a86 +O +S +E %1.asm
::-- IF THERE WAS AN ERROR, STOP
IF ERRORLEVEL 1 GOTO STOP
::-- IF there is a second file name, assume it is a OBJ file,
::-- and link it to the %1 name.
IF X%2 == X GOTO ELSELINK
ECHO link %1+%2;
link %1+%2;
GOTO ENDIFLINK
:ELSELINK
echo link %1;
link %1;
:ENDIFLINK
:STOP
All this does is 1) create an object file (+O), 2) suppress the creation of
the symbol table .sym(+S), and 3) copy the errors to a the filename.err instead
of writing in your file(+E).
If there were any errors, you will have to edit the asm file to fix them. The
error messages displayed by the assembler should indicate the line number and
cause of the problem. Since you are just copying pregenerated code, any errors
will simply be typos.
Once all of the errors have been corrected, a pair of files will have been
created. The will have the same base name as the original asm file, but will
have different extensions:
OBJ- Object file. Contains the basic machine code, but does not have any
references to external procedures. This is, effectively, an intermediate file
which is used by the linker to produce the final executable file.
To run the program just type Message and you will see the line appear on the
screen.
This was a simple Hello program. What you probably want is another example or
two to try out, and that is what we shall do. The next Program that won't be as
long but will have plenty of info.
CharLoop Program
----------------
In this part, you will create a simple program that asks the user to enter a
character, and prints it out again. It does this repeatedly, until the user
hits the ESC key. Dos funtions 01H and 02H are introduced with this program,
and it is the first program containing a comparison loop.
Create two messages by adding the following lines to the Data Segment part of
the program (see the message program instructions, if you don't remember how
to do this):
Now add the code which will put the following "pseudocode" into effect:
Repeat
prompt for and read a character
Print the character back out with a message
While the character read is not esc
char_loop:
;Print the prompt
mov dx, offset prompt
mov ah, 09H
int 21H
;Read a character into AL
mov ah, 01H ;(01H - with echo; 08H - no echo)
int 21H
mov bl, al ;save character in BL
;Print the final message
mov dx, offset outmsg
mov ah, 09H
int 21H
;Write the character to the screen
mov dl, bl ;put character in dl
mov ah, 02H
int 21H
;Loop back, only if the character was not esc (1BH)
cmp bl, 1BH
jne char_loop
;End Repeat
Note how the two new DOS interrupts are called. The Function number is always
placed in AH before calling, and the INT 21H instruction is used to invoke the
interrupt. For Function 1H, which reads a character to the screen, the DL
register must be initialized with the appropiate value.
Note also that the character must be stored somewhere throughout the whole
loop, and it can NOT be stored either AL or DL -- AL is modified by Function
02H, and DL is modified when DX is set to the address of teh strings. So BL is
used to store the character, and the value must be transferred between AL, BL
and DL during processing. This kind of juggling happens often in assembly
programming. Get this program running to watch another good program going ;-).
Function 08H works exactly the same as Function 01H, except for this echo
feature: Function 08H does NOT echo the character after reading it.
Create a new program which is exactly the same as CHARLOOP, except it should
use Function 08H to read the characters, instead of Function 01H. Write and
run the program to see how it works.
NumLoop Program
---------------
This program will work in a similar fashion to the Charloop program above, but
it will read and print numbers. Since there is no DOS interrupt to convert
ASCII characters to numbers, your code will have to do this. Fortunately,
there are already procedures to do this. A few extra steps must be taken to
use them, but it will be much easier than writing the code from scratch. See
the info about DOSIOLT for details on how to use thes procedures.
DOSIOLT Procedures
Here is a description of the DOSIOLT procedures:
inhex16
This procedure reads a HEX number in character format from the standard input,
and converts it to a word. Spaces or Tabs may precede or follow then number.
DOS int 21H-0AH is used to read the input string, so it must be terminated by a
RETURN. Both upper and lower case letters A-F may be used. If the number typed
is larger than FFFH, the upper bits are lost. If anything unpredictable is
typed(like non-HEX chars) the function will return junk.
Inputs: None
Outputs: AX- the word-sized number read.
Modifies: AX, flags
outhex16
This simple routine prints the four 'nibbles' of AX as ASCII digits.
Four digits are always printed.
Input: AX- the number to be printed
Outputs: None
Modifies: Flags
outHex8
This simple routine prints the two 'nibbles' of AL as ASCII digits. Two digits
are always Printed.
Input: AL-the number to be printed
OUTPUT: NoneModifies: Flags
Call
Each of these procedures is invoked with the CALL instruction. Any
inputs(registers) must be initialized before the call; any outputs(also
registers) are set by the procedure, and contain the appropriate value after
the call.
Extern
Since the code for these functions does NOT appear in your ASM file, two
special steps must be taken in creating your executable file. The first is to
declare the names of the procedures as external procedures. This informs the
assembler that the code has been written elsewhere, and you didn't just forget
to write it.
The extern declaration should come someplace early in the ASM file. Although
it doesn't matter greatly where it goes, most programmers will put these
declarations outside of all of the segments. The template file given has a
spot for externals, marked with a commment.
A86 USERS: The A86 Assembler uses the older version of the extern
declaration, which is spelled extrn. If you are using the a86
assembler(asm86.bat), make sure you spell the name of the instruction
extrn.
procedure_name is the name of the procedure that you will use in the program.
The name only needs to be declared once in this way, no matter how many times
it is used. But if two or more DOSIOLT procedures are to be used, each must
have a separate declaration.
You should NOT place these extern declarations in your code unless you are
actually using the routines. The linker may place the code for the procedure
in your final executable even if it is never called.
LINKING
A special step must be taken in linking (the second half of the compilation
phase done by asm86.bat) to link the code in DOSIOLT. Fortunately, asm86.bat
can handle the extra file fairly automatically. Just include the DOSIOLT on
the command line, after your asm file name.
Example: assuming you have written a program in a file called "calc.asm" which
contains calls to the DOSIOLT procedures. To assemble and link the program:
A:\> asm86 calc dosiolt
If you get an "Undefined Symbol" error, it is because you mistyped, or
forgot, the extern declarations for the DOSIOLT procedures. Make sure
these are correct.
This program will illustrate the use of two of the DOSIOLT procedures, and also
the use of variables, rather than registers, as places to store information.
The outline of the program is as follows:
Loop forever
Prompt for, and read a number into the variable NUMBER
IF number = 0, then break out of the loop
print Number, with an appropriate announcement.
End Loop
Your program will need a prompt string, a response string and a word-sized
variable in the Data Segment:
Number has been declared as a word-sized variable, with no initial value. The
Code can now use the name "Number" just like a register name( in most cases).
number_loop:
;Print the first prompt
mov dx, offset prompt1 mov
ah, 09H
int 21H
Note that the inhex16 reads a number into AX, and outhex16 prints the number
AX, yet this code went through all the trouble of storing the number in the
variable, rather than just leaving it in AX throughout the loop. WHY?!?
Because AX was needed in between the reading and printing of the code. Again,
this kind of juggling between registers and variables occurs often in assembly
programming.
Since two DOSIOLT procedures are being used, they must be declared. At the top
you will find the EXTERN part of your program template; add these lines to the
section:
;===============================Extern======================================
extrn inhex16:far
extrn outhex16:far
Those are all the changes needed.
Don't forget to include the DOSIOLT file on the command line when compiling,
which will be --- asm86 numloop dosiolt
I do apologize for the length of this but I got to excited when I was messing
with these old files and playing with these procedures in dosiolt.obj file.
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
Using the Gnu AS Assembler
by mammon_
GAS is the GNU project port of the Unix AS assembler; it is available as part
of the binutils package which is included with any of the GNU compilers (for
example, GCC). GAS support is built into the various GNU compilers, and so GAS
can be invoked by invoking the compiler on a .S (asm source) file; however it
can also be run on any source file (for example, .asm files) by using the 'as'
command.
The sections of the most interest in the manual will be the Directives
('g Pseudo Ops'), Symbols ('g Symbols'), Constants ('g Constants'), and
Sections ('g Sections') nodes. For more immediate references, the Intel 386-
specific topics can be consulted: 'g i386-Syntax', 'g i386-Opcodes',
'g i386-Regs', 'g i386-prefixes', 'g i386-Memory', 'g i386-jumps'.
Registers
One of the most obvious differences in syntax is that the registers in the AT&T
syntax are prefixed with %. Thus, 'eax ax al ah' would be written '%eax %ax %al
%ah' for GAS.
Memory Referencing
This is the part that is most likely to cause trouble for those used to the
Intel syntax. Intel uses the following syntax for memory references:
SECTION:[BASE + INDEX*SCALE + DISP]
where BASE is the register used as a base in the reference, INDEX is a register
used to calculate an offset, SCALE is the multiplier used to calculate the
offset from the INDEX register, and DISP is the displacement from the BASE or
INDEX register. Some examples from the GAS manual:
[ebp - 4] [BASE DISP] (Note: DISP is -4)
[foo + eax*4] [DISP + INDEX*SCALE]
[foo] [DISP] (Value pointed to by 'foo')
gs:foo SECTION:DISP (Contents of variable 'foo')
AT&T syntax uses the following syntax for memoory references:
SECTION:DISP(BASE, INDEX, SCALE)
As with the Intel syntax, all of these are optional (and it appears that BASE
and INDEX are rarely used together). The GAS manual provides the following
examples equivalent to the above Intel examples:
-4(%ebp) DISP(BASE)
foo(,%eax,4) DISP(,INDEX,SCALE)
foo(,1) DISP(,SCALE) (Note: the single comma is intentional)
%gs:foo SECTION:DISP
Note that you must provide commas within the parentheses whenever you skip an
element (e.g., if you do not use BASE).
To illustrate, here are some examples of memory references mixed in with asm
opcodes (from http://www.castle.net/~avly/djasm.html):
__AT&T______________________ __Intel_________________________
movl 4(%ebp), %eax mov eax, [ebp+4])
addl (%eax,%eax,4), %ecx add ecx, [eax + eax*4])
movb $4, %fs:(%eax) mov fs:eax, 4
movl _array(,%eax,4), %eax mov eax, [4*eax + array])
movw _array(%ebx,%eax,4), %cx mov cx, [ebx + 4*eax + array])
Directives
----------
GAS allows most of the standard assembler directives; what follows are the most
commonly used.
.align
Pad the section to a specified alignment (e.g. 4 bytes); this directive takes
as an argument the alignment sized, as well as an optional argument specifying
the byte used to fill the pad areas (default is 00).
.equ, .set
Each of these sets the first argument (a symbol) with the result of the second
argument (an expression), for example
.equ TRUE 1
sets the Symbol TRUE to the value 1.
.extern
The traditional EXTERN directive is available but ignored; GAS treats all
undefined symbols as externs.
.global, .globl
These directives define global (exported) symbols; each takes as an argument
the symbol to be made global.
.if /.endif
GAS provides the usual IF...ENDIF directives for conditional assembly; the .if
directive is followed by an expression, and all code between the .if and the
.endif directive is assembled only if that expression returns non-zero.
.include
This directive includes a file at the current location; it takes as an argument
the name of the file in quotes, for example
.include "stdio.inc"
Assembling a Program
--------------------
A GAS program can ge assembled by invoking GCC with the O2 (optimize: level 2)
option. Note that all GAS programs must have a .text section and a global
"main" label.
The Int80 "pid.asm" program from last month's Liux article would be written for
GAS as follows:
;pid.S====================================================================
.global main
.text
szText1:
.asciz "Getting Current Process ID..."
szDone:
.asciz "Done!"
szError:
.asciz "Error in int 80!"
szOutput:
.string "%d\n"
main:
pushl $szText1
call puts
popl %ecx
mov $20, %eax
int $128
cmp $0, %eax
je Error
pushl %eax
pushl $szOutput
call printf
popl %ecx
popl %ecx
pushl $szDone
call puts
jmp Exit
Error:
pushl $szError
call puts
Exit:
popl %ecx
ret
; EOF ====================================================================
This can be compiled in the same manner as the previous example; note, though,
the need to use decimal numbers when calling interrupts (the 0x?? syntax for
specifying a hexadecimal integer causes the opcode to not be recognized by the
assembler).
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
A Guide to NASM for TASM Coders
by Gij
Generalities
------------
The basic function of any assembler it to turn asm into the equivalent binary
code file; that's true for TASM, NASM, and any other assembler.
The differences arise in the special features each assembler offers you. For
example, the MODEL directive exists in TASM, making it easier for the coder to
reference data variables in other segments. NASM does not have an equivalent
directive, so you have to keep track of the segment registers yourself, and put
segment overrides where they are needed. This does not mean that NASM doesn't
have good SEGMENT or GROUP support; in fact it has both, though they are not
quite the same as in TASM.
It's a different way of coding, and it may seem to require more work, but after
you get used to it it's easier, because you know exactly what's going on in
your code. NASM actually gives you the closest possible idea of what your asm
source code will become once it's compiled.
NASM is also less ambivalent towards syntax, which leaves less room for
software bugs, but makes it more strict when assembling. I actually think NASM
is easier to learn then TASM since it's much more straight-forward.
Your NASM Bible is of course the accompanying docs, you can get them in a
separate package from the same place you got the binaries for NASM. All in all
I think you will find NASM to be just as capable as TASM if not more so.
Although it's missing some features TASM has, you can always mail the author
and ask for a feature, and you just might get lucky when the new version comes out.
ASM code is usually the same in any assembler ( AT&T syntax is an exception )
but there are a few subtleties that TASM coders should look out for. The docs
that accompany NASM have a nice list of them, and I'll mention the most
significant ones here.
NASM on the other hand only supports one way of loading a simple offset into a
register (the LEA form is only valid when using complex offsets):
mov esi, MyVar
This ALWAYS means move the offest of MyVar into esi.
However, using LEA to load a complex offset is valid in both TASM and NASM:
lea edi,[esi*4+EBX] ; valid in both assemblers
Segment Overrides
-----------------
TASM is more lax in it's syntax, so both of these are valid code:
mov ax,ds:[si]
AND
mov ax,[ds:si]
NASM doesn't allow this--if you specify a variable inside the square brackets
all of the specifiers should be inside the square brackets.
So this is the only valid option:
mov ax,[ds:si]
NASM allows these size keywords in many places, and thus gives you a lot of
control over the generated opcodes in a uniform way. For example, the following
are all valid:
push dword 123
jmp [ds: word 1234] ; these both specify the size of the offset
jmp [ds: dword 1234] ; for tricky code when interfacing 32bit and
; 16bit segments
It can get pretty hairy with operand size being this final, but the important
thing to remember is you can have all the control you need, when you want it.
Functions
---------
TASM has special directives for declaring a procedure and ending it. Why?
A procedure is just another code label you CALL instead of JMP--NASM got it
right.
TASM uses:
ProcName PROC
xor ax,ax
ret
ProcName ENDP
Local Labels
------------
Those of you that know C also know that a member of a struct can be referenced
as StructInstance.MemberName. This is rather similar to the way NASM allows
you to use local labels. A Local Label is denoted by prefixing a dot to the
label name:
Label1:
nop
.local:
nop
Label2:
nop
.local:
nop
This won't give you an error on multiple definitions of label, but you can
still jmp to a certain label like this:
jmp Label2.local
...so it's local, and in a way it's also a global label.
ORG Directive
--------------
NASM supports the org directive, so if you are coding a COM file you can start
with:
org 0x100h
OR
org 100h
(NASM allows both the asm and c methods of specifying hex, so both of the
above are valid.)
Reserving Space
---------------
Once again, here NASM uses a different syntax then that of TASM.
In TASM you would declare a 100 bytes of uninitialized space like this:
Array1: db 100 dup (?)
NASM uses its own keywords to do this; these are RESB, RESW and RESD,
standing for REServeByte, REServeWord, and REServeDword, respectively.
To reserve 10 bytes, you would use RES? keywords like this:
Array1: RESB 100
OR
Array1: RESW 100/2
OR
Array1: RESD 100/4
Declaring initialized space is much like TASM, but arrays are different.
In TASM:
Array1: db 100 dup (1)
In NASM:
Array1: TIMES 100 db 1
Making Structs
--------------
I fought long and hard to get structs going, the docs were a bit vague, and
it took a while to get it, here it is.
Using a struct is divided into 2 parts, declaring the prototype, and making an
instance. A simple, 2-member structure would be defined as follows:
struc st
stLong resd 1
stWord resw 1
endstruc
this declares a prototype struct named st, with 2 members, stLong which is a
DWORD, and stWord which is a word. It uses the reserve directives because it's
a prototype, not a real struct. You can use istruc to make a real instance that
you can reference as data in your code:
mystruc:
istruc st
at stLong, dd 1
at stWord, dw 1
iend
*Note: it's important to put the label on a different line.
This creates a struct named mystruc of type st; the "at" keyword is used to
assign initial values to the members of the struc (i.e., at the reserverd bytes
of memory).
The notation for referencing members is not like in C. This is because of the
way structures are implemented; in NASM, each member is assigned an offset
relative to the beginning of the struct:
mystruc:
istruc st
at stLong, dd 1 ; offset 0
at stWord, dw 1 ; offset 4
iend
This is because mystruc is a constant base, and the member is a relative offset
to it. It's similar to referencing a data array.
One thing I should mention: If you declare structs prototypes as above, the
member names/labels will be global, so you will get collisions if you use the
same member name in your code or in another struct prototype. To avoid this,
precede the member names with a dot '.', and then reference them in relation to
the prototype's name in the instance declaration. For example:
struc st
.stLong resd 1
.stWord resw 1
endstruc
mystruc:
istruc st
at st.stLong, dd 1
at st.stWord, dw 1
iend
This may seem confusing; you should understand that "mystruc" is the base of a
particular instance, and "st.stLong" is an offset relative to the start of the
struct, so in pseudo-code it translates into:
mov eax,[offset mystruc + (offset stWord-offset start_of_proto]
or
mov eax,[offset mystruc + 4]
...which gives you the correct offset for the stWord member of the "mystruc"
struct instance.
Using Macros
------------
This is a large part of the nasm docs, and a bit too much to get into in depth
here. I'll try and cover the major issues.
There are 2 types of macros, one-line and multi-line, all macro keywords are
preceeded with a '%' character.
This becomes:
mov eax, ( 2 * ( 3 * 4 ) )
These are not very useful examples, but i'm sure you can see the potential.
Multi-Line macros are much the same as single-line macros, but the syntax
is a bit different:
%macro name number_of_args
<body of macro>
%endmacro
So, for example, if you wanted to make a small asm effort-saver you could write
the following macro:
%macro prologue 1
push ebp
mov ebp,esp
sub esp,%1
%endmacro
...and then you can use it in your code like this:
DemoFunc:
prologue 4*2
<body of function>
This would set up a stack frame and reserve room for 2 DWORD local variables.
You'll notice that args supplied to the macro can be referenced as %1....%n,
similar to DOS and Unix shell/batch programming.
This is just a quick taste, there's more to be learned about NASM macros: the
docs are your friends.
Includes
--------
Including files is easy, If you want to include .inc's into your asm file
you can use:
%include "win32.inc"
If you wish to include binary files, you must use a different keyword:
INCBIN "data.bin"
Conditional Assembly
--------------------
NASM also has support for conditional assembly:
%define INCLUDE_WIN32_INCS
%ifdef INCLUDE_WIN32_INCS
%include "win32.inc"
%include "toolhelp.inc"
%include "messages.inc"
%endif
This way you can control the inclusion of files defining on the command line:
"nasmw -dINCLUDE_WIN32_INC"
or by commenting out the %define line. The body of the %ifdef will be processed
only if a macro/define named INCLUDE_WIN32_INCS is defined.
There are 3 types of symbols in NASM: EXTERN, GLOBAL and COMMON. Their
invocation is all the same:
EXTERN symbol_name ; use this to define API calls for use
GLOBAL symbol_name
COMMON symbol_name
They all must appear before the actual symbol is defined/referenced. If you
have experience in asm/c, their use should be clear -- EXTERN declares an
external reference ofr the linker to resolve (an "import"), GLOBAL declares a
symbol to be globally/publicly available (an "export"), and COMMON declares a
variable to be of Common data type (i.e., all instances of a COMMON variable
are merged into a single instance during compilation).
NASM 0.97 also has IMPORT/EXPORT extensions to the .obj format, for writing
DLL's; read the docs for more info.
Output Formats
--------------
Nasm supports a plethora of output formats; depending on what you are trying
to accomplish, you should read the docs for special extensions to each type.
The output format is chosen using "nasm -f type" on the command line, where
type can be bin, obj, win32 and others.
Each linker likes different formats--tlink likes obj for example, while
LCC-WIN32 likes the win32 format...investigate on your own to find the best
output format for your linker.
*tip: when assembling into the "obj" type, make sure and use the special
"..start:" symbol to specify the entry point for the file.
In Closing
----------
That's all for now. This is intended to be a 'quick-start' guide for TASM
coders who want --or need-- to move into NASM; it is not a substitute for the
NASM documentation. If you need to reach me, my e-mail is gij <at> bigfoot.com
Enjoy NASM!
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
Tips on Saving Bytes in ASM Programs
by Larry Hammick
In all the illustrations, we assume that 16-bit code segments are involved. The
syntax we use is that of MASM 5.1; the difference from other assemblers is
slight.
or, equivalently:
OutputHandle equ word ptr ds:[Go]
InputHandle equ word ptr ds:[Go+2].
Allocating file and memory space just for uninitialized variables wastes a few
bytes here and there. Much worse, for file size, is to put whole buffers and
stacks in the file:
ReadBuffer db 1000h dup (0)
Stack db 40h dup ("--Stack!--")
Examine a few commercial programs under a hex editor or debugger to see how
common this practice is. Worldwide, the quantity of disk space thus wasted must
be astronomical. Moreover, such "data" gets copied from disk every time the
program is loaded, even though it has no meaning! Perhaps assemblers and
linkers will someday be smart enough to avoid this. For now, we do have EXE
packers such as PKLite to compress blank data blocks, but the latter can be
avoided entirely as follows.
If the program will be small enough for the code and all data to fit in one
segment, it is desirable to have CS=DS. Then you can do:
ReadBuffer equ offset EndOfCode
WriteBuffer equ ReadBuffer+BufferSize
Go:
... ;code instructions
mov ah,4Ch
int 21h ;exit
EndOfCode label byte
END Go
This practice is not quite safe for a COM program, because DOS will load a COM
file into less than 64K if no larger block is available or if memory is
fragmented. For an EXE, the EXE header can be adjusted to prevent the program
from loading into too little memory.
You will be able to load or save both variables with one instruction:
mov dx,CursorPosition
Another benefit:
and CursorPosition,0FF00h
jnz NotAtTop
The AND instruction sets one byte and tests another, at the same time.
Consider:
mov cx,MsgSize ;(1)
...
Msg db "Hello",0Dh,0Ah
MsgSize equ $-offset Msg
MsgSize is a constant word. But MASM doen't know that when it assembles the
instruction (1). So it provides 3 bytes for MsgSize, and later fills in the
constant word followed by a NOP byte. One solution:
db 0B9h ;opcode for mov cx,immed
dw MsgSize
...
Msg db "Hello",0Dh,0Ah
MsgSize equ $-offset Msg
4.3 JCXZ
This instruction does not require a preliminary flag-setting instruction. So,
you might prefer
xchg ax,cx
jcxz Mylabel
to
or ax,ax
jz MyLabel,
saving one byte. Be aware that JCXZ is a relatively slow opcode.
...
call MySub
jc Ret1
...
Ret1: ret
Better is:
Mysub: cmp al,3
ja DontRet
...
ret
DontRet: pop ax ;discard return address into some unneeded register
ret
...
call MySub ;returns only if input is okay
...
In the above example, the routine ErrExit writes an ASCIIZ string from CS:SI,
then exits.
The offset of a jump table can sometimes be passed in the same way.
call SmartJump ;does not return
db 3
dw Handle3 ;Handle3 and Handle7 are near code addresses
db 7
dw Handle7
db 0 ;terminator for the table
If this occurs:
cmp al,5
jne Not5
jmp CantRun
Not5:
...
jmp CantRun
...
and CantRun is not reachable by a short jump in either instance, you might
still save a byte like so:
cmp al,5
jne Not5
JmpCantRun: jmp CantRun
Not5:
...
jmp short JmpCantRun ;2-step jump
...
Instead (5 bytes):
mov si,StringSite
mov di,si.
Another illustration:
MyByte db 11h
...
mov MyByte,0 ;a 5-byte instruction
mov MyByte,bh ;4 bytes, and equivalent if bh is known to be 0
mov MyByte,al ;only 3 bytes.
The label ErrExit can be reached by JMP's from several points in the program.
Before jumping, the code pokes in a suitable value of ReturnCode, depending on
the type of error condition encountered. The above example uses part of the
instruction MOV AX,4Cxxh as a variable, saving bytes.
write:
ErrExit2: mov al,2
db 3Dh ;opcode for CMP AX,immed, to disable the following
8. Miscellaneous byte-savers.
----------------------------
Since the instruction sets of the x86 CPU's are so elaborate, there are many
more ad hoc ways to reduce, reuse, and recycle bytes. The following are only a
few.
cmp VideoMode,7
je BlackAndWhite
mov dx,0B800h
jmp short Either
BlackAndWhite: mov dx, 0B000h
Either:
...
The above code wastes bytes. Better is:
mov dx, 0B800h
cmp VideoMode,7
jne GotVideoBase
mov dh,0B0h
GotVideoBase:
...
The improved version has one jump instruction instead of two, and in this
example saves an additional byte by resetting only DH, not DX.
With the Pentium, Intel introduced a useful set of conditional mov's right into
the instruction set.
8.3 To test the high bit of a register, avoid the constants 80h and 8000h.
For example,
test dh,80h
jnz MyLabel
is 5 bytes, but
or dh,dh
js MyLabel
is 4. The latter instruction also leaves more information in the flags.
TEST DH,DH or AND DH,DH have the same effect as OR DH,DH.
8.4 To determine if several variables of the same size are all 0, OR them
together, and the zero flag will tell you. To determine if they are all -1,
AND them together and increment the result.
9. Postlude
-----------
Intel makes their excellent CPU documentation available free, from:
http://developer.intel.com/design/litcentr/index.htm
It is in Adobe PDF format; you will need the Acrobat Reader, also free, from:
http://www.adobe.com/prodindex/acrobat/readstep.html
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::................................WIN32.ASSEMBLY.PROGRAMMING
A Simple Window
by Iczelion
Preliminary:
Windows programs rely heavily on API functions for their GUI. This approach
benefits both users and programmers. For users, they don't have to learn how to
navigate the GUI of each new programs, the GUI of Windows programs are alike.
For programmers, the GUI codes are already there,tested, and ready for use. The
downside for programmers is the increased complexity involved. In order to
create or manipulate any GUI objects such as windows, menu or icons,
programmers must follow a strict recipe. But that can be overcome by modular
programming or OOP paradigm.
I'll outline the steps required to create a window on the desktop below:
As you can see, the structure of a Windows program is rather complex compared
to a DOS program. But the world of Windows is drastically different from the
world of DOS. Windows programs must be able to coexist peacefully with each
other. They must follow stricter rules. You, as a programmer, must also be more
strict with your programming style and habit.
Content:
Below is the source code of our simple window program. Before jumping into the
gory details of Win32 ASM programming, I'll point out some fine points which'll
ease your programming.
You should put all Windows constants, structures and function prototypes in an
include file and include it at the beginning of your .asm file. It'll save you
a lot of effort and avoid typing errors. Most of the time, you can use include
file from some Win32 asm examples. I have used windows.inc from Steve Gibson's
Small Is Beautiful exampleand made some additions of my own.
Use IncludeLib directive to specify the import library used in your program.
For example, if your program calls MessageBoxA, you should put the line:
IncludeLib user32.lib
at the beginning of your .asm file. This directive tells MASM that your program
will make usesof functions in that import library. If your program calls
functions in more than one library, just add an includelib for each library you
use. Using IncludeLib directive, you don't have to worry about import libraries
at link time. You can use the /LIBPATH linker switch to tell Link where all the
libs are.
Use makefile to automate your assembling process. This will save you a lot of
typing.
; =============================================================================
include windows.inc ; .386 and .model are already
declared in windows.inc
includelib user32.lib ; calls to functions in
user32.lib and kernel32.lib
includelib kernel32.lib
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,CmdShow ; display our window on desktop
invoke UpdateWindow, hwnd ; refresh the client area
end start
You may be taken aback that a simple Windows program requires so much coding.
But most of these codes are just *template* codes that you can copy from one
source code to another. Or, if you prefer, you could assemble some of these
codes into a library to be used as prologue and epilogue codes. You can write
only the codes in WinMain function. In fact, this is what C compilers do. They
let you write WinMain codes without worrying about other housekeeping chores.
The only catch is that you must have a function named WinMain else C compilers
will not be able to combine your codes with the prologue and epilogue. You do
not have such restriction with assembly language. You can use any function name
instead of WinMain or no function at all.
Prepare yourself. This is going to be a long, long tutorial. Let's analyze this
program to death!
include windows.inc
includelib user32.lib
includelib kernel32.lib
Next are several macros that its author (Steve Gibson) frequently uses. The
remaining of the file contains important structures, constants and function
prototypes. Please note that windows.inc does not contain all structures,
constants, and function prototypes of Windows. It just holds the most
frequently used ones. You can add in new items if they are not in the file.
This is the approach of MASM. TASM 's way of import library linking is much
more simpler: just link to one and only one file: import32.lib.
.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.CODE
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
.....
end start
.CODE contains all your instructions. Your codes must reside between <starting
label>: and end <starting label>. The name of the label is unimportant. You can
name it anything you like so long as it doesn't violate the naming convention
of MASM.
Upon return from a Win32 function, the function return value, if any, can be
found in eax. All other values are returned through variables passed in the
function parameter list you defined for the call.
A Win32 function that you call will always preserve the segment registers and
the ebx, edi, esi and ebp registers. Conversely, ecx and edx are considered
scratch registers and are always undefined upon return from a Win32 function.
The bottom line is that: when calling an API function, expect return value in
eax. If any of your function will be called by Windows, you must also play by
the rule: preserve and restore the values of the segment registers, ebx, edi,
esi and ebp upon function return else your program will crash very shortly.
Next is the WinMain() call. Here it receives four parameters: the instance
handle of our program, the instance handle of the previous instance of our
program, the command line and window state at first appearance. Under Win32,
there's NO previous instance. Each program is alone in its address space, so
the value of hPrevInst is always 0. This is a lefover from the day of Win16.
Note: You don't have to declare the function name as WinMain. In fact, you have
complete freedom in this regard. You don't have to use any WinMain-equivalent
function at all. You can paste the codes in WinMain next to GetCommandLine and
your program will still be able to function perfectly.
Upon return from WinMain, eax is filled with exit code. We pass that exit code
as parameter to ExitProcess which terminates our application.
The above line is the function declaration of WinMain. Note the parameter:type
pairs that follow PROC directive. They are parameters that WinMain receives
from the caller. You can refer to these parameters by name instead of by stack
manipulation. In addition, MASM will generate the prologue and epilogue codes
for the function. So we don't have to concern ourselves with stack frame on
function enter and exit.
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
LOCAL directive allocates memory from the stack for local variables used in the
function. The LOCAL directive is immediately followed by <the name of local
variable>:<variable type>.
So LOCAL wc:WNDCLASSEX tells MASM to allocate memory from the stack the size of
WNDCLASSEX structure for the variable named wc. We can refer to wc in our codes
without any difficulty involved in stack manipulation. That's really a godsend,
I think. The downside is that local variables cannot be used outside the
function they're created and will be automatically destroyed when the function
returns to the caller. Another drawback is that you cannot initialize local
variables automatically because they're just stack memory allocated dynamically
on function start. You have to manually assign them with desired values after
LOCAL directives.
The inimidating lines above are really simple in concept. It just takes several
lines of instruction to accomplish. The concept behind all these lines is
window class. A window class is nothing more than a blueprint or specification
of a window. It defines several important characteristics of a window such as
its icon, its cursor, the function responsible for it, its color etc. You
create a window from a window class. This is some sort of object oriented
concept. If you want to create more than one window with the same character-
istics, it stands to reason to store all these characteristics in only one
place and refer to them when needed. This scheme will save lots of memory by
avoiding duplication of information.
Remember, Windows is designed in the past when memory chips are prohibitive and
most computers have 1 MB of memory. Windows must be very efficient in using the
scarce memory resource. The point is: if you define your own window, you must
fill the desired characteristics of your window in a WNDCLASS or WNDCLASSEX
structure and call RegisterClass or RegisterClassEx before you're able to
create your window. You only have to register the window class once for each
window type you want to create a window from.
Windows have several predefined Window classes, such as button and edit box.
For these windows (or controls), you don't have to register a window class,
just call CreateWindowEx with the predefined class name.
The single most important member in the WNDCLASSEX is lpfnWndProc. lpfn stands
for long pointer to function. Under Win32, there's no "near" or "far" pointer,
just pointer because of the new FLAT memory model. But this is again a lefover
from the day of Win16. Each window class must be associated with a function
called window procedure. The window procedure is responsible for message
handling of all windows created from the associated window class.
cbSize: The size of WNDCLASSEX structure in bytes. We can use SIZEOF operator
to get the value.
style: The style of windows created from this class. You can combine several
styles together using "or" operator.
cbWndExtra: Specifies the number of extra bytes to allocate following the window
instance. The operating system initializes the bytes to zero. If an application
uses the WNDCLASS structure to register a dialog box created by using the CLASS
directive in the resource file, it must set this member to DLGWINDOWEXTRA.
lpszMenuName: Default menu handle for windows created from the class.
hIconSm: Handle to a small icon that is associated with the window class. If
this member is NULL, the system searches the icon resource specified by the
hIcon member for an icon of the appropriate size to use as the small icon.
invoke CreateWindowEx, NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL
After registering the window class, we can call CreateWindowEx to create our
window based on the submitted window class. Notice that there're 12 parameters
to this function. C function prototype of CreateWindowEx is below:
HWND
WINAPI
CreateWindowExA(
DWORD dwExStyle,
LPCSTR lpClassName,
LPCSTR lpWindowName,
DWORD dwStyle,
int X,
int Y,
int nWidth,
int nHeight,
HWND hWndParent ,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam);
lpWindowName: Address of the ASCIIZ string containing the name of the window.
It'll be shown on the title bar of the window. If this parameter is NULL, the
title bar of the window will be blank.
dwStyle: Styles of the window. You can specify the appearance of the window
here. Passing NULL is ok but the window will have no system menu box, no
minimize-maximize buttons, and no close-window button. The window would not be
of much use at all. You will need to press Alt+F4 to close it. The most common
window style is WS_OVERLAPPEDWINDOW. A window style is only a bit flag. Thus
you can combine several window styles by "or" operator to achieve the desired
appearance of the window. WS_OVERLAPPEDWINDOW style is actually a combination
of the most common window styles by this method.
X,Y: The coordinate of the upper left corner of the window. Normally this
values should be CW_USEDEFAULT, that is, you want Windows to decide for you
where to put the window on the desktop.
nWidth, nHeight: The width and height of the window in pixels. You can also use
CW_USEDEFAULT to let Windows choose the appropriate width and height for you.
hWndParent: A handle to the window's parent window (if exists). This parameter
tells Windows whether this window is a child (subordinate) of some other window
and, if it is, which window is the parent. Note that this is not the parent-
child relationship of multiple document interface (MDI). Child windows are not
bound to the client area of the parent window. This relationship is
specifically for Windows internal use. If the parent window is destroyed, all
child windows will be destroyed automatically. It's really that simple. Since
in our example, there's only one window, we specify this parameter as NULL.
hMenu: A handle to the window's menu. NULL if the class menu is to be used.
Look back at the a member of WNDCLASSEX structure, lpszMenuName. lpszMenuName
specifies *default* menu for the window class. Every window created from this
window class will have the same menu by default. Unless you specify an
*overriding* menu for a specific window via its hMenu parameter. hMenu is
actually a dual-purpose parameter. In case the window you want to create
is of a predefined window type (ie. control), such control cannot own a menu.
hMenu is used as that control's ID instead. Windows can decide whether hMenu is
really a menu handle or a control ID by looking at lpClassName parameter. If
it's the name of a predefined window class, hMenu is a control ID. If it's not,
then it's a handle to the window's menu.
hInstance: The instance handle for the program module creating the window.
mov hwnd,eax
invoke ShowWindow, hwnd,CmdShow
invoke UpdateWindow, hwnd
After successful return from CreateWindowEx, the window handle is stored in eax.
We must keep this value for future use. The window we just created is not
automatically displayed. You must call ShowWindow with the window handle and
the desired *display state* of the window to make it display on the screen.
Next you can call UpdateWindow to order your window to repaint its client area.
This function is useful when you want to update the content of the client area.
You can omit this call though.
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
At this time, our window is up on the screen. But it cannot receive input from
the world. So we have to *inform* it of relevant events. We accomplish this
with a message loop. There's only one message loop for each module. This
message loop continually checks for messages from Windows with GetMessage call.
GetMessage passes a pointer to a MSG structure to Windows. This MSG structure
will be filled with information about the message that Windows want to send to
a window in the module. GetMessage function will not return until there's a
message for a window in the module. During that time, Windows can give control
to other programs. This is what forms the cooperative multitasking scheme of
Win16 platform. GetMessage returns FALSE if WM_QUIT message is received which,
in the message loop, will terminate the loop and exit the program.
mov eax,msg.wParam
ret
WinMain endp
If the message loop terminates, the exit code is stored in wParam member of the
MSG structure. You can store this exit code into eax to return it to Windows.
At the present time, Windows do not make use of the return value, but it's
better to be on the safe side and plays by the rule.
This is our window procedure. You don't have to name it WndProc. The first
parameter, hWnd, is the window handle of the window that the message is destined.
uMsg is the message. Note that uMsg is not a MSG structure. It's just a number,
really. Windows define hundreds of messages, most of which your programs will
not be interested in. Windows will send an appropriate message to a window in
case something relevant to that window happens. Thew indow procedure receives
the message and react to it intelligently. wParam and lParam are just extra
parameters for use by some message. Some message does send accompanying data in
addition to the message itself. Those data are passed to the window procedure
by means of lParam and wParam.
mov eax,uMsg
.IF eax==WM_DESTROY
invoke PostQuitMessage,NULL
xor eax,eax
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
.ENDIF
ret
WndProc endp
Here comes the crucial part. This is where most of your program's intelligence
resides. The code that responds to each Windows message are in the window
procedure. Your code must check the Windows message to see if it's a message
it's interested in. If it is, do anything you want to do in response to that
message and then return with zero in eax. If it's not, you MUST pass ALL
parameters for default processing by DefWindowProc. This DefWindowProc is an
API function that processes the messages your program is not interested in.
The only message that you MUST respond to is WM_DESTROY. This message is sent
to your window procedure whenever your window is closed. At the time your
window procedure receives this message, your window is removed from the screen.
This is just a notification that your window is now destroyed, you should
prepare yourself to return to Windows. In response to this, you can perform
housekeeping prior to return to Windows. You have no choice but to quit when it
comes to this state. If you want to have a chance to stop the user from closing
your window, you should process WM_CLOSE message. Now back to WM_DESTROY, after
performing housekeeping chores, you must call PostQuitMessage which will post
WM_QUIT back to your module. WM_QUIT will make GetMessage return with zero
value in eax, which in turn, terminates the message loop and quits to Windows.
You can send WM_DESTROY message to your own window procedure by calling
DestroyWindow function.
[Reprinted With permission from Iczelion's Win32 Assembly HomePage]
http://203.148.211.201/iczelion/index.html
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::................................WIN32.ASSEMBLY.PROGRAMMING
Painting with Text
by Iczelion
In this tutorial, we will learn how to "paint" text in the client area of a
window. We'll also learn about device context.
Preliminary
-----------
Text in Windows is a type of GUI object. Each character is composed of
numerous pixels that are lumped together into a distinct pattern. That's why
it's called "painting" instead of "writing". Normally, you paint text in your
own client area (actually, you can paint outside client area but that's another
story).
Before you can paint something on the client area, you must ask for permission
from Windows. That's right, you don't have absolute control of the screen as
you were in DOS. You must ask Windows for permission to paint your own client
area. Windows will determine the size of your client area, font, colors and
other GDI attributes and send a handle to device context back to your program.
You can then use the device context as a passport to painting on your client
area.
Some of the values in the device context are graphic attributes such as colors,
font etc. These are default values which you can change at will. They exist to
help reduce the load from having to specify these attributes in every GDI
function calls.
Windows posts WM_PAINT messages to a window to notify that it's now time to
repaint its client area. Windows does not save the content of client area of a
window. Instead, when a situation occurs that warrants a repaint of client
area (such as when a window was covered by another and is just brought back in
front), Windows put WM_PAINT message in that window's message queue. It's the
responsibility of that window to repaint its own client area. You must gather
all information about how to repaint your client area in the WM_PAINT section
of your window procedure, so the window procudure can repaint the client area
when WM_PAINT message arrives.
Another concept you must come to terms with is the invalid rectangle. Windows
defines an invalid rectangle as the smallest rectangular area in the client
area that needs to be repainted. When Windows detects an invalid rectangle in
the client area of a window , it posts WM_PAINT message to that window. In
response to WM_PAINT message, the window can obtain a paintstruct structure
which contains, among others, the coordinate of the invalid rectangle.
You call BeginPaint in response to WM_PAINT message to validate the invalid
rectangle. If you don't process WM_PAINT message, at the very least you must
call DefWindowProc or ValidateRect to validate the invalid rectangle else
Windows will repeatedly send you WM_PAINT message.
Note that you don't have to explicitly validate the invalid rectangle. It's
automatically done by the BeginPaint call. Between BeginPaint-Endpaint pair,
you can call any GDI functions to paint your client area. Nearly everyone of
them requires a handle to device context as a parameter.
Content:
We will write a program that display a text string "Win32 assembly is great
and easy!" in the center of the client area.
include windows.inc
includelib user32.lib
includelib kernel32.lib
.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
OurText db "Win32 assembly is great and easy!",0
.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.CODE
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
The majority of the code is the same as the example in tutorial 3. I'll explain
only the important changes.
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL rect:RECT
These are local variables that are used by GDI functions in our WM_PAINT
section. hdc is used to store the handle to device context returned from
BeginPaint call. ps is a PAINTSTRUCT structure. Normally you don't use the
values in ps. It's passed to BeginPaint function and Windows fills it with
appropriate values. You then pass ps to EndPaint function when you finish
painting the client area. rect is a RECT structure defined as follows:
RECT Struct
left LONG ?
top LONG ?
right LONG ?
bottom LONG ?
RECT ends
Left and top are the coordinates of the upper left corner of a rectangle Right
and bottom are the coordinates of the lower right corner. One thing to remember:
The origin of the x-y axes is at the upper left corner of the client area. So
the point y=10 is BELOW the point y=0.
In response to WM_PAINT message, you call BeginPaint with handle to the window
you want to paint and an uninitialized PAINTSTRUCT structure as parameters.
After successful call, eax contains the handle to device context. Next you call
GetClientRect to retrieve the dimension of the client area. The dimension is
returned in rect variable which you pass to DrawText as one of its parameter.
DrawText's syntax is:
DrawText is a high-level text output API function. It handles some gory details
such as word wrap, centering etc. so you could concentrate on the string you
want to paint. Its low-level brother, TextOut, will be examined in the next
tutorial. DrawText formats a text string to fit within the bounds of a
rectangle. It uses the currently selected font,color and background (in the
device context) to draw the text.Lines are wrapped to fit within the bounds of
the rectangle. It returns the height of the output text in device units, in our
case, pixels. Let's see its parameters:
uFormat The value that specifies how the string is displayed in the
rectangle. We use three values combined by "or" operator:
DT_SINGLELINE specifies a single line of text
DT_CENTER centers the text horizontally.
DT_VCENTER centers the text vertically. Must be used with
DT_SINGLELINE.
After you finish painting the client area, you must call EndPaint function to
release the handle to device context.
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::........................THE.C.STANDARD.LIBRARY.IN.ASSEMBLY
The _Xprintf functions
by Xbios2
I. INTRODUCTION
---------------
This is the second article I write on the C standard library, and perhaps some
ask: "Why should this interest us?" or, more gently, "What's the philosophy
behind these articles?". Well, here is why I write these articles:
- For C programmers that want to know what happens behind the HLL 'curtain'
- For asm programmers who wish to get ideas
- For asm programmers who need a C command but want to keep their code 'slim'
(actually the code section is intended more as source to compile and use than
source to read and understand, that's why it's not always well-commented in
a tutorial-like manner)
- For me, to better understand reverse-engineering and assembly coding.
The _Xprintf functions call the ___vprinter function, with four parameters:
1. output function address
2. output function parameter
3. pointer to format string
4. pointer to arguments list
Functions that send output to a file or to STDOUT also lock/unlock the stream.
Besides that, all the 'dirty job' is passed to ___vprinter.
This way to perform output has the advantage of printing long strings without
allocating much memory, while printing small strings using the output function
only once. Actually this is the only advantage it has. Even if this solution
was written well (which is _not_), it would still be awful in _sprintf and
_vsprintf. In _(v)sprintf chars are written in the local buffer first, then,
when this fills up, the second function (_writestring) is called, which calls
a third function (included in the same .OBJ file with _sprintf) which finally
calls _memcpy. With careful re-writing of sprintf, this could be achieved just
by a simple, one-byte 'stosb'. Then printf and fprintf could be implemented
atop sprintf. The problem here is that those functions should 'know' how much
buffer space to allocate. Maybe the solution to this could be to leave
allocating buffers to the user, by just giving a sprintf function (actually
Microsoft thought this before me, and they give only wsprintf and wvsprintf
in the Win32 API).
This article will actually focus on a vsprintf function, with all the format
specifiers in Borland C (EXCEPT floating point numbers, which would (and maybe
will) require a separate article. Also keep in mind that UNIX has a rather more
complicated Xprintf set, which I'm glad to ignore :)
- A self-contained procedure
That is, there is only a _sprintf function, which calls nothing, while
_sprintf involves: ___vprinter, ___longtoa, ___strlen, plus three other
functions called by ___vprinter (_storechar, _writestring and another one
that converts pointers into hex)
- Much smaller code
- Much less stack used
- Probably faster code (actually it is not a speed-optimized version, but yet
it must be much faster)
- It's home-made, and brand-new :)
; sprintf.asm ============================================================
.386
.model flat
.data
Null db '(null)',0
align 4
jumptable dd offset BlankOrPlus ; 0
dd offset HashSign ; 1
dd offset Asterisk ; 2
dd offset MinusSign ; 3
dd offset Dot ; 4
dd offset Digit ; 5
dd offset h_shortint ; 6
dd offset d_decimal ; 7
dd offset o_octal ; 8
dd offset u_unsigned ; 9
dd offset x_Hexadecimal ; 10
dd offset p_pointer ; 11
dd offset unknown ; 12 = f_floating
dd offset c_char ; 13
dd offset s_string ; 14
dd offset n_CharsWritten ; 15
dd offset formatLoop ; 16 = Ignore character
dd offset unknown ; 17 = Unknown char
dd offset Percent ; 18
.code
_vsprintf proc C near uses ebx edi esi, a_output:dword, a_format:dword, \
a_argList:dword
test ebx, 16
jnz short nchars_short
cmp ecx, 2
jge short asterisk_prec
test eax, eax
jge short width_positive
neg eax
or ebx, 2
;------------------
; we come here if width=0n
mov ecx, [v_width]
sub ecx, eax ; EAX=[v_strLen]
jle short skipzerolen
mov eax, dword ptr [v_sign]
or al, al
jz short setzerolen
dec ecx
shr eax, 8
jz short setzerolen
dec ecx
js short skipzerolen
setzerolen: mov [v_zeroLen], ecx
; ---------------------------------------------------------------------------
; Pointer: same as %.8X
; ---------------------------------------------------------------------------
; we must arrive here with EDX pointing to the string to print
; and it's length in [v_strLen]
ends
end
; EOF ====================================================================
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::............................................THE.UNIX.WORLD
X-Windows in Assembly Language: Part I
by mammon_
The sensible way to write programs for X-Windows is to use a toolkit such as Xt
or Gtk; the easy way would be to use a scripting package such as Python or
Tcl/Tk. Modern assembly language coders, however, are known for sacrificing
ease and sensibility in the name of curiosity and execution speed; it is in
this spirit that the potential for programming X-Windows in assembly language
will now be investigated.
X-Windows Programming
---------------------
Like other GUIs, X-Windows uses an event-driven programming style in which an
application registers itself with the system, displays its main user interface,
and waits for system events signalling that the user has interacted with the
program. There are four main 'levels' of X-Windows Programming: XProtocol,
XLib, Xt or 'toolkit' programming, and scripting.
XProtocol
X-Windows consists of the X Server which handles graphics output, keyboard and
mouse input, event signalling, and commands sent from client programs (Window
Managers, applications). Clients communicate with the X Server using XProtocol,
which consists of byte streams exchanged between the client and the server --
in a sense, like the packets that a network client exchanges with a network
server. XProtocol is virtually useless for application programming, for the
coding overhead for each server request makes development impractical. The
details of XProtocol requests can be found in '/usr/include/X11/Xproto.h'.
XLib
The equivalent of the Win32 API in X-Windows is XLib. Even if one uses toolkits
for application coding, there is no way to escape XLib coding. XLib serves as
an interface between the client programs and the X Server; essentially, it is a
library of XProtocol functions exported for use by applications.
Xt
Toolkit programming is similar to using class libraries (like MFC, OWL, or VCL)
on the Win32 platform. There are a number of toolkits available, such as Qt,
Gtk, Xt Intrinsics, Athena, and the Motif toolkit. Each toolkit consists of
extensible widgets (like resources in Win32) that define basic window types:
buttons, scrollbars, dialogs, edit windows, etc.
Scripting
A wide variety of scripting languages are available for the Unix platorm, and
many of these have windowing toolkits that enable them to produce X-Windows
applications. The most popular are Tcl/Tk, Python, and Java; needless to say,
these programming methods may not be implemented in assembly language.
The simplest way to do this is to use the XLoadQueryFont and the WhitePixel and
BlackPixel macros:
mfontstruct = XLoadQueryFont( p_disp, "fixed");
WhitePix = WhitePixel( p_disp, DefaultScreen(p_disp));
BlackPix = BlackPixel( p_disp, DefaultScreen(p_disp));
Once again, the values are saved for later use. Note that a more complex method
of allocating colors will be used in the assembly code later; there, a handle
to the default X Windows colormap is obtained via a call to XDefaultColormap,
and XAllocNamedColor is used to allocate white and black pixel values: this
accomplishes the same as the above code, but without using the macros.
At this point, the procedure must be created for each child window (buttons,
scrollbars, etc); the following shows the creation of a button with its own GC,
and selection of the Exposure and ButtonPress event masks:
Exit = XCreateSimpleWindow(p_disp, Main, 15, 1, 60, 15, 1,
WhitePix, BlackPix);
XSelectInput(p_disp, Exit, ExposureMask | ButtonPressMask );
XMapWindow(p_disp, Exit);
exitGC = XCreateGC(p_disp, Exit,(GCFont | GCForeground | GCBackground),&gcv);
Note that a separate GC is not needed for each window if they will be sharing
the same background, foreground, and font colors.
Step IV : Event Loop
The event loop is the 'meat' of the program, where the application responds to
user events. This loop calls XNextEvent to get the next system event, and
responds to the ones sent to its windows. The following loop catches the Expose
event and draws text into each window using XDrawString on the initial exposure
of each window (xexpose.count ==0). In addition, when the Exit button is
pressed, the while loop exits and the application terminates.
while( !Done ){
XNextEvent(p_disp, &theEvent);
if( theEvent.xany.window == Main){
if( theEvent.type == Expose && theEvent.xexpose.count == 0){
XDrawString(p_disp, Main, theGC, 1, 40, msgtext, strlen(msgtext));
} }
if( theEvent.xany.window == Exit){
switch(theEvent.type){
case Expose:
if( theEvent.xexpose.count == 0){
XDrawString(p_disp, Exit, exitGC, 2, 11, extext, strlen(extext) );
}
break;
case ButtonPress:
Done = 1; } } }
Note that sll of the functions, structures, and messages used above are defined
in '/usr/include/X11/Xlib.h', './X11/Xutil.h' and './X11/X.h'.
The asm statements are passed directly to GAS, and thus they need to be in a
format that GAS will recognize. For this reason, multiline asm statements will
require a newline (and, optionally, a tab) after each statement, like so:
asm( "
statement1 \n
statement2 \n
statement3 \n
statement4"
: "g" (outvar)
: "g" (invar)
: eax, ebx, ecx
);
or, as I have used below:
asm( "statement1 \n\t"
"statement2 \n\t"
"statement3 \n\t"
"statement4 \n\t");
Other than that there are no real restrictions. Structures do not pass well
between C and GAS; if you need to reference specific structure variables from
inline assembly code, it is better to place those variables into temporary C
variables, whcih can then be accessed from the assembler block as normal. The
following demonstrates this:
fid = mfontstruct->fid;
asm( "
push fid\n
push mainGC\n
push p_disp\n
call XSetFont\n
add $12, %esp");
More information on the GCC inline assembler can be found at:
Avly's Programming Page (http://www.castle.net/~avly/djasm.html)
CodeX Software (http://www.gameprog.com/codex/tut/att_asm.html)
Brennan's DGPP Resources (http://brennan.home.ml.org/djgpp/) [Currently Down]
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::................................ASSEMBLY.LANGUAGE.SNIPPETS
IsASCII?
by Troy Benoist
ENUM
by mammon_
;Summary: A NASM macro emulating the C 'ENUM" command
;Assembler: NASM
%macro ENUM 2-* ;Usage: ENUM int SYMBOLS
%assign i %1 ; where int is the number to begin enumeration at [0]
%rep %0 ; SYMBOLS is a list of Symbols to define
%2 EQU 0xi ;Example: ENUM 0 TRUE FALSE
%assign i i+1 ; this EQUates TRUE to 0 and FALSE to 1
%rotate 1 ;Example: ENUM 11 JACK QUEEN KING
%endrep ; this EQUs JACK to 11, QUEEN to 12, KING to 13
%endmacro
CallTable
by mammon_
;Summary: Error Handler to demonstrate call-tables
;Compatibility:
;Notes: The EQUs define offsets from the start of ErrorHandler. Thus,
; ERROR_FILE_NOT_FOUND is at offset 0, ERROR_FILE_READ_ONLY is
; at offset 4 ( one dword from offset 0), etc.
; Each entry in the call table contains the address of the
; code label listed there...so, in order, ErrorHandler contains
; the addresses for the functions ERROR1, ERROR2, ERROR3, and
; ERROR4.
; The code to call an error handler uses as its base
; call [Errorhandler]
; or, call the function whose address is stored at location
; ErrorHandler. By adding the EQUs to this base, one gets the
; offset for each function within ErrorHandler.
ERROR_FILE_NOT_FOUND EQU 0
ERROR_FILE_READ_ONLY EQU 4
ERROR_DISK_FULL EQU 8
ERROR_UNKNOWN EQU 12
ErrorHandler:
;------------ Here lies the Call-Table
DWORD ERROR1
DWORD ERROR2
DWORD ERROR3
DWORD ERROR4
;------------ Here ends the Call-Table
;Handlers for various errors; offsets to these are stored in the Call-Table
ERROR1:
...Code to Create File...
ret
ERROR2:
...Code to CHMOD File...
ret
ERROR3:
...Code to Display Disk Full Message...
jmp Exit_Program
ERROR4:
...Code to Display Unknown System Error-Code...
jmp Exit_Program
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................ISSUE.CHALLENGE
PE Program Displays Its Command Line
by Xbios2
The Challenge
-------------
Write the smallest possible PE program (win32) that outputs it's command line.
The Solution
------------
This problem looks like the one about the 11-byte .COM program solved on the
previous issue. Yet the method used to solve it is entirely different. This is
because while .COM files include just raw code and data, the PE files include a
header with information on the file. It is this header that must be 'tweaked'
to get a small file.
3. This article was based on a 'trial and error' method. Some solutions exist
only because they work. So don't ask why... (Actually the trial and error
resulted in two BSODs, thus proving that a program can crash windows NT without
even running it's own code)
4. No, I'm not paranoid. I just like pushing things to their limit :)
extrn GetCommandLineA:proc
extrn GetStdHandle:proc
extrn WriteFile:proc
.data?
dummy db ?
.code
start:
call GetCommandLineA
xor ecx, ecx
push ecx
loop1: inc ecx
cmp byte ptr [eax+ecx], 0
jne short loop1
push esp
push ecx
push eax
push -11
call GetStdHandle
push eax
call WriteFile
ret
ends
end start
----------------------------------------------------
some comments on the code:
- the .data? section is present because I can't make TASM work without any data
- there is no ExitProcess. In it's place there is a simple 'ret'. This is
because the entry point is actually called by kernel32 with the following piece
of code:
This program compiles under TASM to 4 KB long. Those 4096 bytes are divided
like this:
It seems that TASM can't create anything smaller. So, the code will have to be
written by hand in a hex editor. Actually you don't have to worry, as you'll
only have to write 192 bytes for the final program (believe it or not!).
In order to shrink the file, the following steps must be taken: Remove Padding,
Use a Single Section, Remove the DOS Stub, Tweak the PE Header, Squeeze the
Code, Squeeze the Imports, and 'ReAssemble' the Program.
1. Remove padding
-----------------
By changing the 'FileAlignment' field in the PE header, all the padding can be
discarded. (Actually it seems that win95 won't allow this)
.code : code
.data : initialized and uninitialized data
.idata : imports
.reloc : relocation info
3. No DOS stub
--------------
All compilers that compile PE executables create a DOS stub that displays a
message like 'This program must be run under Win32'. Yet this is NOT required
by the PE format. What PE needs (as seen in [ntdll.dll]RtlImageNtHeader or
[imagehlp.dll]ImageNtHeader) is:
where ???? is the offset of the PE header from the beginning of the file
4. Tweaked PE header
--------------------
The PE header consists of the following structures:
IMAGE_NT_SIGNATURE: 00004550h
IMAGE_FILE_HEADER:
WORD Machine ; >> 014Ch for Intel 386
WORD NumberOfSections ; 1 for this example
DWORD TimeDateStamp ; *
DWORD PointerToSymbolTable ; *
DWORD NumberOfSymbols ; *
WORD SizeOfOptionalHeader ; >> 70h (Opt. header + directories)
WORD Characteristics ; >> 0102h for 32bit executable
IMAGE_OPTIONAL_HEADER:
WORD Magic ; 0B01h
BYTE MajorLinkerVersion ; *
BYTE MinorLinkerVersion ; *
DWORD SizeOfCode ; *
DWORD SizeOfInitializedData ; *
DWORD SizeOfUninitializedData ; *
DWORD AddressOfEntryPoint ; >> ???? RVA of entry point
DWORD BaseOfCode ; *
DWORD BaseOfData ; *
DWORD ImageBase ; >> 00100000h for this example
DWORD SectionAlignment ; 2
DWORD FileAlignment ; 2
WORD MajorOperatingSystemVersion ; *
WORD MinorOperatingSystemVersion ; *
WORD MajorImageVersion ; *
WORD MinorImageVersion ; *
WORD MajorSubsystemVersion ; >> 0004
WORD MinorSubsystemVersion ; >> 0000
DWORD Win32VersionValue ; *
DWORD SizeOfImage ; >> ????
DWORD SizeOfHeaders ; *
DWORD CheckSum ; *
WORD Subsystem ; 0003 for win32 console application
WORD DllCharacteristics ; *
DWORD SizeOfStackReserve ; 00100000h
DWORD SizeOfStackCommit ; 00001000h
DWORD SizeOfHeapReserve ; 00100000h
DWORD SizeOfHeapCommit ; 00001000h
DWORD LoaderFlags ; *
DWORD NumberOfRvaAndSizes ; 2 data directories (Exports & Imports)
...a number (actually 2) of the following:
IMAGE_DATA_DIRECTORY:
DWORD VirtualAddress ; 0 for exports, ???? for imports
DWORD Size ; 0 for exports, ???? for imports
...a number (actually 1) of the following:
IMAGE_SECTION_HEADER:
BYTE Name[8] ; * (Anything we like)
DWORD VirtualSize ; ?! (h.o. word must be zero??)
DWORD VirtualAddress ; >> ????
DWORD SizeOfRawData ; >> ????
DWORD PointerToRawData ; >> ????
DWORD PointerToRelocations ; *
DWORD PointerToLinenumbers ; *
WORD NumberOfRelocations ; *
WORD NumberOfLinenumbers ; *
DWORD Characteristics ; *
NOTES:
- ???? means that the value is needed but has to be filled in later as it
depends on the code
- **** means that the value is either completely ignored or it can be set to
any value without raising an error
- the main difference between this and a 'normal' PE header is that the size of
the optional header is 70h (112 bytes) instead of the standard 0E0h (224 bytes).
This is because there are only 2 directories instead of 16. This seems to be
the minimum number of directories possible, as there seems to be no way of
running an .exe that has no imports.
5. Squeezed code
----------------
Even though the code we have is already tight, it has one major drawback: It
invokes three API functions. To realize what this means just think that the
names of the functions are included in the imports section as normal ASCII
which means that only the names would take 36 bytes...
The solution here (since those functions are needed) is to call the functions
directly. This is possible because kernel32.dll is never relocated so the
function entry points are always the same (for a given version of windows).
For NT4 those values are:
GetStdHandle: 77F01CBB
WriteFile : 77F0D354
6. Squeezed imports
-------------------
[Comment: read a text on PE format to better understand what's going on]
As mentioned earlier, the PE file must have an imports directory in order to
load properly. Yet, since we call API functions directly, we only have to
specify one dummy import. A good choice (since it really has a short name) is
'Arc' from 'gdi32.dll'. To specify this imported function we should need:
'FirstThunk' is the RVA of a 0-terminated list of RVAs, one for each function
in the specified DLL. For this example we only need one RVA followed by a null
dword. This RVA will point to a structure IMAGE_IMPORT_BY_NAME:
WORD Hint ; *
BYTE Name[...] ; 'Arc',0
dwords 1 and 2 are the two RVAs for the IMAGE_IMPORT_DESCRIPTOR. dword 3 is the
RVA to the IMAGE_IMPORT_BY_NAME. So, dword 2 is the RVA of dword 3. We also
need space for the two strings 'gdi32.dll',0 and 'Arc',0.
There is a way to use even less bytes for the imports. Just remember that the
imports are examined after the file has been mapped into memory. So, since
memory is allocated in blocks, after the end of the file there will be a space
full of zeroes. So by placing the three dwords in the last 12 bytes of the file,
there is no need for the two zeroes.
Notice that the Section data and the Header (DOS and PE) are the same thing.
The section RVA is 0, so file offset and RVAs are the same. The code will be
broken in three pieces, connected by two jumps. The final result will be:
THE PROGRAM
---------------------------------------------
0000| 4D5A A17C 65F4 77BE BB1C F077 33C9 EB08
0010| 5045 0000 4C01 0100 5141 803C 0800 75F9
0020| 5451 EB06 7000 0201 0B01 506A F5FF D650
0030| B854 D3F0 77FF D0C3 0200 0000 1000 0000
0040| 0000 1000 0200 0000 0200 0000
0050| 0400 0000
0060| C000 0000 0300
0070| 0000 1000 0010 0000 0000 1000 0010 0000
0080| 0200 0000 0000 0000 0000 0000
0090| A800 0000 2800 0000 6764 6933 322E 646C
00A0| 6C00 0000 0000 0000 C000 0000 0000 0000
00B0| 4172 6300 9800 0000 BC00 0000 AE00 0000
Wrapping Up
-----------
Well, if you managed to read up to here, and understood what happened, I guess
you need no more explanations. I just gave an idea (actually MANY ideas). Maybe
on another article I will start exploring the possibilities this 'experiment'
showed me...
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::.......................................................FIN