Tutorials - Speicherzugriff Tutorial Teil 2

Sprachenübersicht/Programmierung/C / C++/ C#/Security

Speicherzugriff Tutorial Teil 2

Diese Seite wurde 10312 mal aufgerufen.

Dieser Artikel wurde in einem Wikiweb System geschrieben, das heißt, Sie können die Artikel jederzeit editieren, wenn Sie einen Fehler gefunden haben, oder etwas hinzufügen wollen.

Editieren Versionen Linkpartnerschaft Bottom Printversion

Keywords: C++, Speicherzugriff, Buffer, Memory, Memoryscanner, Trainer programmieren, auf Speicher von anderen Programmen zugreifen, Speicher verändern, Tutorial, manipulieren, Arbeitsspeicher, Cheattools, Tutorial, Anleitung, Programmieren, entwickeln

Orginal von Thomas Nitschke aka namespace.

Dies ist eine Fortsetzung des ersten "Wie lese ich Speicher von fremden Programmen"-Tutorial."

Viele Leute haben mich gefragt wie man denn den
Speicherbereich eines Programmes heraus finden oder zumindest eingrenzen kann.

Ich werde hier eine (und mir einzig bekannte) Methode vorstellen wie sich dies von einem anderen Programm aus bewerkstelligen läßt. Als Basis dient dieser etwas abgeänderte

Code aus dem letzten Tutorial:

Code:


#include <windows.h>
#include <iostream>

using namespace std;

typedef unsigned int uint;
HANDLE hproc;
DWORD procid;

int main(void)
{
    HWND hWnd;

    hWnd = FindWindow(0,"Opfer");
    if(!hWnd)
        return 0;
    GetWindowThreadProcessId(hWnd, &procid);

    hproc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, procid);

    CloseHandle(hproc);//<-- Wichtig!
    return 0;
}


Außerdem benötigen wir wieder unser Opfer-Programm

Da ich das Ermitteln des Speicherbereiches nicht auch
noch in die main() reinquetschen will, schreiben wir
die Funktion GetMemMinMax():

Code:


#include <windows.h>
#include <iostream>

using namespace std;

typedef unsigned int uint;
HANDLE hproc;
DWORD procid;
void GetMemMinMax(void);

int main(void)
{
    HWND hWnd;

    hWnd = FindWindow(0,"Opfer");
    if(!hWnd)
        return 0;

    GetWindowThreadProcessId(hWnd, &procid);
    hproc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, procid);
    GetMemMinMax();
    CloseHandle(hproc);//<-- Wichtig!
    return 0;
}

void GetMemMinMax(void)
{
    //hier speicherbereich bestimmen
}


Infos Über den Speicherbereich erhalten wir mit der
Funktion VirtualQueryEx().
Sie braucht als Parameter das Processhandle, die Adresse,
einen Pointer auf die MEMORY_BASIC_INFORMATION-Struktur,
und die Größe dieser Struktur.
Die MEMORY_BASIC_INFORMATION-Struktur sieht so aus:

Code:


struct _MEMORY_BASIC_INFORMATION {
    PVOID BaseAddress;
    PVOID AllocationBase;
    DWORD AllocationProtect;
    DWORD RegionSize;
    DWORD State;
    DWORD Protect;
    DWORD Type;
} MEMORY_BASIC_INFORMATION;


BaseAdress speichert die Anfangsadresse der
Speicherregion während RegionSize die Größe dieser
Speicherregion enthält. Der Bereich geht also
von BaseAdress bis BaseAdress+RegionSize.
Alles was wir jetzt machen ist in einer
Schleife immer mit VirtualQueryEx() uns
a) die Größe des Speicherbereichs zu holen
b) Prüfen ob er zu dem Programm gehört
c) Beim nächsten Bereich weitermachen
Als Code sieht das ungefähr so aus:

Code:


MEMORY_BASIC_INFORMATION mbi;
unsigned int adress = 0x400000;
do
{
    VirtualQueryEx(    hproc,
                    (void*)adress,
                    &mbi,
                    sizeof(MEMORY_BASIC_INFORMATION)
                );

    adress += mbi.RegionSize;
} while(adress < 0x80000000);


