Reversing/Kernel

[ Window Device Driver ] 6. DKOM Process Hide 탐지

DKOM - ActiveProcessLinks 조작 Process Hide

  • 커널 오브젝트에 직접 접근하는 DKOM 기법을 이용
  • EPROCESS 구조체 ActiveProcessLinks의 FLINK와 BLINK를 변조
  • 연결리스트 형태로 존재하는 프로세스 목록의 연결을 끊어버려 특정 프로세스를 감춤.
  • ZwQuerySystemInformaion() 함수를 이용해 프로세스 데이터를 받아오는 경우 프로세스를 찾을 수 없게 된다.
  • 따라서 ActiveProcessLinks가 아닌 다른 데이터를 가지고 프로세스 리스트를 받아와야한다.

HandleTableList

  • EPROCESS 구조체 속 ObjectTable 필드 내부에 존재함
    • 해당 필드는 HANDLE_TABLE 구조를 가짐
  • ActiveProcessLinks 와 동일하게 Flink와 Blink를 가진 연결 리스트 구조
  • ActiveProcessLinks 목록에서 빠진 프로세스를 해당 리스트를 통해 찾아볼 수 있는지 확인
    •  0: kd> dt_EPROCESS ffff9b8f9d3a0340
      nt!_EPROCESS
         +0x000 Pcb              : _KPROCESS
         +0x438 ProcessLock      : _EX_PUSH_LOCK
         +0x440 UniqueProcessId  : 0x00000000`00001160 Void
         +0x448 ActiveProcessLinks : _LIST_ENTRY [ 0xffff9b8f`9d3d9788 - 0xffff9b8f`9906b4c8 ]​
       
    • 숨겨진 프로세스 notepad.exe 의 Flink와 Blink가 가리키는 프로세스들은 각기 서로의 ActiveProcesslinks 주소를 가리키고 있는 상황
      • 0: kd> dt_EPROCESS ffff9b8f9d3d9788-448
        nt!_EPROCESS
           +0x000 Pcb              : _KPROCESS
           +0x438 ProcessLock      : _EX_PUSH_LOCK
           +0x440 UniqueProcessId  : 0x00000000`00000af8 Void
           +0x448 ActiveProcessLinks : _LIST_ENTRY [ 0xffff9b8f`9b3a54c8 - 0xffff9b8f`9906b4c8 ]
      • 0: kd> dt_EPROCESS ffff9b8f9906b4c8-448
        nt!_EPROCESS
           +0x000 Pcb              : _KPROCESS
           +0x438 ProcessLock      : _EX_PUSH_LOCK
           +0x440 UniqueProcessId  : 0x00000000`000016bc Void
           +0x448 ActiveProcessLinks : _LIST_ENTRY [ 0xffff9b8f`9d3d9788 - 0xffff9b8f`9941d4c8 ]
    • 이 상태에서 각 EPROCESS의 ObjectTable (+0x570) - HandleTableList( +0x018) 을 확인해보자.
      • PID : 1160 - notepad.exe
      • 0: kd> dx -id 0,0,ffff9b8f95c90040 -r1 (*((ntkrnlmp!_LIST_ENTRY *)0xffff8a00a13065d8))
        (*((ntkrnlmp!_LIST_ENTRY *)0xffff8a00a13065d8))                 [Type: _LIST_ENTRY]
            [+0x000] Flink            : 0xffff8a00a1307298 [Type: _LIST_ENTRY *]
            [+0x008] Blink            : 0xffff8a009f6874d8 [Type: _LIST_ENTRY *]
      • PID: af8
      • 0: kd> dx -id 0,0,ffff9b8f95c90040 -r1 (*((ntkrnlmp!_LIST_ENTRY *)0xffff8a00a1307298))
        (*((ntkrnlmp!_LIST_ENTRY *)0xffff8a00a1307298))                 [Type: _LIST_ENTRY]
            [+0x000] Flink            : 0xffff8a00a131b158 [Type: _LIST_ENTRY *]
            [+0x008] Blink            : 0xffff8a00a13065d8 [Type: _LIST_ENTRY *]
      • PID : 16bc
      • 0: kd> dx -id 0,0,ffff9b8f95c90040 -r1 (*((ntkrnlmp!_LIST_ENTRY *)0xffff8a009f6874d8))
        (*((ntkrnlmp!_LIST_ENTRY *)0xffff8a009f6874d8))                 [Type: _LIST_ENTRY]
            [+0x000] Flink            : 0xffff8a00a13065d8 [Type: _LIST_ENTRY *]
            [+0x008] Blink            : 0xffff8a00a8dcced8 [Type: _LIST_ENTRY *]
  • 가리키는 구조가 그대로 남아있는걸 확인가능
  • 즉, ActiveProcessList 가 망가지더라도 ObjectTable - HandleTableList 를 참조할 수 있으면 프로세스 리스트를 구성할 수 있다.

원리

  • EPROCESS 구조체에서 ObjectTable - HandleTableList 에 접근해야함.
    • 구조체 정보나 오프셋을 사용하지 않으려면 다른 함수들을 통해 근처에 멤버들에 접근할 수 있어야한다. → 커널 빌드 버젼벌로 오프셋 바뀌는거 확인됨
//Windows 10 | 2016 1909 19H2 (November 2019 Update) x64
struct _EPROCESS_QUOTA_BLOCK* QuotaBlock;                               //0x410
struct _HANDLE_TABLE* ObjectTable;                                      //0x418
VOID* DebugPort;                                                        //0x420
//Windows 10 | 2016 2004 20H1 (May 2020 Update) x64
struct _EPROCESS_QUOTA_BLOCK* QuotaBlock;                               //0x568
struct _HANDLE_TABLE* ObjectTable;                                      //0x570
VOID* DebugPort;                                                        //0x578
//in WINDBG 
+0x568 QuotaBlock       : 0xfffff805`5f653940 _EPROCESS_QUOTA_BLOCK
+0x570 ObjectTable      : 0xffff8a00`98647e40 _HANDLE_TABLE
+0x578 DebugPort        : (null)
  • ***ObjectTable***인근에 QuotaBlockDebugPort 있는것 확인됨.
    • 둘 중 하나 구해올 수 있으면 -8 로 오프셋 접근 가능.
    • PsGetProcessDebugPort() 함수를 이용해 ***DebugPort***에 접근 가능.
    PsGetProcessDebugPort 
    48 8B 81 **78 05** 00 00                    mov     rax, [rcx+578h]
    C3                                      retn
    
    • MmGetSystemRoutineAddress() 함수사용하여 해당 함수의 시작주소 받아오면 +3 바이트 부터 2바이트 만큼 헥스코드에 존재하는 오프셋 사용 가능.
    • 찾아온 오프셋에서 8만큼 뺀 값을 EPROCESS 구조체 시작주소에 더하면 ***ObjectTable***의 절대주소가 나옴.
      • Windows 8 | 2012 RTM x64 버전부터 적용할 수 있음. windows7 이하부턴 사용 불가
      • HANDLE_TABLE 구조체 형태
      struct _HANDLE_TABLE
      {
          ULONG NextHandleNeedingPool;                                            //0x0
          LONG ExtraInfoPages;                                                    //0x4
          volatile ULONGLONG TableCode;                                           //0x8
          struct _EPROCESS* QuotaProcess;                                         //0x10
          struct _LIST_ENTRY **HandleTableList**;                                     //0x18
          ULONG UniqueProcessId;                                                  //0x28
          union
          {
              ULONG Flags;                                                        //0x2c
              struct
              {
                  UCHAR StrictFIFO:1;                                             //0x2c
                  UCHAR EnableHandleExceptions:1;                                 //0x2c
                  UCHAR Rundown:1;                                                //0x2c
                  UCHAR Duplicated:1;                                             //0x2c
                  UCHAR RaiseUMExceptionOnInvalidHandleClose:1;                   //0x2c
              };
          };
          struct _EX_PUSH_LOCK HandleContentionEvent;                             //0x30
          struct _EX_PUSH_LOCK HandleTableLock;                                   //0x38
          union
          {
              struct _HANDLE_TABLE_FREE_LIST FreeLists[1];                        //0x40
              struct
              {
                  UCHAR ActualEntry[32];                                          //0x40
                  struct _HANDLE_TRACE_DEBUG_INFO* DebugInfo;                     //0x60
              };
          };
      };
      
    • ObjectTable 포인터 따라가서 +0x18 위치에 HandleTableList 존재
    • _LIST_ENTRY 구조체 모양을 그대로 가져가므로, ActiveProcesslinks 를 사용하는것과 크게 다르지 않다.
    • HandleTableList +0x10 에 UniqueProcessId 존재

구상

  • PID 기준으로 EPROCESS를 가져올 수 있음.
    • 이전 DKOM 을 이용한 processhide를 만들 때 이 방식 이용
      • pid → 해당 프로세스의 EPROCESS → ActiveProcesslinks 조작
    • 이번에도 EPROCSSS 이용해 HandleTableList 가져올 수 있음.
      • pid → 해당 프로세스의 EPROCESS→ ObjectTable → HandleTableList 참조
    • pid 하나만 받아서 한 EPROCESS 구조체의 ActiveProcesslinksHandleTableList 를 모두 가져오게 동작을 만들 수 있음.
  • 각 프로세스의 ActiveProcesslinksHandleTableList 의 모든 flink, blink 를 이용
    • 각 링크를 따라가서 찾은 프로세스의 PID가 동일한지 비교
    • 달라진 값이 존재하는지 찾아 알려주기
    • PID 전달받아 저 값 비교하는 함수 만들기
    • 프로세스 목록 순회하면서 pid 계속 넘겨주면서 비교
  • PsGetProcessDebugPort() 함수 시작주소 가져와야함.
    • DebugPort 오프셋 가져오기 위해 함수의 시작주소 참조
    • MmGetSystemRoutineAddress() 함수 사용하여 시작주소 받아옴.
    • 찾아온 시작주소 +3 바이트 부터 2바이트 만큼 헥스코드 받아옴
  • ObjectTable 참조
    • HANDLE_TABLE 구조체
    • EPROCESS 시작주소 + Debugport 오프셋 - 8
    • 포인터 따라가서 +0x18위치의 HandleTableList 찾아와야함.
    • 링크 따라가서 찾은 프로세스의 PID도 구해올 수 있어야함.
  • 링크연결 결과가 다른 프로세스를 찾은 다음, 어떤 프로세스가 숨겨졌는지도 찾아서 알려줄 수 있을까?

실습

  • 찾아온 PsGetProcessDebugPort () 함수 주소 검증
    //dbg message
    ===== PsGetProcessDebugPort ADDRESS : **FFFFF8055ED58050** ======
    
  • //dbg message
    ===== PsGetProcessDebugPort ADDRESS : FFFFF8055ED58050 ======
  • pid 기준으로 찾아온 ObjectTable 의 주소가 정확한지 검증
    • 목표 프로세스 PID : 4276(0x10B4)
    • //PsGetProcessDebugPort 함수 시작주소부터 3바이트 위치에서 2바이트 가져옴
      //PEPROCESS 시작주소로부터 해당 2바이트만큼 오프셋 떨어진 곳에 DebugPort  주소 들어있음 
      offsetPointer = (PVOID*)((ULONGLONG)PsGetProcessDebugPort + 3);
      //DebugPort  로부터 8바이트 떨어진 위치가 ObjectTable 
      offset = ( (USHORT)*offsetPointer - 8 );
      ObjectTable = (PVOID *)((ULONGLONG)targetProcess + offset);
      
      DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "===== DebugPort Offset : %lx ====== \n", (USHORT)*(offsetPointer));
      DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "===== Offset Result : %lx ====== \n", (offset));
      DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "===== ObjectTable POINTER : %p ====== \n", (ObjectTable));
      DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "===== ObjectTable ADDRESS : %p ====== \n", *(ObjectTable));
    • //WinDBG
      0: kd> !process 10b4 0
      Searching for Process with Cid == 10b4
      PROCESS ffff9b8f99df9080
      ~
      +0x570 ObjectTable      : **0xffff8a00`9daae100** _HANDLE_TABLE
      
       
    • //dbg message
      ===== DebugPort Offset : 578 ====== 
      ===== Offset Result : 570 ====== 
      ===== ObjectTable POINTER : FFFF9B8F99DF95F0 ====== 
      ===== ObjectTable ADDRESS : **FFFF8A009DAAE100** ======
      
  • 찾아온 HandleTableList 주소 검증
  • //ObjectTable 구조체 + 0x18 위치에 HandleTableList 존재
    retEntry = (PLIST_ENTRY)((ULONGLONG)(*ObjectTable) + 0x18);
    DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "===== HandleTableList ADDRESS : %p ====== \n", retEntry);
  • //WinDBG
    1: kd> dx -id 0,0,ffff9b8f9d166080 -r1 (*((ntkrnlmp!_LIST_ENTRY *)0xffff8a009daae118))
    (*((ntkrnlmp!_LIST_ENTRY *)**0xffff8a009daae118**))                 [Type: _LIST_ENTRY]
    [+0x000] Flink            : 0xffff8a009dac60d8 [Type: _LIST_ENTRY *]
    [+0x008] Blink            : 0xffff8a009dab3e98 [Type: _LIST_ENTRY *]
    
     
  • //dbg message 
    ===== HandleTableList ADDRESS : **FFFF8A009DAAE118** ======
    
  • HandleTableList 의 링크 따라가서 PID 찾기
  • // 각 링크 따라간 TableObject +0x10 -> PID 
    BackwardProcess = (PVOID*)((ULONGLONG)(HandleTableList->Blink) + 0x10);
    FrontProcess = (PVOID*) ((ULONGLONG)(HandleTableList->Flink) + 0x10 );
    
    DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "===== BackwardProcess POINTER : %p ====== \n", (BackwardProcess));
    DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "===== FrontProcess POINTER : %p ====== \n", (FrontProcess));
    
    DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "===== BackwardProcess PID : %lx ====== \n", *BackwardProcess);
    DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "===== FrontProcess PID : %lx ====== \n", *FrontProcess);
  • //WinDBG
    0: kd> dq FFFF8A009DAB3E98
    ffff8a00`9dab3e98  **ffff8a00`9daae118** ffff8a00`9dab2318
    **ffff8a00`9dab3ea8  00000002`00000fd8** 00000000`00000000
    0: kd> dq FFFF8A009DAC60D8
    ffff8a00`9dac60d8  ffff8a00`9d620298 **ffff8a00`9daae118**
    **ffff8a00`9dac60e8  00000000`000011c4** 00000000`00000000
    
    //dbg message 
    ===== ObjectTable ADDRESS : FFFF8A009DAAE100 ====== 
    ===== HandleTableList ADDRESS : FFFF8A009DAAE118 ====== 
    ===== BackwardProcess POINTER : **FFFF8A009DAB3EA8** ====== 
    ===== FrontProcess POINTER : **FFFF8A009DAC60E8** ====== 
    ===== BackwardProcess PID : **fd8** ====== 
    ===== FrontProcess PID : **11c4** ======
    
    • 각기 링크 따라간 주소 +0x10 위치에서 해당 프로세스의 ***UniqueProcessId***를 찾을 수 있음.
  • ***ActiveProcesslinks***링크 따라가서 PID 찾기 + HandleTableList PID 찾은 결과와 비교
    //dbg message 
    ===== BackwardProcess PID : fd8 ====== 
    ===== FrontProcess PID : 11c4 ====== 
    ===== BackwardProcess PID : **1058** ====== 
    ===== FrontProcess PID : **11c4** ======
    
    • 여러가지 이유로 이미 숨겨진 프로세스와 연결되어 있는경우에 불일치 발견.
    • 0: kd> !process 1058 0
      Searching for Process with Cid == 1058
      PROCESS ffff9b8f99fdd340
      SessionId: 1  Cid: 1058    Peb: d1fe63000  ParentCid: 0258
      DirBase: 2103b9002  ObjectTable: 00000000  HandleCount:   0.
      Image: userinit.exe
    • 0: kd> dq 0xffff9b8f99df6708 -0x8 L 1
      ffff9b8f`99df6700  00000000`**00000fd8**
      0: kd> dq 0xffff9b8f99df94c8 -0x8 L 1
      ffff9b8f`99df94c0  00000000`**000010b4**
      
       
    • ===== DRIVER MESSAGE : PNAME - svchost.exe | PID - fd8 ====== 
      ===== DRIVER MESSAGE : PNAME - explorer.exe | PID - 10b4 ======
      
      • 두 프로세스 사이에 숨겨져있는걸로 확인됨.
      • fd8 - 1058 - 10b4 - 11c4
      • 이 경우는 ActiveProcesslinks 에서는 찾았지만 HandleTableList 에서는 찾지 못한 경우.
    • 새로 생성한 프로세스의 경우는 제대로 찾아오는거 확인함.
      • ===== BackwardProcess PID : 1fc0 ====== 
        ===== FrontProcess PID : 614 ====== 
        ===== BackwardProcess PID : 1fc0 ====== 
        ===== FrontProcess PID : 614 ======
  • 따라서, 두 링크를 참조해 찾아온 PID를 각기 비교하면 서로 다른 방식으로 숨겨진 프로세스들을 전부 찾아낼 수 도 있다.
    • //링크 PID 비교 - 비교시 형변환까지 한 다음 비교해주는게 좋음.  	
      if ((ULONG) *(BackwardProcess[0]) != (ULONG) *(BackwardProcess[1]) ) {
      	DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "===== HandleList Blink : %lx ====== \n", (ULONG)*BackwardProcess[0]);
      	DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "===== ProcessLink Blink : %lx ====== \n", (ULONG)*BackwardProcess[1]);
      	retStatus = 1;
      }
      
      if ((ULONG) *(FrontwardProcess[0]) != (ULONG) *(FrontwardProcess[1]) ) {
      	DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "===== HandleList Flink : %lx ====== \n", (ULONG)*FrontwardProcess[0]);
      	DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "===== ProcessLink Flink : %lx ====== \n", (ULONG)*FrontwardProcess[1]);
      	retStatus = 1;
      }
    • //Flink PID  불일치
      ===== HandleList Flink : 1160 ====== 
      ===== ProcessLink Flink : af8 ======
      //Blink PID 불일치 
      ===== HandleList Blink : fd8 ====== 
      ===== ProcessLink Blink : 1058 ======
      
  • 이제 전체 프로세스 목록 순회할 때 해당 루틴을 같이 실행할 때 포함해주자.
    • Blink에 대한 비교시 어떤 링크가 변조되었는지 알려줄 수 있음.
      • 프로세스 사이사이 어떤 프로세스가 숨겨져 있는지 확실하게 보기 위함.
      • 순회시 이전 프로세스의 PID를 갖고있고, 이를 함수 인자로 함께 넘겨주자.
      • backward 프로세스 PID 비교시 불일치하면 이전 프로세스의 PID를 이용해 둘 중 어떤 링크가 변조되었는지 판정
      • //링크 PID 비교 - 비교시 형변환까지 한 다음 비교해주는게 좋음.  	
        if ((ULONG) *(BackwardProcess[0]) != (ULONG) *(BackwardProcess[1]) ) {
        	//이전 프로세스 PID 까지 비교하여 어떤 링크가 변조되었는지 체크 
        	if ((ULONG) * (BackwardProcess[0]) != BackwardPID){
        		DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "**** HandleList Backward PID Mismatch: %lx **** \n", (ULONG)*BackwardProcess[0]);
        	}
        	else {
        		DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "****  ProcessLink Backward PID Mismatch: %lx **** \n", (ULONG)*BackwardProcess[1]);
        	}
        	retStatus = 1;
        }
    • 혹은, 다음 프로세스까지 얻어온 다음에 세 PID를 한꺼번에 인자로 넘겨볼 수도 있긴하다.
  • 프로세스 숨긴 후 탐지 확인
    • ===== DRIVER MESSAGE : PNAME - notepad.exe | PID - 1168 ====== 
      ===== DRIVER MESSAGE : PNAME - notepad.exe | PID - 18b8 ====== 
      ===== DRIVER MESSAGE : PNAME - notepad.exe | PID - 141c ======
    • pid 18b8 의 NOTEPAD.EXE를 숨김
      • ===== DRIVER MESSAGE : PNAME - notepad.exe | PID - 1168 ====== 
        **** ===== Process Link Currupted : 1168 ===== **** 
        ===== DRIVER MESSAGE : PNAME - notepad.exe | PID - 141c ====== 
        **** HandleList Backward PID Mismatch: 18b8 **** 
        **** ===== Process Link Currupted : 141c ===== ****
      • 숨겨져있는 18b8와 어떤 링크가 변조되었는지 찾아낼 수 있었음.

구현 완료

 

→ object handle 사용해서 다시 해보기


Github

https://github.com/synod2/Kernel_Hooking/tree/main/Window Device Driver/MyDriver5


참고 자료

https://ccibomb.tistory.com/339 - DKOM 탐지 기법

 

DKOM 탐지 기법

○ 목차 I. What is “DKOM” ? 가. DKOM이란? 나. DKOM의 장단점 다. DKOM의 기능 II. DKOM을 이용한 은닉 가. 원리 나. 실습 (notepad.exe 은닉) III. DKOM 탐지 기법 가. 다른 Linked list 이용하기 1. 원리 2..

ccibomb.tistory.com

https://shhoya.github.io/windows_hwndobject.html - HandleTableList

 

Windows Handle Table & Object | Shh0ya Security Lab

Windows Handle Table & Object [0x00] Overview 기본적으로 핸들에 대한 개념은 위키 내 잘 서술되어 있습니다. Windows 커널에서 핸들 테이블과 핸들, 오브젝트의 연관성 및 알고리즘에 대한 내용입니다. Window

shhoya.github.io