본문 바로가기

My Study/Programming&Theory

프로세스 생성 막기

유저영역이던 커널 영역이던 특정 프로세스 생성을 막는 방법은 다양하게 존재합니다.

유저영역에서
지속적으로 프로세스 리스트를 감시하며 특정 프로세스가 생성되면 바로 TerminateProcess 해버릴수도 있고
explorer.exe의 CreateProcess를 후킹해서 인자 값 조작으로 생성을 막을 수도 있습니다.

똑같이 커널에서도 위 2가지 방법을 사용해 프로세스 생성을 막을 수 있습니다.
당연히 커널에서는 explorer.exe에 있는 CreateProcess를 후킹하는게 아닌 SSDT 후킹하거나 NtCreateProcess함수를 인라인 후킹해서 하겠지요. 

하지만 SSDT후킹, 인라인 후킹은 커널 내부를 수정시켜버리는 것이고 지속적인 프로세스 리스트 검사는 cpu에 부담을 줄수 있습니다. 그래서 제가 해볼 방법은 콜백 루틴을 등록해서 해결하는 방법입니다.

어려운건 아니고 그냥 함수만 잘 사용하면 됩니다~

처음에 이 함수를 사용해 했었습니다. 

NTSTATUS PsSetLoadImageNotifyRoutine(

  __in  PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine

);

 콜백 루틴을 OS에 등록해 주는 함수인데
콜백 루틴은 바이너리 이미지가 메모리로 로드될 때 호출이 됩니다.
즉, 바이너리 이미지 이므로 exe일 때도 호출이 되고 dll이 로드될 때도 호출이 됩니다. 

콜백 루틴 함수 원형입니다.

VOID

  (*PLOAD_IMAGE_NOTIFY_ROUTINE)(

    __in_opt PUNICODE_STRING  FullImageName,

    __in HANDLE  ProcessId,

    __in PIMAGE_INFO  ImageInfo

    );

 각 함수 인자는 너무 직관적이군요. 
ImageInfo 인자는 루트킷이 유용하게 사용할 수 있는 정보들이 있습니다.

typedef struct  _IMAGE_INFO {

    union {

        ULONG  Properties;

        struct {

            ULONG ImageAddressingMode   : 8; //code addressing mode

            ULONG SystemModeImage       : 1; //system mode image

            ULONG ImageMappedToAllPids : 1; //mapped in all processes

            ULONG Reserved             : 22;

        };

    };

    PVOID  ImageBase; //이미지의 가상 메모리 베이스 주소

    ULONG  ImageSelector;         //항상 0

    ULONG  ImageSize; //이미지의 가상 사이즈(바이트)

    ULONG  ImageSectionNumber; //항상 0

} IMAGE_INFO, *PIMAGE_INFO;

이미지 베이스 주소도 있으며 유용하게 사용될 것 같지만 여기서는 사용을 하지 않겠습니다.

아무튼 
PsSetLoadImageNotifyRoutine함수를 사용해 콜백 루틴을 등록시키고 콜백 루틴으로 들어오는 FullImageNamePID를 사용해 프로세스를 종료시켜버리는 것입니다.

 방법은 간단합니다.
FullImageName이 종료를 원하는 프로세스 명이라면 해당 프로세스의 PID로부터 핸들을 얻습니다.
핸들은 ZwOpenProcess로 얻으면 됩니다.
그리고 얻은 핸들 값으로 ZwTerminateProcess를 호출함으로서 프로세스를 종료시키면됩니다.


일단 이렇게 만들어 놨더니 Windows XP에서는 잘 작동되었습니다.
이제 Windows 7에서 테스트를 해보았습니다..... 헉!!! ZwTerminateProcess에서 계속 뻑이나는 것이었습니다. 

Bugcheck은 KERNEL_APC_PENDING_DURING_EXIT (20) 이렇게 나왔고 
대충 콜스택과 인자값을 살펴보니 APC disable count 값이 0이 아닌 상태에서 Thread를 종료하려고 해서 BSOD 발생한거 였습니다. 그래서 인자값이 -1이었는데 0을 만들어주기위해 KeLeaveCriticalRegion 함수도 써줘봤지만 소용이 없더군요.

제 생각으론 APC disable count 부분을 조정해 주어야 할것 같은데 잘 할줄 몰라 다른 방법으로 눈을 돌렸습니다.

찾아보니 다른 콜백루틴 등록해주는 함수가 있었으며 해당 콜백루틴의 기능에는 TerminateProcess를 사용하지 않고 프로세스 생성을 막을 수 있는 기능이 존재하였습니다.

함수 원형입니다.

NTSTATUS PsSetCreateProcessNotifyRoutineEx(

  __in  PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine,

  __in  BOOLEAN Remove

);


