Inject by remote load if there's no IAT on Win8+

Windows 8 and later require the IDT to be within a section when there's
no IAT.  This prevents relocated imports from working, so we cannot add
ourself to the import table.  Use `LdrLoadDll` via `CreateRemoteThread`
for such a situation.
This commit is contained in:
Jason Hood 2018-05-04 11:45:10 +10:00
parent f4697b59fa
commit f8509c916c
7 changed files with 144 additions and 25 deletions

21
ANSI.c
View File

@ -197,13 +197,14 @@
v1.83, 16 February, 2018: v1.83, 16 February, 2018:
create the flush thread on first use. 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; close the flush handles on detach;
dynamically load WINMM.DLL; dynamically load WINMM.DLL;
use sprintf/_snprintf/_snwprintf instead of wsprintf, avoiding USER32.DLL; use sprintf/_snprintf/_snwprintf instead of wsprintf, avoiding USER32.DLL;
replace bsearch (in procrva.c) with specific code; replace bsearch (in procrva.c) with specific code;
if the primary thread is detached exit the process; 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" #include "ansicon.h"
@ -2984,7 +2985,7 @@ void Inject( DWORD dwCreationFlags, LPPROCESS_INFORMATION lpi,
} }
else // (type == 48) else // (type == 48)
{ {
InjectDLL64( child_pi ); RemoteLoad64( child_pi );
} }
#else #else
#ifdef W32ON64 #ifdef W32ON64
@ -3840,16 +3841,24 @@ void OriginalAttr( PVOID lpReserved )
// If we were loaded dynamically, remember the current attributes to restore // 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- // 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) if (lpReserved == NULL)
{ {
#ifdef _WIN64 BOOL dynamic = TRUE;
PIMAGE_DOS_HEADER pDosHeader; PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNTHeader; PIMAGE_NT_HEADERS pNTHeader;
pDosHeader = (PIMAGE_DOS_HEADER)GetModuleHandle( NULL ); pDosHeader = (PIMAGE_DOS_HEADER)GetModuleHandle( NULL );
pNTHeader = MakeVA( PIMAGE_NT_HEADERS, pDosHeader->e_lfanew ); 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 #endif
if (pNTHeader->DATADIRS <= IMAGE_DIRECTORY_ENTRY_IAT &&
get_os_version() >= 0x602)
dynamic = FALSE;
if (dynamic)
orgattr = ATTR; orgattr = ATTR;
GetConsoleMode( hConOut, &orgmode ); GetConsoleMode( hConOut, &orgmode );
GetConsoleCursorInfo( hConOut, &orgcci ); GetConsoleCursorInfo( hConOut, &orgcci );

View File

@ -91,7 +91,7 @@
use -pu to unload from the parent. use -pu to unload from the parent.
*/ */
#define PDATE L"30 April, 2018" #define PDATE L"4 May, 2018"
#include "ansicon.h" #include "ansicon.h"
#include "version.h" #include "version.h"
@ -208,7 +208,7 @@ BOOL Inject( LPPROCESS_INFORMATION ppi, BOOL* gui, LPCTSTR app )
else if (type == 32) else if (type == 32)
InjectDLL32( ppi, base ); InjectDLL32( ppi, base );
else // (type == 48) else // (type == 48)
InjectDLL64( ppi ); RemoteLoad64( ppi );
#else #else
wcscpy( DllName + len, L"ANSI32.dll" ); wcscpy( DllName + len, L"ANSI32.dll" );
set_ansi_dll(); set_ansi_dll();

View File

@ -87,9 +87,10 @@ int ProcessType( LPPROCESS_INFORMATION, PBYTE*, BOOL* );
BOOL Wow64Process( HANDLE ); BOOL Wow64Process( HANDLE );
void InjectDLL( LPPROCESS_INFORMATION, PBYTE ); void InjectDLL( LPPROCESS_INFORMATION, PBYTE );
void RemoteLoad32( LPPROCESS_INFORMATION );
#ifdef _WIN64 #ifdef _WIN64
void InjectDLL32( LPPROCESS_INFORMATION, PBYTE ); void InjectDLL32( LPPROCESS_INFORMATION, PBYTE );
void InjectDLL64( LPPROCESS_INFORMATION ); void RemoteLoad64( LPPROCESS_INFORMATION );
DWORD GetProcRVA( LPCTSTR, LPCSTR, int ); DWORD GetProcRVA( LPCTSTR, LPCSTR, int );
#else #else
DWORD GetProcRVA( LPCTSTR, LPCSTR ); DWORD GetProcRVA( LPCTSTR, LPCSTR );
@ -107,6 +108,7 @@ extern char ansi_dll[MAX_PATH];
extern DWORD ansi_len; extern DWORD ansi_len;
extern char* ansi_bits; extern char* ansi_bits;
void set_ansi_dll( void ); void set_ansi_dll( void );
DWORD get_os_version( void );
extern int log_level; extern int log_level;
void DEBUGSTR( int level, LPCSTR szFormat, ... ); void DEBUGSTR( int level, LPCSTR szFormat, ... );

110
injdll.c
View File

@ -84,6 +84,19 @@ void InjectDLL( LPPROCESS_INFORMATION ppi, PBYTE pBase )
pNTHeader = (PIMAGE_NT_HEADERS)(pBase + DosHeader.e_lfanew); pNTHeader = (PIMAGE_NT_HEADERS)(pBase + DosHeader.e_lfanew);
ReadProcVar( pNTHeader, &NTHeader ); 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 ); import_size = sizeof_imports( ppi, pBase, NTHeader.IMPORTDIR.VirtualAddress );
len = 2 * PTRSZ + ansi_len + sizeof(*pImports) + import_size; len = 2 * PTRSZ + ansi_len + sizeof(*pImports) + import_size;
pImports = HeapAlloc( hHeap, 0, len ); pImports = HeapAlloc( hHeap, 0, len );
@ -173,6 +186,13 @@ void InjectDLL32( LPPROCESS_INFORMATION ppi, PBYTE pBase )
pNTHeader = (PIMAGE_NT_HEADERS32)(pBase + DosHeader.e_lfanew); pNTHeader = (PIMAGE_NT_HEADERS32)(pBase + DosHeader.e_lfanew);
ReadProcVar( pNTHeader, &NTHeader ); 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 ); import_size = sizeof_imports( ppi, pBase, NTHeader.IMPORTDIR.VirtualAddress );
len = 8 + ansi_len + sizeof(*pImports) + import_size; len = 8 + ansi_len + sizeof(*pImports) + import_size;
pImports = HeapAlloc( hHeap, 0, len ); 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 Locate the base address of ntdll.dll. This is supposedly really at the same
the same address for every process, but let's find it anyway. A newly- address for every process, but let's find it anyway. A newly-created
created suspended 64-bit process has two images in memory: the process itself suspended process has two images in memory: the process itself and ntdll.dll.
and ntdll.dll - the one that is a DLL must be ntdll.dll. (A 32-bit WOW64 Thus the one that is a DLL must be ntdll.dll. However, a WOW64 process also
process has three images - the process and both 64- & 32-bit ntdll.dll). 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 ) static PBYTE get_ntdll( LPPROCESS_INFORMATION ppi )
#endif
{ {
PBYTE ptr; PBYTE ptr;
MEMORY_BASIC_INFORMATION minfo; MEMORY_BASIC_INFORMATION minfo;
@ -258,7 +283,11 @@ static PBYTE get_ntdll( LPPROCESS_INFORMATION ppi )
&& ReadProcVar( (PBYTE)minfo.BaseAddress + dos_header.e_lfanew, && ReadProcVar( (PBYTE)minfo.BaseAddress + dos_header.e_lfanew,
&nt_header ) &nt_header )
&& nt_header.Signature == IMAGE_NT_SIGNATURE && 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; 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; PBYTE ntdll;
DWORD rLdrLoadDll; DWORD rLdrLoadDll;
@ -285,7 +315,7 @@ void InjectDLL64( LPPROCESS_INFORMATION ppi )
PBYTE* pL; PBYTE* pL;
} ip; } ip;
ntdll = get_ntdll( ppi ); ntdll = get_ntdll( ppi, IMAGE_FILE_MACHINE_AMD64 );
if (ntdll == NULL) if (ntdll == NULL)
return; return;
@ -327,3 +357,67 @@ void InjectDLL64( LPPROCESS_INFORMATION ppi )
VirtualFreeEx( ppi->hProcess, pMem, 0, MEM_RELEASE ); VirtualFreeEx( ppi->hProcess, pMem, 0, MEM_RELEASE );
} }
#endif #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 );
}

