È ansible chiamare una funzione non esportata che risiede in un exe?

Mi piacerebbe chiamare una funzione che risiede in un exe di terze parti e ottenere il suo risultato. Sembra che dovrebbe esserci un modo, purché conosca l’indirizzo della funzione, la chiamata-convenzione, ecc … ma non so come.

Qualcuno sa come lo farei?

Mi rendo conto che qualsiasi soluzione sarebbe un trucco non standard, ma deve esserci un modo!


Il mio caso d’uso non-nefasto: sto decodificando un formato file per il mio software. I calcoli in questa funzione sono troppo complessi per il mio piccolo cervello da capire; Sono stato in grado di estrarre il codice assembly direttamente nella mia DLL per il test, ma ovviamente non posso rilasciarlo, perché questo sarebbe rubare. Partirò dal presupposto che gli utenti abbiano già questa particolare applicazione preinstallata, quindi il mio software funzionerà.

OK, ho messo insieme un prototipo.

Questo programma crea un’altra istanza di se stesso come un processo secondario debug.

Un breakpoint automatico verrà rilevato prima del codice di inizializzazione main () e CRT. Questo è quando possiamo cambiare la memoria e i registri del processo di debug per far sì che esegua una funzione di interesse. E questo è ciò che fa il programma.

Cerca di intercettare e gestire tutte le situazioni negative (ad es. Eccezioni impreviste) e le segnala come errori.

Una brutta situazione è davvero buona. È l’eccezione #UD dell’istruzione UD2 che il programma inserisce nel processo di debug. Usa questo #UD per interrompere l’esecuzione del processo dopo che è stata restituita la funzione di interesse.

Alcune note in più:

  1. Questo codice è solo a 32 bit. Non ho nemmeno provato a renderlo compilabile a 64 bit o supportare processi figlio a 64 bit.

  2. Questo codice probabilmente mancherà le maniglie. Vedere le descrizioni delle funzioni dell’API Debug di Windows su MSDN per scoprire dove devono essere chiusi.

  3. Questo codice è solo una prova di concetto e non supporta il passaggio e la restituzione dei dati tramite puntatori o registri diversi da EAX, ECX ed EDX. Dovrai estenderlo se necessario.

  4. Questo codice richiede alcuni privilegi per poter creare e debuggare completamente un processo. Potresti doverlo preoccupare se gli utenti del tuo programma non sono amministratori.

Godere.

Codice:

