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***인근에 QuotaBlock 와 DebugPort 있는것 확인됨.
- 둘 중 하나 구해올 수 있으면 -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 구조체의 ActiveProcesslinks 와 HandleTableList 를 모두 가져오게 동작을 만들 수 있음.
- 이전 DKOM 을 이용한 processhide를 만들 때 이 방식 이용
- 각 프로세스의 ActiveProcesslinks 와 HandleTableList 의 모든 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를 한꺼번에 인자로 넘겨볼 수도 있긴하다.
- Blink에 대한 비교시 어떤 링크가 변조되었는지 알려줄 수 있음.
- 프로세스 숨긴 후 탐지 확인
-
===== 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 탐지 기법
https://shhoya.github.io/windows_hwndobject.html - HandleTableList