diff --git a/ANSI.c b/ANSI.c index 55ec3c0..6e74805 100644 --- a/ANSI.c +++ b/ANSI.c @@ -197,13 +197,14 @@ v1.83, 16 February, 2018: create the flush thread on first use. - v1.84-wip, 17 February, 26 April to 2 May, 2018: + v1.84-wip, 17 February, 26 April to 4 May, 2018: close the flush handles on detach; dynamically load WINMM.DLL; use sprintf/_snprintf/_snwprintf instead of wsprintf, avoiding USER32.DLL; replace bsearch (in procrva.c) with specific code; if the primary thread is detached exit the process; - get real WriteFile handle before testing for console. + get real WriteFile handle before testing for console; + use remote load on Win8+ when the process has no IAT. */ #include "ansicon.h" @@ -2984,7 +2985,7 @@ void Inject( DWORD dwCreationFlags, LPPROCESS_INFORMATION lpi, } else // (type == 48) { - InjectDLL64( child_pi ); + RemoteLoad64( child_pi ); } #else #ifdef W32ON64 @@ -3840,17 +3841,25 @@ void OriginalAttr( PVOID lpReserved ) // If we were loaded dynamically, remember the current attributes to restore // upon unloading. However, if we're the 64-bit DLL, but the image is 32- - // bit, then the dynamic load was due to injecting into AnyCPU. + // bit, then the dynamic load was due to injecting into AnyCPU. It may also + // be dynamic due to lack of the IAT. if (lpReserved == NULL) { -#ifdef _WIN64 + BOOL dynamic = TRUE; PIMAGE_DOS_HEADER pDosHeader; PIMAGE_NT_HEADERS pNTHeader; pDosHeader = (PIMAGE_DOS_HEADER)GetModuleHandle( NULL ); pNTHeader = MakeVA( PIMAGE_NT_HEADERS, pDosHeader->e_lfanew ); - if (pNTHeader->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64) +#ifdef _WIN64 + if (pNTHeader->FileHeader.Machine == IMAGE_FILE_MACHINE_I386) + dynamic = FALSE; + else #endif - orgattr = ATTR; + if (pNTHeader->DATADIRS <= IMAGE_DIRECTORY_ENTRY_IAT && + get_os_version() >= 0x602) + dynamic = FALSE; + if (dynamic) + orgattr = ATTR; GetConsoleMode( hConOut, &orgmode ); GetConsoleCursorInfo( hConOut, &orgcci ); } diff --git a/ansicon.c b/ansicon.c index 4ea679c..f958ebe 100644 --- a/ansicon.c +++ b/ansicon.c @@ -91,7 +91,7 @@ use -pu to unload from the parent. */ -#define PDATE L"30 April, 2018" +#define PDATE L"4 May, 2018" #include "ansicon.h" #include "version.h" @@ -208,7 +208,7 @@ BOOL Inject( LPPROCESS_INFORMATION ppi, BOOL* gui, LPCTSTR app ) else if (type == 32) InjectDLL32( ppi, base ); else // (type == 48) - InjectDLL64( ppi ); + RemoteLoad64( ppi ); #else wcscpy( DllName + len, L"ANSI32.dll" ); set_ansi_dll(); diff --git a/ansicon.h b/ansicon.h index a4ed9f8..b71e894 100644 --- a/ansicon.h +++ b/ansicon.h @@ -87,9 +87,10 @@ int ProcessType( LPPROCESS_INFORMATION, PBYTE*, BOOL* ); BOOL Wow64Process( HANDLE ); void InjectDLL( LPPROCESS_INFORMATION, PBYTE ); +void RemoteLoad32( LPPROCESS_INFORMATION ); #ifdef _WIN64 void InjectDLL32( LPPROCESS_INFORMATION, PBYTE ); -void InjectDLL64( LPPROCESS_INFORMATION ); +void RemoteLoad64( LPPROCESS_INFORMATION ); DWORD GetProcRVA( LPCTSTR, LPCSTR, int ); #else DWORD GetProcRVA( LPCTSTR, LPCSTR ); @@ -107,6 +108,7 @@ extern char ansi_dll[MAX_PATH]; extern DWORD ansi_len; extern char* ansi_bits; void set_ansi_dll( void ); +DWORD get_os_version( void ); extern int log_level; void DEBUGSTR( int level, LPCSTR szFormat, ... ); diff --git a/injdll.c b/injdll.c index e243d3a..464456d 100644 --- a/injdll.c +++ b/injdll.c @@ -84,6 +84,19 @@ void InjectDLL( LPPROCESS_INFORMATION ppi, PBYTE pBase ) pNTHeader = (PIMAGE_NT_HEADERS)(pBase + DosHeader.e_lfanew); ReadProcVar( pNTHeader, &NTHeader ); + // Windows 8 and later require the IDT to be part of a section when there's + // no IAT. This means we can't move the imports, so remote load instead. + if (NTHeader.DATADIRS <= IMAGE_DIRECTORY_ENTRY_IAT && + get_os_version() >= 0x602) + { +#ifdef _WIN64 + RemoteLoad64( ppi ); +#else + RemoteLoad32( ppi ); +#endif + return; + } + import_size = sizeof_imports( ppi, pBase, NTHeader.IMPORTDIR.VirtualAddress ); len = 2 * PTRSZ + ansi_len + sizeof(*pImports) + import_size; pImports = HeapAlloc( hHeap, 0, len ); @@ -173,6 +186,13 @@ void InjectDLL32( LPPROCESS_INFORMATION ppi, PBYTE pBase ) pNTHeader = (PIMAGE_NT_HEADERS32)(pBase + DosHeader.e_lfanew); ReadProcVar( pNTHeader, &NTHeader ); + if (NTHeader.DATADIRS <= IMAGE_DIRECTORY_ENTRY_IAT && + get_os_version() >= 0x602) + { + RemoteLoad32( ppi ); + return; + } + import_size = sizeof_imports( ppi, pBase, NTHeader.IMPORTDIR.VirtualAddress ); len = 8 + ansi_len + sizeof(*pImports) + import_size; pImports = HeapAlloc( hHeap, 0, len ); @@ -232,16 +252,21 @@ void InjectDLL32( LPPROCESS_INFORMATION ppi, PBYTE pBase ) } } } +#endif /* - Locate the base address of 64-bit ntdll.dll. This is supposedly really at - the same address for every process, but let's find it anyway. A newly- - created suspended 64-bit process has two images in memory: the process itself - and ntdll.dll - the one that is a DLL must be ntdll.dll. (A 32-bit WOW64 - process has three images - the process and both 64- & 32-bit ntdll.dll). + Locate the base address of ntdll.dll. This is supposedly really at the same + address for every process, but let's find it anyway. A newly-created + suspended process has two images in memory: the process itself and ntdll.dll. + Thus the one that is a DLL must be ntdll.dll. However, a WOW64 process also + has the 64-bit version, so test the machine. */ +#ifdef _WIN64 +static PBYTE get_ntdll( LPPROCESS_INFORMATION ppi, WORD machine ) +#else static PBYTE get_ntdll( LPPROCESS_INFORMATION ppi ) +#endif { PBYTE ptr; MEMORY_BASIC_INFORMATION minfo; @@ -258,7 +283,11 @@ static PBYTE get_ntdll( LPPROCESS_INFORMATION ppi ) && ReadProcVar( (PBYTE)minfo.BaseAddress + dos_header.e_lfanew, &nt_header ) && nt_header.Signature == IMAGE_NT_SIGNATURE - && (nt_header.FileHeader.Characteristics & IMAGE_FILE_DLL)) + && (nt_header.FileHeader.Characteristics & IMAGE_FILE_DLL) +#ifdef _WIN64 + && nt_header.FileHeader.Machine == machine +#endif + ) { return minfo.BaseAddress; } @@ -269,7 +298,8 @@ static PBYTE get_ntdll( LPPROCESS_INFORMATION ppi ) } -void InjectDLL64( LPPROCESS_INFORMATION ppi ) +#ifdef _WIN64 +void RemoteLoad64( LPPROCESS_INFORMATION ppi ) { PBYTE ntdll; DWORD rLdrLoadDll; @@ -285,7 +315,7 @@ void InjectDLL64( LPPROCESS_INFORMATION ppi ) PBYTE* pL; } ip; - ntdll = get_ntdll( ppi ); + ntdll = get_ntdll( ppi, IMAGE_FILE_MACHINE_AMD64 ); if (ntdll == NULL) return; @@ -327,3 +357,67 @@ void InjectDLL64( LPPROCESS_INFORMATION ppi ) VirtualFreeEx( ppi->hProcess, pMem, 0, MEM_RELEASE ); } #endif + + +void RemoteLoad32( LPPROCESS_INFORMATION ppi ) +{ + PBYTE ntdll; + DWORD rLdrLoadDll; + PBYTE pMem; + DWORD bMem; + DWORD len; + HANDLE thread; + BYTE code[64]; + union + { + PBYTE pB; + PUSHORT pS; + PDWORD pD; + } ip; + +#ifdef _WIN64 + ntdll = get_ntdll( ppi, IMAGE_FILE_MACHINE_I386 ); +#else + ntdll = get_ntdll( ppi ); +#endif + if (ntdll == NULL) + return; + +#ifdef _WIN64 + rLdrLoadDll = GetProcRVA( L"ntdll.dll", "LdrLoadDll", 32 ); +#else + rLdrLoadDll = GetProcRVA( L"ntdll.dll", "LdrLoadDll" ); +#endif + if (rLdrLoadDll == 0) + return; + + pMem = VirtualAllocEx( ppi->hProcess, NULL, 4096, MEM_COMMIT, + PAGE_EXECUTE_READ ); + if (pMem == NULL) + { + DEBUGSTR(1, " Failed to allocate virtual memory (%u)", GetLastError()); + return; + } + bMem = PtrToUint( pMem ); + + len = (DWORD)TSIZE(wcslen( DllName ) + 1); + ip.pB = code; + + *ip.pS++ = 0x5451; // push ecx esp + *ip.pB++ = 0x68; // push + *ip.pD++ = bMem + 20; // L"path\to\ANSI32.dll" + *ip.pD++ = 0x006A006A; // push 0 0 + *ip.pB++ = 0xe8; // call LdrLoadDll + *ip.pD++ = PtrToUint( ntdll ) + rLdrLoadDll - (bMem + 16); + *ip.pD++ = 0xc359; // pop ecx / ret and padding + *ip.pS++ = (USHORT)(len - TSIZE(1)); // UNICODE_STRING.Length + *ip.pS++ = (USHORT)len; // UNICODE_STRING.MaximumLength + *ip.pD++ = bMem + 28; // UNICODE_STRING.Buffer + WriteProcMem( pMem, code, ip.pB - code ); + WriteProcMem( pMem + (ip.pB - code), DllName, len ); + thread = CreateRemoteThread( ppi->hProcess, NULL, 4096, + (LPTHREAD_START_ROUTINE)pMem, NULL, 0, NULL ); + WaitForSingleObject( thread, INFINITE ); + CloseHandle( thread ); + VirtualFreeEx( ppi->hProcess, pMem, 0, MEM_RELEASE ); +} diff --git a/makefile.vc b/makefile.vc index 1d3c02f..1427cad 100644 --- a/makefile.vc +++ b/makefile.vc @@ -70,9 +70,9 @@ LIBS = advapi32.lib $(LIBS64) # Identify ansicon.exe using "ANSI" as a version number. LINK = /link /version:20033.18771 -X86OBJS = x86\injdll.obj x86\proctype.obj x86\util.obj -X64OBJS = x64\injdll.obj x64\proctype.obj x64\util.obj x64\procrva.obj -X6432OBJS = x86\injdll.obj x64\proctype32.obj x86\util.obj +X86OBJS = x86\injdll.obj x86\procrva.obj x86\proctype.obj x86\util.obj +X64OBJS = x64\injdll.obj x64\procrva.obj x64\proctype.obj x64\util.obj +X6432OBJS = x86\injdll.obj x86\procrva.obj x64\proctype32.obj x86\util.obj !IF !DEFINED(V) V = 0 @@ -99,7 +99,7 @@ ansicon64: x64 x64\ansicon.exe x64\ANSI64.dll x86: mkdir x86 -x86\ansicon.exe: x86\ansicon.obj $(X86OBJS) x86\procrva.obj x86\ansicon.res +x86\ansicon.exe: x86\ansicon.obj $(X86OBJS) 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 diff --git a/readme.txt b/readme.txt index 123bc1e..7f6dc48 100644 --- a/readme.txt +++ b/readme.txt @@ -339,10 +339,10 @@ Version History Legend: + added, - bug-fixed, * changed. - 1.84-wip - 3 May, 2018: + 1.84-wip - 4 May, 2018: - close the flush handles on detach; - - use remote load on Win8+ if the process has no IAT; - WriteFile wasn't properly testing if its handle was for a console; + - use remote load on Win8+ if the process has no IAT; * remove dependency on USER32, dynamically load WINMM; * exit process if the primary thread is detached (for processes on Win10 that return, rather than call ExitProcess). @@ -621,4 +621,4 @@ Distribution ======================== -Jason Hood, 3 May, 2018. +Jason Hood, 4 May, 2018. diff --git a/util.c b/util.c index 069d471..9bbaa00 100644 --- a/util.c +++ b/util.c @@ -74,6 +74,20 @@ void set_ansi_dll( void ) } +// GetVersion and GetVersionEx use Win32VersionValue from the header, which +// could be anything. Retrieve the OS version from NTDLL's header. +DWORD get_os_version( void ) +{ + PIMAGE_DOS_HEADER pDosHeader; + PIMAGE_NT_HEADERS pNTHeader; + + pDosHeader = (PIMAGE_DOS_HEADER)GetModuleHandle( L"ntdll.dll" ); + pNTHeader = MakeVA( PIMAGE_NT_HEADERS, pDosHeader->e_lfanew ); + return pNTHeader->OptionalHeader.MajorOperatingSystemVersion << 8 | + pNTHeader->OptionalHeader.MinorOperatingSystemVersion; +} + + static LPSTR buf; static DWORD buf_len; static BOOL quote, alt;