// file: unexported.c // // compile with Open Watcom C/C++: wcl386 /q /wx /we /s unexported.c // (Note: "/s" is needed to avoid stack check calls from the "unexported" // functions, these calls are through a pointer, and it'll be // uninitialized in our case.) // // compile with MinGW gcc 4.6.2: gcc unexported.c -o unexported.exe #include  #include  #include  #include  #include  #ifndef C_ASSERT #define C_ASSERT(expr) extern char CAssertExtern[(expr)?1:-1] #endif // Compile as a 32-bit app only. C_ASSERT(sizeof(void*) * CHAR_BIT == 32); #define EXC_CODE_AND_NAME(X) { X, #X } const struct { DWORD Code; PCSTR Name; } ExcCodesAndNames[] = { EXC_CODE_AND_NAME(EXCEPTION_ACCESS_VIOLATION), EXC_CODE_AND_NAME(EXCEPTION_ARRAY_BOUNDS_EXCEEDED), EXC_CODE_AND_NAME(EXCEPTION_BREAKPOINT), EXC_CODE_AND_NAME(EXCEPTION_DATATYPE_MISALIGNMENT), EXC_CODE_AND_NAME(EXCEPTION_FLT_DENORMAL_OPERAND), EXC_CODE_AND_NAME(EXCEPTION_FLT_DIVIDE_BY_ZERO), EXC_CODE_AND_NAME(EXCEPTION_FLT_INEXACT_RESULT), EXC_CODE_AND_NAME(EXCEPTION_FLT_INVALID_OPERATION), EXC_CODE_AND_NAME(EXCEPTION_FLT_OVERFLOW), EXC_CODE_AND_NAME(EXCEPTION_FLT_STACK_CHECK), EXC_CODE_AND_NAME(EXCEPTION_FLT_UNDERFLOW), EXC_CODE_AND_NAME(EXCEPTION_ILLEGAL_INSTRUCTION), EXC_CODE_AND_NAME(EXCEPTION_IN_PAGE_ERROR), EXC_CODE_AND_NAME(EXCEPTION_INT_DIVIDE_BY_ZERO), EXC_CODE_AND_NAME(EXCEPTION_INT_OVERFLOW), EXC_CODE_AND_NAME(EXCEPTION_INVALID_DISPOSITION), EXC_CODE_AND_NAME(EXCEPTION_NONCONTINUABLE_EXCEPTION), EXC_CODE_AND_NAME(EXCEPTION_PRIV_INSTRUCTION), EXC_CODE_AND_NAME(EXCEPTION_SINGLE_STEP), EXC_CODE_AND_NAME(EXCEPTION_STACK_OVERFLOW), EXC_CODE_AND_NAME(EXCEPTION_GUARD_PAGE), EXC_CODE_AND_NAME(DBG_CONTROL_C), { 0xE06D7363, "C++ EH exception" } }; PCSTR GetExceptionName(DWORD code) { DWORD i; for (i = 0; i < sizeof(ExcCodesAndNames) / sizeof(ExcCodesAndNames[0]); i++) { if (ExcCodesAndNames[i].Code == code) { return ExcCodesAndNames[i].Name; } } return "?"; } typedef enum tCallConv { CallConvCdecl, // Params on stack; caller removes params CallConvStdCall, // Params on stack; callee removes params CallConvFastCall // Params in ECX, EDX and on stack; callee removes params } tCallConv; DWORD Execute32bitFunctionFromExe(PCSTR ExeName, int FunctionAddressIsRelative, DWORD FunctionAddress, tCallConv CallConvention, DWORD CodeDataStackSize, ULONG64* ResultEdxEax, DWORD DwordParamsCount, .../* DWORD params */) { STARTUPINFO startupInfo; PROCESS_INFORMATION processInfo; DWORD dwContinueStatus = DBG_CONTINUE; // exception continuation DEBUG_EVENT dbgEvt; UCHAR* procMem = NULL; DWORD breakPointCount = 0; DWORD err = ERROR_SUCCESS; DWORD ecxEdxParams[2] = { 0, 0 }; DWORD imageBase = 0; CONTEXT ctx; va_list ap; va_start(ap, DwordParamsCount); *ResultEdxEax = 0; memset(&startupInfo, 0, sizeof(startupInfo)); startupInfo.cb = sizeof(startupInfo); memset(&processInfo, 0, sizeof(processInfo)); if (!CreateProcess( NULL, (LPSTR)ExeName, NULL, NULL, FALSE, DEBUG_ONLY_THIS_PROCESS, // DEBUG_PROCESS, NULL, NULL, &startupInfo, &processInfo)) { printf("CreateProcess() failed with error 0x%08X\n", err = GetLastError()); goto Cleanup; } printf("Process 0x%08X (0x%08X) \"%s\" created,\n" " Thread 0x%08X (0x%08X) created\n", processInfo.dwProcessId, processInfo.hProcess, ExeName, processInfo.dwThreadId, processInfo.hThread); procMem = VirtualAllocEx( processInfo.hProcess, NULL, CodeDataStackSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (procMem == NULL) { printf("VirtualAllocEx() failed with error 0x%08X\n", err = GetLastError()); goto Cleanup; } printf("Allocated RWX memory in process 0x%08X (0x%08X) " "at address 0x%08X\n", processInfo.dwProcessId, processInfo.hProcess, procMem); while (dwContinueStatus) { // Wait for a debugging event to occur. The second parameter indicates // that the function does not return until a debugging event occurs. if (!WaitForDebugEvent(&dbgEvt, INFINITE)) { printf("WaitForDebugEvent() failed with error 0x%08X\n", err = GetLastError()); goto Cleanup; } // Process the debugging event code. switch (dbgEvt.dwDebugEventCode) { case EXCEPTION_DEBUG_EVENT: // Process the exception code. When handling // exceptions, remember to set the continuation // status parameter (dwContinueStatus). This value // is used by the ContinueDebugEvent function. printf("%s (%s) Exception in process 0x%08X, thread 0x%08X\n" " Exc. Code = 0x%08X (%s), Instr. Address = 0x%08X", dbgEvt.u.Exception.dwFirstChance ? "First Chance" : "Last Chance", dbgEvt.u.Exception.ExceptionRecord.ExceptionFlags ? "non-continuable" : "continuable", dbgEvt.dwProcessId, dbgEvt.dwThreadId, dbgEvt.u.Exception.ExceptionRecord.ExceptionCode, GetExceptionName(dbgEvt.u.Exception.ExceptionRecord.ExceptionCode), dbgEvt.u.Exception.ExceptionRecord.ExceptionAddress); if (dbgEvt.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_ACCESS_VIOLATION) { ULONG_PTR* info = dbgEvt.u.Exception.ExceptionRecord.ExceptionInformation; printf(",\n Access Address = 0x%08X, Access = 0x%08X (%s)", (DWORD)info[1], (DWORD)info[0], (info[0] == 0) ? "read" : ((info[0] == 1) ? "write" : "execute")); // 8 = DEP } printf("\n"); // Get the thread context (register state). // We'll need to either display it (in case of unexpected exceptions) or // modify it (to execute our code) or read it (to get the results of // execution). memset(&ctx, 0, sizeof(ctx)); ctx.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL; if (!GetThreadContext(processInfo.hThread, &ctx)) { printf("GetThreadContext() failed with error 0x%08X\n", err = GetLastError()); goto Cleanup; } #if 0 printf(" EAX=0x%08X EBX=0x%08X ECX=0x%08X EDX=0x%08X EFLAGS=0x%08X\n" " ESI=0x%08X EDI=0x%08X EBP=0x%08X ESP=0x%08X EIP=0x%08X\n", ctx.Eax, ctx.Ebx, ctx.Ecx, ctx.Edx, ctx.EFlags, ctx.Esi, ctx.Edi, ctx.Ebp, ctx.Esp, ctx.Eip); #endif if (dbgEvt.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT && breakPointCount == 0) { // Update the context so our code can be executed DWORD mem, i, data; SIZE_T numberOfBytesCopied; mem = (DWORD)procMem + CodeDataStackSize; // Child process memory layout (inside the procMem[] buffer): // // higher // addresses // . // . UD2 instruction (causes #UD, indicator of successful // . completion of FunctionAddress()) // . // . last on-stack parameter for FunctionAddress() // . ... // . first on-stack parameter for FunctionAddress() // . // . address of UD2 instruction (as if "call FunctionAddress" // . executed just before it and is going to return to UD2) // . (ESP will point here) // . // . FunctionAddress()'s stack // . // lower // addresses mem -= 2; data = 0x0B0F; // 0x0F, 0x0B = UD2 instruction if (!WriteProcessMemory(processInfo.hProcess, (PVOID)mem, &data, 2, &numberOfBytesCopied)) { ErrWriteMem1: printf("WriteProcessMemory() failed with error 0x%08X\n", err = GetLastError()); goto Cleanup; } else if (numberOfBytesCopied != 2) { ErrWriteMem2: printf("WriteProcessMemory() failed with error 0x%08X\n", err = ERROR_BAD_LENGTH); goto Cleanup; } // Copy function parameters. mem &= 0xFFFFFFFC; // align the address for the stack for (i = 0; i < DwordParamsCount; i++) { if (CallConvention == CallConvFastCall && i < 2) { ecxEdxParams[i] = va_arg(ap, DWORD); } else { data = va_arg(ap, DWORD); if (!WriteProcessMemory(processInfo.hProcess, (DWORD*)mem - DwordParamsCount + i, &data, sizeof(data), &numberOfBytesCopied)) { goto ErrWriteMem1; } else if (numberOfBytesCopied != sizeof(data)) { goto ErrWriteMem2; } } } // Adjust what will become ESP according to the number of on-stack parameters. for (i = 0; i < DwordParamsCount; i++) { if (CallConvention != CallConvFastCall || i >= 2) { mem -= 4; } } // Store the function return address. mem -= 4; data = (DWORD)procMem + CodeDataStackSize - 2; // address of UD2 if (!WriteProcessMemory(processInfo.hProcess, (PVOID)mem, &data, sizeof(data), &numberOfBytesCopied)) { goto ErrWriteMem1; } else if (numberOfBytesCopied != sizeof(data)) { goto ErrWriteMem2; } // Last-minute preparations for execution... // Set up the registers (ECX, EDX, EFLAGS, EIP, ESP). if (CallConvention == CallConvFastCall) { if (DwordParamsCount >= 1) ctx.Ecx = ecxEdxParams[0]; if (DwordParamsCount >= 2) ctx.Edx = ecxEdxParams[1]; } ctx.EFlags &= ~(1 << 10); // clear DF for string instructions ctx.Eip = FunctionAddress + imageBase * !!FunctionAddressIsRelative; ctx.Esp = mem; if (!SetThreadContext(processInfo.hThread, &ctx)) { printf("SetThreadContext() failed with error 0x%08X\n", err = GetLastError()); goto Cleanup; } printf("Copied code/data to the process\n"); #if 0 for (i = esp; i < (DWORD)procMem + CodeDataStackSize; i++) { data = 0; ReadProcessMemory(processInfo.hProcess, (void*)i, &data, 1, &numberOfBytesCopied); printf("E[SI]P = 0x%08X: 0x%02X\n", i, data); } #endif breakPointCount++; dwContinueStatus = DBG_CONTINUE; // continue execution of our code } else if (dbgEvt.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION && breakPointCount == 1 && ctx.Eip == (DWORD)procMem + CodeDataStackSize - 2/*UD2 size*/) { // The code has finished execution as expected. // Collect the results. *ResultEdxEax = ((ULONG64)ctx.Edx << 32) | ctx.Eax; printf("Copied code/data from the process\n"); dwContinueStatus = 0; // stop debugging } else { // Unexpected event. Do not continue execution. printf(" EAX=0x%08X EBX=0x%08X ECX=0x%08X EDX=0x%08X EFLAGS=0x%08X\n" " ESI=0x%08X EDI=0x%08X EBP=0x%08X ESP=0x%08X EIP=0x%08X\n", ctx.Eax, ctx.Ebx, ctx.Ecx, ctx.Edx, ctx.EFlags, ctx.Esi, ctx.Edi, ctx.Ebp, ctx.Esp, ctx.Eip); err = dbgEvt.u.Exception.ExceptionRecord.ExceptionCode; goto Cleanup; } break; // case EXCEPTION_DEBUG_EVENT: case CREATE_PROCESS_DEBUG_EVENT: // As needed, examine or change the registers of the // process's initial thread with the GetThreadContext and // SetThreadContext functions; read from and write to the // process's virtual memory with the ReadProcessMemory and // WriteProcessMemory functions; and suspend and resume // thread execution with the SuspendThread and ResumeThread // functions. Be sure to close the handle to the process image // file with CloseHandle. printf("Process 0x%08X (0x%08X) " "created, base = 0x%08X,\n" " Thread 0x%08X (0x%08X) created, start = 0x%08X\n", dbgEvt.dwProcessId, dbgEvt.u.CreateProcessInfo.hProcess, dbgEvt.u.CreateProcessInfo.lpBaseOfImage, dbgEvt.dwThreadId, dbgEvt.u.CreateProcessInfo.hThread, dbgEvt.u.CreateProcessInfo.lpStartAddress); // Found image base! imageBase = (DWORD)dbgEvt.u.CreateProcessInfo.lpBaseOfImage; dwContinueStatus = DBG_CONTINUE; break; case EXIT_PROCESS_DEBUG_EVENT: // Display the process's exit code. printf("Process 0x%08X exited, exit code = 0x%08X\n", dbgEvt.dwProcessId, dbgEvt.u.ExitProcess.dwExitCode); // Unexpected event. Do not continue execution. err = ERROR_PROC_NOT_FOUND; goto Cleanup; case CREATE_THREAD_DEBUG_EVENT: case EXIT_THREAD_DEBUG_EVENT: case LOAD_DLL_DEBUG_EVENT: case UNLOAD_DLL_DEBUG_EVENT: case OUTPUT_DEBUG_STRING_EVENT: dwContinueStatus = DBG_CONTINUE; break; case RIP_EVENT: printf("RIP: Error = 0x%08X, Type = 0x%08X\n", dbgEvt.u.RipInfo.dwError, dbgEvt.u.RipInfo.dwType); // Unexpected event. Do not continue execution. err = dbgEvt.u.RipInfo.dwError; goto Cleanup; } // end of switch (dbgEvt.dwDebugEventCode) // Resume executing the thread that reported the debugging event. if (dwContinueStatus) { if (!ContinueDebugEvent(dbgEvt.dwProcessId, dbgEvt.dwThreadId, dwContinueStatus)) { printf("ContinueDebugEvent() failed with error 0x%08X\n", err = GetLastError()); goto Cleanup; } } } // end of while (dwContinueStatus) err = ERROR_SUCCESS; Cleanup: if (processInfo.hProcess != NULL) { if (procMem != NULL) { VirtualFreeEx(processInfo.hProcess, procMem, 0, MEM_RELEASE); } TerminateProcess(processInfo.hProcess, 0); CloseHandle(processInfo.hThread); CloseHandle(processInfo.hProcess); } va_end(ap); return err; } int __cdecl FunctionCdecl(int x, int y, int z) { return x + y + z; } int __stdcall FunctionStdCall(int x, int y, int z) { return x * y * z; } ULONG64 __fastcall FunctionFastCall(DWORD x, DWORD y, DWORD z) { return (ULONG64)x * y + z; } int main(int argc, char** argv) { DWORD err; ULONG64 resultEdxEax; err = Execute32bitFunctionFromExe(argv[0]/*ExeName*/, 1/*FunctionAddressIsRelative*/, (DWORD)&FunctionCdecl - (DWORD)GetModuleHandle(NULL), CallConvCdecl, 4096/*CodeDataStackSize*/, &resultEdxEax, 3/*DwordParamsCount*/, 2, 3, 4); if (err == ERROR_SUCCESS) printf("2 + 3 + 4 = %d\n", (int)resultEdxEax); err = Execute32bitFunctionFromExe(argv[0]/*ExeName*/, 1/*FunctionAddressIsRelative*/, (DWORD)&FunctionStdCall - (DWORD)GetModuleHandle(NULL), CallConvStdCall, 4096/*CodeDataStackSize*/, &resultEdxEax, 3/*DwordParamsCount*/, -2, 3, 4); if (err == ERROR_SUCCESS) printf("-2 * 3 * 4 = %d\n", (int)resultEdxEax); err = Execute32bitFunctionFromExe(argv[0]/*ExeName*/, 1/*FunctionAddressIsRelative*/, (DWORD)&FunctionFastCall - (DWORD)GetModuleHandle(NULL), CallConvFastCall, 4096/*CodeDataStackSize*/, &resultEdxEax, 3/*DwordParamsCount*/, -1, -1, -1); if (err == ERROR_SUCCESS) printf("0xFFFFFFFF * 0xFFFFFFFF + 0xFFFFFFFF = 0x%llX\n", (unsigned long long)resultEdxEax); return 0; } 

