Click here to Skip to main content
12,889,448 members (48,358 online)
Click here to Skip to main content


138 bookmarked
Posted 9 Oct 2003

DLL Injection and function interception tutorial

, 23 Oct 2003
How to inject a DLL into a running process and then intercept function calls in statically linked DLLs.
          �             Adam's Assembler Tutorial 1.0              �Ŀ
          �                                                        � �
          �                        PART IX                         � �
          ��������������������������������������������������������ͼ �

Revision :  1.0
Date     :  19-12-1996
Contact  :

Note     :  Adam's Assembler Tutorial is COPYRIGHT, and all rights are
            reserved by the author.  You may freely redistribute only the
            ORIGINAL archive, and the tutorials should not be edited in any


Well, this tutorial really is late out and I feel particularly guilty about
it.  Nonetheless, I've included yet another demo program with this edition
so that makes up for it a little bit.

Just a word about last Tutorial Eight's demo program - FIRE!.  After obtaining
a later version of TASM, I discovered that FIRE! doesn't like compiling all
that well, so make sure you have the latest revision of Tutorial Eight (V 1.4)
with the bugfix.  You can always get all the latest revisions of the tutorials
straight from  in the  /pub/blackcat/programming/tutorials
directory, and this is highly reccommended.

Anyway, on we go with:


          �                                                          �
          �                        FILE I/O                          �
          �                                                          �

Sooner or later, you will want to mess around with files.  All you have
to bear in mind here is that everything is HANDLE BASED.  Those of you who
have used or experimented with XMS will realize exactly what I mean by
handles, but if you don't, then here's a quick summary:

  * You open/create a file.
  * You get a 16-bit unsigned integer to reference it with.

How hard is that?

