Hola..

Con esta quinta entrega, terminamos esta serie dedicada a la inyeccion de DLL o DLL Injection, vamos a ello.

El mapeo manual o manual mapping es una técnica aún más sigilosa para realizar la inyección de DLL. Esta técnica implica escribir una DLL en la memoria de un proceso, arreglar sus reubicaciones e iniciar un hilo en su punto de entrada.

Puedes pensar en el mapeo manual como básicamente implementar tu propia versión ligera de [Enlace externo eliminado para invitados]. Esta implementación liviana es lo que le da a la técnica su sigilo: solo está implementando lo esencial para cargar su DLL, a diferencia de lo que hace la implementación de Windows de LoadLibraryA , que es cargar su DLL pero también registrar su existencia con varias estructuras de datos de Windows.

Con el mapeo manual, su DLL puede ejecutarse dentro de otro proceso, sin que ese proceso pueda detectar fácilmente la presencia de su DLL.Asignación de bytes DLL al proceso de destinoPara comenzar a realizar el mapeo manual, primero debe obtener los bytes del archivo DLL y escribirlos en el proceso de destino.

Código: Seleccionar todo

std::vector<char> GetDllFileBytes(const std::string& fullModulePath) {

std::ifstream fileStream(fullModulePath.c_str(),
std::ios::in | std::ios::binary | std::ios::ate);

const auto fileSize{ fileStream.tellg() };
fileStream.seekg(0, std::ios::beg);

std::vector<char> fileBytes(fileSize);
fileStream.read(fileBytes.data(), fileSize);

return fileBytes;
}

void* WriteDllFileBytesToProcess(const HANDLE processHandle,
const std::vector<char>& fileBytes) {

const auto dosHeader{ reinterpret_cast<const IMAGE_DOS_HEADER*>(
fileBytes.data()) };
const auto ntHeader{ reinterpret_cast<const IMAGE_NT_HEADERS*>(
fileBytes.data() + dosHeader->e_lfanew) };

const auto remoteBaseAddress{ VirtualAllocEx(processHandle, nullptr,
ntHeader->OptionalHeader.SizeOfImage, MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE) };
if (remoteBaseAddress == nullptr) {
PrintErrorAndExit("VirtualAllocEx");
}

const auto* currentSection{ IMAGE_FIRST_SECTION(ntHeader) };
for (size_t i{}; i < ntHeader->FileHeader.NumberOfSections; i++) {

SIZE_T bytesWritten{};
auto result{ WriteProcessMemory(processHandle,
static_cast<char*>(remoteBaseAddress) + currentSection->VirtualAddress,
fileBytes.data() + currentSection->PointerToRawData,
currentSection->SizeOfRawData, &bytesWritten) };
if (result == 0 || bytesWritten == 0) {
PrintErrorAndExit("WriteProcessMemory");
}

currentSection++;
}

SIZE_T bytesWritten{};
const auto result{ WriteProcessMemory(processHandle, remoteBaseAddress,
fileBytes.data(), REMOTE_PE_HEADER_ALLOC_SIZE, &bytesWritten) };
if (result == 0 || bytesWritten == 0) {
PrintErrorAndExit("WriteProcessMemory");
}

return remoteBaseAddress;
}
La función GetDllFileBytes lee la DLL en un búfer. La función WriteDllFileBytesToProcess escribirá los bytes en un espacio de direcciones de procesos de destino.

GetDllFileBytes toma la ruta absoluta de la DLL y es responsable de leer los bytes del archivo en un vector que se devuelve a la persona que llama. . Una vez que se obtienen los bytes del archivo, la función WriteDllFileBytesToProcess escribirá estos bytes en el espacio de direcciones del proceso de destino. La función WriteDllFileBytesToProcess comienza llamando a VirtualAllocEx para asignar un bloque de memoria en el proceso objetivo.

El tamaño de este bloque es igual al campo SizeOfImage del encabezado del ejecutable portátil (PE), que indica el tamaño de la DLL cargada en la memoria. Cada sección, tal como se define en el encabezado de la sección PE, se escribe en el bloque. Por último, el encabezado PE se escribe en la dirección base del bloque.

Reubicación de la dirección base o Base address relocation

Con la DLL escrita en la memoria, puede comenzar la parte divertida de implementar el cargador. El cargador deberá realizar tres pasos antes de poder llamar a la función DllMain de la DLL: reubicación base de la DLL, resolución de las importaciones de la DLL y escritura en sus direcciones absolutas a la tabla de direcciones de importación e invocar cualquier devoluciones de llamada de almacenamiento local de subprocesos (TLS) que estén presentes en la DLL.

