Windows/DeviceDriver 2021. 4. 25. 18:07

06. 프로그램 실행 제어

  • 프로그램의 실행을 통제하는 드라이버

이론

윈도우 프로세스 실행 과정

  • 프로그램을 실행시키면, 메모리에 프로세스를 위한 공간을 할당하고 EPROCESS 등의 구조체를 생성한다.

  • 공간 할당이 끝나면, 커널이 등록된 Notify Routine 함수를 호출하여 프로세스의 실행 여부를 결정한다.


드라이버에게 프로세스의 생성과 소멸 시기를 알려주기

  • 프로세스가 실행될 때마다 호출할 함수를 커널에 등록해야 한다.

  • WDK에서 제공되는, Notify Routine 함수를 등록하는 함수들

    • PsSetCreateProcessNotifyRoutine

      NTSTATUS
      PsSetCreateProcessNotifyRoutine(
        IN PCREATE_PROCESS_NOTIFY_ROUTINE  NotifyRoutine,
        IN BOOLEAN  Remove
        );
      
      VOID
      (*PCREATE_PROCESS_NOTIFY_ROUTINE) (
        IN HANDLE  ParentId,
        IN HANDLE  ProcessId,
        IN BOOLEAN  Create
        );
      • 이 방식으로 설치하는 NotifyRoutine은 프로세스가 생성되거나 제거되는 상황을 알 수는 있지만, 생성 과정을 제어할 수 없다.

      • 설치할 때는 두 번째 인자인 Remove를 0으로, 제거할 때는 1로 설정한다.

  • PsSetCreateProcessNotifyRoutineEx

    NTSTATUS
    PsSetCreateProcessNotifyRoutineEx(
      IN PCREATE_PROCESS_NOTIFY_ROUTINE_EX  NotifyRoutine,
      IN BOOLEAN  Remove
      );
    
    VOID
    CreateProcessNotifyEx(
      __inout PEPROCESS  Process,
      __in HANDLE  ProcessId,
      __in_opt PPS_CREATE_NOTIFY_INFO  CreateInfo
      );
    
    typedef struct _PS_CREATE_NOTIFY_INFO {
      __in SIZE_T  Size;
      union {
        __in ULONG  Flags;
        struct {
          __in ULONG  FileOpenNameAvailable : 1;
          __in ULONG  Reserved : 31;
        };
      };
      __in HANDLE  ParentProcessId;
      __in CLIENT_ID  CreatingThreadId;
      __inout struct _FILE_OBJECT  *FileObject;
      __in PCUNICODE_STRING  ImageFileName;
      __in_opt PCUNICODE_STRING  CommandLine;
      __inout NTSTATUS  CreationStatus;
    } PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;
    • 위 함수의 단점을 보완한 것으로, 프로세스의 생성 과정을 제어할 수 있다.

    • 프로세스가 종료될 때는 CreateProcessNotifyEx 함수의 세 번째 인자에 NULL이 들어간다.

      • 프로세스의 종료를 제어하려면 이 인자가 NULL일 경우를 처리하면 된다.
    • _PS_CREATE_NOTIFY_INFO 구조체의 ImageFileName는 실행하려는 파일의 Full Path Name이다.

    • _PS_CREATE_NOTIFY_INFO 구조체의 CreationStatus는 프로세스 실행 여부를 결정한다.

유의사항

  • 드라이버 바이너리 파일이 변조될 위험이 있기 때문에, 윈도우에서는 파일의 무결성을 체크한다.

    • 이를 위해 링커 옵션에 /integritycheck를 추가해야 한다.
  • 드라이버가 메모리에서 해제될 때는 등록했던 Notify Routine을 제거해야 한다.

    • PsSetCreateProcessNotifyRoutineEx 함수의 두 번째 인자인 Remove에 1(TRUE) 값을 주면, 첫 번째 인자인 NotifyRoutine의 등록을 해제한다.

실습

#include <ntddk.h>

