Reversing/Kernel

[ Window Device Driver ] 7. Windows10 환경에서 PspCidTable 이용한 Process Listing (Traverse) - HANDLE_TABLE_ENTRY 구조

Process Listing

Handle?

  • 자원에 대한 '추상적인' 참조
  • 어플리케이션이 다른 시스템에서 관리되는 메모리 블록이나 객체를 참조할 때 사용
  • 파일 디스크립터, 파이프, 소켓, DB 연결 소켓, PID 등등

Objcet Handle

  • 커널 오브젝트에 접근하기 위해 만들어진 핸들
  • 커널 리소스에 직접 접근하는 대신, 핸들을 통해 간접적으로 참조할 수 있음.
  • 커널 오브젝트가 생성되면 생성된 오브젝트를 가리키기 위해 오브젝트 핸들이 반환됨.
  • 프로세스가 초기화 되었을 때 각 프로세스별로 사용할 수 있는 오브젝트 핸들 테이블이 할당되어 테이블의 형태로 오브젝트 핸들을 관리.
  • 프로세스마다 독립적으로 관리됨.

Kernel Object Handle Table

  • 커널 오브젝트의 핸들 정보를 저장하는 테이블
  • 핸들을 통해 커널 오브젝트를 찾아갈 수 있게 정보를 저장함.
  • 부모-자식 프로세스의 경우 상속도 됨
  • EPROCESS 구조 내 ObjectTable 필드가 HANDLE_TABLE 구조로 이루어져 있음.
//Windows 10 | 2016 2104 21H1 (May 2021 Update) x64
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
        };
    };
};

