记一次UE逆向--三角洲行动

记一次UE逆向–三角洲行动


关于


本文章仅供学习交流,请勿用于非法用途


信息获取


通往罗马的路不止一条。

三角洲行动这个游戏可以说是市面上反作弊部署最为完善的游戏,上到服务器端,下到内存cr3都有严苛的反作弊措施。严密的检测下几乎不可能直接上手调试。如果只会公式化逆向或者喜欢单刷ACE,只能说死路一条

但是哥们有社会工程学

只要我能获取我想要的信息,那我就可以绕过调试阶段,直接写脚本了

Offset

BV1EigWzGEaj

大佬的dll直接注入即可GetOffset(

6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
																/* Create by ShaHen */
/* QQ:750144893 */
inline static const uint64_t OwningGameInstance = 0x00000000000001B0
inline static const uint64_t LocalPlayers = 0x0000000000000048
inline static const uint64_t PlayerController = 0x0000000000000038
inline static const uint64_t ControlRotation = 0x00000000000003E8
inline static const uint64_t AcknowledgedPawn = 0x0000000000000410
inline static const uint64_t PlayerCameraManager = 0x0000000000000430
inline static const uint64_t CameraCachePrivate = 0x000000000002F300
inline static const uint64_t POV = 0x0000000000000010
inline static const uint64_t RootComponent = 0x0000000000000188
inline static const uint64_t RelativeLocation = 0x000000000000016C
inline static const uint64_t RelativeRotation = 0x0000000000000178
inline static const uint64_t Mesh = 0x00000000000003E8
inline static const uint64_t MasterPoseComponent = 0x0000000000000718
inline static const uint64_t PlayerState = 0x00000000000003A0
inline static const uint64_t PlayerNamePrivate = 0x0000000000000488
inline static const uint64_t bFinishGame = 0x00000000000004D8
inline static const uint64_t TeamId = 0x000000000000067C
inline static const uint64_t CampID = 0x0000000000000680
inline static const uint64_t LoginInfo = 0x0000000000000690
inline static const uint64_t LoginNumber = 0x0000000000000008
inline static const uint64_t CacheCurWeapon = 0x0000000000001560
inline static const uint64_t WeaponID = 0x0000000000000828
inline static const uint64_t HealthComp = 0x0000000000000F00
inline static const uint64_t HealthSet = 0x0000000000000250
inline static const uint64_t Health = 0x0000000000000048
inline static const uint64_t MaxHealth = 0x0000000000000068
inline static const uint64_t CharacterEquipComponentCache = 0x0000000000001F50
inline static const uint64_t EquipmentInfoArray = 0x00000000000001E0
inline static const uint64_t PoolChosenName = 0x0000000000002620
inline static const uint64_t RepItemArray = 0x00000000000017C8
inline static const uint64_t Items = 0x0000000000000108
inline static const uint64_t MarkingItemType = 0x000000000000072A
inline static const uint64_t bLooted = 0x0000000000001E60
inline static const uint64_t bHasOpened = 0x0000000000001C54
inline static const uint64_t TipsText = 0x0000000000001D00
inline static const uint64_t Password = 0x0000000000000D44
inline static const uint64_t PwdSum = 0x0000000000000DA0
inline static const uint64_t GWorld = 0x000000015264C588
inline static const uint64_t Gname = 0x0000000152F50400
inline static const uint64_t Matrix = 0x000000015263C520
inline static const uint64_t Object = 0x0000000152F69D88


值得一提的是,三角洲行动的基址固定为5368709120 (0x140000000)

以Gname举例,实际上它的偏移是0x0000000152F50400 - 0x0000000140000000 = 0x12F50400

我在测试的时候被这个问题搞晕乎了,我一直以为是我的偏移算错了,但是实际上不是。

在cpp中可以直接简化这一步骤,即用inline static const uint64_t Gname = 0x0000000152F50400直接表示Gname。这个现象令我十分困惑,我毕竟逆向水平不高如果有大佬能为我解释请联系我。

gname算法


三角洲的gname算法在b站也是烂大街了,这里不做过多叙述,直接上算法

E:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
.版本 2

ChunkOffset = Uchar (右移 (key, 18))
NameOffset = 位与 (key, 262143)
GNameTable = g_模块 + #GetName


NamePoolChunk = 读长整数 (GNameTable + ChunkOffset × 8 + 8) + 到整数 (2 × NameOffset)

NameEntry = 读短整数 (NamePoolChunk)
NameLength = 到整数 (右移 (NameEntry, 6))

.如果真 (NameLength > 256 或 NameLength ≤ 0)

返回 (“NULL”)
.如果真结束
EncText = 驱动.读写_读字节集 (PID, NamePoolChunk + 2, 1024)


.如果 (位与 (NameEntry, 1) ≠ 0)
' 调试输出 (“宽字符”)
.否则
.如果真 (EncText [1] ≠ 0)
Index = 0

.循环判断首 ()

EncText [Index + 1] = 位异或 (EncText [Index + 1], GetXorKey (NameLength))
Index = Index + 1
.循环判断尾 (Index < NameLength)
EncText [NameLength + 1] = 0
.如果真结束

返回 (到文本 (EncText))

Cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <windows.h>
#include <cstdint>
#include <string>
#include <vector>

uintptr_t g_module;
DWORD PID;


std::vector<BYTE> ReadMemory(uintptr_t address, size_t size) {

std::vector<BYTE> buffer(size);

return buffer;
}

uint64_t ReadUInt64(uintptr_t address) {
auto data = ReadMemory(address, 8);
return *reinterpret_cast<uint64_t*>(data.data());
}

uint16_t ReadUInt16(uintptr_t address) {
auto data = ReadMemory(address, 2);
return *reinterpret_cast<uint16_t*>(data.data());
}

BYTE GetXorKey(int nameLength) {

return 0;
}

std::string GetNameFromKey(uint64_t key) {
BYTE ChunkOffset = static_cast<BYTE>(key >> 18);


uint32_t NameOffset = key & 262143;

const uintptr_t GetNameOffset = 0x12345678; // 需要替换为实际偏移量
uintptr_t GNameTable = g_module + GetNameOffset;

uintptr_t NamePoolChunk = ReadUInt64(GNameTable + ChunkOffset * 8 + 8) + static_cast<uintptr_t>(2 * NameOffset);


uint16_t NameEntry = ReadUInt16(NamePoolChunk);


int NameLength = static_cast<int>(NameEntry >> 6);

if (NameLength > 256 || NameLength <= 0) {
return "NULL";
}


std::vector<BYTE> EncText = ReadMemory(NamePoolChunk + 2, 1024);

if ((NameEntry & 1) != 0) {

return "WIDE_CHAR_NOT_IMPLEMENTED";
} else {
if (EncText.size() > 0 && EncText[0] != 0) {
int Index = 0;
do {
EncText[Index] = EncText[Index] ^ GetXorKey(NameLength);
Index++;
} while (Index < NameLength);

EncText[NameLength] = 0;

return std::string(reinterpret_cast<char*>(EncText.data()));
}
}

return "NULL";
}

绘制过滤


gname对象

这里也没什么好讲的。如果是小兵和真人,成员对象名字都有Character关键词,直接过滤即可

读取坐标

这里的坐标和正常的ue有一些不同,偏移大概是读RootComponent 的值+0x220

其他相对偏移

OwningGameInstance=0x1B0

LocalPlayers=0x50

ULevels = =0x108

其他譬如世界转屏幕,矩阵这里不多叙述,都是直接用上一篇文章远光84的代码

效果

7

线程这块可以优化一下,多次测试表明三个线程:绘制刷新gname刷新矩阵

其中刷新gname设置延时为600000

如上安排效果好,不会出现拖框的情况

后记


84那篇文章里我尝试在进程保护加载器dump内存成功了,我突发奇想能不能用这个方法dump三角洲的内存

然后不出意料的失败了,dump出来180mb的内存,ida分析了一个晚上,字符串都有700万个,很明显,这些都是被混淆的数据。

我在查阅资料的时候发现,至少在2025年3月26日的时候,三角洲行动这个游戏是可以正常dump内存而不会报错的。说明这几个赛季里三角洲行动做了很多反外挂的举措。

后记的后记


过了一天之后我重新上号发现被封了十年。。。好在调试的时候我用的云电脑和小号,也算是免于机器码封禁了。

这篇文章完成于2025/8/20,然而在8/21号这天三角洲做了一个小更新,把ulevel加密了,所以本文的源码和方法应该不适用了(笑)

最后还是希望国产游戏的反作弊能越做越好吧

(本文完)