1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
// modules.cpp — /proc/self/maps parsing and module discovery
#include "hl2h.h"
#include <elf.h>
#include <mutex>
#include <vector>
static std::mutex g_modules_mutex;
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() {
Module client, engine, server;
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 = &client;
else if (basename_is(clean, "engine.so"))
m = &engine;
else if (basename_is(clean, "server.so"))
m = &server;
} else {
// anon: absorb only as .bss tail after a file-backed range
if (client.valid() && client.ranges.back().file &&
lo == client.ranges.back().hi && hi - lo <= 0x1000000)
m = &client;
else if (engine.valid() && engine.ranges.back().file &&
lo == engine.ranges.back().hi && hi - lo <= 0x1000000)
m = &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);
// setup_thread and resolve_server_netvars can both call this; the old code
// reset the shared Module globals and reallocated their vectors live, which
// crashed under the startup thread. Publish complete snapshots instead.
std::lock_guard<std::mutex> lock(g_modules_mutex);
if (client.valid())
g_client = std::move(client);
if (engine.valid())
g_engine = std::move(engine);
if (server.valid())
g_server = std::move(server);
}
// ---------------------------------------------------------------------------
// 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<unsigned char> 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;
}
|