Main.c
InjpJoinPath
디렉토리와 파일네임을 입력받아 FullPath(디렉토리+”//”+파일네임)으로 리턴해주는 함수입니다.
NTSTATUS
NTAPI
InjpJoinPath(
_In_ PUNICODE_STRING Directory,
_In_ PUNICODE_STRING Filename,
_Inout_ PUNICODE_STRING FullPath
)
{
UNICODE_STRING UnicodeBackslash = RTL_CONSTANT_STRING(L"\\");
BOOLEAN DirectoryEndsWithBackslash = Directory->Length > 0 &&
Directory->Buffer[Directory->Length - 1] == L'\\';
if (FullPath->MaximumLength < Directory->Length ||
FullPath->MaximumLength - Directory->Length -
(!DirectoryEndsWithBackslash ? 1 : 0) < Filename->Length)
{
return STATUS_DATA_ERROR;
}
RtlCopyUnicodeString(FullPath, Directory);
if (!DirectoryEndsWithBackslash)
{
RtlAppendUnicodeStringToString(FullPath, &UnicodeBackslash);
}
RtlAppendUnicodeStringToString(FullPath, Filename);
return STATUS_SUCCESS;
}
C++
복사
InjCreateSettings
NTSTATUS
NTAPI
InjCreateSettings(
_In_ PUNICODE_STRING RegistryPath,
_Inout_ PINJ_SETTINGS Settings
)
{
//
// In the "ImagePath" key of the RegistryPath, there
// is a full path of this driver file. Fetch it.
//
NTSTATUS Status;
UNICODE_STRING ValueName = RTL_CONSTANT_STRING(L"ImagePath");
OBJECT_ATTRIBUTES ObjectAttributes;
InitializeObjectAttributes(&ObjectAttributes,
RegistryPath,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
NULL);
HANDLE KeyHandle;
Status = ZwOpenKey(&KeyHandle,
KEY_READ,
&ObjectAttributes);
if (!NT_SUCCESS(Status))
{
return Status;
}
//
// Save all information on stack - simply fail if path
// is too long.
//
UCHAR KeyValueInformationBuffer[sizeof(KEY_VALUE_FULL_INFORMATION) + sizeof(WCHAR) * 128];
PKEY_VALUE_FULL_INFORMATION KeyValueInformation = (PKEY_VALUE_FULL_INFORMATION)KeyValueInformationBuffer;
ULONG ResultLength;
Status = ZwQueryValueKey(KeyHandle,
&ValueName,
KeyValueFullInformation,
KeyValueInformation,
sizeof(KeyValueInformationBuffer),
&ResultLength);
ZwClose(KeyHandle);
//
// Check for succes. Also check if the value is of expected
// type and whether the path has a meaninful length.
//
if (!NT_SUCCESS(Status) ||
KeyValueInformation->Type != REG_EXPAND_SZ ||
KeyValueInformation->DataLength < sizeof(ObpDosDevicesShortNamePrefix))
{
return Status;
}
//
// Save pointer to the fetched ImagePath value and test if
// the path starts with "\??\" prefix - if so, skip it.
//
PWCHAR ImagePathValue = (PWCHAR)((PUCHAR)KeyValueInformation + KeyValueInformation->DataOffset);
ULONG ImagePathValueLength = KeyValueInformation->DataLength;
if (*(PULONGLONG)(ImagePathValue) == ObpDosDevicesShortNamePrefix.Alignment.QuadPart)
{
ImagePathValue += ObpDosDevicesShortName.Length / sizeof(WCHAR);
ImagePathValueLength -= ObpDosDevicesShortName.Length;
}
//
// Cut the string by the last '\' character, leaving there
// only the directory path.
//
PWCHAR LastBackslash = wcsrchr(ImagePathValue, L'\\');
if (!LastBackslash)
{
return STATUS_DATA_ERROR;
}
*LastBackslash = UNICODE_NULL;
UNICODE_STRING Directory;
RtlInitUnicodeString(&Directory, ImagePathValue);
//
// Finally, fill all the buffers...
//
#define INJ_DLL_X86_NAME L"injdllx86.dll"
UNICODE_STRING InjDllNameX86 = RTL_CONSTANT_STRING(INJ_DLL_X86_NAME);
InjpJoinPath(&Directory, &InjDllNameX86, &Settings->DllPath[InjArchitectureX86]);
InjDbgPrint("[injdrv]: DLL path (x86): '%wZ'\n", &Settings->DllPath[InjArchitectureX86]);
#define INJ_DLL_X64_NAME L"injdllx64.dll"
UNICODE_STRING InjDllNameX64 = RTL_CONSTANT_STRING(INJ_DLL_X64_NAME);
InjpJoinPath(&Directory, &InjDllNameX64, &Settings->DllPath[InjArchitectureX64]);
InjDbgPrint("[injdrv]: DLL path (x64): '%wZ'\n", &Settings->DllPath[InjArchitectureX64]);
#define INJ_DLL_ARM32_NAME L"injdllARM.dll"
UNICODE_STRING InjDllNameARM32 = RTL_CONSTANT_STRING(INJ_DLL_ARM32_NAME);
InjpJoinPath(&Directory, &InjDllNameARM32, &Settings->DllPath[InjArchitectureARM32]);
InjDbgPrint("[injdrv]: DLL path (ARM32): '%wZ'\n", &Settings->DllPath[InjArchitectureARM32]);
#define INJ_DLL_ARM64_NAME L"injdllARM64.dll"
UNICODE_STRING InjDllNameARM64 = RTL_CONSTANT_STRING(INJ_DLL_ARM64_NAME);
InjpJoinPath(&Directory, &InjDllNameARM64, &Settings->DllPath[InjArchitectureARM64]);
InjDbgPrint("[injdrv]: DLL path (ARM64): '%wZ'\n", &Settings->DllPath[InjArchitectureARM64]);
return STATUS_SUCCESS;
}
C++
복사
1. RTL_CONSTANT_STRING
이 함수는 문자열 받아 UNICODE_STRING을 바로 생성해줍니다.
UNICODE_STRING RTL_CONSTANT_STRING(
[in] PCWSTR SourceString
);
C++
복사
2. ObjectAttributes, ZwOpenKey
ZwOpenKey에 필요한 ObjectAttributes를 InitializeObjectAttributes함수로 생성합니다.
이후 ZwOpenKey로 레지스트리 키의 핸들을 획득합니다.
OBJECT_ATTRIBUTES ObjectAttributes;
InitializeObjectAttributes(&ObjectAttributes,
RegistryPath,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
NULL);
HANDLE KeyHandle;
Status = ZwOpenKey(&KeyHandle,
KEY_READ,
&ObjectAttributes);
C++
복사
3. KeyValueInformation,ZwQueryValueKey
UCHAR 타입으로 KeyValueInformation에 사용할 버퍼 할당 후 PKEY_VALUE_FULL_INFORMATION으로 사용합니다.
이후 ZwQueryValueKey 함수로 레지스트리 값 획득합니다.
UCHAR KeyValueInformationBuffer[sizeof(KEY_VALUE_FULL_INFORMATION) + sizeof(WCHAR) * 128];
PKEY_VALUE_FULL_INFORMATION KeyValueInformation = (PKEY_VALUE_FULL_INFORMATION)KeyValueInformationBuffer;
ULONG ResultLength;
Status = ZwQueryValueKey(KeyHandle,
&ValueName,
KeyValueFullInformation,
KeyValueInformation,
sizeof(KeyValueInformationBuffer),
&ResultLength);
ZwClose(KeyHandle);
//
// Check for succes. Also check if the value is of expected
// type and whether the path has a meaninful length.
//
C++
복사
•
KeyValueInformationBuffer 사이즈 할당을 저렇게 하는 이유
◦
커널 구조체들은 대체적으로 배열들의 사이즈를 1로 놓고 있기 때문에 추가적으로 할당을 해줘야 합니다.
typedef struct _KEY_VALUE_FULL_INFORMATION {
ULONG TitleIndex;
ULONG Type;
ULONG DataOffset;
ULONG DataLength;
ULONG NameLength;
WCHAR Name[1]; // Variable size
// Data[1]; // Variable size data not declared
} KEY_VALUE_FULL_INFORMATION, *PKEY_VALUE_FULL_INFORMATION;
C++
복사
ImagePathValue
문자열 시작이 //일경우 예외처리하며 가끔식 커널에서 시작 경로를 \\로 처리하는 경우가 있기 때문입니다.
PWCHAR ImagePathValue = (PWCHAR)((PUCHAR)KeyValueInformation + KeyValueInformation->DataOffset);
ULONG ImagePathValueLength = KeyValueInformation->DataLength;
if (*(PULONGLONG)(ImagePathValue) == ObpDosDevicesShortNamePrefix.Alignment.QuadPart)
{
ImagePathValue += ObpDosDevicesShortName.Length / sizeof(WCHAR);
ImagePathValueLength -= ObpDosDevicesShortName.Length;
}
C++
복사
LastBackSlash 처리
문자열에서 \\ 찾은 후 없으면 예외처리, 있을경우 널 처리하여 유니코드 스트링으로 저장합니다.
PWCHAR LastBackslash = wcsrchr(ImagePathValue, L'\\');
if (!LastBackslash)
{
return STATUS_DATA_ERROR;
}
*LastBackslash = UNICODE_NULL;
UNICODE_STRING Directory;
RtlInitUnicodeString(&Directory, ImagePathValue);
C++
복사
InjpJoinPath
마지막으로 아키텍쳐에 따라서 Injlib 의 InjpJoinPath함수를 호출합니다.
#define INJ_DLL_X86_NAME L"injdllx86.dll"
UNICODE_STRING InjDllNameX86 = RTL_CONSTANT_STRING(INJ_DLL_X86_NAME);
InjpJoinPath(&Directory, &InjDllNameX86, &Settings->DllPath[InjArchitectureX86]);
InjDbgPrint("[injdrv]: DLL path (x86): '%wZ'\n", &Settings->DllPath[InjArchitectureX86]);
#define INJ_DLL_X64_NAME L"injdllx64.dll"
UNICODE_STRING InjDllNameX64 = RTL_CONSTANT_STRING(INJ_DLL_X64_NAME);
InjpJoinPath(&Directory, &InjDllNameX64, &Settings->DllPath[InjArchitectureX64]);
InjDbgPrint("[injdrv]: DLL path (x64): '%wZ'\n", &Settings->DllPath[InjArchitectureX64]);
#define INJ_DLL_ARM32_NAME L"injdllARM.dll"
UNICODE_STRING InjDllNameARM32 = RTL_CONSTANT_STRING(INJ_DLL_ARM32_NAME);
InjpJoinPath(&Directory, &InjDllNameARM32, &Settings->DllPath[InjArchitectureARM32]);
InjDbgPrint("[injdrv]: DLL path (ARM32): '%wZ'\n", &Settings->DllPath[InjArchitectureARM32]);
#define INJ_DLL_ARM64_NAME L"injdllARM64.dll"
UNICODE_STRING InjDllNameARM64 = RTL_CONSTANT_STRING(INJ_DLL_ARM64_NAME);
InjpJoinPath(&Directory, &InjDllNameARM64, &Settings->DllPath[InjArchitectureARM64]);
InjDbgPrint("[injdrv]: DLL path (ARM64): '%wZ'\n", &Settings->DllPath[InjArchitectureARM64]);
return STATUS_SUCCESS;
C++
복사
DriverEntry
NTSTATUS
NTAPI
DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
NTSTATUS Status;
//
// Register DriverUnload routine.
//
DriverObject->DriverUnload = &DriverDestroy;
//
// Create injection settings.
//
INJ_SETTINGS Settings;
WCHAR BufferDllPathX86[128];
Settings.DllPath[InjArchitectureX86].Length = 0;
Settings.DllPath[InjArchitectureX86].MaximumLength = sizeof(BufferDllPathX86);
Settings.DllPath[InjArchitectureX86].Buffer = BufferDllPathX86;
WCHAR BufferDllPathX64[128];
Settings.DllPath[InjArchitectureX64].Length = 0;
Settings.DllPath[InjArchitectureX64].MaximumLength = sizeof(BufferDllPathX64);
Settings.DllPath[InjArchitectureX64].Buffer = BufferDllPathX64;
WCHAR BufferDllPathARM32[128];
Settings.DllPath[InjArchitectureARM32].Length = 0;
Settings.DllPath[InjArchitectureARM32].MaximumLength = sizeof(BufferDllPathARM32);
Settings.DllPath[InjArchitectureARM32].Buffer = BufferDllPathARM32;
WCHAR BufferDllPathARM64[128];
Settings.DllPath[InjArchitectureARM64].Length = 0;
Settings.DllPath[InjArchitectureARM64].MaximumLength = sizeof(BufferDllPathARM64);
Settings.DllPath[InjArchitectureARM64].Buffer = BufferDllPathARM64;
Status = InjCreateSettings(RegistryPath, &Settings);
if (!NT_SUCCESS(Status))
{
return Status;
}
#if defined (_M_IX86)
Settings.Method = InjMethodThunk;
#elif defined (_M_AMD64)
Settings.Method = InjMethodThunkless;
#elif defined (_M_ARM64)
Settings.Method = InjMethodWow64LogReparse;
#endif
//
// Initialize injection driver.
//
Status = InjInitialize(DriverObject, RegistryPath, &Settings);
if (!NT_SUCCESS(Status))
{
return Status;
}
//
// Install CreateProcess and LoadImage notification routines.
//
Status = PsSetCreateProcessNotifyRoutineEx(&InjCreateProcessNotifyRoutineEx, FALSE);
if (!NT_SUCCESS(Status))
{
return Status;
}
Status = PsSetLoadImageNotifyRoutine(&InjLoadImageNotifyRoutine);
if (!NT_SUCCESS(Status))
{
PsSetCreateProcessNotifyRoutineEx(&InjCreateProcessNotifyRoutineEx, TRUE);
return Status;
}
return STATUS_SUCCESS;
}
C++
복사
Settings
InjCreateSettings 함수로 Setting 값 초기화를 수행합니다.
INJ_SETTINGS Settings;
WCHAR BufferDllPathX86[128];
Settings.DllPath[InjArchitectureX86].Length = 0;
Settings.DllPath[InjArchitectureX86].MaximumLength = sizeof(BufferDllPathX86);
Settings.DllPath[InjArchitectureX86].Buffer = BufferDllPathX86;
WCHAR BufferDllPathX64[128];
Settings.DllPath[InjArchitectureX64].Length = 0;
Settings.DllPath[InjArchitectureX64].MaximumLength = sizeof(BufferDllPathX64);
Settings.DllPath[InjArchitectureX64].Buffer = BufferDllPathX64;
WCHAR BufferDllPathARM32[128];
Settings.DllPath[InjArchitectureARM32].Length = 0;
Settings.DllPath[InjArchitectureARM32].MaximumLength = sizeof(BufferDllPathARM32);
Settings.DllPath[InjArchitectureARM32].Buffer = BufferDllPathARM32;
WCHAR BufferDllPathARM64[128];
Settings.DllPath[InjArchitectureARM64].Length = 0;
Settings.DllPath[InjArchitectureARM64].MaximumLength = sizeof(BufferDllPathARM64);
Settings.DllPath[InjArchitectureARM64].Buffer = BufferDllPathARM64;
Status = InjCreateSettings(RegistryPath, &Settings);
if (!NT_SUCCESS(Status))
{
return Status;
}
C++
복사
Archtecture 설정
Settings의 Method 값을 빌드 아키텍쳐에 따라서 설정. 이후 호출하는 InjInitialize 함수에서 사용하며
따로 아키텍쳐에 따라서 고정으로 이용합니다.
#if defined (_M_IX86)
Settings.Method = InjMethodThunk;
#elif defined (_M_AMD64)
Settings.Method = InjMethodThunkless;
#elif defined (_M_ARM64)
Settings.Method = InjMethodWow64LogReparse;
#endif
C++
복사
InjInitialize
Status = InjInitialize(DriverObject, RegistryPath, &Settings);
if (!NT_SUCCESS(Status))
{
return Status;
}
C++
복사
NotifyRoutine계열
•
PsSetCreateProcessNotifyRoutineEx
•
PsSetLoadImageNotifyRoutine
Status = PsSetCreateProcessNotifyRoutineEx(&InjCreateProcessNotifyRoutineEx, FALSE);
if (!NT_SUCCESS(Status))
{
return Status;
}
Status = PsSetLoadImageNotifyRoutine(&InjLoadImageNotifyRoutine);
if (!NT_SUCCESS(Status))
{
PsSetCreateProcessNotifyRoutineEx(&InjCreateProcessNotifyRoutineEx, TRUE);
return Status;
}
return STATUS_SUCCESS;
C++
복사
- PsSetCreateProcessNotifyRoutine
PsSetCreateProcessNotifyRoutine 함수는 프로세스가 생성 될 때 등록한 NotifyRoutine 함수가 호출됩니다.
파라미터로 PCREATE_PROCESS_NOTIFY_ROUTINE_EX 타입 NotifyRoutine과 BOOLEAN 형 Remove 값을 전달받습니다.
NTSTATUS PsSetCreateProcessNotifyRoutineEx(
[in] PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine,
[in] BOOLEAN Remove
);
C++
복사
PCREATE_PROCESS_NOTIFY_ROUTINE_EX 구조체는 아래와 같습니다.
PCREATE_PROCESS_NOTIFY_ROUTINE_EX PcreateProcessNotifyRoutineEx;
void PcreateProcessNotifyRoutineEx(
[_Inout_] PEPROCESS Process,
[in] HANDLE ProcessId,
[in, out, optional] PPS_CREATE_NOTIFY_INFO CreateInfo
)
{...}
C++
복사
다시 InjCreateProcessNotifyRoutineEx 함수를 확인 해 봅시다.
CreateInfo가 존재 할 경우 InjCreateInjectionInfo 함수를 호출하고
없을 경우 InjRemoveInjectionInfoByProcessId를 호출합니다.
CreateInfo는 프로세스가 생성 될 때 생성되는 구조체입니다.
- InjCreateProcessNotifyRoutineEx
VOID
NTAPI
InjCreateProcessNotifyRoutineEx(
_Inout_ PEPROCESS Process,
_In_ HANDLE ProcessId,
_Inout_opt_ PPS_CREATE_NOTIFY_INFO CreateInfo
)
{
UNREFERENCED_PARAMETER(Process);
if (CreateInfo)
{
InjCreateInjectionInfo(NULL, ProcessId);
}
else
{
InjRemoveInjectionInfoByProcessId(ProcessId, TRUE);
}
}
C++
복사
- injlib!InjCreateInjectionInfo
프로세스가 생성 될 때 호출하는 함수입니다.
첫번째 파라미터인 InjectionInfo가 존재할 경우 CapturedInjectionInfo값으로 사용하며
없을 경우 ExAllocatePoolWithTag를 이용하여 새로 할당해줍니다.
NTSTATUS
NTAPI
InjCreateInjectionInfo(
_In_opt_ PINJ_INJECTION_INFO* InjectionInfo,
_In_ HANDLE ProcessId
)
{
PINJ_INJECTION_INFO CapturedInjectionInfo;
if (InjectionInfo && *InjectionInfo)
{
CapturedInjectionInfo = *InjectionInfo;
}
else
{
CapturedInjectionInfo = ExAllocatePoolWithTag(NonPagedPoolNx,
sizeof(INJ_INJECTION_INFO),
INJ_MEMORY_TAG);
if (!CapturedInjectionInfo)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
if (InjectionInfo)
{
*InjectionInfo = CapturedInjectionInfo;
}
}
RtlZeroMemory(CapturedInjectionInfo, sizeof(INJ_INJECTION_INFO));
CapturedInjectionInfo->ProcessId = ProcessId;
CapturedInjectionInfo->ForceUserApc = TRUE;
CapturedInjectionInfo->Method = InjMethod;
InsertTailList(&InjInfoListHead, &CapturedInjectionInfo->ListEntry);
return STATUS_SUCCESS;
}
C++
복사
할당한 CapturedInjectionInfo를 초기화하고 값을 할당 해 줍니다.
RtlZeroMemory(CapturedInjectionInfo, sizeof(INJ_INJECTION_INFO));
CapturedInjectionInfo->ProcessId = ProcessId;
CapturedInjectionInfo->ForceUserApc = TRUE;
CapturedInjectionInfo->Method = InjMethod;
InsertTailList(&InjInfoListHead, &CapturedInjectionInfo->ListEntry);
C++
복사
그리고 InjLib에서 Init 했던 InjInfoListHead를 ListEntry→Flink 값으로 변경합니다.
- PsSetLoadImageNotifyRoutine
PsSetLoadImageNotifyRoutine 함수는 특정 이미지(DLL, EXE)가 로드되었을 때 등록된 NotifyRoutine 함수가 호출되는 방식입니다.
파라미터로 PLOAD_IMAGE_NOTIFY_ROUTINE 타입의 파라미터를 전달받습니다
PLOAD_IMAGE_NOTIFY_ROUTINE PloadImageNotifyRoutine;
void PloadImageNotifyRoutine(
[in, optional] PUNICODE_STRING FullImageName,
[in] HANDLE ProcessId,
[in] PIMAGE_INFO ImageInfo
)
{...}
C++
복사
- Injlib!InjLoadImageNotifyRoutine
PsSetLoadImageNotifyRoutine에 등록 할 함수입니다.
VOID
NTAPI
InjLoadImageNotifyRoutine(
_In_opt_ PUNICODE_STRING FullImageName,
_In_ HANDLE ProcessId,
_In_ PIMAGE_INFO ImageInfo
)
{
//
// Check if current process is injected.
//
PINJ_INJECTION_INFO InjectionInfo = InjFindInjectionInfo(ProcessId);
if (!InjectionInfo || InjectionInfo->IsInjected)
{
return;
}
#if defined(INJ_CONFIG_SUPPORTS_WOW64)
//
// If reparse-injection is enabled and this process is
// Wow64 process, then do not track load-images.
//
if (InjectionInfo->Method == InjMethodWow64LogReparse &&
PsGetProcessWow64Process(PsGetCurrentProcess()))
{
return;
}
#endif
if (PsIsProtectedProcess(PsGetCurrentProcess()))
{
//
// Protected processes throw code-integrity error when
// they are injected. Signing policy can be changed, but
// it requires hacking with lots of internal and Windows-
// version-specific structures. Simly don't inject such
// processes.
//
// See Blackbone project (https://github.com/DarthTon/Blackbone)
// if you're interested how protection can be temporarily
// disabled on such processes. (Look for BBSetProtection).
//
InjDbgPrint("[injlib]: Ignoring protected process (PID: %u, Name: '%s')\n",
(ULONG)(ULONG_PTR)ProcessId,
PsGetProcessImageFileName(PsGetCurrentProcess()));
InjRemoveInjectionInfoByProcessId(ProcessId, TRUE);
return;
}
if (!InjCanInject(InjectionInfo))
{
//
// This process is in early stage - important DLLs (such as
// ntdll.dll - or wow64.dll in case of Wow64 process) aren't
// properly initialized yet. We can't inject the DLL until
// they are.
//
// Check if any of the system DLLs we're interested in is being
// currently loaded - if so, mark that information down into the
// LoadedDlls field.
//
for (ULONG Index = 0; Index < RTL_NUMBER_OF(InjpSystemDlls); Index += 1)
{
PUNICODE_STRING SystemDllPath = &InjpSystemDlls[Index].DllPath;
if (RtlxSuffixUnicodeString(SystemDllPath, FullImageName, TRUE))
{
PVOID LdrLoadDllRoutineAddress = RtlxFindExportedRoutineByName(ImageInfo->ImageBase,
&LdrLoadDllRoutineName);
ULONG DllFlag = InjpSystemDlls[Index].Flag;
InjectionInfo->LoadedDlls |= DllFlag;
switch (DllFlag)
{
//
// In case of "thunk method", capture address of the LdrLoadDll
// routine from the ntdll.dll (which is of the same architecture
// as the process).
//
case INJ_SYSARM32_NTDLL_LOADED:
case INJ_SYCHPE32_NTDLL_LOADED:
case INJ_SYSWOW64_NTDLL_LOADED:
if (InjectionInfo->Method != InjMethodThunkless)
{
InjectionInfo->LdrLoadDllRoutineAddress = LdrLoadDllRoutineAddress;
}
break;
//
// For "thunkless method", capture address of the LdrLoadDll
// routine from the native ntdll.dll.
//
case INJ_SYSTEM32_NTDLL_LOADED:
InjectionInfo->LdrLoadDllRoutineAddress = LdrLoadDllRoutineAddress;
break;
default:
break;
}
//
// Break the for-loop.
//
break;
}
}
}
else
{
#if defined(INJ_CONFIG_SUPPORTS_WOW64)
if (InjIsWindows7 &&
InjectionInfo->Method == InjMethodThunk &&
PsGetProcessWow64Process(PsGetCurrentProcess()))
{
//
// On Windows 7, if we're injecting DLL into Wow64 process using
// the "thunk method", we have additionaly postpone the load after
// these system DLLs.
//
// This is because on Windows 7, these DLLs are loaded as part of
// the wow64!ProcessInit routine, therefore the Wow64 subsystem
// is not fully initialized to execute our injected Wow64ApcRoutine.
//
UNICODE_STRING System32Kernel32Path = RTL_CONSTANT_STRING(L"\\System32\\kernel32.dll");
UNICODE_STRING SysWOW64Kernel32Path = RTL_CONSTANT_STRING(L"\\SysWOW64\\kernel32.dll");
UNICODE_STRING System32User32Path = RTL_CONSTANT_STRING(L"\\System32\\user32.dll");
UNICODE_STRING SysWOW64User32Path = RTL_CONSTANT_STRING(L"\\SysWOW64\\user32.dll");
if (RtlxSuffixUnicodeString(&System32Kernel32Path, FullImageName, TRUE) ||
RtlxSuffixUnicodeString(&SysWOW64Kernel32Path, FullImageName, TRUE) ||
RtlxSuffixUnicodeString(&System32User32Path, FullImageName, TRUE) ||
RtlxSuffixUnicodeString(&SysWOW64User32Path, FullImageName, TRUE))
{
InjDbgPrint("[injlib]: Postponing injection (%wZ)\n", FullImageName);
return;
}
}
#endif
//
// All necessary DLLs are loaded - perform the injection.
//
// Note that injection is done via kernel-mode APC, because
// InjInject calls ZwMapViewOfSection and MapViewOfSection
// might be already on the callstack. Because MapViewOfSection
// locks the EPROCESS->AddressCreationLock, we would be risking
// deadlock by calling InjInject directly.
//
#if defined(INJ_CONFIG_SUPPORTS_WOW64)
InjDbgPrint("[injlib]: Injecting (PID: %u, Wow64: %s, Name: '%s')\n",
(ULONG)(ULONG_PTR)ProcessId,
PsGetProcessWow64Process(PsGetCurrentProcess()) ? "TRUE" : "FALSE",
PsGetProcessImageFileName(PsGetCurrentProcess()));
#else
InjDbgPrint("[injlib]: Injecting (PID: %u, Name: '%s')\n",
(ULONG)(ULONG_PTR)ProcessId,
PsGetProcessImageFileName(PsGetCurrentProcess()));
#endif
InjpQueueApc(KernelMode,
&InjpInjectApcNormalRoutine,
InjectionInfo,
NULL,
NULL);
//
// Mark that this process is injected.
//
InjectionInfo->IsInjected = TRUE;
}
}
C++
복사
Injected Check
Injection 체크하는 로직입니다.
이미 Injection이 되었거나, 해당 프로세스가 InjectionInfo에 존재하는지 확인합니다.
PINJ_INJECTION_INFO InjectionInfo = InjFindInjectionInfo(ProcessId);
if (!InjectionInfo || InjectionInfo->IsInjected)
{
return;
}
C++
복사
— InjLib!InjFindInjectionInfo
ProcessId가 InjInfoListHead에 존재하는지 확인하는 함수입니다.
NextEntry가 InjInfoListHead이 아닐 경우(순회되어 첫 리스트에 도달할 때 까지)
InjectionInfo는 NextEntry의 INJ_INJECTION_INFO 값을 가져옵니다.
그리고 PID가 동일할경우 InjectionInfo를 리턴하며 ListEntry에 존재하지 않을경우 NULL을 리턴합니다.
PINJ_INJECTION_INFO
NTAPI
InjFindInjectionInfo(
_In_ HANDLE ProcessId
)
{
PLIST_ENTRY NextEntry = InjInfoListHead.Flink;
while (NextEntry != &InjInfoListHead)
{
PINJ_INJECTION_INFO InjectionInfo = CONTAINING_RECORD(NextEntry,
INJ_INJECTION_INFO,
ListEntry);
if (InjectionInfo->ProcessId == ProcessId)
{
return InjectionInfo;
}
NextEntry = NextEntry->Flink;
}
return NULL;
}
C++
복사
예외 처리 부분
1. LogReparse && Wow64Process 일 경우
#if defined(INJ_CONFIG_SUPPORTS_WOW64)
//
// If reparse-injection is enabled and this process is
// Wow64 process, then do not track load-images.
//
if (InjectionInfo->Method == InjMethodWow64LogReparse &&
PsGetProcessWow64Process(PsGetCurrentProcess()))
{
return;
}
#endif
C++
복사
2. PsIsProtectedProcess is True
프로세스가 Protected 되어 있을 경우 일반적으로 불가능합니다.
if (PsIsProtectedProcess(PsGetCurrentProcess()))
{
//
// Protected processes throw code-integrity error when
// they are injected. Signing policy can be changed, but
// it requires hacking with lots of internal and Windows-
// version-specific structures. Simly don't inject such
// processes.
//
// See Blackbone project (https://github.com/DarthTon/Blackbone)
// if you're interested how protection can be temporarily
// disabled on such processes. (Look for BBSetProtection).
//
InjDbgPrint("[injlib]: Ignoring protected process (PID: %u, Name: '%s')\n",
(ULONG)(ULONG_PTR)ProcessId,
PsGetProcessImageFileName(PsGetCurrentProcess()));
InjRemoveInjectionInfoByProcessId(ProcessId, TRUE);
return;
}
C++
복사
3. InjCanInject
Injection이 가능한 상황인지 체크합니다
(프로세스가 생성 될 때 System Dll, ntdll.dll 등등이 로드되지 않았을 경우)
이후 Loaded 된 DLL에 따라서 LdrLoadDll 함수의 주소값을 획득합니다.
if (!InjCanInject(InjectionInfo))
{
//
// This process is in early stage - important DLLs (such as
// ntdll.dll - or wow64.dll in case of Wow64 process) aren't
// properly initialized yet. We can't inject the DLL until
// they are.
//
// Check if any of the system DLLs we're interested in is being
// currently loaded - if so, mark that information down into the
// LoadedDlls field.
//
for (ULONG Index = 0; Index < RTL_NUMBER_OF(InjpSystemDlls); Index += 1)
{
PUNICODE_STRING SystemDllPath = &InjpSystemDlls[Index].DllPath;
if (RtlxSuffixUnicodeString(SystemDllPath, FullImageName, TRUE))
{
PVOID LdrLoadDllRoutineAddress = RtlxFindExportedRoutineByName(ImageInfo->ImageBase,
&LdrLoadDllRoutineName);
ULONG DllFlag = InjpSystemDlls[Index].Flag;
InjectionInfo->LoadedDlls |= DllFlag;
switch (DllFlag)
{
//
// In case of "thunk method", capture address of the LdrLoadDll
// routine from the ntdll.dll (which is of the same architecture
// as the process).
//
case INJ_SYSARM32_NTDLL_LOADED:
case INJ_SYCHPE32_NTDLL_LOADED:
case INJ_SYSWOW64_NTDLL_LOADED:
if (InjectionInfo->Method != InjMethodThunkless)
{
InjectionInfo->LdrLoadDllRoutineAddress = LdrLoadDllRoutineAddress;
}
break;
//
// For "thunkless method", capture address of the LdrLoadDll
// routine from the native ntdll.dll.
//
case INJ_SYSTEM32_NTDLL_LOADED:
InjectionInfo->LdrLoadDllRoutineAddress = LdrLoadDllRoutineAddress;
break;
default:
break;
}
//
// Break the for-loop.
//
break;
}
}
}
C++
복사
- InjCanInject
RequiredDlls을 system32/ntdll.dll Flag로 지정하고 Wow64Process일 경우 Or 연산을 통해 DLL Flag를 추가해줍니다.
BOOLEAN
NTAPI
InjCanInject(
_In_ PINJ_INJECTION_INFO InjectionInfo
)
{
//
// DLLs that need to be loaded in the native process
// (i.e.: x64 process on x64 Windows, x86 process on
// x86 Windows) before we can safely load our DLL.
//
ULONG RequiredDlls = INJ_SYSTEM32_NTDLL_LOADED;
#if defined(INJ_CONFIG_SUPPORTS_WOW64)
if (PsGetProcessWow64Process(PsGetCurrentProcess()))
{
//
// DLLs that need to be loaded in the Wow64 process
// before we can safely load our DLL.
//
RequiredDlls |= INJ_SYSTEM32_NTDLL_LOADED;
RequiredDlls |= INJ_SYSTEM32_WOW64_LOADED;
RequiredDlls |= INJ_SYSTEM32_WOW64WIN_LOADED;
# if defined (_M_AMD64)
RequiredDlls |= INJ_SYSTEM32_WOW64CPU_LOADED;
RequiredDlls |= INJ_SYSWOW64_NTDLL_LOADED;
}
#endif
return (InjectionInfo->LoadedDlls & RequiredDlls) == RequiredDlls;
}
C++
복사
위에서 사용하는 DLL Flag는 아래와 같습니다
typedef enum _INJ_SYSTEM_DLL
{
INJ_NOTHING_LOADED = 0x0000,
INJ_SYSARM32_NTDLL_LOADED = 0x0001,
INJ_SYCHPE32_NTDLL_LOADED = 0x0002,
INJ_SYSWOW64_NTDLL_LOADED = 0x0004,
INJ_SYSTEM32_NTDLL_LOADED = 0x0008,
INJ_SYSTEM32_WOW64_LOADED = 0x0010,
INJ_SYSTEM32_WOW64WIN_LOADED = 0x0020,
INJ_SYSTEM32_WOW64CPU_LOADED = 0x0040,
INJ_SYSTEM32_WOWARMHW_LOADED = 0x0080,
INJ_SYSTEM32_XTAJIT_LOADED = 0x0100,
} INJ_SYSTEM_DLL;
C++
복사
4. Win7 && MethodThunk && Wow64
Win7이고 MethodThunk 방식이고 32bit 프로세스일 경우 시스템 DLL이 wow64.dll ProcessInit 함수 중에 DLL로드가 되기 때문에 SystemDll이 로드가 다 될때까지 기다려야 합니다.
#if defined(INJ_CONFIG_SUPPORTS_WOW64)
if (InjIsWindows7 &&
InjectionInfo->Method == InjMethodThunk &&
PsGetProcessWow64Process(PsGetCurrentProcess()))
{
//
// On Windows 7, if we're injecting DLL into Wow64 process using
// the "thunk method", we have additionaly postpone the load after
// these system DLLs.
//
// This is because on Windows 7, these DLLs are loaded as part of
// the wow64!ProcessInit routine, therefore the Wow64 subsystem
// is not fully initialized to execute our injected Wow64ApcRoutine.
//
UNICODE_STRING System32Kernel32Path = RTL_CONSTANT_STRING(L"\\System32\\kernel32.dll");
UNICODE_STRING SysWOW64Kernel32Path = RTL_CONSTANT_STRING(L"\\SysWOW64\\kernel32.dll");
UNICODE_STRING System32User32Path = RTL_CONSTANT_STRING(L"\\System32\\user32.dll");
UNICODE_STRING SysWOW64User32Path = RTL_CONSTANT_STRING(L"\\SysWOW64\\user32.dll");
if (RtlxSuffixUnicodeString(&System32Kernel32Path, FullImageName, TRUE) ||
RtlxSuffixUnicodeString(&SysWOW64Kernel32Path, FullImageName, TRUE) ||
RtlxSuffixUnicodeString(&System32User32Path, FullImageName, TRUE) ||
RtlxSuffixUnicodeString(&SysWOW64User32Path, FullImageName, TRUE))
{
InjDbgPrint("[injlib]: Postponing injection (%wZ)\n", FullImageName);
return;
}
}
C++
복사
InjpQueueApc
모든 예외처리가 끝났으면 로그 출력 후 InjpQueueApc 함수를 호출합니다.
#if defined(INJ_CONFIG_SUPPORTS_WOW64)
InjDbgPrint("[injlib]: Injecting (PID: %u, Wow64: %s, Name: '%s')\n",
(ULONG)(ULONG_PTR)ProcessId,
PsGetProcessWow64Process(PsGetCurrentProcess()) ? "TRUE" : "FALSE",
PsGetProcessImageFileName(PsGetCurrentProcess()));
#else
InjDbgPrint("[injlib]: Injecting (PID: %u, Name: '%s')\n",
(ULONG)(ULONG_PTR)ProcessId,
PsGetProcessImageFileName(PsGetCurrentProcess()));
#endif
InjpQueueApc(KernelMode,
&InjpInjectApcNormalRoutine,
InjectionInfo,
NULL,
NULL);
C++
복사
- Injlib!InjpQueueApc
APC에 쓰일 메모리를 할당하고 Init 한 다음 Queue에 넣습니다.
NTSTATUS
NTAPI
InjpQueueApc(
_In_ KPROCESSOR_MODE ApcMode,
_In_ PKNORMAL_ROUTINE NormalRoutine,
_In_ PVOID NormalContext,
_In_ PVOID SystemArgument1,
_In_ PVOID SystemArgument2
)
{
//
// Allocate memory for the KAPC structure.
//
PKAPC Apc = ExAllocatePoolWithTag(NonPagedPoolNx,
sizeof(KAPC),
INJ_MEMORY_TAG);
if (!Apc)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// Initialize and queue the APC.
//
KeInitializeApc(Apc, // Apc
PsGetCurrentThread(), // Thread
OriginalApcEnvironment, // Environment
&InjpInjectApcKernelRoutine, // KernelRoutine
NULL, // RundownRoutine
NormalRoutine, // NormalRoutine
ApcMode, // ApcMode
NormalContext); // NormalContext
BOOLEAN Inserted = KeInsertQueueApc(Apc, // Apc
SystemArgument1, // SystemArgument1
SystemArgument2, // SystemArgument2
0); // Increment
if (!Inserted)
{
ExFreePoolWithTag(Apc, INJ_MEMORY_TAG);
return STATUS_UNSUCCESSFUL;
}
return STATUS_SUCCESS;
}
C++
복사
Insert된 APC가 정상적으로 호출되면 위에서 Normal Routine으로 전달받았던 InjpInjectApcNormalRoutine 함수가 호출됩니다.
— InjLib!InjpInjectApcNormalRoutine
VOID
NTAPI
InjpInjectApcNormalRoutine(
_In_ PVOID NormalContext,
_In_ PVOID SystemArgument1,
_In_ PVOID SystemArgument2
)
{
UNREFERENCED_PARAMETER(SystemArgument1);
UNREFERENCED_PARAMETER(SystemArgument2);
PINJ_INJECTION_INFO InjectionInfo = NormalContext;
InjInject(InjectionInfo);
}
C++
복사
—- InjLib!InjInject
Injection에 사용할 Section(Shared Memory)를 생성하고 Archtecture 분류 한 다음 각 Archtecture에 맞는 Inject 함수를 호출합니다.
NTSTATUS
NTAPI
InjInject(
_In_ PINJ_INJECTION_INFO InjectionInfo
)
{
NTSTATUS Status;
//
// Create memory space for injection-specific data,
// such as path to the to-be-injected DLL. Memory
// of this section will be eventually mapped to the
// injected process.
//
// Note that this memory is created using sections
// instead of ZwAllocateVirtualMemory, mainly because
// function ZwProtectVirtualMemory is not exported
// by ntoskrnl.exe until Windows 8.1. In case of
// sections, the effect of memory protection change
// is achieved by remaping the section with different
// protection type.
//
OBJECT_ATTRIBUTES ObjectAttributes;
InitializeObjectAttributes(&ObjectAttributes,
NULL,
OBJ_KERNEL_HANDLE,
NULL,
NULL);
HANDLE SectionHandle;
SIZE_T SectionSize = PAGE_SIZE;
LARGE_INTEGER MaximumSize;
MaximumSize.QuadPart = SectionSize;
Status = ZwCreateSection(&SectionHandle,
GENERIC_READ | GENERIC_WRITE,
&ObjectAttributes,
&MaximumSize,
PAGE_EXECUTE_READWRITE,
SEC_COMMIT,
NULL);
if (!NT_SUCCESS(Status))
{
return Status;
}
INJ_ARCHITECTURE Architecture = InjArchitectureMax;
if (InjectionInfo->Method == InjMethodThunk ||
InjectionInfo->Method == InjMethodWow64LogReparse)
{
#if defined(_M_IX86)
Architecture = InjArchitectureX86;
#elif defined(_M_AMD64)
Architecture = PsGetProcessWow64Process(PsGetCurrentProcess())
? InjArchitectureX86
: InjArchitectureX64;
#elif defined(_M_ARM64)
switch (PsWow64GetProcessMachine(PsGetCurrentProcess()))
{
case IMAGE_FILE_MACHINE_I386:
Architecture = InjArchitectureX86;
break;
case IMAGE_FILE_MACHINE_ARMNT:
Architecture = InjArchitectureARM32;
break;
case IMAGE_FILE_MACHINE_ARM64:
Architecture = InjArchitectureARM64;
break;
}
#endif
NT_ASSERT(Architecture != InjArchitectureMax);
InjpInject(InjectionInfo,
Architecture,
SectionHandle,
SectionSize);
}
#if defined(_M_AMD64)
else if (InjectionInfo->Method == InjMethodThunkless)
{
Architecture = InjArchitectureX64;
InjpInjectX64NoThunk(InjectionInfo,
Architecture,
SectionHandle,
SectionSize);
}
#endif
ZwClose(SectionHandle);
if (NT_SUCCESS(Status) && InjectionInfo->ForceUserApc)
{
//
// Sets CurrentThread->ApcState.UserApcPending to TRUE.
// This causes the queued user APC to be triggered immediately
// on next transition of this thread to the user-mode.
//
KeTestAlertThread(UserMode);
}
return Status;
}
C++
복사
—— InjLib!InjpInjectX64NoThunk
위에서 할당했던 Section을 사용할 수 있게 MapView로 SectionMemoryAddress에 할당 한 후 DllPath로 사용하고 InjpQueueApc를 이용해 LdrLoadDll에 DllPath를 인자로 주어 호출합니다.
NTSTATUS
NTAPI
InjpInjectX64NoThunk(
_In_ PINJ_INJECTION_INFO InjectionInfo,
_In_ INJ_ARCHITECTURE Architecture,
_In_ HANDLE SectionHandle,
_In_ SIZE_T SectionSize
)
{
NT_ASSERT(InjectionInfo->LdrLoadDllRoutineAddress);
NT_ASSERT(Architecture == InjArchitectureX64);
UNREFERENCED_PARAMETER(Architecture);
NTSTATUS Status;
PVOID SectionMemoryAddress = NULL;
Status = ZwMapViewOfSection(SectionHandle,
ZwCurrentProcess(),
&SectionMemoryAddress,
0,
PAGE_SIZE,
NULL,
&SectionSize,
ViewUnmap,
0,
PAGE_READWRITE);
if (!NT_SUCCESS(Status))
{
goto Exit;
}
//
// Create the UNICODE_STRING structure and fill out the
// full path of the DLL.
//
PUNICODE_STRING DllPath = (PUNICODE_STRING)(SectionMemoryAddress);
PWCHAR DllPathBuffer = (PWCHAR)((PUCHAR)DllPath + sizeof(UNICODE_STRING));
RtlCopyMemory(DllPathBuffer,
InjDllPath[Architecture].Buffer,
InjDllPath[Architecture].Length);
RtlInitUnicodeString(DllPath, DllPathBuffer);
Status = InjpQueueApc(UserMode,
(PKNORMAL_ROUTINE)(ULONG_PTR)InjectionInfo->LdrLoadDllRoutineAddress,
NULL, // Translates to 1st param. of LdrLoadDll (SearchPath)
NULL, // Translates to 2nd param. of LdrLoadDll (DllCharacteristics)
DllPath); // Translates to 3rd param. of LdrLoadDll (DllName)
//
// 4th param. of LdrLoadDll (BaseAddress) is actually an output parameter.
//
// When control is transferred to the KiUserApcDispatcher routine of the
// 64-bit ntdll.dll, the RSP points to the CONTEXT structure which might
// be eventually provided to the ZwContinue function (in case this APC
// dispatch will be routed to the Wow64 subsystem).
//
// Also, the value of the RSP register is moved to the R9 register before
// calling the KiUserCallForwarder function. The KiUserCallForwarder
// function actually passes this value of the R9 register down to the
// NormalRoutine as a "hidden 4th parameter".
//
// Because LdrLoadDll writes to the provided address, it'll actually
// result in overwrite of the CONTEXT.P1Home field (the first field of
// the CONTEXT structure).
//
// Luckily for us, this field is only used in the very early stage of
// the APC dispatch and can be overwritten without causing any troubles.
//
// For excellent explanation, see:
// https://www.sentinelone.com/blog/deep-hooks-monitoring-native-execution-wow64-applications-part-2
//
Exit:
return Status;
}
C++
복사