Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Гайд CS: Source — Дамп внешних интерфейсов (External Interfaces)

Sloppy
Начинающий
Начинающий
Статус
Оффлайн
Регистрация
13 Фев 2026
Сообщения
706
Реакции
18
Накину полезный сниппет для тех, кто ковыряет Source Engine извне (External). Если вы пишете свой проект под старую добрую CSS и вам надоело хардкодить смещения интерфейсов или искать их вручную после каждого мелкого апдейта, этот код сэкономит вам время.

Суть проста: парсим таблицу экспортов нужного модуля, находим CreateInterface, вычисляем относительный адрес головы списка регистраций и проходим по всей связке. На выходе получаем имена интерфейсов и их актуальные оффсеты.

Функция поиска адреса экспорта:
Код:
Expand Collapse Copy
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;
}

Сам дампер интерфейсов:
Код:
Expand Collapse Copy
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;
 }
}

Пара технических моментов:
  1. Код заточен под x64 (IMAGE_NT_HEADERS64), если вы вдруг ковыряете совсем древние 32-битные билды — поправьте структуры под x86.
  2. Функции read и read_string — это ваши обертки над ReadProcessMemory или драйверным чтением.
  3. Смещение exportAddr + 3 — это классический паттерн для выхода на голову списка интерфейсов в Source.

Метод надежный как швейцарские часы и позволяет динамически цепляться к VClient, VEngineClient и прочим радостям движка без лишней головной боли. Форкайте под свои нужды, база рабочая.
 
Назад
Сверху Снизу