3
Unreal Engine offsets dealing
k4sh44 edited this page 2025-09-14 09:53:27 +00:00

Finding offsets opcode access signature

As soon as we have generated a SDK with Dumper-7, we have to open the Basic.hpp file generated in order to get the offsets.
They look like this :

/*
* Disclaimer:
*	- The 'GNames' is only a fallback and null by default, FName::AppendString is used
*	- THe 'GWorld' offset is not used by the SDK, it's just there for "decoration", use the provided 'UWorld::GetWorld()' function instead
*/
namespace Offsets

{
	constexpr int32 GObjects          = 0x092F8D70;
	constexpr int32 AppendString      = 0x01273B00;
	constexpr int32 GNames            = 0x092155C0;
	constexpr int32 GWorld            = 0x090C7008;
	constexpr int32 ProcessEvent      = 0x0149DC80;
	constexpr int32 ProcessEventIdx   = 0x0000004F;
}

These offsets will vary with game' updates and thus can't be used as static in our code because SDK functions call will make the game to crash sooner or later.

So the workaround here is to search for opcode that access them (load the address pointed by the offset).
Typical opcode address loading by offset looks like these :
lea rax,["HellIsUs-Win64-Shipping.exe"+92155C0]
mov rax, ["HellIsUs-Win64-Shipping.exe"+92155C0]
mov eax, ["HellIsUs-Win64-Shipping.exe"+92155C0]

We will need a Cheat Engine lua script in order to find an opcode accessing the offset.
local MODNAME = "HellIsUs-Win64-Shipping.exe" has to be modified with the game executable name
local OFFSET_HEX has to be modified with the Unreal Engine offset we look for found in Basic.hpp
As soon as the script has pointed an address with the opcode accessing the offset, we can generate a unique AOB signature.
For code to be used see below :

-- Lua script for Cheat Engine
-- Scanne plusieurs patterns RIP-relative et ajoute "GNames_ptr" si le match correspond

local MODNAME = "HellIsUs-Win64-Shipping.exe"
local OFFSET_HEX = 0x92155C0
local SIG_SCAN_FLAGS = "+X-C-W"    -- sections exécutables uniquement
local SIGNATURES = {
  {pattern="48 8D 05 ?? ?? ?? ??", dispOffset=3, instrLen=7}, -- lea rax,[rip+disp32]
  {pattern="48 8B 05 ?? ?? ?? ??", dispOffset=3, instrLen=7}, -- mov rax,[rip+disp32]
  {pattern="8B 05 ?? ?? ?? ??",    dispOffset=2, instrLen=6}, -- mov eax,[rip+disp32]
}

-- Helper: lit un int32 little-endian safe
local function safeReadInt32(addr)
  local ok, b = pcall(readBytes, addr, 4, true)
  if not ok or not b or #b < 4 then return nil end
  local val = b[1] | (b[2] << 8) | (b[3] << 16) | (b[4] << 24)
  if val >= 0x80000000 then val = val - 0x100000000 end
  return val
end

-- Helper: trouve module
local function findModule(name)
  local mods = enumModules()
  if not mods then return nil end
  for i, m in ipairs(mods) do
    if m.Name == name then
      return {base = m.Address, size = m.Size}
    end
  end
  return nil
end

-- Récup module
local mod = findModule(MODNAME)
if not mod then
  print("Module non trouvé : " .. MODNAME)
  return
end

local moduleBase = mod.base
local targetAddr = moduleBase + OFFSET_HEX
print(string.format("Module trouvé: %s base=0x%X size=0x%X", MODNAME, moduleBase, mod.size))
print(string.format("Recherche offset cible = 0x%X", targetAddr))

local found = false

for _, sig in ipairs(SIGNATURES) do
  local matches = AOBScan(sig.pattern, SIG_SCAN_FLAGS)
  if matches then
    for i=0, matches.Count-1 do
      local addr = tonumber(matches[i],16)
      if addr then
        local disp32 = safeReadInt32(addr + sig.dispOffset)
        if disp32 then
          local resolved = (addr + sig.instrLen) + disp32
          if resolved == targetAddr then
            found = true
            print(string.format("Match trouvé à 0x%X -> résolu = 0x%X", addr, resolved))

            -- Ajoute à la table
            local al = getAddressList()
            local mr = al.createMemoryRecord()
            mr.Description = "Offset pointer"
            mr.Address = string.format("%X", resolved)
            mr.Type = vtQword
            mr.Active = true

            -- Enregistre symbole pour Auto Assembler
            pcall(function() registerSymbol("Offset pointer", resolved, true) end)
            break
          end
        end
      end
    end
    matches.destroy()
    if found then break end
  end
end

if not found then
  print("Aucun match valide trouvé pour Offset pointer.")
else
  print("Adresse ajoutée sous le nom 'Offset pointer'.")
end

Modifiying SDK

The SDK doesn't allow to modify the offsets natively. So we got to modify the code a bit in order to achieve it.
Open the Basic.hpp and modify at the very beginning the Offsets namespace to look like this.

namespace Offsets
{
	inline int32 GObjects          = 0x092F8D70;
	inline int32 AppendString      = 0x01273B00;
	inline int32 GNames            = 0x092155C0;
	inline int32 GWorld            = 0x090C7008;
	inline int32 ProcessEvent      = 0x0149DC80;
	inline int32 ProcessEventIdx   = 0x0000004F;
}