PspCidTable

  • 모든 프로세스와 스레드의 CID를 관리하는 핸들 테이블
  • 현재 시스템의 모든 프로세스와 스레드의 cid 관련된 정보가 들어있다
  • PspReferenceCidTableEntry 함수 내부에서 PspCidTable 참조해 오는 것 확인됨.
    • PspReferenceCidTableEntry+16  48 83 EC 40            sub     rsp, 40h
      PspReferenceCidTableEntry+1a  48 8B 05 9F 60 69 00   mov     rax, cs:PspCidTable
      PspReferenceCidTableEntry+21  0F B6 EA               movzx   ebp, dl​
    • PspReferenceCidTableEntry+1a 에서 오프셋이 나온다.
    • RIP+OFFSET 으로 값을 찾아올 위치의 주소값이 정해지므로, (PspReferenceCidTableEntry+21) + 0x69609f 의 결과가 ****커널 메모리상 PspCidTable의 실제 주소값이 된다.
    • 즉, +0x1d 위치에서 4바이트를 가져와 오프셋을 구한 다음 PspReferenceCidTableEntry+21 에 더하면 된다.
    • //WINDBG 
      nt!PspReferenceCidTableEntry+0x1a:
      fffff805`5f06652a 488b059f606900  mov     rax,qword ptr [nt!PspCidTable (fffff805`5f6fc5d0)]
      fffff805`5f066531 0fb6ea          movzx   ebp,dl
  • 주소값은 이렇게 가져오는데, 그럼 이제 프로세스나 다른 핸들의 정보는 어떻게 뽑아내면 될까? Win7 용으로 만들어진 코드를 찾아 분석해본다.
    1. PspCidTable을 찾아오는 함수.
      • RtlInitUnicodeString(&sysRoutineName, L"PsLookupProcessByProcessId");
        PUCHAR pFun = (PUCHAR)MmGetSystemRoutineAddress(&sysRoutineName);
        if (pFun)
        {
            do
            {
                if(!MmIsAddressValid(pFun) || !MmIsAddressValid((PUCHAR)pFun + 6))
                {
                    return NULL;
                }
                if (*(PSHORT)pFun == 0X3D8B && *((PUCHAR)pFun + 6) == 0Xe8)
                {
                    unsigned int pspCidTable =  *((unsigned int *)((PUCHAR)pFun + 2));
                    DbgPrint("%x", pspCidTable);
                    return (void*)pspCidTable;
                    //break;
                }
      • PsLookupProcessByProcessId() 함수의 바이트코드에서 PspCidTable을 찾아오는걸로 보이는데, Windows7 시절에는 함수 내부에 주소값이 바로 박혀져 있었기 때문.
      • Windows8 이후부터는 PspReferenceCidTableEntry() 에서 반환받아 사용하므로, PspReferenceCidTableEntry()의 바이트코드에서 PspCidTable을 찾아오면 될듯.
    2. TableCode의 레이어 레벨 분리
      • PHANDLE_TABLE pspCidTable = *pPspCidTable;
        ULONG level = pspCidTable->TableCode & 3;  //Take 2 digits, draw the number of sentenced stem.
        PUCHAR tableBase = (PUCHAR)pspCidTable->TableCode - level;
        
        switch(level)
        {
            case 0:
            {
                 EnumLevel1Tabel((PUCHAR)tableBase);
            }
            break;
            case 1:
            {
                EnumLevel2Tabel((PULONG)tableBase);
            }
            break;
            case  2:
            {
                EnumLevel3Tabel((PULONG)tableBase);
            }
        
        }
      • PspCidTableHANDLE_TABLE구조는 TableCode를 가지고 있는데, 여기서 레이어 레벨을 분리해낸다.
      • TableCode&3 을 연산하여 레이어 레벨을 얻어내고, 얻어낸 값만큼 빼면 table base가 나온다. 이 tablebase를 인자로 레이어에 따라 다른 함수를 돌리게 된다.
      • 0 : Layer Level1, 1: Level2, 2:Level3
    3. Level1의 경우
      • void EnumLevel1Tabel(PUCHAR pLevel1Table)
        {
            DbgPrint("Table1:%x\n", pLevel1Table);
            PHANDLE_TABLE_ENTRY phte = (PHANDLE_TABLE_ENTRY)pLevel1Table;
            for (ULONG i = 0; i < 512; i++)   //512 is the number of items, units: handle_table_entry
            {
                PEPROCESS pProcess = (PEPROCESS)phte->Object;
                POBJECT_TYPE objType;
                objType = *PsProcessType;
                pProcess = (PEPROCESS)((ULONG)pProcess & 0xfffffff8);  //I don't know what to do.
                
                if (pProcess != NULL && objType == ObGetObjectType(pProcess))
                {
                    DbgPrint("Process Name:%s\n", (PUCHAR)pProcess + 0x16c);
                }
                phte++;
            }
        }
      • 인자로 넘긴 주소값은 _HANDLE_TABLE_ENTRY 구조체의 주소값이 된다.
        • Windows7 때는 Object 멤버를 참조하여 AND 연산을 진행 , PEPROCESS 구조체 주소값을 뽑아낼 수 있었다.
        • Windows8 이후부터는 구조체 형태가 바뀌어 새로 분석이 필요함. 이는 후술
      • _HANDLE_TABLE_ENTRY 구조체의 포인터가 연달아 붙어있는 형태이기 때문에 ++ 연산을 진행해 다음 주소값으로 넘어가 연속적으로 구조체를 참조할 수 있어 전체 프로세스의 탐색이 가능하다.
    4. level2, 3의 경우
      • void EnumLevel2Tabel(PULONG pLevel2Table)
        {
        DbgPrint("EnumLevel2Tabel:%x\n", pLevel2Table);
        for (ULONG i = 0; i < 1024; i++) //1024 is the number of secondary tablets, unit: Phandle_Table_ENTRY
            {
            if (*pLevel2Table != 0)
            {
                EnumLevel1Tabel((PUCHAR)*pLevel2Table);
            }
            else
            {
                break;
            }
            pLevel2Table++;
        }
        }
        void EnumLevel3Tabel(PULONG pLevel3Table)
        {
            DbgPrint("EnumLevel3Tabel:%x\n", pLevel3Table);
            for (ULONG i = 0; i < 32; i++) //32 is the number of three-level entry, unit: Phandle_Table_ENTRY *
            {
                if (*pLevel3Table != 0)
                {
                    EnumLevel2Tabel((PULONG)*pLevel3Table);
                }
                else
                {
                    break;
                }
                pLevel3Table++;
            }
        }
      • level 2와 3의 경우 포인터를 따라가면 또 다른 테이블의 포인터가 있는 식으로 구성된다.
      • 따라서 단순히 포인터를 참조하고 하위 레이어의 동작을 수행하면 되는 단순한 동작.
      • 마찬가지로 포인터가 연달아 존재하므로 ++연산으로 다음번 포인터를 참조할 수 있음.
    5. win7 기준 코드이기때문에 , 해당 프로세스를 참고만 하고 구조는 다시 분석해서 적용이 필요할듯.

PspCidTable의 Table Base 연산

  • 이전에 찾은 구조는 Windows7 기준이기 때문에 지금과는 좀 차이점이 있다.
    • 일단 PspCidTable 를 찾아오는 과정부터 다름
      • win7 : PsLookupProcessByProcessId()
      • win8이후 : PsLookupProcessByProcessId() → PspReferenceCidTableEntry()
    • _HANDLE_TABLE_ENTRY 구조체가 다름
    • TableCode 멤버에서 레이어를 분리하고 포인터를 연산하는 방식이 다름
    • 따라서 새로 분석을 진행하여 현재 환경에 맞게 재구성이 필요함.
  • 공통적으로, PspCidTable의 하위 2바이트에서 Layer Level을 찾아내는 과정이 있다.
    • 00,01,11 일때 각각 레이어 레벨 1,2,3이 된다.
    • 하위 2바이트는 이를 위한 비트이므로 레이어를 찾은 다음 제외한다.
  • PspReferenceCidTableEntry() 함수 내에 PspCidTable을 연산하는 루틴이 들어있다.
    • mov     rdi, [rsi]
      sar     rdi, 10h
      and     rdi, 0FFFFFFFFFFFFFFF0h​
       
    • 0x10 바이트만큼 sar 연산 진행 후 0xFFFFFFFFFFFFFFF0과 AND 연산 진행
      • 오른쪽 산술 시프트 연산이기 때문에 , 새로 만들어지는 상위 비트들이 1로 채워진다.
      • 즉, 상위 4바이트를 F로 채우고 하위 4바이트는 버리는 것.

원리 - 디버깅

  • PspCidTable 로 부터 EPROCESS와 PID를 찾는 ****과정을 디버거에서 수동으로 따라가보자.

1. PspCidTable

1: kd> dq PspCidTable
fffff805`5f6fc5d0  ffff8a00`98664dc0 ffff9b8f`95cf5980
fffff805`5f6fc5e0  00000000`00000000 00010000`00000000
fffff805`5f6fc5f0  00000000`00001000 00000000`00000000
fffff805`5f6fc600  00000000`00000000 00009e09`00000000
fffff805`5f6fc610  00000000`00000000 00000000`00000000
fffff805`5f6fc620  00000000`00000000 00000000`00000000
fffff805`5f6fc630  0000002e`00000000 00000000`00000000
fffff805`5f6fc640  ffff9b8f`95cfe4e0 fffff805`5fa46000
  • PspCidTable 심볼이 있는 fffff805`5f6fc5d0 주소를 참조하면 테이블이 나오는데, 나오는 주소인 ffff8a00`98664dc0
1: kd> dt_HANDLE_TABLE ffff8a00`98664dc0
nt!_HANDLE_TABLE
   +0x000 NextHandleNeedingPool : 0x2000
   +0x004 ExtraInfoPages   : 0n0
   +0x008 TableCode        : 0xffff8a00`9c6bc001
   +0x010 QuotaProcess     : (null) 
   +0x018 HandleTableList  : _LIST_ENTRY [ 0xffff8a00`98664dd8 - 0xffff8a00`98664dd8 ]
  • ffff8a00`98664dc0 을 HANDLE_TABLE 구조로 정렬해보면 +0x8 위치의 TableCode 멤버를 찾을 수 있다.
  • 0xffff8a00`9c6bc001 의 마지막 2바이트는 1이므로, 2단계 레이어라고 볼 수 있다.
  • 0xffff8a00`9c6bc000 주소를 찾아가면 2단계 레이어 테이블을 찾을 수 있다.
