From 33f5ed9ef58f26c6b3b52cf605dd4311d658a60d Mon Sep 17 00:00:00 2001 From: salmonik Date: Fri, 6 Nov 2026 16:09:55 +0100 Subject: Fix audit findings: crash safety, correctness, rendering, performance Crash/UB: - gate every hook behind an init-stage flag until config/surface/input are ready - publish module maps as immutable atomic snapshots instead of reallocating shared vectors under unlocked readers - validate the %ebp walk in the RandomSeed/RandomFloat hooks against the stack - bounds-check every RecvTable/RecvProp/string dereference with module_range() - preserve the Itanium vtable header when cloning (client, CInput, model render, studio render) - reject negative usercmd sequence numbers; guard config_load against a null section target; cap the bone parent walk; check mprotect; drop PROT_EXEC on the GOT patch; absorb server.so .bss tail Correctness: - filter dormant entities across ESP, aimbot, triggerbot, melee, items and projectiles (slot self-tested against the local player) - reset the server-health latch per map and only latch on valid model indices - bone cache sized for the 128-target scan; set valid=false on every failure; head_position keeps searching past an invalid bone - vischeck steps past glass by probing until clear, and ignores entities owned by the target (held weapons, bone followers) - aimbot: maxs no longer disables it, AABB-aware FOV prefilter, lock hysteresis, lead point re-checked for FOV and visibility - spread: seed hook arms on the no-spread toggle alone; engine toggle only controls the RandomFloat zeroing - resolve m_hOwnerEntity/m_hOwner; NPC lead velocity from the server entity - vector_angles pitch range, point_ground normal test, underwater jump+duck no longer skips combat, semi-auto press/release edge for synthetic fire - item/projectile markers and blast rings refresh origin per frame while discovery stays at 10 Hz; trajectory uses the real m_DmgRadius and honours the blast-radius toggle Rendering/GUI: - one overlay draw per engine frame (FRAME_RENDER_START boundary), fixing alpha stacking and the speed-graph flicker - clipped lines/outlines and ellipsised text instead of drawing past the content pane or shifting over neighbours - dropdown dismissal consumes the click; hover ignores hidden controls - chams: reset AlphaModulate, reference shared materials once, restore prop tint colour; fonts created once per handle - projection uses Source's 4:3 aspect scaling and roll; screen size refreshed each frame; FOV circle gated on the aim toggles, not the bind Performance: - TTL caches for NPC relation, friendly/neutral and damage class - model names memoised per model index - entity pointer -> index cache for the triggerbot (was a 4096-slot walk/tick) Build: - real Makefile with -MMD dependency tracking, debug/release objects and an always-relink rule; build.sh kept as a thin wrapper --- src/netvars.cpp | 50 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 16 deletions(-) (limited to 'src/netvars.cpp') diff --git a/src/netvars.cpp b/src/netvars.cpp index e22ff38..8e9579c 100644 --- a/src/netvars.cpp +++ b/src/netvars.cpp @@ -6,11 +6,15 @@ // ClientClass (2004-era): CreateFn@0, CreateEventFn@4, Name@8, RecvTable@0xC, Next@0x10 static bool module_string(uintptr_t p, char *out, size_t outn) { - if (!g_client.contains(p) && !g_engine.contains(p) && !g_server.contains(p)) + if (!p || outn == 0 || !module_range(p, 1)) return false; const char *s = (const char *)p; size_t i = 0; while (i < outn - 1) { + // Validate every byte, not just the first: a string near the end of a + // mapping must not walk into an unmapped page looking for its NUL. + if (!module_range(p + i, 1)) + return false; unsigned char ch = (unsigned char)s[i]; if (ch == 0) break; @@ -30,9 +34,13 @@ static int g_dumped_tables = 0; static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { if (depth > 8) return; + // RecvTable is 16 bytes; validate before the first read (the table pointer + // and the nested pDataTable both come from engine memory). + if (!module_range(table, 0x10)) + return; uintptr_t props = *(uintptr_t *)(table + 0x00); int32_t nprops = *(int32_t *)(table + 0x04); - if (!props || !g_client.contains(props) || nprops <= 0 || nprops > 512) + if (!props || !mod_client().contains(props) || nprops <= 0 || nprops > 512) return; char tname[64]; @@ -52,7 +60,7 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { for (int32_t i = 0; i < nprops; i++) { uintptr_t prop = props + (size_t)i * 0x3C; - if (!g_client.contains(prop)) + if (!mod_client().contains(prop)) return; uintptr_t pname = *(uintptr_t *)(prop + 0x00); int32_t ptype = *(int32_t *)(prop + 0x04); @@ -74,7 +82,7 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { strncmp(pname2, "m_hMyWeapons", 12) == 0) { int off = (int)(base_off + (uint32_t)poff); uintptr_t elem = parray ? parray : ptable; - if (elem) { + if (elem && module_range(elem, 0x30)) { int32_t eoff = *(int32_t *)(elem + 0x2C); if (eoff) off = (int)(base_off + (uint32_t)eoff); @@ -89,7 +97,7 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { if (g_net.ammo < 0 && strncmp(pname2, "m_iAmmo", 7) == 0) { int off = (int)(base_off + (uint32_t)poff); uintptr_t elem = parray ? parray : ptable; - if (elem) { + if (elem && module_range(elem, 0x30)) { int32_t eoff = *(int32_t *)(elem + 0x2C); if (eoff) off = (int)(base_off + (uint32_t)eoff); @@ -113,7 +121,7 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { } else if (strcmp(pname2, "m_fFlags") == 0 && g_net.flags < 0) { g_net.flags = (int)(base_off + (uint32_t)poff); } else if (strcmp(pname2, "m_vecVelocity") == 0 && g_net.velocity < 0) { - if (parray && g_client.contains(parray)) { + if (parray && module_range(parray, 0x30)) { int32_t eoff = *(int32_t *)(parray + 0x2C); g_net.velocity = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); } else { @@ -126,6 +134,16 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { } else if (strcmp(pname2, "m_hActiveWeapon") == 0 && g_net.activeweapon < 0) { g_net.activeweapon = (int)(base_off + (uint32_t)poff); + } else if (strcmp(pname2, "m_hOwnerEntity") == 0 && g_net.owner < 0) { + // Used by vischeck to ignore a target's held weapon / bone followers, + // and by item ESP for weapon ownership. + int off = (int)(base_off + (uint32_t)poff); + if (off >= 0x20 && off <= 0x2000) + g_net.owner = off; + } else if (strcmp(pname2, "m_hOwner") == 0 && g_net.weapon_owner < 0) { + int off = (int)(base_off + (uint32_t)poff); + if (off >= 0x20 && off <= 0x2000) + g_net.weapon_owner = off; } else if (strcmp(pname2, "m_clrRender") == 0 && g_net.clr_render < 0) { int off = (int)(base_off + (uint32_t)poff); // Reject the 0 offset some embedded tables report; the real render @@ -149,7 +167,7 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { } else if (strcmp(pname2, "m_iTeamNum") == 0 && g_net.teamnum < 0) { g_net.teamnum = (int)(base_off + (uint32_t)poff); } else if (strcmp(pname2, "m_vecOrigin") == 0 && g_net.origin < 0) { - if (parray && g_client.contains(parray)) { + if (parray && module_range(parray, 0x30)) { int32_t eoff = *(int32_t *)(parray + 0x2C); g_net.origin = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); } else { @@ -157,21 +175,21 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { } } else if (strncmp(pname2, "m_vecViewOffset", 15) == 0 && g_net.viewoffset < 0) { - if (parray && g_client.contains(parray)) { + if (parray && module_range(parray, 0x30)) { int32_t eoff = *(int32_t *)(parray + 0x2C); g_net.viewoffset = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); } else { g_net.viewoffset = (int)(base_off + (uint32_t)poff); } } else if (strcmp(pname2, "m_vecMins") == 0 && g_net.mins < 0) { - if (parray && g_client.contains(parray)) { + if (parray && module_range(parray, 0x30)) { int32_t eoff = *(int32_t *)(parray + 0x2C); g_net.mins = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); } else { g_net.mins = (int)(base_off + (uint32_t)poff); } } else if (strcmp(pname2, "m_vecMaxs") == 0 && g_net.maxs < 0) { - if (parray && g_client.contains(parray)) { + if (parray && module_range(parray, 0x30)) { int32_t eoff = *(int32_t *)(parray + 0x2C); g_net.maxs = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); } else { @@ -187,7 +205,7 @@ static bool resolve_movetype() { const size_t nlen = sizeof(needle); uintptr_t str_va = 0; - for (auto &r : g_client.ranges) { + for (auto &r : mod_client().ranges) { const uint8_t *base = (const uint8_t *)r.lo; size_t span = r.hi - r.lo; for (size_t i = 0; i + nlen <= span; i++) { @@ -208,7 +226,7 @@ static bool resolve_movetype() { int counts[16] = {}; int ncand = 0; const uint32_t target = (uint32_t)str_va; - for (auto &r : g_client.ranges) { + for (auto &r : mod_client().ranges) { const uint8_t *base = (const uint8_t *)r.lo; size_t span = r.hi - r.lo; for (size_t off = 4; off + 8 <= span; off += 4) { @@ -247,7 +265,7 @@ static bool resolve_absvelocity() { const size_t nlen = sizeof(needle); uintptr_t str_va = 0; - for (auto &r : g_client.ranges) { + for (auto &r : mod_client().ranges) { const uint8_t *base = (const uint8_t *)r.lo; size_t span = r.hi - r.lo; for (size_t i = 0; i + nlen <= span; i++) { @@ -268,7 +286,7 @@ static bool resolve_absvelocity() { int counts[16] = {}; int ncand = 0; const uint32_t target = (uint32_t)str_va; - for (auto &r : g_client.ranges) { + for (auto &r : mod_client().ranges) { const uint8_t *base = (const uint8_t *)r.lo; size_t span = r.hi - r.lo; for (size_t off = 4; off + 8 <= span; off += 4) { @@ -306,12 +324,12 @@ void resolve_netvars() { using GetAllClassesFn = void *(*)(void *); uintptr_t head = (uintptr_t)vfunc(g_iface.client, 8)(g_iface.client); - if (!g_client.contains(head)) { + if (!mod_client().contains(head)) { logf("GetAllClasses returned %p, outside client module", (void *)head); return; } int classes = 0; - for (uintptr_t cc = head; cc && g_client.contains(cc) && classes < 1024; + for (uintptr_t cc = head; cc && mod_client().contains(cc) && classes < 1024; cc = *(uintptr_t *)(cc + 0x10)) { classes++; collect_netvars(*(uintptr_t *)(cc + 0x0C), 0, 0); -- cgit v1.2.3