Note:  Back in the days before DOS 2, you had to use File Control Blocks to
       reference your files.  (You've probably seen FCBS=xxxx sometime before
       in CONFIG.SYS files, and this is to support programs that were designed
       for XT's.)  We can forget all about FCBs now, as they are pretty much


  Opening A File:           ( Interrupt 21H )

   AH     = 3DH
   AL     = operation type:

            0 = Read only operation;
            1 = Write only operation;
            2 = Read/write operation.

   DS:DX  = Filename


   If successful, the carry flag is cleared to zero, and the file handle is
   returned in AX.  However, if something went wrong, the carry flag is set
   to one, and the error code returned in AX.  For a list of all the error
   codes, see the table further down.

Now, after all that, an example:

   .STACK 200H

      FileName   DB "EXAMPLE.TXT$"
      Error      DB "Uh oh$"



      MOV   AX, @DATA               ; Point AX to the data segment
      MOV   DS, AX                  ; AX --> DX
      MOV   DX, OFFSET FileName     ; Put offset of the file to open in DX
      MOV   AH, 3DH                 ; Open file
      MOV   AL, 00H                 ; Read only
      INT   21H

      JC    Problem                 ; Something happen?

      ; Here you would get the handle from AX, and do some stuff

      JMP   Done                    ; Nope


      MOV   DX, OFFSET Error        ; Uh oh
      MOV   AH, 09H
      INT   21H


      MOV   AX, 4C00H               ; Jump back to DOS - closing any open
      INT   21H                     ; files.  Slack, but we don't know how
                                    ; to close files yet.


Okay... simple enough I hope.  Now, suppose we want to create a new file?
It's just another simple subfunction of interrupt 21H.  This is how you do

  Creating A New File:      ( Interrupt 21H )

   AH     = 3CH
   CX     = file type:

            0 = Normal file;
            1 = Read only;
            2 = Hidden file;
            4 = System file;

   DS:DX  = Filename


   As before, if successful, the carry flag is cleared to zero, and the file
   handle is returned in AX.  Note that you must watch out for existing files
   before creating a new file of the same name.  DOS will not check if a file
   of the same name already exists, and overwrites the old one.

   Before you create a new file - try to open the file first.  If you get
   error code 2 in AX, (file does not exist), then go ahead and create a new
   file.  If you don't get error 2, you'll be overwriting an existing file!


As you should know from experience with high-level languages, you must close
your files before you end your program.  (Actually, function 4CH closes all
open files anyway, but that's a slack way of going about things.)  To close
an open file, you should do this:

  Closing A File:           ( Interrupt 21H )

   AH     = 3EH
   BX     = file handle


   Yet again, any errors are reflected in the carry flag and AX.


Finally, error codes.  Just checking CF to see if anything went wrong will
certainly let us know if anything is amiss, but we'd really like more detail.
Examining AX after an error is detected is the way to go, and AX could
contain any one of the following codes:

        �  Error Code  � Explanation                                  �
        �      00H     � Unknown error                                �
        �      01H     � Invalid function number                      �
        �      02H     � File not found                               �
        �      03H     � Path not found                               �
        �      04H     � Too many open files                          �
        �      05H     � Access denied                                �
        �      06H     � Invalid handle                               �
        �      07H     � Control blocks destroyed                     �
        �      08H     � Out of memory                                �
        �      09H     � Bad control block address                    �
        �      0AH     � Invalid environment                          �
        �      0BH     � Invalid format                               �
        �      0CH     � Invalid access code                          �
        �      0DH     � Invalid data                                 �
        �      0EH     � Unkown error                                 �
        �      0FH     � Invalid drive                                �
        �      10H     � Cannot remove current directory              �
        �      11H     � Device not the same                          �
        �      12H     � No more files available                      �
        �      13H     � Disk is write-protected                      �
        �      14H     � Bad unit                                     �
        �      15H     � Drive not ready                              �
        �      16H     � Unknown command                              �
        �      17H     � CRC error                                    �
        �      18H     � Bad structure length                         �
        �      19H     � Seek error                                   �
        �      1AH     � Invalid medium                               �
        �      1BH     � Sector not found                             �
        �      1CH     � Printer out **                               �
        �      1DH     � Write error                                  �
        �      1EH     � Read error                                   �
        �      1FH     � General failure                              �

  ** I know, I don't get it either.  I guess it's in there because DOS treats
     everything as a file.

Phew!  All courtesy of the good old DOS technical reference.  Some of the
ones up there were pretty obscure - there's only a few you really need to
remember.  Some of my *favourites* are:  Sector not found, Seek error and
CRC error half way through a stack of arjed diskettes.  Gee that brings back
memories.  :)


Okay, so we've seen how to create, open and close files.  Now let's do
something with 'em.  To read some bytes from a file, you must use function
3FH.  Assuming you've already opened the file you want to read from, you can
now use a bit of code like below:

   MOV   AH, 3FH                      ; Read byte(s)
   MOV   BX, Handle                   ; Fileto work on
   MOV   CX, BytesToRead              ; How much to get
   MOV   DX, OFFSET WhereToPutThem    ; An array or variable
   INT   21H

   JC    DidSomethingGoWrong          ; Check for an error

If you are having problems grasping any of this - don't worry too much.  Just
go back over the examples above and see what sense you can make of it.  Next
tutorial we'll press on with sprites - (and loading them from disk) - so
you'll get to see a good example.

Okay... now, writing to a file.  Much the same as reading, we now use function
40H.  Some code to write a byte would look like:

   MOV   AH, 40H                      ; Write byte(s)
   MOV   BX, Handle                   ; Fileto write to
   MOV   CX, BytesToWrite             ; How much to write
   MOV   DX, OFFSET WhereToWriteFrom  ; Where the data is coming from
   INT   21H
   JC    DidSomethingGoWrong          ; Any errors?

Well, that just about concludes file I/O for this tutorial.  Although not a
major component of Assembly language programming, file I/O is nonetheless
an important concept to get a grip on.


          �                                                          �
          �              CALLING ASSEMBLER FROM C/C++                �
          �                                                          �

I suppose it's about time I covered linking assembler with C.  Personally, I
prefer doing VGA coding in an Assembler/Pascal combination.  However, C has
its place, and linking with C is an important aspect we should cover.

You should have realized that you can start entering Assembler code into your
C program like so:

   /* Your C code goes here */

   asm  {
                /*                               */
                /* Your assembler code goes here */
                /*                               */

   /* Your C code resumes here */

Now, considering that we can insert Assembler straight into C code, why would
we bother to write external code?  The answer is fairly simple.  By using
external routines, we have code that is faster to execute, faster to compile,
can use some of Turbo Assembler's special features - such as ideal mode, and
may even be portable to other languages.

Writing external code for C is fairly simple, and is thankfully more easy than
writing external code for Pascal.  (See Tutorial Seven).  As you noticed back
in Tutorial seven, we had to declare the code and data segments ourselves
using the somewhat messy SEGMENT directive.  This is due to the way that
Pascal likes to organise memory, and there is only one way we can get around
the problem - we can use the TPASCAL model.  Unfortunately, TPASCAL is an
antiquated and outdated way of going about things, so we have to put a bit of
work in.  I'm not going to mention TPASCAL ever again, so we can safely forget
about the gritty details.

Note that none of this applies to us in C - we can quite happily use our nice
simple assembler skeletons.  There are a few restrictions placed upon us by
most compilers though:

   � The compiler uses SI and DI to store register variables.  If you have
     used register variables in your code, remember to push and pop SI and
     DI in your external code.

   � The compiler probably will not push and pop CS, DS, SS and BP so ensure
     that you are careful if you alter any of these registers.

Other than those little snippets, there is little else we need to bear in
mind.  Let's blaze!


Okay... now we're going to write a small external routine and link it to C.
Let's take a look at a basic skeleton that just puts some text onto the

   ============================  LIBRARY.ASM  =============================


   Message   DB "Well looky here - we got ourselves some text$"


   PUBLIC    _sample

; ---------------------------------------------------------------------------

; void sample();

_sample      PROC   NEAR        ; Declare a near procedure

   MOV   AH, 00H                ; Set video mode
   MOV   AL, 03H                ; Mode 03H
   INT   10H

   MOV   AH, 09H                ; Print a string
   MOV   DX, OFFSET Message     ; DS:DX <-- Message
   INT   21H

   RET                          ; Outta here!

_sample      ENDP



Well.... nothing too tricky there.  Now, what about the C code that goes along
with it?

   =============================  EXAMPLE.C ==============================

   extern void sample();

   int main()

      return 0;



And to compile the lot, the line below'll do the job.


Of course, if you are using another flavour of C then replace TCC with
whatever command line interpreter you have.  It's also possible to get C to
recognise variables declared in Assembler, and the following skeleton details
how this is done:

   ============================  LIBRARY.ASM  =============================


   PUBLIC _YourVariable      ; Declare an external variable

   _YourVariable  DW 9999    ; Make that variable a word with a value of 9999



   =============================  EXAMPLE.C ==============================

   extern int YourVariable;

   int main()

      printf("The Assembler external variable is: %d", YourVariable);


Again, compile this with:  TCC EXAMPLE.C LIBRARY.ASM

But what about passing parameters to your routines?  We could do this the hard
way like we did with Pascal, or alternatively we could use the ARG directive.

ARG is brilliant, because it greatly simplifies things -- but it has some
shortcomings.  Namely, in every routine you need an additional three
instructions.  If you want speed and don't mind a bit of hard work, work with
the stack directly like we did in Tutorial Seven.

Here's how you use ARG:

   ============================  LIBRARY.ASM  =============================


   PUBLIC _putpixel        ; Declare the external procedure

; ---------------------------------------------------------------------------
; void putpixel(int x, int y, char color, int location);

_putpixel   PROC NEAR

   ARG   X : Word, Y : Word, Color : Byte, Location : Word

   PUSH  BP                ; Save BP
   MOV   BP, SP            ; BP *must* equal SP for ARG to work

   MOV   AX, [Location]    ; Parameters can now be accessed easily
   MOV   ES, AX
   MOV   BX, [X]
   MOV   DX, [Y]
   MOV   DI, BX
   MOV   BX, DX
   SHL   DX, 8
   SHL   BX, 6
   ADD   DX, BX
   ADD   DI, DX
   MOV   AL, [Color]
   MOV   ES:[DI], AL

   POP   BP                ; BP needs to be restored!


_putpixel   ENDP


   =============================  EXAMPLE.C ==============================

   extern void putpixel(int x, int y, char color, int location);

   int main()

      asm {
         mov   ax, 0x13
         int   0x10

      putpixel(100, 100, 12, 0xa000);

      asm {
         mov   ax, 0x03
         int   0x10


Not all that tricky, huh?  However, if you choose to write external
routines because you want the speed Assembler can give, then access the stack
the hard way.  Those extra pops and pushes can really add up if your putpixel
routine gets called 320x200 times!


          �                                                          �
          �                AN INTRODUCTION TO MACROS                 �
          �                                                          �

Macros are one of the most powerful features you have at your disposal when
you are working with Assembler.  Often you will find yourself repeating the
same few lines of code over and over again when writing larger programs.  You
don't want to go to the trouble of creating a procedure -- which would slow
down the code, but you don't want to keep repeating yourself.

The answer.... MACROS.

A macro is just a set of instructions that are given a name by which it will
be referred to in the code.  You can define a macro like this:

   MyMacroName    MACRO

      ; Your instructions go here

   ENDM           MyMacroName

And from then on, whenever you put  MyMacroName  in your code, the
instructions contained within the macro will be assembled in place of the
macro name.

NOTE:  It is probably best to declare any macros just before you declare the
       data segment.  For clarity, place all your macros in another text file
       and then use  INCLUDE <filename>  to include the macros.


Macros may also have parameters, making them especially handy.  For example,
I have often used DOS function 09H to put a string on the screen.  I could
make the programs I write more easy to read at first glance by creating the
following macro:

   PutText  MACRO TextParam

      MOV   AH, 09H                  ; TextParam is the parameter -- NOT
      MOV   DX, OFFSET TextParam     ; a variable.  Replace TextParam with
      INT   21H                      ; whatever name you choose.

   ENDM     PutText

Then, assuming in the data segment I had declared a string like this:

   AString   DB "This is a string$"

I could display that string by writing:

   PutText   AString

NOTE:  When you are working with macros, be careful to observe what
       registers they change.  If in doubt, push and then pop any
       registers you feel may be affected.


Although that simple macro wasn't really anything special, macros have many
other uses.  I'm not going to say anything more on macros for now, but I'll
use them from time to time in future demo programs, and you'll learn other
techniques you can put to good use.

Anyway, on with what I've been wanting to do:


          �                                                          �
          �                    THE DEMO PROGRAM                      �
          �                                                          �

At first I was going to release this tutorial without a demo program, but
seeing as I've been a bit too lazy for too long, (and also because a friend of
mine just made a demo like this), I've decided to include a plasma.

Plasmas can be a bit tricky in places -- it took me a while to get the whole
thing working properly because of a problem I had with my lookup table.  But
if you follow the algorithm set out below, you shouldn't have any problems.

 *** Before you begin, you'll need FOUR temporary variables in your code. ***
     In Assembler this can get a bit tricky because you will often find
     yourself running out of registers.  You could declare some bytes up
     in the data segment, but it's faster to use registers.  These four
     temporary variables will only hold numbers between 0 and 255, so
     they only need to be BYTES.

     In the algorithm, I refer to these temporary variables as Temp1,
 *** Temp2, Temp3 and Temp4.                                              ***

The algorithm looks like this:

   � Create a lookup table

     This is basically just one long sine wave.  You can experiment by using
     a cosine wave instead, or alter the amplitude of the function you are
     using.  I created my lookup table using the following expression:

     For W := 1 To 512 Do
      SinTable[W] := Round(Sin(W / 255 * Pi * 2) * 128);

     (SinTable is an array of 512 BYTES)

   � Initialize the palette

     I personally like to make my palettes after I see the demo running with
     the standard palette.  That way, by making certain colors dark and others
     very light, the result is exactly the way I want it.  I've found the best
     way to do this is to grab the screen when the demo is running with a
     program like Screen Thief, then load up that picture in a paint program
     that lets you alter the palette.

     Once you get the palette how you want it, save it to disk as a COL file
     (if possible) and then write a little program to read in the COL file and
     write the file so it looks like PLASMA.DAT.

     Remember, Screen Thief is shareware, so if you use it, send the author
     some money huh?

Loop (1):  Before you start plotting the first row, you must:

           * Zero out Temp4;
           * Decrement Temp3 by two;

             You can experiment with Temp3 -- the larger the number you
             subtract from it, the faster the plasma will 'move'.

	   You now move onto Loop (2).

Loop (2):  At the start of each row you must:

           * Increment Temp4 by one;
           * Let Temp1 = Sintable[Current row + Temp3];
           * Let Temp2 = SinTable[Temp4];

           You now move onto Loop (3).

Loop (3):  For every pixel on the current row you must:

           * Work out the color of that pixel to be plotted;

             The color value of that pixel is quite simply:

             SinTable[Temp1 + Temp2] + SinTable[Current row + Temp2]

             Unfortunately, this is a little harder to work out in Assembler
             and ends up taking several lines of code!!

           * Increment Temp1 by one;
           * Increment Temp2 by one;

           After doing an entire row, you then loop back to Loop (2).

  Once you have done all the rows (200), you can then loop back to loop (1).

Of course, you'll also want to put a check for retrace in there, and checking
to see if someone hit a key would be a good idea!!

NOTE:  For those that didn't know, the VGA has a status register that is worth
       paying particular attention to.  This is register 03DAH, and by
       checking its various bits we can see what's happening right now with
       the VGA.

       (For those who want to know exactly what all the other bits are for,
       you should obtain a copy Ralf Brown's Interrupt List.  This is
       available from my homepage and contains a complete list of all the
       interrupts, registers and much more.)

       Anyway, we are only interested in bit four of 03DAH which lets us know
       is a retrace is in progress.  If we can access the VGA while the
       electron gun in the monitor is retracing it's path to the top of the
       screen -- we get fast, flicker-free access.  What's more, because
       retrace happens every 1/80th of a second ON ALL COMPUTERS, we have a
       method of locking our demo to a particular speed on all machines.

       To check if retrace is happening, we simply examine bit four.  If bit
       four is set, a retrace is now in progress.  However, we don't know
       just how far into a retrace it is and we don't know how much time of
       flicker-free access we have.  The solution is to check again for a
       retrace so we can be sure that we are at the START of one.

       I've used a retrace in the code to basically make sure the demo runs
       at the same speed on all machines.  (More or less anyway).

       Also note that my plasma is more of a plasma variant.  You can, and
       are encouraged to alter the code -- (try incrementing the temp values
       instead of decrementing, changing the amount the temp values are
       decremented by or changing the way the color value is found.  Also try
       changing the palette, because this can make the plasma appear
       completely different).

       It is possible to create all sorts of effects by only making simple
       changes, so experiment... be creative!


Well, that just about concludes things for this tutorial.  I'm not exactly
sure what I'll be putting in Tutorial Ten -- sprites will be covered, but I
can see the tutorials leaning towards demo effects and how you can code them
in standalone Assembler.

Until next time,

-- Adam.


            This is getting a bit old now, but is still amusing:


If Operating Systems Were Beers...

DOS Beer:  Requires you to use your own can opener, and requires you to read
           the directions carefully before opening the can.  Originally only
           came in an 8-oz. can, but now comes in a 16-oz. can.  However, the
           can is divided into 8 compartments of 2 oz. each, which have to be
           accessed separately.  Soon to be discontinued, although a lot of
           people are going to keep drinking it after it's no longer

Mac Beer:  At first, came only a 16-oz. can, but now comes in a 32-oz. can.
           Considered by many to be a "light" beer. All the cans look
           identical.  When you take one from the fridge, it opens itself.
           The ingredients list is not on the can. If you call to ask about
           the ingredients, you are told that "you don't need to know."  A
           notice on the side reminds you to drag your empties to the

3.1 Beer:  The world's most popular. Comes in a 16-oz. can that looks a
           lot like Mac Beer's.  Requires that you already own a DOS Beer.
           Claims that it allows you to drink several DOS Beers
           simultaneously,but in reality you can only drink a few of them,
           very slowly, especially slowly if you are drinking the Windows Beer
           at the same time.  Sometimes, for apparently no reason, a can
           of Windows Beer will explode when you open it..

OS/2 Beer: Comes in a 32-oz can.  Does allow you to drink several DOS Beers
           simultaneously.  Allows you to drink Windows 3.1 Beer
           simultaneously too, but somewhat slower. Advertises that its cans
           won't explode when you open them, even if you shake them up. You
           never really see anyone drinking OS/2 Beer, but the manufacturer
           (International Beer Manufacturing) claims that 9 million six-packs
           have been sold.

95 Beer:   You can't buy it yet, but a lot of people have taste-tested it and
           claim it's wonderful.  The can looks a lot like Mac Beer's can, but
           tastes more like Windows 3.1 Beer.  It comes in 32-oz. cans, but
           when you look inside, the cans only have 16 oz. of beer in them.
           Most people will probably keep drinking Windows 3.1 Beer until
           their friends try Windows 95 Beer and say they like it.  The
           ingredients list, when you look at the small print, has some of the
           same ingredients that come in DOS beer, even though the
           manufacturer claims that this is an entirely new brew.

NT Beer:   Comes in 32-oz. cans, but you can only buy it by the truckload.
           This causes most people to have to go out and buy bigger
           refrigerators.  The can looks just like Windows 3.1 Beer's, but
           the company promises to change the can to look just like Windows
           95 Beer's - after Windows 95 beer starts shipping.  Touted as an
           "industrial strength" beer, and suggested only for use in bars.

Unix Beer: Comes in several different brands, in cans ranging from 8 oz. to
           64 oz.  Drinkers of Unix Beer display fierce brand loyalty, even
           though they claim that all the different brands taste almost
           identical.  Sometimes the pop-tops break off when you try to open
           them,  so you have to have your own can opener around for those
           occasions, in which case you either need a complete set of
           instructions, or a friend who has been drinking Unix Beer for
           several years.

VMS Beer:  Requires minimal user interaction, except for popping the top and
           sipping.  However cans have been known on occasion to explode, or
           contain extremely un-beer-like contents.

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.


This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


About the Author

Qatar Qatar
Nasser R. Rowhani
Programming simply pumps my adrenaline..

Okay... I like people critisizing me...
Let me fix this article...

You may also be interested in...

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.170424.1 | Last Updated 24 Oct 2003
Article Copyright 2003 by CrankHank
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid