Tulip 2021 Spielassistenztechnologie (Anfängerklasse) (Mittel)

MFC Dynamic Link Library und injizierte DLL

Früher haben wir Remote-Threads zum Aufrufen von CALL verwendet. Heute rufen wir CALL lokal über die Dynamic Link Library auf.

Fügen Sie hier eine Bildbeschreibung ein

Jede Dynamic Link Library ist ein Programm, das nicht gestartet und nicht alleine ausgeführt werden kann. Sie kann nur an andere Exe-Programme angehängt werden. Dynamic Link Libraries bedienen unabhängige Prozesse anderer Exe. Es entspricht einer Ressource für die Codebasis.
Wir müssen nur ein Fenster in diese dynamische Linkbibliothek einfügen und es dann in einen anderen Prozess einfügen, das Fenster anzeigen und den darin enthaltenen CALL testen.

Fügen Sie hier eine Bildbeschreibung ein
Doppelklicken Sie auf die *.rc-Datei im Ressourcendateiverzeichnis, um zur Ressourcenansicht zu wechseln. Klicken Sie mit der rechten Maustaste auf das *.rc-Verzeichnis in der Ressourcenansicht, um das Menü aufzurufen. Klicken Sie auf Ressource hinzufügen und wählen Sie die Option „Dialog“, um den Link dynamisch zu erstellen Die Bibliothek fügt eine Dialogressource hinzu:

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Um dieses Fenster anzeigen zu können, müssen wir zunächst eine Klasse zur Dialogfensterressource hinzufügen:

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Schaufenster:

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Beachten Sie, dass Sie vor dem Kompilieren des Programms die Plattform auf x86 ändern müssen, da der zum Testen verwendete Code-Injektor nur auf der x86-Plattform verwendet werden kann.

Fügen Sie hier eine Bildbeschreibung ein

Jetzt müssen wir die generierte DLL-Datei über den Code-Injektor in den laufenden .exe-Prozess einschleusen:

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Nachdem unser Fenster eingefügt wurde, können wir call00, call01 und call02 aufrufen. Der Anruf ist zu diesem Zeitpunkt sehr praktisch.

Wir fügen dem Fenster eine Schaltfläche hinzu und fügen den Code zum Aufruf von call02 im Click-Ereignis der Schaltfläche hinzu:

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein


Schreiben Sie Code, um Speicher im Zielprozess zuzuweisen

Schreiben Sie Code (Konsolenanwendung), um den Funktionscode call02 in den Zielprozess zu schreiben.

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

  • Parameter flAllocationType
    Das MEM_OMMIT bedeutet, dass der auf diese Weise zugewiesene Speicher auf unserer Festplatte ersetzt werden kann, was wir als Seitenspeicher bezeichnen.

Fügen Sie hier eine Bildbeschreibung ein

  • Parameter flProtect
    Dies ist das Seitenattribut unseres Speichers. PAGE_EXECUTE_READWRITE bedeutet, dass der zugewiesene Speicher lesbar, beschreibbar und ausführbar ist.

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Wir können sehen, dass der im Zielprozess zugewiesene Speicher bei 0x00250000 beginnt. Wir verwenden x64dbg, um zu überprüfen, ob dieser Speicher vorhanden ist:

Fügen Sie hier eine Bildbeschreibung ein

Zu diesem Zeitpunkt können Sie sehen, dass tatsächlich ein großer Speicherplatz von 0 zugewiesen ist.

Als nächstes müssen wir unsere Daten (Code) in den im Zielprozess zugewiesenen Speicherplatz schreiben. Wie schreibt man sie?
Schreiben Sie zunächst eine nackte Assembly:

Fügen Sie hier eine Bildbeschreibung ein

Schreiben Sie dann diese nackte Assembly in den im Adressraum des Zielprozesses zugewiesenen Speicherplatz:
Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein


Fügen Sie Code in den Zielprozess ein, um die DLL zu laden

Ziel::
Schreiben Sie Code (Konsolenanwendung), um die in MFC geschriebene DLL (den vollständigen Pfad der DLL in Lektion 26) in den Zielprozess (23) einzufügen (zu schreiben). Klasse) (erstellen Sie einen Remote-Thread über CreateRemoteThread und rufen Sie LoadLibrary auf, um die DLL zu laden).

- VirtualAllocEx
- CreateRemoteThread
- LoadLibraryA	 

LoadLibrary有两个作用:
1、加载DLL到当前进程
2、获取模块的基址(模块句柄)
有时候我们会碰到一些动态基址的游戏,那么我们就需要用到这个函数LoadLibrary来动态的定位它那个主模块的基址。

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Dies ist der vollständige Pfad der DLL, die wir einfügen möchten, in den wir ihn geschrieben haben.

Fügen Sie hier eine Bildbeschreibung ein


029-Analysieren Sie die Eigenschaften des Rollenobjekts

Fügen Sie hier eine Bildbeschreibung ein


Die Aufrufberechnungsparameternummer des äußeren Stapels

Fügen Sie hier eine Bildbeschreibung ein

Um den Aufruf einzugeben, überprüfen Sie, ob [ebp+8] der erste Parameter, [ebp+C] der zweite Parameter und [ebp+10] der dritte Parameter ist.
Oben im Bild oben befinden sich Push ESI und Push EDI, die zum Speichern nichtflüchtiger nichtflüchtiger Register verwendet werden. Hinten gibt es entsprechende Pop EDI und Pop ESI: < /span>

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Edi und esi sind also keine Parameter des nahegelegenen Aufrufs 84E0E0. Dieser Aufruf hat nur 3 Parameter.


Schreiben Sie Code in C und C++, um Objektattributwerte zu lesen

Da jeder Prozess über eine Konsole verfügt, können wir zur Erleichterung des Testens die Konsole über dieAllocConsole-API öffnen, um die Ausgabe zu erleichtern.
Wenn die Konsolenanzeige während des Tests abnormal ist (es werden keine Ergebnisse erzeugt), können Sie sie zuerst über FreeConsole0 diese API schließen, sie dann erneut öffnen und es erneut versuchen.

Wie verknüpfen wir diesen Hauptthread?
Das Einbinden des Hauptthreads ist ein relativ komplizierter Prozess. Es gibt viele Methoden. Jetzt können wir es schnell verwenden. Wir können die Timer-Methode verwenden, um es an den Thread anzuhängen, in dem sich das Spielfenster befindet. weil Die Fensterschnittstelle des Spiels läuft im Hauptthread, sodass wir diese Methode verwenden können, um den Hauptthread des Spiels einzubinden (andere Methoden zum Einbinden des Hauptthreads werden in späteren Lektionen besprochen).

Den Titel und Klassennamen des Spielfensters erhalten wir zunächst über Spy++:

Fügen Sie hier eine Bildbeschreibung ein

Wenn das Spielfenster-Handle gefunden wird, können wir den Hauptzeilencode über SetTimer daran anhängen. Natürlich haben wir früher Unterklassen verwendet (verwenden Sie SetWindowLong für Unterklassen) oder SetWindowsHookEx, um Nachrichten-Hooks aufzuhängen. Sie können auch einen Inline-Hook erstellen, der kann in den Hauptfaden eingehakt werden. Hier verwenden wir eine einfache Möglichkeit, den Hauptfaden einzuhaken.

Mit dem Titel und dem Klassennamen des Spielfensters können wir dessen Fensterhandle abrufen und dann den Timer installieren:

Fügen Sie hier eine Bildbeschreibung ein

Der zweite Parameter von SetTimer in der obigen Abbildung ist ein beliebiger ID-Wert, der den Timer identifiziert. Der dritte Parameter gibt an, wie oft (Millisekundenebene) die durch den vierten Parameter dargestellte Funktion ausgeführt wird. Der vierte Parameter dieser Funktion hat ein festes Format ist eigentlich ein Rückruf des Timers:

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Die Ausführung der KillTimer-Funktion im Hauptzeilencode kann den installierten Timer 1236 ausschalten, sodass sie jedes Mal, wenn auf die Schaltfläche geklickt wird (der 1236-Timer ist installiert), nur einmal ausgeführt wird (Ausschalten des Timers und Ausführen des Hauptzeilencodes um). die selbe Zeit).

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Nach dem Test haben wir festgestellt, dass es keinen Rückgabewert gibt. Ändern wir den Code, um zu sehen, ob die aktuelle Thread-ID-Ausgabe der Hauptthread ist, prüfen Sie, ob er mit dem Hauptthread des Spiels verknüpft ist, und fügen Sie < hinzu /span>MessageBeep(1);Diese Codezeile kann den CALL unter der MessageBeep-Funktion in x64dbg zum Debuggen leicht finden:

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Aus dem Bild oben können wir ersehen, dass der Hauptthread 2CE4 ist, was bedeutet, dass der Hauptthread des Spiels verbunden ist, aber warum wird CALL nicht erfolgreich aufgerufen?
Wir gehen mit Strg+g zur MessageBeep-Funktion und setzen einen Haltepunkt am Anfang:

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Führen Sie die Ausführung für MessageBeep aus, doppelklicken Sie auf die Rücksprungadresse, um zur vorherigen Ebene zurückzukehren, suchen Sie den CALL unter dem Funktionsaufruf von MessageBeep und prüfen Sie, ob die an den CALL übergebenen Parameter und der zurückgegebene EAX-Wert korrekt sind:

