541 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			541 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
| //////////////////////////////////////////////////////////////////////////////
 | |
| //
 | |
| //  Test DetourCreateProcessWithDll function (withdll.cpp).
 | |
| //
 | |
| //  Microsoft Research Detours Package
 | |
| //
 | |
| //  Copyright (c) Microsoft Corporation.  All rights reserved.
 | |
| //
 | |
| #include <stdio.h>
 | |
| #include <windows.h>
 | |
| #include <detours.h>
 | |
| #pragma warning(push)
 | |
| #if _MSC_VER > 1400
 | |
| #pragma warning(disable:6102 6103) // /analyze warnings
 | |
| #endif
 | |
| #include <strsafe.h>
 | |
| #pragma warning(pop)
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////////////
 | |
| //
 | |
| void PrintUsage(void)
 | |
| {
 | |
|     printf("Usage:\n"
 | |
|            "    withdll.exe [options] [command line]\n"
 | |
|            "Options:\n"
 | |
|            "    /d:file.dll   : Start the process with file.dll.\n"
 | |
|            "    /v            : Verbose, display memory at start.\n"
 | |
|            "    /?            : This help screen.\n");
 | |
| }
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////////////
 | |
| //
 | |
| //  This code verifies that the named DLL has been configured correctly
 | |
| //  to be imported into the target process.  DLLs must export a function with
 | |
| //  ordinal #1 so that the import table touch-up magic works.
 | |
| //
 | |
| struct ExportContext
 | |
| {
 | |
|     BOOL    fHasOrdinal1;
 | |
|     ULONG   nExports;
 | |
| };
 | |
| 
 | |
| static BOOL CALLBACK ExportCallback(_In_opt_ PVOID pContext,
 | |
|                                     _In_ ULONG nOrdinal,
 | |
|                                     _In_opt_ LPCSTR pszSymbol,
 | |
|                                     _In_opt_ PVOID pbTarget)
 | |
| {
 | |
|     (void)pContext;
 | |
|     (void)pbTarget;
 | |
|     (void)pszSymbol;
 | |
| 
 | |
|     ExportContext *pec = (ExportContext *)pContext;
 | |
| 
 | |
|     if (nOrdinal == 1) {
 | |
|         pec->fHasOrdinal1 = TRUE;
 | |
|     }
 | |
|     pec->nExports++;
 | |
| 
 | |
|     return TRUE;
 | |
| }
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////////////
 | |
| //
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////////////
 | |
| //
 | |
| 
 | |
| void TypeToString(DWORD Type, char *pszBuffer, size_t cBuffer)
 | |
| {
 | |
|     if (Type == MEM_IMAGE) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "img");
 | |
|     }
 | |
|     else if (Type == MEM_MAPPED) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "map");
 | |
|     }
 | |
|     else if (Type == MEM_PRIVATE) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "pri");
 | |
|     }
 | |
|     else {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "%x", Type);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void StateToString(DWORD State, char *pszBuffer, size_t cBuffer)
 | |
| {
 | |
|     if (State == MEM_COMMIT) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "com");
 | |
|     }
 | |
|     else if (State == MEM_FREE) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "fre");
 | |
|     }
 | |
|     else if (State == MEM_RESERVE) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "res");
 | |
|     }
 | |
|     else {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "%x", State);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void ProtectToString(DWORD Protect, char *pszBuffer, size_t cBuffer)
 | |
| {
 | |
|     if (Protect == 0) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "");
 | |
|     }
 | |
|     else if (Protect == PAGE_EXECUTE) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "--x");
 | |
|     }
 | |
|     else if (Protect == PAGE_EXECUTE_READ) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "r-x");
 | |
|     }
 | |
|     else if (Protect == PAGE_EXECUTE_READWRITE) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "rwx");
 | |
|     }
 | |
|     else if (Protect == PAGE_EXECUTE_WRITECOPY) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "rcx");
 | |
|     }
 | |
|     else if (Protect == PAGE_NOACCESS) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "---");
 | |
