是否有可能调用驻留在exe中的非导出函数?

我想调用驻留在第三方.exe中的函数并获取其结果。 似乎应该有办法,只要我知道函数地址,调用约定等,但我不知道如何。

有谁知道我会怎么做?

我意识到,任何解决scheme将是一个非标准的黑客攻击,但必须有一个办法!


我的非恶意用例:我正在为我的软件逆向工程一个文件格式。 这个函数的计算太复杂了,我的小脑子想不出来。 我已经能够将汇编代码直接拉到我自己的DLL进行testing,但我当然不能释放,因为这会被窃取。 我会假设用户已经预装了这个特定的应用程序,所以我的软件将运行。

好的,我已经制作了一个原型。

此程序创建另一个自己的实例作为调试的子进程。

在main()和CRT初始化代码之前会遇到一个自动断点。 这是当我们可以改变调试过程的内存和寄存器,使其执行一个感兴趣的功能。 这就是程序所做的。

它试图捕捉和处理所有不良情况(例如意外的例外情况)并将其报告为错误。

一个糟糕的情况实际上是一个好的情况。 这是程序放入调试进程的UD2指令的#UD异常。 它使用这个#UD来在感兴趣的函数返回之后停止处理执行。

还有一些注意事项:

  1. 这个代码只有32位。 我什至没有尝试使其64位可编译或支持64位子进程。

  2. 这段代码可能会泄漏句柄。 请参阅MSDN上的Windows调试API函数描述以找出需要关闭的位置。

  3. 这个代码只是一个概念证明,不支持通过EAX,ECX和EDX以外的指针或寄存器来传递和返回数据。 您必须根据需要进行扩展。

  4. 这段代码需要一些特权才能创建并完全调试一个进程。 如果你的程序的用户不是管理员,你可能不得不担心这个问题。

请享用。

码:

// 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 <windows.h> #include <stdio.h> #include <string.h> #include <stdarg.h> #include <limits.h> #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)GetmoduleeHandle(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)GetmoduleeHandle(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)GetmoduleeHandle(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; } 

输出:

 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 

这是可能的,但不是微不足道的。 是的,这是一个非常肮脏的黑客。

在某些情况下用LoadLibrary加载EXE文件就足够了。 返回的HMODULE实际上是加载的EXE的基地址。 将其转换为合适的int类型,将相对函数地址添加到该类型中,将其转换回函数指针并通过该指针调用该函数。

不幸的是,EXE文件可能会删除其重定位信息。 这意味着EXE将期待从一个特定的地址运行。 在这种情况下,您必须更改您自己的程序的基址,以避免冲突。 看看你的链接文件,应该有一个选项来做到这一点。 之后,LoadLibrary将加载EXE的首选基地址,并希望所有应该正常工作。

这里有一些非常有用的信息。 确保检查页面末尾的更新,以获得在某些情况下可能会更好的不同技术。

编辑:正如亚历克斯在下面的评论中正确地陈述,如果函数依赖于一些初始化的值,或者它调用了这样一个函数,包括大多数C运行时功能,它将是很难使其工作。 人们可以识别初始化函数并预先调用它们,但在这些情况下使用调试API可能是最好的选择。

而不是将EXE加载到您的过程中,两种更好的(IMO)方式:

1)使用调试API(或类似PyDbg)在调试器下启动目标,然后在堆栈中设置参数,将EIP设置为必要的地址,在返回地址上放置断点,然后继续。

2)用一些IPC与你的程序进行通信,将其注入到目标中(有几种方法可以做到这一点,最简单的方法就是键盘挂钩),然后调用必要的代码。 或者你也可以使用现有的,例如英特尔的PIN 。