Fügen Sie hier eine Bildbeschreibung ein

Wir haben festgestellt, dass es ein Problem mit den übergebenen Parametern gab. Die übergebene Adresse enthielt die String-Variable „player“, was bedeutete, dass es eine zusätzliche Ebene gab, d. h. 70A57024 sollte übergeben werden, aber die Ergebnis war 03A2FD80.
Zum Testen ändern wir den 03A2FD80, der in den Stapel in der unteren rechten Ecke des Bildes oben geschoben wird, in 70A57024 und prüfen dann, ob der CALL-Aufruf erfolgreich ist:

Fügen Sie hier eine Bildbeschreibung ein

Im __asm-Anweisungsblock im Hauptzeilencode sollten Sie nicht die lea-Anweisung verwenden, um die Adresse von Parameter 1 abzurufen, sondern Parameter 1 direkt auf den Stapel verschieben:

Fügen Sie hier eine Bildbeschreibung ein

Da sich die DLL, in der sich der Hauptzeilencode befindet, bereits im Adressraum des Spiels befindet, müssen wir die Prozessdaten nicht mehr über die API-Funktion ReadProcessMemory (für prozessübergreifende Prozesse) lesen. Wir können den Zeiger direkt zum Lesen verwenden (sehr schnell). :

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Beim Testen haben wir festgestellt, dass das Konsolenfenster oft keine Ausgabe hatte. Dies kann daran liegen, dass es vom Spiel umgeleitet wurde (z. B. vom Spiel in eine Datei umgeleitet wurde). Danach werden wir den Steuercode der Konsole ändern Hier können wir zunächst den Rüstungswert im MessageBoxA-Popup-Fenster oder Bearbeitungsfeld ausgeben, um zu vermeiden, dass die Konsole vom Spiel umgeleitet wird.

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Da sich dieses Popup-Fenster im Hauptthread befindet und im Hauptthread hängen bleibt, wird es nicht auf einmal angezeigt, sodass wir eine andere Möglichkeit finden müssen, die Ausgabe anzuzeigen;
Beispielsweise die Möglichkeit, Debugging-Informationen auszugeben (verwenden Sie das DebugView-Tool, um Debugging-Informationen anzuzeigen):

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Wir haben die printf-Ausgabeanweisung im Code auskommentiert, um zu vermeiden, dass sie vom Spiel an andere Stellen umgeleitet wird, was dazu führt, dass printf hängen bleibt und Debugging-Informationen nicht an DebugView ausgegeben werden.

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Wir haben jedoch festgestellt, dass die Methode zum Einbinden des Timers in den Hauptthread nicht sehr stabil ist und der Rückruf des Hauptthreads häufig nicht ausgeführt werden kann. Wir werden dieses Problem später optimieren und lösen.


C-, C++-Eingabe- und Ausgabeumleitung

Wir können die Ausgabe von printf auf dem Bildschirm, einem bestimmten Gerät oder einer Datei steuern. Hier möchten wir die Ausgabe auf dem Konsolenbildschirm zulassen, um zu vermeiden, dass das Spiel sie in eine Datei umleitet.

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Die chinesischen Namen von stdout, stdin und stderr sind Standardausgabe, Standardeingabe bzw. Standardfehler.

freopen( „CONIN$“ , „r+t“ , stdin); // STDIN umleiten

freopen( „CONOUT$“ , „w+t“ , stdout); // STDOUT umleiten

CONOUT$ stellt den Bildschirm unserer Konsole dar. Dies dient dazu, die Standardausgabe-Stdout auf dem Bildschirm CONOUT$ wiederherzustellen, da das Spiel möglicherweise auch diese Freopen-Funktion verwendet, um unsere Standardausgabe-Stdout in der Datei zu finden.

Fügen Sie hier eine Bildbeschreibung ein

Dieser neue Code positioniert unsere Ausgabe auf die Standardausgabe. Mit anderen Worten, er gibt Funktionen wie printf auf dem schwarzen Fensterbildschirm aus.

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Das obige Bild wird zur Behebung des Fehlers C4996 verwendet. Durch die Definition des Makros _CRT_SECURE_NO_WARNINGS können Sie die unsichere Funktion freopen weiterhin verwenden.

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Die Injektion scheint stecken zu bleiben, und der Code-Injektor steckt fest, weil er auf die Rückkehr unseres Threads wartet. Da unsere DLL für die dynamische Linkbibliothek jedoch hier in der InitInstance-Funktion in der Quelldatei A031_MFC_DLL.cpp initialisiert wird, bleibt sie hängen. Leben:

