// modules.cpp — /proc/self/maps parsing and module discovery #include "hl2h.h" #include #include static bool basename_is(const char *path, const char *base) { size_t pl = strlen(path), bl = strlen(base); if (pl < bl || strcmp(path + pl - bl, base) != 0) return false; return pl == bl || path[pl - bl - 1] == '/'; } void find_modules() { g_client = Module(); g_engine = Module(); g_server = Module(); FILE *f = fopen("/proc/self/maps", "r"); if (!f) return; char line[1024]; while (fgets(line, sizeof(line), f)) { unsigned long lo, hi; char perms[8]; int consumed = 0; if (sscanf(line, "%lx-%lx %7s %*x %*x:%*x %*u%n", &lo, &hi, perms, &consumed) < 3) continue; if (perms[0] == '-' && perms[1] == '-' && perms[2] == '-') continue; // PROT_NONE const char *path = line + consumed; while (*path == ' ' || *path == '\t') path++; size_t pl = strlen(path); while (pl && (path[pl - 1] == '\n' || path[pl - 1] == '\r')) pl--; Module *m = nullptr; char clean[1024]; if (pl && pl < sizeof(clean)) { memcpy(clean, path, pl); clean[pl] = 0; if (basename_is(clean, "client.so")) m = &g_client; else if (basename_is(clean, "engine.so")) m = &g_engine; else if (basename_is(clean, "server.so")) m = &g_server; } else { // anon: absorb only as .bss tail after a file-backed range if (g_client.valid() && g_client.ranges.back().file && lo == g_client.ranges.back().hi && hi - lo <= 0x1000000) m = &g_client; else if (g_engine.valid() && g_engine.ranges.back().file && lo == g_engine.ranges.back().hi && hi - lo <= 0x1000000) m = &g_engine; } if (!m) continue; if (m->ranges.empty()) { if (!pl) continue; m->path = clean; } bool file = pl != 0; if (!m->ranges.empty() && lo == m->ranges.back().hi) { m->ranges.back().hi = hi; m->ranges.back().file = m->ranges.back().file && file; } else { m->ranges.push_back({lo, hi, file}); } } fclose(f); } // --------------------------------------------------------------------------- // ELF relocation lookup // --------------------------------------------------------------------------- // // The server RandomSeed hook used to hardcode the GOT offset measured on one // server.so build. The anniversary branch ships a different server.so // (hl2_complete/bin/server.so) whose slot moved, so the patch silently landed // on unrelated data and No spread stopped working. Read the loaded module's // ELF relocation table instead: find the R_386_JMP_SLOT for a symbol and // return its virtual address plus the load bias implied by the first PT_LOAD. bool module_jump_slot(const char *path, const char *name, uintptr_t *out_vaddr, uintptr_t *out_bias) { if (!path || !*path || !name) return false; FILE *f = fopen(path, "rb"); if (!f) return false; if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return false; } long sz = ftell(f); if (sz < (long)sizeof(Elf32_Ehdr) || fseek(f, 0, SEEK_SET) != 0) { fclose(f); return false; } std::vector buf((size_t)sz); size_t got = fread(buf.data(), 1, buf.size(), f); fclose(f); if (got != buf.size()) return false; if (buf.size() < sizeof(Elf32_Ehdr)) return false; const Elf32_Ehdr *eh = (const Elf32_Ehdr *)buf.data(); if (memcmp(eh->e_ident, ELFMAG, SELFMAG) != 0 || eh->e_ident[EI_CLASS] != ELFCLASS32) return false; if (eh->e_shoff == 0 || eh->e_shentsize != sizeof(Elf32_Shdr)) return false; if ((uint64_t)eh->e_shoff + (uint64_t)eh->e_shnum * sizeof(Elf32_Shdr) > buf.size()) return false; const Elf32_Shdr *sh = (const Elf32_Shdr *)(buf.data() + eh->e_shoff); uintptr_t bias = 0; if (eh->e_phoff && eh->e_phnum && eh->e_phentsize == sizeof(Elf32_Phdr) && (uint64_t)eh->e_phoff + (uint64_t)eh->e_phnum * sizeof(Elf32_Phdr) <= buf.size()) { const Elf32_Phdr *ph = (const Elf32_Phdr *)(buf.data() + eh->e_phoff); for (int i = 0; i < eh->e_phnum; i++) { if (ph[i].p_type == PT_LOAD) { bias = ph[i].p_vaddr; break; } } } for (int s = 0; s < eh->e_shnum; s++) { if (sh[s].sh_type != SHT_REL || sh[s].sh_link >= eh->e_shnum) continue; if ((uint64_t)sh[s].sh_offset + (uint64_t)sh[s].sh_size > buf.size()) continue; const Elf32_Shdr &symsh = sh[sh[s].sh_link]; if (symsh.sh_link >= eh->e_shnum) continue; const Elf32_Shdr &strsh = sh[symsh.sh_link]; if ((uint64_t)symsh.sh_offset + (uint64_t)symsh.sh_size > buf.size() || (uint64_t)strsh.sh_offset + (uint64_t)strsh.sh_size > buf.size()) continue; const Elf32_Rel *rels = (const Elf32_Rel *)(buf.data() + sh[s].sh_offset); size_t nrels = sh[s].sh_size / sizeof(Elf32_Rel); const Elf32_Sym *syms = (const Elf32_Sym *)(buf.data() + symsh.sh_offset); size_t nsyms = symsh.sh_size / sizeof(Elf32_Sym); const char *strs = (const char *)(buf.data() + strsh.sh_offset); size_t strn = strsh.sh_size; for (size_t r = 0; r < nrels; r++) { unsigned type = ELF32_R_TYPE(rels[r].r_info); unsigned sym = ELF32_R_SYM(rels[r].r_info); if (type != R_386_JMP_SLOT || sym >= nsyms) continue; if (syms[sym].st_name >= strn) continue; const char *sn = strs + syms[sym].st_name; if (strcmp(sn, name) != 0) continue; if (out_vaddr) *out_vaddr = rels[r].r_offset; if (out_bias) *out_bias = bias; return true; } } return false; }