관리 메뉴

kisoo

Scanner minifilter 본문

01.About Programming /2.Kernel Lab

Scanner minifilter

JamesK78 2011. 3. 29. 14:14

 

SCANNER (MiniFilter)

 

DriverEntry

모든 DriverEntry는 이와 비슷한 작업을 한다.
  • FltRegisterFilter()

    • FltCreateCommunicationPort()

      • FltBuildDefaultSecurityDescriptor() : 관리자만이 접근 가능하도록
      • InitializeObjectAttributes() : OBJ_KERNEL_HANDLE을 통해 커널 핸들임을 알려야 한다.
  • FltStartFiltering()

    • FltCloseCommunicationPort()
  • FltUnregisterFilter()

 

Structure

FLT_OPERATION_REGISTRATION

Scanner에서는

  • IRP_MJ_CREATE (Pre, Post)
  • IRP_MJ_CLEANUP (Pre)
  • IRP_MJ_WRITE (Pre)

만을 처리한다. CLEANUP과 WRITE는 Post 처리를 하지 않는다.

 

FLT_CONTEXT_REGISTRATION

사용자가 정의한 SCANNER_STREAM_HANDLE_CONTEXT를 등록하는데, 이 구조체에는 ReScan에 대한 BOOLEAN 변수가 하나 들어있다.

 

FLT_REGISTRATION

Callbacks와 Context를 등록한다. 각각 위의 구조체로 선언되는 것이다.

그리고 InstanceSetup, InstanceQueryTearDown, FilterUnload, InstanceTeardownStart, InstanceTeardownComplete 등의 콜백을 선언한다.

 

Port & Instance

ScannerPortConnect

PsGetCurrentProcess() : UserProcess에 프로세스 ID 저장

ClientPort 기억

 

ScannerPortDisconnect

FltCloseClientPort()

UserProcess 초기화

 

ScannerUnload

FltCloseCommunicationPort()

FltUnregisterFilter()

 

ScannerInstanceSetup

Network volume은 attach하지 않고 나머지 volume들만 attach.

STATUS_FLT_DO_NOT_ATTACH / STATUS_SUCCESS

 

ScannerQueryTearDown

STATUS_SUCCESS만을 return.

이것만으로도 사용자는 fltmc detach 명령어를 통해 scanner instance를 volume으로부터 제거할 수 있다.

 

Callbacks

ScannerPreCreate
  • FLT_PREOP_SUCCESS_NO_CALLBACK : UserProcess가 IO를 요청한 것이면
  • FLT_PREOP_SUCCESS_WITH_CALLBACK : otherwise

을 return. Post를 실행할 것이냐 말 것이냐에 대한 return value이다.

 

