덤프(dump)란 메모리 상의 내용을 그대로 파일에 기록하는 것을 말한다. 이 또한 공격자들이 실행 파일을 분석하기 위해서 사용하는 한 가지 방법이다. 알려지지 않은 실행 압축 프로그램을 사용한 실행 파일도 덤프 기법을 사용하면 쉽게 압축이 해제된 원본 코드를 저장할 수 있기 때문이다.
공격 기법이 있으면 응당 그에 대한 방어 기법이 있기 마련이다. 어떤 형태로 덤프를 막을 수 있을지 한번 생각해보자. 여러 가지 방법이 있지만 그 중에 가장 간단한 방법은 덤프에 사용되는 정보를 삭제하는 방법이다. <리스트 2>에 이 방법을 구현한 소스 코드가 나와 있다. 코드를 살펴보면 NT 헤더의 OptionalHeader 부분의 SizeOfImage와 AddressOfEntryPoint 부분을 제거하여 덤프 프로그램이 이미지 크기와 실행 파일의 시작 코드를 추척하기 힘들도록 만들고 있다.
리스트 2 안티덤프 소스
#include "windows.h"
#include "tchar.h"
#include "stdio.h"
PVOID GetPtr(LPCVOID addr, SIZE_T offset)
{
return (PVOID)((DWORD_PTR) addr + offset);
}
int main()
{
HANDLE module = GetModuleHandle(NULL);
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(module, &mbi, sizeof(mbi));
DWORD old;
BOOL b = VirtualProtect(module
, mbi.RegionSize
, PAGE_READWRITE
, &old);
PIMAGE_DOS_HEADER dos;
PIMAGE_NT_HEADERS nt;
PIMAGE_SECTION_HEADER sec;
dos = (PIMAGE_DOS_HEADER) module;
nt = (PIMAGE_NT_HEADERS) GetPtr(module, dos->e_lfanew);
sec = (PIMAGE_SECTION_HEADER) GetPtr(nt, sizeof(*nt));
nt->OptionalHeader.SizeOfImage = 0;
nt->OptionalHeader.AddressOfEntryPoint = 0;
for(;;)
{
printf("hello\n");
Sleep(1000);
}
return 0;
}
💡 2015-06-26
사실 실질적으로 덤프를 방지하기 위해서는 위와 같은 코드가 크게 도움이 되지 않는다.
- SizeOfImage 필드는 진짜 거의 의미가 없는데 왜냐하면 EXE이 DLL이 매핑된 섹션 영역을 구해서 덤프를 뜨는 경우가 많기 때문이다. 따라서 실질적으로 덤프 프로그램이 SizeOfImage를 참조하는 일은 잘 없다고 보는게 옳다.
- AddressOfEntryPoint는 효과는 있겠지만 그 효과가 제한적이다. 왜냐하면 대부분 CRT 코드에 연결해서 사용하기 때문에 패턴 검색을 통해서 진입점을 추론할 수 있는 경우가 많기 때문이다. 그리고 DLL 같은 경우에는 로딩/언로딩에 엔트리가 계속 호출되기 때문에 위와 같이 제거해 버리면 문제가 발생할 소지가 있기도 하다.
덤프 대응력을 높이는 실질적인 방법으로는 ASLR을 사용하고 재배치 정보를 숨기는 것이 있다. 이렇게 할 경우 덤프를 뜬 대상체도 원본이 있었던 주소에서만 정상적으로 실행할 수 있기 때문에 덤프를 뜬 것을 재사용하기 위해서는 제약이 따른다. 다른 방법으로는 이미지 밖의 다른 주소 영역을 참조하는 포인트를 만드는 방법이 있을 수 있다. 이 경우에는 덤프를 원본 이미지 영역만 추출하게 되면 같이 딸려 있는 메모리가 추출되지 않았기 때문에 참조 포인터를 추론하기가 쉽지 않아서 효과적이다. 물론 이 두가지 방법다 이미지 로딩 전처리기 단계에서 뭔가를 프로세싱 해야 하기 때문에 위에 있는 것보다는 훨씬 더 복잡한 내용이다.
끝으로 사족을 하나 더 달자면 원론적인 관점에서 덤프를 방지할 수 있는 방법은 사실상 없다. 덤프 뜬 걸 분석, 재활용 하는 작업을 얼마나 더 어렵게 만들 것인가의 관점에서 접근하는 것이 옳다.