Half life 1 hacking notes

Hexui Undetected CSGO Cheats Sinkicheat PUBG Cheat

h4nsbr1x

Dank Tier Donator
Full Member
Jul 24, 2020
30
2,448
0
How long you been coding/hacking?
years
Coding Language
N/A
I've got a couple fun projects in progress, but I realised my actual game-specific hacking needs a bit of work, so I figured I'd go through some of the classics to get up to speed. I'm aware the source is out there, but my goal is to learn how to reverse, rather than jump to any specific hacks

Here's a summary of what I've got so far:
  • The game is run out of hw.dll and it relies on hl.dll for the entities and some game logic. Having both open in IDA helps a lot
  • Player entity is pretty easy to find, rest is stored in nested linked lists (half of which are stored in globals that only get referenced via the linked list...)
  • ClassInformer is pretty useful for a bunch of stuff, as is the export table in hl.dll for finding a bunch of weapon init code
  • Keyboard is handled through SDL and the switch statement for handling the KEYDOWN messages was pretty easy to navigate
Specifics:

View angles are at hw.dll+108AEC4 and hw.dll+108AEC8, they're in degrees, 90 pitch is down, 0 yaw faces along the X axis, 90 yaw faces along the Y axis. Here's some asm for you kids (blink forward 100.0 units when you press Z, enough to jump into an office)

Code:
{ Game   : hl.exe
  Version:
  Date   : 2020-09-29
  Author : User

  This script does blah blah blah
}

[ENABLE]

aobscanmodule(blink,hw.dll,8B 0D 6C C3 99 03 83 C4 10 50) // should be unique
alloc(newmem,$1000)

label(code)
label(return)

newmem:
  cmp dword ptr [ebp-40], 0x300
  jne code
  cmp eax, 0x7A
  je hax
  jmp code

hax:
  pushad
  mov eax, [hw.dll+7F6304]
  test eax, eax
  jz abort
  // get yaw
  fld dword ptr [hw.dll+108AEC8]
  fld dword ptr [degreestoradians]
  fmulp
  fsincos
  // cos on bottom, get x
  fld dword ptr [jumpvalue]
  fmulp
  fld [eax+88]
  faddp
  fstp [eax+88]
  // sin next, that's our y
  fld dword ptr [jumpvalue]
  fmulp
  fld [eax+8c]
  faddp
  fstp [eax+8c]
  jmp abort

abort:
  popad
  jmp code

jumpvalue:
  dd (float)100.0

degreestoradians:
  dd (float)0.017452

code:
  mov ecx,[hw.dll+15C36C]
  jmp return

blink:
  jmp newmem
  nop
return:
registersymbol(blink)

[DISABLE]

blink:
  db 8B 0D 6C C3 99 03

unregistersymbol(blink)
dealloc(newmem)

{
// ORIGINAL CODE - INJECTION POINT: "hw.dll"+B00C7

"hw.dll"+B00A9: 8B 4D D4              -  mov ecx,[ebp-2C]
"hw.dll"+B00AC: 83 EC 10              -  sub esp,10
"hw.dll"+B00AF: 8B D4                 -  mov edx,esp
"hw.dll"+B00B1: 89 02                 -  mov [edx],eax
"hw.dll"+B00B3: 8B 45 D8              -  mov eax,[ebp-28]
"hw.dll"+B00B6: 89 4A 04              -  mov [edx+04],ecx
"hw.dll"+B00B9: 8B 4D DC              -  mov ecx,[ebp-24]
"hw.dll"+B00BC: 89 42 08              -  mov [edx+08],eax
"hw.dll"+B00BF: 89 4A 0C              -  mov [edx+0C],ecx
"hw.dll"+B00C2: E8 59 03 00 00        -  call hw.dll+B0420
// ---------- INJECTING HERE ----------
"hw.dll"+B00C7: 8B 0D 6C C3 99 03     -  mov ecx,[hw.dll+15C36C]
// ---------- DONE INJECTING  ----------
"hw.dll"+B00CD: 83 C4 10              -  add esp,10
"hw.dll"+B00D0: 50                    -  push eax
"hw.dll"+B00D1: FF 56 28              -  call dword ptr [esi+28]
"hw.dll"+B00D4: E8 77 08 00 00        -  call hw.dll+B0950
"hw.dll"+B00D9: E9 79 02 00 00        -  jmp hw.dll+B0357
"hw.dll"+B00DE: 83 3D 9C 42 E8 03 01  -  cmp dword ptr [hw.dll+64429C],01
"hw.dll"+B00E5: 0F 85 6C 02 00 00     -  jne hw.dll+B0357
"hw.dll"+B00EB: 8B 1D A0 42 E8 03     -  mov ebx,[hw.dll+6442A0]
"hw.dll"+B00F1: BA 77 00 00 00        -  mov edx,00000077
"hw.dll"+B00F6: 8D 7D CC              -  lea edi,[ebp-34]
}
Player entity is at ["hw.dll"+007F6304] (part of a big linked list of other entities, more work to be done there), coordinates are at +88 and stored as floats.
Health is stored at ["hw.dll"+007F6304]->7C (CBasePlayer object) -> 4 -> 160
Weapons are at ["hw.dll"+007F6304]->7C, some offsets here:
  • 4B8 is button 1 weapons
  • 4BC is button 2 weapons
  • 4C0 is button 3 weapons
  • 4C4 is button 4 weapons
  • 4C8 is button 5 weapons
  • 4cc is current weapon
  • 4d0 is current weapon (clone)
  • 4d4 is previous weapon (switch with Q)
reserve ammo is as follows:
  • 4dc is shotgun ammo
  • 4e0 is pistol/machine gun ammo
  • 4e4 is grenade launcher ammo
  • early on in game so missing others here
  • 4f8 is tripmines
  • 500 is grenades
inside a weapon object ammo is stored at both +A0 and +A4, only changing +A0 actually updates things (+A4 gets overridden when you change it)
also weapon objects have a link to the next weapon at +74 (e.g. button 2 has both pistols, so we'll point to the first pistol the player has, and if they have a second one this will be pointer to at +74), +70 points back to the player object

that's all for now, currently doing some code to navigate all these masses of linked lists, base seems to be at hw.dll+7E9700 (although player is an entity and it has its own direct pointer, will find out if it's in here somewhere too)
 

h4nsbr1x

Dank Tier Donator
Full Member
Jul 24, 2020
30
2,448
0
Used @BDKPlayer's code here to put a transparent rectangle on the screen. Worth noting that the steam overlay already hooks wglSwapBuffers but we can re-hook that with out any problem (especially since they used a long jump instead of doing a detour via the nop(2) at the start of the func)

Code:
{ Game   : hl.exe
  Version:
  Date   : 2020-10-01
  Author : User

  This script does blah blah blah
}

[ENABLE]

aobscanmodule(overlay,OPENGL32.dll,E9 75 80 4A 08) // should be unique
alloc(newmem,$1000)

label(code)
label(return)