Produzione:

 Process 0x00001514 (0x00000040) "C:\MinGW\msys\1.0\home\Alex\unexported.exe" cre ated, Thread 0x00000CB0 (0x0000003C) created Allocated RWX memory in process 0x00001514 (0x00000040) at address 0x002B0000 Process 0x00001514 (0x00000044) created, base = 0x00400000, Thread 0x00000CB0 (0x00000048) created, start = 0x0040126C First Chance (continuable) Exception in process 0x00001514, thread 0x00000CB0 Exc. Code = 0x80000003 (EXCEPTION_BREAKPOINT), Instr. Address = 0x77090FAB Copied code/data to the process First Chance (continuable) Exception in process 0x00001514, thread 0x00000CB0 Exc. Code = 0xC000001D (EXCEPTION_ILLEGAL_INSTRUCTION), Instr. Address = 0x002 B0FFE Copied code/data from the process 2 + 3 + 4 = 9 Process 0x00001828 (0x0000003C) "C:\MinGW\msys\1.0\home\Alex\unexported.exe" cre ated, Thread 0x00001690 (0x00000040) created Allocated RWX memory in process 0x00001828 (0x0000003C) at address 0x002B0000 Process 0x00001828 (0x0000006C) created, base = 0x00400000, Thread 0x00001690 (0x00000074) created, start = 0x0040126C First Chance (continuable) Exception in process 0x00001828, thread 0x00001690 Exc. Code = 0x80000003 (EXCEPTION_BREAKPOINT), Instr. Address = 0x77090FAB Copied code/data to the process First Chance (continuable) Exception in process 0x00001828, thread 0x00001690 Exc. Code = 0xC000001D (EXCEPTION_ILLEGAL_INSTRUCTION), Instr. Address = 0x002 B0FFE Copied code/data from the process -2 * 3 * 4 = -24 Process 0x00001388 (0x00000040) "C:\MinGW\msys\1.0\home\Alex\unexported.exe" cre ated, Thread 0x00001098 (0x0000003C) created Allocated RWX memory in process 0x00001388 (0x00000040) at address 0x002B0000 Process 0x00001388 (0x0000008C) created, base = 0x00400000, Thread 0x00001098 (0x00000090) created, start = 0x0040126C First Chance (continuable) Exception in process 0x00001388, thread 0x00001098 Exc. Code = 0x80000003 (EXCEPTION_BREAKPOINT), Instr. Address = 0x77090FAB Copied code/data to the process First Chance (continuable) Exception in process 0x00001388, thread 0x00001098 Exc. Code = 0xC000001D (EXCEPTION_ILLEGAL_INSTRUCTION), Instr. Address = 0x002 B0FFE Copied code/data from the process 0xFFFFFFFF * 0xFFFFFFFF + 0xFFFFFFFF = 0xFFFFFFFF00000000 