Fügen Sie hier eine Bildbeschreibung ein

Aber unser Code-Injektor hat bis Zeile 65 der obigen Abbildung auf die Ausführung unseres Codes gewartetreturn TRUE;, aber hier in Zeile 64.DoModal Es ist blockiert , es steckt hier fest, also wie kann man es lösen?

Diese beiden Codezeilen sollten zur Ausführung in einen neuen Thread eingefügt werden:

Fügen Sie hier eine Bildbeschreibung ein

Die MFC-Funktion muss das MakroAFX_MANAGE_STATE(AfxGetStaticModuleState()); an der Vorderseite der Anzeigefensterfunktion hinzufügen. Das heißt, wenn wir das MFC-Fenster in der DLL verwenden, müssen wir dieses hinzufügen Zeile Der Code wird in die erste Zeile der Funktion eingefügt. Dieses Makro führt einige Ressourceninitialisierungsarbeiten durch. Erst wenn wir das Fenster später anzeigen, kann es die entsprechenden Ressourcen finden und das Fenster normal anzeigen.

Wenn Sie ein DLL-Programm schreiben, werden Sie feststellen, dass durch Aufrufen der AfxGetApp-Funktion in der DLL das Anwendungsobjekt der DLL abgerufen wird. Der Grund erscheint im Modulstatus der DLL. Um sicherzustellen, dass beim Aufruf einer DLL keine Ressourcenprobleme auftreten, rufen Anwendungen häufig Folgendes auf:
AFX_MANAGE_STATE(AfxGetStaticModuleState()); //Thread-Status zurück auf DLL schalten
Beachten Sie, dass dies der Fall ist ein Makro. Seine Funktion besteht darin, den globalen Variablenbereich des Moduls zu ändern, dh die globale Variablenkopie der Anwendung auf die globale Variablenkopie der DLL umzustellen. Natürlich erhalten Sie mit AfxGetApp die APP in der DLL. Wenn Sie die AfxGetApp-Funktion verwenden möchten, um auf das App-Objekt der Anwendung zuzugreifen, müssen Sie nur den Modulstatus zurückwechseln. Denken Sie daran, den Status nach der Ausführung wieder zurückzuschalten, da sonst Probleme auftreten.
Zum Beispiel:

// switch thread state back to application
_AFX_THREAD_STATE* pState = AfxGetThreadState();
AfxSetModuleState(pState->m_pPrevModuleState);
// do something with the application
AfxGetApp()->...
// switch thread state back to dll
AFX_MANAGE_STATE(AfxGetStaticModuleState())

Referenzlink: https://blog.csdn.net/tianmeshi/article/details/4209904

Fügen Sie hier eine Bildbeschreibung ein

Nachdem hier über CreateRemoteThread oder CreateThread ein unabhängiger Thread erstellt wurde, wird nicht darauf gewartet, dass der Code bis Zeile 64 ausgeführt wird (es wird nicht blockiert), sondern direkt Zeile 74 ausgeführt und zurückgegeben, sodass kein Code vorhanden ist Injektionsgerät.
Da es nur im neuen Thread ausgeführt wird und in Zeile 63 hängen bleibt, wartet unser Code-Injektor einfach darauf, dass die Initialisierung der InitInstance-Funktion abgeschlossen ist, d. h. nach der Ausführung des Codes CreateRemoteThread , führen Sie es direkt aus. Zeile 74return TRUE; gibt OK zurück. Der von CreateRemoteThread erstellte unabhängige neue Thread hat die Ausführung noch nicht abgeschlossen und hat daher nichts mit der Ausführung von InitInstance (dem Thread, der die InitInstance-Funktion ausführt) zu tun und der Thread, der die Anzeigefenster-Threads ausführt, sind unterschiedlich.

Fügen Sie hier eine Bildbeschreibung ein

Zu diesem Zeitpunkt ist das Ablesen des Rüstungswerts sehr reibungslos. Wenn Sie die Ausrüstung beiläufig wechseln, können Sie schnell den neuesten Rüstungswert ablesen.

Das Lösen der Schnittstellenblockierung durch Konsolenumleitung und das Erstellen neuer Threads löst das Problem der instabilen Konsolenausgabe in der vorherigen Lektion.

