[cpp] 안티 덤프

@codemaru · July 30, 2011 · 5 min read

덤프(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

사실 실질적으로 덤프를 방지하기 위해서는 위와 같은 코드가 크게 도움이 되지 않는다.

  1. SizeOfImage 필드는 진짜 거의 의미가 없는데 왜냐하면 EXE이 DLL이 매핑된 섹션 영역을 구해서 덤프를 뜨는 경우가 많기 때문이다. 따라서 실질적으로 덤프 프로그램이 SizeOfImage를 참조하는 일은 잘 없다고 보는게 옳다.
  2. AddressOfEntryPoint는 효과는 있겠지만 그 효과가 제한적이다. 왜냐하면 대부분 CRT 코드에 연결해서 사용하기 때문에 패턴 검색을 통해서 진입점을 추론할 수 있는 경우가 많기 때문이다. 그리고 DLL 같은 경우에는 로딩/언로딩에 엔트리가 계속 호출되기 때문에 위와 같이 제거해 버리면 문제가 발생할 소지가 있기도 하다.

덤프 대응력을 높이는 실질적인 방법으로는 ASLR을 사용하고 재배치 정보를 숨기는 것이 있다. 이렇게 할 경우 덤프를 뜬 대상체도 원본이 있었던 주소에서만 정상적으로 실행할 수 있기 때문에 덤프를 뜬 것을 재사용하기 위해서는 제약이 따른다. 다른 방법으로는 이미지 밖의 다른 주소 영역을 참조하는 포인트를 만드는 방법이 있을 수 있다. 이 경우에는 덤프를 원본 이미지 영역만 추출하게 되면 같이 딸려 있는 메모리가 추출되지 않았기 때문에 참조 포인터를 추론하기가 쉽지 않아서 효과적이다. 물론 이 두가지 방법다 이미지 로딩 전처리기 단계에서 뭔가를 프로세싱 해야 하기 때문에 위에 있는 것보다는 훨씬 더 복잡한 내용이다.

끝으로 사족을 하나 더 달자면 원론적인 관점에서 덤프를 방지할 수 있는 방법은 사실상 없다. 덤프 뜬 걸 분석, 재활용 하는 작업을 얼마나 더 어렵게 만들 것인가의 관점에서 접근하는 것이 옳다.

@codemaru
돌아보니 좋은 날도 있었고, 나쁜 날도 있었다. 그런 나의 모든 소소한 일상과 배움을 기록한다. 여기에 기록된 모든 내용은 한 개인의 관점이고 의견이다. 내가 속한 조직과는 1도 상관이 없다.
(C) 2001 YoungJin Shin, 0일째 운영 중