View File

@ -70,9 +70,9 @@ LIBS = advapi32.lib $(LIBS64)
# Identify ansicon.exe using "ANSI" as a version number. # Identify ansicon.exe using "ANSI" as a version number.
LINK = /link /version:20033.18771 LINK = /link /version:20033.18771
X86OBJS = x86\injdll.obj x86\proctype.obj x86\util.obj X86OBJS = x86\injdll.obj x86\procrva.obj x86\proctype.obj x86\util.obj
X64OBJS = x64\injdll.obj x64\proctype.obj x64\util.obj x64\procrva.obj X64OBJS = x64\injdll.obj x64\procrva.obj x64\proctype.obj x64\util.obj
X6432OBJS = x86\injdll.obj x64\proctype32.obj x86\util.obj X6432OBJS = x86\injdll.obj x86\procrva.obj x64\proctype32.obj x86\util.obj
!IF !DEFINED(V) !IF !DEFINED(V)
V = 0 V = 0
@ -99,7 +99,7 @@ ansicon64: x64 x64\ansicon.exe x64\ANSI64.dll
x86: x86:
mkdir 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 $(LDmsg)$(CC) /nologo $(SHARE) /Fe$@ $** $(LIBS) $(LINK) /filealign:512
!IF "$(_NMAKE_VER)" == "9.00.30729.01" !IF "$(_NMAKE_VER)" == "9.00.30729.01"
$(MTmsg)$(MT) /nologo -manifest $@.manifest -outputresource:$@;1 $(MTmsg)$(MT) /nologo -manifest $@.manifest -outputresource:$@;1

View File

@ -339,10 +339,10 @@ Version History
Legend: + added, - bug-fixed, * changed. Legend: + added, - bug-fixed, * changed.
1.84-wip - 3 May, 2018: 1.84-wip - 4 May, 2018:
- close the flush handles on detach; - 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; - 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; * remove dependency on USER32, dynamically load WINMM;
* exit process if the primary thread is detached (for processes on Win10 * exit process if the primary thread is detached (for processes on Win10
that return, rather than call ExitProcess). that return, rather than call ExitProcess).
@ -621,4 +621,4 @@ Distribution
======================== ========================
Jason Hood, 3 May, 2018. Jason Hood, 4 May, 2018.

14
util.c
View File

@ -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 LPSTR buf;
static DWORD buf_len; static DWORD buf_len;
static BOOL quote, alt; static BOOL quote, alt;