|     }
 | |
|     else if (Protect == PAGE_READONLY) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "r--");
 | |
|     }
 | |
|     else if (Protect == PAGE_READWRITE) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "rw-");
 | |
|     }
 | |
|     else if (Protect == PAGE_WRITECOPY) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "rc-");
 | |
|     }
 | |
|     else if (Protect == (PAGE_GUARD | PAGE_EXECUTE)) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "g--x");
 | |
|     }
 | |
|     else if (Protect == (PAGE_GUARD | PAGE_EXECUTE_READ)) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "gr-x");
 | |
|     }
 | |
|     else if (Protect == (PAGE_GUARD | PAGE_EXECUTE_READWRITE)) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "grwx");
 | |
|     }
 | |
|     else if (Protect == (PAGE_GUARD | PAGE_EXECUTE_WRITECOPY)) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "grcx");
 | |
|     }
 | |
|     else if (Protect == (PAGE_GUARD | PAGE_NOACCESS)) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "g---");
 | |
|     }
 | |
|     else if (Protect == (PAGE_GUARD | PAGE_READONLY)) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "gr--");
 | |
|     }
 | |
|     else if (Protect == (PAGE_GUARD | PAGE_READWRITE)) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "grw-");
 | |
|     }
 | |
|     else if (Protect == (PAGE_GUARD | PAGE_WRITECOPY)) {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "grc-");
 | |
|     }
 | |
|     else {
 | |
|         StringCchPrintfA(pszBuffer, cBuffer, "%x", Protect);
 | |
|     }
 | |
| }
 | |
| 
 | |
| typedef union
 | |
| {
 | |
|     struct
 | |
|     {
 | |
|         DWORD Signature;
 | |
|         IMAGE_FILE_HEADER FileHeader;
 | |
|     } ih;
 | |
| 
 | |
|     IMAGE_NT_HEADERS32 ih32;
 | |
|     IMAGE_NT_HEADERS64 ih64;
 | |
| } IMAGE_NT_HEADER;
 | |
| 
 | |
| struct SECTIONS
 | |
| {
 | |
|     PBYTE   pbBeg;
 | |
|     PBYTE   pbEnd;
 | |
|     CHAR    szName[16];
 | |
| } Sections[256];
 | |
| DWORD SectionCount = 0;
 | |
| DWORD Bitness = 0;
 | |
| 
 | |
| PCHAR FindSectionName(PBYTE pbBase, PBYTE& pbEnd)
 | |
