이번 내용은 MMF(Memory Mapped File) 을 이용한 프로세스간의 메모리 공유입니다. 일전에 설명드렸다시피(링크) Win32 이상의 환경에서는 프로세스의 주소공간은 독립적으로 관리되기 때문에 어떤 하나의 프로세스는 다른 프로세스의 주소공간을 공유할 수 없습니다. A라는 프로세스의 0x1234라는 주소값은 B라는 프로세스의 0x1234와는 전혀 다른 공간입니다. 이런 메모리 관리 구조는 OS를 안정적이고 견고하게 만들어주었지만 한편으로는 프로그램 사이에 정보를 주고 받기가 어렵게 되어버렸습니다. 프로세스 사이에 정보를 공유하기 위해서 MMF라는 방식을 사용합니다. MMF는 이름 그대로 파일을 메모리에 맵핑하기 위해서 사용되는 기법입니다. 간단하게 설명하자면, 파일을 열어서 해당 파일을 메모리에 맵핑시켜서 사용하는 것입니다. MMF를 이용해서 하드디스크의 파일을 프로세스의 주소 공간에 연결해서 사용이 가능합니다. 하지만 여기서는 파일을 여는 것이 아닌 프로세스가 서로 접근할 수 있는 메모리의 공유 영역을 생성하기 위해서 사용하겠습니다. File Mapping object는 Named kernel object로 생성되면 모든 프로세스에 영향을 줍니다. 뮤텍스와 유사한데 한 쪽 프로세스에서 뮤텍스를 생성하면 모든 프로세스에 영향을 주듯이 이것도 같은 이름의 File Mapping object는 어느 프로세스나 사용이 가능합니다. 전에 커널 영역은 접근이 불가능하다고 했는데, Windows가 제공하는 함수들을 이용해서 정해진 방법으로 커널 오브젝트에 접근이 가능합니다. 그럼 예제를 먼저 보도록 하겠습니다. 일단 A 프로세스의 코드입니다.
그리고 B 프로세스의 코드입니다.
중요한 WndProc과 정의 부분만 캡쳐해서 올립니다.
일단 A 프로세스에서는 WM_CREATE부분에서 버튼 하나를 만들어주고
CreateFileMapping 함수를 이용해서 메모리 영역을 맵핑시키는데 중요한 것은
맨 처음 인자를 INVALID_HANDLE_VALUE(0xFFFFFFFF)로 주는 것이 핵심입니다.
일반적인 상황이라면 CreateFile에서 반환되는 핸들을 넣지만 여기서는 저 값을 넣어줍니다.
그리고 MapViewOfFile 함수를 이용해서 파일(여기서는 메모리의 일정 부분)의 뷰를
주소 공간에 맵핑시킵니다.
그럼 모든 준비는 끝나고 마우스로 버튼을 클릭하게 되면
MapViewOfFile 함수가 리턴하는 포인터에 문자열을 입력합니다.
_T("Message from ProcA")라는 문자열이 포인터가 가리키는 메모리 영역에 저장이 될 것입니다.
그리고 ProcB의 핸들(Window Handle)을 찾아서 거기에 메시지를 날립니다.
B에서 A가 기록한 문자열을 읽게 하기 위해서죠.
B 프로세스는 CreateFileMapping 대신에 OpenFileMapping을 사용하고 있습니다.
이미 생성된 object를 단순하게 열기만 할 경우에는 Open류의 함수를 이용하시면 됩니다.
WM_SHAREDMEMORY 메시지가 들어오면 B 프로세스 또한 MapViewOfFile 가 리턴한
포인터를 이용해서 해당 메모리 부분을 읽습니다.
일반적으로 A의 주소 공간과 B의 주소 공간은 완전히 격리되어있어서
A의 주소 공간을 B는 절대 볼 수 없지만 Name kernel object를 이용해서 공유가 가능해졌습니다.
그리고 종료시킬 때에는 시작시의 역순으로 UnmapViewOfFile 함수를 호출하고
CloseHandle로 열려진 핸들을 닫는 것으로 끝이 납니다.
어려운 것 같지만 차근차근 읽어보면 전혀 어렵지 않습니다.
Windows가 프로세스의 주소 공간을 실제 가상 메모리(실제 메모리)에 맵핑시켜주는데
이름을 갖는 커널 오브젝트가 같은 실제 가상 메모리에 연결되는 점을 이용해서
어디에서나 같은 가상 메모리를 가리키게 처리해주고 그걸 그냥 읽고 쓰기만 하는겁니다.
간단하게 보자면 A 프로세스가 파일에 기록을 하고 메시지를 날리면
B는 파일의 내용을 읽는 것으로도 프로세스간 데이터의 공유는 가능합니다.
하지만 그것은 조금 비효율적일 수 있기 때문에 위와 같은 방법을 이용해서 공유를 하는것이죠.
IAT(Import Address Table) Hooking: IAT 에 적혀있는 API 의 주소를 자신의 함수주소로 바꾸고 자신의 함수 끝에 다시 원래 API 주소로 돌려주는 방식. 가장 일반적으로 바이러스에서 사용하는 기법.
Inline Function Hooking (Detour Hooking): 사용할 API 의 첫 5바이트를 자신의 함수주소로 Jmp 하는 코드로 바꾸고 자신의 코드에서 다시 원래 API 의 바뀐 코드를 수정해주고 API 시작위치로 돌려주는 방식. IAT 후킹보다 지능적이여서 찾아내기가 쉽지 않다. 요새 많이 등장한다.
Kernel-Mode Hooking (루트킷)
SSDT(System Service Descriptor Table Modification): SSDT 가 가리키는 주소를 후킹 함수의 주소로 바꾸고 그 함수 호출후 다시 원래 커널 API 의 주소로 돌려주는 기법. 50% 이상의 루트킷이 사용하는 기법. 이런 기법은 프로세스, 파일의 은폐에 많이 사용됨.
DKOM(Direct Kernel Object Modification): 커널 Object 를 직접 조작해서 실행되는 프로세스, 스레드, 서비스, 포트, 드라이버 및 핸들의 Entry 를 실행리스트(PsActiveProcessHead, PsActiveModuleHead....)에서 감추는 기법.
SYSENTER: 유저모드에서 시스템 호출로 넘어갈때 INT 2E(for Windows 2000)/ SYSENTER 를 사용하게 되는데 호출후 시스템 서비스의 핸들러는 IA32_SYSENTER_EIP 라는 레지스터리에 저장된다. 커널 드라이버를 설치하여 해당 값을 수정하여 루트킷을 호출하고 다시 원래 값으로 돌려주는 기법.
Filter Device Drivers: 시큐리티 제품의 하단에 filter device driver 로 등록하는 기법이다. 부트 타임에 로드됨으로써 다른 어떤 안티바이러스 제품보다 먼저 실행된다.
Runtime Detour Patching: 커널 메모리를 직접 조작함으로써 그 메모리의 포인터가 루트킷을 가르키게 함으로써 커널 함수들을 후킹하는 기법. 예를 들면 Exception 을 일으키고 Exception Handle 을 컨트롤하는 IDT 레지스터를 자신을 가리키는 주소로 써줌으로써 후킹목적을 달성한다.
IRP table Modification: 디바이스 드라이버가 네트웍 패킷을 처리하거나 파일을 쓸때 사용하는 I/O Request Packets을 제어하는 Dispatch Routine 은 DEVICE_OBJECT 구조체에 저장된다. 바이러스에서 사용하는 루트킷은 IoGetDeviceObjectPointer 란 API를 사용하여 DEVICE_OBJECT 구조체에서 DRIVER_OBJECT 의 위치를 선정해줄수 있다. 즉 다른 Original Driver Call 이 일어나기 전에 자신의 루트킷을 먼저 실행하여 Call 결과를 조 작한다.