Wir können den gelesenen Wert auch in die Datei 123.txt auf dem Laufwerk C schreiben:

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Es gibt noch ein weiteres Problem: Jedes Mal, wenn wir eine DLL injizieren, müssen wir die injizierte DLL über den Code-Injektor im Bild oben deinstallieren. Es ist auch sehr mühsam, dies jedes Mal manuell zu tun. Wir können einen Code hinzufügen, um das Fenster zu schließen. Gleichzeitig verwenden wir Code, um diese DLL der Dynamic Link Library zu deinstallieren:

Fügen Sie hier eine Bildbeschreibung ein

Solange das eingefügte Fenster immer angezeigt wird, wird der Code nach Zeile 63 nicht ausgeführt (blockiert in Zeile 63.DoModal()-Funktion). Der nachfolgende Code wird erst ausgeführt, wenn das Fenster geschlossen wird. Deinstallieren Sie die DLL und beenden Sie diese Faden.

Fügen Sie hier eine Bildbeschreibung ein

Hier haben wir ein weiteres Problem gefunden. Nachdem wir auf die Schaltfläche zum Lesen des Rüstungswerts geklickt hatten, wurde die Datei 123.txt auf das Laufwerk C geschrieben, aber wir sahen die Größe der Datei 123.txt auf dem Laufwerk C. 0 KB. Dies liegt daran, dass die Daten aufgrund der Zwischenspeicherung nicht sofort in die Datei geschrieben werden. Wir können die Konsole über die Funktion fclose schließen, um die Datei sofort zu schreiben. Ändern Sie den Code wie folgt:

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Bei dieser Methode zum sofortigen Schreiben in eine Datei muss viel Code geändert werden. Hier verwenden wir eine einfache Methode zur Ausgabe auf die Konsole (kommentieren Sie den geänderten Code in den beiden Bildern oben aus).

Fügen Sie hier eine Bildbeschreibung ein


C, C++-Timer und Hauptthread

Fügen Sie hier eine Bildbeschreibung ein

Der Timer bedeutet, wie der Name schon sagt, Uhr, was bedeutet, dass er jedes Mal den entsprechenden Code ausführt.


Timer (Microsoft-Dokumentation)

Ein Timer ist eine interne Routine, die wiederholt ein angegebenes Intervall in Millisekunden misst.
Ein Timer ist eine interne Routine, die wiederholt ein angegebenes Intervall in Millisekunden misst. Einheit).

