[x86] Simple memory browser
Page 1 sur 1
[x86] Simple memory browser
Pour faire un peu suite à mon tuto sur l'injection DLL, voici un petit bout de code utile pour retrouver des adresses mémoire que l'on cherche dans la mémoire code d'un process dans lequel on s'exécute (ce qui veut dire que ce code est sensé être appelé depuis une dll qu'on injecte dans le programme cible).
On peut avoir besoin d'un tel outil dans le cas d'un programme qui hook le code d'un autre programme mis à jour régulièrement ou simplement si on souhaite retrouver des bouts de code connus à l'avance (d'une autre dll par exemple) sans avoir décompilé le programme cible.
memorybrowser.h
memorybrowser.cpp
La structure MemoryEntry contient à la fois les paramètres nécessaires pour effectuer la recherche dans la mémoire, et le résultat de cette recherche une fois celle-ci effectuée.
Prenons par exemple la première entrée mise en exemple :
MemorySearchEntry((BYTE*)"\x01\x51\x8B\x4D\x18\x52", "xxxxxx", -0x4d, 1, false), //01 51 8B 4D 18 52
Le pattern recherché est la suite d'opcode 01 51 8B 4D 18 52 correspondant à un bout de code supposé ne pas changer selon les updates du jeu, et surtout ne contenant pas d'adresses absolues.
Ce bout de code asm se situe à un offset de +0x4D du début de la fonction (donc le début de la fonction est à -0x4D du pattern) qui nous intéresse (ici une fonction qui envoie les messages du chat du jeu). Les 2 derniers paramètres de l'entrée sont l’occurrence du pattern qui nous intéresse (ici la première occurrence), et le type de valeur à lire.
Comme vous le savez surement les adresses de variables statiques sont inscrites telles quelles dans l'opcode alors que les adresses de jump et de call sont inscrite en adresse relative par rapport à l'instruction de jmp/call.
Nous avons donc ici 3 modes de lecture : MemorySearchEntry::RT_LOCATION qui renvoie directement l'adresse à laquelle se trouve le byte pattern (modulo l'offset et l'occurrence bien sûr). MemorySearchEntry::RT_ADDRESS qui renvoie la valeur contenue à l'adresse trouvée par la recherche (utilisé donc pour une adresse absolue à lire (celle d'une variable statique en général)). Et enfin MemorySearchEntry::RT_REL_ADDRESS qui fait à peu près la même chose que le précédent sauf qu'elle convertie l'adresse relative lue en adresse absolue (pour obtenir une adresse dans une instruction call ou jmp).
La fonction DumpAddresses va dumper dans un fichier texte toutes les adresses que nous avons donné dans la table de patterns sous la forme :
#define CHAT_SEND_MESSAGE_FUNCTION 0x005F4C50 dans le cas où l'on voudrait par exemple définir dans notre programme l'adresse des fonctions à hooker de manière statique.
La manière dynamique d'obtenir le même résultat en utilisant dynamiquement le memory browser est décrire :
#define CHAT_SEND_MESSAGE_FUNCTION MemoryPatternTable[GAID_CHAT_SEND_MESSAGE_FUNCTION].Address
Et bien évidemment s'assurer que ce define n'est pas utilisé avant d'avoir appelé RetrieveAddresses(); ...
On peut avoir besoin d'un tel outil dans le cas d'un programme qui hook le code d'un autre programme mis à jour régulièrement ou simplement si on souhaite retrouver des bouts de code connus à l'avance (d'une autre dll par exemple) sans avoir décompilé le programme cible.
memorybrowser.h
- Code:
#ifndef MEMORYBROWSER_H
#define MEMORYBROWSER_H
#include<stdio.h>
#include<windows.h>
// The list of all the interesting addresses we use
enum GameAddressId
{
GAID_CHAT_SEND_MESSAGE_FUNCTION,
GAID_DETOUR_CRASH_HANDLER_OFFSET,
GAID_DETOUR_CHAT_RECEIVE_OFFSET,
GAID_MAX
};
// a structure containing all the parameters and the result for finding a given address
struct MemorySearchEntry
{
enum ReadType {RT_LOCATION, RT_ADDRESS, RT_REL_ADDRESS};
MemorySearchEntry() : Pattern((BYTE*)""), PatternMask(""), Offset(0), Occurence(0), ReadType(RT_LOCATION), Address(0) {}
MemorySearchEntry(BYTE* pattern, char* patternMask, int offset = 0, unsigned int occurence = 1, ReadType readType = RT_LOCATION)
: Pattern(pattern), PatternMask(patternMask), Offset(offset), Occurence(occurence), ReadType(readType), Address(0) {}
BYTE* Pattern;
char* PatternMask;
int Offset;
unsigned int Occurence;
ReadType ReadType;
ULONG Address;
};
bool RetrieveAddresses();// retrieve all the addresses of the list
bool DumpAddresses();// dump all the addresses of the list
#endif
memorybrowser.cpp
- Code:
#include "memorybrowser.h"
#include <string>
// The table of all the addresses alias we use (for dump and debug)
const char* const MemoryPatternTableStrings[GAID_MAX] =
{
"CHAT_SEND_MESSAGE_FUNCTION",
"DETOUR_CRASH_HANDLER_OFFSET",
"DETOUR_CHAT_RECEIVE_OFFSET",
};
// The table of all the memory entries we use initialised with all the parameters we need to retrieve the addresses
MemorySearchEntry MemoryPatternTable[GAID_MAX] =
{
MemorySearchEntry((BYTE*)"\x01\x51\x8B\x4D\x18\x52", "xxxxxx", -0x4d, 1, MemorySearchEntry::RT_LOCATION), //01 51 8B 4D 18 52
MemorySearchEntry((BYTE*)"\x2C\x56\x57\x8D\x45\xF3", "xxxxxx", -0x1A, 1, MemorySearchEntry::RT_LOCATION), //2C 56 57 8D 45 F3
MemorySearchEntry((BYTE*)"\x04\x02\x00\x00\x53\x56", "xxxxxx", -0x1A, 1, MemorySearchEntry::RT_LOCATION), //04 02 00 00 53 56
};
// check any match between the given address content and a byte pattern (any other char than 'x' used as wildcard)
bool Match(const BYTE* pData, const BYTE* bMask, const char* szMask)
{
for(;*szMask;++szMask,++pData,++bMask)
if(*szMask=='x' && *pData!=*bMask )
return false;
return (*szMask) == NULL;
}
// find the unique address fitting with all the parameters
DWORD FindPattern(DWORD dwAddress, DWORD dwLen, BYTE* bMask, char* szMask, unsigned int occurence)
{
if(occurence == 0)
return 0;
for(DWORD i=0; i < dwLen; i++)
if( Match( (BYTE*)( dwAddress+i ),bMask,szMask) )
if((--occurence) == 0)
return (DWORD)(dwAddress+i);
return 0;
}
bool RetrieveAddresses()
{
HMODULE hmod = GetModuleHandle((LPCSTR)"ProcessTarget.exe");
if(hmod == 0)
return false;
MEMORY_BASIC_INFORMATION info;
// Start at PE32 header
SIZE_T len = VirtualQuery(hmod, &info, sizeof(info));
BYTE* processBase = (BYTE*)info.AllocationBase;
BYTE* address = processBase;
SIZE_T size = 0;
for (;;)
{
len = VirtualQuery(address, &info, sizeof(info));
if (info.AllocationBase != processBase)
break;
address = (BYTE*)info.BaseAddress + info.RegionSize;
size += info.RegionSize;
}
// we now have the size of the memory to browse inside the process
// process the search !
bool errorFound = false;
for(int i = 0; i < GAID_MAX; ++i)
{
MemorySearchEntry& entry = MemoryPatternTable[i];
DWORD dwaddy = FindPattern((DWORD)processBase, (DWORD)size, entry.Pattern, entry.PatternMask, entry.Occurence);
if(dwaddy == 0)
{
errorFound = true;
continue;
}
dwaddy += entry.Offset;
if(entry.ReadType == MemorySearchEntry::RT_ADDRESS)
dwaddy = *(DWORD*)dwaddy;
else if(entry.ReadType == MemorySearchEntry::RT_REL_ADDRESS)
{
unsigned int addr = *(DWORD*)dwaddy;
addr += (dwaddy + 4);
dwaddy = addr;
}
entry.Address = dwaddy;
}
return !errorFound;
}
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
bool DumpAddresses()
{
char dllPath[_MAX_PATH];
::GetModuleFileName((HINSTANCE)&__ImageBase, dllPath, _MAX_PATH);
std::string path = dllPath;
std::string::size_type pos = path.rfind('\\');
path = path.substr(0, pos+1);
std::string filename = path + "addresses.txt";
FILE* file = fopen(filename.c_str(), "w");
if (!file)
return false;
for(int i = 0; i < GAID_MAX; ++i)
{
MemorySearchEntry& entry = MemoryPatternTable[i];
char value[16];
sprintf(value, " 0x%08X \n", entry.Address);
std::string str = "#define ";
str += MemoryPatternTableStrings[i];
str += value;
fwrite(str.c_str(), 1, str.size(), file);
}
fclose(file);
return true;
}
La structure MemoryEntry contient à la fois les paramètres nécessaires pour effectuer la recherche dans la mémoire, et le résultat de cette recherche une fois celle-ci effectuée.
Prenons par exemple la première entrée mise en exemple :
MemorySearchEntry((BYTE*)"\x01\x51\x8B\x4D\x18\x52", "xxxxxx", -0x4d, 1, false), //01 51 8B 4D 18 52
Le pattern recherché est la suite d'opcode 01 51 8B 4D 18 52 correspondant à un bout de code supposé ne pas changer selon les updates du jeu, et surtout ne contenant pas d'adresses absolues.
Ce bout de code asm se situe à un offset de +0x4D du début de la fonction (donc le début de la fonction est à -0x4D du pattern) qui nous intéresse (ici une fonction qui envoie les messages du chat du jeu). Les 2 derniers paramètres de l'entrée sont l’occurrence du pattern qui nous intéresse (ici la première occurrence), et le type de valeur à lire.
Comme vous le savez surement les adresses de variables statiques sont inscrites telles quelles dans l'opcode alors que les adresses de jump et de call sont inscrite en adresse relative par rapport à l'instruction de jmp/call.
Nous avons donc ici 3 modes de lecture : MemorySearchEntry::RT_LOCATION qui renvoie directement l'adresse à laquelle se trouve le byte pattern (modulo l'offset et l'occurrence bien sûr). MemorySearchEntry::RT_ADDRESS qui renvoie la valeur contenue à l'adresse trouvée par la recherche (utilisé donc pour une adresse absolue à lire (celle d'une variable statique en général)). Et enfin MemorySearchEntry::RT_REL_ADDRESS qui fait à peu près la même chose que le précédent sauf qu'elle convertie l'adresse relative lue en adresse absolue (pour obtenir une adresse dans une instruction call ou jmp).
La fonction DumpAddresses va dumper dans un fichier texte toutes les adresses que nous avons donné dans la table de patterns sous la forme :
#define CHAT_SEND_MESSAGE_FUNCTION 0x005F4C50 dans le cas où l'on voudrait par exemple définir dans notre programme l'adresse des fonctions à hooker de manière statique.
La manière dynamique d'obtenir le même résultat en utilisant dynamiquement le memory browser est décrire :
#define CHAT_SEND_MESSAGE_FUNCTION MemoryPatternTable[GAID_CHAT_SEND_MESSAGE_FUNCTION].Address
Et bien évidemment s'assurer que ce define n'est pas utilisé avant d'avoir appelé RetrieveAddresses(); ...
Page 1 sur 1
Permission de ce forum:
Vous ne pouvez pas répondre aux sujets dans ce forum
|
|