Al igual que en la técnica de secuestro de contexto, este código auxiliar se escribirá en el proceso de destino y se ejecutará para inyectar la DLL. Sin embargo, afortunadamente, el código auxiliar se puede escribir en C++ en lugar de necesitar un ensamblado x64.

Dado que el código auxiliar se escribirá en C++ y luego sus instrucciones de ensamblaje se escribirán en el proceso de destino, el código auxiliar debe codificarse de tal manera que el compilador genere independiente de la posición. código (PIC).

Esto significa que el compilador generará un código auxiliar que se puede ejecutar independientemente de dónde esté escrito en la memoria, como será el caso ya que VirtualAllocEx probablemente devolverá una dirección diferente cada vez.

Para que el compilador genere el código ensamblador independiente de la posición, no puede llamar a otras funciones, usar variables globales ni hacer referencia a nada fuera del alcance de las funciones. La única referencia externa permitida que tendrá el código auxiliar será su argumento, que será un puntero a los valores que necesite.

Código: Seleccionar todo

using LoadLibraryAPtr = HMODULE(__stdcall*)(LPCSTR lpLibFileName);
using GetProcAddressPtr = FARPROC(__stdcall*)(HMODULE hModule, LPCSTR lpProcName);

typedef struct {
void* const remoteDllBaseAddress;
LoadLibraryAPtr remoteLoadLibraryAAddress;
GetProcAddressPtr remoteGetProcAddressAddress;
} RelocationStubParameters;
La estructura RelocationStubParameters contiene la información que necesitará el código auxiliar.

Estos parámetros se completarán y escribirán en el espacio de direcciones del proceso de destino, de modo que estén disponibles para su uso dentro del código auxiliar. Como sugieren sus nombres, los tres datos que necesitará el código auxiliar son la dirección base de la DLL y la dirección de LoadLibraryA y . Funciones GetProcAddress. Con los parámetros identificados, se puede implementar el stub.

Código: Seleccionar todo

void RelocationStub(RelocationStubParameters* parameters) {

const auto dosHeader{ reinterpret_cast<IMAGE_DOS_HEADER*>(
parameters->remoteDllBaseAddress) };
const auto ntHeader{ reinterpret_cast<IMAGE_NT_HEADERS*>(
reinterpret_cast<DWORD_PTR>(
parameters->remoteDllBaseAddress) + dosHeader->e_lfanew) };

const auto relocationOffset{ reinterpret_cast<DWORD_PTR>(
parameters->remoteDllBaseAddress) - ntHeader->OptionalHeader.ImageBase };

typedef struct {
WORD offset : 12;
WORD type : 4;
} RELOCATION_INFO;

const auto* baseRelocationDirectoryEntry{
reinterpret_cast<IMAGE_BASE_RELOCATION*>(
reinterpret_cast<DWORD_PTR>(parameters->remoteDllBaseAddress) +
ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]
.VirtualAddress) };

while (baseRelocationDirectoryEntry->VirtualAddress != 0) {

const auto relocationCount{
(baseRelocationDirectoryEntry->SizeOfBlock –
sizeof(IMAGE_BASE_RELOCATION)) / sizeof(RELOCATION_INFO) };

const auto* baseRelocationInfo{ reinterpret_cast<RELOCATION_INFO*>(
reinterpret_cast<DWORD_PTR>(
baseRelocationDirectoryEntry) + sizeof(RELOCATION_INFO)) };

for (size_t i{}; i < relocationCount; i++, baseRelocationInfo++) {
if (baseRelocationInfo->type == IMAGE_REL_BASED_DIR64) {
const auto relocFixAddress{ reinterpret_cast<DWORD*>(
reinterpret_cast<DWORD_PTR>(parameters->remoteDllBaseAddress) +
baseRelocationDirectoryEntry->VirtualAddress +
baseRelocationInfo->offset) };
*relocFixAddress += static_cast<DWORD>(relocationOffset);
}
}

baseRelocationDirectoryEntry = reinterpret_cast<IMAGE_BASE_RELOCATION*>(
reinterpret_cast<DWORD_PTR>(baseRelocationDirectoryEntry) +
baseRelocationDirectoryEntry->SizeOfBlock);
}

const auto* baseImportsDirectory{
reinterpret_cast<IMAGE_IMPORT_DESCRIPTOR*>(
reinterpret_cast<DWORD_PTR>(parameters->remoteDllBaseAddress) +
ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
.VirtualAddress) };

