서론
소프트웨어 개발 분야에서, 특히 민감한 데이터를 다룰 때 메모리 보안은 매우 중요합니다. 개발자들은 종종 라이선스 키나 비밀번호와 같은 민감한 정보를 외부의 시선으로부터 보호해야 하는 상황에 직면합니다. 이러한 맥락에서 제기되는 질문은: 디스크에 페이징이 발생하지 않도록 하고 디버거를 통해 접근하기 어려운 C++에서 안전한 메모리 할당기를 어떻게 만들 수 있을까요?
이 블로그 포스트에서는 메모리 보안과 관련된 문제를 자세히 살펴보고 이러한 요구 사항을 충족하는 C++ 할당기를 구현하는 방법을 탐구하겠습니다.
도전 과제 이해하기
해결책을 논의하기 전에, 메모리를 보호할 때 수반되는 몇 가지 도전 과제를 이해하는 것이 중요합니다:
-
디스크로의 페이징 방지: 가상 메모리를 사용하는 시스템에서는 메모리가 디스크로 페이징될 수 있으며, 이는 무단 접근에 취약해질 수 있습니다. 메모리가 페이징 아웃될 때, 이는 스왑 영역에 저장되며 다른 프로세스가 접근할 수 있는 가능성이 있습니다.
-
디버거를 통한 접근: 디버거는 메모리에 접근하고 조작할 수 있으므로, 특히 민감한 정보의 경우 이 위험을 최소화하는 방법을 찾는 것이 매우 중요합니다.
-
운영 체제 수준의 한계: 우리의 의도와 상관없이 운영 체제가 프로세스를 제어할 수 있으며 메모리 내용을 읽을 수 있는 가능성이 있습니다.
C++의 안전한 메모리 할당기
이러한 도전에도 불구하고, Windows API 함수를 사용하여 C++에서 사용자 정의 할당기를 구현할 수 있습니다. 솔루션을 단계별로 나누어 살펴보겠습니다:
1. VirtualAlloc 사용하기
안전하게 메모리를 할당하기 위한 주요 도구는 VirtualAlloc
입니다. 이 함수는 지정된 방식으로 메모리를 할당할 수 있게 해줍니다:
- 할당된 메모리에 대한 접근 방식을 제어하기 위해 보호 수준을 설정할 수 있습니다.
LPVOID pMem = ::VirtualAlloc(NULL, allocLen, allocType, allocProtect);
여기서 allocType
은 MEM_COMMIT
으로 설정되고, allocProtect
는 PAGE_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와 같은 자료는 암호화 관행과 안전한 메모리 관리 방법론에 대해 귀중한 맥락을 제공할 수 있습니다.