WCHAR g_TempString[512] = { 0, };
void NotifyRoutine(PEPROCESS Process, HANDLE ProcessId, \
  PPS_CREATE_NOTIFY_INFO CreateInfo)
{
  Process = Process; ProcessId = ProcessId;
  if (CreateInfo == NULL)
    goto exit;

  memset(g_TempString, 0, sizeof(WCHAR) * 512);
  memcpy(g_TempString, CreateInfo->ImageFileName->Buffer, CreateInfo->ImageFileName->Length);
  _wcsupr(g_TempString);
  if (wcswcs(g_TempString, L"NOTEPAD.EXE"))
  {
    CreateInfo->CreationStatus = STATUS_UNSUCCESSFUL;
  }

exit:
  return;
}

void SampleDriverUnload(PDRIVER_OBJECT pDrvObj)
{
  pDrvObj = pDrvObj;

  PsSetCreateProcessNotifyRoutineEx(NotifyRoutine, TRUE);
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath)
{
  pRegPath = pRegPath;

  pDrvObj->DriverUnload = SampleDriverUnload;

  PsSetCreateProcessNotifyRoutineEx(NotifyRoutine, FALSE);

  return STATUS_SUCCESS;
}
  • 프로젝트의 속성(Ctrl + Enter)에서 Linker - All Options 탭의 Additional Options/integritycheck를 추가해야 정상적으로 Notify Routine 함수가 실행된다.

    • 무결성 체크 옵션을 주지 않고 빌드하여 드라이버를 올리면, 일단 정상적으로 올라가긴 하는데 메모장도 실행이 된다. 아마 PsSetCreateProcessNotifyRoutineEx 함수가 작동하지 않는 것 같다.

  • 빌드하고 .sys 드라이버 파일은 가상 머신에, .pdb 심볼 파일은 WinDbg에서 지정한 위치로 옮긴 후 관리자 권한의 CMD에서 sc start sample 명령어로 드라이버를 올린다.

  • 그 후 notepad 명령어로 메모장을 실행하면 다음과 같은 오류가 발생한다.

    시스템이 지정된 프로그램을 실행할 수 없습니다.
  • 관리자 권한으로 메모장을 실행하면 다음과 같은 오류를 볼 수 있다.

  • sc stop sample 명령으로 드라이버를 내리면 Notify Routine 함수가 내려가므로 정상적으로 메모장을 실행할 수 있다.

프로세스가 실행될 때 해당 프로그램의 경로 확인

  • WinDbg에서 브레이크 포인트를 걸어준다.

    bp sample!NotifyRoutine
  • sc start sample 명령어를 실행하면 Notify Routine 함수가 실행되는데, 세 번째 인자인 CreateInfoNULL로 들어오기 때문에 바로 함수를 종료하게 된다.

  • 메모장을 실행하게 되면 다시 Notify Routine 함수가 호출되고, 여기서 CreateInfo->ImageFileName->Buffer를 통해 파일의 전체 경로를 볼 수 있다.

    • 꼭 메모장 뿐만 아니라, 모든 프로세스가 실행될 때 등록한 Notify Routine 함수가 호출된다.

  • Notify Routine 함수에 브레이크 포인트를 걸면 프로세스를 종료할 때도 브레이크가 걸리는 것을 알 수 있다.

    • 시스템 종료를 눌러도 Notify Routine이 호출된다.

브레이크 포인트 명령어

  • bl

    • 브레이크 포인트의 리스트를 볼 수 있다.

    • 명령어를 입력하면 브레이크 포인트의 번호와, 클릭할 수 있는 Disable, Clear이 나온다.

      • Disable은 잠깐 비활성화 시키는 것이고, Clear는 브레이크 포인트를 해제하는 것이다.
  • bd 번호

    • bl 명령어로 각 브레이크 포인트의 번호를 얻을 수 있는데, 이를 인자로 받아서 브레이크 포인트를 비활성화(Disable) 시킨다.
  • be 번호

    • bd로 비활성화한 브레이크 포인트를 다시 활성화(Enable) 시킨다.
  • bc 번호

    • 브레이크 포인트를 해제(Clear) 시킨다.

참고