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.