for (size_t index{}; baseImportsDirectory[index].Characteristics != 0; index++){

const auto* const moduleName{ RvaToPointer(char*,
parameters->remoteDllBaseAddress,
baseImportsDirectory[index].Name) };
const auto loadedModuleHandle{
parameters->remoteLoadLibraryAAddress(moduleName) };

auto* addressTableEntry{ RvaToPointer(IMAGE_THUNK_DATA*,
parameters->remoteDllBaseAddress,
baseImportsDirectory[index].FirstThunk) };
const auto* nameTableEntry{ RvaToPointer(IMAGE_THUNK_DATA*,
parameters->remoteDllBaseAddress,
baseImportsDirectory[index].OriginalFirstThunk) };

if (nameTableEntry == nullptr) {
nameTableEntry = addressTableEntry;
}

for (; nameTableEntry->u1.Function != 0;
nameTableEntry++, addressTableEntry++) {

const auto* const importedFunction{ RvaToPointer(IMAGE_IMPORT_BY_NAME*,
parameters->remoteDllBaseAddress, nameTableEntry->u1.AddressOfData)
};

if (nameTableEntry->u1.Ordinal & IMAGE_ORDINAL_FLAG) {

addressTableEntry->u1.Function = reinterpret_cast<ULONGLONG>(
parameters->remoteGetProcAddressAddress(loadedModuleHandle,
MAKEINTRESOURCEA(nameTableEntry->u1.Ordinal)));
}
else {
addressTableEntry->u1.Function = reinterpret_cast<ULONGLONG>(
parameters->remoteGetProcAddressAddress(loadedModuleHandle,
importedFunction->Name));
}
}
}

if (ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].Size > 0){
const auto* baseTlsEntries{
reinterpret_cast<IMAGE_TLS_DIRECTORY*>(
reinterpret_cast<DWORD_PTR>(parameters->remoteDllBaseAddress) +
ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS]
.VirtualAddress) };

const auto* tlsCallback{ reinterpret_cast<PIMAGE_TLS_CALLBACK*>(
baseTlsEntries->AddressOfCallBacks) };
while (tlsCallback != nullptr) {
(*tlsCallback)(parameters->remoteDllBaseAddress, DLL_PROCESS_ATTACH,
nullptr);
tlsCallback++;
}
}

using DllMainPtr = BOOL(__stdcall*)(HINSTANCE hinstDLL,
DWORD fdwReason, LPVOID lpvReserved);

const auto DllMain{ reinterpret_cast<DllMainPtr>(
reinterpret_cast<DWORD_PTR>(parameters->remoteDllBaseAddress) +
ntHeader->OptionalHeader.AddressOfEntryPoint) };

DllMain(reinterpret_cast<HINSTANCE>(parameters->remoteDllBaseAddress),
DLL_PROCESS_ATTACH, nullptr);
}
La implementación del código auxiliar de reubicación.

El código auxiliar comenzará realizando la reubicación base de la DLL obteniendo primero el inicio de la tabla de reubicación base. Para cada entrada de la tabla, se recupera el campo que contiene el número de reubicaciones presentes. Luego, el tipo de reubicación se compara con IMAGE_REL_BASED_DIR64 para ver si es una reubicación que se aplica a un campo de 64 bits. Si ese es el caso, entonces la dirección se ajusta para compensar la dirección de carga de la DLL y el desplazamiento de reubicación. Este proceso continúa en un bucle para cada entrada de reubicación de base en la tabla.Arreglando importacionesDespués de realizar la reubicación de la base, las importaciones de la DLL deben arreglarse con direcciones absolutas. Para hacer esto, el código auxiliar obtiene la base del directorio de importación. Para cada importación, el código auxiliar encontrará el módulo al que pertenece la importación y lo cargará, luego iterará sobre las tablas de nombres de importación y direcciones de importación.

Se llamará a la función [Enlace externo eliminado para invitados] para cada nombre de importación y ordinal, y la dirección absoluta se escribirá en la entrada de la tabla de direcciones de importación que corresponde a la importación. Puede haber lo que parece ser una llamada de función a RvaToPointer, pero como el código auxiliar debe ser independiente de la posición, RvaToPointer se ha redefinido como una macro.

Código: Seleccionar todo

#define RvaToPointer(type, baseAddress, offset) \
reinterpret_cast<type>( \
reinterpret_cast<DWORD_PTR>(baseAddress) + offset)
La macro RvaToPointer para convertir una dirección virtual relativa en un puntero.Invocar llamadas  TLS

