특정 프로세스를 보호하는 데에는 여러 가지 방법이 존재합니다.
일반적으로 하는 SDT Hook 이외에 여러 방법들이 존재하는데, 지금부터 그 방법들을 알아봅시다.
1. ZwXxx() Hook
일반적으로 많이 쓰이는 방법이라 생각하는데, 대부분 이러한 함수들을 후킹함으로써 프로세스를 보호합니다.
ZwOpenProcess() : 프로세스 핸들을 얻어오지 못하게 해서 프로세스에 접근하지 못하게 합니다.
ZwTerminateProcess() : 프로세스 핸들을 얻지 못하게 했다 해도, 프로세스 핸들을 얻어오는 또다른 방법은
ObOpenObjectByPointer()/ObOpenObjectByName() 을 호출하는 방법이 있습니다. 따라서 프로세스를 종료하지 못하게 막으려면 이 함수를 후킹해야 합니다.
ZwCreateThread() : 프로세스에 새로운 쓰레드를 만드는 함수인데, 아래와 같은 방법으로 프로세스를 종료할 수 있으므로 이 함수를 후킹해야 프로세스를 종료/접근 에서 막을 수 있습니다.
(간단하게 알기 쉽게 CreateThread()를 이용해서 설명하겠습니다.)
DWORD ProcessTerminateRoutine(PVOID pContext)
{
return TerminateProcess((HANDLE)0xFFFFFFFF, 0);
}
void RemoteTerminateProcess(HANDLE hProcess)
{
HANDLE hThread=CreateThread(hProcess, ..., ProcessTerminateRoutine, ...);
CloseHandle(hThread);
}
ZwOpenThread() : ZwOpenProcess()를 후킹했다 해도, 쓰레드에 접근 가능하다면 쓰레드 핸들을 얻어 온 후, 쓰레드를 종료해 버리면 프로세스는 종료됩니다. 그리하여 이 함수 역시 후킹할 필요가 있습니다.
ZwTerminateThread() : ZwOpenThread()를 후킹하는 이유와 거의 일치합니다.
ZwQuerySystemInformation() : 이 함수로 시스템의 정보를 알 수 있습니다. 그리고 프로세스/스레드 정보 역시 이 함수를 통해 얻을 수 있습니다(리스트로). 그리하여 이 함수를 후킹하여 SystemInformationBuffer에서 보호하려는 프로세스와의 링크를 끊어 주면 프로세스를 은폐함으로써 프로세스를 보호할 수 있습니다.
이외에도 ZwReadVirtualMemory()/ZwWriteVirtualMemory()/ZwQueryInformationProcess() 등등을 후킹할 필요도 있습니다.
이 방법들의 단점은, 여러 개의 함수를 후킹해야 한다는 점과 SDT Hook일 경우, SDT를 Restore하거나 Relocate되었을 경우, 혹은 이미지 파일에서 주소를 구해올 경우 무력화될수 있다는 점입니다.
2. EPROCESS 구조체의 ActiveProcessLinks 링크를 끊는다.
EPROCESS 구조체에는 ActiveProcessLinks가 있는데, 이것을 Tracing하면 프로세스 리스트를 얻을 수 있습니다.
그러나, 이 Link를 끊는다면, 프로세스는 은폐하여 보이지 않게 되어 프로세스를 보호할 수 있습니다.
이 방법의 단점은, Handle Table을 Tracing 할 경우, 혹은 무작위로 OpenProcess() 시도시, 들킬 수 있다는 것이 단점입니다.
3. PspCidTable의 주소를 구해서 프로세스의 흔적을 지워버린다.
PspCidTable에는 프로세스의 핸들에 대한 정보가 담겨 있습니다.
그런데, PspCidTable에서 프로세스의 핸들을 지워 버린다면, ZwOpenProcess()로 프로세스를 열지라도, 핸들이 지워졌기 때문에 프로세스 핸들을 얻어 오는 데에 실패할 것입니다.
결국, 프로세스는 보호됩니다.(FU_RootKit이 사용했던 방법입니다.)
이 방법의 단점은 PspCidTable의 주소를 직접 구해 와야 한다는 것과, 이 방법으로도 프로세스를 완전히 흔적을 없앨 수 없습니다.
4. ObOpenObjectByName() Hook
프로세스 핸들을 얻어 오는 방법 중 하나는 ObOpenObjectByName()을 호출하여 이름으로 프로세스 핸들을 얻어 오는 것입니다.
이 함수를 후킹한다면, 특정 이름의 Object를 여는 것을 막아서 프로세스를 보호할 수 있을 것입니다.
단점은 핸들이 어떤 Type의 핸들인지를 알 수 있어야 한다는 것(ObjectType이 NULL일 때), ObjectName을 어떻게 구분지을 것인지를 알아야 하고, 좀더 막으려면 PsLookupXxx()등의 함수를 보조적으로 후킹해야 한다는 것이 있습니다.
5. ObOpenObjectByPointer() Hook
이 함수 역시 프로세스 핸들을 얻어 올 수 있습니다. 프로세스 핸들을 얻을 때, Parameter 중 Object에는 EPROCESS의 포인터가 들어가고, ObjectType에는 PsProcessType이 들어갑니다(NULL이 들어가기도 한다). 그리하여 후킹한 후 내부적으로 인자를 검사해서 프로세스를 여는 것일 경우 막으면 프로세스를 보호할 수 있습니다.
단점은 핸들이 어떤 Type의 핸들인지를 알 수 있어야 한다는 것(ObjectType이 NULL일 때)과 좀더 막으려면 PsLookupXxx()등의 함수를 보조적으로 후킹해야 한다는 것이 있습니다.(4번과 비슷)
6. KeAttachProcess()/KeStackAttachProcess() Hook
이 함수는 프로세스에 Attach할 수 있는 함수입니다. Attach하면.....일단 프로세스에 접근 할 수 있을 테니 종료도 가능할 것입니다.
그러므로 이 함수를 후킹할 필요가 있습니다.(GameGuard에서 후킹하는 함수라죠)
단점은 인자에 EPROCESS 구조체의 포인터가 필요하므로 PsLookupXxx()등의 함수도 후킹해야 합니다.
7. PspTerminateProcess() Hook
이 함수는 ntoskrnl.exe가 내부적으로 가지고 있는 하나의 내부함수로서, 프로세스를 종료할 때 쓰이나, ZwTerminateProcess()와의 차이점은 첫번째 인자에 ProcessHandle 대신 Process Object의 포인터(EPROCESS 포인터)가 들어간다는 것입니다.
이 함수는 Unexported된 함수이며, 주소를 직접 구해 오는 수밖에 없습니다. 이 함수를 후킹하면 프로세스를 종료에서 보호할 수 있습니다.
단점은 무엇보다도 PspTerminateProcess()의 주소를 직접 구해와야 하는데에 있을거라 생각됩니다. (필자가 시도중)
8. PspTerminateThreadByPointer() Hook
이 함수는 ntoskrnl::NtTerminateProcess()과 ntoskrnl::NtTerminateThread()가 내부적으로 호출하는 함수이고, PspTerminateProcess() 역시 이 함수를 내부적으로 사용합니다.
PspTerminateProcess()는 PsGetNextProcessThread()를 사용하여 프로세스의 Next Thread Object Pointer를 얻어 온 후(ETHREAD의 포인터), PspTerminateThreadByPointer()를 호출하여 프로세스가 소유하는 쓰레드를 하나하나씩 종료합니다.
이 함수 역시 Unexported된 함수이며, 주소를 직접 구해 와야 합니다. 이 함수는 PspTerminateProcess() 조차도 내부적으로 호출하는 함수이므로 후킹하면 프로세스/쓰레드를 종료에서 보호할 수 있습니다.
단점 역시 7번과 비슷합니다.
이상으로 몇몇 안되지만, 프로세스를 보호하는 여러 방법과 그 단점에 대해 알아보았습니다.
[출처] 프로세스를 보호하는 법.