Introducción
En el ámbito del desarrollo de software, especialmente al manejar datos sensibles, la seguridad de la memoria es fundamental. Los desarrolladores a menudo se encuentran en situaciones donde necesitan proteger información sensible como claves de licencia o contraseñas de miradas curiosas. Una pregunta que surge en tales contextos es: ¿Cómo podemos crear un asignador de memoria segura en C++ que prevenga la paginación al disco y sea difícil de acceder a través de depuradores?
En esta publicación del blog, profundizaremos en los problemas que rodean la seguridad de la memoria y exploraremos cómo implementar un asignador en C++ que cumpla con estos requisitos.
Comprendiendo los Desafíos
Antes de sumergirnos en la solución, es esencial entender algunos de los desafíos que conlleva proteger la memoria:
-
Prevenir la Paginación al Disco: En sistemas que utilizan memoria virtual, la memoria puede ser paginada al disco, lo que la hace vulnerable a accesos no autorizados. Cuando la memoria se pagine, se almacena en el espacio de intercambio, que podría ser accesible para otros procesos.
-
Acceso a Través de Depuradores: Los depuradores pueden acceder y manipular la memoria, lo que hace crucial encontrar formas de minimizar este riesgo, especialmente para la información sensible.
-
Limitaciones a Nivel de SO: Independientemente de nuestras intenciones, el sistema operativo tiene control sobre los procesos y puede potencialmente leer el contenido de la memoria.
Un Asignador de Memoria Segura en C++
A pesar de estos desafíos, podemos implementar un asignador personalizado en C++ utilizando funciones de la API de Windows. Desglosemos la solución paso a paso:
1. Usando VirtualAlloc
La herramienta principal para asignar memoria de forma segura es VirtualAlloc
. Esta función nos permite asignar memoria de una manera específica:
- Podemos establecer niveles de protección para controlar cómo se puede acceder a la memoria asignada.
LPVOID pMem = ::VirtualAlloc(NULL, allocLen, allocType, allocProtect);
Aquí, allocType
se establece en MEM_COMMIT
, y allocProtect
se establece en PAGE_READWRITE
, habilitando tanto el acceso de lectura como de escritura.
2. Bloqueando Memoria
Para evitar que el SO pagine la memoria al disco, usamos la función VirtualLock
:
if (pMem != NULL) {
::VirtualLock(pMem, allocLen);
}
Sin embargo, ten en cuenta que VirtualLock
impone restricciones sobre la cantidad de memoria que puedes asignar, lo cual puede ser manejable según tus necesidades.
3. Liberando Memoria de Forma Segura
Al liberar memoria, es esencial poner a cero el contenido sensible antes de liberarlo. Esto se puede hacer utilizando SecureZeroMemory
:
::SecureZeroMemory(_pPtr, allocLen);
::VirtualUnlock(_pPtr, allocLen);
::VirtualFree(_pPtr, 0, MEM_RELEASE);
Ejemplo Completo
Aquí está la implementación completa de nuestro asignador de memoria segura:
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);
}
}
};
// Ejemplo de Uso
typedef std::basic_string<char, std::char_traits<char>, LockedVirtualMemAllocator<char>> modulestring_t;
Conclusión
Crear un asignador de memoria segura en C++ es una tarea compleja que implica navegar a través de varias restricciones a nivel de sistema y desafíos de seguridad. Si bien es imposible lograr una protección total contra el acceso a la memoria, usar funciones como VirtualAlloc
, VirtualLock
y SecureZeroMemory
puede mejorar significativamente la seguridad de los datos sensibles.
Es esencial tener en cuenta que ningún sistema puede ser completamente seguro del propietario del dispositivo. Por lo tanto, comprender estas limitaciones permite a los desarrolladores crear aplicaciones más robustas y resilientes.
Para quienes estén interesados en obtener una comprensión más profunda, recursos como Practical Cryptography de Neil Ferguson y Bruce Schneier pueden proporcionar un contexto valioso en prácticas criptográficas y metodologías de gestión segura de memoria.