newmem:
  push ebp
  mov ebp, esp
  pushad

  call wglGetCurrentContext
  mov [gameContext], eax

  mov eax, [myContext]
  test eax, eax
  jnz gotContext

  mov eax, [ebp+8]
  push eax
  call wglCreateContext
  mov [myContext], eax

  mov eax, [myContext]
  push eax
  mov eax, [ebp+8]
  push eax
  call wglMakeCurrent

  push 0x1701 // GL_PROJECTION
  call glMatrixMode

  call glLoadIdentity

  push viewport
  push 0x0BA2 // GL_VIEWPORT
  call glGetDoublev

  sub esp, 8
  movsd xmm0, [negativeOneD]
  movsd [esp], xmm0
  sub esp, 8
  movsd xmm0, [oneD]
  movsd [esp], xmm0
  sub esp, 8
  movsd xmm0, [zeroD]
  movsd [esp], xmm0
  mov ecx, viewport
  sub esp, 8
  movsd xmm0, [viewport+18]
  movsd [esp], xmm0
  sub esp, 8
  movsd xmm0, [viewport+10]
  movsd [esp], xmm0
  sub esp, 8
  movsd xmm0, [zeroD]
  movsd [esp], xmm0
  call glOrtho

  push 0x1700 // GL_MODELVIEW
  call glMatrixMode

  call glLoadIdentity

  mov eax, (float)1.0
  push eax
  mov eax, (float)0.0
  push eax
  push eax
  push eax
  call glClearColor

  push 0xBE2 // GL_BLEND
  call glEnable

  push 0x303 // GL_ONE_MINUS_SRC_ALPHA
  push 0x302 // GL_SRC_ALPHA
  call glBlendFunc

gotContext:
  mov eax, [myContext]
  push eax
  mov eax, [ebp+8]
  push eax
  call wglMakeCurrent

  mov eax, (float)0.1
  push eax
  mov eax, (float)1.0
  push eax
  mov eax, (float)0.73
  push eax
  mov eax, (float)1.0
  push eax
  call glColor4f

  push 7 // GL_QUADS
  call glBegin

  push 0
  push 0
  call glVertex2i

  push 0
  push 200
  call glVertex2i

  push 100
  push 200
  call glVertex2i

  push 100
  push 0
  call glVertex2i

  call glEnd

cleanUp:
  mov eax, [gameContext]
  push eax
  mov eax, [ebp+8]
  push eax
  call wglMakeCurrent

  popad
  mov esp, ebp
  pop ebp
  jmp code

myContext:
  dd 0

gameContext:
  dd 0

oneD:
  dq (double)1.0

zeroD:
  dq (double)0.0

negativeOneD:
  dq (double)-1.0

viewport:
  dq 0
  dq 0
  dq 0
  dq 0

code:
  jmp 76710E0A
  jmp return

overlay:
  jmp newmem
return:
registersymbol(overlay)

[DISABLE]

overlay:
  db E9 75 80 4A 08

unregistersymbol(overlay)
dealloc(newmem)

{
// ORIGINAL CODE - INJECTION POINT: "OPENGL32.dll"+38D90

"OPENGL32.dll"+38D86: CC                 -  int 3
"OPENGL32.dll"+38D87: CC                 -  int 3
"OPENGL32.dll"+38D88: CC                 -  int 3
"OPENGL32.dll"+38D89: CC                 -  int 3
"OPENGL32.dll"+38D8A: CC                 -  int 3
"OPENGL32.dll"+38D8B: CC                 -  int 3
"OPENGL32.dll"+38D8C: CC                 -  int 3
"OPENGL32.dll"+38D8D: CC                 -  int 3
"OPENGL32.dll"+38D8E: CC                 -  int 3
"OPENGL32.dll"+38D8F: CC                 -  int 3
// ---------- INJECTING HERE ----------
"OPENGL32.dll"+38D90: E9 75 80 4A 08     -  jmp 76710E0A
// ---------- DONE INJECTING  ----------
"OPENGL32.dll"+38D95: 83 E4 F8           -  and esp,-08
"OPENGL32.dll"+38D98: 83 EC 14           -  sub esp,14
"OPENGL32.dll"+38D9B: 53                 -  push ebx
"OPENGL32.dll"+38D9C: 56                 -  push esi
"OPENGL32.dll"+38D9D: 57                 -  push edi
"OPENGL32.dll"+38D9E: 8B 7D 08           -  mov edi,[ebp+08]
"OPENGL32.dll"+38DA1: 33 DB              -  xor ebx,ebx
"OPENGL32.dll"+38DA3: 8B CF              -  mov ecx,edi
"OPENGL32.dll"+38DA5: E8 76 C1 FE FF     -  call OPENGL32.dll+24F20
"OPENGL32.dll"+38DAA: 85 C0              -  test eax,eax
}
One change I made was pulling out the GL_VIEWPORT values as doubles instead of as ints, glOrtho() takes doubles, which doesn't matter if you're in c++ but requires you to pay attention when doing the asm by hand.
 