Por último, el código auxiliar debe invocar cualquier devolución de llamada TLS que esté presente en la DLL. Esto se hace obteniendo la base del directorio TLS, que tendrá una serie de punteros de función. Estos punteros de función son devoluciones de llamada TLS y cada uno de ellos se invoca por turno.

Una vez que se hayan llamado todas las devoluciones de llamada TLS, se puede llamar a la función DllMain. Este será el punto de entrada de la DLL y llamará a su DllMain definido después de ejecutar algunas funciones de inicialización de inicio.Escribir el talón de reubicación o relocation stubUna vez generado el código auxiliar, ahora se puede escribir en el proceso de destino.

Código: Seleccionar todo

std::pair<void*, void*> WriteRelocationStubToTargetProcess(
const HANDLE processHandle, const RelocationStubParameters& parameters) {

auto* const remoteParametersAddress{ VirtualAllocEx(processHandle, nullptr,
REMOTE_RELOC_STUB_ALLOC_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE) };
if (remoteParametersAddress == nullptr) {
PrintErrorAndExit("VirtualAllocEx");
}

SIZE_T bytesWritten{};
auto result{ WriteProcessMemory(processHandle, remoteParametersAddress,
&parameters, sizeof(RelocationStubParameters),
&bytesWritten) };
if (result == 0 || bytesWritten == 0) {
PrintErrorAndExit("WriteProcessMemory");
}

auto* const remoteRelocationStubAddress{ VirtualAllocEx(processHandle, nullptr,
REMOTE_RELOC_STUB_ALLOC_SIZE,
MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE) };
if (remoteRelocationStubAddress == nullptr) {
PrintErrorAndExit("VirtualAllocEx");
}

result = WriteProcessMemory(processHandle, remoteRelocationStubAddress,
RelocationStub, REMOTE_RELOC_STUB_ALLOC_SIZE, &bytesWritten);
if (result == 0 || bytesWritten == 0) {
PrintErrorAndExit("WriteProcessMemory");
}

return std::make_pair(remoteRelocationStubAddress, remoteParametersAddress);
}
La función WriteRelocationStubToTargetProcess escribirá los parámetros y reubicará el código auxiliar en un proceso de destino.

El WriteRelocationStubToTargetProcess toma un identificador de proceso para el proceso de destino y una referencia a los parámetros del código auxiliar. Los parámetros del código auxiliar y el código auxiliar en sí se escribirán en el proceso de destino en dos bloques de memoria distintos. Las direcciones de estos bloques se devolverán como un par a la persona que llama.

Creando el hilo remoto o remote thread

Ahora todo lo que queda por hacer es ejecutar el código auxiliar. Esto implicará la creación de un subproceso para que se ejecute la DLL y se logra llamando a [Enlace externo eliminado para invitados], pasando la dirección del código auxiliar como punto de entrada del subproceso y el Los parámetros del código auxiliar se dirigen como los parámetros del hilo.

Código: Seleccionar todo

void InjectByManualMapping(const DWORD processId,
const std::string& fullModulePath) {

const auto processHandle{ GetTargetProcessHandle(processId) };
const auto fileBytes{ GetDllFileBytes(fullModulePath) };

auto* const remoteDllBaseAddress{ WriteDllFileBytesToProcess(
processHandle, fileBytes) };
auto* const remoteLoadLibraryAddress{ GetRemoteModuleFunctionAddress(
"kernel32.dll", "LoadLibraryA", processId) };
auto* const remoteGetProcAddressAddress{ GetRemoteModuleFunctionAddress(
"kernel32.dll", "GetProcAddress", processId) };

const RelocationStubParameters parameters{
.remoteDllBaseAddress = remoteDllBaseAddress,
.remoteLoadLibraryAAddress = reinterpret_cast<LoadLibraryAPtr>(
remoteLoadLibraryAddress),
.remoteGetProcAddressAddress = reinterpret_cast<GetProcAddressPtr>(
remoteGetProcAddressAddress)
};

const auto relocationInfo{
WriteRelocationStubToTargetProcess(processHandle, parameters) };

const auto remoteThread{ CreateRemoteThreadEx(processHandle, nullptr, 0,
reinterpret_cast<LPTHREAD_START_ROUTINE>(relocationInfo.first),
relocationInfo.second, 0, nullptr, 0) };
if (remoteThread == nullptr) {
PrintErrorAndExit("CreateRemoteThreadEx");
}
}