Microsoft-Referenzlink: SetTimer-Funktion

  • Parameter

    • [in, optional] hWnd
      Typ: HWND
      Ein Handle für das Fenster, das dem Timer zugeordnet werden soll. Dieses Fenster muss Eigentümer sein der aufrufende Thread. Wenn ein NULL-Wert für hWnd zusammen mit einem nIDEvent eines vorhandenen Timers übergeben wird, wird dieser Timer auf die gleiche Weise ersetzt wie ein vorhandener hWnd-Timer ungleich NULL.
      Das mit dem Timer verknüpfte Fensterhandle. Das Fenster muss dem aufrufenden Thread gehören. Wenn ein NULL-Wert für hWnd zusammen mit dem nIDEvent eines vorhandenen Timers übergeben wird, wird der Timer ersetzt, als wäre er ein vorhandener hWnd-Timer ungleich NULL.

    • [in] nIDEvent
      Typ: UINT_PTR
      Eine Timer-ID ungleich Null. Wenn der hWnd-Parameter NULL ist und das nIDEvent nicht mit einem vorhandenen übereinstimmt Wenn der hWnd-Parameter nicht NULL ist und das durch hWnd angegebene Fenster bereits einen Timer mit dem Wert nIDEvent hat, wird der vorhandene Timer durch den neuen Timer ersetzt. Wenn SetTimer a ersetzt Timer, der Timer wird zurückgesetzt. Daher wird nach Ablauf des aktuellen Timeout-Werts eine Nachricht gesendet, der zuvor festgelegte Timeout-Wert wird jedoch ignoriert. Wenn der Aufruf nicht dazu gedacht ist, einen vorhandenen Timer zu ersetzen, sollte nIDEvent 0 sein wenn hWnd NULL ist.
      Eine Timer-ID ungleich Null. Wenn der hWnd-Parameter NULL ist und nIDEvent nicht mit einem vorhandenen Timer übereinstimmt, wird der nIDEvent-Parameter ignoriert und eine neue Timer-ID generiert. Wenn der hWnd-Parameter nicht NULL ist und das durch hWnd angegebene Fenster bereits über einen Timer mit dem Wert nIDEvent verfügt, wird der vorhandene Timer durch den neuen Timer ersetzt. Wenn SetTimer den Timer ersetzt, wird der Timer zurückgesetzt. Daher wird nach Ablauf des aktuellen Timeout-Werts uElapse eine Nachricht gesendet, der zuvor eingestellte Timeout-Wert wird jedoch ignoriert. Wenn der Zweck des Aufrufs nicht darin besteht, einen vorhandenen Timer zu ersetzen, sollte nIDEvent 0 sein, wenn hWnd NULL ist.

    • [in] uElapse
      Typ: UINT
      Der Timeout-Wert in Millisekunden.
      Zeit- Ausgabewert in Millisekunden.

    • [in, optional] lpTimerFunc
      Typ: TIMERPROC
      Ein Zeiger auf die Funktion, die benachrichtigt werden soll, wenn der Timeout-Wert abläuft. Weitere Informationen Informationen zur Funktion finden Sie unter TimerProc. Wenn lpTimerFunc NULL ist, sendet das System eine WM_TIMER-Nachricht an die Anwendungswarteschlange. Das hwnd-Mitglied der MSG-Struktur der Nachricht enthält den Wert des hWnd-Parameters.
      Timeout Zeiger auf eine Funktion, die benachrichtigt werden soll, wenn der Wert (uElapse) abläuft. Weitere Informationen zu Funktionen finden Sie unter TimerProc. Wenn lpTimerFunc NULL ist, sendet das System eine WM_TIMER-Nachricht an die Anwendungswarteschlange. Das hwnd-Mitglied der Nachrichten-MSG-Struktur enthält den Wert des hWnd-Parameters.
      Der Timer-Bezeichner nIDEvent ist spezifisch für das zugehörige Fenster. Ein anderes Fenster kann seinen eigenen Timer haben, der denselben Bezeichner hat wie ein Timer, der einem anderen Fenster gehört. Die Timer sind unterschiedlich. Das heißt, die Timer-IDs verschiedener Fenster können gleich sein und unterscheiden sich durch ihre zugehörigen Fensterhandles (Timer-IDs sind für jedes Fenster privat).
      Die Timer-ID nIDEvent ist spezifisch für das zugehörige Fenster. Ein anderes Fenster kann einen eigenen Timer mit derselben Kennung haben wie der Timer des anderen Fensters. Timer sind unterschiedlich.

  • Rückgabewert

    • Typ: UINT_PTR
      Wenn die Funktion erfolgreich ist und der hWnd-Parameter NULL ist, ist der Rückgabewert eine Ganzzahl, die den neuen Timer identifiziert. Eine Anwendung kann diesen Wert an die KillTimer-Funktion übergeben Zerstöre den Timer.
      Wenn die Funktion erfolgreich ausgeführt wird und der hWnd-Parameter NULL ist, ist der Rückgabewert eine Ganzzahl, die den neuen Timer identifiziert. Anwendungen können diesen Wert an die KillTimer-Funktion übergeben, um den Timer zu zerstören.
      Wenn die Funktion erfolgreich ist und der hWnd-Parameter nicht NULL ist, ist der Rückgabewert eine Ganzzahl ungleich Null. Eine Anwendung kann den Wert des nIDEvent-Parameters an die KillTimer-Funktion übergeben, um den Timer zu zerstören.< /span> Wenn die Funktion keinen Timer erstellen kann , Der Rückgabewert ist Null. Um erweiterte Fehlerinformationen zu erhalten, rufen Sie GetLastError auf. Wenn die Funktion keinen Timer erstellen kann, ist der Rückgabewert Null. Um erweiterte Fehlerinformationen zu erhalten, rufen Sie GetLastError auf.
      Wenn die Funktion erfolgreich ausgeführt wird und der hWnd-Parameter nicht NULL ist, ist der Rückgabewert eine Ganzzahl ungleich Null. Anwendungen können den Wert des nIDEvent-Parameters an die KillTimer-Funktion übergeben, um den Timer zu zerstören.

  • Bemerkungen
    Eine Anwendung kann WM_TIMER-Nachrichten verarbeiten, indem sie eine WM_TIMER-Case-Anweisung in die Fensterprozedur einschließt oder beim Erstellen des Timers eine TimerProc-Rückruffunktion angibt. Wenn Sie eine TimerProc-Rückruffunktion angeben ruft DispatchMessage die Rückruffunktion auf, anstatt die Fensterprozedur aufzurufen, wenn WM_TIMER mit einem lParam ungleich NULL verarbeitet wird. Daher müssen Sie Nachrichten im aufrufenden Thread versenden, auch wenn Sie TimerProc verwenden, anstatt WM_TIMER zu verarbeiten.
    Eine Anwendung kann die WM_TIMER-Nachricht verarbeiten, indem sie eine WM_TIMER-Case-Anweisung in die Fensterprozedur einfügt oder beim Erstellen des Timers eine TimerProc-Rückruffunktion angibt. Wenn Sie eine TimerProc-Rückruffunktion angeben, ruft DispatchMessage die Rückruffunktion auf, wenn WM_TIMER mit einem lParam ungleich Null verarbeitet wird, anstatt die Fensterprozedur aufzurufen. Daher müssen Sie die Nachricht im aufrufenden Thread versenden, auch wenn Sie TimerProc verwenden, anstatt WM_TIMER zu verarbeiten.
    Der wParam-Parameter der WM_TIMER-Nachricht enthält den Wert des nIDEvent-Parameters.
    Der wParam-Parameter der WM_TIMER-Nachricht enthält den Wert des nIDEvent-Parameters.
    Der Timer-Bezeichner nIDEvent ist spezifisch für das zugehörige Fenster. Ein anderes Fenster kann seinen eigenen Timer haben, der denselben Bezeichner hat wie ein Timer, der einem anderen Fenster gehört. Die Timer sind unterschiedlich. Wenn Sie Wenn Ihre Anwendung beim Erstellen des Timers kein Fensterhandle angibt, muss Ihre Anwendung die Nachrichtenwarteschlange auf WM_TIMER-Nachrichten überwachen und diese an das entsprechende Fenster weiterleiten. Wenn Ihre Anwendung einen Timer erstellt, ohne ein Fensterhandle anzugeben, muss Ihre Anwendung die Nachrichtenwarteschlange auf WM_TIMER-Nachrichten überwachen und diese an das entsprechende Fenster weiterleiten. SetTimer kann Timer-IDs wiederverwenden, wenn hWnd NULL ist. SetTimer kann Timer-IDs wiederverwenden, wenn hWnd NULL ist.
    Die Timer-ID nIDEvent ist spezifisch für das zugehörige Fenster. Ein anderes Fenster kann einen eigenen Timer mit derselben Kennung haben wie der Timer des anderen Fensters. Diese Timer sind unterschiedlich.



  • Timer-Benachrichtigungen

    • WM_TIMER
      Wird in der Nachrichtenwarteschlange des installierenden Threads gepostet, wenn ein Timer abläuft. Die Nachricht wird von der GetMessage- oder PeekMessage-Funktion gepostet.
      Wenn der Timer abgelaufen ist läuft ab. Wenn es abläuft, wird es in der Nachrichtenwarteschlange des Installationsthreads gepostet. Die Nachricht wird von der GetMessage- oder PeekMessage-Funktion veröffentlicht.

