В данной статье я хочу рассказать вам о том, как можно с помощью языка SourcePawn можно прочитать таблицу импорта определенного dll файла.
Что необходимо знать:
Необходимо иметь базовые представления о том, что такое PE-Header исполняемого файла/библиотеки
Какие инструменты будут использоваться:
1) Некоторый функции Другое - [INC] Memory Extended
2) Голый SourcePawn
Предыстория
У вас, возможно, возник вопрос: Для чего все это, разве нельзя обойтись без всего этого?
Ответ:
Вам необходимо взять адрес g_pMemAlloc. Какие шаги необходимы для получения его?
1) Необходимо найти функцию в которой используется тот самый g_pMemAlloc.
2) Узнать сигнатуру данной функции.
3) Создать gamedata + узнать смещение до g_pMemAlloc внутри функции.
4) Использовать в самом плагине gamedata и получить адрес g_pMemAlloc.
В чем же проблема этих шагов? - После очередного обновления игры, сигнатура функции или смещение до нашего g_pMemAlloc могло изменится, и необходимо будет обновлять плагин, но действительно нужно мучиться с этим?
И тут на помощь проходит таблица импорта, с помощью которой - можно упростить жизнь себе и пользователям вашего плагина.
Начало
Все начинается с PE формата, адрес которой = базовому адресу dll, а так же с этой иллюстрации, которая должна быть у вас перед глазами до конца вступительного текста
Для получения адреса таблицы импорта, нам необходимо дойти до этой части иллюстрации
Первое что необходимо сделать, это получить PE заголовок, адрес которого находится что по смещению 0x3C
.
Второй наш шаг, по иллюстрации, узнать, каков размер всего PE заголовка => Он является = 0x78, так же добавляем 0x08 к нашему смещению т.к нам необходимо пропустить таблицу экспорта
В итоге смещение до адреса таблицы = 0x80, но как это выглядит в SourcePawn? -
Теперь у нас есть тот самый адрес таблицы импорта, но что же делать дальше с этим?
Для этого необходимо погрузиться глубже....
Чтение таблицы импорта
Когда я задавался вопросом, что вообще делать дальше - я нашел статью на хабре. Из нее становится ясно, что нам необходимо воссоздать на SourcePawn структуру IMAGE_IMPORT_DESCRIPTOR (Автор предложил смотреть структуру в WINNT.h - но это не обязательно, все это есть в документации => PE Format - Win32 apps)
Но до конца, что это такое, помогла вот эта статья, которая расставила все на свои места - Import x64
На основе полученной информации у меня получилась такая структура
Что же такое
В SourcePawn при попытке узнать смещение элемента в enum struct, например, к dllName, вернется значение = 0x03, но у dllName для корректного чтения информации из памяти, необходимо, чтобы смещение = 0x12, по этому, каждое смещение структуры мы умножаем на 4.
Такая же история, если попытаться узнать размер всей структуры - SourcePawn вернет 0x05 - по этому, мы так же умножаем размер на 4 с помощью
Далее нам необходимо узнать, сколько всего динамических библиотек задействовано, это можно сделать так: Размер таблицы импорта / размер структуры IMAGE_IMPORT_DESCRIPTOR (У нас это ImportDiscription)
На SourcePawn это выглядит так
Остается понять то, как найти во всем этом, нужную нам переменную/функцию. Я не буду описывать весь процесс, ведь это описано в статье, которую я уже упоминал чуть выше => выглядит это примерно так, как изображено на иллюстрации ниже (Вырезал из статьи)
Цель статьи, ведь, показать - как это реализовать на SourcePawn.
Код поиска выглядит так:
Я бы хотел остановится на этом участке кода и рассказать поподробнее, что тут происходит.
Почему я делаю смещение на 0x02 - можно понять из вот этого - PE Format - Win32 apps .
В памяти это выглядит так (Т.е первые два байта - это просто индекс, а далее идет то, что нам необходимо - название)
Позже сравниваем названия - и если оно совпадает - возвращаем нужный нам адрес.
Пример работоспособности
Когда нибудь, этот текст дополнится и для linux, если это будет необходимо.
Но, пока что, это моя первая статья на данном форуме, по этому - постарался более подробно все расписать
Что необходимо знать:
Необходимо иметь базовые представления о том, что такое PE-Header исполняемого файла/библиотеки
Какие инструменты будут использоваться:
1) Некоторый функции Другое - [INC] Memory Extended
2) Голый SourcePawn
Предыстория
У вас, возможно, возник вопрос: Для чего все это, разве нельзя обойтись без всего этого?
Ответ:
Вам необходимо взять адрес g_pMemAlloc. Какие шаги необходимы для получения его?
1) Необходимо найти функцию в которой используется тот самый g_pMemAlloc.
2) Узнать сигнатуру данной функции.
3) Создать gamedata + узнать смещение до g_pMemAlloc внутри функции.
4) Использовать в самом плагине gamedata и получить адрес g_pMemAlloc.
В чем же проблема этих шагов? - После очередного обновления игры, сигнатура функции или смещение до нашего g_pMemAlloc могло изменится, и необходимо будет обновлять плагин, но действительно нужно мучиться с этим?
И тут на помощь проходит таблица импорта, с помощью которой - можно упростить жизнь себе и пользователям вашего плагина.
Начало
Все начинается с PE формата, адрес которой = базовому адресу dll, а так же с этой иллюстрации, которая должна быть у вас перед глазами до конца вступительного текста
Для получения адреса таблицы импорта, нам необходимо дойти до этой части иллюстрации
Первое что необходимо сделать, это получить PE заголовок, адрес которого находится что по смещению 0x3C
Второй наш шаг, по иллюстрации, узнать, каков размер всего PE заголовка => Он является = 0x78, так же добавляем 0x08 к нашему смещению т.к нам необходимо пропустить таблицу экспорта
В итоге смещение до адреса таблицы = 0x80, но как это выглядит в SourcePawn? -
C-подобный:
Address pBase = GetModuleHandle("engine");
Address PE = pBase + view_as<Address>(LoadFromAddress(pBase + view_as<Address>(0x3C), NumberType_Int32)); // Адрес PE заголовка
Address importTable = pBase + view_as<Address>(LoadFromAddress(PE + view_as<Address>(0x80), NumberType_Int32)); // Адрес таблицы импорта
Address importTableSize = view_as<Address>(LoadFromAddress(PE + view_as<Address>(0x84), NumberType_Int32)); // Размер таблицы импорта
Для этого необходимо погрузиться глубже....
Чтение таблицы импорта
Когда я задавался вопросом, что вообще делать дальше - я нашел статью на хабре. Из нее становится ясно, что нам необходимо воссоздать на SourcePawn структуру IMAGE_IMPORT_DESCRIPTOR (Автор предложил смотреть структуру в WINNT.h - но это не обязательно, все это есть в документации => PE Format - Win32 apps)
Но до конца, что это такое, помогла вот эта статья, которая расставила все на свои места - Import x64
На основе полученной информации у меня получилась такая структура
C-подобный:
#define CALC_STRUCT_OFFSET(%0) view_as<Address>(%0 * 0x04)
#define STRUCT_SIZE(%0) (sizeof(%0) * 0x04)
enum struct ImportDiscription
{
Address originalFirstThunk;
int timeDateStamp;
int forwarderChaine;
Address dllName;
Address firstThunk;
void FillInfo(Address base, Address addr)
{
this.originalFirstThunk = base + view_as<Address>(LoadFromAddress(addr + CALC_STRUCT_OFFSET(ImportDiscription::originalFirstThunk), NumberType_Int32));
this.timeDateStamp = LoadFromAddress(addr + CALC_STRUCT_OFFSET(ImportDiscription::timeDateStamp), NumberType_Int32);
this.forwarderChaine = LoadFromAddress(addr + CALC_STRUCT_OFFSET(ImportDiscription::forwarderChaine), NumberType_Int32);
this.dllName = base + view_as<Address>(LoadFromAddress(addr + CALC_STRUCT_OFFSET(ImportDiscription::dllName), NumberType_Int32));
this.firstThunk = base + view_as<Address>(LoadFromAddress(addr + CALC_STRUCT_OFFSET(ImportDiscription::firstThunk), NumberType_Int32));
}
void GetName(char[] sBuffer, int iMaxLength)
{
ReadString(this.dllName, sBuffer, iMaxLength);
}
}
Что же такое
#define CALC_STRUCT_OFFSET(%0) view_as<Address>(%0 * 0x04)
и зачем это нужно?В SourcePawn при попытке узнать смещение элемента в enum struct, например, к dllName, вернется значение = 0x03, но у dllName для корректного чтения информации из памяти, необходимо, чтобы смещение = 0x12, по этому, каждое смещение структуры мы умножаем на 4.
Такая же история, если попытаться узнать размер всей структуры - SourcePawn вернет 0x05 - по этому, мы так же умножаем размер на 4 с помощью
STRUCT_SIZE
Далее нам необходимо узнать, сколько всего динамических библиотек задействовано, это можно сделать так: Размер таблицы импорта / размер структуры IMAGE_IMPORT_DESCRIPTOR (У нас это ImportDiscription)
На SourcePawn это выглядит так
int iSize = view_as<int>(importTableSize) / STRUCT_SIZE(ImportDiscription);
Остается понять то, как найти во всем этом, нужную нам переменную/функцию. Я не буду описывать весь процесс, ведь это описано в статье, которую я уже упоминал чуть выше => выглядит это примерно так, как изображено на иллюстрации ниже (Вырезал из статьи)
Цель статьи, ведь, показать - как это реализовать на SourcePawn.
Код поиска выглядит так:
C-подобный:
Address GetProcAddress(const char[] sLibrary, const char[] sName )
{
return GetServerOS() == OS_Windows ? GetProcAddressWindows(sLibrary, sName) : GetProcAddressLinux(sLibrary, sName);
}
Address GetProcAddressWindows(const char[] sLibrary, const char[] sName )
{
Address pBase = GetModuleHandle(sLibrary);
if(pBase == Address_Null)
{
return Address_Null;
}
Address PE = pBase + view_as<Address>(LoadFromAddress(pBase + view_as<Address>(0x3C), NumberType_Int32));
Address importTable = pBase + view_as<Address>(LoadFromAddress(PE + view_as<Address>(0x80), NumberType_Int32));
Address importTableSize = view_as<Address>(LoadFromAddress(PE + view_as<Address>(0x84), NumberType_Int32));
int iSize = view_as<int>(importTableSize) / STRUCT_SIZE(ImportDiscription);
ImportDiscription disc;
//char sDll[64];
char sFunction[256];
for(int x = 0; x < iSize; x++)
{
Address addr = importTable + view_as<Address>((STRUCT_SIZE(ImportDiscription) * x));
disc.FillInfo(pBase, addr);
// disc.GetName(sDll, sizeof sDll); Это нигде не используется - но для демонстрации было оставлено
for(Address offset = Address_Null; offset >= Address_Null; offset += view_as<Address>(0x04)) // endless cycle
{
Address pFunctionStr = view_as<Address>(LoadFromAddress(disc.originalFirstThunk + offset, NumberType_Int32));
if(pFunctionStr == Address_Null)
{
break;
}
pFunctionStr += pBase;
ReadString(pFunctionStr + view_as<Address>(0x02), sFunction, sizeof sFunction);
if(!strcmp(sName, sFunction))
{
return view_as<Address>(LoadFromAddress(disc.firstThunk + offset, NumberType_Int32));
}
}
}
return Address_Null;
}
Address GetProcAddressLinux(const char[] sLibrary, const char[] sName )
{
//....
}
Я бы хотел остановится на этом участке кода и рассказать поподробнее, что тут происходит.
C-подобный:
ReadString(pFunctionStr + view_as<Address>(0x02), sFunction, sizeof sFunction);
if(!strcmp(sName, sFunction))
{
return view_as<Address>(LoadFromAddress(disc.firstThunk + offset, NumberType_Int32));
}
Почему я делаю смещение на 0x02 - можно понять из вот этого - PE Format - Win32 apps .
В памяти это выглядит так (Т.е первые два байта - это просто индекс, а далее идет то, что нам необходимо - название)
Пример работоспособности
C-подобный:
#include <MemoryEx/ServerLibrary>
#define CALC_STRUCT_OFFSET(%0) view_as<Address>(%0 * 0x04)
#define STRUCT_SIZE(%0) (sizeof(%0) * 0x04)
enum struct ImportDiscription
{
Address originalFirstThunk;
int timeDateStamp;
int forwarderChaine;
Address dllName;
Address firstThunk;
void FillInfo(Address base, Address addr)
{
this.originalFirstThunk = base + view_as<Address>(LoadFromAddress(addr + CALC_STRUCT_OFFSET(ImportDiscription::originalFirstThunk), NumberType_Int32));
this.timeDateStamp = LoadFromAddress(addr + CALC_STRUCT_OFFSET(ImportDiscription::timeDateStamp), NumberType_Int32);
this.forwarderChaine = LoadFromAddress(addr + CALC_STRUCT_OFFSET(ImportDiscription::forwarderChaine), NumberType_Int32);
this.dllName = base + view_as<Address>(LoadFromAddress(addr + CALC_STRUCT_OFFSET(ImportDiscription::dllName), NumberType_Int32));
this.firstThunk = base + view_as<Address>(LoadFromAddress(addr + CALC_STRUCT_OFFSET(ImportDiscription::firstThunk), NumberType_Int32));
}
void GetName(char[] sBuffer, int iMaxLength)
{
ReadString(this.dllName, sBuffer, iMaxLength);
}
}
public void OnPluginStart()
{
Address pProcId = GetProcAddress("server", "GetCurrentProcessId");
PrintToServer("pProcId = 0x%X offsetoriginal = 0x%X time off = %d sizeof = %d", pProcId, ImportDiscription::originalFirstThunk, ImportDiscription::timeDateStamp, sizeof(ImportDiscription));
if(pProcId == Address_Null)
{
return;
}
StartPrepSDKCall(SDKCall_Static);
PrepSDKCall_SetAddress(pProcId);
PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain);
Handle h = EndPrepSDKCall();
int iProcID = SDKCall(h);
PrintToServer("CurrentProcessId = %d", iProcID);
delete h;
}
Address GetProcAddress(const char[] sLibrary, const char[] sName )
{
return GetServerOS() == OS_Windows ? GetProcAddressWindows(sLibrary, sName) : GetProcAddressLinux(sLibrary, sName);
}
Address GetProcAddressWindows(const char[] sLibrary, const char[] sName )
{
Address pBase = GetModuleHandle(sLibrary);
if(pBase == Address_Null)
{
return Address_Null;
}
Address PE = pBase + view_as<Address>(LoadFromAddress(pBase + view_as<Address>(0x3C), NumberType_Int32));
Address importTable = pBase + view_as<Address>(LoadFromAddress(PE + view_as<Address>(0x80), NumberType_Int32));
Address importTableSize = view_as<Address>(LoadFromAddress(PE + view_as<Address>(0x84), NumberType_Int32));
int iSize = view_as<int>(importTableSize) / STRUCT_SIZE(ImportDiscription);
ImportDiscription disc;
//char sDll[64];
char sFunction[256];
for(int x = 0; x < iSize; x++)
{
Address addr = importTable + view_as<Address>((STRUCT_SIZE(ImportDiscription) * x));
disc.FillInfo(pBase, addr);
//disc.GetName(sDll, sizeof sDll);
for(Address offset = Address_Null; offset >= Address_Null; offset += view_as<Address>(0x04)) // endless cycle
{
Address pFunctionStr = view_as<Address>(LoadFromAddress(disc.originalFirstThunk + offset, NumberType_Int32));
if(pFunctionStr == Address_Null)
{
break;
}
pFunctionStr += pBase;
ReadString(pFunctionStr + view_as<Address>(0x02), sFunction, sizeof sFunction);
if(!strcmp(sName, sFunction))
{
return view_as<Address>(LoadFromAddress(disc.firstThunk + offset, NumberType_Int32));
}
}
}
return Address_Null;
}
stock Address GetProcAddressLinux(const char[] sLibrary, const char[] sName )
{
// ...
}
Когда нибудь, этот текст дополнится и для linux, если это будет необходимо.
Но, пока что, это моя первая статья на данном форуме, по этому - постарался более подробно все расписать