h4nsbr1x

Dank Tier Donator
Full Member
Jul 24, 2020
30
2,448
0
Found an actual entity list that isn't just a mess of linked lists, set up as follows:

hw.dll+843D58: DWORD number of entities
hw.dll+843D60: start of entity list

each entity is 0x324 bytes long. lots of circular pointers too (e.g. +7c is the object, +7c+4 has a pointer back to entity+80 so we can skip that chain), so can simplify the pointers for the player:

player base (pointed by "hw.dll"+007F6304 but can be the second entry in the entity list):
7C: pointer to object
88: X coord
8C: Y coord
90: Z coord
A0: X velocity (calculated, not authoritative)
A4: Y velocity (calculated, not authoritative)
A8: Z velocity (calculated, not authoritative)
D0: pitch / 3? (clone, not authoritative)
D4: yaw (clone, not authoritative)
F4: pitch (clone, not authoritative)
F8: yaw (clone, not authoritative)
12C: set on headcrab
130: set on headcrab:
134: set to high numbers on player and headcrab, lower numbers on other things...
144-14c: corner of hitbox?
150-158: other corner of hitbox?
1AC: contact flags (0 when in air, 2 on ground, 3 moving, 4 when touching a wall)
1E0: health
23C: suit power
2B4: opposite of z velocity
2BC: mask of keys down, 1=left click, 2=space, 4=control, 8=forward, 10=back, 200=left, 400=right, 800=right click

couldn't find anything on the object that shows the forces so presumably this gets calculated based on what keys are down (maybe the mask at 2BC?)

this is an easy list to iterate through, monsters should have health and other things should have none, so we can just filter on things with health (also we can check 7C->0 for the vtables and match on those)

Here's some code that dumps out everything with positive health (note that lots of these have 0,0,0 coordinates...)

main.cpp:
#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>

#include <gl/gl.h>
#include "monster.h"

char appName[] = "hl.exe";

DWORD GetProcessId(const char* appName) {
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    DWORD processId = 0;

    if (hSnapshot != INVALID_HANDLE_VALUE) {
        PROCESSENTRY32 processEntry;
        processEntry.dwSize = sizeof(processEntry);

        if (Process32First(hSnapshot, &processEntry)) {
            do {
                if (!_stricmp(processEntry.szExeFile, appName)) {
                    processId = processEntry.th32ProcessID;
                    break;
                }
            } while (Process32Next(hSnapshot, &processEntry));
        }
    }

    CloseHandle(hSnapshot);
    return processId;
}

DWORD_PTR GetModuleBaseAddr(DWORD processId, const char* moduleName) {
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, processId);
    DWORD_PTR baseAddr = 0;

    if (hSnapshot && hSnapshot != INVALID_HANDLE_VALUE) {
        MODULEENTRY32 moduleEntry;
        moduleEntry.dwSize = sizeof(MODULEENTRY32);
        if (Module32First(hSnapshot, &moduleEntry)) {
            do {
                if (!_stricmp(moduleEntry.szModule, moduleName)) {
                    baseAddr = (DWORD_PTR)moduleEntry.modBaseAddr;
                    break;
                }
            } while (Module32Next(hSnapshot, &moduleEntry));
        }
        CloseHandle(hSnapshot);
    }

    return baseAddr;
}