프로세스가 생성 또는 종료 시 호출되는 콜백 루틴을 등록하는 함수입니다.
두번째 인자 Remove는 TRUE일 경우 해당 콜백 루틴을 제거를 시키는 것이고 FALSE일 경우 콜백 루틴을 추가시키는 것입니다.

만약 언로드 루틴이 존재하는 드라이버라면 언로드 루틴에서 위 함수를 다시 사용해 TRUE값으로 호출시켜주어야 겠지요.

콜백 루틴 함수 원형입니다. 

VOID CreateProcessNotifyEx(

  __inout   PEPROCESS Process,

  __in      HANDLE ProcessId,

  __in_opt  PPS_CREATE_NOTIFY_INFO CreateInfo

);

첫번째 인자는 EPROCESS구조체 포인터이고 두번째 인자는 PID값입니다.
그리고 세번째 인자는 생성되는 프로세스 정보인데 여기선 이 정보가 중요합니다.
세번째 인자가 NULL이면 프로세스가 종료 될 때 호출되는 것이고 NULL이 아니면 생성될 때 호출되는 것입니다.

세번째 인자를 자세히 보겠습니다.

typedef struct _PS_CREATE_NOTIFY_INFO {

  SIZE_T              Size;

  union {

    ULONG  Flags;

    struct {

      ULONG FileOpenNameAvailable  :1;

      ULONG Reserved  :31;

    };

  };

  HANDLE              ParentProcessId;

  CLIENT_ID           CreatingThreadId;

  struct _FILE_OBJECT *FileObject;

  PCUNICODE_STRING    ImageFileName;

  PCUNICODE_STRING    CommandLine;

  NTSTATUS            CreationStatus;

} PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;

 
인자 3개만 설명하겠습니다.
ImageFileName은 프로세스 경로가 있지만 앞에 \??\가 붙어있는 경로이고
CommandLine은 \??\가 없는 경로입니다.
그리고 가장 중요한 CreateionStatus!
해당 인자는 프로세스 생성 시 반환되는 리턴 값을 결정하게 됩니다.

루트킷은 저 인자를 조작함으로서 프로세스 생성을 막을 수 있습니다.
CreateInfo->CreationStatus = STATUS_ACCESS_DENIED;

그리고 PsSetCreateProcessNotifyRoutineEx 함수는 Visata SP1 이상 Server 2008 이상 버전의 Windows 에서만 작동을 하기 때문에 Windows XP 같은 경우는 사용할 수 없습니다.

또한 해당 함수를 쓰기 위해선 특별한 한가지 작업을 해주어야 합니다.
이 함수를 그냥 쓰면 리턴 값이
STATUS_ACCESS_DENIED가 발생하게 됩니다.
그 이유는 msdn에 잘 나와있습니다.
The image that contains the callback routine pointer did not have IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY set in its image header.
링커에서 IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 를 추가시켜주면 됩니다.

SOURCES 파일을 열어서 내부에
LINKED_FLAGS=/INTEGRITYCHECK
를 추가시켜주면 됩니다.
 

프로그램 작성은 이렇게 하면 됩니다.
제가 작성한 드라이버의 DriverEntry의 일부입니다.

각 버전에 따라 나눠서 2개의 함수를 적절히 사용해 주었습니다.
코드를 보시면  직접적으로 
PsSetCreateProcessNotifyRoutineEx함수를 호출해서 쓰지 않고 
MmGetSystemRoutineAddress함수를 사용해 함수 포인터를 얻어와 쓰고 있는 것을 볼수 있는데
이렇게 한 이유는
직접적으로  PsSetCreateProcessNotifyRoutineEx함수를 써버리면 Windows XP에서는 
드라이버 로드에 실패
하게 됩니다. 왜냐하면 해당 함수는 Windows XP에는 없기 때문이죠.
그렇기 때문에 함수 포인터를 얻어와 간접적으로 호출 한 것 입니다.

Windows XP에서는 ZwTerminateProcess를 사용해 프로세스를 죽이고
Windows 7에서는 CreationStatus 값을 바꿈으로서 프로세스 생성 실패를 만드는 것입니다.

그러므로 Windows XP에서는 그냥 타겟 프로그램 더블 클릭 시  아무런 반응이 없지만 
Windows 7 같은 경우는 해당 프로그램에 접근 할 수 없다는 에러창이 뜰 것입니다. 

.... 그냥 
PsSetCreateProcessNotifyRoutine 이 함수를 사용하면 Windows XP던 Windows 7이던 다 잘 작동합니다.
ZwTerminateProcess도 Windows 7에서 잘 되더군요.. 

'My Study > Programming&Theory' 카테고리의 다른 글

Interrupt Descriptor Table Architecture  (0) 2011.05.18
Get clipboard data  (0) 2011.05.17
IEEE Std 754  (2) 2011.04.27
DFS, BFS, UCS  (1) 2011.04.10
Python Eclipse에서 코딩하기  (1) 2011.02.25