1: kd> dq 0xffff8a00`9c6bc000
ffff8a00`9c6bc000  **ffff8a00`986b2000** ffff8a00`9c6bd000
ffff8a00`9c6bc010  ffff8a00`9ce55000 ffff8a00`9d6ac000
ffff8a00`9c6bc020  ffff8a00`9dbfe000 ffff8a00`9e9fb000
ffff8a00`9c6bc030  ffff8a00`9f1ff000 ffff8a00`a017c000
ffff8a00`9c6bc040  00000000`00000000 00000000`00000000

2. 레이어 테이블

  • 2단계와 3단계 레이어 테이블은 다음 단계 레이어 테이블의 주소를 담고 있다. 위 테이블의 경우 8개의 1단계 레이어 테이블 주소를 가지고 있는 셈.
  • x64기반 시스템에서 각 레이어 테이블의 크기는 0x1000(4096)bytes 이고, 1단계 테이블은 16바이트씩의 _HANDLE_TABLE_ENTRY 구조를 가진다 ****
    • 이 부분 정확하게 추가적인 분석이 필요함
  • 0xffff8a00`986b2000 주소를 찾아가면 1단계 레이어 테이블을 찾을 수 있다.
1: kd> dq ffff8a00`986b2000
ffff8a00`986b2000  00000000`00000000 00000000`00000000
ffff8a00`986b2010  **9b8f95c9`0040fdfd** 00000000`00000000
ffff8a00`986b2020  9b8f999b`e0800001 00000000`00000000
ffff8a00`986b2030  9b8f95cc`d080fff5 00000000`00000000
ffff8a00`986b2040  9b8f95d5`7080fff5 00000000`00000000
ffff8a00`986b2050  9b8f95d2`4080fff5 00000000`00000000
ffff8a00`986b2060  9b8f95d9`b080fff5 00000000`00000000
ffff8a00`986b2070  9b8f95c8`4580fff5 00000000`00000000