int main(int argc, char* argv) {

const auto fullModulePath{ GetInjectedDllPath("Ch10_GenericDll.dll") };

const auto processId{ GetTargetProcessAndThreadId(
"Untitled - Notepad").first };

InjectByManualMapping(processId, fullModulePath);

return 0;
}
La implementación del cargador de mapeador manual.

El hilo remoto comenzará su ejecución en la dirección del código auxiliar de reubicación, con un puntero a sus parámetros como argumento para el código auxiliar. El código auxiliar comenzará la ejecución, realizará las correcciones apropiadas para la DLL y llamará a la función DllMain.

En el momento en que se llama a la función DllMain, la DLL tendrá su propio subproceso para ejecutarse y habrá sido completamente configurada para ejecutarse dentro del proceso de destino.Ejecutando la demostraciónNota: si está utilizando el nuevo Bloc de notas para UWP que se encuentra en la última versión de Windows, deberá [Enlace externo eliminado para invitados] para que la demostración funcione.El proyecto [Enlace externo eliminado para invitados] proporciona la implementación completa que se presentó en esta sección. Para probar esto localmente, cree el proyecto GenericDll y el cargador ManualMapper. proyecto.

* El proyecto ManualMapper solo se compila en modo de lanzamiento o Release. Esto es para eliminar indicadores del compilador que harían que el código auxiliar de reubicación generara código que no es completamente independiente de la posición.

Después de una compilación exitosa, inicie el Bloc de notas y luego la aplicación de carga. Verá el conocido mensaje "¡DLL inyectada!" cuadro de mensaje emergente. No descarte este cuadro de mensaje todavía. En su lugar, abra [Enlace externo eliminado para invitados] y busque el proceso notepad.exe.

Al mirar la pestaña Módulos, debería ver que GenericDll.dll no aparece en la lista, a pesar de que claramente está cargado y ejecutándose, ya que hay un cuadro de mensaje emergente. Esto muestra que la DLL se inyectó exitosamente en el proceso notepad.exe, pero de tal manera que no es detectable.Para ver esto realmente usted mismo, puede ver cómo se ejecuta el código auxiliar de reubicación de mapeo manual en el proceso del Bloc de notas. Como antes, abra [Enlace externo eliminado para invitados] y adjunte al proceso notepad.exe.

Asegúrese de que x64dbg no esté roto y que el Bloc de notas esté ejecutándose. Navegue a Visual Studio y establezca un punto de interrupción en la llamada CreateRemoteThreadEx. Inicie la aplicación del cargador y copie la dirección de inicio que el cargador envía a la consola. Navegue a esta dirección en x64dbg. Debería ver las instrucciones del talón de reubicación como se muestra a continuación.
Imagen
Las instrucciones de reubicación del código auxiliar en la dirección inicial del código auxiliar.

Establezca un punto de interrupción o breakpoint en la primera instrucción y regrese a Visual Studio. Antes de reanudar la ejecución en Visual Studio, abra la ventana Desensamblado. Escriba RelocationStub en la ventana Dirección para navegar hasta las instrucciones de ensamblaje del código auxiliar de reubicación.
Imagen
Las instrucciones de ensamblaje de RelocationStub generadas, junto con sus asignaciones de origen.

Copie y pegue todo el desmontaje de la función RelocationStub en otro editor de texto. Esto le permitirá mapear fácilmente lo que ve en x64dbg con las líneas del código fuente original.

Después de hacer esto, reanude la ejecución del cargador en Visual Studio. El cargador creará el subproceso remoto para iniciar la ejecución del código auxiliar de reubicación y luego saldrá. Vuelva a x64dbg después de que el cargador haya finalizado y finalizado la ejecución.

En este punto, debería alcanzarse su punto de interrupción. Puede recorrer el código auxiliar de reubicación en x64dbg, mientras hace referencia al código fuente original al que se asignan las instrucciones. Esto hará que sea más fácil comprender qué está sucediendo y cómo el código auxiliar de reubicación realiza su lógica mientras se ejecuta en el contexto del proceso del Bloc de notas.

Bueno, pues hasta aqui esta serie de posts, en relacion a la inyeccion de DLL o DLL Injection, espero que le sirva a alguien, poco a poco ire poniendo cosas, por si le sirve a alguien.

Saludos a tod@s...

cLn

Nota: Podeis ver esta serie en ingles en la siguiente pagina:

[Enlace externo eliminado para invitados]

Nota2: Aqui los codigos de ejemplo:

[Enlace externo eliminado para invitados]







 
Imagen


Solo lo mejor es suficiente...
Responder

Volver a “Manuales”