Introducción
El propósito de esta publicación es discutir un Código Independiente de Posición (PIC) escrito en C que se utilizará para demostrar la inyección de procesos en el sistema operativo Windows. La carga útil simplemente ejecutará una instancia de la calculadora, y no pretende ser maliciosa de ninguna manera. En las publicaciones de seguimiento, analizaré varios métodos de inyección para ayudar al lector a comprender cómo funcionan. A continuación se muestra una captura de pantalla del método PROPagate en acción, que espero discutir en una publicación de seguimiento.

Prototipos de funciones
La mayoría de los métodos de inyección requieren un PIC para ejecutarse con éxito en un espacio de proceso remoto, a menos que, por supuesto, uno quiera cargar una biblioteca de enlace dinámico. (DLL) En el caso de cargar una DLL, solo se necesita ejecutar la API LoadLibrary que proporciona la ruta de una DLL como parámetro. Tradicionalmente, los PIC se han escrito en C o ensamblado, pero existen problemas al usar código de ensamblado puro. Tenga en cuenta que Windows puede ejecutarse en múltiples arquitecturas (por ejemplo, x86, amd64, arm), con múltiples convenciones de llamadas (por ejemplo, stdcall, fastcall). ¿Qué sucede si se requieren correcciones o cambios en el código? Por esas razones, tiene más sentido usar un lenguaje de alto nivel (HLL) como C.
Algunos de los métodos que se discutirán tienen requisitos individuales. Algunas de las funciones de devolución de llamada ejecutadas pasarán una serie de parámetros, y debido a la convención de llamada esperarán que esos parámetros se eliminen al regresar a la persona que llama.
Procedimiento de hilo
La creación de un nuevo hilo en un proceso remoto se puede realizar utilizando una de las siguientes API.
- CreateRemoteThread
- RtlCreateUserThread
- NtCreateThreadEx
- ZwCreateThreadEx
Cada API espera un puntero a una función de devolución de llamada ThreadProc. Lo siguiente se define en el SDK de Windows. Esto también es perfecto para LoadLibrary que solo espera un parámetro.
DWORD WINAPI ThreadProc ( _In_ LPVOID lpParameter ) ;
Llamada a procedimiento asincrónico
También es posible adjuntar un PIC a un hilo existente utilizando una de las siguientes API. El hilo debe ser alertable para que esto funcione, y no hay una manera conveniente de determinar si un hilo es alertable o no.
- QueueUserAPC
- NtQueueApcThread
- NtQueueApcThreadEx
- ZwQueueApcThread
- ZwQueueApcThreadEx
- RtlQueueApcWow64Thread
VOID CALLBACK APCProc ( _IN_ ULONG_PTR dwParam ) ;
Función de devolución de llamada de WindowProc
¿Algunos de ustedes sabrán del método de inyección de memoria extra de ventana (EWM)? El método bien conocido implica reemplazar el objeto "CTray" que se usa para controlar el comportamiento de la clase "Shell_TrayWnd" registrada por explorer.exe. Algunas versiones de Windows permiten actualizar este objeto a través de SetWindowLongPtr, por lo tanto, permite la ejecución de código sin la creación de un nuevo hilo, pero más sobre eso más adelante.
La siguiente API se puede utilizar para actualizar la función de devolución de llamada de la ventana.
- SetWindowLong (32 bits)
- GetWindowLong (32 bits)
- SetWindowLongPtr (32 y 64 bits)
- GetWindowLongPtr (32 y 64 bits)
- SetClassLongPtr (32 y 64 bits)
- GetClassLongPtr (32 y 64 bits)
LRESULT CALLBACK WindowProc ( _In_ HWND hwnd , _In_ UINT uMsg , _In_ WPARAM wParam , _In_ LPARAM lParam ) ;
Función de devolución de llamada de subclase
El método PROPagate lleva el nombre de las API GetProp y SetProp utilizadas para cambiar la dirección de una función de devolución de llamada para una ventana subclasificada. A partir de julio de 2018, esta es una técnica relativamente nueva que es similar a la inyección EWM o los ataques de ruptura descritos en 2002.
La siguiente API (pero no todas) puede ser de interés.
- GetProp
- SetProp
- EnumProps
typedef LRESULT ( CALLBACK * SUBCLASSPROC ) ( HWND hWnd , UINT uMsg , WPARAM wParam , LPARAM lParam , UINT_PTR uIdSubclass , DWORD_PTR dwRefData ) ;
Biblioteca de enlace dinámico (DLL)
Aunque no es un PIC, la inyección de proceso a veces puede implicar cargar una DLL. Compilar la carga útil en un archivo DLL es, por lo tanto, una opción.
__declspec ( dllexport ) BOOL WINAPI DllMain ( HINSTANCE hInstance , DWORD fdwReason , LPVOID lpvReserved ) ;
Compilando
Para generar la carga útil adecuada utilizando los parámetros correctos y el tipo de retorno, utilizamos la directiva define. Si necesitamos compilar la carga útil para otro prototipo de función, sería relativamente fácil modificar este código.
# Ifdef XAPC // Procedimiento asíncrona llamada VOID RETROLLAMADA APCProc ( ULONG_PTR dwParam ) # endif # Ifdef HILO // Crear remota hilo DWORD WINAPI ThreadProc ( LPVOID lpParameter ) # endif # Ifdef SUBCLASE // Ventana subclase procedimiento de devolución de llamada LRESULT CALLBACK SubclassProc ( HWND hWnd , UINT uMsg , WPARAM wParam , LPARAM lParam , UINT_PTR uIdSubclass , DWORD_PTR dwRefData ) # endif # ifdef WINDOW // Procedimiento de devolución de llamada de ventana LRESULT CALLBACK WndProc ( HWND hWnd , UINT uMsg , WPARAM wParam , LPARAM lParam ) # endif # ifdef DLL // compilar como Dynamic-link Library # advertencia pragma (push) # advertencia pragma (deshabilitar: 4100) __declspec ( dllexport ) BOOL WINAPI DllMain ( HINSTANCE hInstance , DWORD fdwReason , LPVOID lpvReserved ) # endif
¿Qué pasa si la carga útil se llama varias veces? Para cada método discutido, abordaré esa pregunta.
Declarando cuerdas
Si declara una cadena en C y compila la fuente en un ejecutable, puede encontrar literales de cadena almacenados en una sección de memoria de solo lectura. Este es un problema para un PIC porque necesitamos todas las variables, incluidas las cadenas almacenadas en la pila. Para ilustrar el problema, considere el siguiente fragmento de código simple.
# include < stdio.h > int main ( void ) { char msg [ ] = " ¡Hola, mundo! \ n " ; printf ( " % s " , mensaje ) ; devuelve 0 ; }
Aunque esta salida de ensamblaje del compilador MSVC no es muy legible, muestra que la pila no se usa para el almacenamiento local del literal de cadena.