ScannerPostCreate
  • 우선 FLT_CALLBACK_DATA를 점검하여 IoStatus를 본다. 상태가 NT_SUCCESS가 아니면 파일을 여는데 실패한 것이므로 스캐닝을 할 필요도 없다. STATUS_REPARSE인지 별도로 보는 이유는 STATUS_REPARSE는 SUCCESS 리턴 중에 하나이기 때문인데 이 리턴 값은 SUCCESS 임에도 불구하고 파일을 여는데는 실패했다는 의미이다.
  • FltGetFileNameInformation() : 파일 이름을 얻어온다.

    • FltParseFileNameInformation()
  • ScannerpCheckExtension() : 관심있는 확장자만 걸러낸다.
  • ScannerpScanFileInUserMode() : ScanUser에게 파일을 스캔하도록 한다.
  • safeToOpen을 돌려받는데, 이것이 false이면

    • FltCancelFileOpen()
    • IoStatus : STATUS_ACCESS_DENIED
  • 아니더라도 열린 파일이 쓰기 권한을 가지는지 확인하고 (http://msdn.microsoft.com/en-us/library/aa906961.aspx), 그렇다면

    • FltAllocateContext()

      • scannerContext에 RescanRequired를 TRUE로 설정
    • FltSetStreamHandleContext()
    • FltReleaseContext()

 

ScannerPreCleanup
  • FltGetStreamHandleContext()
  • ReScanRequired가 set이라면

    • ScannerpScanFileInUserMode()
    • 검색된다하더라도 어떤 작업도 하지 않는다는 점에 주의

      • http://blog.naver.com/PostView.nhn?blogId=process3&logNo=20063999562
      • Minifilter drivers must never fail IRP_MJ_CLEANUP or IRP_MJ_CLOSE operations. These operations can be pended, returned to the filter manager, or completed with STATUS_SUCCESS. However, a preoperation callback routine must never fail these operations.
      • ScannerPreWrite의 같은 코드 조각과 비교해볼 것.
  • FltReleaseContext()

 

ScannerPreWrite
  • ClientPort가 NULL이면 FLT_PREOP_SUCCESS_NO_CALLBACK 리턴.
  • FltGetStreamHandleContext()

    • 실패하면 역시 같은 값 리턴.
  • Data->Iopb->Parameters.Write.Length가 0인지 점검 (FLT_PARAMETERS for IRP_MJ_WRITE: http://msdn.microsoft.com/en-us/library/ms793938.aspx)

    • Length: 쓰여질 데이터의 길이
    • WriteBuffer: 실제 데이터를 담고 있는 포인터
    • MdlAddress: MDL(Memory Descriptor List)의 주소. 여기서의 MDL은 WriteBuffer가 포인팅하고 있는 버퍼를 describe.
  • 데이터가 있으면, MdlAddress 멤버가 NULL인지 점검.

  • ExAllocatePoolWithTag() : SCANNER_NOTIFICATION을 NonPagedPool에 할당.
  • notification->BytesToScan 설정
  • RtlCopyMemory() : notification->Contents에 buffer를 복사
  • FltSendMessage()
  • Reply로 돌아온 safe 변수값을 점검

 

그 외, 편의를 위한 함수들

ScannerpCheckExtension

확장자 비교

 

ScannerpScanFileInUserMode
  • SafeToOpen을 TRUE로 초기화.
  • ClientPort가 NULL이면 그대로 return. 즉, 클라이언트가 동작하고 있지 않으면 언제나 SafeToOpen은 TRUE가 된다.
  • FltGetVolumeFromInstance()
  • FltGetVolumeProperties() : http://msdn.microsoft.com/en-us/library/aa488600.aspx

    • STATUS_BUFFER_OVERFLOW는 warning code이므로 그대로 진행. 이름 정보 외에는 얻어낼 수 있음.
    • STATUS_BUFFER_TOO_SMALL은 error code이므로 더 이상 진행할 수 없음. 이 경우 어떤 정보로 얻어지지 않는다.
  • length는 SCANNER_READ_BUFFER_SIZE(1024)와 volume property의 SectorSize중 큰 값을 취한다.

    • SectorSize는 실험결과 512
  • FltAllocatePoolAlignedWithTag() : Non-cached I/O를 위한 device-aligned buffer를 할당하는 함수 (http://msdn.microsoft.com/en-us/library/aa488566.aspx)

    • FltReadFile(), FltWriteFile()는 non-cached I/O
  • FltReadFile() : 해당 파일을 읽어온다.

    • offset : 0
    • length : 1024 (SectorSize의 배수여야 한다. 512 * 2)

      • 1024 bytes 이후는 검색하지 않기 때문에 foul 이 탐지되지 않는다.
      • 1025.txt
    • FLTFL_IO_OPERATION_NON_CACHED | FLTFL_IO_OPERATION_DO_NOT_UPDATE_BYTE_OFFSET
  • 읽기가 성공하였고 읽은 byte 수가 0이 아니면 계속 진행
  • notification.BytesToScan에 bytesRead를 저장. 이 값은 FltReadFile에서 얻은 값.
  • RtlCopyMemory() : buffer에 읽은 파일 내용을 읽은 만큼 notification.Contents에 저장.
  • FltSendMessage()

    • SenderBuffer와 Reply Buffer에 모두 notification 변수를 이용함에 유의.
    • Reply는 단순히 BOOLEAN값 하나이다. (SafeToOpen)
  • FltFreePoolAlignedWithTag() / FltObjectDereference()

 

 SCANUSER (User Program)

Usage

사용법 출력.

 

ScanBuffer
  • NULL 문자를 길이 계산에서 제외
  • 버퍼를 초과해서 검색하지 않도록 검색 문자의 길이만큼 뒤를 남기고 검색

    • 단순 순차 비교
  • 있으면 TRUE, 없으면 FALSE

 

main

Overlapped I/O

http://msdn.microsoft.com/en-us/library/ms740087.aspx (socket 기반으로 설명되어 있으나 같은 I/O 관점에서 읽어볼만 함)

사용자 공간이 할당되지 않았는데 데이터가 먼저 도착한다면 버퍼에 잠시 저장했다가 후에 요청할 때 복사해줄 수 있다.

 1.jpg

데이터가 전달되기 전에 데이터를 받기 위한 공간을 할당한다면 데이터를 버퍼에 담지 않고 그대로 가져갈 수 있는 이점이 있다. 데이터를 일단 버퍼에 담고 다시 함수가 호출될 때마다 사용자의 공간으로 복사하는 것보다는 훨씬 효율적일 것이다.

 2.jpg

IO_PENDING은 overlapped operation이 정상적으로 초기화되었다는 의미

receive function은 여러 번 호출되어 receive buffer를 존비할 수 있으며, send function은 여러 번 호출되어 queue에 보낼 내용을 담을 수 있다.

 

OVERLAPPED structure

http://msdn.microsoft.com/en-us/library/ms684342.aspx

사용하기 전에 항상 zero로 초기화해야 한다.

 

함수 분석

3.jpg 

 

첫 번째 parameter는 request count (5), 두 번째는 thread count (2).

  • FilterConnectCommunicationPort()

    • 첫 번째 파라미터에 port name
    • 마지막 파라미터에서 port를 얻어온다
  • CreateIoCompletionPort()

    • 핸들은 위의 함수에서 얻어온 port (여기에 지정되는 핸들은 반드시 overlapped I/O가 지원되어야 한다)
    • thread count 지정
  • context에 port와 completion 저장
  • thread count만큼 CreateThread()

    • 각 thread마다 request count만큼 다음 작업 수행

      • SCANNER_MESSAGE malloc
      • OVERLAPPED 부분 zero로 초기화
      • FilterGetMessage()

        • 핸들은 당연히 받았던 port 이용
        • 메시지를 받아올 주소를 넘기는데, 반드시 FILTER_MESSAGE_HEADER를 포함하고 있어야 한다.
        • 받아올 바이트 수 : FIELD_OFFSET(SCANNER_MESSAGE, Ovlp)라고 한 것에 주의

          • Ovlp 부분은 안 쓴다는 이야기가 된다.
        • Ovlp를 지정하면 asynchronous I/O를 하겠다는 의미.

          • NULL로 하면 어떻게 될까?

            • 메시지를 받을 때까지 block
          • 지정했는데 메시지가 없으면?

            • ERROR_IO_PENDING
      • ERROR_IO_PENDING이 리턴되지 않으면 정리하고 종료.
  • WaitForMultipleObjects()

    • 만든 thread 수 만큼
    • threads 핸들을 넘겨주고
    • 모든 핸들에서 신호가 왔을 때만 return
    • 기다리는 시간은 INFINITE
  • 모든 thread가 종료되면 비로소 main도 종료.

 

ScannerWorker

Worker threads

 크기변환_사본_-20090520_211355.jpg

Worker의 수 조절이 중요하다: 너무 많으면 system thrash. 너무 적으면 큐가 점점 커질 것이다.

Processor 수만큼 잡는 것이 적절할 가능성이 높다.

Thread queue는 LIFO 방식으로 돈다. 사용되지 않는 thread는 계속 사용되지 않을 가능성이 높다.

I/O Completion Ports는 process간의 공유는 불가능하지만 한 process 안에서 여러 thread가 공유할 수는 있다.

CreateIoCompletionPort에서 NumberOfConcurrentThreads를 정한다. 이 개수만큼의 WORKER가 동시 작업을 할 수 있게 될 것이다.

 

  • 무한 루프를 돈다.

    • GetQueuedCompletionStatus()

      • 위 그림에서 WORKER가 각자 일을 받아가는 것을 이 함수를 통해서 한다고 생각하면 된다.
      • Return value가 0이 아니면 completion port로부터 성공한 I/O operation에 대한 completion packet을 받아냈다는 것.

         

    • CONTAINING_RECORD() 매크로

      • 우리가 얻은 것은 OVERLAPPED에 대한 포인터뿐이다. 따라서 SCANNER_MESSAGE의 시작 위치를 알기 위해서는 이 매크로를 사용해야 한다.
    • OVERLAPPED의 InternalHigh는 에러 없이 I/O 요청이 완료된 경우, 전송된 총 바이트 수를 가지고 있다.

      • 1048이 계속 전달되었음. SCANNER_MESSAGE structure

        • FILTER_MESSAGE_HEADER = 16
        • SCANNER_NOTIFICATION structure: ULONG BytesToScan + ULONG Reserved + UCHAR Contents[SCANNER_READ_BUFFER_SIZE] = 4 + 4 + 1024 = 1032
        • OVERLAPPED = 20 (Count되지 않는다)
    • ScanBuffer() : foul이 있나 비교
    • FilterReplyMessage()
    • OVERLAPPED 구조체 부분 초기화
    • FilterGetMessage() : 받기 요청

      • ERROR_IO_PENDING 이 아니면 무한 루프를 빠져나간다.
  • ERROR_INVALID_HANDLE인지 점검. 그렇다면 port가 닫힌 것.

     

 퍼옴 : http://john6.springnote.com/pages/3431601.xhtml

이 글은 스프링노트에서 작성되었습니다.

Comments