Windows에서 파일을 삭제하는 방법은... 사실 두가지가 있습니다. (뭐 따지고 보면.. MoveFile하거나 CreateFile시 CreateDisposition값에 따라 파일이 Truncate되는 경우 등등... 도 결과적으로 파일이 삭제된다고 볼수 있겠지만... 뭐 명시적으로 삭제되는 건 다음의 두가지입니다.)

I. FILE_FLAG_DELETE_ON_CLOSE Flag를 지정하여 CreateFile하는 경우
우선... CreateFile을 해서 핸들을 한번 여는 것만으로 파일을 삭제할 수 있습니다.
CreateFile api는 다음과 같이 생겼죠.

HANDLE WINAPI CreateFile( _In_      LPCTSTR lpFileName, _In_      DWORD dwDesiredAccess, _In_      DWORD dwShareMode, _In_opt_  LPSECURITY_ATTRIBUTES lpSecurityAttributes, _In_      DWORD dwCreationDisposition, _In_      DWORD dwFlagsAndAttributes, _In_opt_  HANDLE hTemplateFile );

저 인자 중에서 dwFlagsAndAttributes 에 FILE_FLAG_DELETE_ON_CLOSE를 지정해서 파일 핸들을 열면... 됩니다. (커널인 경우 FLAG_DELETE_ON_CLOSE). 그러고 나면... 그 파일에 열려있는 모든 핸들이 닫히는 순간 파일이 삭제됩니다.

즉, 아래와 같이 하면 파일이 삭제된다는 것이죠.
 

한가지 재미있는 것은... 이 FILE_FLAG_DELETE_ON_CLOSE이 지정된 핸들을 열 때는... 파일 핸들의 DesiredAccess에 FILE_DELETE 같은 특정 권한을 지정하거나, ShareMode에 FILE_SHARE_DELETE를 주거나 할 필요가 전혀 없다는 것입니다. 즉, 아래와 같이 해도 파일은 잘 지워집니다.
 

보통 악성코드들이 자신을 보호하기 위해 자기 파일에 ShareMode가 0인 배타적 파일 핸들을 Open하여 파일에 Lock을 걸곤 하는데요... 다음과 같이 하더라도 Sharing Violation 없이 핸들을 열 수 있고, 결과적으로 파일을 삭제할 수 있다는 것을 의미합니다. (악성코드를 어떻게 진단할 것이냐...는 별개의 문제로 칩니다. ^^;;)



II. ZwSetInformationFile 을 이용해 삭제하는 방법
뭐 가장 일반적인 방법이라고 할 수 있죠. DeleteFile api를 호출하는 방법이 여기에 속합니다.

일단 ZwSetInformationFile api는 이렇게 생겼습니다.
NTSTATUS ZwSetInformationFile(
  _In_   HANDLE FileHandle,
  _Out_  PIO_STATUS_BLOCK IoStatusBlock,
  _In_   PVOID FileInformation,
  _In_   ULONG Length,
  _In_   FILE_INFORMATION_CLASS FileInformationClass
);

일단 파일에 대한 핸들을 열구요... 그 핸들을 이용해 ZwSetInformationFile을 호출하는데요... (그러기 위해선 핸들을 열때 DELETE 권한을 설정해서 열어야 합니다.)


일단 FileInformationClass에는 FileDispositionInformation 값을 지정합니다. 

그러고서 FileInformation에는 FILE_DISPOSITION_INFORMATION 구조체의 주소를 설정하는데요... 이때 DeleteFile 멤버를 TRUE로 설정해줍니다.

typedef struct _FILE_DISPOSITION_INFORMATION {
  BOOLEAN DeleteFile;
} FILE_DISPOSITION_INFORMATION, *PFILE_DISPOSITION_INFORMATION;

DeleteFile을 TRUE로 설정하여 이 api를 호출하게 되면... 파일은 "DeletePending" Mark가 찍히게 되고 이후에 언젠가 그 파일에 대한 모든 핸들이 닫힐 때 파일이 삭제되게 됩니다. 말하자면... "삭제 예정"이라고 표시만 해 놨다가 나중에 핸들이 닫힐 때 (=더 이상 사용되지 않을때) 실제로 지운다 이거죠.


현재 어떤 파일이 DeletePending 상태인지 여부는... ZwQueryInformationFile이라는 api에 FileInformationClass인자에 FileStandardInformation 값을 주고 호출해 보면 알 수 있습니다. 

typedef struct _FILE_STANDARD_INFORMATION {
  LARGE_INTEGER AllocationSize;
  LARGE_INTEGER EndOfFile;
  ULONG         NumberOfLinks;
  BOOLEAN       DeletePending;
  BOOLEAN       Directory;
} FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION;


그런데... 저 위에 나왔던  FileDispositionInformation이란 클래스에 DeleteFile이라는  BOOLEAN값을 설정하게 되어 있는 게... 좀 이상하게 생겼네요. 그렇다면 저 DeleteFile 인자 값에 FALSE를 주고 호출하면 어떻게 될까요??


그것은... 이미 DeleteFile에 TRUE를 주어 호출한 적이 있는, 즉 DeletePending = TRUE인 파일의 DeletePending값을 다시 FALSE로 Reset한다는 의미가 되겠습니다~~~!!!


인자를 저렇게 줘서 호출할 일이 진짜로 생길까요...??


네~ 물론 생깁니다.


일반 파일을 탐색기에서 "Delete" 키를 눌러서 삭제하면... 모두다 아시다시피 파일은 즉시 지워지지 않고 휴지통에 들어가게 되는데요... 이것은 DeleteFile이 아닌 MoveFile Operation이 되겠습니다.

그런데... 이 MoveFile이 이루어지기 전에 DeleteFile을 먼저 시뮬레이션을 해 보고 성공했을 때에 한해서 성공한 DeleteFile을 Cancel시킨 후 휴지통으로 MoveFile이 진행되게 됩니다. (아래 캡쳐이미지 참고)




이런 문제들이 소프트웨어 개발시에도 영향을 미칠 수 있습니다.


예를 들어... 파일 삭제 행위를 로깅하는 기능을 개발한다고 치겠습니다.

파일 삭제 행위를 취소할 수 있다는 것은... 파일 삭제를 로깅하기 위해서는 미니필터에서 SetInformationFile의 FileDispositionInformation class가 아니라 사실은 Cleanup 시점에서 로깅해야 된다는 것을 의미합니다. 어떤 파일이 삭제되었다고 로그를 남겼는데 나중에 삭제가 취소되어 그 파일이 그대로 남아있는 상황이 생길 수도 있으니까요. 

그렇다면 Stream Context나 File Context에 DeleteFile 플래그를 기록해놨다가 최종적으로 핸들이 닫힐때의 플래그 값이 뭔지를 확인한다던지... 이런 식이 되어야겠네요. ^^;;









Posted by kuaaan
,


사랑합니다. 편안히 잠드소서