Finding the local player offset in source games

Index | Up


Table of Contents

1. Introduction

This short article shows how to get the offset for the local player in Source 1 games. This has been tested in the GNU/Linux version of Counter-Strike: Source, but it should be work in most Source 1 games.

2. Finding the offset

The offset is located inside the client.so shared object, located in <GAME-DIR>/cstrike/bin/. In this case I will use IDA Pro to analyze it, but you should be able to follow these steps with most reverse engineering tools.

Open the “Strings” sub-view in IDA with Shift+F12, and search for the string localplayer_changeteam with Ctrl+F. Double click the result, press X and jump through its x-refs until you see something like this.

cs-source-localplayer1.png

The left split shows the disassembly in text mode, and the right split shows the decompiler, which you can open with F5.

This code corresponds to the CAchievementMgr::FireGameEvent function, as you can see in the leaked CS:GO source code.

The call to C_BasePlayer::GetLocalPlayer is sometimes optimized to a simple mov instruction. If that’s the case, you can use that offset directly. Otherwise, double-click on the function (sub_5723E0) and you will see the offset there.

cs-source-localplayer2.png

That 0xBD0750 is your offset. Make sure you subtract the ImageBase returned by get_imagebase(), in my case it was zero.

cs-source-localplayer3.png

3. Reading from an external process

I will be doing this from GNU/Linux, because it’s what I used for this tutorial.

First, you will need to get the real virtual address used by the hl2_linux process. You can use my libsigscan_get_module_bounds function from my libsigscan repository, which parses the /proc/PID/maps file. Once you get the base address for client.so, you can add the offset we found in the previous step.

To read from that address, since we are in an external process, you will need to use a function like process_vm_readv.

#include <stdlib.h>  /* exit() */
#include <sys/uio.h> /* process_vm_readv() */
#include <errno.h>   /* errno */

void readProcessMemory(pid_t pid, void* addr, void* out, size_t sz) {
    /* The function expects an array, even though our array has one element */
    struct iovec local[1];
    struct iovec remote[1];

    local[0].iov_base  = out;
    local[0].iov_len   = sz;
    remote[0].iov_base = addr;
    remote[0].iov_len  = sz;

    if (process_vm_readv(pid, local, 1, remote, 1, 0) == -1) {
        fprintf(stderr, "Error reading memory of process with ID %d. Errno: %d",
                pid, errno);
        exit(1);
    }
}

You can then call it with the following.

void* localPlayer_ptr = BASE + OFFSET;
uintptr_t localPlayer;
readProcessMemory(PID, localPlayer_ptr, &out, sizeof(uintptr_t));

To get the PID of the game, you can use my libsigscan_pidof function.

#include <stdio.h>  /* fopen(), FILE* */
#include <stdlib.h> /* atoi() */
#include <string.h> /* strstr() */
#include <dirent.h> /* readdir() */

int pidof(const char* process_name) {
    static char filename[50];
    static char cmdline[256];

    DIR* dir = opendir("/proc");
    if (dir == NULL)
        return -1;

    struct dirent* de;
    while ((de = readdir(dir)) != NULL) {
        /* The name of each folder inside /proc/ is a PID */
        int pid = atoi(de->d_name);
        if (pid <= 0)
            continue;

        /* See proc_cmdline(5). You can also try:
         *   cat /proc/self/maps | xxd   */
        sprintf(filename, "/proc/%d/cmdline", pid);

        FILE* fd = fopen(filename, "r");
        if (fd == NULL)
            continue;

        char* fgets_ret = fgets(cmdline, sizeof(cmdline), fd);
        fclose(fd);

        if (fgets_ret == NULL)
            continue;

        /* We found the PID */
        if (strstr(cmdline, process_name)) {
            closedir(dir);
            return pid;
        }
    }

    /* We checked all /proc/.../cmdline's and we didn't find the process */
    closedir(dir);
    return -1;
}

The complete and updated functions can be found in my cs-source-external repository.

4. Credits