3. 주소값 연산

  • 1단계 레이어 테이블은 프로세스 혹은 PID에 따라 인덱싱되는데, 각 데이터는 EPROCESS 혹은 ETHREAD 구조체의 주소값이 연산 과정을 거친것이다.
  • (data >> 10) & 0xFFFFFFFFFFFFFFF0 연산을 거친 결과가 최종 주소값이 된다.
  • 레이어 테이블의 각 데이터에는 규칙이 있는데, PID 와 TID에 따라 레이어 테이블 순서와 행 순서가 결정된다 .
    • ( 2단계 레이어테이블 순서 * 0x400 ) + (1단계 레이어 테이블 행 순서 * 0x4 )
    • 9b8f95c9`0040fdfd 1번째 레이어 테이블, 1번째 행 → PID : 4
    1: kd> dt_EPROCESS FFFF9B8F95C90040 UniqueProcessId
    nt!_EPROCESS
       +0x440 UniqueProcessId : 0x00000000`00000004 Void
    
    • 9b8f999b`e0800001 → 1번째 레이어 테이블, 2번째 행 → TID : 8
    1: kd> dt_ETHREAD FFFF9B8F999BE080 Cid.UniqueThread
    nt!_ETHREAD
       +0x478 Cid              : 
          +0x008 UniqueThread     : 0x00000000`0000000**8** Void
    
    • 9b8f991b`3300ffe3 → 2번째 레이어 테이블, 1번째 행 → TID : 404
    1: kd> dt_ETHREAD FFFF9B8F991B3300 Cid.UniqueThread
    nt!_ETHREAD
       +0x478 Cid              : 
          +0x008 UniqueThread     : 0x00000000`00000**404** Void
    
    • 9b8f97ea`4080fffb → 3번째 레이어 테이블, 1번째 행 → TID : 804
    1: kd> dt_ETHREAD FFFF9B8F97EA4080 Cid.UniqueThread
    nt!_ETHREAD
       +0x478 Cid              : 
          +0x008 UniqueThread     : 0x00000000`00000**804** Void
    
    • PID가 92(0x5c)인 Registry 프로세스 → 1번째 레이어 테이블, 17번째 행
    1: kd> dq ffff8a00`986b2180
    ffff8a00`986b2180  9b8f95db`d0800001 00000000`00000000
    ffff8a00`986b2190  **9b8f95dc`e080fff5** 00000000`00000000
    ffff8a00`986b21a0  9b8f95cd`7080fff5 00000000`00000000
    
    1: kd> dt_EPROCESS FFFF9B8F95DAC080 UniqueProcessId
    nt!_EPROCESS
       +0x440 UniqueProcessId : 0x00000000`000000**5c** Void
    
  • 따라서, 레이어 테이블 전체를 순회하면 모든 프로세스와 모든 쓰레드의 정보를 받아올 수 있다.
    • 연산을 거친 주소값이 EPROCESS 구조체인지, ETHREAD 구조체인지만 판정할 수 있으면 된다.
    • 주소에 접근하지 않아도, 인덱스 번호를 통해 해당 주소가 가리킬 PID 혹은 TID를 미리 알 수 있다.
    • 따라서 , PID 를 통해 프로세스 정보를 가져오는 함수를 사용하면 연산을 거쳐 원래 주소에 접근할 필요 없이 프로세스를 가져올 수'는' 있다. 
    • 지금은 실습을 위해 연산을 통해 EPROCESS 주소를 가져와 데이터를 뽑아내는걸로 한다.
  • PsLookupProcessByProcessId() 함수의 동작과 유사하다.
    • PspReferenceCidTableEntry() 함수를 실행해 PspCidTable 에 접근한다.
    • 이후 전달받은 PID에 맞는 순서에 위치한 데이터를 가져오는것으로 보인다.
  • 이 경우 PspCidTable를 직접 참조하기 때문에 Link등의 변조에 영향을 받지 않아 DKOM등의 ProcessHide에 무관하게 프로세스를 찾아올 수 있게된다.

구상

  • PspReferenceCidTableEntry() 함수의 바이트 코드로부터 PspCidTable 가져옴
    • MmGetSystemRoutineAddress() 함수 이용해 실제 주소 가져오기
    • 함수 시작주소 +0x1d 위치에서 4바이트 오프셋 가져옴
    • 함수 시작주소 + 21 + 오프셋 = PspCidTable 주소
  • PspCidTable 포인터 참조하여 테이블에 접근
    • 포인터 따라간 다음 0x3과 비트연산, 레이어 테이블 단계 찾기
    • 레이어 테이블 단계에 따라 다른 동작 실행
  • 레이어 테이블에서 EPROCESS 값 디코딩
    • (data >> 0x10) & 0xFFFFFFFFFFFFFFF0
    • EPROCESS + 0x440에 PID 존재
    • EPROCESS or ETHREAD
      • ETHREAD인 경우 해당 값이 0xFFFFFFFC 이상임.
    • EPROCESS→PID 와 EPROCESS→ImageFileName 찾아 출력.

실습

  • PspCidTable의 실제 주소 구하는 코드 작성
    • MmGetSystemRoutineAddress 함수에서 PspReferenceCidTableEntry 직접 참조 불가.
    • PsLookupProcessByProcessId+24 E8 77 01 00 00    call    PspReferenceCidTableEntry
      PsLookupProcessByProcessId+29 48 8B D8          mov     rbx, rax​
       
      • 따라서 PsLookupProcessByProcessId먼저 찾아간 다음 바이트코드에서 오프셋 가져와 PsLookupProcessByProcessId+29 과 연산해야함.
      • PsLookupProcessByProcessId+25 에서 4바이트 만큼이 오프셋 
      //PspReferenceCidTableEntry 찾기위해 바이트코드에서 상대주소 오프셋 찾아옴.
      tmpAddr = (PVOID*)((ULONGLONG)PsLookupProcessByProcessId + 0x25);
      FuncOffset = (int)*tmpAddr;
      
      //PspReferenceCidTableEntry 주소 연산
      PspReferenceCidTableEntry = (PVOID)((ULONGLONG)PsLookupProcessByProcessId + 0x29 + FuncOffset);
      
      **** PsLookupProcessByProcessId : FFFFF80649466370 **** 
      **** Offset addr : FFFFF80649466395 **** 
      **** PspReferenceCidTableEntry addr : FFFFF80649466510 **** 
      **** offset : 0000000000000177 ****
      
      //WinDBG
      6: kd> x nt!PsLookupProcessByProcessId
      fffff806`49466370 nt!PsLookupProcessByProcessId (void)
      
      6: kd> dw FFFFF80649466395 
      fffff806`49466395  0177 0000
      
      6: kd> x nt!PspReferenceCidTableEntry
      fffff806`49466510 nt!PspReferenceCidTableEntry (void)
      
      • 주소값들 제대로 가져옴
    • PspReferenceCidTableEntry 함수 바이트코드에서 PspCidTable 오프셋 가져오기
      • PspReferenceCidTableEntry+1d 에서 4바이트 가져옴
      • PspReferenceCidTableEntry+21 과 오프셋 연산 ****
      //PspCidTable 상대주소 오프셋 찾아옴
      tmpAddr = (PVOID*)((ULONGLONG)PspReferenceCidTableEntry + 0x1d);
      FuncOffset = (int)*tmpAddr;
      
      PspCidTableOffset = (ULONGLONG) ((ULONGLONG)PspReferenceCidTableEntry + 0x21 + FuncOffset);
      DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "**** PspCidTable: %llx **** \\n", PspCidTableOffset);
      
      **** PspCidTable: fffff80649afc5d0 ****
      ///Windbg
      3: kd> dq !PspCidTable
      fffff806`49afc5d0  ffffba02`61a805c0 ffffa901`e1cfde80
      
      • 주소값 제대로 가져옴
  • PspCidTable 참조하여 EPROCESS 주소 가져오기
    • PspCidTable 포인터 + 8 위치에 TableCode위치함.
      TableCode = (PVOID*)((ULONGLONG)(*PspCidTable) + 0x8);
      DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "**** TableCode: %p **** \\n", TableCode);
      
      **** TableCode: FFFFBA0261A805C8 ****
      //WinDBG
      0: kd> dq FFFFBA0261A805C8
      ffffba02`61a805c8  ffffba02`656fb001
      
    • TableCode 연산하여 테이블레이어 단계 구분
    • //TableCode에서 LayerLevel 분리
      LayerLevel = (ULONGLONG)(*TableCode) & 3;
      TableAddr = (PVOID*)( (ULONGLONG)(*TableCode) - LayerLevel);
      DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "**** TableAddr: %p **** \\n", TableAddr);
      
      **** TableAddr: FFFFBA02656FB000 ****
      
    • 테이블 레이어 2단계→ 1단계순으로 타고 내려가야함
      • 레이어 단계별로 다르게 동작하는 함수 구성
      • 2,3단계 함수는 반복문 돌면서 하위단계로 넘겨주는 역할
        • 데이터가 테이블에 연속적으로 존재하니, 유효한 주소를 가져오지 못하는 시점에서 반복문이 중단되면 될듯.
      • 1단계 함수는 반복문 돌면서 테이블 데이터 읽어와서 EPROCESS 출력
        • 1단계 테이블 하나에 들어갈 수 있는 PID/TID 의 최대 수치는 0x400
        • 따라서, 0x100행→256행까지만 들어갈 수 있으므로 최대 256회만 반복 돌면 됨.
    • 1단계 함수 돌면서 찾아온 데이터 디코딩해서 EPROCESS 주소 찾아오기
    • retResult = (PVOID)( (  (ULONGLONG)((ULONGLONG)*EncodeAddr >> 0x10 ) + 0xFFFF000000000000 ) & 0xFFFFFFFFFFFFFFF0);
    • **** Target addr : ffffba0261ab2010 **** 
      **** Target val : a901e1c62080fb91 **** 
      ---- DECODE : ffffa901e1c62080 ----
      
      • 읽어온 주소의 UniqueProcessId(0x440) 멤버 참조하여 0xFFFFFFFC 보다 작으면 EPROCESS로 판정
      if(*TargetAddress){
      	TargetProcess = DecodeTableData(TargetAddress);
      	PID =  (ULONGLONG) * ((PVOID*)((ULONGLONG)TargetProcess + 0x440));
      	if(PID < 0xFFFFFFFC)
      		DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "**** Target PID : %llx **** \\n",PID);
      }
      
      **** Target PID : 4 **** 
      
      • PsGetProcessId 함수를 이용하는 경우, HANDLE 타입이기 때문에 4바이트값 밖에 가져오지 못함. 0xE0000000 이상 반환되는 경우로 필터링 조건 변경.
      **** PID : 4 **** 
      **** PID : e1d57a38 **** 
      **** PID : e1d8a4b8 **** 
      **** PID : e1cd74b8 ****
      
      PID = PsGetProcessId(TargetProcess);
      DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "**** PID : %lx **** \\n", PID);
      if ((ULONG)PID < 0xE0000000) {
      ~~~
      
      
  • 찾은 EPROCESS 구조체의 PID와 ImageFileName 가져와 출력
    • if (PID < 0xFFFFFFFC) {
      	PName = (UCHAR*)((ULONGLONG)TargetProcess + 0x5a8);
      	DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "**** PNAME : %s PID : %llx **** \\n", PName,PID);
      }
      
      **** PNAME : System PID : 4 ****
      
      • PsGetProcessImageFileName() 함수 이용하는 방법
      //PsGetProcessImageFileName  함수 원형 선언
      typedef UCHAR*(*NTAPI PsGetProcessImageFileName_t)(
          PEPROCESS Process
      );
      PsGetProcessImageFileName_t(PsGetProcessImageFileName) = nullptr;
      ~~~~
      if ((ULONG)PID < 0xE0000000) {
      	PName = PsGetProcessImageFileName(TargetProcess);
      	DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "**** PNAME : %s PID : %llx **** \\n", PName, PID);
      }
      
  • 전체 프로세스 목록 출력
    • **** PNAME : System PID : 4 **** 
      **** PNAME : Registry PID : 8c **** 
      **** PNAME : svchost.exe PID : 194 **** 
      **** PNAME : smss.exe PID : 1b8 **** 
      **** PNAME : dwm.exe PID : 1e4 **** 
      **** PNAME : svchost.exe PID : 208 ****
      ~~~~~~~~
      **** PNAME : svchost.exe PID : 2378 **** 
      **** PNAME : msedge.exe PID : 239c **** 
      **** PNAME : svchost.exe PID : 242c **** 
      **** PNAME : dllhost.exe PID : 24f4 **** 
      **** PNAME : cmd.exe PID : 272c **** 
      **** PNAME : conhost.exe PID : 2734 **** 
      **** PNAME : ShellExperienc PID : 2790 **** 
      **** PNAME : RuntimeBroker. PID : 27e0 ****​
  • 1860(0x744) 프로세스 숨긴 후 전체 프로세스 목록 출력
  •   **** PNAME : svchost.exe PID : 68c **** 
      **** PNAME : svchost.exe PID : 6c8 **** 
      **** PNAME : notepad.exe PID : 744 **** 
      **** PNAME : svchost.exe PID : 750 **** 
      **** PNAME : svchost.exe PID : 76c ****
    • 잘 나오는것 확인됨

구현 완료

추가로 해 볼것

  • NtQuerySystemInformation의 handle(0x40) 반환값 이용해 동일 기능 구현

GITHUB

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

 

GitHub - synod2/Kernel_Hooking: windows Kernel Hooking

windows Kernel Hooking. Contribute to synod2/Kernel_Hooking development by creating an account on GitHub.

github.com


참고 문서

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

 

Windows Handle Table & Object | Shh0ya Security Lab

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

shhoya.github.io

https://ko.wikipedia.org/wiki/핸들_(컴퓨팅)

 

핸들 (컴퓨팅) - 위키백과, 우리 모두의 백과사전

핸들(handle)은 자원(resource)에 대한 추상적인 참조이다. 핸들은 응용 소프트웨어가 데이터베이스나 운영 체제 같은 다른 시스템에서 관리되는 메모리 블록들이나 객체들을 참조하는데 사용된다.

ko.wikipedia.org

https://docs.microsoft.com/ko-kr/windows/win32/sysinfo/handles-and-objects - MSDN 핸들

 

핸들 및 개체 - Win32 apps

개체는 파일, 스레드 또는 그래픽 이미지와 같은 시스템 리소스를 나타내는 데이터 구조입니다.

docs.microsoft.com

https://wonjayk.tistory.com/270 - window handle object

 

윈도우 핸들 (Windows Handle Object)

핸들(Handle)이란 무엇인가? 우리가 흔히 핸들이라고 생각하면 자동차의 핸들(Steering Wheel)을 떠올리기 마련인데요, 자동차 바퀴의 방향을 제어하기 위해 이 핸들을 사용하게 됩니다. 윈도우의 핸

wonjayk.tistory.com

http://egloos.zum.com/sweeper/v/2814944

 

Kernel Object / Object Handle

1. 커널 오브젝트란? 우선 한 문장 정의부터 정리하면 아래와 같다고 할 수 있다. "커널에서 관리하는 중요한 정보를 담아둔 데이터 블록" 예를 들어, 프로세스를 하나 생성했다고 치자. OS에서는

egloos.zum.com

https://titanwolf.org/Network/Articles/Article?AID=d8c016a4-1610-46c5-a751-deb46dd7a97a

 

Table traverse PspCidTable detect hidden processes(Others-Community)

 

titanwolf.org

http://pds8.egloos.com/pds/200805/13/51/about_windows_handle.pdf - PspCidTable

https://driverentry.tistory.com/entry/PspCidTable-HANDLETABLEENTRY-ObjectPointerBits - PspCidTable

 

PspCidTable, ObTypeIndexTable, _HANDLE_TABLE_ENTRY -> ObjectPointerBits

숨겨진 프로세스 탐지 방법을 찾던중 PspCidTable이 가지고 있는 핸들 정보를 이용하여 검색하는 방법을 구현하게 되었다. 인터넷에 검색해보면 몇몇 코드가 나오는데 대부분 XP에서 테스트가 된거

driverentry.tistory.com

http://egloos.zum.com/nerd/v/2592677 - PspCidTable

 

FUTo & PspCidTable

Blacklight라는 루트킷 감지툴은 유저모드에서 작동하며, 0x0 부터 0x4E1C까지의모든 PID에 대해서 OpenProcess를 시도하는 PID BruteForce방식을 사용한다.먼저, PIDB를 시작하기 전에 CreateToolhelp32Snapshot를 호

egloos.zum.com

https://blog.pages.kr/587 - PspCidTable

 

[윈도우] 숨겨진 프로세스를 찾는 방법들

 1. EPROCESS의 ActiveProcessLinks Linked List를 이용해서 Traverse.  ( 왠만한 루트킷들은 이 값을 조작하므로 별로 소용없을지도 모르지만, ZwQuerySystemInformation()을 후킹하여 결과값을 조작하는 식으..

blog.pages.kr

http://www.vijaymukhi.com/seccourse/june2606/pspctbl.htm - PspCidTable

 

PspCidTable

 

www.vijaymukhi.com

https://www.programmerall.com/article/69211807726/ - PspCidTable

 

Traverse PSPCIDTable enumeration process - Programmer All

The version of Delphi code used in this blog is DelphiXE10.x 1.1. Enumeration process Get the specified process ID by the process name, the code is very detailed, and I will not repeat it 1.2 The enum...

www.programmerall.com

https://www.unknowncheats.me/forum/3021549-post36.html - PspCidTable

https://www.codenong.com/cs106975071/- PspCidTable TableCode 연산루틴

https://www.freesion.com/article/7384815990/- PspCidTable TableCode 연산루틴

 

x64驱动 遍历 PspCidTable 枚举隐进程和线程 - 灰信网(软件开发博客聚合)

介绍 PspCidTable 是一个内核句柄表,存放进程和线程的内核对象(EPROCESS 和 ETHREAD),并通过 PID 和 TID 进行索引(所以进程ID和线程ID不可能相同),ID 号以 4 递增。 获取 PspCidTable 地址 win7: PsLook

www.freesion.com