La solución podría ser declarar la cadena como una matriz de bytes, como la siguiente.
# include < stdio.h > int main ( void ) { char msg [ ] = { 'H' , 'e' , 'l' , 'l' , 'o' , ',' , '' , 'W' , 'o' , 'r' , 'l' , 'd' , '!' , '\ n' , 0 } ; printf ( " % s " , mensaje ) ; devuelve 0 ; }
El resultado del ensamblaje de esto muestra que MSVC usará la pila local.

Esto funciona para MSVC, pero desafortunadamente no para GCC que aún almacenará la cadena en la sección de solo lectura del ejecutable. ¿Qué otras opciones hay?
Usar ensamblaje en línea
Si no recuerdo mal, fue Z0MBiE / 29a quien una vez sugirió en Solucionar problemas de cadenas simples en HLL usando un ensamblaje basado en macro con el compilador Borland C para almacenar cadenas en la pila con el fin de ofuscarlas. En Phrack # 69, Shellcode de la mejor manera, o cómo usar su compilador por fishstiqz sugiere usar una macro en línea.
# define INLINE_STR ( name , str ) \ const char * name ; \ asm ( \ " call 1f \ n " \ " .asciz \" " str " \ " \ n " \ " 1: \ n " \ " pop% 0 \ n " \ : " = r " \ ) ;
La macro se puede usar con lo siguiente.
INLINE_STR ( kernel32 , " kernel32 " ) ; PVOID pKernel32 = scGetModuleBase ( kernel32 ) ;
Dependemos del código de ensamblaje aquí, y eso es precisamente lo que debemos evitar. Tampoco podemos ensamblar en línea los 64 bits, como señala fishstiqz. ¿Qué más?
Declaración utilizando matrices
Después de examinar varias opciones y de haber seguido el consejo de otros (@solardiz) declarando cadenas como matrices de 32 bits, se resuelve el problema tanto para GCC como para MSVC.
# include < stdio.h > typedef unsigned int W ; int main ( void ) { W msg [ 4 ] ; msg [ 0 ] = * ( W * ) " Infierno " ; msg [ 1 ] = * ( W * ) " o, W " ; msg [ 2 ] = * ( W * ) " orld " ; msg [ 3 ] = * ( W * ) " ! \ n " ; printf ( " % s " , ( char * ) msg ) ; devuelve 0 ; }
Como puede ver en la salida de MSVC y GCC, ambos colocan la cadena en la pila.
Salida MSVC

