From dc7569dc267c7c7548b8218c47a26c0dec320e7a Mon Sep 17 00:00:00 2001 From: Jason Hood Date: Wed, 5 Feb 2014 00:21:42 +1000 Subject: [PATCH] Inject by adding to the Import Directory Table. -p uses CreateRemoteThread, determining kernel32.dll & LLW dynamically. Loading via LoadLibrary will remember the current attributes, restoring them on unload. Tweaked log output (remove quotes around CreateProcess command line; add an underscore to 64-bit addresses). ansicon.exe will really output (to the console) strings as Unicode. Fixed ansicon.exe, if installed, restoring the default attributes, not current. ansicon.exe will start with ANSICON_DEF (if defined and -m not used). --- ANSI.c | 105 ++++++++--------- LICENSE.txt | 2 +- LLW.c | 115 +++++++++++++++++++ ansicon.c | 314 ++++++++++++++++++++++++++++++++------------------- ansicon.h | 33 +++++- injdll.c | 202 +++++++++++++++++++++++++++++++++ injdll32.c | 251 ---------------------------------------- injdll64.c | 243 --------------------------------------- makefile.gcc | 26 +++-- makefile.vc | 31 +++-- proctype.c | 133 +++++++++++----------- readme.txt | 38 ++++--- util.c | 58 ++++++++-- version.h | 10 +- wow64.h | 88 --------------- 15 files changed, 767 insertions(+), 882 deletions(-) create mode 100644 LLW.c create mode 100644 injdll.c delete mode 100644 injdll32.c delete mode 100644 injdll64.c delete mode 100644 wow64.h diff --git a/ANSI.c b/ANSI.c index 0d89f66..a95bf71 100644 --- a/ANSI.c +++ b/ANSI.c @@ -108,13 +108,15 @@ v1.65, 28 August, 2013: fix \e[K (was using window, not buffer). - v.166, 20 & 21 September, 2013: + v1.66, 20 & 21 September, 2013: fix 32-bit process trying to detect 64-bit process. - v1.67, 25 to 27 January, 2014: + v1.70, 25 January to 4 February, 2014: don't hook ourself from LoadLibrary or LoadLibraryEx; update the LoadLibraryEx flags that should not cause hooking; - always find the base address of kernel32.dll. + inject by manipulating the import directory table; + restore original attribute on detach (for LoadLibrary/FreeLibrary usage); + log: remove the quotes around the CreateProcess command line string. */ #include "ansicon.h" @@ -135,6 +137,7 @@ // ========== Global variables and constants HANDLE hConOut; // handle to CONOUT$ +WORD orgattr; // original attribute #define ESC '\x1B' // ESCape character #define BEL '\x07' @@ -253,11 +256,6 @@ SHARED DWORD s_flag; #define GRM_INIT 1 #define GRM_EXIT 2 -SHARED DWORD LLW32r; -#ifdef _WIN64 -SHARED DWORD LLW64r; -#endif - // Wait for the child process to finish, then update our GRM to the child's. DWORD WINAPI UpdateGRM( LPVOID child_pi ) @@ -470,6 +468,7 @@ void InterpretEscSeq( void ) case 7: grm.rvideo = 1; break; case 8: grm.concealed = 1; break; case 21: // oops, this actually turns on double underline + // but xterm turns off bold too, so that's alright case 22: grm.bold = 0; break; case 25: case 24: grm.underline = 0; break; @@ -1049,9 +1048,7 @@ BOOL HookAPIOneMod( // We now have a valid pointer to the module's PE header. // Get a pointer to its imports section. pImportDesc = MakeVA( PIMAGE_IMPORT_DESCRIPTOR, - pNTHeader->OptionalHeader. - DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]. - VirtualAddress ); + pNTHeader->IMPORTDIR.VirtualAddress ); // Bail out if the RVA of the imports section is 0 (it doesn't exist) if (pImportDesc == (PIMAGE_IMPORT_DESCRIPTOR)pDosHeader) @@ -1113,35 +1110,18 @@ BOOL HookAPIOneMod( } if (patch) { - DWORD flOldProtect, flNewProtect, flDummy; - MEMORY_BASIC_INFORMATION mbi; + DWORD pr; DEBUGSTR( 3, L" %S", hook->name ); - // Get the current protection attributes. - VirtualQuery( &pThunk->u1.Function, &mbi, sizeof(mbi) ); - // Take the access protection flags. - flNewProtect = mbi.Protect; - // Remove ReadOnly and ExecuteRead flags. - flNewProtect &= ~(PAGE_READONLY | PAGE_EXECUTE_READ); - // Add on ReadWrite flag - flNewProtect |= (PAGE_READWRITE); - // Change the access protection on the region of committed pages in the - // virtual address space of the current process. - VirtualProtect( &pThunk->u1.Function, sizeof(PVOID), - flNewProtect, &flOldProtect ); + // Change the access protection on the region of committed pages in + // the virtual address space of the current process. + VirtualProtect( &pThunk->u1.Function, PTRSZ, PAGE_READWRITE, &pr ); // Overwrite the original address with the address of the new function. - if (!WriteProcessMemory( GetCurrentProcess(), - &pThunk->u1.Function, - &patch, sizeof(patch), NULL )) - { - DEBUGSTR( 1, L"Could not patch!" ); - return FALSE; - } + pThunk->u1.Function = (DWORD_PTR)patch; // Put the page attributes back the way they were. - VirtualProtect( &pThunk->u1.Function, sizeof(PVOID), - flOldProtect, &flDummy ); + VirtualProtect( &pThunk->u1.Function, PTRSZ, pr, &pr ); } } pThunk++; // Advance to next imported function address @@ -1211,11 +1191,12 @@ void Inject( DWORD dwCreationFlags, LPPROCESS_INFORMATION lpi, LPPROCESS_INFORMATION child_pi, BOOL wide, LPCVOID lpApp, LPCVOID lpCmd ) { - int type; - BOOL gui; + int type; + PBYTE base; + BOOL gui; - type = ProcessType( child_pi, &gui ); - if (gui) + type = ProcessType( child_pi, &base, &gui ); + if (gui && type > 0) { TCHAR app[MAX_PATH]; LPTSTR name; @@ -1273,20 +1254,20 @@ void Inject( DWORD dwCreationFlags, LPPROCESS_INFORMATION lpi, type = 0; } } - if (type != 0) + if (type > 0) { #ifdef _WIN64 if (type == 32) { - hDllNameType[0] = '3'; - hDllNameType[1] = '2'; - InjectDLL32( child_pi, hDllName ); + ansi_bits[0] = '3'; + ansi_bits[1] = '2'; + InjectDLL32( child_pi, base ); } else { - hDllNameType[0] = '6'; - hDllNameType[1] = '4'; - InjectDLL64( child_pi, hDllName ); + ansi_bits[0] = '6'; + ansi_bits[1] = '4'; + InjectDLL( child_pi, base ); } #else #ifdef W32ON64 @@ -1313,7 +1294,7 @@ void Inject( DWORD dwCreationFlags, LPPROCESS_INFORMATION lpi, } else #endif - InjectDLL32( child_pi, hDllName ); + InjectDLL( child_pi, base ); #endif if (!gui && !(dwCreationFlags & (CREATE_NEW_CONSOLE | DETACHED_PROCESS))) { @@ -1370,7 +1351,7 @@ BOOL WINAPI MyCreateProcessA( LPCSTR lpApplicationName, &child_pi )) return FALSE; - DEBUGSTR( 1, L"CreateProcessA: (%lu) \"%S\", \"%S\"", + DEBUGSTR( 1, L"CreateProcessA: (%lu) \"%S\", %S", child_pi.dwProcessId, (lpApplicationName == NULL) ? "" : lpApplicationName, (lpCommandLine == NULL) ? "" : lpCommandLine ); @@ -1406,7 +1387,7 @@ BOOL WINAPI MyCreateProcessW( LPCWSTR lpApplicationName, &child_pi )) return FALSE; - DEBUGSTR( 1, L"CreateProcessW: (%lu) \"%s\", \"%s\"", + DEBUGSTR( 1, L"CreateProcessW: (%lu) \"%s\", %s", child_pi.dwProcessId, (lpApplicationName == NULL) ? L"" : lpApplicationName, (lpCommandLine == NULL) ? L"" : lpCommandLine ); @@ -1609,7 +1590,6 @@ WINAPI MyWriteConsoleA( HANDLE hCon, LPCVOID lpBuffer, return WriteConsoleA( hCon, lpBuffer, nNumberOfCharsToWrite, lpNumberOfCharsWritten, lpReserved ); - } BOOL @@ -1774,6 +1754,7 @@ void OriginalAttr( void ) NULL, OPEN_EXISTING, 0, 0 ); if (!GetConsoleScreenBufferInfo( hConOut, &csbi )) csbi.wAttributes = 7; + orgattr = csbi.wAttributes; CloseHandle( hConOut ); if (s_flag == GRM_INIT && s_pid == GetCurrentProcessId()) @@ -1823,7 +1804,8 @@ void OriginalAttr( void ) // and terminated. //----------------------------------------------------------------------------- -__declspec(dllexport) // just to stop MinGW exporting everything +// Need to export something for static loading to work, this is as good as any. +__declspec(dllexport) BOOL WINAPI DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved ) { BOOL bResult = TRUE; @@ -1840,19 +1822,19 @@ BOOL WINAPI DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved ) hDllNameType = hDllName - 6 + #endif GetModuleFileName( hInstance, hDllName, lenof(hDllName) ); +#ifdef _WIN64 + ansi_bits = (LPSTR)hDllNameType; +#endif + set_ansi_dll( hDllName ); hDllInstance = hInstance; // save Dll instance handle - DEBUGSTR( 1, L"hDllInstance = %p", hDllInstance ); - - if (LLW32r == 0) - { - if (!get_LLW32r()) - return FALSE; #ifdef _WIN64 - if (!get_LLW64r()) - return FALSE; + DEBUGSTR( 1, L"hDllInstance = %.8X_%.8X", + (DWORD)((DWORD_PTR)hDllInstance >> 32), + PtrToUint( hDllInstance ) ); +#else + DEBUGSTR( 1, L"hDllInstance = %p", hDllInstance ); #endif - } // Get the entry points to the original functions. hKernel = GetModuleHandleA( APIKernel ); @@ -1869,6 +1851,11 @@ BOOL WINAPI DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved ) { DEBUGSTR( 1, L"Unloading" ); HookAPIAllMod( Hooks, TRUE ); + hConOut = CreateFile( L"CONOUT$", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, 0 ); + SetConsoleTextAttribute( hConOut, orgattr ); + CloseHandle( hConOut ); } else { diff --git a/LICENSE.txt b/LICENSE.txt index ca3654f..eaaa56a 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (C) 2005-2013 Jason Hood +Copyright (C) 2005-2014 Jason Hood This software is provided 'as-is', without any express or implied warranty. In no event will the author be held liable for any damages diff --git a/LLW.c b/LLW.c new file mode 100644 index 0000000..bacf893 --- /dev/null +++ b/LLW.c @@ -0,0 +1,115 @@ +/* + Locate the relative address of LoadLibraryW in kernel32.dll. This is needed + to get the 32-bit address from 64-bit code, and it eliminates the possibility + of it already being hooked. +*/ + +#include "ansicon.h" + +static PIMAGE_DOS_HEADER pDosHeader; + +#define MakeVA( cast, offset ) (cast)((DWORD_PTR)pDosHeader + (DWORD)(offset)) + + +static int export_cmp( const void* a, const void* b ) +{ + return strcmp( (LPCSTR)a, MakeVA( LPCSTR, *(const PDWORD)b ) ); +} + + +DWORD get_LLW32r( void ) +{ + HMODULE kernel32; + TCHAR buf[MAX_PATH]; + UINT len; + PIMAGE_NT_HEADERS32 pNTHeader; + PIMAGE_EXPORT_DIRECTORY pExportDir; + PDWORD fun_table, name_table; + PWORD ord_table; + PDWORD pLLW; + DWORD LLWr; + +#ifdef _WIN64 + len = GetSystemWow64Directory( buf, MAX_PATH ); +#else + len = GetSystemDirectory( buf, MAX_PATH ); +#endif + wcscpy( buf + len, L"\\kernel32.dll" ); + kernel32 = LoadLibraryEx( buf, NULL, LOAD_LIBRARY_AS_IMAGE_RESOURCE ); + if (kernel32 == NULL) + { + DEBUGSTR( 1, L"Unable to load 32-bit kernel32.dll!" ); + return 0; + } + // The handle uses low bits as flags, so strip 'em off. + pDosHeader = (PIMAGE_DOS_HEADER)((DWORD_PTR)kernel32 & ~0xFFFF); + pNTHeader = MakeVA( PIMAGE_NT_HEADERS32, pDosHeader->e_lfanew ); + pExportDir = MakeVA( PIMAGE_EXPORT_DIRECTORY, + pNTHeader->EXPORTDIR.VirtualAddress ); + + fun_table = MakeVA( PDWORD, pExportDir->AddressOfFunctions ); + name_table = MakeVA( PDWORD, pExportDir->AddressOfNames ); + ord_table = MakeVA( PWORD, pExportDir->AddressOfNameOrdinals ); + + pLLW = bsearch( "LoadLibraryW", name_table, pExportDir->NumberOfNames, + sizeof(DWORD), export_cmp ); + if (pLLW == NULL) + { + DEBUGSTR( 1, L"Could not find 32-bit LoadLibraryW!" ); + LLWr = 0; + } + else + { + LLWr = fun_table[ord_table[pLLW - name_table]]; + } + FreeLibrary( kernel32 ); + return LLWr; +} + + +#ifdef _WIN64 +DWORD64 get_LLW64r( void ) +{ + HMODULE kernel32; + TCHAR buf[MAX_PATH]; + UINT len; + PIMAGE_NT_HEADERS pNTHeader; + PIMAGE_EXPORT_DIRECTORY pExportDir; + PDWORD fun_table, name_table; + PWORD ord_table; + PDWORD pLLW; + DWORD64 LLWr; + + len = GetSystemDirectory( buf, MAX_PATH ); + wcscpy( buf + len, L"\\kernel32.dll" ); + kernel32 = LoadLibraryEx( buf, NULL, LOAD_LIBRARY_AS_IMAGE_RESOURCE ); + if (kernel32 == NULL) + { + DEBUGSTR( 1, L"Unable to load 64-bit kernel32.dll!" ); + return 0; + } + // The handle uses low bits as flags, so strip 'em off. + pDosHeader = (PIMAGE_DOS_HEADER)((DWORD_PTR)kernel32 & ~0xFFFF); + pNTHeader = MakeVA( PIMAGE_NT_HEADERS, pDosHeader->e_lfanew ); + pExportDir = MakeVA( PIMAGE_EXPORT_DIRECTORY, + pNTHeader->EXPORTDIR.VirtualAddress ); + + fun_table = MakeVA( PDWORD, pExportDir->AddressOfFunctions ); + name_table = MakeVA( PDWORD, pExportDir->AddressOfNames ); + ord_table = MakeVA( PWORD, pExportDir->AddressOfNameOrdinals ); + + pLLW = bsearch( "LoadLibraryW", name_table, pExportDir->NumberOfNames, + sizeof(DWORD), export_cmp ); + if (pLLW == NULL) + { + DEBUGSTR( 1, L"Could not find 64-bit LoadLibraryW!" ); + LLWr = 0; + } + else + { + LLWr = fun_table[ord_table[pLLW - name_table]]; + } + FreeLibrary( kernel32 ); + return LLWr; +} +#endif diff --git a/ansicon.c b/ansicon.c index fd58f18..1f5eea7 100644 --- a/ansicon.c +++ b/ansicon.c @@ -76,17 +76,30 @@ v1.63, 25 July, 2013: don't write the reset sequence if output is redirected. + + v1.70, 31 January to 3 February, 2014: + restore the original (current, not default) attributes if using ansicon.exe + when it's already installed; + use ANSICON_DEF if defined and -m not given; + -e and -t will not output anything if the DLL could not load; + use Unicode output (_O_U16TEXT, for compilers/systems that support it); + log: 64-bit addresses get an underscore between the 8-digit groups. */ -#define PDATE L"27 January, 2014" +#define PDATE L"4 February, 2014" #include "ansicon.h" #include "version.h" #include #include +#include #include #include +#ifndef _O_U16TEXT +#define _O_U16TEXT 0x20000 +#endif + #ifdef __MINGW32__ int _CRT_glob = 0; #endif @@ -99,7 +112,7 @@ int _CRT_glob = 0; void help( void ); void display( LPCTSTR, BOOL ); -void print_error( LPCTSTR, ... ); +void print_error( LPCTSTR ); LPTSTR skip_spaces( LPTSTR ); void get_arg( LPTSTR, LPTSTR*, LPTSTR* ); void get_file( LPTSTR, LPTSTR*, LPTSTR* ); @@ -110,12 +123,49 @@ BOOL find_proc_id( HANDLE snap, DWORD id, LPPROCESSENTRY32 ppe ); BOOL GetParentProcessInfo( LPPROCESS_INFORMATION ppi, LPTSTR ); -// The DLL shares this variable, so injection requires it here. -DWORD LLW32r; -#ifdef _WIN64 -DWORD LLW64r; -#endif -extern LPVOID kernel32_base; +static HANDLE hConOut; +static WORD wAttr; + +void get_original_attr( void ) +{ + CONSOLE_SCREEN_BUFFER_INFO csbi; + + hConOut = CreateFile( L"CONOUT$", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, 0 ); + GetConsoleScreenBufferInfo( hConOut, &csbi ); + wAttr = csbi.wAttributes; +} + + +void set_original_attr( void ) +{ + SetConsoleTextAttribute( hConOut, wAttr ); + CloseHandle( hConOut ); +} + + +// The fputws function in MSVCRT.DLL (Windows 7 x64) is broken for Unicode +// output (it just writes the first character). VC6 & 7 don't support Unicode +// output at all (just converting to ANSI) and even when it is supported, it +// just writes single characters (as does _putws & fwprintf). So what the +// heck, DIY. +int my_fputws( const wchar_t* s, FILE* f ) +{ + if (_isatty( _fileno( f ) )) + { + DWORD written; + WriteConsole( hConOut, s, (DWORD)wcslen( s ), &written, NULL ); + } + else + { + fputws( s, f ); + } + return 0; +} + +#define fputws my_fputws +#define _putws( s ) my_fputws( s L"\n", stdout ) // Find the name of the DLL and inject it. @@ -124,12 +174,14 @@ BOOL Inject( LPPROCESS_INFORMATION ppi, BOOL* gui, LPCTSTR app ) DWORD len; WCHAR dll[MAX_PATH]; int type; + PBYTE base; DEBUGSTR( 1, L"%s (%lu)", app, ppi->dwProcessId ); - type = ProcessType( ppi, gui ); - if (type == 0) + type = ProcessType( ppi, &base, gui ); + if (type <= 0) { - fwprintf( stderr, L"ANSICON: %s: unsupported process.\n", app ); + if (type == 0) + fwprintf( stderr, L"ANSICON: %s: unsupported process.\n", app ); return FALSE; } @@ -137,41 +189,94 @@ BOOL Inject( LPPROCESS_INFORMATION ppi, BOOL* gui, LPCTSTR app ) memcpy( dll, prog_path, TSIZE(len) ); #ifdef _WIN64 wsprintf( dll + len, L"ANSI%d.dll", type ); + ansi_bits = (LPSTR)(dll + len + 4); + set_ansi_dll( dll ); if (type == 32) - { - get_LLW32r(); - InjectDLL32( ppi, dll ); - } + InjectDLL32( ppi, base ); else - { - get_LLW64r(); - InjectDLL64( ppi, dll ); - } + InjectDLL( ppi, base ); #else wcscpy( dll + len, L"ANSI32.dll" ); - get_LLW32r(); - InjectDLL32( ppi, dll ); + set_ansi_dll( dll ); + InjectDLL( ppi, base ); #endif return TRUE; } -static HANDLE hConOut; -static CONSOLE_SCREEN_BUFFER_INFO csbi; - -void get_original_attr( void ) +// Use CreateRemoteThread to load our DLL in the target process. +void RemoteLoad( LPPROCESS_INFORMATION ppi ) { - hConOut = CreateFile( L"CONOUT$", GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, 0, 0 ); - GetConsoleScreenBufferInfo( hConOut, &csbi ); -} + HANDLE hSnap; + MODULEENTRY32 me; + PBYTE LLW; + BOOL fOk; + WCHAR dll[MAX_PATH]; + DWORD len; + LPVOID mem; + HANDLE thread; +#ifdef _WIN64 + BOOL WOW64; +#endif + // Find the base address of kernel32.dll. + LLW = NULL; + hSnap = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, + ppi->dwProcessId ); + if (hSnap != INVALID_HANDLE_VALUE) + { + me.dwSize = sizeof(MODULEENTRY32); + for (fOk = Module32First( hSnap, &me ); fOk; + fOk = Module32Next( hSnap, &me )) + { + if (_wcsicmp( me.szModule, L"kernel32.dll" ) == 0) + { + LLW = me.modBaseAddr; + break; + } + } + CloseHandle( hSnap ); + } +#ifndef _WIN64 + else if (GetLastError() == ERROR_PARTIAL_COPY) + { + fputws( L"ANSICON: parent is 64-bit (use x64\\ansicon).\n", stderr ); + return; + } +#endif + if (LLW == NULL) + { + no_llw: + fputws( L"ANSICON: unable to inject into parent.\n", stderr ); + return; + } -void set_original_attr( void ) -{ - SetConsoleTextAttribute( hConOut, csbi.wAttributes ); - CloseHandle( hConOut ); + len = (DWORD)(prog - prog_path); + memcpy( dll, prog_path, TSIZE(len) ); +#ifdef _WIN64 + if (IsWow64Process( ppi->hProcess, &WOW64 ) && WOW64) + { + wcscpy( dll + len, L"ANSI32.dll" ); + LLW += get_LLW32r(); + } + else + { + wcscpy( dll + len, L"ANSI64.dll" ); + LLW += get_LLW64r(); + } +#else + wcscpy( dll + len, L"ANSI32.dll" ); + LLW += get_LLW32r(); +#endif + if (LLW == me.modBaseAddr) + goto no_llw; + mem = VirtualAllocEx( ppi->hProcess, NULL, len, MEM_COMMIT, PAGE_READWRITE ); + WriteProcMem( mem, dll, TSIZE(len + 11) ); + thread = CreateRemoteThread( ppi->hProcess, NULL, 4096, + (LPTHREAD_START_ROUTINE)LLW, mem, 0, NULL ); + WaitForSingleObject( thread, INFINITE ); + CloseHandle( thread ); + VirtualFreeEx( ppi->hProcess, mem, 0, MEM_RELEASE ); } @@ -186,13 +291,25 @@ int main( void ) STARTUPINFO si; PROCESS_INFORMATION pi; LPTSTR argv, arg, cmd; - TCHAR logstr[4]; - BOOL installed; + TCHAR buf[4]; BOOL shell, run, gui; HMODULE ansi; DWORD len; int rc = 0; + // Convert wide strings using the current code page. + sprintf( (LPSTR)buf, ".%u", GetConsoleOutputCP() ); + setlocale( LC_CTYPE, (LPSTR)buf ); + + // Switch console output to Unicode. + if (_isatty( 1 )) + _setmode( 1, _O_U16TEXT); + if (_isatty( 2 )) + _setmode( 2, _O_U16TEXT); + + // Create a console handle and store the current attribute. + get_original_attr(); + argv = GetCommandLine(); len = (DWORD)wcslen( argv ) + 1; if (len < MAX_PATH) @@ -217,20 +334,16 @@ int main( void ) } prog = get_program_name( NULL ); - *logstr = '\0'; - GetEnvironmentVariable( L"ANSICON_LOG", logstr, lenof(logstr) ); - log_level = _wtoi( logstr ); - - // Using "" for setlocale uses the system ANSI code page. - sprintf( (LPSTR)logstr, ".%u", GetConsoleOutputCP() ); - setlocale( LC_CTYPE, (LPSTR)logstr ); + *buf = '\0'; + GetEnvironmentVariable( L"ANSICON_LOG", buf, lenof(buf) ); + log_level = _wtoi( buf ); #ifdef _WIN64 if (*arg == '-' && arg[1] == 'P') { swscanf( arg + 2, L"%u:%u", &pi.dwProcessId, &pi.dwThreadId ); - pi.hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pi.dwProcessId); - pi.hThread = OpenThread( THREAD_ALL_ACCESS, FALSE, pi.dwThreadId ); + pi.hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, pi.dwProcessId ); + pi.hThread = OpenThread( THREAD_ALL_ACCESS, FALSE, pi.dwThreadId ); Inject( &pi, &gui, arg ); CloseHandle( pi.hThread ); CloseHandle( pi.hProcess ); @@ -241,19 +354,7 @@ int main( void ) if (log_level) DEBUGSTR( 1, NULL ); // start a new session - installed = (GetEnvironmentVariable( L"ANSICON_VER", NULL, 0 ) != 0); - // If it's already installed, remove it. This serves two purposes: preserves - // the parent's GRM; and unconditionally injects into GUI, without having to - // worry about ANSICON_GUI. - if (installed) - { - if (_isatty( 1 )) - fputws( L"\33[m", stdout ); - FreeLibrary( GetModuleHandle( ANSIDLL ) ); - } - shell = run = TRUE; - get_original_attr(); while (*arg == '-') { @@ -277,40 +378,12 @@ int main( void ) case 'p': shell = FALSE; - // If it's already installed, there's no need to do anything. - if (installed) + if (GetParentProcessInfo( &pi, arg + 3 )) { - DEBUGSTR( 1, L"Already installed" ); - } - else if (GetParentProcessInfo( &pi, arg )) - { - HANDLE hSnap; - MODULEENTRY32 me; - BOOL fOk; - pi.hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pi.dwProcessId); pi.hThread = OpenThread( THREAD_ALL_ACCESS, FALSE, pi.dwThreadId ); SuspendThread( pi.hThread ); - // Find the base address of kernel32.dll. - hSnap = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE | - TH32CS_SNAPMODULE32, - pi.dwProcessId ); - if (hSnap != INVALID_HANDLE_VALUE) - { - me.dwSize = sizeof(MODULEENTRY32); - for (fOk = Module32First( hSnap, &me ); fOk; - fOk = Module32Next( hSnap, &me )) - { - if (_wcsicmp( me.szModule, L"kernel32.dll" ) == 0) - { - kernel32_base = me.modBaseAddr; - break; - } - } - CloseHandle( hSnap ); - } - if (!Inject( &pi, &gui, arg )) - rc = 1; + RemoteLoad( &pi ); ResumeThread( pi.hThread ); CloseHandle( pi.hThread ); CloseHandle( pi.hProcess ); @@ -360,6 +433,15 @@ arg_out: } } + // Ensure the default attributes are the current attributes. + if (GetEnvironmentVariable( L"ANSICON_DEF", buf, lenof(buf) ) != 0) + { + int a = wcstol( buf, NULL, 16 ); + if (a < 0) + a = ((-a >> 4) & 15) | ((-a & 15) << 4); + SetConsoleTextAttribute( hConOut, (WORD)a ); + } + if (run) { if (*cmd == '\0') @@ -392,7 +474,7 @@ arg_out: } else { - print_error( arg, arg ); + print_error( arg ); rc = 1; } } @@ -404,8 +486,7 @@ arg_out: print_error( ANSIDLL ); rc = 1; } - - if (*arg == 'e' || *arg == 'E') + else if (*arg == 'e' || *arg == 'E') { cmd += 2; if (*cmd == ' ' || *cmd == '\t') @@ -433,14 +514,10 @@ arg_out: get_file( arg, &argv, &cmd ); } while (*arg); } - FreeLibrary( ansi ); } - if (run || *arg) - set_original_attr(); - else - CloseHandle( hConOut ); + set_original_attr(); return rc; } @@ -495,30 +572,30 @@ void display( LPCTSTR name, BOOL title ) } -void print_error( LPCTSTR name, ... ) +void print_error( LPCTSTR name ) { LPTSTR errmsg = NULL; - DWORD err = GetLastError(); - va_list arg; + DWORD err = GetLastError(); + fputws( L"ANSICON: ", stderr ); if (err == ERROR_BAD_EXE_FORMAT) { - // This error requires an argument, which is a duplicate of name. - va_start( arg, name ); - FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, - NULL, err, 0, (LPTSTR)(LPVOID)&errmsg, 0, &arg ); - va_end( arg ); - fwprintf( stderr, L"ANSICON: %s", errmsg ); + // This error requires an argument, which is name. + FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_ARGUMENT_ARRAY, + NULL, err, 0, (LPTSTR)(LPVOID)&errmsg, 0, (va_list*)&name ); + fputws( errmsg, stderr ); } else { - FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, - NULL, err, 0, (LPTSTR)(LPVOID)&errmsg, 0, NULL ); - // Just in case there are other messages requiring args... - if (errmsg == NULL) - fwprintf( stderr, L"ANSICON: %s: Error %lu.\n", name, err ); + if (FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, err, 0, (LPTSTR)(LPVOID)&errmsg, 0, NULL )) + fwprintf( stderr, L"%s: %s", name, errmsg ); else - fwprintf( stderr, L"ANSICON: %s: %s", name, errmsg ); + fwprintf( stderr, L"%s: Error %lu.\n", name, err ); } LocalFree( errmsg ); } @@ -759,7 +836,7 @@ void get_file( LPTSTR arg, LPTSTR* argv, LPTSTR* cmd ) for (path = name = arg; *path != '\0'; ++path) if (*path == '\\' || *path == '/') name = path + 1; - glob = malloc( (globbed + 1) * sizeof(LPTSTR) + TSIZE(size) ); + glob = malloc( (globbed + 1) * PTRSZ + TSIZE(size) ); path = (LPTSTR)(glob + globbed + 1); globbed = 0; fh = FindFirstFile( arg, &fd ); @@ -789,7 +866,7 @@ void get_file( LPTSTR arg, LPTSTR* argv, LPTSTR* cmd ) FindClose( fh ); glob[globbed] = NULL; - qsort( glob, globbed, sizeof(LPTSTR), glob_sort ); + qsort( glob, globbed, PTRSZ, glob_sort ); wcscpy( name, glob[0] ); globbed = 1; @@ -799,6 +876,13 @@ void get_file( LPTSTR arg, LPTSTR* argv, LPTSTR* cmd ) } +// VC macros don't like preprocessor statements mixed with strings. +#ifdef _WIN64 +#define WINTYPE L"Windows" +#else +#define WINTYPE L"Win32" +#endif + void help( void ) { _putws( @@ -806,11 +890,7 @@ L"ANSICON by Jason Hood .\n" L"Version " PVERS L" (" PDATE L"). Freeware.\n" L"http://ansicon.adoxa.vze.com/\n" L"\n" -#ifdef _WIN64 -L"Process ANSI escape sequences in Windows console programs.\n" -#else -L"Process ANSI escape sequences in Win32 console programs.\n" -#endif +L"Process ANSI escape sequences in " WINTYPE L" console programs.\n" L"\n" L"ansicon [-l] [-i] [-I] [-u] [-U] [-m[]] [-p]\n" L" [-e|E string | -t|T [file(s)] | program [args]]\n" diff --git a/ansicon.h b/ansicon.h index 5a9a5b5..d5c62a5 100644 --- a/ansicon.h +++ b/ansicon.h @@ -31,6 +31,22 @@ #define LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE 0x20 #endif +#define EXPORTDIR OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT] +#define IMPORTDIR OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] +#define BOUNDDIR OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT] +#define IATDIR OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT] +#define COMDIR OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR] + + +// Reduce the verbosity of some functions (assuming variable names). +#define ReadProcVar(a, b) ReadProcMem( a, b, sizeof(*(b)) ) +#define WriteProcVar(a, b) WriteProcMem( a, b, sizeof(*(b)) ) +#define ReadProcMem(a, b, c) ReadProcessMemory( ppi->hProcess, a, b, c, NULL ) +#define WriteProcMem(a, b, c) WriteProcessMemory( ppi->hProcess, a, b, c, NULL ) +#define VirtProtVar(a, b) VirtualProtectEx( ppi->hProcess, a, sizeof(*(a)), b, &pr ) + +#define PTRSZ sizeof(PVOID) + typedef struct { @@ -44,16 +60,23 @@ typedef struct } GRM, *PGRM; // Graphic Rendition Mode -int ProcessType( LPPROCESS_INFORMATION, BOOL* ); -void InjectDLL32( LPPROCESS_INFORMATION, LPCTSTR ); -void InjectDLL64( LPPROCESS_INFORMATION, LPCTSTR ); -BOOL get_LLW32r( void ); -BOOL get_LLW64r( void ); +int ProcessType( LPPROCESS_INFORMATION, PBYTE*, BOOL* ); + +void InjectDLL( LPPROCESS_INFORMATION, PBYTE ); +void InjectDLL32( LPPROCESS_INFORMATION, PBYTE ); + +DWORD get_LLW32r( void ); +DWORD64 get_LLW64r( void ); extern TCHAR prog_path[MAX_PATH]; extern LPTSTR prog; LPTSTR get_program_name( LPTSTR ); +extern char ansi_dll[MAX_PATH]; +extern DWORD ansi_len; +extern char* ansi_bits; +void set_ansi_dll( LPTSTR ); + extern int log_level; void DEBUGSTR( int level, LPTSTR szFormat, ... ); diff --git a/injdll.c b/injdll.c new file mode 100644 index 0000000..7efe624 --- /dev/null +++ b/injdll.c @@ -0,0 +1,202 @@ +/* + Inject the DLL into the target process by modifying its import descriptor + table. The target process must have been created suspended. +*/ + +#include "ansicon.h" + + +// Search for a suitable free area after the main image. (32-bit code could +// really go anywhere, but let's keep it relatively local.) +PVOID FindMem( HANDLE hProcess, PBYTE base, DWORD len ) +{ + MEMORY_BASIC_INFORMATION minfo; + PBYTE ptr; + PVOID mem; + + for (ptr = base; + VirtualQueryEx( hProcess, ptr, &minfo, sizeof(minfo) ); + ptr += minfo.RegionSize) + { + if ((minfo.State & MEM_FREE) && minfo.RegionSize >= len) + { +#ifdef _WIN64 + if ((PBYTE)minfo.BaseAddress - base > 0xFfFfFfFf - len) + return NULL; +#endif + mem = VirtualAllocEx( hProcess, (PVOID) + (((DWORD_PTR)minfo.BaseAddress + 0xFFFF) & ~0xFFFF), + len, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE ); + if (mem != NULL) + return mem; + } + } + return NULL; +} + + +void InjectDLL( LPPROCESS_INFORMATION ppi, PBYTE pBase ) +{ + DWORD rva; + PVOID pMem; + DWORD len; + DWORD pr; + IMAGE_DOS_HEADER DosHeader; + IMAGE_NT_HEADERS NTHeader, *pNTHeader; + PIMAGE_IMPORT_DESCRIPTOR pImports, pANSI_ImportDesc; + IMAGE_COR20_HEADER ComHeader, *pComHeader; + union + { + PBYTE pB; + PLONG_PTR pL; + } ip; + + ReadProcVar( pBase, &DosHeader ); + pNTHeader = (PIMAGE_NT_HEADERS)(pBase + DosHeader.e_lfanew); + ReadProcVar( pNTHeader, &NTHeader ); + + len = 4 * PTRSZ + ansi_len + + sizeof(*pANSI_ImportDesc) + NTHeader.IMPORTDIR.Size; + pImports = malloc( len ); + if (pImports == NULL) + { + DEBUGSTR( 1, L" Failed to allocate memory." ); + return; + } + pMem = FindMem( ppi->hProcess, pBase, len ); + if (pMem == NULL) + { + DEBUGSTR( 1, L" Failed to allocate virtual memory." ); + free( pImports ); + return; + } + rva = (DWORD)((PBYTE)pMem - pBase); + + ip.pL = (PLONG_PTR)pImports; + *ip.pL++ = IMAGE_ORDINAL_FLAG + 1; + *ip.pL++ = 0; + *ip.pL++ = IMAGE_ORDINAL_FLAG + 1; + *ip.pL++ = 0; + memcpy( ip.pB, ansi_dll, ansi_len ); + ip.pB += ansi_len; + pANSI_ImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)ip.pB; + pANSI_ImportDesc->OriginalFirstThunk = rva + 2 * PTRSZ; + pANSI_ImportDesc->TimeDateStamp = 0; + pANSI_ImportDesc->ForwarderChain = 0; + pANSI_ImportDesc->Name = rva + 4 * PTRSZ; + pANSI_ImportDesc->FirstThunk = rva; + ReadProcMem( pBase + NTHeader.IMPORTDIR.VirtualAddress, + pANSI_ImportDesc + 1, NTHeader.IMPORTDIR.Size ); + WriteProcMem( pMem, pImports, len ); + free( pImports ); + + NTHeader.IMPORTDIR.VirtualAddress = rva + 4 * PTRSZ + ansi_len; + NTHeader.IMPORTDIR.Size += sizeof(*pANSI_ImportDesc); + + // If there's no IAT, copy the IDT. + if (NTHeader.IATDIR.VirtualAddress == 0) + NTHeader.IATDIR = NTHeader.IMPORTDIR; + + // Remove bound imports, so the updated import table is used. + NTHeader.BOUNDDIR.VirtualAddress = 0; + NTHeader.BOUNDDIR.Size = 0; + + VirtProtVar( pNTHeader, PAGE_READWRITE ); + WriteProcVar( pNTHeader, &NTHeader ); + VirtProtVar( pNTHeader, pr ); + + // Remove the IL-only flag on a managed process. + if (NTHeader.COMDIR.VirtualAddress != 0 && NTHeader.COMDIR.Size != 0) + { + pComHeader = (PIMAGE_COR20_HEADER)(pBase + NTHeader.COMDIR.VirtualAddress); + ReadProcVar( pComHeader, &ComHeader ); + if (ComHeader.Flags & COMIMAGE_FLAGS_ILONLY) + { + ComHeader.Flags &= ~COMIMAGE_FLAGS_ILONLY; + VirtProtVar( pComHeader, PAGE_READWRITE ); + WriteProcVar( pComHeader, &ComHeader ); + VirtProtVar( pComHeader, pr ); + } + } +} + + +#ifdef _WIN64 +void InjectDLL32( LPPROCESS_INFORMATION ppi, PBYTE pBase ) +{ + DWORD rva; + PVOID pMem; + DWORD len; + DWORD pr; + IMAGE_DOS_HEADER DosHeader; + IMAGE_NT_HEADERS32 NTHeader, *pNTHeader; + PIMAGE_IMPORT_DESCRIPTOR pImports, pANSI_ImportDesc; + IMAGE_COR20_HEADER ComHeader, *pComHeader; + union + { + PBYTE pB; + PLONG pL; + } ip; + + ReadProcVar( pBase, &DosHeader ); + pNTHeader = (PIMAGE_NT_HEADERS32)(pBase + DosHeader.e_lfanew); + ReadProcVar( pNTHeader, &NTHeader ); + + len = 16 + ansi_len + sizeof(*pANSI_ImportDesc) + NTHeader.IMPORTDIR.Size; + pImports = malloc( len ); + if (pImports == NULL) + { + DEBUGSTR( 1, L" Failed to allocate memory." ); + return; + } + pMem = FindMem( ppi->hProcess, pBase, len ); + if (pMem == NULL) + { + DEBUGSTR( 1, L" Failed to allocate virtual memory." ); + free( pImports ); + return; + } + rva = (DWORD)((PBYTE)pMem - pBase); + + ip.pL = (PLONG)pImports; + *ip.pL++ = IMAGE_ORDINAL_FLAG32 + 1; + *ip.pL++ = 0; + *ip.pL++ = IMAGE_ORDINAL_FLAG32 + 1; + *ip.pL++ = 0; + memcpy( ip.pB, ansi_dll, ansi_len ); + ip.pB += ansi_len; + pANSI_ImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)ip.pB; + pANSI_ImportDesc->OriginalFirstThunk = rva + 8; + pANSI_ImportDesc->TimeDateStamp = 0; + pANSI_ImportDesc->ForwarderChain = 0; + pANSI_ImportDesc->Name = rva + 16; + pANSI_ImportDesc->FirstThunk = rva; + ReadProcMem( pBase + NTHeader.IMPORTDIR.VirtualAddress, + pANSI_ImportDesc + 1, NTHeader.IMPORTDIR.Size ); + WriteProcMem( pMem, pImports, len ); + free( pImports ); + + NTHeader.IMPORTDIR.VirtualAddress = rva + 16 + ansi_len; + NTHeader.IMPORTDIR.Size += sizeof(*pANSI_ImportDesc); + if (NTHeader.IATDIR.VirtualAddress == 0) + NTHeader.IATDIR = NTHeader.IMPORTDIR; + NTHeader.BOUNDDIR.VirtualAddress = 0; + NTHeader.BOUNDDIR.Size = 0; + VirtProtVar( pNTHeader, PAGE_READWRITE ); + WriteProcVar( pNTHeader, &NTHeader ); + VirtProtVar( pNTHeader, pr ); + + if (NTHeader.COMDIR.VirtualAddress != 0 && NTHeader.COMDIR.Size != 0) + { + pComHeader = (PIMAGE_COR20_HEADER)(pBase + NTHeader.COMDIR.VirtualAddress); + ReadProcVar( pComHeader, &ComHeader ); + if (ComHeader.Flags & COMIMAGE_FLAGS_ILONLY) + { + ComHeader.Flags &= ~COMIMAGE_FLAGS_ILONLY; + VirtProtVar( pComHeader, PAGE_READWRITE ); + WriteProcVar( pComHeader, &ComHeader ); + VirtProtVar( pComHeader, pr ); + } + } +} +#endif diff --git a/injdll32.c b/injdll32.c deleted file mode 100644 index 038702c..0000000 --- a/injdll32.c +++ /dev/null @@ -1,251 +0,0 @@ -/* - Inject code into the target process to load our DLL. The target thread - should be suspended on entry; it remains suspended on exit. - - Initially I used the "stack" method of injection. However, this fails - when DEP is active, since that doesn't allow code to execute in the stack. - To overcome this I used the "CreateRemoteThread" method. However, this - would fail with Wselect, a program to assist batch files. Wselect runs, - but it has no output. As it turns out, removing the suspended flag would - make Wselect work, but it caused problems with everything else. So now I - allocate a section of memory and change the context to run from there. At - first I had an event to signal when the library was loaded, then the memory - was released. However, that wouldn't work with -p and CMD.EXE (4NT v8 - worked fine). Since it's possible the DLL might start a process suspended, - I've decided to simply keep the memory. -*/ - -#include "ansicon.h" - -#ifdef _WIN64 -#ifndef WOW64_CONTEXT_ALL -#include "wow64.h" - -TWow64GetThreadContext Wow64GetThreadContext; -TWow64SetThreadContext Wow64SetThreadContext; -#define IMPORT_WOW64 -#endif - -#define CONTEXT WOW64_CONTEXT -#undef CONTEXT_CONTROL -#define CONTEXT_CONTROL WOW64_CONTEXT_CONTROL -#define GetThreadContext Wow64GetThreadContext -#define SetThreadContext Wow64SetThreadContext -#endif - -extern DWORD LLW32r; -LPVOID kernel32_base; -PIMAGE_DOS_HEADER pDosHeader; - -#define MakeVA( cast, offset ) (cast)((DWORD_PTR)pDosHeader + (DWORD)(offset)) - -int export_cmp( const void* a, const void* b ) -{ - return strcmp( (LPCSTR)a, MakeVA( LPCSTR, *(const PDWORD)b ) ); -} - - -/* - Get the relative address of LoadLibraryW direct from kernel32.dll. -*/ -BOOL get_LLW32r( void ) -{ - HMODULE kernel32; - TCHAR buf[MAX_PATH]; - UINT len; - PIMAGE_NT_HEADERS32 pNTHeader; - PIMAGE_EXPORT_DIRECTORY pExportDir; - PDWORD fun_table, name_table; - PWORD ord_table; - PDWORD pLLW; - -#ifdef _WIN64 - len = GetSystemWow64Directory( buf, MAX_PATH ); -#else - len = GetSystemDirectory( buf, MAX_PATH ); -#endif - wcscpy( buf + len, L"\\kernel32.dll" ); - kernel32 = LoadLibraryEx( buf, NULL, LOAD_LIBRARY_AS_IMAGE_RESOURCE ); - if (kernel32 == NULL) - { - DEBUGSTR( 1, L"Unable to load 32-bit kernel32.dll!" ); - return FALSE; - } - // The handle uses low bits as flags, so strip 'em off. - pDosHeader = (PIMAGE_DOS_HEADER)((DWORD_PTR)kernel32 & ~0xFFFF); - pNTHeader = MakeVA( PIMAGE_NT_HEADERS32, pDosHeader->e_lfanew ); - pExportDir = MakeVA( PIMAGE_EXPORT_DIRECTORY, - pNTHeader->OptionalHeader. - DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]. - VirtualAddress ); - - fun_table = MakeVA( PDWORD, pExportDir->AddressOfFunctions ); - name_table = MakeVA( PDWORD, pExportDir->AddressOfNames ); - ord_table = MakeVA( PWORD, pExportDir->AddressOfNameOrdinals ); - - pLLW = bsearch( "LoadLibraryW", name_table, pExportDir->NumberOfNames, - sizeof(DWORD), export_cmp ); - if (pLLW == NULL) - { - DEBUGSTR( 1, L"Could not find LoadLibraryW!" ); - FreeLibrary( kernel32 ); - return FALSE; - } - LLW32r = fun_table[ord_table[pLLW - name_table]]; - - FreeLibrary( kernel32 ); - return TRUE; -} - - -void InjectDLL32( LPPROCESS_INFORMATION ppi, LPCTSTR dll ) -{ - CONTEXT context; - DWORD ep; - BOOL eip; - LPVOID mem; - DWORD mem32; - DWORD pr; - DWORD LLW; - - DWORD len; - #define CODESIZE 20 - BYTE code[CODESIZE+TSIZE(MAX_PATH)]; - union - { - PBYTE pB; - PDWORD pL; - } ip; - - struct unicode_string - { - USHORT Length; - USHORT MaximumLength; - DWORD Buffer; - }; - struct ldr_module // incomplete definition - { - DWORD next, prev; - DWORD baseAddress; - DWORD entryPoint; - DWORD sizeOfImage; - struct unicode_string fullDllName; - struct unicode_string baseDllName; - } ldr; - WCHAR basename[MAX_PATH]; - -#ifdef IMPORT_WOW64 - if (Wow64GetThreadContext == 0) - { - #define GETPROC( proc ) proc = (T##proc)GetProcAddress( hKernel, #proc ) - HMODULE hKernel = GetModuleHandle( L"kernel32.dll" ); - GETPROC( Wow64GetThreadContext ); - GETPROC( Wow64SetThreadContext ); - // Assume if one is defined, so is the other. - if (Wow64GetThreadContext == 0) - { - DEBUGSTR( 1, L"Failed to get pointer to Wow64GetThreadContext." ); - return; - } - } -#endif - - len = TSIZE(lstrlen( dll ) + 1); - if (len > TSIZE(MAX_PATH)) - return; - CopyMemory( code + CODESIZE, dll, len ); - len += CODESIZE; - - context.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; - GetThreadContext( ppi->hThread, &context ); - mem = VirtualAllocEx( ppi->hProcess, NULL, len, MEM_COMMIT, - PAGE_READWRITE ); - mem32 = (DWORD)(DWORD_PTR)mem; - - ip.pB = code; - - // Determine the base address of kernel32.dll. If injecting into the parent - // process, the base has already been determined. Otherwise, use the PEB to - // walk the loaded modules. - if (kernel32_base != 0) - { - ep = context.Eip; - eip = TRUE; - } - else - { - // When a process is created suspended, EAX has the entry point and EBX - // points to the PEB. - if (!ReadProcessMemory( ppi->hProcess, UIntToPtr( context.Ebx + 0x0C ), - ip.pL, 4, NULL )) - { - DEBUGSTR( 1, L"Failed to read Ldr from PEB." ); - return; - } - ep = context.Eax; - eip = FALSE; - // In case we're a bit slow (which seems to be unlikely), set up an - // infinite loop as the entry point. - WriteProcessMemory( ppi->hProcess, mem, "\xEB\xFE", 2, NULL ); - FlushInstructionCache( ppi->hProcess, mem, 2 ); - context.Eax = mem32; - SetThreadContext( ppi->hThread, &context ); - VirtualProtectEx( ppi->hProcess, mem, len, PAGE_EXECUTE, &pr ); - // Now resume the thread, as the PEB hasn't even been created yet. - ResumeThread( ppi->hThread ); - while (*ip.pL == 0) - { - Sleep( 0 ); - ReadProcessMemory( ppi->hProcess, UIntToPtr( context.Ebx + 0x0C ), - ip.pL, 4, NULL ); - } - // Read PEB_LDR_DATA.InInitializationOrderModuleList.Flink. - ReadProcessMemory( ppi->hProcess, UIntToPtr( *ip.pL + 0x1c ), - &ip.pL[1], 4, NULL ); - // Sometimes we're so quick ntdll.dll is the only one present, so keep - // looping until kernel32.dll shows up. - for (;;) - { - ldr.next = ip.pL[1]; - do - { - ReadProcessMemory( ppi->hProcess, UIntToPtr( ldr.next ), - &ldr, sizeof(ldr), NULL ); - ReadProcessMemory( ppi->hProcess, UIntToPtr( ldr.baseDllName.Buffer ), - basename, ldr.baseDllName.MaximumLength, NULL ); - if (_wcsicmp( basename, L"kernel32.dll" ) == 0) - { - kernel32_base = UIntToPtr( ldr.baseAddress ); - goto gotit; - } - } while (ldr.next != *ip.pL + 0x1c); - } - gotit: - SuspendThread( ppi->hThread ); - VirtualProtectEx( ppi->hProcess, mem, len, pr, &pr ); - } - LLW = PtrToUint( kernel32_base ) + LLW32r; - kernel32_base = 0; - - *ip.pB++ = 0x68; // push ep - *ip.pL++ = ep; - *ip.pB++ = 0x9c; // pushf - *ip.pB++ = 0x60; // pusha - *ip.pB++ = 0x68; // push L"path\to\ANSI32.dll" - *ip.pL++ = mem32 + CODESIZE; - *ip.pB++ = 0xe8; // call LoadLibraryW - *ip.pL++ = LLW - (mem32 + (DWORD)(ip.pB+4 - code)); - *ip.pB++ = 0x61; // popa - *ip.pB++ = 0x9d; // popf - *ip.pB++ = 0xc3; // ret - - WriteProcessMemory( ppi->hProcess, mem, code, len, NULL ); - FlushInstructionCache( ppi->hProcess, mem, len ); - VirtualProtectEx( ppi->hProcess, mem, len, PAGE_EXECUTE, &pr ); - - if (eip) - { - context.Eip = mem32; - SetThreadContext( ppi->hThread, &context ); - } -} diff --git a/injdll64.c b/injdll64.c deleted file mode 100644 index 933da5d..0000000 --- a/injdll64.c +++ /dev/null @@ -1,243 +0,0 @@ -/* - Inject code into the target process to load our DLL. The target thread - should be suspended on entry; it remains suspended on exit. - - Initially I used the "stack" method of injection. However, this fails - when DEP is active, since that doesn't allow code to execute in the stack. - To overcome this I used the "CreateRemoteThread" method. However, this - would fail with Wselect, a program to assist batch files. Wselect runs, - but it has no output. As it turns out, removing the suspended flag would - make Wselect work, but it caused problems with everything else. So now I - allocate a section of memory and change the context to run from there. At - first I had an event to signal when the library was loaded, then the memory - was released. However, that wouldn't work with -p and CMD.EXE (4NT v8 - worked fine). Since it's possible the DLL might start a process suspended, - I've decided to simply keep the memory. -*/ - -#include "ansicon.h" - -extern DWORD LLW64r; -extern LPVOID kernel32_base; -extern PIMAGE_DOS_HEADER pDosHeader; - -#define MakeVA( cast, offset ) (cast)((DWORD_PTR)pDosHeader + (DWORD)(offset)) - -extern int export_cmp( const void* a, const void* b ); - - -/* - Get the relative address of LoadLibraryW direct from kernel32.dll. -*/ -BOOL get_LLW64r( void ) -{ - HMODULE kernel32; - TCHAR buf[MAX_PATH]; - UINT len; - PIMAGE_NT_HEADERS pNTHeader; - PIMAGE_EXPORT_DIRECTORY pExportDir; - PDWORD fun_table, name_table; - PWORD ord_table; - PDWORD pLLW; - - len = GetSystemDirectory( buf, MAX_PATH ); - wcscpy( buf + len, L"\\kernel32.dll" ); - kernel32 = LoadLibraryEx( buf, NULL, LOAD_LIBRARY_AS_IMAGE_RESOURCE ); - if (kernel32 == NULL) - { - DEBUGSTR( 1, L"Unable to load 64-bit kernel32.dll!" ); - return FALSE; - } - // The handle uses low bits as flags, so strip 'em off. - pDosHeader = (PIMAGE_DOS_HEADER)((DWORD_PTR)kernel32 & ~0xFFFF); - pNTHeader = MakeVA( PIMAGE_NT_HEADERS, pDosHeader->e_lfanew ); - pExportDir = MakeVA( PIMAGE_EXPORT_DIRECTORY, - pNTHeader->OptionalHeader. - DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]. - VirtualAddress ); - - fun_table = MakeVA( PDWORD, pExportDir->AddressOfFunctions ); - name_table = MakeVA( PDWORD, pExportDir->AddressOfNames ); - ord_table = MakeVA( PWORD, pExportDir->AddressOfNameOrdinals ); - - pLLW = bsearch( "LoadLibraryW", name_table, pExportDir->NumberOfNames, - sizeof(DWORD), export_cmp ); - if (pLLW == NULL) - { - DEBUGSTR( 1, L"Could not find LoadLibraryW!" ); - FreeLibrary( kernel32 ); - return FALSE; - } - LLW64r = fun_table[ord_table[pLLW - name_table]]; - - FreeLibrary( kernel32 ); - return TRUE; -} - - -void InjectDLL64( LPPROCESS_INFORMATION ppi, LPCTSTR dll ) -{ - CONTEXT context; - DWORD64 ep; - BOOL rip; - LPVOID mem; - DWORD pr; - DWORD64 LLW; - - union - { - PBYTE pB; - PDWORD64 pL; - } ip; - - struct unicode_string - { - USHORT Length; - USHORT MaximumLength; - DWORD64 Buffer; - }; - struct ldr_module // incomplete definition - { - DWORD64 next, prev; - DWORD64 baseAddress; - DWORD64 entryPoint; - DWORD64 sizeOfImage; - struct unicode_string fullDllName; - struct unicode_string baseDllName; - } ldr; - WCHAR basename[MAX_PATH]; - - DWORD len; - #define CODESIZE 92 - static BYTE code[CODESIZE+TSIZE(MAX_PATH)] = { - 0,0,0,0,0,0,0,0, // original rip - 0,0,0,0,0,0,0,0, // LoadLibraryW - 0x9C, // pushfq - 0x50, // push rax - 0x51, // push rcx - 0x52, // push rdx - 0x53, // push rbx - 0x55, // push rbp - 0x56, // push rsi - 0x57, // push rdi - 0x41,0x50, // push r8 - 0x41,0x51, // push r9 - 0x41,0x52, // push r10 - 0x41,0x53, // push r11 - 0x41,0x54, // push r12 - 0x41,0x55, // push r13 - 0x41,0x56, // push r14 - 0x41,0x57, // push r15 - 0x48,0x83,0xEC,0x28, // sub rsp, 40 - 0x48,0x8D,0x0D,41,0,0,0, // lea ecx, L"path\to\ANSI64.dll" - 0xFF,0x15,-49,-1,-1,-1, // call LoadLibraryW - 0x48,0x83,0xC4,0x28, // add rsp, 40 - 0x41,0x5F, // pop r15 - 0x41,0x5E, // pop r14 - 0x41,0x5D, // pop r13 - 0x41,0x5C, // pop r12 - 0x41,0x5B, // pop r11 - 0x41,0x5A, // pop r10 - 0x41,0x59, // pop r9 - 0x41,0x58, // pop r8 - 0x5F, // pop rdi - 0x5E, // pop rsi - 0x5D, // pop rbp - 0x5B, // pop rbx - 0x5A, // pop rdx - 0x59, // pop rcx - 0x58, // pop rax - 0x9D, // popfq - 0xFF,0x25,-91,-1,-1,-1, // jmp original Rip - 0, // dword alignment for LLW, fwiw - }; - - len = TSIZE(lstrlen( dll ) + 1); - if (len > TSIZE(MAX_PATH)) - return; - CopyMemory( code + CODESIZE, dll, len ); - len += CODESIZE; - - context.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; - GetThreadContext( ppi->hThread, &context ); - mem = VirtualAllocEx( ppi->hProcess, NULL, len, MEM_COMMIT, - PAGE_READWRITE ); - - ip.pB = code; - - // Determine the base address of kernel32.dll. If injecting into the parent - // process, the base has already been determined. Otherwise, use the PEB to - // walk the loaded modules. - if (kernel32_base != 0) - { - ep = context.Rip; - rip = TRUE; - } - else - { - // When a process is created suspended, RCX has the entry point and RDX - // points to the PEB. - if (!ReadProcessMemory( ppi->hProcess, (LPVOID)(context.Rdx + 0x18), - ip.pL, 8, NULL )) - { - DEBUGSTR( 1, L"Failed to read Ldr from PEB." ); - return; - } - ep = context.Rcx; - rip = FALSE; - // In case we're a bit slow (which seems to be unlikely), set up an - // infinite loop as the entry point. - WriteProcessMemory( ppi->hProcess, (PBYTE)mem + 16, "\xEB\xFE", 2, NULL ); - FlushInstructionCache( ppi->hProcess, (PBYTE)mem + 16, 2 ); - context.Rcx = (DWORD64)mem + 16; - SetThreadContext( ppi->hThread, &context ); - VirtualProtectEx( ppi->hProcess, mem, len, PAGE_EXECUTE, &pr ); - // Now resume the thread, as the PEB hasn't even been created yet. - ResumeThread( ppi->hThread ); - while (*ip.pL == 0) - { - Sleep( 0 ); - ReadProcessMemory( ppi->hProcess, (LPVOID)(context.Rdx + 0x18), - ip.pL, 8, NULL ); - } - // Read PEB_LDR_DATA.InInitializationOrderModuleList.Flink. - ReadProcessMemory( ppi->hProcess, (LPVOID)(*ip.pL + 0x30), - &ip.pL[1], 8, NULL ); - // Sometimes we're so quick ntdll.dll is the only one present, so keep - // looping until kernel32.dll shows up. - for (;;) - { - ldr.next = ip.pL[1]; - do - { - ReadProcessMemory( ppi->hProcess, (LPVOID)ldr.next, - &ldr, sizeof(ldr), NULL ); - ReadProcessMemory( ppi->hProcess, (LPVOID)ldr.baseDllName.Buffer, - basename, ldr.baseDllName.MaximumLength, NULL ); - if (_wcsicmp( basename, L"kernel32.dll" ) == 0) - { - kernel32_base = (LPVOID)ldr.baseAddress; - goto gotit; - } - } while (ldr.next != *ip.pL + 0x30); - } - gotit: - SuspendThread( ppi->hThread ); - VirtualProtectEx( ppi->hProcess, mem, len, pr, &pr ); - } - LLW = (DWORD64)kernel32_base + LLW64r; - kernel32_base = 0; - - *ip.pL++ = ep; - *ip.pL++ = LLW; - - WriteProcessMemory( ppi->hProcess, mem, code, len, NULL ); - FlushInstructionCache( ppi->hProcess, mem, len ); - VirtualProtectEx( ppi->hProcess, mem, len, PAGE_EXECUTE, &pr ); - - if (rip) - { - context.Rip = (DWORD64)mem + 16; - SetThreadContext( ppi->hThread, &context ); - } -} diff --git a/makefile.gcc b/makefile.gcc index d9f9e6d..6a5b781 100644 --- a/makefile.gcc +++ b/makefile.gcc @@ -16,13 +16,16 @@ # # Tested with: # * MinGW/gcc 4.7.2; -# * tdm-gcc-4.7.1-2; -# * tdm64-gcc-4.7.1-3; +# * tdm-gcc-4.8.1-3; +# * tdm64-gcc-4.8.1-3; # * MinGW-builds x64-4.8.1-release-posix-seh-rev1. CC = gcc CFLAGS = -O2 -Wall +# Identify ansicon.exe using "ANSI" as a version number. +IVER = -Wl,--major-image-version,20033,--minor-image-version,18771 + #ARCH = 32 #ARCH = 64 #ARCH = multi @@ -41,8 +44,8 @@ endif endif endif -X86OBJS = x86/proctype.o x86/injdll32.o x86/util.o -X64OBJS = x64/proctype.o x64/injdll64.o x64/injdll32.o x64/util.o +X86OBJS = x86/proctype.o x86/injdll.o x86/util.o +X64OBJS = x64/proctype.o x64/injdll.o x64/util.o # Determine the appropriate separator to run multiple commands - ";" for sh.exe # and "&" for CMD.EXE. $(SHELL) is initially defined to "sh.exe" - if it @@ -67,7 +70,7 @@ x86/%v.o: %.rc version.h $(RCmsg)windres -U _WIN64 -F pe-i386 $< $@ x64/%.o: %.c ansicon.h - $(CCmsg)$(CC) -m64 -c $(CFLAGS) $< -o $@ + $(CCmsg)$(CC) -m64 -g -c $(CFLAGS) $< -o $@ x64/%v.o: %.rc version.h $(RCmsg)windres -F pe-x86-64 $< $@ @@ -89,8 +92,8 @@ ansicon64: x64 x64/ansicon.exe x64/ANSI64.dll x86: cmd /c "mkdir x86" -x86/ansicon.exe: x86/ansicon.o $(X86OBJS) x86/ansiconv.o - $(LDmsg)$(CC) -m32 $+ -s -o $@ +x86/ansicon.exe: x86/ansicon.o $(X86OBJS) x86/LLW.o x86/ansiconv.o + $(LDmsg)$(CC) -m32 $+ -s -o $@ $(IVER) x86/ANSI32.dll: x86/ANSI.o $(X86OBJS) x86/ansiv.o $(LDmsg)$(CC) -m32 $+ -s -o $@ -mdll -Wl,-shared,--image-base,0xAC0000 @@ -98,14 +101,15 @@ x86/ANSI32.dll: x86/ANSI.o $(X86OBJS) x86/ansiv.o x64: cmd /c "mkdir x64" -x64/ansicon.exe: x64/ansicon.o $(X64OBJS) x64/ansiconv.o - $(LDmsg)$(CC) -m64 $+ -s -o $@ +x64/ansicon.exe: x64/ansicon.o $(X64OBJS) x64/LLW.o x64/ansiconv.o + $(LDmsg)$(CC) -m64 $+ -s -o $@ $(IVER) x64/ANSI64.dll: x64/ANSI.o $(X64OBJS) x64/ansiv.o $(LDmsg)$(CC) -m64 $+ -s -o $@ -mdll -Wl,-shared,--image-base,0xAC000000 -x64/ANSI32.dll: x64/ANSI32.o x64/proctype32.o x86/injdll32.o x86/util.o x86/ansiv.o - $(LDmsg)$(CC) -m32 $+ -s -o $@ -mdll -Wl,-shared,--image-base,0xAC0000 +x64/ANSI32.dll: x64/ANSI32.o x64/proctype32.o x86/injdll.o x86/util.o x86/ansiv.o + $(LDmsg)$(CC) -m32 $+ -s -o $@ -mdll \ + -Wl,-shared,--image-base,0xAC0000,--large-address-aware x86/ansicon.o: version.h x86/ANSI.o: version.h diff --git a/makefile.vc b/makefile.vc index 96937b2..bcca7c2 100644 --- a/makefile.vc +++ b/makefile.vc @@ -62,8 +62,11 @@ MT = mt.exe CFLAGS = /nologo /W3 /O2 $(SHARE) /D_CRT_SECURE_NO_WARNINGS LIBS = advapi32.lib user32.lib $(LIBS64) -X86OBJS = x86\proctype.obj x86\injdll32.obj x86\util.obj -X64OBJS = x64\proctype.obj x64\injdll64.obj x64\injdll32.obj x64\util.obj +# Identify ansicon.exe using "ANSI" as a version number. +LINK = /link /version:20033.18771 + +X86OBJS = x86\proctype.obj x86\injdll.obj x86\util.obj +X64OBJS = x64\proctype.obj x64\injdll.obj x64\util.obj !IF !DEFINED(V) V = 0 @@ -90,15 +93,16 @@ ansicon64: x64 x64\ansicon.exe x64\ANSI64.dll x86: mkdir x86 -x86\ansicon.exe: x86\ansicon.obj $(X86OBJS) x86\ansicon.res - $(LDmsg)$(CC) /nologo $(SHARE) /Fe$@ $** $(LIBS) /link /filealign:512 +x86\ansicon.exe: x86\ansicon.obj $(X86OBJS) x86\LLW.obj x86\ansicon.res + $(LDmsg)$(CC) /nologo $(SHARE) /Fe$@ $** $(LIBS) $(LINK) /filealign:512 !IF "$(_NMAKE_VER)" == "9.00.30729.01" $(MTmsg)$(MT) /nologo -manifest $@.manifest -outputresource:$@;1 @del $@.manifest !ENDIF x86\ANSI32.dll: x86\ANSI.obj $(X86OBJS) x86\ansi.res - $(LDmsg)$(CC) /nologo $(SHARE) /LD /Fe$@ $** $(LIBS) /link /base:0xAC0000 /section:.shared,s /filealign:512 + $(LDmsg)$(CC) /nologo $(SHARE) /LD /Fe$@ $** $(LIBS) /link \ + /base:0xAC0000 /section:.shared,s /filealign:512 !IF "$(_NMAKE_VER)" == "9.00.30729.01" $(MTmsg)$(MT) /nologo -manifest $@.manifest -outputresource:$@;2 @del $@.manifest @@ -107,14 +111,16 @@ x86\ANSI32.dll: x86\ANSI.obj $(X86OBJS) x86\ansi.res x64: mkdir x64 -x64\ansicon.exe: x64\ansicon.obj $(X64OBJS) x64\ansicon.res - $(LDmsg)$(CC) /nologo $(SHARE) /Fe$@ $** $(LIBS) +x64\ansicon.exe: x64\ansicon.obj $(X64OBJS) x64\LLW.obj x64\ansicon.res + $(LDmsg)$(CC) /nologo $(SHARE) /Fe$@ $** $(LIBS) $(LINK) x64\ANSI64.dll: x64\ANSI.obj $(X64OBJS) x64\ansi.res - $(LDmsg)$(CC) /nologo $(SHARE) /LD /Fe$@ $** $(LIBS) /link /base:0xAC000000 /section:.shared,s + $(LDmsg)$(CC) /nologo $(SHARE) /LD /Fe$@ $** $(LIBS) /link \ + /base:0xAC000000 /section:.shared,s -x64\ANSI32.dll: x64\ANSI32.obj x64\proctype32.obj x86\injdll32.obj x86\util.obj x86\ansi.res - $(LDmsg)$(CC) /nologo $(SHARE) /LD /Fe$@ $** $(LIBS) /link /base:0xAC0000 /section:.shared,s /filealign:512 +x64\ANSI32.dll: x64\ANSI32.obj x64\proctype32.obj x86\injdll.obj x86\util.obj x86\ansi.res + $(LDmsg)$(CC) /nologo $(SHARE) /LD /Fe$@ $** $(LIBS) /link \ + /base:0xAC0000 /section:.shared,s /filealign:512 /largeaddressaware !IF "$(_NMAKE_VER)" == "9.00.30729.01" $(MTmsg)$(MT) /nologo -manifest $@.manifest -outputresource:$@;2 @del $@.manifest @@ -125,12 +131,13 @@ ansicon.rc: version.h ANSI.c: ansicon.h version.h ANSI.rc: version.h util.c: ansicon.h version.h -injdll32.c: ansicon.h -injdll64.c: ansicon.h +injdll.c: ansicon.h proctype.c: ansicon.h +LLW.c: ansicon.h x64\ANSI32.obj: ANSI.c $(CCmsg)$(CC) /DW32ON64 /c $(CFLAGS) /Fo$@ $? + x64\proctype32.obj: proctype.c $(CCmsg)$(CC) /DW32ON64 /c $(CFLAGS) /Fo$@ $? diff --git a/proctype.c b/proctype.c index c035b8e..646b9a9 100644 --- a/proctype.c +++ b/proctype.c @@ -1,99 +1,96 @@ /* - Test for a valid process. This may sometimes detect GUI, even for a console - process. I think this is due to a DLL being loaded in the address space - before the main image. Ideally I could just use the base address directly, - but that doesn't seem easy to do for another process - there doesn't seem to - be a GetModuleHandle for another process. The CreateRemoteThread trick won't - work with 64-bit (exit code is DWORD) and setting it up to make it work - hardly seems worth it. There's GetModuleInformation, but passing in NULL just - returns a base of NULL, so that's no help. Since 64/32 is sufficient, let - ansicon.exe handle the difference between console/GUI. - - Update: ignore images characterised as DLL. + Test for a valid process (i386 for x86; that or AMD64 for x64). We can get + that info from the image header, which means getting the process's base + address (which we need anyway, to modify the imports). The simplest way to + do that is to enumerate the pages, looking for an executable image. */ #include "ansicon.h" -int ProcessType( LPPROCESS_INFORMATION pinfo, BOOL* gui ) +int ProcessType( LPPROCESS_INFORMATION ppi, PBYTE* pBase, BOOL* gui ) { - char* ptr; + PBYTE ptr; MEMORY_BASIC_INFORMATION minfo; IMAGE_DOS_HEADER dos_header; IMAGE_NT_HEADERS nt_header; - SIZE_T read; + *pBase = NULL; *gui = FALSE; + for (ptr = NULL; - VirtualQueryEx( pinfo->hProcess, ptr, &minfo, sizeof(minfo) ); + VirtualQueryEx( ppi->hProcess, ptr, &minfo, sizeof(minfo) ); ptr += minfo.RegionSize) { - if (minfo.BaseAddress == minfo.AllocationBase && - ReadProcessMemory( pinfo->hProcess, minfo.AllocationBase, - &dos_header, sizeof(dos_header), &read )) + if (minfo.BaseAddress == minfo.AllocationBase + && ReadProcVar( minfo.BaseAddress, &dos_header ) + && dos_header.e_magic == IMAGE_DOS_SIGNATURE + && ReadProcVar( (PBYTE)minfo.BaseAddress + dos_header.e_lfanew, + &nt_header ) + && nt_header.Signature == IMAGE_NT_SIGNATURE + && !(nt_header.FileHeader.Characteristics & IMAGE_FILE_DLL)) { - if (dos_header.e_magic == IMAGE_DOS_SIGNATURE) + // Don't load into ansicon.exe, it wants to do that itself. + if (nt_header.OptionalHeader.MajorImageVersion == 20033 && + nt_header.OptionalHeader.MinorImageVersion == 18771) + return -1; + + *pBase = minfo.BaseAddress; + if (nt_header.OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI) + *gui = TRUE; + if (*gui || + nt_header.OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_CUI) { - if (ReadProcessMemory( pinfo->hProcess, (char*)minfo.AllocationBase + - dos_header.e_lfanew, &nt_header, - sizeof(nt_header), &read )) + if (nt_header.FileHeader.Machine == IMAGE_FILE_MACHINE_I386) { - if (nt_header.Signature == IMAGE_NT_SIGNATURE && - (nt_header.FileHeader.Characteristics & - (IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_DLL)) - == IMAGE_FILE_EXECUTABLE_IMAGE) - { - *gui = (nt_header.OptionalHeader.Subsystem - == IMAGE_SUBSYSTEM_WINDOWS_GUI); - if (nt_header.OptionalHeader.Subsystem == - IMAGE_SUBSYSTEM_WINDOWS_CUI || *gui) - { - if (nt_header.FileHeader.Machine == IMAGE_FILE_MACHINE_I386) - { - // Microsoft ignores precision on %p. - DEBUGSTR( 1, L" 32-bit %s (base = %.8X)", - (*gui) ? L"GUI" : L"console", - (DWORD)(DWORD_PTR)minfo.AllocationBase ); - return 32; - } -#ifdef _WIN64 - if (nt_header.FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64) - { - DEBUGSTR( 1, L" 64-bit %s (base = %p)", - (*gui) ? L"GUI" : L"console", minfo.AllocationBase ); - return 64; - } -#elif defined(W32ON64) - if (nt_header.FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64) - { - DEBUGSTR( 1, L" 64-bit %s", - (*gui) ? L"GUI" : L"console" ); - return 64; - } -#endif - DEBUGSTR( 1, L" Ignoring unsupported machine (0x%X)", - nt_header.FileHeader.Machine ); - } - else - { - DEBUGSTR( 1, L" Ignoring unsupported subsystem (%u)", - nt_header.OptionalHeader.Subsystem ); - } - return 0; - } + DEBUGSTR( 1, L" 32-bit %s (base = %.8X)", + (*gui) ? L"GUI" : L"console", + PtrToUint( minfo.BaseAddress ) ); + return 32; } + if (nt_header.FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64) + { +#ifdef _WIN64 + DEBUGSTR( 1, L" 64-bit %s (base = %.8X_%.8X)", + (*gui) ? L"GUI" : L"console", + (DWORD)((DWORD_PTR)minfo.BaseAddress >> 32), + PtrToUint( minfo.BaseAddress ) ); + return 64; +#elif defined(W32ON64) + // Console will log due to -P, but GUI may be ignored (if not, + // this'll show up twice). + if (*gui) + DEBUGSTR( 1, L" 64-bit GUI (base = 00000000_%.8X)", + PtrToUint( minfo.BaseAddress ) ); + return 64; +#else + DEBUGSTR( 1, L" 64-bit %s (base = 00000000_%.8X)", + (*gui) ? L"GUI" : L"console", + PtrToUint( minfo.BaseAddress ) ); + DEBUGSTR( 1, L" Unsupported (use x64\\ansicon)" ); + return 0; +#endif + } + DEBUGSTR( 1, L" Ignoring unsupported machine (0x%X)", + nt_header.FileHeader.Machine ); } + else + { + DEBUGSTR( 1, L" Ignoring unsupported subsystem (%u)", + nt_header.OptionalHeader.Subsystem ); + } + return 0; } #ifndef _WIN64 // If a 32-bit process loads a 64-bit one, we may miss the base // address. If the pointer overflows, assume 64-bit. - if (((DWORD)ptr >> 12) + ((DWORD)minfo.RegionSize >> 12) > 0x80000) + if (((DWORD)ptr >> 12) + ((DWORD)minfo.RegionSize >> 12) >= 0x100000) { #ifdef W32ON64 - DEBUGSTR( 1, L" Pointer overflow: assuming 64-bit console" ); + DEBUGSTR( 1, L" Pointer overflow: assuming 64-bit" ); return 64; #else - DEBUGSTR( 1, L" Ignoring apparent 64-bit process" ); + DEBUGSTR( 1, L" Ignoring apparent 64-bit process (use x64\\ansicon)" ); return 0; #endif } diff --git a/readme.txt b/readme.txt index c088d82..cc96383 100644 --- a/readme.txt +++ b/readme.txt @@ -1,9 +1,9 @@ ANSICON - Copyright 2005-2013 Jason Hood + Copyright 2005-2014 Jason Hood - Version 1.66. Freeware + Version 1.70. Freeware Description @@ -17,7 +17,7 @@ Requirements ============ 32-bit: Windows 2000 Professional and later (it won't work with NT or 9X). - 64-bit: Vista and later (it won't work with XP64). + 64-bit: AMD64 (IA64 could work with a little modification). Installation @@ -93,10 +93,10 @@ Usage 16 Log all imported modules (add to any of the above) The log option will not work with '-p'; set the environment variable - ANSICON_LOG instead. The variable is only read once when a new process is - started; changing it won't affect running processes. If you identify a - module that causes problems, add it to the ANSICON_EXC environment variable - (see ANSICON_API below, but the extension is required). + ANSICON_LOG (to the number) instead. The variable is only read once when a + process is started; changing it won't affect running processes. If you + identify a module that causes problems, add it to the ANSICON_EXC environ- + ment variable (see ANSICON_API below, but the extension is required). E.g.: 'ansicon -l5' will start a new command processor, logging every pro- cess it starts along with their output. @@ -264,12 +264,26 @@ Limitations ANSICON_EXC=nvd3d9wrap.dll;nvd3d9wrapx.dll + An application using multiple screen buffers will not have separate + attributes in each buffer. + Version History =============== Legend: + added, - bug-fixed, * changed. + 1.70 - 4 February, 2014: + - don't hook again if using LoadLibrary or LoadLibraryEx; + - update the LoadLibraryEx flags that shouldn't hook; + - restore original attributes on detach (for LoadLibrary/FreeLibrary usage); + - ansicon.exe will start with ANSICON_DEF (if defined and -m not used); + - an installed ansicon.exe will restore current (not default) attributes; + * inject into a created process by modifying the import descriptor table + (use CreateRemoteThread for -p); + * log: remove the quotes around the CreateProcess command line; + add an underscore in 64-bit addresses to distinguish 8-digit groups. + 1.66 - 20 September, 2013: - fix 32-bit process trying to detect 64-bit process. @@ -442,12 +456,6 @@ Contact http://ansicon.adoxa.vze.com/ https://github.com/adoxa/ansicon - Jason Hood - 11 Buckle Street - North Rockhampton - Qld 4701 - Australia - Distribution ============ @@ -461,5 +469,5 @@ Distribution in LICENSE.txt. -=============================== -Jason Hood, 20 September, 2013. +============================= +Jason Hood, 4 February, 2014. diff --git a/util.c b/util.c index 8bb4524..a4e2c77 100644 --- a/util.c +++ b/util.c @@ -8,9 +8,14 @@ TCHAR prog_path[MAX_PATH]; LPTSTR prog; + int log_level; -char tempfile[MAX_PATH]; -DWORD pid; + +char ansi_dll[MAX_PATH]; +DWORD ansi_len; +#ifdef _WIN64 +char* ansi_bits; +#endif // Get just the name of the program: "C:\path\program.exe" -> "program". @@ -37,13 +42,51 @@ LPTSTR get_program_name( LPTSTR program ) } +// Get the ANSI path of the DLL for the import. If it can't be converted, +// just use the name and hope it's on the PATH. Returns the length of the +// path/name, including padding to make it dword-aligned. The 64-bit version +// expects ansi_bits to point to the size within dll on entry. +void set_ansi_dll( LPTSTR dll ) +{ + BOOL bad; + + ansi_len = WideCharToMultiByte( CP_ACP, WC_NO_BEST_FIT_CHARS, dll, -1, + NULL, 0, NULL, &bad ); + if (bad || ansi_len > MAX_PATH) + { + ansi_len = 12; + memcpy( ansi_dll, "ANSI32.dll\0", 12 ); +#ifdef _WIN64 + if (*ansi_bits == '6') + { + ansi_dll[4] = '6'; + ansi_dll[5] = '4'; + } + ansi_bits = ansi_dll + 4; +#endif + } + else + { + WideCharToMultiByte( CP_ACP, WC_NO_BEST_FIT_CHARS, dll, -1, + ansi_dll, MAX_PATH, NULL, NULL ); +#ifdef _WIN64 + ansi_bits = ansi_dll + ansi_len - 7; +#endif + ansi_len = (ansi_len + 3) & ~3; + } +} + + void DEBUGSTR( int level, LPTSTR szFormat, ... ) { - TCHAR szBuffer[1024], szEscape[1024]; + static char tempfile[MAX_PATH]; + static DWORD pid; + + TCHAR szBuffer[1024], szEscape[1024]; va_list pArgList; - HANDLE mutex; - DWORD wait; - FILE* file; + HANDLE mutex; + DWORD wait; + FILE* file; if ((log_level & 3) < level && !(level & 4 & log_level)) return; @@ -55,6 +98,7 @@ void DEBUGSTR( int level, LPTSTR szFormat, ... ) } if (szFormat == NULL) { + // Explicitly use 't', as _fmode might be binary. file = fopen( tempfile, (log_level & 8) ? "at" : "wt" ); if (file != NULL) { @@ -116,7 +160,7 @@ void DEBUGSTR( int level, LPTSTR szFormat, ... ) mutex = CreateMutex( NULL, FALSE, L"ANSICON_debug_file" ); wait = WaitForSingleObject( mutex, 500 ); - file = fopen( tempfile, "at" ); // _fmode might be binary + file = fopen( tempfile, "at" ); if (file != NULL) { fwprintf( file, L"%s (%lu): %s\n", prog, pid, szFormat ); diff --git a/version.h b/version.h index d21198b..3ee1e69 100644 --- a/version.h +++ b/version.h @@ -2,11 +2,11 @@ version.h - Version defines. */ -#define PVERS L"1.67" // wide string -#define PVERSA "1.67" // ANSI string (windres 2.16.91 didn't like L) -#define PVERE L"167" // wide environment string -#define PVEREA "167" // ANSI environment string -#define PVERB 1,6,7,0 // binary (resource) +#define PVERS L"1.70" // wide string +#define PVERSA "1.70" // ANSI string (windres 2.16.91 didn't like L) +#define PVERE L"170" // wide environment string +#define PVEREA "170" // ANSI environment string +#define PVERB 1,7,0,0 // binary (resource) #ifdef _WIN64 # define BITS L"64" diff --git a/wow64.h b/wow64.h deleted file mode 100644 index ab71a64..0000000 --- a/wow64.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - wow64.h - Definitions for Wow64. - - The 2003 Platform SDK does not include these Wow64 definitions. -*/ - -#ifndef WOW64_H -#define WOW64_H - -#define WIN32_LEAN_AND_MEAN -#include - -#define WOW64_CONTEXT_i386 0x00010000 - -#define WOW64_CONTEXT_CONTROL (WOW64_CONTEXT_i386 | 0x00000001L) -#define WOW64_CONTEXT_INTEGER (WOW64_CONTEXT_i386 | 0x00000002L) -#define WOW64_CONTEXT_SEGMENTS (WOW64_CONTEXT_i386 | 0x00000004L) -#define WOW64_CONTEXT_FLOATING_POINT (WOW64_CONTEXT_i386 | 0x00000008L) -#define WOW64_CONTEXT_DEBUG_REGISTERS (WOW64_CONTEXT_i386 | 0x00000010L) -#define WOW64_CONTEXT_EXTENDED_REGISTERS (WOW64_CONTEXT_i386 | 0x00000020L) - -#define WOW64_CONTEXT_FULL (WOW64_CONTEXT_CONTROL | WOW64_CONTEXT_INTEGER | WOW64_CONTEXT_SEGMENTS) - -#define WOW64_CONTEXT_ALL (WOW64_CONTEXT_CONTROL | WOW64_CONTEXT_INTEGER | WOW64_CONTEXT_SEGMENTS | \ - WOW64_CONTEXT_FLOATING_POINT | WOW64_CONTEXT_DEBUG_REGISTERS | \ - WOW64_CONTEXT_EXTENDED_REGISTERS) - -#define WOW64_SIZE_OF_80387_REGISTERS 80 - -#define WOW64_MAXIMUM_SUPPORTED_EXTENSION 512 - -typedef struct _WOW64_FLOATING_SAVE_AREA { - DWORD ControlWord; - DWORD StatusWord; - DWORD TagWord; - DWORD ErrorOffset; - DWORD ErrorSelector; - DWORD DataOffset; - DWORD DataSelector; - BYTE RegisterArea[WOW64_SIZE_OF_80387_REGISTERS]; - DWORD Cr0NpxState; -} WOW64_FLOATING_SAVE_AREA; - -typedef WOW64_FLOATING_SAVE_AREA *PWOW64_FLOATING_SAVE_AREA; - -typedef struct _WOW64_CONTEXT { - - DWORD ContextFlags; - - DWORD Dr0; - DWORD Dr1; - DWORD Dr2; - DWORD Dr3; - DWORD Dr6; - DWORD Dr7; - - WOW64_FLOATING_SAVE_AREA FloatSave; - - DWORD SegGs; - DWORD SegFs; - DWORD SegEs; - DWORD SegDs; - - DWORD Edi; - DWORD Esi; - DWORD Ebx; - DWORD Edx; - DWORD Ecx; - DWORD Eax; - - DWORD Ebp; - DWORD Eip; - DWORD SegCs; - DWORD EFlags; - DWORD Esp; - DWORD SegSs; - - BYTE ExtendedRegisters[WOW64_MAXIMUM_SUPPORTED_EXTENSION]; - -} WOW64_CONTEXT; - -typedef WOW64_CONTEXT *PWOW64_CONTEXT; - - -typedef BOOL (WINAPI *TWow64GetThreadContext)( HANDLE hThread, PWOW64_CONTEXT lpContext ); -typedef BOOL (WINAPI *TWow64SetThreadContext)( HANDLE hThread, CONST WOW64_CONTEXT *lpContext ); - -#endif