// MyTimerProc ist eine anwendungsdefinierte Rückruffunktion, die WM_TIMER-Nachrichten verarbeitet.
VOID CALLBACK MyTimerProc(
HWND hwnd, // Handle zum Fenster für Timer-Nachrichten
UINT-Nachricht, // WM_TIMER-Nachricht
UINT idTimer, // Timer-ID
DWORD dwTime) // aktuelle Systemzeit


Codebeispiel

Hier fügen wir eine Schaltfläche hinzu, um einen Timer zu erstellen, und hängen den Timer an das Fenster, in dem sich die Schaltfläche befindet, da Timer ein Fenster benötigen, um Timer-Nachrichten zu empfangen:

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Der Code in Zeile 141 wird alle 1 Sekunde ausgeführt. Dies ist die Rolle des Timers.

Wir ändern den Code so, dass diese geplante Funktion jedes Mal ausgeführt wird, wenn die Taste gedrückt wird. Gleichzeitig wird die ID des aktuellen Threads in der Funktion gedruckt, damit sie mit der ID des Threads verglichen werden kann, der die Hauptzeile ausführt Code:

Fügen Sie hier eine Bildbeschreibung ein

Sie können auch den printf-Code ändern und m_hWnd und hWnd separat drucken.

Fügen Sie hier eine Bildbeschreibung ein

Dies ist der Thread, den wir erstellt haben (die Thread-ID ist 25DC), und die Thread-ID des gedruckten Hauptzeilencodes ist tatsächlich die Thread-ID des Hauptthreads des Spiels (20D4):

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Wir können sehen, dass die ID des Hauptthreads 20D4 ist.

Fügen Sie hier eine Bildbeschreibung ein

