├── .gitignore ├── LICENSE.txt ├── errout.c ├── errout.h ├── errout.rc ├── errouthk.c ├── errouthk.rc ├── injdll32.c ├── injdll64.c ├── makefile.gcc ├── makefile.vc ├── proctype.c ├── readme.txt └── wow64.h /.gitignore: -------------------------------------------------------------------------------- 1 | /x86 2 | /x64 3 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011-2013 Jason Hood 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the author be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | 19 | Jason Hood 20 | jadoxa@yahoo.com.au 21 | -------------------------------------------------------------------------------- /errout.c: -------------------------------------------------------------------------------- 1 | /* 2 | errout.c - Send stderr to stdout. 3 | 4 | Jason Hood, 29 June to 2 July, 2011. 5 | 6 | Set up a WriteFile hook to redirect both stdout & stderr, maintaining the 7 | order as you'd see it on the console (which "2>&1" doesn't necessarily do). 8 | 9 | v1.11, 5 December, 2013: 10 | - return the exit code of the program; 11 | * enable read sharing of the files. 12 | */ 13 | 14 | #define PVERS L"1.11" 15 | #define PDATE L"5 December, 2013" 16 | 17 | #define DO_IMPORT 18 | #include "errout.h" 19 | #include 20 | 21 | #ifdef __MINGW32__ 22 | // I think it's a flaw in gcc that it doesn't read object files from its library 23 | // directory, hence doing this rather than adding CRT_noglob.o. Only necessary 24 | // for MinGW32, TDM apparently doesn't do it by default (besides which, it uses 25 | // _dowildcard, same as VC). 26 | int _CRT_glob = 0; 27 | #endif 28 | 29 | 30 | #ifdef _WIN64 31 | # define BITS L"64" 32 | # define APPS L"Windows" 33 | #else 34 | # define BITS L"32" 35 | # define APPS L"Win32" 36 | #endif 37 | 38 | __declspec(dllimport) 39 | Globals global; 40 | 41 | void help( void ); 42 | 43 | LPWSTR skip_spaces( LPWSTR ); 44 | void get_arg( LPWSTR, LPWSTR*, LPWSTR* ); 45 | 46 | 47 | DWORD CtrlHandler( DWORD event ) 48 | { 49 | return (event == CTRL_C_EVENT || event == CTRL_BREAK_EVENT); 50 | } 51 | 52 | 53 | int main( void ) 54 | { 55 | STARTUPINFO si; 56 | PROCESS_INFORMATION pi; 57 | SECURITY_ATTRIBUTES sa; 58 | HANDLE hStdErr; 59 | FILE* stdcon; 60 | LPWSTR argv, arg, cmd; 61 | char cp[8]; 62 | WCHAR env[4]; 63 | BOOL delay; 64 | int rc; 65 | 66 | rc = 0; 67 | argv = GetCommandLine(); 68 | arg = malloc( wcslen( argv ) * sizeof(WCHAR) ); 69 | get_arg( arg, &argv, &cmd ); // skip the program name 70 | get_arg( arg, &argv, &cmd ); 71 | 72 | if (*arg == '\0' || 73 | wcscmp( arg, L"/?" ) == 0 || 74 | wcscmp( arg, L"--help" ) == 0) 75 | { 76 | help(); 77 | free( arg ); 78 | return rc; 79 | } 80 | if (wcscmp( arg, L"--version" ) == 0) 81 | { 82 | _putws( L"Errout (" BITS L"-bit) version " PVERS L" (" PDATE L")." ); 83 | free( arg ); 84 | return rc; 85 | } 86 | 87 | #ifdef _WIN64 88 | if (*arg == '-' && arg[1] == 'P') 89 | { 90 | swscanf( arg + 2, L"%u:%u", &pi.dwProcessId, &pi.dwThreadId ); 91 | pi.hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pi.dwProcessId); 92 | pi.hThread = OpenThread( THREAD_ALL_ACCESS, FALSE, pi.dwThreadId ); 93 | InjectDLL64( &pi ); 94 | CloseHandle( pi.hThread ); 95 | CloseHandle( pi.hProcess ); 96 | return 0; 97 | } 98 | #endif 99 | 100 | // Using "" for setlocale uses the system ANSI code page. 101 | sprintf( cp, ".%u", GetConsoleOutputCP() ); 102 | setlocale( LC_CTYPE, cp ); 103 | 104 | sa.nLength = sizeof(sa); 105 | sa.lpSecurityDescriptor = NULL; 106 | sa.bInheritHandle = TRUE; 107 | 108 | global.hStdOut = GetStdHandle( STD_OUTPUT_HANDLE ); 109 | hStdErr = GetStdHandle( STD_ERROR_HANDLE ); 110 | 111 | // If a pipe creates a new console (like TDE does), then CONOUT$ will (may?) 112 | // be created in that console, so use stderr (if it's not been redirected for 113 | // some reason) to keep -c in the original console. 114 | if (GetFileType( hStdErr ) == FILE_TYPE_CHAR) 115 | { 116 | // Can't just copy the handle, as we need to distinguish them. 117 | DuplicateHandle( GetCurrentProcess(), hStdErr, 118 | GetCurrentProcess(), &global.hStdCon, 119 | 0, TRUE, DUPLICATE_SAME_ACCESS ); 120 | } 121 | else 122 | { 123 | global.hStdCon = CreateFile( L"CONOUT$", GENERIC_READ | GENERIC_WRITE, 124 | FILE_SHARE_READ | FILE_SHARE_WRITE, 125 | &sa, OPEN_EXISTING, 0, 0 ); 126 | } 127 | 128 | // Unfortunately, the above doesn't change stdout, so create a console file. 129 | stdcon = _wfopen( L"con", L"w" ); 130 | 131 | *env = '\0'; 132 | GetEnvironmentVariable( L"ERROUTCOL", env, lenof(env) ); 133 | if (*env != '\0') 134 | { 135 | LPWSTR end; 136 | long attr = wcstol( env, &end, 16 ); 137 | if (end != env && *end == '\0' && attr < 256) 138 | global.errcol = (WORD)attr; 139 | } 140 | 141 | rc = -1; 142 | delay = FALSE; 143 | while (*arg == '-') 144 | { 145 | if (arg[1] == 'a') 146 | { 147 | LPWSTR end; 148 | long attr = wcstol( arg+2, &end, 16 ); 149 | if (end == arg+2 || *end != '\0' || attr >= 256) 150 | { 151 | fwprintf( stdcon, L"Errout: Expecting one or two hex digits: \"%s\".\n", arg ); 152 | goto tidy_up; 153 | } 154 | global.errcol = (WORD)attr; 155 | } 156 | else if (arg[1] == 'c') 157 | { 158 | if (arg[2] == '\0') 159 | global.console = 3; 160 | else 161 | { 162 | if (arg[2] == 'e') 163 | global.console = 2; 164 | else if (arg[2] == 'o') 165 | global.console = 1; 166 | if (global.console == 0 || arg[3] != '\0') 167 | { 168 | fwprintf( stdcon, L"Errout: Expecting -c[e|o]: \"%s\".\n", arg ); 169 | goto tidy_up; 170 | } 171 | } 172 | } 173 | else if (arg[1] == 'd') 174 | { 175 | if (arg[2] != '\0') 176 | { 177 | fwprintf( stdcon, L"Errout: Not expecting anything: \"%s\".\n", arg ); 178 | goto tidy_up; 179 | } 180 | delay = TRUE; 181 | } 182 | else 183 | { 184 | HANDLE* pFile; 185 | BOOL append = !(arg[1] & 0x20); 186 | switch (arg[1] | 0x20) 187 | { 188 | default: 189 | fwprintf( stdcon, L"Errout: Unknown option: \"%s\".\n", arg ); 190 | goto tidy_up; 191 | 192 | case 'o': pFile = &global.hFilOut; break; 193 | case 'e': pFile = &global.hFilErr; break; 194 | case 'f': pFile = &global.hFilCon; break; 195 | } 196 | get_arg( arg, &argv, &cmd ); 197 | *pFile = CreateFile( arg, GENERIC_WRITE, FILE_SHARE_READ, &sa, 198 | (append) ? OPEN_ALWAYS : CREATE_ALWAYS, 0, 0 ); 199 | if (*pFile == INVALID_HANDLE_VALUE) 200 | { 201 | fwprintf( stdcon, L"Errout: Unable to %s \"%s\".\n", 202 | (append) ? L"open" : L"create", arg ); 203 | goto tidy_up; 204 | } 205 | if (append) 206 | SetFilePointer( *pFile, 0, NULL, FILE_END ); 207 | } 208 | get_arg( arg, &argv, &cmd ); 209 | } 210 | 211 | // Ignore the color if stdout is redirected and -c hasn't been used - this 212 | // prevents a pipe writing to console from being inadvertently coloured. 213 | if (GetFileType( global.hStdOut ) != FILE_TYPE_CHAR && global.console == 0) 214 | global.errcol = -1; 215 | 216 | ZeroMemory( &si, sizeof(si) ); 217 | si.cb = sizeof(si); 218 | if (!delay) 219 | SetStdHandle( STD_OUTPUT_HANDLE, global.hStdCon ); 220 | if (CreateProcess( NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi )) 221 | { 222 | // Point stdout to the new console handle. This is necessary to force the 223 | // combination of out & err, otherwise they still remain separate. I think 224 | // this is due to the differing buffering characteristics of character & 225 | // file types. I couldn't find a function to change that, hence the hook. 226 | // I originally did this earlier, but the whole reason I created this 227 | // program was so I could pipe a 7z archive and see the file names along 228 | // with the contents. Of course, that was the last thing I tested and it 229 | // didn't work - "I won't extract data and program's messages to the same 230 | // terminal." Well, now you will. :) 231 | if (delay) 232 | SetStdHandle( STD_OUTPUT_HANDLE, global.hStdCon ); 233 | CloseHandle( pi.hThread ); 234 | SetConsoleCtrlHandler( (PHANDLER_ROUTINE)CtrlHandler, TRUE ); 235 | WaitForSingleObject( pi.hProcess, INFINITE ); 236 | GetExitCodeProcess( pi.hProcess, (LPDWORD)&rc ); 237 | CloseHandle( pi.hProcess ); 238 | } 239 | else 240 | { 241 | get_arg( arg, &cmd, &cmd ); 242 | fwprintf( stdcon, L"Errout: \"%s\" could not be executed.\n", arg ); 243 | rc = -2; 244 | } 245 | 246 | tidy_up: 247 | fclose( stdcon ); 248 | if (global.hFilOut != NULL) 249 | CloseHandle( global.hFilOut ); 250 | if (global.hFilErr != NULL) 251 | CloseHandle( global.hFilErr ); 252 | if (global.hFilCon != NULL) 253 | CloseHandle( global.hFilCon ); 254 | SetStdHandle( STD_OUTPUT_HANDLE, global.hStdOut ); 255 | CloseHandle( global.hStdCon ); 256 | free( arg ); 257 | return rc; 258 | } 259 | 260 | 261 | // Return the first non-space character from arg. 262 | LPWSTR skip_spaces( LPWSTR arg ) 263 | { 264 | while (*arg == ' ' || *arg == '\t') 265 | ++arg; 266 | 267 | return arg; 268 | } 269 | 270 | 271 | // Retrieve an argument from the command line. cmd gets the existing argv; argv 272 | // is ready for the next argument. 273 | void get_arg( LPWSTR arg, LPWSTR* argv, LPWSTR* cmd ) 274 | { 275 | LPWSTR line = skip_spaces( *argv ); 276 | *cmd = line; 277 | 278 | while (*line != ' ' && *line != '\t' && *line != '\0') 279 | { 280 | if (*line == '"') 281 | { 282 | while (*++line != '"' && *line != '\0') 283 | *arg++ = *line; 284 | if (*line != '\0') 285 | ++line; 286 | } 287 | else 288 | { 289 | *arg++ = *line++; 290 | } 291 | } 292 | if (*line != '\0') 293 | ++line; 294 | *arg = '\0'; 295 | *argv = line; 296 | } 297 | 298 | 299 | void help( void ) 300 | { 301 | _putws( 302 | L"Errout by Jason Hood .\n" 303 | L"Version " PVERS L" (" PDATE L"). Freeware.\n" 304 | L"http://errout.adoxa.vze.com/\n" 305 | L"\n" 306 | L"Send standard error (stderr) to standard output (stdout) in " APPS L" console\n" 307 | L"programs, and optionally to the console and/or files as well.\n" 308 | L"\n" 309 | L"errout [options] program [args]\n" 310 | L"\n" 311 | L" -a[B]F\tset stderr to foreground F & background B (0 if absent)\n" 312 | L" -c[e|o]\talso output both, or just stderr/stdout, to the console\n" 313 | L" -d\t\tdelay the hook till after program is started\n" 314 | L" -e FILE\talso write stderr to FILE\n" 315 | L" -f FILE\talso write both to FILE\n" 316 | L" -o FILE\talso write stdout to FILE\n" 317 | L" program\trun the specified program with its arguments\n" 318 | L"\n" 319 | L"Lower case letter will create FILE; upper case will append.\n" 320 | L"F & B are hex digits; see COLOR/? for the values.\n" 321 | L"Set the environment variable ERROUTCOL=[B]F to use -a by default.\n" 322 | L"Delay may be useful for some programs (notably 7z)." 323 | ); 324 | } 325 | -------------------------------------------------------------------------------- /errout.h: -------------------------------------------------------------------------------- 1 | /* 2 | errout.h - Header file for common definitions. 3 | 4 | Jason Hood, 29 June to 3 July, 2011. 5 | */ 6 | 7 | #ifndef ERROUT_H 8 | #define ERROUT_H 9 | 10 | #ifndef UNICODE 11 | # define UNICODE 12 | #endif 13 | 14 | #define WIN32_LEAN_AND_MEAN 15 | #ifdef _WIN64 16 | #define _WIN32_WINNT 0x0600 // MinGW-w64 wants this defined for Wow64 stuff 17 | #else 18 | #define _WIN32_WINNT 0x0500 // MinGW wants this defined for OpenThread 19 | #endif 20 | #include 21 | #include 22 | #include 23 | 24 | #define lenof(array) (sizeof(array)/sizeof(*(array))) 25 | #define TSIZE(size) ((size) * sizeof(TCHAR)) 26 | 27 | 28 | struct Globals 29 | { 30 | HANDLE hStdOut; // the original stdout 31 | HANDLE hStdCon; // direct to console 32 | HANDLE hFilOut; // file for stdout 33 | HANDLE hFilErr; // file for stderr 34 | HANDLE hFilCon; // file for combined output 35 | WORD console; // flags to write to the console 36 | WORD errcol; // colour to use for stderr 37 | }; 38 | 39 | typedef struct Globals Globals; 40 | 41 | 42 | int ProcessType( LPPROCESS_INFORMATION ); 43 | void InjectDLL32( LPPROCESS_INFORMATION ); 44 | #ifdef DO_IMPORT 45 | __declspec(dllimport) 46 | #else 47 | __declspec(dllexport) 48 | #endif 49 | void InjectDLL64( LPPROCESS_INFORMATION ); 50 | 51 | 52 | #define CODE32SIZE 20 53 | #define GLOBAL32SIZE (6*4) 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /errout.rc: -------------------------------------------------------------------------------- 1 | /* 2 | errout.rc - Version resource for errout.exe. 3 | 4 | Jason Hood, 29 June, 2011. 5 | */ 6 | 7 | #include 8 | 9 | 1 VERSIONINFO 10 | FILEVERSION 1,1,1,0 11 | PRODUCTVERSION 1,1,1,0 12 | FILEOS VOS_NT 13 | FILETYPE VFT_APP 14 | { 15 | BLOCK "StringFileInfo" 16 | { 17 | BLOCK "040904B0" 18 | { 19 | VALUE "Comments", "http://errout.adoxa.vze.com/" 20 | VALUE "CompanyName", "Jason Hood" 21 | VALUE "FileDescription", "Errout" 22 | VALUE "FileVersion", "1.11" 23 | VALUE "InternalName", "errout" 24 | VALUE "LegalCopyright", "Freeware" 25 | VALUE "OriginalFilename", "errout.exe" 26 | VALUE "ProductName", "Errout" 27 | VALUE "ProductVersion", "1.11" 28 | } 29 | } 30 | 31 | BLOCK "VarFileInfo" 32 | { 33 | VALUE "Translation", 0x0409, 0x04B0 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /errouthk.c: -------------------------------------------------------------------------------- 1 | /* 2 | errouthk.c - Send stderr to stdout. 3 | 4 | Jason Hood, 29 June to 3 July, 2011. 5 | 6 | This is the WriteFile hook, needed to maintain the order between stderr and 7 | stdout when redirecting both. 8 | 9 | API hooking derived from ANSI.xs by Jean-Louis Morel, from his Perl package 10 | Win32::Console::ANSI (this version ripped out of ANSICON v1.40). 11 | 12 | v1.10, 13 & 14 November, 2013: 13 | * use all the improvements made to ANSICON v1.66. 14 | */ 15 | 16 | #include "errout.h" 17 | #include 18 | 19 | #ifdef __GNUC__ 20 | #define SHARED __attribute__((dllexport, shared, section(".shared"))) 21 | #else 22 | #pragma data_seg(".shared", "read,write,shared") 23 | #pragma data_seg() 24 | #define SHARED __declspec(dllexport allocate(".shared")) 25 | #endif 26 | 27 | 28 | SHARED Globals global = 29 | { 30 | NULL, // the original stdout 31 | NULL, // direct to console 32 | NULL, // file for stdout 33 | NULL, // file for stderr 34 | NULL, // file for combined output 35 | 0, // don't also write to console 36 | -1, // don't colour stderr 37 | }; 38 | 39 | 40 | BOOL WINAPI MyWriteFile( HANDLE hFile, LPCVOID lpBuffer, 41 | DWORD nNumberOfBytesToWrite, 42 | LPDWORD lpNumberOfBytesWritten, 43 | LPOVERLAPPED lpOverlapped ) 44 | { 45 | BOOL setcol = FALSE; 46 | WORD col = 0; // silence gcc 47 | BOOL rc; 48 | 49 | HANDLE hStdOut = GetStdHandle( STD_OUTPUT_HANDLE ); 50 | HANDLE hStdErr = GetStdHandle( STD_ERROR_HANDLE ); 51 | 52 | if (hFile == hStdOut || hFile == hStdErr) 53 | { 54 | if (hFile == hStdOut) 55 | { 56 | if (global.console & 1) 57 | { 58 | WriteFile( global.hStdCon, lpBuffer, nNumberOfBytesToWrite, 59 | lpNumberOfBytesWritten, NULL ); 60 | } 61 | if (global.hFilOut != NULL) 62 | { 63 | WriteFile( global.hFilOut, lpBuffer, nNumberOfBytesToWrite, 64 | lpNumberOfBytesWritten, NULL ); 65 | } 66 | } 67 | else // (hFile == hStdErr) 68 | { 69 | if (global.errcol != (WORD)-1) 70 | { 71 | CONSOLE_SCREEN_BUFFER_INFO csbi; 72 | GetConsoleScreenBufferInfo( global.hStdCon, &csbi ); 73 | col = csbi.wAttributes; 74 | setcol = TRUE; 75 | SetConsoleTextAttribute( global.hStdCon, global.errcol ); 76 | } 77 | if (global.console & 2) 78 | { 79 | WriteFile( global.hStdCon, lpBuffer, nNumberOfBytesToWrite, 80 | lpNumberOfBytesWritten, NULL ); 81 | } 82 | if (global.hFilErr != NULL) 83 | { 84 | WriteFile( global.hFilErr, lpBuffer, nNumberOfBytesToWrite, 85 | lpNumberOfBytesWritten, NULL ); 86 | } 87 | } 88 | if (global.hFilCon != NULL) 89 | { 90 | WriteFile( global.hFilCon, lpBuffer, nNumberOfBytesToWrite, 91 | lpNumberOfBytesWritten, NULL ); 92 | } 93 | hFile = global.hStdOut; 94 | } 95 | 96 | rc = WriteFile( hFile, lpBuffer, nNumberOfBytesToWrite, 97 | lpNumberOfBytesWritten, lpOverlapped ); 98 | 99 | if (setcol) 100 | SetConsoleTextAttribute( global.hStdCon, col ); 101 | 102 | return rc; 103 | } 104 | 105 | 106 | // Everything else in this file is just to make the above work. 107 | 108 | 109 | #ifdef _WIN64 110 | SHARED DWORD LLW32; 111 | #endif 112 | 113 | 114 | // ========== Hooking API functions 115 | // 116 | // References about API hooking (and dll injection): 117 | // - Matt Pietrek ~ Windows 95 System Programming Secrets. 118 | // - Jeffrey Richter ~ Programming Applications for Microsoft Windows 4th ed. 119 | 120 | // Macro for adding pointers/DWORDs together without C arithmetic interfering 121 | #define MakeVA( cast, offset ) (cast)((DWORD_PTR)(pDosHeader)+(DWORD)(offset)) 122 | 123 | 124 | const char APIKernel[] = "kernel32.dll"; 125 | const char APIProcessThreads[] = "API-MS-Win-Core-ProcessThreads-"; 126 | const char APILibraryLoader[] = "API-MS-Win-Core-LibraryLoader-"; 127 | const char APIFile[] = "API-MS-Win-Core-File-"; 128 | 129 | typedef struct 130 | { 131 | PCSTR name; 132 | DWORD len; 133 | HMODULE base; 134 | } API_DATA, *PAPI_DATA; 135 | 136 | API_DATA APIs[] = 137 | { 138 | { APIProcessThreads, sizeof(APIProcessThreads) - 1, NULL }, 139 | { APILibraryLoader, sizeof(APILibraryLoader) - 1, NULL }, 140 | { APIFile, sizeof(APIFile) - 1, NULL }, 141 | { NULL, 0, NULL } 142 | }; 143 | 144 | 145 | HMODULE hKernel; // Kernel32 module handle 146 | HINSTANCE hDllInstance; // Dll instance handle 147 | TCHAR hDllName[MAX_PATH]; // Dll file name 148 | #if defined(_WIN64) || defined(W32ON64) 149 | LPTSTR hDllNameType; // pointer to process type within above 150 | #endif 151 | 152 | typedef struct 153 | { 154 | PCSTR lib; 155 | PSTR name; 156 | PROC newfunc; 157 | PROC oldfunc; 158 | PROC apifunc; 159 | } HookFn, *PHookFn; 160 | 161 | HookFn Hooks[]; 162 | 163 | //----------------------------------------------------------------------------- 164 | // HookAPIOneMod 165 | // Substitute a new function in the Import Address Table (IAT) of the 166 | // specified module. 167 | // Return FALSE on error and TRUE on success. 168 | //----------------------------------------------------------------------------- 169 | 170 | BOOL HookAPIOneMod( 171 | HMODULE hFromModule, // Handle of the module to intercept calls from 172 | PHookFn Hooks, // Functions to replace 173 | BOOL restore // Restore the original functions 174 | ) 175 | { 176 | PIMAGE_DOS_HEADER pDosHeader; 177 | PIMAGE_NT_HEADERS pNTHeader; 178 | PIMAGE_IMPORT_DESCRIPTOR pImportDesc; 179 | PIMAGE_THUNK_DATA pThunk; 180 | PHookFn hook; 181 | 182 | // Tests to make sure we're looking at a module image (the 'MZ' header) 183 | pDosHeader = (PIMAGE_DOS_HEADER)hFromModule; 184 | if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) 185 | return FALSE; 186 | 187 | // The MZ header has a pointer to the PE header 188 | pNTHeader = MakeVA( PIMAGE_NT_HEADERS, pDosHeader->e_lfanew ); 189 | 190 | // One more test to make sure we're looking at a "PE" image 191 | if (pNTHeader->Signature != IMAGE_NT_SIGNATURE) 192 | return FALSE; 193 | 194 | // We now have a valid pointer to the module's PE header. 195 | // Get a pointer to its imports section. 196 | pImportDesc = MakeVA( PIMAGE_IMPORT_DESCRIPTOR, 197 | pNTHeader->OptionalHeader. 198 | DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]. 199 | VirtualAddress ); 200 | 201 | // Bail out if the RVA of the imports section is 0 (it doesn't exist) 202 | if (pImportDesc == (PIMAGE_IMPORT_DESCRIPTOR)pDosHeader) 203 | return TRUE; 204 | 205 | // Iterate through the array of imported module descriptors, looking 206 | // for the module whose name matches the pszFunctionModule parameter. 207 | for (; pImportDesc->Name; pImportDesc++) 208 | { 209 | BOOL kernel = TRUE; 210 | PSTR pszModName = MakeVA( PSTR, pImportDesc->Name ); 211 | if (_stricmp( pszModName, APIKernel ) != 0) 212 | { 213 | PAPI_DATA lib; 214 | for (lib = APIs; lib->name; ++lib) 215 | { 216 | if (_strnicmp( pszModName, lib->name, lib->len ) == 0) 217 | { 218 | if (lib->base == NULL) 219 | { 220 | lib->base = GetModuleHandleA( pszModName ); 221 | for (hook = Hooks; hook->name; ++hook) 222 | if (hook->lib == lib->name) 223 | hook->apifunc = GetProcAddress( lib->base, hook->name ); 224 | } 225 | break; 226 | } 227 | } 228 | if (lib->name == NULL) 229 | continue; 230 | kernel = FALSE; 231 | } 232 | 233 | // Get a pointer to the found module's import address table (IAT). 234 | pThunk = MakeVA( PIMAGE_THUNK_DATA, pImportDesc->FirstThunk ); 235 | 236 | // Blast through the table of import addresses, looking for the ones 237 | // that match the original addresses. 238 | while (pThunk->u1.Function) 239 | { 240 | for (hook = Hooks; hook->name; ++hook) 241 | { 242 | PROC patch = 0; 243 | if (restore) 244 | { 245 | if ((PROC)pThunk->u1.Function == hook->newfunc) 246 | patch = (kernel) ? hook->oldfunc : hook->apifunc; 247 | } 248 | else if ((PROC)pThunk->u1.Function == hook->oldfunc || 249 | (PROC)pThunk->u1.Function == hook->apifunc) 250 | { 251 | patch = hook->newfunc; 252 | } 253 | if (patch) 254 | { 255 | DWORD flOldProtect, flNewProtect, flDummy; 256 | MEMORY_BASIC_INFORMATION mbi; 257 | 258 | // Get the current protection attributes. 259 | VirtualQuery( &pThunk->u1.Function, &mbi, sizeof(mbi) ); 260 | // Take the access protection flags. 261 | flNewProtect = mbi.Protect; 262 | // Remove ReadOnly and ExecuteRead flags. 263 | flNewProtect &= ~(PAGE_READONLY | PAGE_EXECUTE_READ); 264 | // Add on ReadWrite flag 265 | flNewProtect |= (PAGE_READWRITE); 266 | // Change the access protection on the region of committed pages in the 267 | // virtual address space of the current process. 268 | VirtualProtect( &pThunk->u1.Function, sizeof(PVOID), 269 | flNewProtect, &flOldProtect ); 270 | 271 | // Overwrite the original address with the address of the new function. 272 | if (!WriteProcessMemory( GetCurrentProcess(), 273 | &pThunk->u1.Function, 274 | &patch, sizeof(patch), NULL )) 275 | { 276 | return FALSE; 277 | } 278 | 279 | // Put the page attributes back the way they were. 280 | VirtualProtect( &pThunk->u1.Function, sizeof(PVOID), 281 | flOldProtect, &flDummy ); 282 | } 283 | } 284 | pThunk++; // Advance to next imported function address 285 | } 286 | } 287 | 288 | return TRUE; // Function not found 289 | } 290 | 291 | //----------------------------------------------------------------------------- 292 | // HookAPIAllMod 293 | // Substitute a new function in the Import Address Table (IAT) of all 294 | // the modules in the current process. 295 | // Return FALSE on error and TRUE on success. 296 | //----------------------------------------------------------------------------- 297 | 298 | BOOL HookAPIAllMod( PHookFn Hooks, BOOL restore ) 299 | { 300 | HANDLE hModuleSnap; 301 | MODULEENTRY32 me; 302 | BOOL fOk; 303 | 304 | // Take a snapshot of all modules in the current process. 305 | hModuleSnap = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE, 306 | GetCurrentProcessId() ); 307 | 308 | if (hModuleSnap == INVALID_HANDLE_VALUE) 309 | return FALSE; 310 | 311 | // Fill the size of the structure before using it. 312 | me.dwSize = sizeof(MODULEENTRY32); 313 | 314 | // Walk the module list of the modules. 315 | for (fOk = Module32First( hModuleSnap, &me ); fOk; 316 | fOk = Module32Next( hModuleSnap, &me )) 317 | { 318 | // We don't hook functions in our own module. 319 | if (me.hModule != hDllInstance && me.hModule != hKernel) 320 | { 321 | // Hook this function in this module. 322 | if (!HookAPIOneMod( me.hModule, Hooks, restore )) 323 | { 324 | CloseHandle( hModuleSnap ); 325 | return FALSE; 326 | } 327 | } 328 | } 329 | CloseHandle( hModuleSnap ); 330 | return TRUE; 331 | } 332 | 333 | 334 | // ========== Child process injection 335 | 336 | // Inject code into the target process to load our DLL. 337 | void Inject( LPPROCESS_INFORMATION pinfo, LPPROCESS_INFORMATION lpi, 338 | DWORD dwCreationFlags ) 339 | { 340 | int type = ProcessType( pinfo ); 341 | if (type != 0) 342 | { 343 | #ifdef _WIN64 344 | if (type == 32) 345 | { 346 | hDllNameType[0] = '3'; 347 | hDllNameType[1] = '2'; 348 | InjectDLL32( pinfo ); 349 | } 350 | else 351 | { 352 | hDllNameType[0] = '6'; 353 | hDllNameType[1] = '4'; 354 | InjectDLL64( pinfo ); 355 | } 356 | #else 357 | #ifdef W32ON64 358 | if (type == 64) 359 | { 360 | TCHAR args[64]; 361 | STARTUPINFO si; 362 | PROCESS_INFORMATION pi; 363 | wcscpy( hDllNameType, L".exe" ); 364 | wsprintf( args, L"errout -P%lu:%lu", 365 | pinfo->dwProcessId, pinfo->dwThreadId ); 366 | ZeroMemory( &si, sizeof(si) ); 367 | si.cb = sizeof(si); 368 | if (CreateProcess( hDllName, args, NULL, NULL, FALSE, 0, NULL, NULL, 369 | &si, &pi )) 370 | { 371 | WaitForSingleObject( pi.hProcess, INFINITE ); 372 | CloseHandle( pi.hProcess ); 373 | CloseHandle( pi.hThread ); 374 | } 375 | wcscpy( hDllNameType, L"32.dll" ); 376 | } 377 | else 378 | #endif 379 | InjectDLL32( pinfo ); 380 | #endif 381 | } 382 | 383 | if (!(dwCreationFlags & CREATE_SUSPENDED)) 384 | ResumeThread( pinfo->hThread ); 385 | 386 | if (lpi) 387 | { 388 | memcpy( lpi, pinfo, sizeof(PROCESS_INFORMATION) ); 389 | } 390 | else 391 | { 392 | CloseHandle( pinfo->hProcess ); 393 | CloseHandle( pinfo->hThread ); 394 | } 395 | } 396 | 397 | 398 | BOOL WINAPI MyCreateProcessA( LPCSTR lpApplicationName, 399 | LPSTR lpCommandLine, 400 | LPSECURITY_ATTRIBUTES lpThreadAttributes, 401 | LPSECURITY_ATTRIBUTES lpProcessAttributes, 402 | BOOL bInheritHandles, 403 | DWORD dwCreationFlags, 404 | LPVOID lpEnvironment, 405 | LPCSTR lpCurrentDirectory, 406 | LPSTARTUPINFOA lpStartupInfo, 407 | LPPROCESS_INFORMATION lpProcessInformation ) 408 | { 409 | PROCESS_INFORMATION pi; 410 | 411 | if (!CreateProcessA( lpApplicationName, 412 | lpCommandLine, 413 | lpThreadAttributes, 414 | lpProcessAttributes, 415 | bInheritHandles, 416 | dwCreationFlags | CREATE_SUSPENDED, 417 | lpEnvironment, 418 | lpCurrentDirectory, 419 | lpStartupInfo, 420 | &pi )) 421 | return FALSE; 422 | 423 | Inject( &pi, lpProcessInformation, dwCreationFlags ); 424 | 425 | return TRUE; 426 | } 427 | 428 | 429 | BOOL WINAPI MyCreateProcessW( LPCWSTR lpApplicationName, 430 | LPWSTR lpCommandLine, 431 | LPSECURITY_ATTRIBUTES lpThreadAttributes, 432 | LPSECURITY_ATTRIBUTES lpProcessAttributes, 433 | BOOL bInheritHandles, 434 | DWORD dwCreationFlags, 435 | LPVOID lpEnvironment, 436 | LPCWSTR lpCurrentDirectory, 437 | LPSTARTUPINFOW lpStartupInfo, 438 | LPPROCESS_INFORMATION lpProcessInformation ) 439 | { 440 | PROCESS_INFORMATION pi; 441 | 442 | if (!CreateProcessW( lpApplicationName, 443 | lpCommandLine, 444 | lpThreadAttributes, 445 | lpProcessAttributes, 446 | bInheritHandles, 447 | dwCreationFlags | CREATE_SUSPENDED, 448 | lpEnvironment, 449 | lpCurrentDirectory, 450 | lpStartupInfo, 451 | &pi )) 452 | return FALSE; 453 | 454 | Inject( &pi, lpProcessInformation, dwCreationFlags ); 455 | 456 | return TRUE; 457 | } 458 | 459 | 460 | FARPROC WINAPI MyGetProcAddress( HMODULE hModule, LPCSTR lpProcName ) 461 | { 462 | PHookFn hook; 463 | FARPROC proc; 464 | 465 | proc = GetProcAddress( hModule, lpProcName ); 466 | 467 | if (proc) 468 | { 469 | if (hModule == hKernel) 470 | { 471 | // Ignore LoadLibrary so other hooks continue to work (our version 472 | // might end up at a different address). 473 | if (proc == Hooks[0].oldfunc || proc == Hooks[1].oldfunc) 474 | return proc; 475 | 476 | for (hook = Hooks + 2; hook->name; ++hook) 477 | { 478 | if (proc == hook->oldfunc) 479 | return hook->newfunc; 480 | } 481 | } 482 | else 483 | { 484 | PAPI_DATA api; 485 | for (api = APIs; api->name; ++api) 486 | { 487 | if (hModule == api->base) 488 | { 489 | if (proc == Hooks[0].apifunc || proc == Hooks[1].apifunc) 490 | return proc; 491 | for (hook = Hooks + 2; hook->name; ++hook) 492 | { 493 | if (proc == hook->apifunc) 494 | return hook->newfunc; 495 | } 496 | break; 497 | } 498 | } 499 | } 500 | } 501 | 502 | return proc; 503 | } 504 | 505 | 506 | HMODULE WINAPI MyLoadLibraryA( LPCSTR lpFileName ) 507 | { 508 | HMODULE hMod = LoadLibraryA( lpFileName ); 509 | if (hMod && hMod != hKernel) 510 | HookAPIOneMod( hMod, Hooks, FALSE ); 511 | return hMod; 512 | } 513 | 514 | 515 | HMODULE WINAPI MyLoadLibraryW( LPCWSTR lpFileName ) 516 | { 517 | HMODULE hMod = LoadLibraryW( lpFileName ); 518 | if (hMod && hMod != hKernel) 519 | HookAPIOneMod( hMod, Hooks, FALSE ); 520 | return hMod; 521 | } 522 | 523 | 524 | #define LOAD_LIBRARY_AS_DATA 0x62 525 | // 0x02 LOAD_LIBRARY_AS_DATAFILE 526 | // 0x20 LOAD_LIBRARY_AS_IMAGE_RESOURCE 527 | // 0x40 LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE 528 | 529 | HMODULE WINAPI MyLoadLibraryExA( LPCSTR lpFileName, HANDLE hFile, 530 | DWORD dwFlags ) 531 | { 532 | HMODULE hMod = LoadLibraryExA( lpFileName, hFile, dwFlags ); 533 | if (hMod && hMod != hKernel && !(dwFlags & LOAD_LIBRARY_AS_DATA)) 534 | HookAPIOneMod( hMod, Hooks, FALSE ); 535 | return hMod; 536 | } 537 | 538 | 539 | HMODULE WINAPI MyLoadLibraryExW( LPCWSTR lpFileName, HANDLE hFile, 540 | DWORD dwFlags ) 541 | { 542 | HMODULE hMod = LoadLibraryExW( lpFileName, hFile, dwFlags ); 543 | if (hMod && hMod != hKernel && !(dwFlags & LOAD_LIBRARY_AS_DATA)) 544 | HookAPIOneMod( hMod, Hooks, FALSE ); 545 | return hMod; 546 | } 547 | 548 | 549 | // ========== Initialisation 550 | 551 | HookFn Hooks[] = { 552 | // These two are expected first; WriteFile is expected last! 553 | { APILibraryLoader, "LoadLibraryA", (PROC)MyLoadLibraryA, NULL, NULL }, 554 | { APILibraryLoader, "LoadLibraryW", (PROC)MyLoadLibraryW, NULL, NULL }, 555 | { APIProcessThreads, "CreateProcessA", (PROC)MyCreateProcessA, NULL, NULL }, 556 | { APIProcessThreads, "CreateProcessW", (PROC)MyCreateProcessW, NULL, NULL }, 557 | { APILibraryLoader, "GetProcAddress", (PROC)MyGetProcAddress, NULL, NULL }, 558 | { APILibraryLoader, "LoadLibraryExA", (PROC)MyLoadLibraryExA, NULL, NULL }, 559 | { APILibraryLoader, "LoadLibraryExW", (PROC)MyLoadLibraryExW, NULL, NULL }, 560 | { APIFile, "WriteFile", (PROC)MyWriteFile, NULL, NULL }, 561 | { NULL, NULL, NULL, NULL } 562 | }; 563 | 564 | 565 | #ifdef W32ON64 566 | // Locate the globals placed by the 64-bit code and copy it for our 32-bit. 567 | void CopyGlobals( void ) 568 | { 569 | char* ptr; 570 | MEMORY_BASIC_INFORMATION minfo; 571 | 572 | for (ptr = NULL; 573 | VirtualQuery( ptr, &minfo, sizeof(minfo) ); 574 | ptr += minfo.RegionSize) 575 | { 576 | if (minfo.BaseAddress == minfo.AllocationBase && 577 | !IsBadReadPtr( minfo.AllocationBase, minfo.RegionSize )) 578 | { 579 | if (wcscmp( (LPCWSTR)(ptr + CODE32SIZE + GLOBAL32SIZE), hDllName ) == 0) 580 | { 581 | memcpy( &global, ptr + CODE32SIZE, GLOBAL32SIZE ); 582 | break; 583 | } 584 | } 585 | } 586 | } 587 | #endif 588 | 589 | 590 | //----------------------------------------------------------------------------- 591 | // DllMain() 592 | // Function called by the system when processes and threads are initialized 593 | // and terminated. 594 | //----------------------------------------------------------------------------- 595 | 596 | BOOL WINAPI DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved ) 597 | { 598 | BOOL bResult = TRUE; 599 | PHookFn hook; 600 | LPSTR name; 601 | 602 | if (dwReason == DLL_PROCESS_ATTACH) 603 | { 604 | #if defined(_WIN64) || defined(W32ON64) 605 | hDllNameType = hDllName - 6 + 606 | #endif 607 | GetModuleFileName( hInstance, hDllName, lenof(hDllName) ); 608 | 609 | hDllInstance = hInstance; // save Dll instance handle 610 | 611 | // Don't hook WriteFile in errout.exe (static load). 612 | name = Hooks[lenof(Hooks)-2].name; 613 | if (lpReserved != NULL) 614 | Hooks[lenof(Hooks)-2].name = NULL; 615 | 616 | // Get the entry points to the original functions. 617 | hKernel = GetModuleHandleA( APIKernel ); 618 | for (hook = Hooks; hook->name; ++hook) 619 | hook->oldfunc = GetProcAddress( hKernel, hook->name ); 620 | 621 | bResult = HookAPIAllMod( Hooks, FALSE ); 622 | DisableThreadLibraryCalls( hInstance ); 623 | 624 | Hooks[lenof(Hooks)-2].name = name; 625 | 626 | #ifdef W32ON64 627 | if (global.hStdOut == NULL) 628 | CopyGlobals(); 629 | #endif 630 | } 631 | else if (dwReason == DLL_PROCESS_DETACH) 632 | { 633 | // Unhook if it's being unloaded, but not if the process is exiting. 634 | if (lpReserved == NULL) 635 | HookAPIAllMod( Hooks, TRUE ); 636 | } 637 | 638 | return( bResult ); 639 | } 640 | -------------------------------------------------------------------------------- /errouthk.rc: -------------------------------------------------------------------------------- 1 | /* 2 | errouthk.rc - Version resource for errout{32,64}.dll. 3 | 4 | Jason Hood, 29 June, 2011. 5 | */ 6 | 7 | #include 8 | 9 | #ifdef _WIN64 10 | # define BITS "64" 11 | #else 12 | # define BITS "32" 13 | #endif 14 | 15 | 1 VERSIONINFO 16 | FILEVERSION 1,1,1,0 17 | PRODUCTVERSION 1,1,1,0 18 | FILEOS VOS_NT 19 | FILETYPE VFT_DLL 20 | { 21 | BLOCK "StringFileInfo" 22 | { 23 | BLOCK "040904B0" 24 | { 25 | VALUE "Comments", "http://errout.adoxa.vze.com/" 26 | VALUE "CompanyName", "Jason Hood" 27 | VALUE "FileDescription", "Errout Hook" 28 | VALUE "FileVersion", "1.11" 29 | VALUE "InternalName", "errouthk" 30 | VALUE "LegalCopyright", "Freeware" 31 | VALUE "OriginalFilename", "errout" BITS ".dll" 32 | VALUE "ProductName", "Errout" 33 | VALUE "ProductVersion", "1.11" 34 | } 35 | } 36 | 37 | BLOCK "VarFileInfo" 38 | { 39 | VALUE "Translation", 0x0409, 0x04B0 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /injdll32.c: -------------------------------------------------------------------------------- 1 | /* 2 | Inject code into the target process to load our DLL. The target thread 3 | should be suspended on entry; it remains suspended on exit. 4 | 5 | Initially I used the "stack" method of injection. However, this fails 6 | when DEP is active, since that doesn't allow code to execute in the stack. 7 | To overcome this I used the "CreateRemoteThread" method. However, this 8 | would fail with Wselect, a program to assist batch files. Wselect runs, 9 | but it has no output. As it turns out, removing the suspended flag would 10 | make Wselect work, but it caused problems with everything else. So now I 11 | allocate a section of memory and change the context to run from there. At 12 | first I had an event to signal when the library was loaded, then the memory 13 | was released. However, that wouldn't work with -p and CMD.EXE (4NT v8 14 | worked fine). Since it's possible the DLL might start a process suspended, 15 | I've decided to simply keep the memory. 16 | */ 17 | 18 | #include "errout.h" 19 | 20 | extern TCHAR hDllName[MAX_PATH]; 21 | extern struct Globals global; 22 | 23 | #ifdef _WIN64 24 | #ifndef WOW64_CONTEXT_ALL 25 | #include "wow64.h" 26 | 27 | TWow64GetThreadContext Wow64GetThreadContext; 28 | TWow64SetThreadContext Wow64SetThreadContext; 29 | #define IMPORT_WOW64 30 | #endif 31 | 32 | #define CONTEXT WOW64_CONTEXT 33 | #undef CONTEXT_CONTROL 34 | #define CONTEXT_CONTROL WOW64_CONTEXT_CONTROL 35 | #define GetThreadContext Wow64GetThreadContext 36 | #define SetThreadContext Wow64SetThreadContext 37 | 38 | #define MakeVA( cast, offset ) (cast)((DWORD_PTR)pDosHeader + (DWORD)(offset)) 39 | 40 | extern DWORD LLW32; 41 | static PIMAGE_DOS_HEADER pDosHeader; 42 | 43 | int export_cmp( const void* a, const void* b ) 44 | { 45 | return strcmp( (LPCSTR)a, MakeVA( LPCSTR, *(const PDWORD)b ) ); 46 | } 47 | 48 | 49 | /* 50 | Get the relative address of the 32-bit LoadLibraryW function from 64-bit code. 51 | This was originally done via executing a helper program (errout-LLW.exe), but 52 | I never liked doing that, so now I do it the "hard" way - load the 32-bit 53 | kernel32.dll directly and search the exports. 54 | */ 55 | BOOL get_LLW32( void ) 56 | { 57 | HMODULE kernel32; 58 | TCHAR buf[MAX_PATH]; 59 | UINT len; 60 | PIMAGE_NT_HEADERS32 pNTHeader; 61 | PIMAGE_EXPORT_DIRECTORY pExportDir; 62 | PDWORD fun_table, name_table; 63 | PWORD ord_table; 64 | PDWORD pLLW; 65 | 66 | len = GetSystemWow64Directory( buf, MAX_PATH ); 67 | wcscpy( buf + len, L"\\kernel32.dll" ); 68 | // MinGW-w64 had a typo, calling it LINRARY. 69 | kernel32 = LoadLibraryEx( buf, NULL, 0x20/*LOAD_LIBRARY_AS_IMAGE_RESOURCE*/ ); 70 | if (kernel32 == NULL) 71 | return FALSE; 72 | 73 | // The handle uses low bits as flags, so strip 'em off. 74 | pDosHeader = (PIMAGE_DOS_HEADER)((DWORD_PTR)kernel32 & ~0xFFFF); 75 | pNTHeader = MakeVA( PIMAGE_NT_HEADERS32, pDosHeader->e_lfanew ); 76 | pExportDir = MakeVA( PIMAGE_EXPORT_DIRECTORY, 77 | pNTHeader->OptionalHeader. 78 | DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]. 79 | VirtualAddress ); 80 | 81 | fun_table = MakeVA( PDWORD, pExportDir->AddressOfFunctions ); 82 | name_table = MakeVA( PDWORD, pExportDir->AddressOfNames ); 83 | ord_table = MakeVA( PWORD, pExportDir->AddressOfNameOrdinals ); 84 | 85 | pLLW = bsearch( "LoadLibraryW", name_table, pExportDir->NumberOfNames, 86 | sizeof(DWORD), export_cmp ); 87 | if (pLLW == NULL) 88 | { 89 | FreeLibrary( kernel32 ); 90 | return FALSE; 91 | } 92 | LLW32 = fun_table[ord_table[pLLW - name_table]]; 93 | 94 | FreeLibrary( kernel32 ); 95 | return TRUE; 96 | } 97 | #else 98 | DWORD LLW32; 99 | #endif 100 | 101 | 102 | void InjectDLL32( LPPROCESS_INFORMATION ppi ) 103 | { 104 | CONTEXT context; 105 | DWORD ep; 106 | DWORD len; 107 | LPVOID mem; 108 | DWORD mem32; 109 | DWORD pr; 110 | BYTE code[CODE32SIZE+GLOBAL32SIZE+TSIZE(MAX_PATH)]; 111 | union 112 | { 113 | PBYTE pB; 114 | PWORD pW; 115 | PDWORD pL; 116 | } ip; 117 | #ifdef _WIN64 118 | BOOL entry = FALSE; 119 | #endif 120 | 121 | #ifdef IMPORT_WOW64 122 | if (Wow64GetThreadContext == 0) 123 | { 124 | #define GETPROC( proc ) proc = (T##proc)GetProcAddress( hKernel, #proc ) 125 | HMODULE hKernel = GetModuleHandle( L"kernel32.dll" ); 126 | GETPROC( Wow64GetThreadContext ); 127 | GETPROC( Wow64SetThreadContext ); 128 | // Assume if one is defined, so is the other. 129 | if (Wow64GetThreadContext == 0) 130 | return; 131 | } 132 | #endif 133 | 134 | len = TSIZE(lstrlen( hDllName ) + 1); 135 | if (len > TSIZE(MAX_PATH)) 136 | return; 137 | 138 | CopyMemory( code + CODE32SIZE + GLOBAL32SIZE, hDllName, len ); 139 | len += CODE32SIZE + GLOBAL32SIZE; 140 | 141 | context.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; 142 | GetThreadContext( ppi->hThread, &context ); 143 | mem = VirtualAllocEx( ppi->hProcess, NULL, len, MEM_COMMIT, 144 | PAGE_READWRITE ); 145 | mem32 = (DWORD)(DWORD_PTR)mem; 146 | 147 | ip.pB = code; 148 | 149 | ep = context.Eip; 150 | if (LLW32 == 0) 151 | { 152 | #ifndef _WIN64 153 | LLW32 = (DWORD)GetProcAddress( GetModuleHandle( L"kernel32.dll" ), 154 | "LoadLibraryW" ); 155 | #else 156 | struct unicode_string 157 | { 158 | USHORT Length; 159 | USHORT MaximumLength; 160 | DWORD Buffer; 161 | }; 162 | struct ldr_module // incomplete definition 163 | { 164 | DWORD next, prev; 165 | DWORD baseAddress; 166 | DWORD entryPoint; 167 | DWORD sizeOfImage; 168 | struct unicode_string fullDllName; 169 | struct unicode_string baseDllName; 170 | } ldr; 171 | WCHAR basename[MAX_PATH]; 172 | 173 | if (!get_LLW32()) 174 | return; 175 | // Determine the base address of the 32-bit kernel32.dll. 176 | // Use the PEB to walk the loaded modules. 177 | // When a process is created suspended, EAX has the entry point and EBX 178 | // points to the PEB. 179 | if (!ReadProcessMemory( ppi->hProcess, UIntToPtr( context.Ebx + 0x0C ), 180 | ip.pL, 4, NULL )) 181 | { 182 | return; 183 | } 184 | // In case we're a bit slow (which seems to be unlikely), set up an 185 | // infinite loop as the entry point. 186 | WriteProcessMemory( ppi->hProcess, mem, "\xEB\xFE", 2, NULL ); 187 | FlushInstructionCache( ppi->hProcess, mem, 2 ); 188 | ep = context.Eax; 189 | context.Eax = mem32; 190 | SetThreadContext( ppi->hThread, &context ); 191 | VirtualProtectEx( ppi->hProcess, mem, len, PAGE_EXECUTE, &pr ); 192 | // Now resume the thread, as the PEB hasn't even been created yet. 193 | ResumeThread( ppi->hThread ); 194 | while (*ip.pL == 0) 195 | { 196 | Sleep( 0 ); 197 | ReadProcessMemory( ppi->hProcess, UIntToPtr( context.Ebx + 0x0C ), 198 | ip.pL, 4, NULL ); 199 | } 200 | // Read PEB_LDR_DATA.InInitializationOrderModuleList.Flink. 201 | ReadProcessMemory( ppi->hProcess, UIntToPtr( *ip.pL + 0x1c ), 202 | &ip.pL[1], 4, NULL ); 203 | // Sometimes we're so quick ntdll.dll is the only one present, so keep 204 | // looping until kernel32.dll shows up. 205 | for (;;) 206 | { 207 | ldr.next = ip.pL[1]; 208 | do 209 | { 210 | ReadProcessMemory( ppi->hProcess, UIntToPtr( ldr.next ), 211 | &ldr, sizeof(ldr), NULL ); 212 | ReadProcessMemory( ppi->hProcess, UIntToPtr( ldr.baseDllName.Buffer ), 213 | basename, ldr.baseDllName.MaximumLength, NULL ); 214 | if (_wcsicmp( basename, L"kernel32.dll" ) == 0) 215 | { 216 | LLW32 += ldr.baseAddress; 217 | goto gotit; 218 | } 219 | } while (ldr.next != *ip.pL + 0x1c); 220 | } 221 | gotit: 222 | SuspendThread( ppi->hThread ); 223 | VirtualProtectEx( ppi->hProcess, mem, len, pr, &pr ); 224 | entry = TRUE; 225 | #endif 226 | } 227 | 228 | *ip.pB++ = 0x68; // push ep 229 | *ip.pL++ = ep; 230 | *ip.pB++ = 0x9c; // pushf 231 | *ip.pB++ = 0x60; // pusha 232 | *ip.pB++ = 0x68; // push L"path\to\errout32.dll" 233 | *ip.pL++ = mem32 + CODE32SIZE + GLOBAL32SIZE; 234 | *ip.pB++ = 0xe8; // call LoadLibraryW 235 | *ip.pL++ = LLW32 - (mem32 + (DWORD)(ip.pB+4 - code)); 236 | *ip.pB++ = 0x61; // popa 237 | *ip.pB++ = 0x9d; // popf 238 | *ip.pB++ = 0xc3; // ret 239 | 240 | // Should probably now use shared memory rather than a shared section. 241 | *ip.pL++ = PtrToUint( global.hStdOut ); 242 | *ip.pL++ = PtrToUint( global.hStdCon ); 243 | *ip.pL++ = PtrToUint( global.hFilOut ); 244 | *ip.pL++ = PtrToUint( global.hFilErr ); 245 | *ip.pL++ = PtrToUint( global.hFilCon ); 246 | *ip.pW++ = global.console; 247 | *ip.pW++ = global.errcol; 248 | 249 | WriteProcessMemory( ppi->hProcess, mem, code, len, NULL ); 250 | FlushInstructionCache( ppi->hProcess, mem, len ); 251 | VirtualProtectEx( ppi->hProcess, mem, len, PAGE_EXECUTE, &pr ); 252 | #ifdef _WIN64 253 | if (entry) 254 | return; 255 | #endif 256 | context.Eip = mem32; 257 | SetThreadContext( ppi->hThread, &context ); 258 | } 259 | -------------------------------------------------------------------------------- /injdll64.c: -------------------------------------------------------------------------------- 1 | /* 2 | Inject code into the target process to load our DLL. The target thread 3 | should be suspended on entry; it remains suspended on exit. 4 | 5 | Initially I used the "stack" method of injection. However, this fails 6 | when DEP is active, since that doesn't allow code to execute in the stack. 7 | To overcome this I used the "CreateRemoteThread" method. However, this 8 | would fail with Wselect, a program to assist batch files. Wselect runs, 9 | but it has no output. As it turns out, removing the suspended flag would 10 | make Wselect work, but it caused problems with everything else. So now I 11 | allocate a section of memory and change the context to run from there. At 12 | first I had an event to signal when the library was loaded, then the memory 13 | was released. However, that wouldn't work with -p and CMD.EXE (4NT v8 14 | worked fine). Since it's possible the DLL might start a process suspended, 15 | I've decided to simply keep the memory. 16 | */ 17 | 18 | #include "errout.h" 19 | 20 | extern TCHAR hDllName[MAX_PATH]; 21 | 22 | 23 | __declspec(dllexport) 24 | void InjectDLL64( LPPROCESS_INFORMATION ppi ) 25 | { 26 | CONTEXT context; 27 | DWORD len; 28 | LPVOID mem; 29 | DWORD64 LLW; 30 | union 31 | { 32 | PBYTE pB; 33 | PDWORD64 pL; 34 | } ip; 35 | #define CODESIZE 92 36 | static BYTE code[CODESIZE+TSIZE(MAX_PATH)] = { 37 | 0,0,0,0,0,0,0,0, // original rip 38 | 0,0,0,0,0,0,0,0, // LoadLibraryW 39 | 0x9C, // pushfq 40 | 0x50, // push rax 41 | 0x51, // push rcx 42 | 0x52, // push rdx 43 | 0x53, // push rbx 44 | 0x55, // push rbp 45 | 0x56, // push rsi 46 | 0x57, // push rdi 47 | 0x41,0x50, // push r8 48 | 0x41,0x51, // push r9 49 | 0x41,0x52, // push r10 50 | 0x41,0x53, // push r11 51 | 0x41,0x54, // push r12 52 | 0x41,0x55, // push r13 53 | 0x41,0x56, // push r14 54 | 0x41,0x57, // push r15 55 | 0x48,0x83,0xEC,0x28, // sub rsp, 40 56 | 0x48,0x8D,0x0D,41,0,0,0, // lea ecx, L"path\to\errout64.dll" 57 | 0xFF,0x15,-49,-1,-1,-1, // call LoadLibraryW 58 | 0x48,0x83,0xC4,0x28, // add rsp, 40 59 | 0x41,0x5F, // pop r15 60 | 0x41,0x5E, // pop r14 61 | 0x41,0x5D, // pop r13 62 | 0x41,0x5C, // pop r12 63 | 0x41,0x5B, // pop r11 64 | 0x41,0x5A, // pop r10 65 | 0x41,0x59, // pop r9 66 | 0x41,0x58, // pop r8 67 | 0x5F, // pop rdi 68 | 0x5E, // pop rsi 69 | 0x5D, // pop rbp 70 | 0x5B, // pop rbx 71 | 0x5A, // pop rdx 72 | 0x59, // pop rcx 73 | 0x58, // pop rax 74 | 0x9D, // popfq 75 | 0xFF,0x25,-91,-1,-1,-1, // jmp original Rip 76 | 0, // dword alignment for LLW, fwiw 77 | }; 78 | 79 | len = TSIZE(lstrlen( hDllName ) + 1); 80 | if (len > TSIZE(MAX_PATH)) 81 | return; 82 | CopyMemory( code + CODESIZE, hDllName, len ); 83 | len += CODESIZE; 84 | 85 | context.ContextFlags = CONTEXT_CONTROL; 86 | GetThreadContext( ppi->hThread, &context ); 87 | mem = VirtualAllocEx( ppi->hProcess, NULL, len, MEM_COMMIT, 88 | PAGE_EXECUTE_READWRITE ); 89 | LLW = (DWORD64)LoadLibraryW; 90 | 91 | ip.pB = code; 92 | 93 | *ip.pL++ = context.Rip; 94 | *ip.pL++ = LLW; 95 | 96 | WriteProcessMemory( ppi->hProcess, mem, code, len, NULL ); 97 | FlushInstructionCache( ppi->hProcess, mem, len ); 98 | context.Rip = (DWORD64)mem + 16; 99 | SetThreadContext( ppi->hThread, &context ); 100 | } 101 | -------------------------------------------------------------------------------- /makefile.gcc: -------------------------------------------------------------------------------- 1 | # Makefile for Errout. 2 | # Jason Hood, 29 June, 2011. Updated 13 November, 2013. 3 | # 4 | # Tested with: 5 | # * MinGW/gcc 4.7.2; 6 | # * tdm-gcc-4.8.1-3; 7 | # * tdm64-gcc-4.8.1-3; 8 | # * MinGW-builds x64-4.8.1-release-posix-seh-rev1. 9 | # 10 | # Commands are hidden by default, add "V=1" to show them (or change var below). 11 | 12 | CC = gcc 13 | CFLAGS = -O2 -Wall 14 | 15 | #ARCH = 32 16 | #ARCH = 64 17 | #ARCH = multi 18 | 19 | ifndef ARCH 20 | # Use the machine to distinguish between MinGW and MinGW-w64. 21 | ifeq (,$(findstring 64,$(shell gcc -dumpmachine))) 22 | ARCH = 32 23 | else 24 | # It's 64-bit, if it's multi the lib name will be different. 25 | ifeq ($(shell gcc -m32 -print-libgcc-file-name),$(shell gcc -m64 -print-libgcc-file-name)) 26 | ARCH = 64 27 | else 28 | ARCH = multi 29 | endif 30 | endif 31 | endif 32 | 33 | # Determine the appropriate separator to run multiple commands - ";" for sh.exe 34 | # and "&" for CMD.EXE. $(SHELL) is initially defined to "sh.exe" - if it 35 | # actually exists, it becomes the full path. 36 | ifneq ($(wildcard $(SHELL)),) 37 | SEP = ; 38 | else 39 | SEP = & 40 | endif 41 | 42 | V ?= 0 43 | ifeq ($(V),0) 44 | CCmsg = @echo $<$(SEP) 45 | RCmsg = $(CCmsg) 46 | LDmsg = @echo $@$(SEP) 47 | endif 48 | 49 | x86/%.o: %.c errout.h 50 | $(CCmsg)$(CC) -m32 -c $(CFLAGS) $< -o $@ 51 | 52 | x86/%v.o: %.rc 53 | $(RCmsg)windres -U _WIN64 -F pe-i386 $< $@ 54 | 55 | x64/%.o: %.c errout.h 56 | $(CCmsg)$(CC) -m64 -c $(CFLAGS) $< -o $@ 57 | 58 | x64/%v.o: %.rc 59 | $(RCmsg)windres -F pe-x86-64 $< $@ 60 | 61 | x64/%32.o: %.c 62 | $(CCmsg)$(CC) -m32 -DW32ON64 $(CFLAGS) $< -c -o $@ 63 | 64 | 65 | ifeq ($(ARCH),multi) 66 | all: errout32 errout64 67 | else 68 | all: errout$(ARCH) 69 | endif 70 | 71 | errout32: x86 x86/errout32.dll x86/errout.exe x64 x64/errout32.dll 72 | 73 | errout64: x64 x64/errout64.dll x64/errout.exe 74 | 75 | x86: 76 | cmd /c "mkdir x86" 77 | 78 | x86/errout.exe: x86/errout.o x86/erroutv.o 79 | $(LDmsg)$(CC) -m32 $+ -s -o $@ x86/errout32.a 80 | 81 | x86/errout32.dll: x86/errouthk.o x86/proctype.o x86/injdll32.o x86/erroutv.o 82 | $(LDmsg)$(CC) -m32 $+ -s -o $@ -mdll -Wl,-shared,--out-implib,x86/errout32.a,--image-base,0xE00000 83 | 84 | x64: 85 | cmd /c "mkdir x64" 86 | 87 | x64/errout.exe: x64/errout.o x64/erroutv.o 88 | $(LDmsg)$(CC) -m64 $+ -s -o $@ x64/errout64.a 89 | 90 | x64/errout64.dll: x64/errouthk.o x64/proctype.o x64/injdll64.o x64/injdll32.o x64/erroutv.o 91 | $(LDmsg)$(CC) -m64 $+ -s -o $@ -mdll -Wl,-shared,--out-implib,x64/errout64.a,--image-base,0xE0000000 92 | 93 | x64/errout32.dll: x64/errouthk32.o x64/proctype32.o x86/injdll32.o x86/erroutv.o 94 | $(LDmsg)$(CC) -m32 $+ -s -o $@ -mdll -Wl,-shared,--image-base,0xE00000 95 | 96 | # Need two commands, because if the directory doesn't exist, it won't delete 97 | # anything at all. 98 | clean: 99 | -cmd /c "del x86\*.o 2>nul" 100 | -cmd /c "del x64\*.o 2>nul" 101 | -------------------------------------------------------------------------------- /makefile.vc: -------------------------------------------------------------------------------- 1 | # VC makefile for Errout. 2 | # Jason Hood, 2 July, 2011. Updated 13 November, 2013. 3 | # 4 | # Tested with: 5 | # * Visual Studio 6.0 (VC6, in conjuction with 2003 PSDK); 6 | # * Visual C++ 2003 Toolkit (VC7); 7 | # * Platform SDK for Windows Server 2003 R2 (VC8 64-bit); 8 | # * Visual Studio 2008 Express SP1 (VC9); 9 | # * Visual Studio 2010 Professional (VC10). 10 | # 11 | # Note that the 64-bit version still requires a 32-bit compiler. 12 | # 13 | # Commands are hidden by default, add "V=1" to show them (or change var below). 14 | 15 | #BITS = 32 16 | #BITS = 64 17 | 18 | !IFNDEF BITS 19 | !IF "$(CPU)" == "AMD64" || "$(PLATFORM)" == "x64" 20 | BITS = 64 21 | !ELSE 22 | BITS = 32 23 | !ENDIF 24 | !ENDIF 25 | 26 | !IF $(BITS) == 32 27 | DIR = x86 28 | !ELSE 29 | !IF $(BITS) == 64 30 | DIR = x64 31 | RFLAGS = /D_WIN64 32 | !ELSE 33 | !ERROR BITS should be defined to 32 or 64. 34 | !ENDIF 35 | !ENDIF 36 | 37 | STUBS = noarg.obj noenv.obj 38 | 39 | # This is required for the 2003 Platform SDK, but not for Visual Studio 2010. 40 | !IF "$(_NMAKE_VER)" == "7.00.8882" 41 | !IF $(BITS) == 64 42 | LIBS64 = bufferoverflowu.lib 43 | !ELSE 44 | # The 2003 Toolkit doesn't have MSVCRT.LIB, but VC98 does. 45 | !IF !DEFINED(SHARE) && !DEFINED(MSVCDIR) 46 | SHARE = 47 | !ENDIF 48 | # Neither have the stubbed out functions. 49 | STUBS = 50 | !ENDIF 51 | !ENDIF 52 | 53 | # Link with MSVCRT.LIB by default. 54 | !IFNDEF SHARE 55 | SHARE = /MD 56 | !ENDIF 57 | 58 | # Manifest tool to embed the manifest required by 2008. 59 | MT = mt.exe 60 | 61 | CFLAGS = /nologo /W3 /O2 $(SHARE) /D_CRT_SECURE_NO_WARNINGS 62 | LIBS = $(STUBS) advapi32.lib shell32.lib user32.lib $(LIBS64) 63 | 64 | !IF !DEFINED(V) 65 | V = 0 66 | !ENDIF 67 | !IF $(V) == 0 68 | CCmsg = @ 69 | RCmsg = @echo $<& 70 | LDmsg = @echo $@& 71 | MTmsg = @echo Embedding manifest& 72 | !ENDIF 73 | 74 | {}.c{$(DIR)}.obj: 75 | $(CCmsg)$(CC) /c $(CFLAGS) /Fo$@ $< 76 | 77 | {}.rc{$(DIR)}.res: 78 | $(RCmsg)$(RC) $(RFLAGS) /fo$@ $< 79 | 80 | all: errout$(BITS) 81 | 82 | errout32: x86 x86\errout32.dll x86\errout.exe x64 x64\errout32.dll 83 | 84 | errout64: x64 x64\errout64.dll x64\errout.exe 85 | 86 | x86: 87 | mkdir x86 88 | 89 | x86\errout.exe: x86\errout.obj x86\errout.res 90 | $(LDmsg)$(CC) /nologo $(SHARE) /Fe$@ $** $(LIBS) x86\errout32.lib /link /filealign:512 91 | !IF "$(_NMAKE_VER)" == "9.00.30729.01" 92 | $(MTmsg)$(MT) /nologo -manifest $@.manifest -outputresource:$@;1 93 | @del $@.manifest 94 | !ENDIF 95 | 96 | x86\errout32.dll: x86\errouthk.obj x86\proctype.obj x86\injdll32.obj x86\errouthk.res 97 | $(LDmsg)$(CC) /nologo $(SHARE) /LD /Fe$@ $** $(LIBS) /link /base:0xE00000 /section:.shared,s /filealign:512 98 | !IF "$(_NMAKE_VER)" == "9.00.30729.01" 99 | $(MTmsg)$(MT) /nologo -manifest $@.manifest -outputresource:$@;2 100 | @del $@.manifest 101 | !ENDIF 102 | 103 | x64: 104 | mkdir x64 105 | 106 | x64\errout.exe: x64\errout.obj x64\errout.res 107 | $(LDmsg)$(CC) /nologo $(SHARE) /Fe$@ $** $(LIBS) x64\errout64.lib 108 | 109 | x64\errout64.dll: x64\errouthk.obj x64\proctype.obj x64\injdll64.obj x64\injdll32.obj x64\errouthk.res 110 | $(LDmsg)$(CC) /nologo $(SHARE) /LD /Fe$@ $** $(LIBS) /link /base:0xE0000000 /section:.shared,s 111 | 112 | x64\errout32.dll: x64\errouthk32.obj x64\proctype32.obj x86\injdll32.obj x86\errouthk.res 113 | $(LDmsg)$(CC) /nologo $(SHARE) /LD /Fe$@ $** $(LIBS) /link /base:0xE00000 /section:.shared,s /filealign:512 114 | !IF "$(_NMAKE_VER)" == "9.00.30729.01" 115 | $(MTmsg)$(MT) /nologo -manifest $@.manifest -outputresource:$@;2 116 | @del $@.manifest 117 | !ENDIF 118 | 119 | errout.c: errout.h 120 | errouthk.c: errout.h 121 | injdll32.c: errout.h 122 | injdll64.c: errout.h 123 | proctype.c: errout.h 124 | 125 | x64\errouthk32.obj: errouthk.c 126 | $(CCmsg)$(CC) /DW32ON64 /c $(CFLAGS) /Fo$@ $? 127 | x64\proctype32.obj: proctype.c 128 | $(CCmsg)$(CC) /DW32ON64 /c $(CFLAGS) /Fo$@ $? 129 | 130 | clean: 131 | -del $(DIR)\*.obj $(DIR)\*.res $(DIR)\*.lib $(DIR)\*.exp 132 | !IF $(BITS) == 32 133 | -del x64\errouthk32.obj x64\proctype32.obj 134 | !ENDIF 135 | -------------------------------------------------------------------------------- /proctype.c: -------------------------------------------------------------------------------- 1 | /* 2 | Test for a valid process. This may sometimes detect GUI, even for a console 3 | process. I think this is due to a DLL being loaded in the address space 4 | before the main image. Ideally I could just use the base address directly, 5 | but that doesn't seem easy to do for another process - there doesn't seem to 6 | be a GetModuleHandle for another process. The CreateRemoteThread trick won't 7 | work with 64-bit (exit code is DWORD) and setting it up to make it work 8 | hardly seems worth it. There's GetModuleInformation, but passing in NULL just 9 | returns a base of NULL, so that's no help. At the moment, 64/32 is 10 | sufficient, so don't worry about it. 11 | 12 | Update: ignore images characterised as DLL. 13 | */ 14 | 15 | #include "errout.h" 16 | 17 | 18 | int ProcessType( LPPROCESS_INFORMATION pinfo ) 19 | { 20 | char* ptr; 21 | MEMORY_BASIC_INFORMATION minfo; 22 | IMAGE_DOS_HEADER dos_header; 23 | IMAGE_NT_HEADERS nt_header; 24 | SIZE_T read; 25 | 26 | for (ptr = NULL; 27 | VirtualQueryEx( pinfo->hProcess, ptr, &minfo, sizeof(minfo) ); 28 | ptr += minfo.RegionSize) 29 | { 30 | if (minfo.BaseAddress == minfo.AllocationBase && 31 | ReadProcessMemory( pinfo->hProcess, minfo.AllocationBase, 32 | &dos_header, sizeof(dos_header), &read )) 33 | { 34 | if (dos_header.e_magic == IMAGE_DOS_SIGNATURE) 35 | { 36 | if (ReadProcessMemory( pinfo->hProcess, (char*)minfo.AllocationBase + 37 | dos_header.e_lfanew, &nt_header, 38 | sizeof(nt_header), &read )) 39 | { 40 | if (nt_header.Signature == IMAGE_NT_SIGNATURE && 41 | (nt_header.FileHeader.Characteristics & 42 | (IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_DLL)) 43 | == IMAGE_FILE_EXECUTABLE_IMAGE) 44 | { 45 | int subsys = nt_header.OptionalHeader.Subsystem; 46 | if (subsys == IMAGE_SUBSYSTEM_WINDOWS_CUI || 47 | subsys == IMAGE_SUBSYSTEM_WINDOWS_GUI) 48 | { 49 | if (nt_header.FileHeader.Machine == IMAGE_FILE_MACHINE_I386) 50 | { 51 | return 32; 52 | } 53 | #if defined(_WIN64) || defined(W32ON64) 54 | if (nt_header.FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64) 55 | { 56 | return 64; 57 | } 58 | #endif 59 | } 60 | return 0; 61 | } 62 | } 63 | } 64 | } 65 | #ifndef _WIN64 66 | // If a 32-bit process loads a 64-bit one, we may miss the base 67 | // address. If the pointer overflows, assume 64-bit. 68 | if (((DWORD)ptr >> 12) + ((DWORD)minfo.RegionSize >> 12) > 0x80000) 69 | { 70 | #ifdef W32ON64 71 | return 64; 72 | #else 73 | return 0; 74 | #endif 75 | } 76 | #endif 77 | } 78 | 79 | return 0; 80 | } 81 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | Errout 2 | 3 | Copyright 2011-2013 Jason Hood 4 | 5 | Version 1.11. Freeware 6 | 7 | 8 | Description 9 | =========== 10 | 11 | There are occasions when you would like to redirect both standard output 12 | (stdout) and standard error (stderr). The normal method to achieve this is 13 | ">file 2>&1" - send stdout to the file and duplicate stderr to stdout. The 14 | problem with this method is it may change the order (as seen on the 15 | console) - you end up with all of stdout, followed by all of stderr. 16 | Errout really sends stderr to stdout, maintaining the order. 17 | 18 | 19 | Requirements 20 | ============ 21 | 22 | 32-bit: Windows 2000 Professional and later (it won't work with NT or 9X). 23 | 64-bit: Vista and later (it won't work with XP64 or Server 2003). 24 | 25 | 26 | Installation 27 | ============ 28 | 29 | Add "x86" (if your OS is 32-bit) or "x64" (if 64-bit) to your PATH, or copy 30 | the relevant files to a directory already on the PATH. 31 | 32 | Upgrading 33 | --------- 34 | 35 | Delete "errout-LLW.exe", it is no longer used. 36 | 37 | 38 | Usage 39 | ===== 40 | 41 | errout [options] program [args] 42 | 43 | -a[B]F 44 | 45 | Define the color (background and foreground) for stderr; if B is absent, it 46 | will default to 0 (black). I'll cheat here and just point you to COLOR/? 47 | for the values. The environment variable ERROUTCOL=[B]F may be set to 48 | provide -a by default. 49 | 50 | -c[e|o] 51 | 52 | In addition to sending output to stdout, this option will send it to the 53 | console (both stderr & stdout, or just one or the other). This should 54 | really only be used if you redirect, as otherwise you'll get an echo. 55 | 56 | -d 57 | 58 | Delay the hook till after the program is started. This may be useful for 59 | some programs. The prime example is 7z (this program's raison detre), 60 | which will refuse to combine the streams without it. 61 | 62 | -o FILE 63 | -e FILE 64 | -f FILE 65 | 66 | In addition to sending output to stdout, these options will send stdout, 67 | stderr and/or both to file. The lower case letter will always create the 68 | file; an upper case letter will append. 69 | 70 | program [args] 71 | 72 | The program and its arguments to run with stderr sent to stdout. 73 | 74 | 75 | Version History 76 | =============== 77 | 78 | Legend: + added, - bug-fixed, * changed. 79 | 80 | 1.11 - 5 December, 2013: 81 | - return the exit code of the program; 82 | * enable read sharing of the files. 83 | 84 | 1.10 - 14 November, 2013: 85 | * improved compatibility (thanks to all the work done on ANSICON). 86 | 87 | 88 | Contact 89 | ======= 90 | 91 | mailto:jadoxa@yahoo.com.au 92 | http://errout.adoxa.vze.com/ 93 | https://github.com/adoxa/errout 94 | 95 | Jason Hood 96 | 11 Buckle Street 97 | North Rockhampton 98 | Qld 4701 99 | Australia 100 | 101 | 102 | Distribution 103 | ============ 104 | 105 | The original zipfile can be freely distributed, by any means. However, I 106 | would like to be informed if it is placed on a CD-ROM (other than an arch- 107 | ive compilation; permission is granted, I'd just like to know). Modified 108 | versions may be distributed, provided it is indicated as such in the ver- 109 | sion text and a source diff is made available. In particular, the supplied 110 | binaries are freely redistributable. A formal license (zlib) is available 111 | in LICENSE.txt. 112 | 113 | 114 | ============================= 115 | Jason Hood, 5 December, 2013. 116 | -------------------------------------------------------------------------------- /wow64.h: -------------------------------------------------------------------------------- 1 | /* 2 | wow64.h - Definitions for Wow64. 3 | 4 | Mingw64/TDM does not include these Wow64 definitions. 5 | */ 6 | 7 | #ifndef WOW64_H 8 | #define WOW64_H 9 | 10 | #define WIN32_LEAN_AND_MEAN 11 | #include 12 | 13 | #define WOW64_CONTEXT_i386 0x00010000 14 | 15 | #define WOW64_CONTEXT_CONTROL (WOW64_CONTEXT_i386 | 0x00000001L) 16 | #define WOW64_CONTEXT_INTEGER (WOW64_CONTEXT_i386 | 0x00000002L) 17 | #define WOW64_CONTEXT_SEGMENTS (WOW64_CONTEXT_i386 | 0x00000004L) 18 | #define WOW64_CONTEXT_FLOATING_POINT (WOW64_CONTEXT_i386 | 0x00000008L) 19 | #define WOW64_CONTEXT_DEBUG_REGISTERS (WOW64_CONTEXT_i386 | 0x00000010L) 20 | #define WOW64_CONTEXT_EXTENDED_REGISTERS (WOW64_CONTEXT_i386 | 0x00000020L) 21 | 22 | #define WOW64_CONTEXT_FULL (WOW64_CONTEXT_CONTROL | WOW64_CONTEXT_INTEGER | WOW64_CONTEXT_SEGMENTS) 23 | 24 | #define WOW64_CONTEXT_ALL (WOW64_CONTEXT_CONTROL | WOW64_CONTEXT_INTEGER | WOW64_CONTEXT_SEGMENTS | \ 25 | WOW64_CONTEXT_FLOATING_POINT | WOW64_CONTEXT_DEBUG_REGISTERS | \ 26 | WOW64_CONTEXT_EXTENDED_REGISTERS) 27 | 28 | #define WOW64_SIZE_OF_80387_REGISTERS 80 29 | 30 | #define WOW64_MAXIMUM_SUPPORTED_EXTENSION 512 31 | 32 | typedef struct _WOW64_FLOATING_SAVE_AREA { 33 | DWORD ControlWord; 34 | DWORD StatusWord; 35 | DWORD TagWord; 36 | DWORD ErrorOffset; 37 | DWORD ErrorSelector; 38 | DWORD DataOffset; 39 | DWORD DataSelector; 40 | BYTE RegisterArea[WOW64_SIZE_OF_80387_REGISTERS]; 41 | DWORD Cr0NpxState; 42 | } WOW64_FLOATING_SAVE_AREA; 43 | 44 | typedef WOW64_FLOATING_SAVE_AREA *PWOW64_FLOATING_SAVE_AREA; 45 | 46 | typedef struct _WOW64_CONTEXT { 47 | 48 | DWORD ContextFlags; 49 | 50 | DWORD Dr0; 51 | DWORD Dr1; 52 | DWORD Dr2; 53 | DWORD Dr3; 54 | DWORD Dr6; 55 | DWORD Dr7; 56 | 57 | WOW64_FLOATING_SAVE_AREA FloatSave; 58 | 59 | DWORD SegGs; 60 | DWORD SegFs; 61 | DWORD SegEs; 62 | DWORD SegDs; 63 | 64 | DWORD Edi; 65 | DWORD Esi; 66 | DWORD Ebx; 67 | DWORD Edx; 68 | DWORD Ecx; 69 | DWORD Eax; 70 | 71 | DWORD Ebp; 72 | DWORD Eip; 73 | DWORD SegCs; 74 | DWORD EFlags; 75 | DWORD Esp; 76 | DWORD SegSs; 77 | 78 | BYTE ExtendedRegisters[WOW64_MAXIMUM_SUPPORTED_EXTENSION]; 79 | 80 | } WOW64_CONTEXT; 81 | 82 | typedef WOW64_CONTEXT *PWOW64_CONTEXT; 83 | 84 | 85 | typedef BOOL (WINAPI *TWow64GetThreadContext)( HANDLE hThread, PWOW64_CONTEXT lpContext ); 86 | typedef BOOL (WINAPI *TWow64SetThreadContext)( HANDLE hThread, CONST WOW64_CONTEXT *lpContext ); 87 | 88 | #endif 89 | --------------------------------------------------------------------------------