int main(int argc, char** argv) {
    DWORD bytesRead;
    printf("started\n");

    // find window
    printf("Looking for process %s\n", appName);
    DWORD processId = GetProcessId(appName);

    if (processId > 0) {
        printf("Process ID: %d\n", processId);
    }
    else {
        printf("Couldn't find process ID, exiting\n");
        return -1;
    }

    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, processId);

    if (hProcess && hProcess != INVALID_HANDLE_VALUE) {
        printf("Successfully opened process\n");

        uintptr_t baseAddr = GetModuleBaseAddr(processId, "hw.dll");

        if (baseAddr) {
            printf("Base address of hw.dll: %X\n", baseAddr);

            // just list entity list
            DWORD entityCount = 0;
            uintptr_t pMonster = 0;
            ReadProcessMemory(hProcess, (void*)(baseAddr + 0x843D58), (void*)&entityCount, sizeof(entityCount), &bytesRead);
            ReadProcessMemory(hProcess, (void*)(baseAddr + 0x843D60), (void*)&pMonster, sizeof(pMonster), &bytesRead);

            printf("Number of entities: %d\n", entityCount);

            for (int i = 0; i < entityCount; i++) {
                MONSTER monster;
                ReadProcessMemory(hProcess, (void*)(pMonster + (i*0x324)), (void*)&monster, sizeof(MONSTER), &bytesRead);
                if (monster.health == 0)
                    continue;
                printf("Read monster %d from %08X\n", i, pMonster + (i * 0x324));
                printf("Coords: %0.2f,%0.2f,%0.2f\n", monster.x, monster.y, monster.z);
                printf("Health: %0.0f\n", monster.health);
                printf("Suit: %0.0f\n", monster.suitPower);
                printf("Markers: %0.2f %0.2f %X\n", monster.marker1, monster.marker2, monster.marker3);
            }

            return 0;
        }
        else {
            printf("Couldn't get base address for hw.dll\n");
        }
    }
    else {
        printf("Error opening process\n");
        return -1;
    }

    CloseHandle(hProcess);

    return 0;
}
monster.h:
#pragma once
#include <cstdint>

typedef struct _MONSTER {
    char _fill[0x7C];
    uintptr_t hlObject;
    char _fill2[0x08];
    float x;
    float y;
    float z;
    char _fill3[0x0C];
    float xVelocity;
    float yVelocity;
    float zVelocity;
    char _fill4[0x24];
    float pitch_3;
    float yaw2;
    char _fill5[0x1C];
    float pitch;
    float yaw;
    char _fill6[0x30];
    float marker1;
    float marker2;
    unsigned int marker3;
    char _fill7[0x0C];
    float hitbox1x;
    float hitbox1y;
    float hitbox1z;
    float hitbox2x;
    float hitbox2y;
    float hitbox2z;
    char _fill8[0x84];
    float health;
    char _fill9[0x58];
    float suitPower;
} MONSTER;
 
  • Like
Reactions: Человек

h4nsbr1x

Dank Tier Donator
Full Member
Jul 24, 2020
30
2,448
0
First pass at an aimbot:

C++:
while (TRUE) {
    int entityCount = getEntityCount(hProcess, baseAddr);
    uintptr_t pMonster = getEntityListOffset(hProcess, baseAddr);
    float bestAimVector[3] = { 0.0, 0.0, 0.0 };
    float bestDistance = NULL;
    MONSTER player;
    getPlayer(hProcess, baseAddr, &player);

    for (int i = 0; i < entityCount; i++) {
        MONSTER monster;
        float aimVector[3];
        getMonster(hProcess, pEntityBase, &monster, i);
        // alive things
        if (monster.marker188 != 4)
            continue;
        // don't shoot barney
        if (monster.marker194 == 1)
            continue;
        getVectorDiff(player.position, monster.position, aimVector);
        float distance = getVectorLength(aimVector);
        if (!bestDistance || distance < bestDistance) {
            bestAimVector[0] = aimVector[0];
            bestAimVector[1] = aimVector[1];
            bestAimVector[2] = aimVector[2];
            bestDistance = distance;
        }

    }
    if (bestDistance) {
        float radiansToDegrees = 180.0 / M_PI;
        float yaw = atan2f(bestAimVector[1], bestAimVector[0]) * radiansToDegrees;
        float pitch = -asinf(bestAimVector[2] / bestDistance) * radiansToDegrees;
        // todo clip to 89 -> -89
        WriteProcessMemory(hProcess, (void*)(baseAddr + 0x108AEC4), &pitch, sizeof(pitch), &bytesRead);
        WriteProcessMemory(hProcess, (void*)(baseAddr + 0x108AEC8), &yaw, sizeof(yaw), &bytesRead);
    }

    Sleep(20);
}
Some weird things from this
  • The markers I'm using aren't super stable
  • Stuff on a level above can often be closer than the stuff nearby that is more important to shoot at right now
  • We're not comparing from Gordon's position to the hitbox of the character so we end up missing headcrabs
  • We hit the bodies of zombies instead of the heads
  • The g-man gets marked as an enemy and the aimbot will track him as he's walking past
Apart from that it does what it says on the label
 

Lukor

ded
Meme Tier VIP
Fleep Tier Donator
Dec 13, 2013
500
6,253
25
The g-man gets marked as an enemy
As far as I remember, the GMan was indeed tagged as an enemy so that the monsters do not fokus on him early in development before better NPC logic and grouping was added.
He is invincible tho so shooting him is pointless. Ignoring him by model would be a bad idea since it is a playable character model in MP.
 

h4nsbr1x

Dank Tier Donator
Full Member
Jul 24, 2020
30
2,448
0
As far as I remember, the GMan was indeed tagged as an enemy so that the monsters do not fokus on him early in development before better NPC logic and grouping was added.
He is invincible tho so shooting him is pointless. Ignoring him by model would be a bad idea since it is a playable character model in MP.
Yeah that makes sense, I'm still a way off finding anything definitive. The models have a field which looks to be alien vs human, but then the grunts get marked as human so that isn't useful either. Slowly getting there though, having fun loading up a few different entities in struct dissect in cheat engine and looking for consistent differences. At a glance it might be enough to test if they're a subclass of CTalkMonster, that seems to cover CBarney and CScientist, and everything else that wants to kill us is a CMonster but not CTalkMonster
 

h4nsbr1x

Dank Tier Donator
Full Member
Jul 24, 2020
30
2,448
0
Very small update, I started going through the vtables and I found the monster objects all have a fairly boring function at +0x20 in the vtable that just returns a number. I put a breakpoint on this on the player object and found it got hit when aliens went to attack us, so I tried setting it to different values and it made aliens (or grunts, depending how i set it) decide to not attack the player. There's another function that looks to take two objects, get these values, and put them through a matrix that gets built at runtime, so the cleanest "peace mode" would be to modify that matrix directly... alternatively, a "chaos mode" would be to make everything hate everything except the player

As far as I remember, the GMan was indeed tagged as an enemy so that the monsters do not fokus on him early in development before better NPC logic and grouping was added.
GMan returns 0 to this call, we can return 0 ourselves, and make the GMan return 2 (as we do)...

 
Last edited:
  • Like
Reactions: Lukor and timb3r
Attention! Before you post:

Read the How to Ask Questions Guide
99% of questions are answered in the Beginner's Guide, do it before asking a question.

No Hack Requests. Post in the correct section.  Search the forum first. Read the rules.

How to make a good post:

  • Fill out the form correctly
  • Tell us the game name & coding language
  • Post everything we need to know to help you
  • Ask specific questions, be descriptive
  • Post errors, line numbers & screenshots
  • Post code snippets using code tags
  • If it's a large project, zip it up and attach it

If you do not comply, your post may be deleted.  We want to help, please make a good post and we will do our best to help you.

Community Mods