-
-
UnrealEngine POLYGON 全逆向笔记
-
发表于: 2024-7-19 15:10 7747
-
[!WARNING]
本文仅用于交流学习请勿用于不法用途,如有侵权联系Euarno@outlook.com或Euarno@qq.com要求删除
本文由Euarno原创,转载请标明出处
1.在Steam下载并安装POLYGON
2.找到游戏目录POLYGON\POLYGON\Binaries\Win64
3.找到游戏文件POLYGON-Win64-Shipping.exe
4.拖进IDA等待漫长分析过程
1.Visual Studio 2022
2.C++20
1.Cheat Engine
2.IDA Pro
3.Inject Tool
[!TIP]
游戏有EasyAntiCheat保护
[!NOTE]
在同一台设备上,Dx虚表位置**“固定”**
[!IMPORTANT]
有关GName的寻找原理请参见前文,本文不做赘述
先通过字符串熟练地找到void __fastcall FNamePool_FNamePool(__int64 a1)
分析交叉引用,如下表:
[!NOTE]
Down
表示当前函数调用了 FNamePool_FNamePool
函数。
Up
表示 FNamePool_FNamePool
函数被当前函数调用。
依次看看几个Up调用内容,不要找太长的函数,也尽量避开明显提到其他组件的函数,要时时刻刻切记我们寻找的只是简单的通过
这种方式进行的构造函数调用,在为数不多的Up调用中寻找到以下符合要求的伪函数
GName偏移通过计算 0x1480AD880-0x140000000=0x80AD880 得到,偏移为 0x80AD880
我们开CheatEngine简单检验一下,也确实是这个结果,我们有充分的理由认为,GName地址是 “POLYGON-Win64-Shipping.exe”+0x80AD880
通过以下代码验证GName正确性:
得到输出:
在UnrealEngine.cpp源代码中寻找如下函数
它以UWorld*作为返回值,在函数中有大量明文字符串可用来作为特征寻找该函数
[!NOTE]
如果你发现你在IDA中无法搜索到这些字符串,请设置一下识别的字符串风格,把unicode加进去
锁定return qword_1482ACFD0
,直接计算0x1482ACFD0-0x0x140000000=0x82ACFD0,偏移为0x82ACFD0
还是先看引擎源码,在UObjectHash.cpp,有GObject的定义
分析对GUObjectArray的引用,有很多含有字符串的函数可以作为寻找UObject的跳板,我选择了这个函数
在IDA中定位到源码后,分析这部分
在MaxObjectsNotConsideredByGC = InMaxObjectsNotConsideredByGC时,类成员变量被赋值,对应伪代码
定位类索引首地址,就是类全局变量的地址,用0x148153F28-0x8-0x140000000=0x8153F20
ID | Description | ShowAsSigned | VariableType | Address |
---|---|---|---|---|
0 | 无描述 | 0 | 4 Bytes | d3d11.dll |
1 | 无描述 | 0 | 4 Bytes | d3d12.dll |
2 | 无描述 | 0 | 4 Bytes | d3d9.dll |
ID | Description | ShowAsSigned | ShowAsHex | VariableType | Address |
---|---|---|---|---|---|
0 | SwapChain | 1 | 8 Bytes | 1C8BD143B20 |
ID | Description | LastState Value | LastState RealAddress | VariableType | Address | Offset(倒序) |
---|---|---|---|---|---|---|
0 | 指针扫描结果 | 618754048 | 1C8BD143B20 | 4 Bytes | "POLYGON-Win64-Shipping.exe"+08157630 | 0, 70, 90, 68, 10 |
1 | 指针扫描结果 | 618754048 | 1C8BD143B20 | 4 Bytes | "POLYGON-Win64-Shipping.exe"+08244B40 | 0, 70, 80, 528, 10 |
3 | 指针扫描结果 | 618754048 | 1C8BD143B20 | 4 Bytes | "POLYGON-Win64-Shipping.exe"+0827D5B8 | 0, 70, 20, 778, 110 |
4 | 指针扫描结果 | 618754048 | 1C8BD143B20 | 4 Bytes | "POLYGON-Win64-Shipping.exe"+0827D5C0 | 0, 70, 20, 778, 110 |
5 | 指针扫描结果 | 618754048 | 1C8BD143B20 | 4 Bytes | "POLYGON-Win64-Shipping.exe"+08296C30 | 0, 70, 20, 748, 110 |
6 | 指针扫描结果 | 618754048 | 1C8BD143B20 | 4 Bytes | "POLYGON-Win64-Shipping.exe"+08296C38 | 0, 70, 20, 748, 110 |
7 | 指针扫描结果 | 618754048 | 1C8BD143B20 | 4 Bytes | "POLYGON-Win64-Shipping.exe"+082C7F60 | 0, 130, 10, 390, 110 |
8 | 指针扫描结果 | 618754048 | 1C8BD143B20 | 4 Bytes | "POLYGON-Win64-Shipping.exe"+082C7F60 | 0, 130, 10, 398, 110 |
9 | 指针扫描结果 | 618754048 | 1C8BD143B20 | 4 Bytes | "POLYGON-Win64-Shipping.exe"+082C7F68 | 0, 130, 10, 390, 110 |
10 | 指针扫描结果 | 618754048 | 1C8BD143B20 | 4 Bytes | "POLYGON-Win64-Shipping.exe"+082C7F68 | 0, 130, 10, 398, 110 |
11 | 指针扫描结果 | 618754048 | 1C8BD143B20 | 4 Bytes | "POLYGON-Win64-Shipping.exe"+0817B270 | 0, 10, 0, 110, 160 |
12 | 指针扫描结果 | 618754048 | 1C8BD143B20 | 4 Bytes | "POLYGON-Win64-Shipping.exe"+0817B270 | 0, 10, 8, 110, 160 |
13 | 指针扫描结果 | 618754048 | 1C8BD143B20 | 4 Bytes | "POLYGON-Win64-Shipping.exe"+082312C8 | 0, 10, 0, 110, 160 |
14 | 指针扫描结果 | 618754048 | 1C8BD143B20 | 4 Bytes | "POLYGON-Win64-Shipping.exe"+082312C8 | 0, 10, 8, 110, 160 |
15 | 指针扫描结果 | 618754048 | 1C8BD143B20 | 4 Bytes | "opencv_world455.dll"+02723310 | 0, 10, 0, 110, 3E8 |
16 | 指针扫描结果 | 618754048 | 1C8BD143B20 | 4 Bytes | "opencv_world455.dll"+02723310 | 0, 10, 8, 110, 3E8 |
ID | Description | LastState Value | LastState RealAddress | VariableType | Address | Offset(倒序) |
---|---|---|---|---|---|---|
0 | 指针扫描结果 | 618754048 | 1ED4E835FB0 | 4 Bytes | "POLYGON-Win64-Shipping.exe"+0817B270 | 0, 10, 0, 110, 160 |
1 | 指针扫描结果 | 618754048 | 1ED4E835FB0 | 4 Bytes | "POLYGON-Win64-Shipping.exe"+082312C8 | 0, 10, 0, 110, 160 |
ID | Description | VariableType | Address | Offset(倒序) |
---|---|---|---|---|
0 | 指针扫描结果 | 4 Bytes | "POLYGON-Win64-Shipping.exe"+0817B270 | 0, 10, 0, 110, 160 |
Direction | Type | Address | Text |
---|---|---|---|
Down | p | sub_142B09630+47 | call FNamePool_FNamePool |
Down | p | sub_142B09740+48 | call FNamePool_FNamePool |
Down | p | sub_142B0A890+30 | call FNamePool_FNamePool |
Down | p | sub_142B0A950+30 | call FNamePool_FNamePool |
Down | p | sub_142B0AA10+30 | call FNamePool_FNamePool |
Down | p | sub_142B0B100+4E | call FNamePool_FNamePool |
Down | p | sub_142B0BCF0+4E | call FNamePool_FNamePool |
Down | p | sub_142B0C260+30 | call FNamePool_FNamePool |
Down | p | sub_142B0CCB0+95 | call FNamePool_FNamePool |
Down | p | sub_142B0CE90+1F | call FNamePool_FNamePool |
Down | p | sub_142B0D0B0+21 | call FNamePool_FNamePool |
Down | p | sub_142B0D110+21 | call FNamePool_FNamePool |
Down | p | sub_142B0D540+28 | call FNamePool_FNamePool |
Down | p | sub_142B0D5D0+28 | call FNamePool_FNamePool |
Down | p | sub_142B0D670+28 | call FNamePool_FNamePool |
Down | p | sub_142B0D700+28 | call FNamePool_FNamePool |
Down | p | sub_142B0DA90+28 | call FNamePool_FNamePool |
Down | p | sub_142B10080+72 | call FNamePool_FNamePool |
Down | p | sub_142B159A0+25B | call FNamePool_FNamePool |
Down | p | sub_142B16D10+3A | call FNamePool_FNamePool |
Down | p | sub_142B16D10+D1 | call FNamePool_FNamePool |
Down | p | sub_142B16EA0+3F | call FNamePool_FNamePool |
Down | p | sub_142B16F90+30 | call FNamePool_FNamePool |
Down | o | .pdata:0000000148513348 | RUNTIME_FUNCTION <rva FNamePool_FNamePool, rva algn_142B092C5, |
Up | p | sub_1407D8E60+154 | call FNamePool_FNamePool |
Up | p | sub_1407D90C0+154 | call FNamePool_FNamePool |
Up | p | sub_142B04A20+A3 | call FNamePool_FNamePool |
Up | p | sub_142B04B20+C3 | call FNamePool_FNamePool |
Up | p | sub_142B04D00+FE | call FNamePool_FNamePool |
Up | p | sub_142B04D00+25B | call FNamePool_FNamePool |
Up | p | sub_142B05050+A5 | call FNamePool_FNamePool |
static
bool
bNamePoolInitialized;
alignas(FNamePool)
static
uint8 NamePoolData[
sizeof
(FNamePool)];
static
bool
bNamePoolInitialized;
alignas(FNamePool)
static
uint8 NamePoolData[
sizeof
(FNamePool)];
_DWORD *__fastcall sub_142B04A20(_DWORD *a1, _BYTE *a2)
{
bool
v2;
// zf
_BYTE *v3;
// r8
__int64
v5;
// rax
int
v6;
// eax
RTL_SRWLOCK *v7;
// rax
_DWORD *result;
// rax
const
char
*v9;
// [rsp+20h] [rbp-18h] BYREF
int
v10;
// [rsp+28h] [rbp-10h]
char
v11;
// [rsp+2Ch] [rbp-Ch]
int
v12;
// [rsp+40h] [rbp+8h] BYREF
int
v13;
// [rsp+44h] [rbp+Ch]
v2 = *a2 == 0;
v3 = a2 + 2;
v9 = a2 + 2;
v5 = -1i64;
if
( v2 )
{
do
++v5;
while
( v3[v5] );
v11 = 0;
}
else
{
do
++v5;
while
( *(_WORD *)&v3[2 * v5] );
v11 = 1;
}
v10 = v5;
if
( (unsigned
int
)v5 < 0x400 )
{
if
( byte_148089CF9 )
{
v7 = &stru_1480AD880;
}
else
{
FNamePool_FNamePool((
__int64
)&stru_1480AD880);
byte_148089CF9 = 1;
}
sub_142B16A60(v7, &v12, &v9);
v13 = v12;
v6 = v12;
}
else
{
v10 = 24;
v9 =
"ERROR_NAME_SIZE_EXCEEDED"
;
v11 = 0;
v6 = sub_142B0CCB0(&v9, 1i64);
}
*a1 = v6;
result = a1;
a1[1] = 0;
return
result;
}
_DWORD *__fastcall sub_142B04A20(_DWORD *a1, _BYTE *a2)
{
bool
v2;
// zf
_BYTE *v3;
// r8
__int64
v5;
// rax
int
v6;
// eax
RTL_SRWLOCK *v7;
// rax
_DWORD *result;
// rax
const
char
*v9;
// [rsp+20h] [rbp-18h] BYREF
int
v10;
// [rsp+28h] [rbp-10h]
char
v11;
// [rsp+2Ch] [rbp-Ch]
int
v12;
// [rsp+40h] [rbp+8h] BYREF
int
v13;
// [rsp+44h] [rbp+Ch]
v2 = *a2 == 0;
v3 = a2 + 2;
v9 = a2 + 2;
v5 = -1i64;
if
( v2 )
{
do
++v5;
while
( v3[v5] );
v11 = 0;
}
else
{
do
++v5;
while
( *(_WORD *)&v3[2 * v5] );
v11 = 1;
}
v10 = v5;
if
( (unsigned
int
)v5 < 0x400 )
{
if
( byte_148089CF9 )
{
v7 = &stru_1480AD880;
}
else
{
FNamePool_FNamePool((
__int64
)&stru_1480AD880);
byte_148089CF9 = 1;
}
sub_142B16A60(v7, &v12, &v9);
v13 = v12;
v6 = v12;
}
else
{
v10 = 24;
v9 =
"ERROR_NAME_SIZE_EXCEEDED"
;
v11 = 0;
v6 = sub_142B0CCB0(&v9, 1i64);
}
*a1 = v6;
result = a1;
a1[1] = 0;
return
result;
}
std::string GetName(uint32_t Id)
{
uint32_t Block = Id >> 16;
uint32_t Offset = Id & 65535;
uint8_t* GameBase = (uint8_t*)GetModuleHandleA(
"POLYGON-Win64-Shipping.exe"
);
uint8_t** GName = (uint8_t**)(GameBase + 0x80AD880);
FNameEntry* Info = (FNameEntry*)((GName)[2 + Block] + 2 * Offset);
return
std::string(Info->AnsiName, Info->Len);
}
printf
(
"Name:%s\n"
, GetName(0).c_str());
std::string GetName(uint32_t Id)
{
uint32_t Block = Id >> 16;
uint32_t Offset = Id & 65535;
uint8_t* GameBase = (uint8_t*)GetModuleHandleA(
"POLYGON-Win64-Shipping.exe"
);
uint8_t** GName = (uint8_t**)(GameBase + 0x80AD880);
FNameEntry* Info = (FNameEntry*)((GName)[2 + Block] + 2 * Offset);
return
std::string(Info->AnsiName, Info->Len);
}
printf
(
"Name:%s\n"
, GetName(0).c_str());
Name:None
Name:None
UWorld* UEngine::GetWorldFromContextObject(
const
UObject* Object, EGetWorldErrorMode ErrorMode)
const
{
if
(Object == nullptr)
{
switch
(ErrorMode)
{
case
EGetWorldErrorMode::Assert:
check(Object);
break
;
case
EGetWorldErrorMode::LogAndReturnNull:
FFrame::KismetExecutionMessage(TEXT(
"A null object was passed as a world context object to UEngine::GetWorldFromContextObject()."
), ELogVerbosity::Warning);
//UE_LOG(LogEngine, Warning, TEXT("UEngine::GetWorldFromContextObject() passed a nullptr"));
break
;
case
EGetWorldErrorMode::ReturnNull:
break
;
}
return
nullptr;
}
bool
bSupported =
true
;
UWorld* World = (ErrorMode == EGetWorldErrorMode::Assert) ? Object->GetWorldChecked(
/*out*/
bSupported) : Object->GetWorld();
if
(bSupported && (World == nullptr) && (ErrorMode == EGetWorldErrorMode::LogAndReturnNull))
{
FFrame::KismetExecutionMessage(*FString::Printf(TEXT(
"No world was found for object (%s) passed in to UEngine::GetWorldFromContextObject()."
), *GetPathNameSafe(Object)), ELogVerbosity::Warning);
}
return
(bSupported ? World : GWorld);
}
UWorld* UEngine::GetWorldFromContextObject(
const
UObject* Object, EGetWorldErrorMode ErrorMode)
const
{
if
(Object == nullptr)
{
switch
(ErrorMode)
{
case
EGetWorldErrorMode::Assert:
check(Object);
break
;
case
EGetWorldErrorMode::LogAndReturnNull:
FFrame::KismetExecutionMessage(TEXT(
"A null object was passed as a world context object to UEngine::GetWorldFromContextObject()."
), ELogVerbosity::Warning);
//UE_LOG(LogEngine, Warning, TEXT("UEngine::GetWorldFromContextObject() passed a nullptr"));
break
;
case
EGetWorldErrorMode::ReturnNull:
break
;
}
return
nullptr;
}
bool
bSupported =
true
;
UWorld* World = (ErrorMode == EGetWorldErrorMode::Assert) ? Object->GetWorldChecked(
/*out*/
bSupported) : Object->GetWorld();
if
(bSupported && (World == nullptr) && (ErrorMode == EGetWorldErrorMode::LogAndReturnNull))
{
FFrame::KismetExecutionMessage(*FString::Printf(TEXT(
"No world was found for object (%s) passed in to UEngine::GetWorldFromContextObject()."
), *GetPathNameSafe(Object)), ELogVerbosity::Warning);
}
return
(bSupported ? World : GWorld);
}
.rdata:00000001470E6BE0 aANullObjectWas: ; DATA XREF: sub_144FEE480+1F↑o
.rdata:00000001470E6BE0 text
"UTF-16LE"
,
'A null object was passed as a world context object '
.rdata:00000001470E6C46 text
"UTF-16LE"
,
'to UEngine::GetWorldFromContextObject().'
,0
.rdata:00000001470E6BE0 aANullObjectWas: ; DATA XREF: sub_144FEE480+1F↑o
.rdata:00000001470E6BE0 text
"UTF-16LE"
,
'A null object was passed as a world context object '
.rdata:00000001470E6C46 text
"UTF-16LE"
,
'to UEngine::GetWorldFromContextObject().'
,0
__int64
__fastcall sub_144FEE480(
__int64
a1,
__int64
a2,
int
a3)
{
__int64
v4;
// rsi
__int64
v6;
// rax
__int64
v7;
// rdi
const
wchar_t
*v8;
// rbx
const
wchar_t
*v9;
// r8
__int64
v10;
// rdx
const
wchar_t
*v11;
// [rsp+20h] [rbp-28h] BYREF
int
v12;
// [rsp+28h] [rbp-20h]
const
wchar_t
*v13;
// [rsp+30h] [rbp-18h] BYREF
int
v14;
// [rsp+38h] [rbp-10h]
__int64
v15;
// [rsp+58h] [rbp+10h] BYREF
v4 = a2;
if
( a2 )
{
LOBYTE(v15) = 1;
if
( a3 == 2 )
v6 = sub_142C63C80(a2, &v15);
else
v6 = (*(
__int64
(__fastcall **)(
__int64
))(*(_QWORD *)a2 + 392i64))(a2);
v7 = v6;
if
( !(_BYTE)v15 )
return
qword_1482ACFD0;
if
( !v6 && a3 == 1 )
{
sub_142CD1520(v4, &v13, 0i64);
v8 = &chText;
v9 = &chText;
if
( v14 != (_DWORD)v7 )
v9 = v13;
sub_1429C9990(&v11, L
"No world was found for object (%s) passed in to UEngine::GetWorldFromContextObject()."
, v9);
LOBYTE(v10) = 3;
if
( v12 != (_DWORD)v7 )
v8 = v11;
sub_142CAF290(v8, v10, 0i64);
if
( v11 )
sub_142A062C0();
if
( v13 )
sub_142A062C0();
}
if
( !(_BYTE)v15 )
return
qword_1482ACFD0;
return
v7;
}
else
{
if
( a3 == 1 )
{
v15 = 0i64;
LOBYTE(a2) = 3;
sub_142CAF290(
L
"A null object was passed as a world context object to UEngine::GetWorldFromContextObject()."
,
a2,
0i64);
}
return
0i64;
}
}
__int64
__fastcall sub_144FEE480(
__int64
a1,
__int64
a2,
int
a3)
{
__int64
v4;
// rsi
__int64
v6;
// rax
__int64
v7;
// rdi
const
wchar_t
*v8;
// rbx
const
wchar_t
*v9;
// r8
__int64
v10;
// rdx
const
wchar_t
*v11;
// [rsp+20h] [rbp-28h] BYREF
int
v12;
// [rsp+28h] [rbp-20h]
const
wchar_t
*v13;
// [rsp+30h] [rbp-18h] BYREF
int
v14;
// [rsp+38h] [rbp-10h]
__int64
v15;
// [rsp+58h] [rbp+10h] BYREF
v4 = a2;
if
( a2 )
{
LOBYTE(v15) = 1;
if
( a3 == 2 )
v6 = sub_142C63C80(a2, &v15);
else
v6 = (*(
__int64
(__fastcall **)(
__int64
))(*(_QWORD *)a2 + 392i64))(a2);
v7 = v6;
if
( !(_BYTE)v15 )
return
qword_1482ACFD0;
if
( !v6 && a3 == 1 )
{
sub_142CD1520(v4, &v13, 0i64);
v8 = &chText;
v9 = &chText;
if
( v14 != (_DWORD)v7 )
v9 = v13;
sub_1429C9990(&v11, L
"No world was found for object (%s) passed in to UEngine::GetWorldFromContextObject()."
, v9);
LOBYTE(v10) = 3;
if
( v12 != (_DWORD)v7 )
v8 = v11;
sub_142CAF290(v8, v10, 0i64);
if
( v11 )
sub_142A062C0();
if
( v13 )
sub_142A062C0();
}
if
( !(_BYTE)v15 )
return
qword_1482ACFD0;
return
v7;
}
else
{