본문 바로가기

My Study/Programming&Theory

ZwProtectVirtualMemory 의 악몽

ZwProtectVirtualMemory... 이 함수 때문에 벌써 두 번이나 애를 먹었네요.


일단 저 함수를 쓰려는 시나리오는 다음과 같습니다.


커널 드라이버에서 특정 프로세스의 유저영역 메모리에 접근해 text section 값을 변경하려고 했습니다.

이를 위해 ZwProtectVirtualMemory를 사용해 WRITE 권한을 주소 ZwWriteVirtualMemory를 사용해 메모리를 수정하려는 작업니다.


간략한 pseudo-code를 보겠습니다.

ZwProtectVirtualMemory(

hProcess,

&TargetAddress,

&memSize,

PAGE_EXECUTE_READWRITE,

&ulOldProtect

);


ZwWriteVirtualMemory(

hProcess,

TargetAddress,

SourceBuffer,

memSize,

&szWrittenBytes

);


위와 같이 썼을 경우 정상적으로 작동할까요?

user에서 사용할 때처럼 VirtualProtectEx, WriteProcessMemory와 같은 결과가 나오면 좋겠지만 그렇지 않습니다.

각각 함수의 원형을 보겠습니다.

NTSTATUS NTAPI ZwWriteVirtualMemory (

IN HANDLE ProcessHandle,

IN PVOID BaseAddress,

IN PVOID Buffer,

IN SIZE_T NumberOfBytesToWrite,

OUT PSIZE_T NumberOfBytesWritten OPTIONAL 

);


NTSTATUS NTAPI ZwProtectVirtualMemory (

IN HANDLE ProcessHandle,

  IN OUT PVOID *BaseAddress,

  IN OUT PSIZE_T ProtectSize,

IN ULONG NewProtect,

OUT PULONG OldProtect

);


ZwProtectVirtualMemory의 인자를 보면 BaseAddress와 ProtectSize가 IN OUT 인자들입니다.

OUT? 뭘 OUT하지? 확인해본결과


ZwProtectVirtualMemory는 페이지 단위로 보호 옵션을 바꿉니다. ( 당연히 윈도우에선 주소마다가 아닌 페이지 단위로 보호옵션을 나타낼 수 있기 때문에 User에서 VirtualProtect를 써도 페이지 단위로 적용됩니다. ) 그러므로 ProtectSize를 10으로 넘겨도 0x1000 만큼 적용이 된다는 것입니다. 바로 BaseAddress부터..가 아니라 BaseAddress를 포함하는 페이지 베이스 주소부터입니다.

예를 들어, 

BaseAddress => 0x770FE334

ProtectSize => 0x30


해도 Protect가 적용되는 부분은


BaseAddress => 0x770FE000

ProtectSize => 0x1000


이렇게 됩니다.

그리고 저 결과를 OUT 해주는 것이지요 ^^;; 고로 BaseAddress와 ProtectSize가 변경되게 됩니다.

이 변경된 결과를 가지고 아래서 Write를 하고 있으니 잘못된 주소에 Write 하고 있을 뿐더러 SourceBuffer에서 0x1000만큼 꺼내는 과정에서 SourceBuffer 크기가 0x1000이 안된다면 잘못된 영역에 접근하게 되면서 뻑이나게 됩니다.


다들 조심하고 사용합시다.


결론 : IN OUT 으로 된 인자는 사용할 때 조심하고 또 조심하자!