Der Thread 25DC im Bild oben wurde von uns selbst injiziert. Wir können zur Eintragsadresse des Threads gehen, um Folgendes zu sehen:

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Wie Sie dem Bild oben entnehmen können, ist die Anzeigefensterfunktion genau die Thread-Funktion des Threads, den wir selbst erstellt haben.

Fügen Sie hier eine Bildbeschreibung ein

Wenn unser Code (Erstellungstimer) etwas anderes als das Fensterhandle des Spiels übergibt, kann er definitiv nicht im Hauptthread des Spiels hängen bleiben und unser Code kann dann nicht normal ausgeführt werden.
Bei einigen CALLs muss der Hauptthread hängen bleiben, bei anderen nicht. Dies hängt davon ab, ob die CALLs in mehreren Threads Daten untereinander übertragen. Bei den meisten Spielen ist ein Hängen erforderlich. Nur durch Herstellen einer Verbindung mit dem Kann der Hauptthread normal ausgeführt werden?


Basis-Offset-Analyse, Überprüfung der Rolleninformationen

Fügen Sie hier eine Bildbeschreibung ein

Sie können auch die Stapelansicht der aktuellen Funktion in CE anzeigen:

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Wie finde ich schnell den vorherigen CALL der aktuellen Funktion? Es gibt viele Methoden, z. B. das Unterbrechen und anschließende Zurückgeben der Rücksprungadresse auf dem Stapel (am sichersten). Für die Genauigkeit müssen Sie herunterladen Bedingter Haltepunkt; oder ausführen, um zurückzukehren, aber es kann leicht abstürzen oder folgen (insbesondere, wenn das Spiel geschützt ist);
oder so Wie in der Abbildung unten gezeigt, suchen Sie nach dem aktuellen Modul und den Konstanten, geben Sie die Funktionsadresse ein (finden Sie die obere Schicht der Funktion 72CC30 im Bild unten) und Sie finden die Position der CALL-Funktionsadresse:

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Im Stapelfenster können wir schwarze vertikale Linien sehen. Jede Linie wird als Stapelrahmen bezeichnet und jeder Stapelrahmen stellt eine CALL-Umgebung dar.

Fügen Sie hier eine Bildbeschreibung ein

Wir doppelklicken auf die im Stapelfenster oben ausgewählte Rücksendeadresszeile, und das Disassemblierungsfenster findet die Rücksendeanweisung 49545B, d. h. die Anweisung CALL wow.494F30 über der Anweisung lautet For Beim CALL dieser Rücksprungadresse ist der Stack-Frame von 3BDFE04 bis 3BDFDE8 im Stack-Fensterwow.494F30der Stack-Frame dieses CALL (der ausgewählte Teil in der Abbildung unten):

Fügen Sie hier eine Bildbeschreibung ein

Wie wir in der vorherigen Lektion gesagt haben, befindet sich der nächste Haltepunkt für den Hardwarezugriff (1 Byte oder 4 Byte) im Speicherfenster (Datenfenster) 2AA8883C in der unteren linken Ecke des Bildes unten. Wenn das Zeicheninformationsfeld angezeigt wird geöffnet wird, wird es bei der Anweisung 4F54E1 im Disassemblierungsfenster unterbrochen. Die Anweisung an der Adresse 4F54DAmov esi, dword ptr ds:[eax+edx*4+174] greift auf den Inhalt an der Adresse 2AA8883C zu. Es gibt drei Push-Anweisungen für diese Anweisung, also die Stapelfenster in der unteren rechten Ecke des Bildes unten. Die ausgewählte Zeile mit roten Buchstaben ist die Rücksprungadresse, also der Ort des vorherigen CALL:

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Wie im ausgewählten Befehl im Bild oben gezeigt, müssen wir nach der Quelle von esi suchen:

Fügen Sie hier eine Bildbeschreibung ein

Aus dem Bild oben können wir sehen, dass die Zeichenfolge „player“ in eax platziert ist. Drücken Sie dann F8, um 00610F01 auszuführen:

Fügen Sie hier eine Bildbeschreibung ein

Klicken Sie wie im Bild oben gezeigt auf das Speicherfenster und drücken Sie dann Strg+G. Der durch die Formel in 输入将在内存窗口中转到的表达式... berechnete Wert ist genau die Adresse des Rüstungswerts 2AA8883C.

Wenn Sie die Konsole dieses Spiels direkt schließen, wird auch das Wow-Spiel beendet. Sie müssen also FreeConsole(); verwenden, um die Konsole des Spiels zu schließen.

Guess you like

Origin blog.csdn.net/zhaopeng01zp/article/details/132082355