Einführung
Im Bereich der Softwareentwicklung, insbesondere beim Umgang mit sensiblen Daten, ist die Sicherheit des Speichers von größter Bedeutung. Entwickler stehen häufig vor Situationen, in denen sie sensible Informationen wie Lizenzschlüssel oder Passwörter vor neugierigen Blicken schützen müssen. Eine Frage, die in solchen Kontexten aufkommt, ist: Wie können wir einen sicheren Speicherallokator in C++ erstellen, der das Paging auf die Festplatte verhindert und schwer über Debugger zugänglich ist?
In diesem Blogbeitrag werden wir uns mit den Herausforderungen der Speichersicherheit auseinandersetzen und untersuchen, wie man einen C++-Allokator implementiert, der diese Anforderungen erfüllt.
Verständnis der Herausforderungen
Bevor wir in die Lösung eintauchen, ist es wichtig, einige der Herausforderungen zu verstehen, die mit dem Schutz des Speichers verbunden sind:
-
Verhinderung von Paging auf die Festplatte: Auf Systemen, die virtuellen Speicher verwenden, kann der Speicher auf die Festplatte gepaged werden, wodurch er anfällig für unbefugten Zugriff wird. Wenn Speicher ausgelagert wird, wird er im Swap-Space gespeichert, der möglicherweise von anderen Prozessen zugänglich ist.
-
Zugriff über Debugger: Debugger können auf den Speicher zugreifen und ihn manipulieren, was es entscheidend macht, Wege zu finden, dieses Risiko zu minimieren, insbesondere für sensible Informationen.
-
Betriebssystemseitige Einschränkungen: Unabhängig von unseren Absichten hat das Betriebssystem die Kontrolle über Prozesse und kann möglicherweise den Inhalt des Speichers lesen.
Ein sicherer Speicherallokator in C++
Trotz dieser Herausforderungen können wir einen benutzerdefinierten Allokator in C++ mithilfe von Windows-API-Funktionen implementieren. Lassen Sie uns die Lösung Schritt für Schritt aufschlüsseln:
1. Verwendung von VirtualAlloc
Das Hauptwerkzeug zur sicheren Speicherzuweisung ist VirtualAlloc
. Diese Funktion ermöglicht es uns, Speicher auf eine spezifizierte Weise zuzuweisen:
- Wir können Schutzlevels festlegen, um zu kontrollieren, wie auf den zugewiesenen Speicher zugegriffen werden kann.
LPVOID pMem = ::VirtualAlloc(NULL, allocLen, allocType, allocProtect);
Hierbei wird allocType
auf MEM_COMMIT
und allocProtect
auf PAGE_READWRITE
gesetzt, wodurch sowohl Lese- als auch Schreibzugriff ermöglicht wird.
2. Sperren des Speichers
Um zu verhindern, dass das Betriebssystem den Speicher auf die Festplatte paget, verwenden wir die Funktion VirtualLock
:
if (pMem != NULL) {
::VirtualLock(pMem, allocLen);
}
Es ist jedoch zu beachten, dass VirtualLock
Einschränkungen für die Menge des speicherbaren Speichers festlegt, was je nach Bedarf handhabbar sein kann.
3. Sichere Speicherfreigabe
Beim Freigeben von Speicher ist es wichtig, die sensiblen Inhalte vor der Freigabe auf Null zu setzen. Dies kann mit SecureZeroMemory
erfolgen:
::SecureZeroMemory(_pPtr, allocLen);
::VirtualUnlock(_pPtr, allocLen);
::VirtualFree(_pPtr, 0, MEM_RELEASE);
Vollständiges Beispiel
Hier ist die vollständige Implementierung unseres sicheren Speicherallokators:
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);
}
}
};
// Beispielverwendung
typedef std::basic_string<char, std::char_traits<char>, LockedVirtualMemAllocator<char>> modulestring_t;
Fazit
Die Erstellung eines sicheren Speicherallokators in C++ ist eine komplexe Aufgabe, die es erfordert, verschiedene systemseitige Einschränkungen und Sicherheitsherausforderungen zu navigieren. Während es unmöglich ist, einen vollständigen Schutz gegen den Zugriff auf den Speicher zu erreichen, können Funktionen wie VirtualAlloc
, VirtualLock
und SecureZeroMemory
die Sicherheit sensibler Daten erheblich verbessern.
Es ist wichtig, sich daran zu erinnern, dass kein System vollständig vor dem Eigentümer des Geräts gesichert werden kann. Das Verständnis dieser Einschränkungen ermöglicht es Entwicklern, robustere und widerstandsfähigere Anwendungen zu erstellen.
Für alle, die an tiefergehenden Einblicken interessiert sind, können Ressourcen wie Practical Cryptography von Neil Ferguson und Bruce Schneier wertvollen Kontext zu kryptografischen Praktiken und sicheren Methoden des Speichermanagements bieten.