El lenguaje de programación que usaremos será C/C++ con el compilador Dev-C++, utilizando APIs. Si no sabéis que son preguntad a Google o a Wikipedia. Empezaré desde cero y ya adelanto que no voy a hacer un tutorial demasiado avanzado, puesto que quiero que lo entienda todo el mundo. Las APIs que use las explicaré por encima, para una mayor explicación podéis consultar en MSDN:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winprog/winprog/functions_in_alphabetical_order.asp
Análisis del código maligno
Bien, explicaré el método más sencillo. Este consiste en utilizar una máquina virtual y con un programa llamado Regshot mirar que cambios ha realizado en el sistema tras su ejecución. No es el mejor método porque un programa bien hecho puede evitar ser registrado por programas así, pero es mucho más sencillo que el otro método (desensamblarlo) y como este manual pretende ser básico, será el método que usaremos.
Lo primero de todo será instalar un programa para crear máquinas virtuales, yo personalmente uso para eso VMWare. No voy a explicar como se crea una máquina virtual, para eso mirad el siguiente enlace:
http://foro.elhacker.net/index.php/topic,158384.0.html
Una vez tenemos nuestra máquina virtual con el sistema operativo instalado (Windows) ya tan sólo hay que deshabilitar las conexiones (no queremos que se propague el malware ni que ataque a otros sitios mientras lo analizamos) y cerrar todo programa no imprescindible. Hacemos una primera foto con Regshot y luego otra después de haber ejecutado el malware. Entonces hacemos que compare y tendremos nuestra lista de cambios
Comencemos a programar
Antes que nada tenéis que tener instalado Dev-C++ o vuestro compilador si usáis otro, una vez hecho, vayamos al grano. Todo programa en C/C++ se divide en funciones, que son trozos de código a los que se puede llamar. Pueden devolver una salida o no (void). En C/C++ hay una función imprescindible que es main, aquella por la que empieza el programa cuando lo ejecutamos. Tiene la siguiente estructura:
Código
int main() { ... }
La función main es la que menos utilizaremos, puesto que borraremos el gusano con funciones aparte, que llamaremos desde el main. Será necesario utilizar APIs, por lo que habrá que incluir el header (archivo de cabecera) que permite usarlas, que es windows.h. También queremos comunicarnos con el usuario, por lo que hace falta stdio.h (Standard Input/Output). También nos hará falta Tlhelp32.h y ctype.h (por la función toupper). Para incluir headers hay que poner las siguientes líneas al principio del código, antes que ninguna función:
Código
#include <stdio.h> #include <windows.h> #include <Tlhelp32.h> #include <ctype.h>
Matando procesos y eliminando archivos concretos
Para poder eliminar un archivo que se está ejecutando, es necesario matar antes su proceso. Para ello haremos una función que llamaremos KillProcess, que recibirá como entrada una cadena con el nombre del archivo al que hay que matar el proceso. Las funciones (excepto main) se declaran antes de poner el código de otra función y después de los headers incluidos.
Como salida nuestra función devolvera un entero (un número) indicando si ha tenido éxito o no. Para declarar la función habría que usar la sentencia siguiente:
Código
int KillProcess(LPCTSTR lpfilename);
Luego también tendríamos que poner la función en sí, por lo que hasta ahora tendríamos que tener el código así:
Código
#include <stdio.h> #include <windows.h> #include <Tlhelp32.h> #include <ctype.h> int KillProcess(LPCTSTR lpfilename); int KillProcess(LPCTSTR lpfilename) { ... } int main() { ... }
Ahora veamos como matar el proceso de un archivo. Hay que declarar una serie de variables que usaremos:
Código
Estas son las que necesitaremos para poder matar el proceso. No hace falta que los nombres de variable (la segunda palabra) sea la misma, estos son los que usaré yo aquí. Los tipos de variable no los podéis cambiar y tened en cuenta que C/C++ distingue mayúsculas de minúsculas.
WIN32_FIND_DATA Win32FindData; HANDLE handle; DWORD exitcode; PROCESSENTRY32 pe32;
Ahora tenemos que hacer como si fuera una foto de todos los procesos que se ejecutan, para ello usaremos la API CreateToolhelp32Snapshot, que tiene la siguiente forma:
Código
HANDLE WINAPI CreateToolhelp32Snapshot( __in DWORD dwFlags, __in DWORD th32ProcessID );
Luego recorremos los procesos con las APIs Process32First (para el primero) y Process32Next (para los siguientes). Tienen la siguiente forma:
Código
BOOL WINAPI Process32First( __in HANDLE hSnapshot, __inout LPPROCESSENTRY32 lppe ); BOOL WINAPI Process32Next( __in HANDLE hSnapshot, __out LPPROCESSENTRY32 lppe );
Harán falta otras APIs:
Código
Para hacer todo esto, traducido a código hay que hacer lo siguiente:
//Cierra un handle abierto BOOL WINAPI CloseHandle( __in HANDLE hObject ); //Abre un proceso HANDLE WINAPI OpenProcess( __in DWORD dwDesiredAccess, __in BOOL bInheritHandle, __in DWORD dwProcessId ); //Devuelve el estado de finalización del proceso BOOL WINAPI GetExitCodeProcess( __in HANDLE hProcess, __out LPDWORD lpExitCode ); //Termina un proceso y sus threads BOOL WINAPI TerminateProcess( __in HANDLE hProcess, __in UINT uExitCode );
Código
handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); //Guardamos en nuestra variable de tipo HANDLE la "foto" if(handle == INVALID_HANDLE_VALUE) return ERROR_LISTAR; //Si no es correcta la "foto" se acaba la funcion y se devuelve un error pe32.dwSize = sizeof(PROCESSENTRY32); //Definimos la medida de nuestra estructura PROCESSENTRY32 if(!Process32First(handle, &pe32)) //Si hay un error al mirar el primer proceso { CloseHandle(handle); //Cierra el HANDLE que usamos return ERROR_PROCESO; //Acaba la funcion devolviendo un error } int i; //Declaramos un par de variables más que hacen falta para pasar a mayusculas dos cadenas char lpfilename2[strlen(lpfilename) + 1]; strcpy(lpfilename2, lpfilename); for(i = 0; pe32.szExeFile[i] != '\0'; i++) pe32.szExeFile[i] = toupper(pe32.szExeFile[i]); //Pasamos a mayúsculas las dos cadenas a comparar para que si no son exactamente iguales en cuanto a mayusculas o minusculas, también mate el proceso for(i = 0; lpfilename2[i] != '\0'; i++) lpfilename2[i] = toupper(lpfilename2[i]); while(strcmp(pe32.szExeFile,lpfilename2)) /Mientras el proceso no coincida con el que buscamos... { if(!Process32Next(handle, &pe32)) //Va mirando el siguiente, acabando la funcion si hay errores { CloseHandle(handle); return ERROR_PROCESO; } for(i = 0; pe32.szExeFile[i] != '\0'; i++) pe32.szExeFile[i] = toupper(pe32.szExeFile[i]); //Pasa a mayusculas por lo que he dicho antes } /**Una vez hemos encontrado el proceso**// handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID); //Abrimos el proceso if(handle == NULL) //Si sale mal, damos error y salimos de la funcion { CloseHandle(handle); return ERROR_PROCESO; } else //Si no sale mal... { GetExitCodeProcess(handle, &exitcode); //Cogemos el exitcode del proceso y lo guardamos en nuestra variable destinada a ese cometido TerminateProcess(handle, exitcode); //Matamos el proceso CloseHandle(handle); //Cerramos el HANDLE que habiamos usado return 0; //Devolvemos el valor 0, que es el que indicara que todo va bien y acaba la funcion }
Como ya supongo que habréis supuesto, return se utiliza para terminar la función y devolver un valor. La doble barra sirve para introducir comentarios de una línea y también son comentario todo aquello entre /* y */. Además, también podéis ver que excepto el cero del final, los return devuelven cosas como ERROR_PROCESO. Esto sirve para llamar de una forma más recordable a una salida, en vez de usar un número. Para usar estos nombres, hay que definirlos antes, en la zona de los headers. Veamos todo lo que llevamos de código, incluyendo estas definiciones:
Código
#define ERROR_LISTAR 1 #define ERROR_PROCESO 2 #include <stdio.h> #include <windows.h> #include <Tlhelp32.h> #include <ctype.h> int KillProcess(LPCTSTR lpfilename); int KillProcess(LPCTSTR lpfilename) { WIN32_FIND_DATA Win32FindData; HANDLE handle; DWORD exitcode; PROCESSENTRY32 pe32; handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if(handle == INVALID_HANDLE_VALUE) return ERROR_LISTAR; pe32.dwSize = sizeof(PROCESSENTRY32); if(!Process32First(handle, &pe32)) { CloseHandle(handle); return ERROR_PROCESO; } int i; char lpfilename2[strlen(lpfilename) + 1]; strcpy(lpfilename2, lpfilename); for(i = 0; pe32.szExeFile[i] != '\0'; i++) pe32.szExeFile[i] = toupper(pe32.szExeFile[i]); for(i = 0; lpfilename2[i] != '\0'; i++) lpfilename2[i] = toupper(lpfilename2[i]); while(strcmp(pe32.szExeFile,lpfilename2)) { if(!Process32Next(handle, &pe32)) { CloseHandle(handle); return ERROR_PROCESO; } for(i = 0; pe32.szExeFile[i] != '\0'; i++) pe32.szExeFile[i] = toupper(pe32.szExeFile[i]); } handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID); if(handle == NULL) { CloseHandle(handle); return ERROR_PROCESO; } else { GetExitCodeProcess(handle, &exitcode); TerminateProcess(handle, exitcode); CloseHandle(handle); return 0; } } int main() { ... }
Bueno, ahora que ya está hecha la función de matar el proceso, vayamos con la de borrar el archivo. Usaremos una función con la misma estructura, recibe como parámetro un archivo y de salida devuelve un entero. Para seguir poniendo de manifiesto mi gran originalidad, la llamaremos FileDelete. La declaración que habría que poner junto con la de la otra función, quedaría así:
Código
int FileDelete(LPCTSTR lpfilename);
Para la función es muy sencillo, tan sólo hay que ver una API, que es DeleteFile:
Código
BOOL WINAPI DeleteFile( __in LPCTSTR lpFileName );
El código de la función quedaría así:
Código
int FileDelete(LPCTSTR lpfilename) { return DeleteFile(lpfilename); }
Y el código total por ahora:
Código
#define ERROR_LISTAR 1 #define ERROR_PROCESO 2 #include <stdio.h> #include <windows.h> #include <Tlhelp32.h> #include <ctype.h> int KillProcess(LPCTSTR lpfilename); int FileDelete(LPCTSTR lpfilename); int KillProcess(LPCTSTR lpfilename) { WIN32_FIND_DATA Win32FindData; HANDLE handle; DWORD exitcode; PROCESSENTRY32 pe32; handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if(handle == INVALID_HANDLE_VALUE) return ERROR_LISTAR; pe32.dwSize = sizeof(PROCESSENTRY32); if(!Process32First(handle, &pe32)) { CloseHandle(handle); return ERROR_PROCESO; } int i; char lpfilename2[strlen(lpfilename) + 1]; strcpy(lpfilename2, lpfilename); for(i = 0; pe32.szExeFile[i] != '\0'; i++) pe32.szExeFile[i] = toupper(pe32.szExeFile[i]); for(i = 0; lpfilename2[i] != '\0'; i++) lpfilename2[i] = toupper(lpfilename2[i]); while(strcmp(pe32.szExeFile,lpfilename2)) { if(!Process32Next(handle, &pe32)) { CloseHandle(handle); return ERROR_PROCESO; } for(i = 0; pe32.szExeFile[i] != '\0'; i++) pe32.szExeFile[i] = toupper(pe32.szExeFile[i]); } handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID); if(handle == NULL) { CloseHandle(handle); return ERROR_PROCESO; } else { GetExitCodeProcess(handle, &exitcode); TerminateProcess(handle, exitcode); CloseHandle(handle); return 0; } } int FileDelete(LPCTSTR lpfilename) { return DeleteFile(lpfilename); } int main() { ... }
Realmente la función FileDelete es inútil, podríamos llamar directamente a la API, que es lo único que hace la función. Si no hago esto es porque más adelante (en próximos capítulos xD) ampliaremos esta función.
Borrando valores del registro
Este será el último apartado de este capítulo, el borrado de valores del registro, para por ejemplo borrar una entrada que pueda usar un gusano para autoejecutarse al inicio. Llamaremos a nuestra función RegKill, que será así:
Código
long RegKill(HKEY hkey, LPCTSTR lpSubKey, LPCTSTR lpValueName);
Devolverá un entero largo y como parámetros recibe un HKEY, que será en este caso HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE o HKEY_USERS, que son valores predefinidos; como segundo parámetro una cadena que indica la ruta de la clave donde está el valor a borrar y como tercer parámetro otra cadena que indica el nombre del valor a borrar.
Usaremos tres APIs para esta función. La primera es RegOpenKeyEx, que abre una clave del registro y tiene la siguiente forma:
Código
LONG WINAPI RegOpenKeyEx( __in HKEY hKey, __in_opt LPCTSTR lpSubKey, __reserved DWORD ulOptions, __in REGSAM samDesired, __out PHKEY phkResult );
La segunda API es RegDeleteValue, que elimina un valor de una clave del registro y tiene la siguiente forma:
Código
LONG WINAPI RegDeleteValue( __in HKEY hKey, __in_opt LPCTSTR lpValueName );
La última es RegCloseKey, que cierra el handle abierto y tiene esta forma:
Código
LONG WINAPI RegCloseKey( __in HKEY hKey );
El código de la función quedaría así:
Código
long RegKill(HKEY hkey, LPCTSTR lpSubKey, LPCTSTR lpValueName) { HKEY hregkey; //Definimos la variable donde guardaremos la ruta entera de la clave long vuelta; //Definimos la variable donde guardaremos el valor a devolver if(RegOpenKeyEx(hkey, lpSubKey, 0, KEY_ALL_ACCESS, &hregkey) == ERROR_SUCCESS) //Si consigue abrir la clave... { vuelta = RegDeleteValue(hregkey, lpValueName); //Borra el valor de la clave y guarda el resultado en "vuelta" RegCloseKey(hregkey); //Cierra el handle que hemos usado return vuelta; //Devuelve "vuelta" } else return ERROR_OPEN_REG; //Si no consigue abrir la clave, devuelve este error }
Hace falta definir al inicio ERROR_OPEN_REG. Definiendo eso e incorporando el código, queda así:
Código
#define ERROR_LISTAR 1 #define ERROR_PROCESO 2 #define ERROR_OPEN_REG 3 #include <stdio.h> #include <windows.h> #include <Tlhelp32.h> #include <ctype.h> int KillProcess(LPCTSTR lpfilename); int FileDelete(LPCTSTR lpfilename); long RegKill(HKEY hkey, LPCTSTR lpSubKey, LPCTSTR lpValueName); int KillProcess(LPCTSTR lpfilename) { WIN32_FIND_DATA Win32FindData; HANDLE handle; DWORD exitcode; PROCESSENTRY32 pe32; handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if(handle == INVALID_HANDLE_VALUE) return ERROR_LISTAR; pe32.dwSize = sizeof(PROCESSENTRY32); if(!Process32First(handle, &pe32)) { CloseHandle(handle); return ERROR_PROCESO; } int i; char lpfilename2[strlen(lpfilename) + 1]; strcpy(lpfilename2, lpfilename); for(i = 0; pe32.szExeFile[i] != '\0'; i++) pe32.szExeFile[i] = toupper(pe32.szExeFile[i]); for(i = 0; lpfilename2[i] != '\0'; i++) lpfilename2[i] = toupper(lpfilename2[i]); while(strcmp(pe32.szExeFile,lpfilename2)) { if(!Process32Next(handle, &pe32)) { CloseHandle(handle); return ERROR_PROCESO; } for(i = 0; pe32.szExeFile[i] != '\0'; i++) pe32.szExeFile[i] = toupper(pe32.szExeFile[i]); } handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID); if(handle == NULL) { CloseHandle(handle); return ERROR_PROCESO; } else { GetExitCodeProcess(handle, &exitcode); TerminateProcess(handle, exitcode); CloseHandle(handle); return 0; } } int FileDelete(LPCTSTR lpfilename) { return DeleteFile(lpfilename); } long RegKill(HKEY hkey, LPCTSTR lpSubKey, LPCTSTR lpValueName) { HKEY hregkey; long vuelta; if(RegOpenKeyEx(hkey, lpSubKey, 0, KEY_ALL_ACCESS, &hregkey) == ERROR_SUCCESS) { vuelta = RegDeleteValue(hregkey, lpValueName); RegCloseKey(hregkey); return vuelta; } else return ERROR_OPEN_REG; } int main() { ... }
Ahora que ya tenemos las funciones, falta ver que poner en el main. Esto variará según qué malware queramos eliminar. Imaginemos que tenemos un malware que crea el archivo siguiente:
Código:
C:\gusano.exe
Y se autoejecuta a cada inicio con la siguiente clave:
Código:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run\Soy un gusano malo y propagador
La función main deberá llamar a las tres funciones que hemos hecho: primero tendrá que matar el proceso, luego eliminar el archivo y después borrar el valor del registro. Quedaría así nuestro main de forma cutre:
Código
int main() { KillProcess("gusano"); FileDelete("C:\\gusano"); RegKill(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", "Soy un gusano malo y propagador"); return 0; }
Pero si nos hemos molestado en poner diferentes valores a las salidas es para tenerlos en cuenta, no para pasar de ellos. Hagamos un main que mire también como les va a nuestras funciones xDD:
Código
El hecho de que las contrabarras aparezcan duplicadas es porque la contrabarra se usa en C/C++ para las secuencias de escape (\n simboliza un salto de línea). Para poner una contrabarra, se ponen dos. Y el código entero sería así:
int main() { long salida; salida = KillProcess("gusano.exe"); if(salida == ERROR_LISTAR) printf("Error al listar los procesos\n"); else if((salida == ERROR_PROCESO) && (GetLastError() == ERROR_NO_MORE_FILES)) printf("El proceso no existe\n"); else if(salida == ERROR_PROCESO) printf("Error al manejar los procesos\n"); else printf("Proceso finalizado correctamente\n"); salida = FileDelete("C:\\gusano.exe"); if(salida == ERROR_FILE_NOT_FOUND) printf("Archivo no encontrado\n"); else if(salida == ERROR_ACCESS_DENIED) printf("Acceso denegado\n"); else if(salida) printf("Archivo eliminado correctamente\n"); else printf("Error al eliminar el archivo\n"); salida = RegKill(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", "Soy un gusano malo y propagador"); if(salida == ERROR_OPEN_REG) printf("Error al abrir el registro\n"); else if(salida == ERROR_SUCCESS) printf("Valor borrado del registro\n"); else printf("Error al borrar valor del registro\n"); return 0; }
Código
#define ERROR_LISTAR 1 #define ERROR_PROCESO 2 #define ERROR_OPEN_REG 3 #include <stdio.h> #include <windows.h> #include <Tlhelp32.h> #include <ctype.h> int KillProcess(LPCTSTR lpfilename); int FileDelete(LPCTSTR lpfilename); long RegKill(HKEY hkey, LPCTSTR lpSubKey, LPCTSTR lpValueName); int KillProcess(LPCTSTR lpfilename) { WIN32_FIND_DATA Win32FindData; HANDLE handle; DWORD exitcode; PROCESSENTRY32 pe32; handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if(handle == INVALID_HANDLE_VALUE) return ERROR_LISTAR; pe32.dwSize = sizeof(PROCESSENTRY32); if(!Process32First(handle, &pe32)) { CloseHandle(handle); return ERROR_PROCESO; } int i; char lpfilename2[strlen(lpfilename) + 1]; strcpy(lpfilename2, lpfilename); for(i = 0; pe32.szExeFile[i] != '\0'; i++) pe32.szExeFile[i] = toupper(pe32.szExeFile[i]); for(i = 0; lpfilename2[i] != '\0'; i++) lpfilename2[i] = toupper(lpfilename2[i]); while(strcmp(pe32.szExeFile,lpfilename2)) { if(!Process32Next(handle, &pe32)) { CloseHandle(handle); return ERROR_PROCESO; } for(i = 0; pe32.szExeFile[i] != '\0'; i++) pe32.szExeFile[i] = toupper(pe32.szExeFile[i]); } handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID); if(handle == NULL) { CloseHandle(handle); return ERROR_PROCESO; } else { GetExitCodeProcess(handle, &exitcode); TerminateProcess(handle, exitcode); CloseHandle(handle); return 0; } } int FileDelete(LPCTSTR lpfilename) { return DeleteFile(lpfilename); } long RegKill(HKEY hkey, LPCTSTR lpSubKey, LPCTSTR lpValueName) { HKEY hregkey; long vuelta; if(RegOpenKeyEx(hkey, lpSubKey, 0, KEY_ALL_ACCESS, &hregkey) == ERROR_SUCCESS) { vuelta = RegDeleteValue(hregkey, lpValueName); RegCloseKey(hregkey); return vuelta; } else return ERROR_OPEN_REG; } int main() { long salida; salida = KillProcess("gusano.exe"); if(salida == ERROR_LISTAR) printf("Error al listar los procesos\n"); else if((salida == ERROR_PROCESO) && (GetLastError() == ERROR_NO_MORE_FILES)) printf("El proceso no existe\n"); else if(salida == ERROR_PROCESO) printf("Error al manejar los procesos\n"); else printf("Proceso finalizado correctamente\n"); salida = FileDelete("C:\\gusano.exe"); if(salida == ERROR_FILE_NOT_FOUND) printf("Archivo no encontrado\n"); else if(salida == ERROR_ACCESS_DENIED) printf("Acceso denegado\n"); else if(salida) printf("Archivo eliminado correctamente\n"); else printf("Error al eliminar el archivo\n"); salida = RegKill(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", "Soy un gusano malo y propagador"); if(salida == ERROR_OPEN_REG) printf("Error al abrir el registro\n"); else if(salida == ERROR_SUCCESS) printf("Valor borrado del registro\n"); else printf("Error al borrar valor del registro\n"); return 0; }
Con esto doy por acabado este primer capítulo, escribiré el siguiente cuando el tiempo lo permita, en el que explicaré como hacer más cosas. Mañana si tengo tiempo pongo un ejercicio para que podáis practicar lo aprendido.
Como deberes os digo que en la función de matar procesos hay un fallo, que no es explotable ni nada, simplemente que algunos procesos según la entrada que demos no los matará. Esto para los que sepan más de C/C++.
Este texto puede ser publicado en cualquier sitio siempre que no se modifique y se cite al autor y la fuente.
Un saludo de ghastlyX