Jetzt müssen wir nur noch Überprüfen ob der
Speicherbereich zu unserem Programm gehört und ob er
mit Dingen "gefüllt" ist die wir brauchen.
Es kann nähmlich auch sein, dass wir auf den
Speicher stoßen in dem der Programmcode unseres
Zielprogs liegt! Für Virencoder sicherlich
interessant, aber wir begnügen uns jetzt mal
mit dem Verändern von Variablen.
Zum Überprüfen ob das "unser" Speicher ist,
müssen wir uns die State-Variable der
MEMORY_BASIC_INFORMATION-Struktur anschauen.
Ist sie == MEM_COMMIT, gehört der Speicher uns.
Danach müssen wir noch darauf achten dass
wir nicht Programmcode oder andere Sachen
gefunden haben. Das kann man mit der
Protect-Variable überprüfen. Normale Variablen
kann man ja sowohl auslesen als auch schreiben,
also ist der gesuchte Wert: PAGE_READWRITE
Als zusätzliche Sicherheit überprüfen ich
noch die Type-Variable, sie sollte immer == MEM_PRIVATE
sein. Hier ist der erweiterte Code:

Code:


void GetMemMinMax(void)
{
    MEMORY_BASIC_INFORMATION mbi;
    unsigned int adress = 0x400000;
    do
    {
        VirtualQueryEx(    
                        hproc,
                        (void*)adress,
                        &mbi,
                        sizeof(MEMORY_BASIC_INFORMATION)
                    );

        if((mbi.State == MEM_COMMIT) &&
           (mbi.Protect == PAGE_READWRITE) &&
           (mbi.Type == MEM_PRIVATE))
        {
            uint start = (uint)mbi.BaseAddress;
            uint end = (uint)mbi.BaseAddress+mbi.RegionSize;

            cout << "Bereich: " <<
                    hex << start << " - " <<
                    hex << end;
        }

        adress += mbi.RegionSize;
    } while(adress < 0x80000000);
}


In der start und end Variable wird jeweils die
Anfangs und Endadresse des Bereich gespeichert.
So, jetzt können wir also nach unseren
Speicherbereichen suchen! Ob der gesuchte Wert
darin enthalten ist müssen wir noch prüfen.
Dazu schreibe ich wieder eine neue Funktion mit
Namen ScanMem():

Code:


void ScanMem(DWORD start, DWORD end)
{
    cout << "Bereich wird gescannt...
";
    DWORD read = 0;
    uint buffer = 0;
    for(start; start < end; start++)
    {
        ReadProcessMemory(
                            hproc,
                            (void*)start,
                            &buffer,
                            sizeof(uint),
                            &read
                        );

        if(buffer == 15)
        {
            cout << "Wert an " << hex << start << " gefunden!";
            char choice;
            cout << "Abbrechen? [j,n]";
            cin >> choice;
            if(choice == 'j')
                return;
        }
    }
}


Wenn das Programm den Wert gefunden hat,
wird der User gefragt ob weiter gesucht werden
soll. Man kann nämlich immer noch nicht sicher
sein ob dieser Wert DIE gesuchte Variable ist.
Hier ist ScanMem() bereits in die Schleife eingebaut:

Code:


    do
    {
        VirtualQueryEx(    hproc,
                        (void*)adress,
                        &mbi,
                        sizeof(MEMORY_BASIC_INFORMATION)
                    );

        if((mbi.State == MEM_COMMIT)&&
           (mbi.Protect == PAGE_READWRITE)&&
           (mbi.Type == MEM_PRIVATE))
        {
            uint start = (uint)mbi.BaseAddress;
            uint end = (uint)mbi.BaseAddress+mbi.RegionSize;

            cout << "Bereich: " <<
                            hex << start << " - " <<
                            hex << end;

            ScanMem(start,end);
        }

        adress += mbi.RegionSize;
    } while(adress < 0x80000000);


Hier vollständigkeitshalber
nochmal der gesamte Programmcode:

Code:


#include <windows.h>
#include <iostream>

using namespace std;

