서론

소프트웨어 개발 분야에서, 특히 민감한 데이터를 다룰 때 메모리 보안은 매우 중요합니다. 개발자들은 종종 라이선스 키나 비밀번호와 같은 민감한 정보를 외부의 시선으로부터 보호해야 하는 상황에 직면합니다. 이러한 맥락에서 제기되는 질문은: 디스크에 페이징이 발생하지 않도록 하고 디버거를 통해 접근하기 어려운 C++에서 안전한 메모리 할당기를 어떻게 만들 수 있을까요?

이 블로그 포스트에서는 메모리 보안과 관련된 문제를 자세히 살펴보고 이러한 요구 사항을 충족하는 C++ 할당기를 구현하는 방법을 탐구하겠습니다.

도전 과제 이해하기

해결책을 논의하기 전에, 메모리를 보호할 때 수반되는 몇 가지 도전 과제를 이해하는 것이 중요합니다:

  • 디스크로의 페이징 방지: 가상 메모리를 사용하는 시스템에서는 메모리가 디스크로 페이징될 수 있으며, 이는 무단 접근에 취약해질 수 있습니다. 메모리가 페이징 아웃될 때, 이는 스왑 영역에 저장되며 다른 프로세스가 접근할 수 있는 가능성이 있습니다.

  • 디버거를 통한 접근: 디버거는 메모리에 접근하고 조작할 수 있으므로, 특히 민감한 정보의 경우 이 위험을 최소화하는 방법을 찾는 것이 매우 중요합니다.

  • 운영 체제 수준의 한계: 우리의 의도와 상관없이 운영 체제가 프로세스를 제어할 수 있으며 메모리 내용을 읽을 수 있는 가능성이 있습니다.

C++의 안전한 메모리 할당기

이러한 도전에도 불구하고, Windows API 함수를 사용하여 C++에서 사용자 정의 할당기를 구현할 수 있습니다. 솔루션을 단계별로 나누어 살펴보겠습니다:

1. VirtualAlloc 사용하기

안전하게 메모리를 할당하기 위한 주요 도구는 VirtualAlloc입니다. 이 함수는 지정된 방식으로 메모리를 할당할 수 있게 해줍니다:

  • 할당된 메모리에 대한 접근 방식을 제어하기 위해 보호 수준을 설정할 수 있습니다.
LPVOID pMem = ::VirtualAlloc(NULL, allocLen, allocType, allocProtect);

여기서 allocTypeMEM_COMMIT으로 설정되고, allocProtectPAGE_READWRITE로 설정되어 읽기 및 쓰기 접근을 가능하게 합니다.

2. 메모리 잠금

운영 체제가 메모리를 디스크로 페이징하지 못하게 하려면 VirtualLock 함수를 사용합니다:

if (pMem != NULL) {
    ::VirtualLock(pMem, allocLen);
}

그러나 VirtualLock은 할당할 수 있는 메모리 양에 대한 제한을 부과하므로, 필요에 따라 관리 가능할 수 있습니다.

3. 메모리 안전하게 해제하기

메모리를 해제할 때는, 해제하기 전에 민감한 내용을 0으로 초기화하는 것이 중요합니다. 이는 SecureZeroMemory 함수를 사용하여 수행할 수 있습니다:

::SecureZeroMemory(_pPtr, allocLen);
::VirtualUnlock(_pPtr, allocLen);
::VirtualFree(_pPtr, 0, MEM_RELEASE);

전체 예제

다음은 안전한 메모리 할당기의 완전한 구현입니다:

template<class _Ty>
class LockedVirtualMemAllocator : public std::allocator<_Ty> {
public:
    template<class _Other>
    LockedVirtualMemAllocator<_Ty>& operator=(const LockedVirtualMemAllocator<_Other>&) {
        return (*this);
    }

    template<class Other>
    struct rebind {
        typedef LockedVirtualMemAllocator<Other> other;
    };

    pointer allocate(size_type _n) {
        SIZE_T allocLen = (_n * sizeof(_Ty));
        DWORD allocType = MEM_COMMIT;
        DWORD allocProtect = PAGE_READWRITE;
        LPVOID pMem = ::VirtualAlloc(NULL, allocLen, allocType, allocProtect);
        if (pMem != NULL) {
            ::VirtualLock(pMem, allocLen);
        }
        return reinterpret_cast<pointer>(pMem);
    }

    void deallocate(void* _pPtr, size_type _n) {
        if (_pPtr != NULL) {
            SIZE_T allocLen = (_n * sizeof(_Ty));
            ::SecureZeroMemory(_pPtr, allocLen);
            ::VirtualUnlock(_pPtr, allocLen);
            ::VirtualFree(_pPtr, 0, MEM_RELEASE);
        }
    }
};

// 사용 예제
typedef std::basic_string<char, std::char_traits<char>, LockedVirtualMemAllocator<char>> modulestring_t;

결론

C++에서 안전한 메모리 할당기를 만드는 것은 다양한 시스템 수준의 제약과 보안 문제를 탐색해야 하는 복잡한 작업입니다. 메모리 접근으로부터 완전한 보호를 이루는 것은 불가능하지만, VirtualAlloc, VirtualLock, SecureZeroMemory와 같은 함수를 사용하면 민감한 데이터의 보안을 크게 향상시킬 수 있습니다.

어떤 시스템도 장치의 소유자에게 완전히 안전할 수 없다는 점을 염두에 두는 것이 중요합니다. 이러한 한계를 이해하는 것은 개발자들이 더 강력하고 회복력 있는 애플리케이션을 만드는 데 도움이 됩니다.

더 깊은 통찰력을 원하는 분들을 위해 Neil Ferguson과 Bruce Schneier의 Practical Cryptography와 같은 자료는 암호화 관행과 안전한 메모리 관리 방법론에 대해 귀중한 맥락을 제공할 수 있습니다.