| {
 | |
|     for (DWORD n = 0; n < SectionCount; n++) {
 | |
|         if (Sections[n].pbBeg == pbBase) {
 | |
|             pbEnd = Sections[n].pbEnd;
 | |
|             return Sections[n].szName;
 | |
|         }
 | |
|     }
 | |
|     pbEnd = NULL;
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| ULONG PadToPage(ULONG Size)
 | |
| {
 | |
|     return (Size & 0xfff)
 | |
|         ? Size + 0x1000 - (Size & 0xfff)
 | |
|         : Size;
 | |
| }
 | |
| 
 | |
| BOOL GetSections(HANDLE hp, PBYTE pbBase)
 | |
| {
 | |
|     DWORD beg = 0;
 | |
|     DWORD cnt = 0;
 | |
|     SIZE_T done;
 | |
|     IMAGE_DOS_HEADER idh;
 | |
| 
 | |
|     if (!ReadProcessMemory(hp, pbBase, &idh, sizeof(idh), &done) || done != sizeof(idh)) {
 | |
|         return FALSE;
 | |
|     }
 | |
| 
 | |
|     if (idh.e_magic != IMAGE_DOS_SIGNATURE) {
 | |
|         return FALSE;
 | |
|     }
 | |
| 
 | |
|     IMAGE_NT_HEADER inh;
 | |
|     if (!ReadProcessMemory(hp, pbBase + idh.e_lfanew, &inh, sizeof(inh), &done) || done != sizeof(inh)) {
 | |
|         printf("No Read\n");
 | |
|         return FALSE;
 | |
|     }
 | |
| 
 | |
|     if (inh.ih.Signature != IMAGE_NT_SIGNATURE) {
 | |
|         printf("No NT\n");
 | |
|         return FALSE;
 | |
|     }
 | |
| 
 | |
|     beg = idh.e_lfanew
 | |
|         + FIELD_OFFSET( IMAGE_NT_HEADERS, OptionalHeader )
 | |
|         + inh.ih.FileHeader.SizeOfOptionalHeader;
 | |
|     cnt = inh.ih.FileHeader.NumberOfSections;
 | |
|     Bitness = (inh.ih32.OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) ? 32 : 64;
 | |
| #if 0
 | |
|     printf("%d %d count=%d\n", beg, Bitness, cnt);
 | |
| #endif
 | |
| 
 | |
|     IMAGE_SECTION_HEADER ish;
 | |
|     for (DWORD n = 0; n < cnt; n++) {
 | |
|         if (!ReadProcessMemory(hp, pbBase + beg + n * sizeof(ish), &ish, sizeof(ish), &done) || done != sizeof(ish)) {
 | |
|             printf("No Read\n");
 | |
|             return FALSE;
 | |
|         }
 | |
|         Sections[n].pbBeg = pbBase + ish.VirtualAddress;
 | |
|         Sections[n].pbEnd = pbBase + ish.VirtualAddress + PadToPage(ish.Misc.VirtualSize);
 | |
|         memcpy(Sections[n].szName, ish.Name, sizeof(ish.Name));
 | |
|         Sections[n].szName[sizeof(ish.Name)] = '\0';
 | |
| #if 0
 | |
|         printf("--- %p %s\n", Sections[n].pbBeg, Sections[n].szName);
 | |
| #endif
 | |
|     }
 | |
|     SectionCount = cnt;
 | |
| 
 | |
|     return TRUE;
 | |
| }
 | |
| 
 | |
| BOOL DumpProcess(HANDLE hp)
 | |
| {
 | |
|     ULONG64 base;
 | |
|     ULONG64 next;
 | |
| 
 | |
|     MEMORY_BASIC_INFORMATION mbi;
 | |
| 
 | |
|     printf("  %12s %8s %8s: %3s %3s %4s %3s : %8s\n", "Address", "Offset", "Size", "Typ", "Sta", "Prot", "Ini", "Contents");
 | |
|     printf("  %12s %8s %8s: %3s %3s %4s %3s : %8s\n", "------------", "--------", "--------", "---", "---", "----", "---", "-----------------");
 | |
| 
 | |
|     for (next = 0;;) {
 | |
|         base = next;
 | |
|         ZeroMemory(&mbi, sizeof(mbi));
 | |
|         if (VirtualQueryEx(hp, (PVOID)base, &mbi, sizeof(mbi)) == 0) {
 | |
|             break;
 | |
|         }
 | |
|         if ((mbi.RegionSize & 0xfff) == 0xfff) {
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         next = (ULONG64)mbi.BaseAddress + mbi.RegionSize;
 | |
| 
 | |
|         if (mbi.State == MEM_FREE) {
 | |
|             continue;
 | |
|         }
 | |
| 
 | |
|         CHAR szType[16];
 | |
|         TypeToString(mbi.Type, szType, ARRAYSIZE(szType));
 | |
|         CHAR szState[16];
 | |
|         StateToString(mbi.State, szState, ARRAYSIZE(szState));
 | |
|         CHAR szProtect[16];
 | |
|         ProtectToString(mbi.Protect, szProtect, ARRAYSIZE(szProtect));
 | |
|         CHAR szAllocProtect[16];
 | |
|         ProtectToString(mbi.AllocationProtect, szAllocProtect, ARRAYSIZE(szAllocProtect));
 | |
| 
 | |
|         CHAR szFile[MAX_PATH];
 | |
|         szFile[0] = '\0';
 | |
|         DWORD cb = 0;
 | |
|         PCHAR pszFile = szFile;
 | |
| 
 | |
|         if (base == (ULONG64)mbi.AllocationBase) {
 | |
| #if 0
 | |
|             cb = pfGetMappedFileName(hp, (PVOID)mbi.AllocationBase, szFile, ARRAYSIZE(szFile));
 | |
| #endif
 | |
|             if (GetSections(hp, (PBYTE)mbi.AllocationBase)) {
 | |
|                 next = base + 0x1000;
 | |
|                 StringCchPrintfA(szFile, ARRAYSIZE(szFile), "%d-bit PE", Bitness);
 | |
|             }
 | |
|         }
 | |
|         if (cb > 0) {
 | |
|             for (DWORD c = 0; c < cb; c++) {
 | |
|                 szFile[c] = (szFile[c] >= 'a' && szFile[c] <= 'z')
 | |
|                     ? szFile[c] - 'a' + 'A' : szFile[c];
 | |
|             }
 | |
|             szFile[cb] = '\0';
 | |
|         }
 | |
| 
 | |
|         if ((pszFile = strrchr(szFile, '\\')) == NULL) {
 | |
|             pszFile = szFile;
 | |
|         }
 | |
|         else {
 | |
|             pszFile++;
 | |
|         }
 | |
| 
 | |
|         PBYTE pbEnd;
 | |
|         PCHAR pszSect = FindSectionName((PBYTE)base, pbEnd);
 | |
|         if (pszSect != NULL) {
 | |
|             pszFile = pszSect;
 | |
|             if (next > (ULONG64)pbEnd) {
 | |
|                 next = (ULONG64)pbEnd;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         CHAR szDesc[128];
 | |
|         ZeroMemory(&szDesc, ARRAYSIZE(szDesc));
 | |
|         if (base == (ULONG64)mbi.AllocationBase) {
 | |
|             StringCchPrintfA(szDesc, ARRAYSIZE(szDesc), "  %12I64x %8I64x %8I64x: %3s %3s %4s %3s : %s",
 | |
|                              (ULONG64)base,
 | |
|                              (ULONG64)base - (ULONG64)mbi.AllocationBase,
 | |
|                              (ULONG64)next - (ULONG64)base,
 | |
|                              szType,
 | |
|                              szState,
 | |
|                              szProtect,
 | |
|                              szAllocProtect,
 | |
|                              pszFile);
 | |
| 
 | |
| 
 | |
|         }
 | |
|         else {
 | |
|             StringCchPrintfA(szDesc, ARRAYSIZE(szDesc), "  %12s %8I64x %8I64x: %3s %3s %4s %3s : %s",
 | |
|                              "-",
 | |
|                              (ULONG64)base - (ULONG64)mbi.AllocationBase,
 | |
|                              (ULONG64)next - (ULONG64)base,
 | |
|                              szType,
 | |
|                              szState,
 | |
|                              szProtect,
 | |
|                              szAllocProtect,
 | |
|                              pszFile);
 | |
|         }
 | |
|         printf("%s\n", szDesc);
 | |
|     }
 | |
|     return TRUE;
 | |
| }
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////// main.
 | |
| //
 | |
| int CDECL main(int argc, char **argv)
 | |
| {
 | |
|     BOOLEAN fNeedHelp = FALSE;
 | |
|     BOOLEAN fVerbose = FALSE;
 | |
|     LPCSTR rpszDllsRaw[256];
 | |
|     LPCSTR rpszDllsOut[256];
 | |
|     DWORD nDlls = 0;
 | |
| 
 | |
|     for (DWORD n = 0; n < ARRAYSIZE(rpszDllsRaw); n++) {
 | |
|         rpszDllsRaw[n] = NULL;
 | |
|         rpszDllsOut[n] = NULL;
 | |
|     }
 | |
| 
 | |
|     int arg = 1;
 | |
|     for (; arg < argc && (argv[arg][0] == '-' || argv[arg][0] == '/'); arg++) {
 | |
| 
 | |
|         CHAR *argn = argv[arg] + 1;
 | |
|         CHAR *argp = argn;
 | |
|         while (*argp && *argp != ':' && *argp != '=')
 | |
|             argp++;
 | |
|         if (*argp == ':' || *argp == '=')
 | |
|             *argp++ = '\0';
 | |
| 
 | |
|         switch (argn[0]) {
 | |
|           case 'd':                                     // Set DLL Name
 | |
|           case 'D':
 | |
|             if (nDlls < ARRAYSIZE(rpszDllsRaw)) {
 | |
|                 rpszDllsRaw[nDlls++] = argp;
 | |
|             }
 | |
|             else {
 | |
|                 printf("withdll.exe: Too many DLLs.\n");
 | |
|                 fNeedHelp = TRUE;
 | |
|                 break;
 | |
|             }
 | |
|             break;
 | |
| 
 | |
|           case 'v':                                     // Verbose
 | |
|           case 'V':
 | |
|             fVerbose = TRUE;
 | |
|             break;
 | |
| 
 | |
|           case '?':                                     // Help
 | |
|             fNeedHelp = TRUE;
 | |
|             break;
 | |
| 
 | |
|           default:
 | |
|             fNeedHelp = TRUE;
 | |
|             printf("withdll.exe: Bad argument: %s\n", argv[arg]);
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (arg >= argc) {
 | |
|         fNeedHelp = TRUE;
 | |
|     }
 | |
| 
 | |
|     if (nDlls == 0) {
 | |
|         fNeedHelp = TRUE;
 | |
|     }
 | |
| 
 | |
|     if (fNeedHelp) {
 | |
|         PrintUsage();
 | |
|         return 9001;
 | |
|     }
 | |
| 
 | |
|     /////////////////////////////////////////////////////////// Validate DLLs.
 | |
|     //
 | |
|     for (DWORD n = 0; n < nDlls; n++) {
 | |
|         CHAR szDllPath[1024];
 | |
|         PCHAR pszFilePart = NULL;
 | |
| 
 | |
|         if (!GetFullPathNameA(rpszDllsRaw[n], ARRAYSIZE(szDllPath), szDllPath, &pszFilePart)) {
 | |
|             printf("withdll.exe: Error: %s is not a valid path name..\n",
 | |
|                    rpszDllsRaw[n]);
 | |
|             return 9002;
 | |
|         }
 | |
| 
 | |
|         DWORD c = (DWORD)strlen(szDllPath) + 1;
 | |
|         PCHAR psz = new CHAR [c];
 | |
|         StringCchCopyA(psz, c, szDllPath);
 | |
|         rpszDllsOut[n] = psz;
 | |
| 
 | |
|         HMODULE hDll = LoadLibraryExA(rpszDllsOut[n], NULL, DONT_RESOLVE_DLL_REFERENCES);
 | |
|         if (hDll == NULL) {
 | |
|             printf("withdll.exe: Error: %s failed to load (error %ld).\n",
 | |
|                    rpszDllsOut[n],
 | |
|                    GetLastError());
 | |
|             return 9003;
 | |
|         }
 | |
| 
 | |
|         ExportContext ec;
 | |
|         ec.fHasOrdinal1 = FALSE;
 | |
|         ec.nExports = 0;
 | |
|         DetourEnumerateExports(hDll, &ec, ExportCallback);
 | |
|         FreeLibrary(hDll);
 | |
| 
 | |
|         if (!ec.fHasOrdinal1) {
 | |
|             printf("withdll.exe: Error: %s does not export ordinal #1.\n",
 | |
|                    rpszDllsOut[n]);
 | |
|             printf("             See help entry DetourCreateProcessWithDllEx in Detours.chm.\n");
 | |
|             return 9004;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //////////////////////////////////////////////////////////////////////////
 | |
|     STARTUPINFOA si;
 | |
|     PROCESS_INFORMATION pi;
 | |
|     CHAR szCommand[2048];
 | |
|     CHAR szExe[1024];
 | |
|     CHAR szFullExe[1024] = "\0";
 | |
|     PCHAR pszFileExe = NULL;
 | |
| 
 | |
|     ZeroMemory(&si, sizeof(si));
 | |
|     ZeroMemory(&pi, sizeof(pi));
 | |
|     si.cb = sizeof(si);
 | |
| 
 | |
|     szCommand[0] = L'\0';
 | |
| 
 | |
|     StringCchCopyA(szExe, sizeof(szExe), argv[arg]);
 | |
|     for (; arg < argc; arg++) {
 | |
|         if (strchr(argv[arg], ' ') != NULL || strchr(argv[arg], '\t') != NULL) {
 | |
|             StringCchCatA(szCommand, sizeof(szCommand), "\"");
 | |
|             StringCchCatA(szCommand, sizeof(szCommand), argv[arg]);
 | |
|             StringCchCatA(szCommand, sizeof(szCommand), "\"");
 | |
|         }
 | |
|         else {
 | |
|             StringCchCatA(szCommand, sizeof(szCommand), argv[arg]);
 | |
|         }
 | |
| 
 | |
|         if (arg + 1 < argc) {
 | |
|             StringCchCatA(szCommand, sizeof(szCommand), " ");
 | |
|         }
 | |
|     }
 | |
|     printf("withdll.exe: Starting: `%s'\n", szCommand);
 | |
|     for (DWORD n = 0; n < nDlls; n++) {
 | |
|         printf("withdll.exe:   with `%s'\n", rpszDllsOut[n]);
 | |
|     }
 | |
|     fflush(stdout);
 | |
| 
 | |
|     DWORD dwFlags = CREATE_DEFAULT_ERROR_MODE | CREATE_SUSPENDED;
 | |
| 
 | |
|     SetLastError(0);
 | |
|     SearchPathA(NULL, szExe, ".exe", ARRAYSIZE(szFullExe), szFullExe, &pszFileExe);
 | |
|     if (!DetourCreateProcessWithDllsA(szFullExe[0] ? szFullExe : NULL, szCommand,
 | |
|                                      NULL, NULL, TRUE, dwFlags, NULL, NULL,
 | |
|                                      &si, &pi, nDlls, rpszDllsOut, NULL)) {
 | |
|         DWORD dwError = GetLastError();
 | |
|         printf("withdll.exe: DetourCreateProcessWithDllEx failed: %ld\n", dwError);
 | |
|         if (dwError == ERROR_INVALID_HANDLE) {
 | |
| #if DETOURS_64BIT
 | |
|             printf("withdll.exe: Can't detour a 32-bit target process from a 64-bit parent process.\n");
 | |
| #else
 | |
|             printf("withdll.exe: Can't detour a 64-bit target process from a 32-bit parent process.\n");
 | |
| #endif
 | |
|         }
 | |
|         ExitProcess(9009);
 | |
|     }
 | |
| 
 | |
|     if (fVerbose) {
 | |
|         DumpProcess(pi.hProcess);
 | |
|     }
 | |
| 
 | |
|     ResumeThread(pi.hThread);
 | |
| 
 | |
|     WaitForSingleObject(pi.hProcess, INFINITE);
 | |
| 
 | |
|     DWORD dwResult = 0;
 | |
|     if (!GetExitCodeProcess(pi.hProcess, &dwResult)) {
 | |
|         printf("withdll.exe: GetExitCodeProcess failed: %ld\n", GetLastError());
 | |
|         return 9010;
 | |
|     }
 | |
| 
 | |
|     for (DWORD n = 0; n < nDlls; n++) {
 | |
|         if (rpszDllsOut[n] != NULL) {
 | |
|             delete[] rpszDllsOut[n];
 | |
|             rpszDllsOut[n] = NULL;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return dwResult;
 | |
| }
 | |
| //
 | |
| ///////////////////////////////////////////////////////////////// End of File.
 |