UE5 逆向笔记
2110 字
11 分钟
UE5 逆向笔记
UE5 逆向笔记
说明:本笔记由原始记录整理而来,尽量保留原有结论、偏移、定位关键词和代码片段。
偏移会随游戏更新失效,实际使用时优先重新定位并验证。
目录
SCUM 当前偏移
GName : 0x75BFA40GObject : 0x75FBF80GWorld : 0x7741DE0Matrix : 0x7274AD0补充记录:
LineTraceSingle:先在 VS Code 中查看 SDK 偏移,再去 IDA 定位函数地址。find_obj:通过项目中提供的字符串,再去 IDA 定位。- 游戏更新后很多偏移都会失效,优先尝试重新定位或修改偏移。
快速定位关键词
FName : IntPropertyGObject : Max UObject count is invalid. It mustGWorld : All Windows ClosedMatrix : CanvasObject全局对象定位
GName / FNamePool
目标函数:
static FNamePool& GetNamePool()主要思路:
- 找到
FNamePool。 - 通过返回的
FNamePool定位GName。 FNamePool内部存在FNameEntryAllocator Entries;。FNameEntryAllocator底部存在名称块指针数组,是解析名称的关键。
相关截图:



GObject / GUObjectArray
相关位置:
void UObjectBaseInit()TUObjectArray ObjObjects;主要思路:
- 先找到
InitUObject(),这个函数会初始化全局GObject。 - 通过 IDA 找到的地址还需要
+8。 - 重点结构为
TUObjectArray/FUObjectItem。
// GObject 关键数据结构FUObjectItem** Objects;FUObjectItem* PreAllocatedObjects;相关截图:



GWorld
目标函数:
void UGameEngine::Tick(float DeltaSeconds, bool bIdleMode)关键结构:
TArray<TObjectPtr<AActor>> Actors;主要链路:
UWorld -> ULevel(0x30) -> TArray<AActor*> Actors(0x98)相关截图:




Matrix / ViewProjectionMatrix
目标函数:
void UDisplayClusterViewportClient::Draw(FViewport* InViewport, FCanvas* SceneCanvas)static UCanvas* GetCanvasByName(FName CanvasName)定位思路:
- 找到关键函数
GetCanvasByName。 - 找到
UCanvas对象。 - 在
UCanvas中定位ViewProjectionMatrix。 - 程序会调用两次
GetCanvasByName,第二次调用才是目标。 - 在 CE 中查看第二个指针,通过晃动视角观察变动值。
- 当前记录中矩阵偏移为
0x280,内存中按单浮点查看。
UCanvas 相关成员:
public: FCanvas* Canvas; FSceneView* SceneView; FMatrix ViewProjectionMatrix; FQuat HmdOrientation;相关截图:




UE5 核心数据结构
TArray
template<typename T>struct TArray { T data; int number; int maxnumber;};FName
通过 AActor + 0x18 可以看到 Name 占用 0x8 字节。
struct FName { int index; int number;};UE5 源码中的相关结构:
private: FNameEntryId ComparisonIndex; // 定位全局名称表,4 字节
#if !UE_FNAME_OUTLINE_NUMBER uint32 Number; // 区分相同名称,4 字节#endif
#if WITH_CASE_PRESERVING_NAME FNameEntryId DisplayIndex; // 通常不参与编译#endif字段含义:
ComparisonIndex:用于定位全局名称表。Number:用于区分名称表中相同名称的不同实例。DisplayIndex:通常不参与编译。
UObject
在虚幻引擎中,所有对象都会继承 UObject。
UObjectBase 中存在 FName 字段,用来保存对象名称。
UObjectBase() : ClassPrivate(NoInit), NamePrivate(NoInit), OuterPrivate(NoInit){}关键成员:
EObjectFlags ObjectFlags; // 4 bytesint32 InternalIndex; // 4 bytes
ObjectPtr_Private::TNonAccessTrackedObjectPtr<UClass> ClassPrivate; // 8 bytesFName NamePrivate; // 8 bytes记录:
NamePrivate在对象内的偏移通常为0x18。- 通过 SDK Dump 观察,游戏中的
UObject大小约为0x28。 - CE 中对象开头的指针类型通常是虚函数表指针。
相关截图:

AActor 与角色继承
僵尸类继承关系:
ABP_Zombie2_C -> AZomBie2 -> AConZcharacter -> ACharacter -> APawn -> AActor -> UObject类职责记录:
AActor:实现移动行为,提供灵活性,可用于投掷物、移动平台、门、物理对象等。APawn:最简单的可控制移动对象,可用于载具、飞行器等。ACharacter:拥有复杂移动逻辑,例如走、跳、奔跑、攀爬,常用于玩家和 NPC 角色。
常见组件:
CharacterMovementComponent 处理所有移动相关逻辑CapsuleComponent 碰撞检测、物理模拟SkeletalMeshComponent 骨骼网格相关组件SkinnedMeshComponent 包含骨骼具体坐标数组AActor 中的重要成员:
struct USceneComponent* RootComponent; // 0x130(0x08)人物坐标信息通常从 RootComponent 中继续查找。
相关截图:

名称解析:FName 与全局名称表
全局名称表相关结构:
// FNameEntryAllocator 全局名称表内存结构// mutable FRWLock Lock;// uint32 CurrentBlock = 0;// uint32 CurrentByteCursor = 0;// uint8* Blocks[FNameMaxBlocks] = {};源码中的解析函数:
FNameEntry& Resolve(FNameEntryHandle Handle) const{ // Lock not needed return *reinterpret_cast<FNameEntry*>(Blocks[Handle.Block] + Stride * Handle.Offset);}解析步骤:
- 自定义
FName:
struct FName { int index; int number;};- 从源码确认
Stride、Handle.Block、Handle.Offset。 - 通过
GName获取Blocks。 - 通过对象内的
NamePrivate获取FName值。 - 根据
Handle.Block定位名称块。 - 计算
Blocks[Handle.Block] + Stride * Handle.Offset。 - 取出
FNameEntry后读取AnsiName。
示例伪代码:
auto Stride = alignof(FNameEntry);auto EntryAddress = Blocks[Handle.Block] + Stride * Handle.Offset;FNameEntry* NameEntry = reinterpret_cast<FNameEntry*>(EntryAddress);
return std::string(NameEntry->AnsiName, NameEntry->Header.Len);GWorld + 0x18 示例:

调试示例:
char tst[] = { 60, 2, 77, 97, 105, 110, 77, 101, 110, 117, 82, 4, 79, 110, 67, 111, 110};
auto Stride = alignof(FNameEntry);printf("Stride [+]: %x\n", Stride);
FNameEntryHandle Handle = { 0x000A8726 }; // GWorld NamePrivate 的值auto Stridel = Stride * Handle.Offset;
printf("Stride [+]: %x\n", Stridel);printf("Handle.Block [+]: %x\nHandle.Offset [+]: %x\n", Handle.Block, Handle.Offset);
auto a = 0x23c;FNameEntryHeader header = { 0x23c };memcpy(&header, &a, 2);
FNameEntry* Fname_entry = (FNameEntry*)tst;printf("%s\n", Fname_entry->AnsiName);std::cout.write(Fname_entry->AnsiName + 2, 6);骨骼坐标
相关截图:

分析记录:
- 第一个函数与
FName结构有关。 - 通过
if判断是否获取骨骼成功。 - 之后通过函数获取结构体中的骨骼数组。
- 最后使用
GetOrigin()取得坐标。
重要函数:
GetBoneMatrix(BoneIndex);WorldTM.GetOrigin();实现要点:
ToMatrixWithScale可以参考 UE4 源码。XMLoadFloat4x4A可以在 UE4 源码中搜索。XMMatrixMultiply同上。
骨骼坐标读取示例:
FVector3D Mesh::GetBoneArray(int Index){ // GetComponentSpaceTransforms()[ParentBoneIndex].ToMatrixWithScale() int CurrentReadComponentTransforms = Memory::Read<int>(ptr + Offset::CurrentReadComponentTransforms);
Tarary<ULONG64> BoneArray{}; if (CurrentReadComponentTransforms) { BoneArray = Memory::Read<Tarary<ULONG64>>(ptr + Offset::BonArray2); } else { BoneArray = Memory::Read<Tarary<ULONG64>>(ptr + Offset::BonArray1); }
FTransform bonetf = Memory::Read<FTransform>(BoneArray.data + (Index * sizeof(FTransform)));
// GetComponentTransform() FTransform ComponentTf = this->GetComponentTransform();
FMatrix BoneMatrix = FMatrix::ToMatrixWithScale(bonetf); FMatrix ComponentMatrix = FMatrix::ToMatrixWithScale(ComponentTf);
FMatrix result = FMatrix::VectorMatrixMultiply(&BoneMatrix, &ComponentMatrix); return FVector3D(FMatrix::GetOrigin(result));}骨骼索引:
int BoneIndexArray[] = { 98, 3, 2, 9, 10, 97, // 上半身 27, 22, 21, 20, 42, 43, 44, 45, // 下半身 1, 16, 17, 20 // 脊椎};绘制骨骼线示例:
void BrawBoneArray(AActor* obj, int start, int end){ FVector4D point2{};
for (int i{ start }; i < end; i++) { FVector4D point1{}; FVector3D pos3 = obj->GetMesh().GetBoneArray(BoneIndexArray[i]);
if (Engine::WorldToScrren(pos3, point1, false)) { if (i != start) { if (point2.x == 0.0f || point2.y == 0.0f || point1.x == 0.0f || point1.y == 0.0f) { continue; }
ImGui::GetForegroundDrawList()->AddLine( { point2.x, point2.y }, { point1.x, point1.y }, IM_COL32(255, 0, 0, 255) ); } }
point2 = point1; }}可视判断:LineTraceSingle
骨骼遮挡 / 可视判断函数:
LineTraceSingle函数原型:
bool LineTraceSingle( struct UObject* WorldContextObject, struct FVector Start, struct FVector End, enum class ETraceTypeQuery TraceChannel, bool bTraceComplex, struct TArray<struct AActor*>& ActorsToIgnore, enum class EDrawDebugTrace DrawDebugType, struct FHitResult& OutHit, bool bIgnoreSelf, struct FLinearColor TraceColor, struct FLinearColor TraceHitColor, float DrawTime);SDK 记录:
Function Engine.KismetSystemLibrary.LineTraceSingle(Final|Native|Static|Public|HasOutParms|HasDefaults|BlueprintCallable)@ game+0x46ea700自瞄角度计算
基础角度计算:
FVector2D Aim_xy{};
FMinimalViewInfo info = Engine::GetplayerCamera(); // 获取 CameraCachePrivateFVector3D diff = pos - info.Location; // 目标位置 - 摄像机位置
Aim_xy.x = atan2(diff.z, sqrt(diff.x * diff.x + diff.y * diff.y)) * 180 / 3.1415926;Aim_xy.y = atan2(diff.y, diff.x) * 180 / 3.1415926;调试记录:
- 问题点:
FVector3D pos = AimPlayer.GetMesh().GetBoneArray(86); - 现象:
pos锁定的是玩家当前坐标,而不是僵尸坐标。 - 后续手动修改僵尸值后,成功锁头。
- 再次调试发现问题出在
AutoAim::SetAimPlayer(actor);。 - 原因:没有把代码写入过滤逻辑,大量未过滤的
actor被传入AutoAim,最先传入的是玩家本身。 - 使用以下输出确认:
printf("%s\n", Engine::GetName(AimPlayer.GetObjFName()).c_str());输出结果:
BP_Prisoner_C结论:需要先完成目标过滤,避免玩家自身被设置为目标。
Hook 与远程线程
基本流程:
- 在目标进程中申请一段内存。
- 清空申请的内存。
- 构造 shellcode。
- 执行前保存寄存器,执行后还原寄存器。
- 使用写入函数把参数和 shellcode 写入目标内存。
- 通过远程线程执行。
- 等待线程结束并关闭句柄。
- 使用
VirtualFreeEx释放申请的远程内存。
示例:
ULONG64 adr = Memory::alloc_mem(0x1000);Memory::mem_zeor(adr, 0x1000);
// 构造 shellcode 后,以 adr 为基地址写入参数和代码。// 写入时需要注意系统函数调用约定。
HANDLE handle = create_remote_thread(adr);WaitForSingleObject(handle, INFINITE);CloseHandle(handle);
BOOL freed = VirtualFreeEx(Memory::GetPid(), (LPVOID)adr, 0, MEM_RELEASE);if (!freed) { DWORD err = GetLastError(); printf("VirtualFreeEx failed: %u\n", err);}待整理 / 待补充
ImGui章节目前只有标题,后续可补充菜单、绘制、字体、热键、窗口样式等内容。LineTraceSingle可以补充实际调用封装、参数枚举与可视判断返回逻辑。GName可以补充完整的FNameEntryHeader、宽字符 / 窄字符判断与读取逻辑。GObject可以补充对象遍历、对象数量读取、无效对象过滤。GWorld可以补充GameInstance、LocalPlayer、PlayerController、Pawn、CameraManager链路。- 骨骼章节可以补充常用骨骼 ID 表,并按头部、躯干、手臂、腿部分组。
- 偏移建议按游戏版本或更新时间建立表格,避免新旧记录混在一起。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
相关文章 智能推荐
1
vue-office 学习记录
文章示例 使用vue-offce导入pdf文件在线查看
2
从零开始搭建一个最简单的NestJS应用
文章示例 记录学习NestJS过程
3
分享一个自己Zsh的主题配置
文章示例 自定义主题。
4
五月二十六日
文章示例 朋友的作业
5
实验4
文章示例 2026-01-28
随机文章 随机推荐