- Статус
- Оффлайн
- Регистрация
- 13 Фев 2026
- Сообщения
- 706
- Реакции
- 18
Накину полезный сниппет для тех, кто ковыряет Source Engine извне (External). Если вы пишете свой проект под старую добрую CSS и вам надоело хардкодить смещения интерфейсов или искать их вручную после каждого мелкого апдейта, этот код сэкономит вам время.
Суть проста: парсим таблицу экспортов нужного модуля, находим CreateInterface, вычисляем относительный адрес головы списка регистраций и проходим по всей связке. На выходе получаем имена интерфейсов и их актуальные оффсеты.
Функция поиска адреса экспорта:
Сам дампер интерфейсов:
Пара технических моментов:
Метод надежный как швейцарские часы и позволяет динамически цепляться к VClient, VEngineClient и прочим радостям движка без лишней головной боли. Форкайте под свои нужды, база рабочая.
Суть проста: парсим таблицу экспортов нужного модуля, находим CreateInterface, вычисляем относительный адрес головы списка регистраций и проходим по всей связке. На выходе получаем имена интерфейсов и их актуальные оффсеты.
Функция поиска адреса экспорта:
Код:
inline uintptr_t GetModuleExportAddress(uintptr_t Base, const char* funcName)
{
auto peHeadersOffset = read<int32_t>(Base + 0x3c);
auto peHeaders = read<IMAGE_NT_HEADERS64>(Base + peHeadersOffset);
auto exportDirRVA = peHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
auto exports = read<IMAGE_EXPORT_DIRECTORY>(Base + exportDirRVA);
for (DWORD i = 0; i < exports.NumberOfNames; i++)
{
DWORD nameRVA = read<DWORD>(Base + exports.AddressOfNames + i * 4);
auto name = read_string(Base + nameRVA);
if (strcmp(name.chars, funcName) == 0)
{
WORD ordinal = read<WORD>(Base + exports.AddressOfNameOrdinals + i * 2);
DWORD funcRVA = read<DWORD>(Base + exports.AddressOfFunctions + ordinal * 4);
return Base + funcRVA;
}
}
return 0;
}
Сам дампер интерфейсов:
Код:
inline void DumpInterfaces(const char* moduleName, uintptr_t ModuleBase)
{
struct TInterfaceReg {
uintptr_t callback; // Указатель на Create-функцию
uintptr_t namePtr; // Указатель на строку с именем
uintptr_t next; // Следующий нод в списке
};
uintptr_t exportAddr = GetModuleExportAddress(ModuleBase, "CreateInterface");
if (!exportAddr) {
printf("Failed to find CreateInterface export in %s\n", moduleName);
return;
}
// Вычисляем jmp/relative offset
int32_t relativeOffset = read<int32_t>(exportAddr + 3);
uintptr_t listHeadGlobal = exportAddr + 7 + relativeOffset;
uintptr_t currentInterfaceAddr = read<uintptr_t>(listHeadGlobal);
printf("--- [%s] Interfaces (Base: 0x%llX) ---\n", moduleName, ModuleBase);
while (currentInterfaceAddr != 0)
{
TInterfaceReg reg = read<TInterfaceReg>(currentInterfaceAddr);
auto interfaceName = read_string(reg.namePtr);
uintptr_t regOffset = currentInterfaceAddr - ModuleBase;
uintptr_t funcOffset = reg.callback - ModuleBase;
printf(" > %-32s | RegOffset: 0x%llX | FuncOffset: 0x%llX\n",
interfaceName.chars, regOffset, funcOffset);
currentInterfaceAddr = reg.next;
}
}
Пара технических моментов:
- Код заточен под x64 (IMAGE_NT_HEADERS64), если вы вдруг ковыряете совсем древние 32-битные билды — поправьте структуры под x86.
- Функции read и read_string — это ваши обертки над ReadProcessMemory или драйверным чтением.
- Смещение exportAddr + 3 — это классический паттерн для выхода на голову списка интерфейсов в Source.
Метод надежный как швейцарские часы и позволяет динамически цепляться к VClient, VEngineClient и прочим радостям движка без лишней головной боли. Форкайте под свои нужды, база рабочая.