È ansible ma non banale. E sì, questo è un trucco molto sporco.

In alcuni casi è sufficiente caricare il file EXE con LoadLibrary. L’HMODULE restituito è in realtà l’indirizzo di base dell’EXE caricato. Trasmettetelo a un tipo int appropriato, aggiungete il vostro indirizzo relativo alla funzione, lo restituite a un puntatore di funzione e chiamate la funzione attraverso quel puntatore.

Sfortunatamente, il file EXE potrebbe avere le sue informazioni di riposizionamento spogliate. Significa che l’EXE si aspetta di essere eseguito da un indirizzo specifico. In questo caso, devi modificare l’indirizzo di base del tuo programma per evitare conflitti. Controlla i documenti del tuo linker, dovrebbe esserci un’opzione per farlo. Successivamente, LoadLibrary caricherà l’EXE nel suo indirizzo di base preferito e si spera che tutto funzioni correttamente.

Ci sono alcune informazioni molto utili su questo qui . Assicurati di controllare l’aggiornamento alla fine della pagina per una tecnica diversa che potrebbe funzionare meglio in alcuni casi.

Modifica: come afferma correttamente Alex nel commento qui sotto, se la funzione si basa su qualche valore inizializzato, o chiama tale funzione, inclusa la maggior parte delle funzioni di runtime di C, sarà molto più difficile farlo funzionare. È ansible identificare le funzioni di inizializzazione e chiamarle in anticipo, ma l’utilizzo dell’API di debug potrebbe essere la soluzione migliore in tali situazioni.

Invece di caricare l’EXE nel tuo processo, due modi migliori (IMO):

1) Utilizzare l’API di debug (o qualcosa come PyDbg) per avviare la destinazione con un debugger, quindi impostare gli argomenti nello stack, impostare EIP sull’indirizzo necessario, inserire breakpoint sull’indirizzo di ritorno e riprendere.

2) crea una piccola DLL con un IPC per comunicare con il tuo programma, immettila nel target (ci sono diversi modi per farlo, il più semplice è probabilmente l’aghook della tastiera) e chiama il codice necessario. Oppure potresti usare anche uno esistente che può farlo, ad esempio il PIN di Intel .