일반적으로 fork 계열을 사용하는 유닉스 운영체제에서는 좀비 프로세스라는 말이 자주 등장합니다. 스티븐스 아저씨의 역작인 유닉스 프로그래밍 책에도 좀비 프로세스에 대한 소개가 여러 차례 등장하지요. 하지만 윈도우 운영체제에서는 좀비 프로세스라는 용어는 생경합니다. 이런 현상을 반영하듯이 스티븐스 아저씨에 필적하는 제프리 리처 아저씨의 역작 “Windows via C/C++”에도 좀비 프로세스라는 용어는 등장하지 않습니다.
그렇다면 윈도우에는 좀비 프로세스가 없는 걸까요? 결론부터 말하면 그건 아닙니다. 근데 왤케 낯선 걸까요? 그 가장 큰 이유는 윈도우 운영체제가 좀비 프로세스에 대한 정보를 꽁꽁 숨겨 두었기 때문입니다. 작업 관리자에서 좀비라고 표시되는 프로세스나 죽은 프로세스 임에도 죽지 않고 버젓이 표시되는 것을 본 적이 있으신가요? 없을 겁니다. Toolhelp나 PSAPI를 사용해서 프로세스를 열거할 때에 죽은 프로세스가 나타나는 것을 보신 적이 있으신가요? 없을 겁니다. 이는 NT 네이티브 API를 사용해도 똑같습니다.
그렇다면 이렇게 운영체제가 꽁꽁 숨겨놨는데 무슨 근거로 존재한다고 주장하는 걸까요? 나타나지도 않는데 무슨 근거로 있다고 하는 겁니까, 라고 물어볼지도 모르겠습니다. 당.연.히 일반적이지 않은 방법을 사용해야 그 존재를 찾을 수 있습니다. 좀비를 만나게 되는 경우는 크게 두 가지 경우가 있는데요. 하나는 PID 브루트포스라는 방법으로 무작위 PID를 대입해서 프로세스가 존재하는지를 검사할 때에 이 좀비가 나타나게 됩니다. 다른 하나는 커널 레벨에서 EPROCESS 구조체를 직접 열거하면 만날 수 있습니다.
뭐 어쨌든 좀비 좀비 거렸는데 그러면 일단 그 좀비 프로세스의 실체를 한 번 살펴볼까요? 아래는 간단하게 notepad를 좀비로 만들고 그 결과를 관찰한 windbg 출력입니다. 이 내용을 보면 좀비 프로세스가 무엇인지 명확하게 알 수 있습니다.
kd> !process 0 0 notepad.exe
PROCESS fffffa8002236b30
SessionId: 1 Cid: 08dc Peb: 7fffffdf000 ParentCid: 076c
DirBase: 3cc09000 ObjectTable: 00000000 HandleCount: 0.
Image: notepad.exe
kd> !process fffffa8002236b30
PROCESS fffffa8002236b30
SessionId: 1 Cid: 08dc Peb: 7fffffdf000 ParentCid: 076c
DirBase: 3cc09000 ObjectTable: 00000000 HandleCount: 0.
Image: notepad.exe
VadRoot 0000000000000000 Vads 0 Clone 0 Private 1. Modified 4. Locked 0.
DeviceMap fffff8a0016b0200
Token fffff8a001c6a060
ElapsedTime 00:00:27.341
UserTime 00:00:00.046
KernelTime 00:00:00.109
QuotaPoolUsage[PagedPool] 0
QuotaPoolUsage[NonPagedPool] 0
Working Set Sizes (now,min,max) (5, 50, 345) (20KB, 200KB, 1380KB)
PeakWorkingSetSize 1799
VirtualSize 55 Mb
PeakVirtualSize 77 Mb
PageFaultCount 1850
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 0
No active threads
아직도 좀비 프로세스가 뭔지 정확하게 이해하지 못하신 분들을 위해서 첨언하면 이렇습니다. 좀비 프로세스란 프로세스 종료 처리가 모두 완전하게 끝났음에도 프로세스 구조체가 사라지지 않고 남아있는 경우를 말합니다. 즉, 프로세스가 죽고 싶어도 죽지를 못하니 그 증상이 마치 좀비 같다고 해서 좀비 프로세스라고 부르는 거지요. 물론 앞선 windbg 출력에 보이는 것처럼 프로세스 종료에 관한 모든 처리가 끝났기 때문에 좀비 프로세스는 어떠한 주소 공간도, 어떠한 커널 오브젝트도, 어떠한 스레드도 포함하고 있지 않습니다. 단지 커널 구조체와 PID란 자원을 낭비하고 있을 뿐이지요.
그렇다면 이 좀비 프로세스는 도대체 왜 죽지 않고 이렇게 남아 있는 걸까요? 이유는 간단합니다. 프로세스가 죽었음에도 해당 프로세스를 참조하는 다른 프로세스가 남아 있다면 그 죽은 프로세스는 좀비가 됩니다. 뭐 유닉스에서 fork로 좀비가 생기는 원리랑 동일하다고 생각하시면 됩니다. 아래 프로그램을 사용하면 아주 손쉽게 좀비를 만들 수 있습니다.
#include "windows.h"
#include "tchar.h"
int _tmain(int argc, _TCHAR* argv[])
{
if(argc < 2)
return 0;
HANDLE process = OpenProcess(SYNCHRONIZE, FALSE, _tcstoul(argv[1], NULL, 10));
if(!process)
{
printf("open error %d\n", GetLastError());
return 0;
}
getchar();
CloseHandle(process);
return 0;
}
-
notepad.exe를 실행합니다.
-
앞선 코드의 프로그램을 실행합니다. ex) zombie_maker.exe [notepad PID]
-
실행한 notepad를 종료합니다.
이 상태가 되면 죽은 notepad는 좀비가 됩니다. zombie_maker가 죽은 녀석의 핸들을 잡고 있기 때문이지요. 그 핸들이 닫히기 전까지 notepad는 영원히 좀비 상태로 남아있게 됩니다. 그 핸들이 닫히는 순간 비로소 좀비는 죽을 수 있게 되지요.
결국 좀비 프로세스란 탐색기가 파일 핸들을 잡아서 파일 삭제가 안 되는 것처럼 누군가 프로세스 핸들을 잡고 있어서 그런 경우가 다반사 입니다. 그럼 주로 어떤 놈들이 잡고 있는 경우가 많을까요? 보안 프로그램, 특히 백신같이 프로세스를 스캔하는 녀석들이 잡고 있는 경우가 많이 있습니다. 다행이 유저 레벨에서 벌어지는 일의 경우에는 핸들을 잡고 있는 프로세스만 종료하면 좀비가 사라지지만 커널 모드 드라이버에서 핸들릭이 발생해서 좀비가 된 프로세스는 재부팅 전까지는 영.원.히 좀비 상태가 된다는 것에 주의해야 합니다.