Yritän hookata DirecX9:n Present-funktiota ihan kokeilumielessä. Dll-tiedoston injektoiminen ja funktion muistiosoitteen selvittäminen onnistuvat, mutta hookkaaminen kaatuu hyppykoodin kopioimiseen. Aluksi käytin memcpy:ä, mutta se ei toiminut joten siirryin käsin kopioimiseen debuggaamisen helpottamiseksi. Olen onnistunut paikantamaan bugin assemblykoodiin asti, jossa ohjelma vain pysähtyy kohtaan, jossa hyppykoodi kopioitaisiin kohteeseensa. Suoritus ei vain ikinä palaa kyseiseltä riviltä (merkitty assemblylistaukseen). Ohessa on hookkaamiseen käyttämäni koodi sekä Redirect-funktiosta kohta deassembloituna.
#pragma once #include <Windows.h> #include <string> #include <sstream> using namespace std; class Hook { private: BYTE jumpCode[5]; BYTE origCode[5]; volatile LPVOID origFunc; DWORD oldProtection; public: Hook() { jumpCode[0] = 0; } Hook(LPVOID originalFunction, LPVOID newFunction) : origFunc(originalFunction) { jumpCode[0] = 0; origFunc = originalFunction; DWORD newProtection = PAGE_EXECUTE_READWRITE; if (!VirtualProtect(origFunc, 8, newProtection, &oldProtection)) throw "Failed to change function protection level"; // Compute new jump code jumpCode[0] = 0xE9; // Long jump DWORD offset = (DWORD)((UINT_PTR)newFunction - (UINT_PTR)origFunc) - sizeof(jumpCode); memcpy(jumpCode+1, &offset, sizeof(offset)); stringstream ss; ss << hex; for (int i = 0; i < 8; i++) ss << (unsigned int)(((char*)originalFunction)[i]) << " "; MessageBoxA(0, ss.str().c_str(), "Code before replace", 0); // Copy original code to memory memcpy(origCode, origFunc, sizeof(origCode)); // And replace it with jump code //memcpy(origFunc, jumpCode, sizeof(jumpCode)); } ~Hook(void) { // Remove created hook if (origFunc == 0 || jumpCode[0] == 0) return; // Not even hooked so no hook to be removed memcpy(origFunc, origCode, sizeof(origCode)); DWORD tmpProtection; VirtualProtect(origFunc, 8, oldProtection, &tmpProtection); jumpCode[0] = 0; } void Original() { stringstream ss; ss << hex << "Original: " << (int) origFunc; MessageBoxA(0, ss.str().c_str(), "", 0); memcpy(origFunc, origCode, sizeof(origCode)); } void Redirect() { stringstream ss; ss << hex << "Redirect: " << (int) origFunc; MessageBoxA(0, ss.str().c_str(), "", 0); char *ptr = (char*) ((int)origFunc); ptr[0] = jumpCode[0]; ptr[1] = jumpCode[1]; ptr[2] = jumpCode[2]; ptr[3] = jumpCode[3]; ptr[4] = jumpCode[4]; //memcpy(origFunc, jumpCode, sizeof(jumpCode)); } };
char *ptr = (char*) ((int)origFunc); 53656832 8B 45 E8 mov eax,dword ptr [this] 53656835 8B 48 0C mov ecx,dword ptr [eax+0Ch] 53656838 89 8D 24 FF FF FF mov dword ptr [ptr],ecx ptr[0] = jumpCode[0]; 5365683E B8 01 00 00 00 mov eax,1 53656843 6B C0 00 imul eax,eax,0 53656846 B9 01 00 00 00 mov ecx,1 5365684B 6B C9 00 imul ecx,ecx,0 5365684E 8B 95 24 FF FF FF mov edx,dword ptr [ptr] 53656854 8B 75 E8 mov esi,dword ptr [this] 53656857 8A 04 06 mov al,byte ptr [esi+eax] 5365685A 88 04 0A mov byte ptr [edx+ecx],al <-- Pysähtyy tälle riville ptr[1] = jumpCode[1]; 5365685D B8 01 00 00 00 mov eax,1 53656862 C1 E0 00 shl eax,0 53656865 B9 01 00 00 00 mov ecx,1 5365686A C1 E1 00 shl ecx,0 5365686D 8B 95 24 FF FF FF mov edx,dword ptr [ptr] 53656873 8B 75 E8 mov esi,dword ptr [this] 53656876 8A 04 06 mov al,byte ptr [esi+eax] 53656879 88 04 0A mov byte ptr [edx+ecx],al ptr[2] = jumpCode[2]; 5365687C B8 01 00 00 00 mov eax,1 53656881 D1 E0 shl eax,1 53656883 B9 01 00 00 00 mov ecx,1 53656888 D1 E1 shl ecx,1 5365688A 8B 95 24 FF FF FF mov edx,dword ptr [ptr] 53656890 8B 75 E8 mov esi,dword ptr [this] 53656893 8A 04 06 mov al,byte ptr [esi+eax] 53656896 88 04 0A mov byte ptr [edx+ecx],al ptr[3] = jumpCode[3]; 53656899 B8 01 00 00 00 mov eax,1 5365689E 6B C0 03 imul eax,eax,3 536568A1 B9 01 00 00 00 mov ecx,1 536568A6 6B C9 03 imul ecx,ecx,3 536568A9 8B 95 24 FF FF FF mov edx,dword ptr [ptr] 536568AF 8B 75 E8 mov esi,dword ptr [this] 536568B2 8A 04 06 mov al,byte ptr [esi+eax] 536568B5 88 04 0A mov byte ptr [edx+ecx],al ptr[4] = jumpCode[4]; 536568B8 B8 01 00 00 00 mov eax,1 536568BD C1 E0 02 shl eax,2 536568C0 B9 01 00 00 00 mov ecx,1 536568C5 C1 E1 02 shl ecx,2 536568C8 8B 95 24 FF FF FF mov edx,dword ptr [ptr] 536568CE 8B 75 E8 mov esi,dword ptr [this] 536568D1 8A 04 06 mov al,byte ptr [esi+eax] 536568D4 88 04 0A mov byte ptr [edx+ecx],al
Käytän Visual Studio 2012 Expressiä koodin kirjoittamiseen sekä debuggaamiseen.
Pikaisella läpiluvulla ongelma on siinä että yrität kirjoittaa kirjoitussuojattuun muistiin, olettaen siis että osoitin todellakin osoittaa alkuperäiseen funktioon. Ja tätähän käsittääkseni loppujen lopuksi yritätkin tehdä.
Yksinkertaistaen syy on kirjoitussuojaus virtuaalimuistin alueilla, jotka on omistettu ohjelmakoodiksi merkatulle datalle.
Edit: Pari tutustumisen arvoista linkkiä, jotka ainakin kokeiluissa auttavat:
VirtualProtect
WriteProcessMemory
Juu, oikeuksissahan se vika olikin, vaikka muutinkin muistialueen oikeudet oikeiksi, niin destruktoria kutsuttiin ja se palautti kirjoitussuojauksen takaisin käyttöön. Muutin koodia nyt siten, että en enää luo uutta Hook-instanssia vaan kutsun vanhan Init-funktiota, joka on oleellisesti sama kuin nykyinen konstruktori.
Ps. Vika johtui siis siitä, että uuden instanssin destruktoria kutsuttiin tällä rivillä:
presentHook = Hook(original_DX9_Present, DX9_Present);
Joku C++:aa syvemmin osaava voi selittää, miksi sitä kutsutaan. Ymmärtääkseni johtuu siitä, että uutta arvoa asettaessa uusi instanssi kopioidaan vanhan päälle ja sitten alkuperäinen tuhotaan.
Tuolla rivillä luodaan väliaikainen Hook-instanssi, kutsutaan sijoitusoperaattoria ja lopuksi tuhotaan väliaikainen instanssi. Siis suunnilleen näin:
{ Hook tmp(foo, bar); presentHook = tmp; // Lohko loppuu, tmp tuhoutuu; tmp.~Hook() }
Tämä ei mielestäni ole kovin syvällistä tietoa; jokaisen C++-ohjelmoijan pitäisi ymmärtää, että oliomuuttujat eivät ole viittauksia ja siis sijoitusoperaattori ei vaihda oliota toiseksi vaan kopioi arvoja.
Jos ylipäänsä kirjoitat tuollaista koodia, sinun pitäisi ehkä käyttää osoittimia:
Hook* presentHook = new Hook(foo, bar); delete presentHook; presentHook = new Hook(foo2, bar2);
Tai nykyaikaisemmin osoitinluokkia:
std::shared_ptr<Hook> presentHook(new Hook(foo, bar)); presentHook.reset(); presentHook.reset(new Hook(foo2, bar2));
Jos haluaa kirjoittaa harvinaista koodia ja brassailla taidoillaan, voi kutsua tuhoajaa ja muodostinta myös ilman osoittimia:
Hook presentHook(foo, bar); presentHook.~Hook(); new (&presentHook) Hook(foo2, bar2);
Tietenkin myös erillisen muutosfunktion käyttö on mahdollista, jos se sopii kuvioihin. C++11 lisää valikoimaan siirtomuodostimet.
Aihe on jo aika vanha, joten et voi enää vastata siihen.