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

23
ANSI.c
View File

@ -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 );
}

View File

@ -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();

View File

@ -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, ... );

110
injdll.c
View File

@ -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 );
}

View File

@ -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

View File

@ -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.

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