Search

injdrv

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

Injlib의 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++
복사