개요
모든 32bit 윈도우 프로세스는 4Gbyte의 가상주소공간을 개별적으로 할당받는다. 이 주소공간은 다시 2Gbyte 씩 유저영역과 커널영역으로 나뉘어 지며, 유저영역의 주소공간은 프로세스별로 독립적이기에 프로세스간 서로 접근할 수 없다.
우리가 흔히 알고있는 프로그램들은 유저영역에서 동작이 이뤄진다. 커널영역에서는 하드웨어 드라이버, 프로세스 보안, 자원관리 등 PC의 동작에 필수적이고 민감한 정보를 다둔다. 따라서 유저영역에서 동작하는 프로세스는 커널영역에 직접 접근하는것이 차단되고, OS가 제공하는 안전한 API를 통해서만 커널 메모리에 접근할 수 있게 설계되어있다. 과거 윈도우 3.1 시절 잦은 블루스크린은 유저영역과 커널영역의 구분이 없었기에 있던 일이라 볼 수 있다. 프로그래밍 상의 작은 실수로 커널영역을 임의로 수정하게 된다면 치명적인 오류가 발생하기 때문이다.
또한 OS에서 관리하기에 신뢰성을 보장(?) 받는다는 면에서 모든 프로세스가 공유하는 메모리 영역이기도 하기에, 악성코드가 즐겨 공략하는 대상이기도 하다.
DKOM 이란:
Direct Kernel Object Manipulation 의 약자로, 커널영역의 메모리(오브젝트)를 유저영역에서 수정하는 행위를 말한다.
드라이버는 커널영역에서 동작하기 때문에 커널영역에 접근하기 위해선 드라이버 설치가 가장 확실한 방법이다. 하지만 DKOM은 별도의 드라이버 설치 없이도 유저영역에서 커널영역의 메모리를 수정할 수 있다는 편의성 떄문에 자주 사용됐었던 기법이다. Win7 부터는 여기서 다루는 DKOM에 필수적인 함수를 더이상 지원하지 않는다
ntdll.ZwSystemDebugControl
유저영역에서 커널영역 메모리 수정을 위해 사용되는 함수 ZwSystemDebugControl() 이다. 이 함수를 통해 커널영역 메모리를 읽기/쓰기 할 수 있어 DKOM의 시작과 끝은 이 함수와 함께한다 볼 수 있다. Undocumented 함수로 언제든 사전고지 없이 수정될 수 있는 함수였고, 실제로 그 일이 일어났다.
이 함수는 아래와 같이 선언해 사용할 수 있다.
typedef NTSTATUS (NTAPI *NTSYSTEMDEBUGCONTROL)(
IN DEBUG_CONTROL_CODE ControlCode,
IN PVOID InputBuffer OPTIONAL,
IN ULONG InputBufferLength,
OUT PVOID OutputBuffer OPTIONAL,
IN ULONG OutputBufferLength,
OUT PULONG ReturnLength OPTIONAL
);
typedef enum _DEBUG_CONTROL_CODE{
DebugGetTraceInformation=1,
DebugSetInternalBreakpoint,
DebugSetSpecialCall,
DebugClearSpecialCalls,
DebugQuerySpecialCalls,
DebugDbgBreakPoint,
DebugMaximum,
DebugReadVirtualMemory,
DebugWriteVirtualMemory
} DEBUG_CONTROL_CODE;
NTSTATUS KdReadVirtualMemory(PVOID VirtualAddress, PVOID Buffer, ULONG BufferSize){
MEMORY_CHUNKS MemoryChunks;
MemoryChunks.VirtualAddress=VirtualAddress;
MemoryChunks.Buffer=Buffer;
MemoryChunks.BufferSize=BufferSize;
return NtSystemDebugControl(DebugReadVirtualMemory, &MemoryChunks, sizeof(MemoryChunks),
NULL, 0, NULL);
}
NTSTATUS KdWriteVirtualMemory(PVOID VirtualAddress, PVOID Buffer, ULONG BufferSize){
MEMORY_CHUNKS MemoryChunks;
MemoryChunks.VirtualAddress=VirtualAddress;
MemoryChunks.Buffer=Buffer;
MemoryChunks.BufferSize=BufferSize;
return NtSystemDebugControl(DebugWriteVirtualMemory, &MemoryChunks, sizeof(MemoryChunks),
NULL, 0, NULL);
}
//https://gist.github.com/cr3denza/c73f98b4da155eca96ac#file-hideproc-c
위 코드를 보면 NtSystemDebugControl() 함수의 첫번째 인자 설정을 통해 커널 메모리를 읽고 쓰는데 동일한 함수를 사용하는 것을 볼 수 있다.
이제 커널 메모리에 접근할 수 있는 방법을 얻었으니, 이후에 EPROCESS 구조체를 수정하든, LDR_DATA_TABLE_ENTRY 를 수정하든 비슷한 방식을 사용하여 프로세스 및 드라이버를 숨길 수 있다.
사실 NtSystemDebugControl() 함수를 이용한 DKOM 에 관한 자료는 손쉽게 구할 수 있기도 하거니와, XP 이상에선 지원하지 않는 기능이기에 그 파급력이 크지는 않다. 따라서 이 글에선 기법에 관한 간단한 설명으로 마무리 짓는다.
DKOM 에 관해 더 자세히 알고싶으신 다면 위 소스코드에 첨부된 링크를 참조하거나 안랩 블로그 에서 루트킷의 역사와 함께 그 종류에 관해 자세히 소개한 글이 있으니 읽어보길 추천한다.