2.游戏逆向-pxxx-分析UE源码中的GName
免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!
本次游戏没法给
内容参考于:微尘网络安全
上一个内容:1.游戏逆向-pxxx-认识pxx文件结构(使用x64dbg工具脱壳(dump))
ue4.16.3的GName算法没有那么复杂,但是游戏给GName做了一层封装进行了加密,这个加密只能通过ida来分析
首先还是打开UE源码,搜索GetName(

它的位置,在下图红框,第一次找肯定不知道是下图红框,只能一个一个的点

如下图GetName的代码,这里和之前找的UE代码一样

GetName方法在下图红框当类中
![]()
class COREUOBJECT_API UObjectBaseUtility : public UObjectBase {FORCEINLINE void GetName(FString &ResultString) const{GetFName().ToString(ResultString);} }
然后开始找GName,按着CTRL鼠标左键单击下图红框

如下图,GetFName在UObjectBase类中也就是UObjectBaseUtility的父类里

然后按着CTRL点击下图红框

可以看到它是一个FName类型,这也跟之前一样,它也是在UObjectBase类里
![]()
class COREUOBJECT_API UObjectBase { private:/** Flags used to track and report various object states. This needs to be 8 byte aligned on 32-bitplatforms to reduce memory waste */EObjectFlags ObjectFlags;/** Index into GObjectArray...very private. */int32 InternalIndex;/** Class the object belongs to. */UClass* ClassPrivate;/** Name of this object */FName NamePrivate;/** Object this object resides in. */UObject* OuterPrivate; }
然后现在找到了FName,接下来看ToString,按着CTRL鼠标左键点击下图红框

然后就来到ToString函数的实现,如下图红框,之前找的CE在ToString函数中有加密的代码,这里由于版本老就没有加密,之前看加密的代码,找的了GName,这里没有该去哪里找?GetFName和ToString也都找了,也没看到GName,它到底在哪?

它的GName在FName类中,在FName的初始化(Init)函数中可以看到,按着CTRL鼠标左键单击下图红框

然后就来到了FName类里

它的Init函数里,这里按着CTRL点击第一个框里的Init

然后再进入下图红框的函数,也就是按着CTRL鼠标左键单机下图红框

然后再进入下图红框的函数

然后再进入下图红框函数

然后就能看到下图红框的代码,到这也就可以说找到了,因为之前也是通过它找到的GName地址

GName在下图红框的函数中

下图红框初始化了一个静态的Names,也就是创建了一个全局的变量,它就是GName

然后GName类型的样子GName是TNameEntryArray类型,而TNameEntryArray是TStaticIndirectArrayThreadSafeRead类型

然后TStaticIndirectArrayThreadSafeRead里的内容

然后还需要看一下,下图红框的函数

它的代码样子

然后进入下图红框函数

如下图它的内容
![]()
/*** 扩展数组的块结构,确保指定索引(Index)对应的元素所在的块已分配。* 新分配的块中所有元素指针会被初始化为零(nullptr)。* 注:此函数为数组内部维护块结构的核心逻辑,确保访问指定索引时不会因块未分配导致内存错误。* * @param Index 需确保已分配对应块的元素索引(目标索引)*/ void ExpandChunksToIndex(int32 Index) {// 调试断言:验证索引有效性(必须在 [0, MaxTotalElements) 范围内)// 若索引无效(如负数或超出最大容量),程序会在调试模式中断,提示错误check(Index >= 0 && Index < MaxTotalElements);// 计算目标索引所在的块索引(ChunkIndex)// 逻辑:每个块包含 ElementsPerChunk 个元素,因此索引除以块大小得到块索引// 例:若 ElementsPerChunk=100,Index=150,则 ChunkIndex=1(第2个块)int32 ChunkIndex = Index / ElementsPerChunk;// 循环扩展块,直到目标块索引(ChunkIndex)小于当前已分配的块数量(NumChunks)// 即:若目标块尚未分配,则持续添加新块while (1){// 若目标块已分配(ChunkIndex < 当前块数量),退出循环if (ChunkIndex < NumChunks){break;}// 1. 获取目标块在主表(Chunks)中的指针地址(二级指针的地址)// Chunks 是存储块指针的主表,Chunk 指向主表中目标块的位置(用于后续原子操作)ElementType*** Chunk = &Chunks[ChunkIndex];// 2. 分配新块:每个块包含 ElementsPerChunk 个元素指针,大小为 sizeof(ElementType*) * ElementsPerChunkElementType** NewChunk = (ElementType**)FMemory::Malloc(sizeof(ElementType*) * ElementsPerChunk);// 3. 初始化新块:将所有元素指针置为 nullptr(避免野指针)FMemory::Memzero(NewChunk, sizeof(ElementType*) * ElementsPerChunk);// 4. 原子操作:检查并设置目标块指针,确保线程安全(但不支持并发添加)// 逻辑:若目标块当前为 nullptr(未分配),则将其设置为 NewChunk;否则返回原指针// 作用:防止多线程同时分配同一个块(若已有其他线程分配,当前线程会检测到)if (FPlatformAtomics::InterlockedCompareExchangePointer((void**)Chunk, NewChunk, nullptr)){// 若进入此分支,说明其他线程已抢先分配了该块// 程序在调试模式中断,因为类设计不支持多线程并发添加块(仅允许单线程添加)check(0)}else{// 若成功分配新块,更新已分配块的数量(NumChunks 自增)NumChunks++;}}// 调试断言:确保目标块已分配且有效// 验证 ChunkIndex 在有效范围内,且对应块指针不为 nullptr(防止逻辑错误导致的未分配)check(ChunkIndex < NumChunks && Chunks[ChunkIndex]); }
但是到这也没看到它的算法,它的算法在下图红框的函数,这个函数重新定义了[]它的用法,也就是说GName[1],就会调用到下图红框的函数,它的入参就是[和]之间的内容也就是1

/*** 重载[]运算符,通过索引获取数组中指定位置的元素指针(常量引用)。* 这是该数组的核心访问接口,用于安全地读取指定索引处的元素。* * 线程安全特性:* - 支持多线程并发读取(只要索引有效,读取过程不会导致数据竞争)。* - 若索引当前有效,则永远有效(元素一旦添加不会被删除,仅可能被修改为非nullptr)。* - 可能返回nullptr(若该位置元素尚未被初始化),但其他线程可能在读取后将其更新为有效指针。* * @param Index 要访问的元素索引(需满足 IsValidIndex(Index) 为 true,否则触发调试断言)* @return 对 "ElementType指针" 的常量引用(即 ElementType const* const&),确保无法通过此引用修改指针本身*/
FORCEINLINE ElementType const* const& operator[](int32 Index) const
{// 1. 调用 GetItemPtr 获取指向 "元素指针" 的指针(即 ElementType const* const*)// 作用:根据索引解析出元素所在的块及块内位置,返回该元素指针在内存中的地址ElementType const* const* ItemPtr = GetItemPtr(Index);// 2. 调试断言:验证 ItemPtr 不为 nullptr(确保 GetItemPtr 逻辑正确,未返回无效地址)// 若触发此断言,说明索引越界或块未分配,属于程序逻辑错误check(ItemPtr);// 3. 返回 ItemPtr 指向的内容(即元素指针本身的常量引用)// 由于返回的是 const&,调用者无法修改该指针(保证指针的线程安全访问)return *ItemPtr;
}
/*** 获取指向"元素指针"的指针(即二级指针),用于定位指定索引处的元素指针在内存中的位置。* 是 `operator[]` 的核心依赖函数,负责将索引映射到实际存储的元素指针地址。* * 功能本质:将一维索引转换为"块索引+块内偏移"的二维定位,实现分块存储结构下的元素指针寻址。* * @param Index 要定位的元素索引(需满足 IsValidIndex(Index) 为 true,否则触发调试断言)* @return 指向"元素指针"的常量二级指针(ElementType const* const*),即:* - 外层指针指向块内的元素指针数组;* - 内层指针指向实际的元素(ElementType*);* - 整体为 const 修饰,确保无法通过此指针修改块结构或元素指针本身。*/
FORCEINLINE_DEBUGGABLE ElementType const* const* GetItemPtr(int32 Index) const
{// 1. 计算索引所在的块索引(ChunkIndex)// 逻辑:每个块包含 ElementsPerChunk 个元素指针,因此索引除以块大小得到块索引// 例:若 ElementsPerChunk=100,Index=150,则 ChunkIndex=1(第2个块)int32 ChunkIndex = Index / ElementsPerChunk;// 2. 计算索引在块内的偏移(WithinChunkIndex)// 逻辑:索引对块大小取余,得到在当前块内的位置(0 ~ ElementsPerChunk-1)// 例:Index=150,ElementsPerChunk=100,则 WithinChunkIndex=50(块内第50个元素指针)int32 WithinChunkIndex = Index % ElementsPerChunk;// 3. 调试断言:验证索引有效性// - IsValidIndex(Index):确保索引在 [0, NumElements) 范围内(已分配元素)// - ChunkIndex < NumChunks:确保块索引在已分配的块数量内(块已存在)// - Index < MaxTotalElements:确保索引不超过数组最大容量(防止越界)// 若任一条件不满足,调试模式下程序中断,提示逻辑错误check(IsValidIndex(Index) && ChunkIndex < NumChunks && Index < MaxTotalElements);// 4. 获取当前块的指针(Chunk)// Chunks 是存储块指针的主表,ChunkIndex 对应块在主表中的位置ElementType** Chunk = Chunks[ChunkIndex];// 5. 调试断言:验证块指针不为 nullptr(确保块已分配,避免访问空指针)check(Chunk);// 6. 返回块内指定偏移处的元素指针地址(即指向"元素指针"的二级指针)// 计算:Chunk(块的起始地址) + WithinChunkIndex(块内偏移),得到该元素指针在块中的位置return Chunk + WithinChunkIndex;
}
到这就是UE4.16.3的GName,下一节去找游戏中GName的偏移,这个过程中会看到游戏对GName的加密,游戏中的加密我们也要搞明白,然后自己对它实现一下