typedef unsigned int uint;

void GetMemMinMax(void);
void ScanMem(DWORD start, DWORD end);

HANDLE hproc;
DWORD procid;

int main(void)
{
    HWND hWnd;

    hWnd = FindWindow(0,"Opfer");
    if(!hWnd)
        return 0;

    GetWindowThreadProcessId(hWnd, &procid);
    hproc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, procid);
    GetMemMinMax();
    CloseHandle(hproc);//<-- Wichtig!
    return 0;
}

void GetMemMinMax(void)
{
    MEMORY_BASIC_INFORMATION mbi;
    unsigned int adress = 0x400000;
    do
    {
        VirtualQueryEx(
                        hproc,
                        (void*)adress,
                        &mbi,
                        sizeof(MEMORY_BASIC_INFORMATION)
                    );

        if((mbi.State == MEM_COMMIT)&&
           (mbi.Protect == PAGE_READWRITE)&&
           (mbi.Type == MEM_PRIVATE))
        {
            uint start = (uint)mbi.BaseAddress;
            uint end = (uint)mbi.BaseAddress+mbi.RegionSize;

            cout << "Bereich: " <<
                            hex << start << " - " <<
                            hex << end;

            ScanMem(start,end);
        }

        adress += mbi.RegionSize;
    } while(adress < 0x80000000);
}


void ScanMem(DWORD start, DWORD end)
{
    cout << " Bereich wird gescannt...
";
    DWORD read = 0;
    int buffer = 0;
    for(start;start<end;start++)
    {
        ReadProcessMemory(
                            hproc,
                            (void*)start,
                            &buffer,
                            sizeof(int),
                            &read
                        );
        if(buffer == 15)
        {
            cout << "Wert an " << hex << start << " gefunden!";
            char choice;
            cout << "Abbrechen? [j,n]";
            cin >> choice;
            if(choice == 'j')
                return;
        }
    }
}


Machen wir einen Testlauf! Wenn alles klappt, steht in
der Konsole:
Bereich: 408000 - 40d000 Bereich wird gescannt...
Wert an 409040 gefunden! Abbrechen? [j,n]

Und *täterätä* die Adresse stimmt!!!
Ich hoffe, das damit die meisten Fragen, was
Speicherbereiche angeht, beantwortet sind.
Wenn nicht, Google und MSDN lesen! :>

Der MemoryScanner, den wir geschrieben haben, ist natürlich featuretechnisch
der letzte Schei-*hust*.
Sein Hauptproblem ist, dass er immer nur nach 4 Byte-Variablen
sucht (int, uint, float etc).
Die Werte, die uns interessieren, könnten aber nur 2, 1 oder sogar mehr als 4 Byte belegen.
Auch kann das Format unterschiedlich sein (floats werden anders gespeichert als ints).
Statt sich jetzt in sowas selber rein zu knien, empfehle ich eher einen
guten Debugger (siehe Links).
Hat man einmal Adressen und Größe der Variablen, kann man wie in Tutorial 1 vorgehen.
Trotzdem ist es gut zu wissen, wie diese Dinge intern funktionieren, nicht wahr? laugh

Hier noch ein paar nützliche Links mit Tools und Infos:
(Der Autor übernimmt keine Haftung für den Inhalt dieser Links)

www.ghu.as.ro/ghtuts/oboema1.txt
protools.cjb.net
www.gamehack.com
66.98.132.48/fravia/trainer.htm
www.zotteljedi.de/doc/stacksmashing/stacksmashing.html

Falls ihr Kritik, Anregungen oder Fragen habt,
schickt eine Mail an spam@codecreator.net!

Thomas

Gibt es noch irgendwelche Fragen, oder wollen Sie über den Artikel diskutieren?

Editieren Versionen Linkpartnerschaft Top Printversion

Haben Sie einen Fehler gefunden? Dann klicken Sie doch auf Editieren, und beheben den Fehler, keine Angst, Sie können nichts zerstören, das Tutorial kann wiederhergestellt werden

Sprachenübersicht/Programmierung/C / C++/ C#/Security/Speicherzugriff Tutorial Teil 2