Salida de GCC

Por supuesto, tendría sentido automatizar este proceso, en lugar de intentar inicializar manualmente 
Declaración de punteros de función
No tiene que declarar punteros de función de esta manera, pero para un shellcode, hace que el código sea mucho más fácil de leer.
typedef HANDLE ( WINAPI * OpenEvent_t ) ( _In_ DWORD dwDesiredAccess , _In_ BOOL bInheritHandle , _In_ LPCTSTR lpName ) ; typedef BOOL ( WINAPI * SetEvent_t ) ( _In_ HANDLE hEvent ) ; typedef BOOL ( WINAPI * CloseHandle_t ) ( _In_ HANDLE hOject ) ; typedef UINT ( WINAPI * WinExec_t ) ( _In_ LPCSTR lpCmdLine , _In_ UINT uCmdShow ) ;
Una vez que se define la función, puede declararla dentro del código principal, así.
SetEvent_t pSetEvent ; OpenEvent_t pOpenEvent ; CloseHandle_t pCloseHandle ; WinExec_t pWinExec ;
Resolviendo direcciones API
En lugar de cubrir el mismo terreno, recomiendo leer dos publicaciones sobre esta tarea. Resolviendo direcciones API en memoria y Fido, cómo resuelve GetProcAddress y LoadLibraryA . Ambos cubren algunas buenas formas de resolver API dinámicamente utilizando la tabla de direcciones de importación (IAT) y la tabla de direcciones de exportación (EAT) de un archivo ejecutable portátil (PE).
Atravesar los módulos del Bloque de entorno de proceso (PEB) se encuentra comúnmente en los códigos de shell, y se requiere que un PIC resuelva la dirección de las funciones API.
// busca en todos los módulos del PEB API LPVOID xGetProcAddress ( LPVOID pszAPI ) { PPEB peb ; PPEB_LDR_DATA ldr ; PLDR_DATA_TABLE_ENTRY dte ; LPVOID api_adr = NULL ; # si está definido ( _WIN64 ) peb = ( PPEB ) __readgsqword ( 0x60 ) ; # else peb = ( PPEB ) __readfsdword ( 0x30 ) ; # endif ldr = ( PPEB_LDR_DATA ) peb - > Ldr ; // para cada DLL cargado DTE = ( PLDR_DATA_TABLE_ENTRY ) LDR - > InLoadOrderModuleList . Flink ; para ( ; DTE - > DllBase ! = NULL y Y api_adr = = NULL ; DTE = ( PLDR_DATA_TABLE_ENTRY ) DTE - > InLoadOrderLinks . Flink ) { // buscar en la tabla de exportación de API api_adr =FindExport ( DTE - > DllBase , ( PChar ) pszAPI ) ; } return api_adr ; }
Buscar en la tabla de direcciones de exportación (EAT)
Ciertamente, se puede buscar en el IAT la dirección de la API, pero a continuación se utiliza el EAT. Esta función no maneja referencias hacia adelante y también estamos buscando por cadena, en lugar de hash, que normalmente se usa en shellcode.
// localizar la dirección de API en la tabla de direcciones de exportación LPVOID FindExport ( LPVOID de base , PChar pszAPI ) { dos PIMAGE_DOS_HEADER ; PIMAGE_NT_HEADERS nt ; DWORD cnt , rva , dll_h ; Directorio PIMAGE_DATA_DIRECTORY ; PIMAGE_EXPORT_DIRECTORY exp ; PDWORD adr ; PDWORD sym ; PWORD ord ; PCHAR api , dll ; LPVOID api_adr = NULL ; dos = ( PIMAGE_DOS_HEADER ) base ; nt = RVA2VA ( PIMAGE_NT_HEADERS , base , dos - > e_lfanew ) ; dir = ( PIMAGE_DATA_DIRECTORY ) nt - > OpcionalHeader . DataDirectory ; rva = dir [ IMAGE_DIRECTORY_ENTRY_EXPORT ] . VirtualAddress ; // si no hay tabla de exportación, devuelve NULL if ( rva = = 0 ) return NULL ; exp = ( PIMAGE_EXPORT_DIRECTORY ) RVA2VA ( ULONG_PTR , base , rva ) ; cnt = exp - > NumberOfNames ; // si no hay nombres de API, devuelve NULL if ( cnt = = 0 ) return NULL ; adr = RVA2VA ( PDWORD , base , exp - > AddressOfFunctions ) ; sym = RVA2VA ( PDWORD , base , exp - > AddressOfNames ) ; ord = RVA2VA ( PWORD , base , exp - > AddressOfNameOrdinals ) ; dll = RVA2VA ( PCHAR , base , exp- > Nombre ) ; do { // calcular el hash de la cadena api api = RVA2VA ( PCHAR , base , sym [ cnt - 1 ] ) ; // agregar al hash de DLL y comparar if ( ! xstrcmp ( pszAPI , api ) ) { // dirección de retorno de la función api_adr = RVA2VA ( LPVOID , base , adr [ ord [ cnt - 1 ] ]) ; return api_adr ; } } while ( - - cnt & & api_adr = = 0 ) ; return api_adr ; }
Resumen
Si usamos C en lugar de ensamblar, escribir una carga útil no es tan difícil. Las próximas publicaciones sobre este tema cubrirán algunos métodos de inyección individualmente.
Carga útil para sistemas de 32 y 64 bits que ejecutan el bloc de notas. El bloc de notas tuvo que usarse en lugar de la calculadora porque algunos procesos host no admiten aplicaciones de metro
Comentarios
Publicar un comentario
Todos sus comentarios seran bienvenidos, no se admiten insultos todo con el debido respeto que se merece cada persona, o de lo contrario seran eliminado cada comentario inrespetuoso hacia los demas. y autores del blog tambien puedes seguirnos en:
Facebook: https://www.facebook.com/groups/HackingTeamCyber/
Grupo de Telegram: https://t.me/TheHackForceOfficial
Canal de Youtube: https://www.youtube.com/channel/UCXy8Lg28OuGuI5Z-2EWJaNA?view_as=subscriber
Canal Vimeo: https://vimeo.com/403136547?activityReferer=1
Red Social Twitter: https://twitter.com/TheHackForce