hack.js的用法为:终端输入
等待游戏启动之后继续在终端输入: my_hack()调用my_hack函数
用frida-ue4dumper得到base: 0x764bd2f000, GUObjectArray: undefined, GName: 0x7656b1f7c0
得到GName 偏移:0xADF07C0
GWorld : 0xAFAC398
(通过字符串SeamlessTravel FlushLevelStreaming
定位)GUObject : 0xAE34A98
(通过字符串Max UObject count is invalid. It must be a number that is greater than 0.
定位)
dumpSDK
dumpActors
dump libUE4.so
直接删掉libGame.so再启动游戏会crash,查看log可以定位到libGame.so
是在libUE4.so
加载的时候加载的,并且发现libUE4.so的依赖中包含了libGame.so,可以确认是ELF感染注入 通过readelf可以看到libGame.so是libUE4.so的依赖,具体的实现逻辑应该是通过修改DT_NEEDED
标签来加载这个so.
检查PHT数组中p_tag 为PT_DYNAMIC
的元素,并找到其中d_tag 为DT_STRTAB
的元素,其值就是字符串表在文件中的偏移,d_tag 为DT_STRSZ
的元素的值是字符串表的长度,将两者相加即为字符串表末尾的地址,发现字符串表中有libGame.so
,并且DT_NEEDED 条目中包含了libGame.so
,当linker 加载libUE4.so时,会解析libUE4.so的dynamic 段,并遍历DT_NEEDED条目,生成新的 LoadTask
,递归加载所有依赖库
libGame.so的函数都被CFF混淆过了,用D810插件可以有比较不错的去混淆效果,起码算是能看了 libGame.so的init_array段中会调用一个函数,追踪这个函数的调用链可以发现sub_27F0会创建一个线程,那么这个线程执行的函数就是外挂逻辑的实现了
0x1B9C函数是主要执行外挂逻辑的函数
在libGame.so里面还有几个字符串解密函数,由于数量不是很多所以我并没有写一个一次性解密所有字符串的函数,如果需要解密字符串可以通过特征匹配 的思路来解决,字符串解密函数的第一个参数存放解密后的字符串,第二个参数是密文,这种字符串解密函数的特征就是前n个字节做密钥,后面的则是密文 ,字符串的长度就是if条件中的值,如果使用特征匹配的话思路就是匹配^
,%
, if == n
这样的式子来获取字符串长度和密钥长度 ,然后查找字符串函数的交叉引用,提取参数中的地址,并用IDA的api读取指定地址的数据,然后复现解密函数进行批量解密
我这里为了不复杂化就写一个解密函数来手动修改长度和数据
于是可以得到字符串信息
sub_B80函数是寻找libUE4.so的基址,具体实现流程如下:通过fopen函数打开proc/pid/maps文件打开程序的虚拟内存空间,通过fgets函数遍历maps文件的每一行字符串,通过strstr函数筛选包含libUE4.so
字符串的行,然后通过strtok函数以字符-
分割文本,提取字符串,最后使用strtoul
函数讲字符串转换成unsigned long int类型的整数,通过Fridahook可以很容易分析出来这一函数
发现这里和之前找到的GWorld偏移一致,可以知道这里获取了GWorld的地址
这里获取了持久关卡 的指针,这是UWorld类中的一个重要成员,存放了Actors
等数据,是遍历玩家角色的关键成员变量
这里获取了Actor的数组和长度,用于下面遍历Actor获取目标对象
此处遍历Actor并获取指定对象,这里的0xA63BE28是对象的虚标指针,UE4中每个类的虚表(vtable)在libUE4.so
中的偏移是固定的。通过比对虚表偏移,判断当前 Actor 是否为目标对象
通过frida脚本可以发现程序寻找的Actor为: FirstPersonCharacter_C
这里可以看到外挂寻找了几个当前对象的偏移
可以在SDK找到这几个偏移对应的变量
通过修改对应的内存值可以发现RecoilAccumulationRate
实际上是计算枪口抖动的系数,外挂中把此处修改成了0,改成非0的值就可以发现开枪之后枪口会上调MaxAcceleration
是人物移动速度的变量,程序把这里的值修改成了1000000000,把值改成1000则可以使人物正常行走
仔细观察游戏会发现,每次开枪的时候准星都会锁定在一个箱子 那里,无论是重启游戏还是移动角色或者箱子到不同位置,准星始终在一个箱子身上,于是先寻找出来是哪一个箱子,在之前dump的Actor列表中发现有名为EditorCube
的对象,一共有14个,数了一下箱子的数量也是14个,那么基本可以确定箱子就是这个对象,然后通过输出所有箱子的vector
的值获取坐标,再移动目标箱子就可以比对出来是哪一个箱子
通过上述方法比对之后不难发现只有EditorCube8
的值发生了变化,那么可以确定这个就是我们的目标Actor,接下来寻找这个箱子的世界坐标并给坐标下硬件断点打印调用栈
通过Frida hook 我们可以获取角色视角的Vector的值,通过PlayerController + 0x288
的偏移处可以获取到ControlRotation
的地址,其中后续的12个字节就是角色的相机的位置信息,即FRotator
结构体,其成员变量分别是Pitch
,Yaw
,Roll
,使用stackplz
给此处下硬件断点(w)并打印调用栈可回溯到写入此内存地址的函数的调用栈
下断点
得到调用栈信息,经过分析发现0x8b387c0
位置的函数是向FRotator
结构体的成员变量写入坐标的,而0x670f3f8
貌似是处理用户交互的函数,也就是处理射击 这一行为的函数,使用Frida将这一处的调用Patch成NOP可以实现自瞄的去除
可以看到这里会对坐标进行一些计算,之后就调用函数0x8b387c0
将坐标写入到对应结构体中,patch此处函数调用即可使外挂只计算而不写入坐标信息
考虑到自瞄实现的逻辑中,角色视角和子弹弹道的改变应该同时处理,所以在前面修改角色视角的函数中同样实现了修改子弹弹道的具体实现逻辑
分析Actorlist可以知道子弹的对象是FirstPersonProjectile_C
,这个类继承自ProjectileMovementComponent.MovementComponent.ActorComponent.Object
通过hook打印这个类的InitialSpeed
和MaxSpeed
可以发现这两个值都是3000 说明子弹速度并没有被修改
在函数sub_670F110
中继续分析可以看到前面patch的地方的后面就是处理子弹弹道的逻辑
其中ChangeCorner
函数(已改名)是用来控制子弹弹道随机化 的(-30°到30°)
sub8D2ED80函数是控制枪口Location和Rotation的关键逻辑,函数调用链为sub_8D2ED80->sub_8D2E214
,sub_8D2E214是处理spawnactor的函数,见下图
阅读UE4官方文档可以知道子弹对象的生成逻辑和发射方向的逻辑
可以看到MuzzleLocation
是枪口的位置信息 ,MuzzleRotation
是枪口的朝向信息 ,而发射物的初始轨迹就是通过MuzzleRotation
来设置的,通过Frida hook获取 sub_8D2ED80函数调用前的寄存器信息可以知道其第三和第四个参数就是我们需要的Location和Rotation
通过控制变量多次调整角色的位置并触发hook可以知道我们的分析没有问题,下面只需要把子弹的Rotation
换成摄像机的Rotation即可,在onEnter回调中动态获取角色的Rotation并修改进去即可,Frida脚本如下
观察游戏中透视的特征可以发现无视了墙壁直接渲染在我们的视角中,说明他可能修改了材质的bDisableDepthTest
的值为True ,使得人物模型可以透过墙壁看见,也可能是修改了模型的渲染顺序 使得玩家的模型最后渲染,并且人物变成了红色高亮 状态,这通常会调用SetVectorParameterValue
函数对材质的RGB值进行修改。
通过类成员变量之间的引用,可以找到一条获取Material
对象的指针链:Character -> SkeletalMeshComponent - >SkinnedMeshComponent -> SkeletalMesh -> SkeletalMaterial -> MaterialInterface -> Material ;获取到Material之后就可以修改他的成员变量bDisableDepthTest
,经过Frida hook之后也确实发现这个值是True,但是修改成False之后透视效果依然存在,也想过可能是这个bool变量只会读取一次,但是实在是找不到hook时机来修改这一点
也尝试了去修改bCollideWithEnvironment
的值,然而也是没有效果,实在是想不到还有哪些地方会修改了
这次比赛的题目主要是考验对UE4引擎的熟悉程度,逆向的部分占比不算特别大,也没有出什么比较难的混淆加壳之类的,虽然已经做出了大部分的内容,但是还是需要加深对UE4引擎的理解,如果对UE4足够熟悉,应该能减少很多在寻找各个类之间的关系和所需的成员变量上。
文件名称
功能
decode.py
处理字符串解密
hack.js
实现修复功能的Frida脚本
SDK.txt
Dump得到的游戏SDK
Actor.txt
Dump得到的Actorlist
frida -U -f com.ACE2025.Game -l hack.js
frida -U -f com.ACE2025.Game -l hack.js
.
/ue4dumper64
--package com.ACE2025.Game --ptrdec --sdku --gname 0xADF07C0 --guobj 0xAE34A98 --output
/data/local/tmp
--newue+
.
/ue4dumper64
--package com.ACE2025.Game --ptrdec --sdku --gname 0xADF07C0 --guobj 0xAE34A98 --output
/data/local/tmp
--newue+
.
/ue4dumper64
-- package com.ACE2025.Game --ptrdec --actors --gname 0xADF07C0 --gworld 0xAFAC398 --output
/data/local/tmp/actors
.txt --newue+
.
/ue4dumper64
-- package com.ACE2025.Game --ptrdec --actors --gname 0xADF07C0 --gworld 0xAFAC398 --output
/data/local/tmp/actors
.txt --newue+
[INFO] RootService: Service connected
Process : com.ACE2025.Game
PID: 15801
FILE: libUE4.so
Start Address: 764bd2f000
End Address: 7656a3d000
Size Memory: 173MB (181460992)
[INFO] Fixing...
[INFO] Fixer output :
Rebuilding Elf(So)
warning load size [185078944] is bigger than so size [181460992], dump maybe incomplete!!!
warning DT_HASH not found,try to detect dynsym size...
fixed so has write to /sdcard/PADumper/com.ACE2025.Game/764bd2f000-7656a3d000-libUE4_fix.so
Rebuilding Complete
Output: /sdcard/PADumper/com.ACE2025.Game
[INFO] Dump Success
[INFO] RootService: Service connected
Process : com.ACE2025.Game
PID: 15801
FILE: libUE4.so
Start Address: 764bd2f000
End Address: 7656a3d000
Size Memory: 173MB (181460992)
[INFO] Fixing...
[INFO] Fixer output :
Rebuilding Elf(So)
warning load size [185078944] is bigger than so size [181460992], dump maybe incomplete!!!
warning DT_HASH not found,try to detect dynsym size...
fixed so has write to /sdcard/PADumper/com.ACE2025.Game/764bd2f000-7656a3d000-libUE4_fix.so
Rebuilding Complete
Output: /sdcard/PADumper/com.ACE2025.Game
[INFO] Dump Success
data
=
[
0xA5
,
0x05
,
0x5E
,
0x29
,
0xE2
,
0x9A
,
0xBB
,
0x6E
,
0x08
,
0x42
,
0xC3
,
0x55
,
0xEC
,
0x01
,
0x7C
,
0xA9
,
0x96
,
0xD3
,
0x59
,
0xA8
,
0x91
,
0xCF
,
0x89
,
0x11
,
0x24
,
0xD6
,
0xC9
,
0x6C
,
0x3C
,
0x7C
,
0xA7
,
0xAE
,
0x95
,
0x1D
,
0x67
,
0x42
]
count
=
0
for
i
in
range
(
len
(data)):
print
(
chr
(data[i
+
0x1A
] ^ data[i
%
0x1A
]), end
=
'')
count
+
=
1
if
count
=
=
0x9
:
break
data
=
[
0xA5
,
0x05
,
0x5E
,
0x29
,
0xE2
,
0x9A
,
0xBB
,
0x6E
,
0x08
,
0x42
,
0xC3
,
0x55
,
0xEC
,
0x01
,
0x7C
,
0xA9
,
0x96
,
0xD3
,
0x59
,
0xA8
,
0x91
,
0xCF
,
0x89
,
0x11
,
0x24
,
0xD6
,
0xC9
,
0x6C
,
0x3C
,
0x7C
,
0xA7
,
0xAE
,
0x95
,
0x1D
,
0x67
,
0x42
]
count
=
0
for
i
in
range
(
len
(data)):
print
(
chr
(data[i
+
0x1A
] ^ data[i
%
0x1A
]), end
=
'')
count
+
=
1
if
count
=
=
0x9
:
break
地址
字符串
0xB658
libUE4.so
0xB650
-
0xB648
r
0xB634
/proc/%d/maps
0xB620
/proc/self/maps
function
hook_strstr() {
const targetModule =
"libGame.so"
;
const strstrPtr = Module.getExportByName(
null
,
"strstr"
);
Interceptor.attach(strstrPtr, {
onEnter:
function
(args) {
const returnAddress =
this
.returnAddress;
const module = Process.findModuleByAddress(returnAddress);
if
(!module || module.name !== targetModule)
return
;
const mainStr = Memory.readCString(args[0]);
const subStr = Memory.readCString(args[1]);
console.log(`[${targetModule}] strstr(
"${mainStr}"
,
"${subStr}"
)`);
}
});
}
const waitForGameLib = setInterval(() => {
if
(Module.findBaseAddress(
"libGame.so"
)) {
clearInterval(waitForGameLib);
hook_strstr();
}
}, 100);
function
hook_strstr() {
const targetModule =
"libGame.so"
;
const strstrPtr = Module.getExportByName(
null
,
"strstr"
);
Interceptor.attach(strstrPtr, {
onEnter:
function
(args) {
const returnAddress =
this
.returnAddress;
const module = Process.findModuleByAddress(returnAddress);
if
(!module || module.name !== targetModule)
return
;
const mainStr = Memory.readCString(args[0]);
const subStr = Memory.readCString(args[1]);
console.log(`[${targetModule}] strstr(
"${mainStr}"
,
"${subStr}"
)`);
}
});
}
const waitForGameLib = setInterval(() => {
if
(Module.findBaseAddress(
"libGame.so"
)) {
clearInterval(waitForGameLib);
hook_strstr();
}
}, 100);
function
my_hack(){
getlibue4();
var
actorsAddr = getActorsAddr();
var
base = Module.getBaseAddress(
"libUE4.so"
);
for
(
var
key
in
actorsAddr) {
if
(actorsAddr[key].readPointer() - base == 0xA63BE28) {
console.log(
"[+] Successful Get target Actor : "
, key);
}
}
}
function
my_hack(){
getlibue4();
var
actorsAddr = getActorsAddr();
var
base = Module.getBaseAddress(
"libUE4.so"
);
for
(
var
key
in
actorsAddr) {
if
(actorsAddr[key].readPointer() - base == 0xA63BE28) {
console.log(
"[+] Successful Get target Actor : "
, key);
}
}
}
偏移
类
对象
0x538
MyProjectCharacter.Character.Pawn.Actor.Object
RecoilAccumulationRate
0x288
Character.Pawn.Actor.Object
CharacterMovementComponent
0x1A0
CharacterMovementComponent.PawnMovementComponent....
MaxAcceleration
function
my_hack(){
getlibue4();
var
actorsAddr = getActorsAddr();
var
base = Module.getBaseAddress(
"libUE4.so"
);
for
(
var
key
in
actorsAddr) {
if
(actorsAddr[key].readPointer() - base == 0xA63BE28) {
console.log(
"[+] Successful Get target Actor : "
, key);
var
player_addr = actorsAddr[key];
var
RecoilAccumulationRate = player_addr.add(0x538);
var
CharacterMovementComponent = player_addr.add(0x288).readPointer();
var
MaxAcceleration = CharacterMovementComponent.add(0x1A0);
Memory.writeFloat(MaxAcceleration, 1000);
Memory.writeFloat(RecoilAccumulationRate, 5);
}
}
}
function
my_hack(){
getlibue4();
var
actorsAddr = getActorsAddr();
var
base = Module.getBaseAddress(
"libUE4.so"
);
for
(
var
key
in
actorsAddr) {
if
(actorsAddr[key].readPointer() - base == 0xA63BE28) {
console.log(
"[+] Successful Get target Actor : "
, key);
var
player_addr = actorsAddr[key];
var
RecoilAccumulationRate = player_addr.add(0x538);
var
CharacterMovementComponent = player_addr.add(0x288).readPointer();
var
MaxAcceleration = CharacterMovementComponent.add(0x1A0);
Memory.writeFloat(MaxAcceleration, 1000);
Memory.writeFloat(RecoilAccumulationRate, 5);
}
}
}
class Vector {
constructor(x, y, z) {
this
.x = x;
this
.y = y;
this
.z = z;
}
toString() {
return
`(${
this
.x}, ${
this
.y}, ${
this
.z})`;
}
}
function
dumpVector(addr) {
const values = Memory.readByteArray(addr, 3 * 4);
const vec =
new
Vector(
new
Float32Array(values, 0, 1)[0],
new
Float32Array(values, 4, 1)[0],
new
Float32Array(values, 8, 1)[0]
);
console.log(
"[+] 坐标:"
, vec);
}
function
get_box_vec(){
getlibue4();
var
base = Module.getBaseAddress(
"libUE4.so"
);
var
actorsAddr = getActorsAddr();
for
(
var
key
in
actorsAddr) {
if
(key.includes(
"EditorCube"
)){
var
actor_addr = actorsAddr[key];
var
RootComponent = actor_addr.add(0x130).readPointer();
var
Location = RootComponent.add(0x11C);
console.log(
"[+]Actor: "
, key);
dumpVector(Location);
}
}
}
class Vector {
constructor(x, y, z) {
this
.x = x;
this
.y = y;
this
.z = z;
}
toString() {
return
`(${
this
.x}, ${
this
.y}, ${
this
.z})`;
}
}
function
dumpVector(addr) {
const values = Memory.readByteArray(addr, 3 * 4);
const vec =
new
Vector(
new
Float32Array(values, 0, 1)[0],
new
Float32Array(values, 4, 1)[0],
new
Float32Array(values, 8, 1)[0]
);
console.log(
"[+] 坐标:"
, vec);
}
function
get_box_vec(){
getlibue4();
var
base = Module.getBaseAddress(
"libUE4.so"
);
var
actorsAddr = getActorsAddr();
for
(
var
key
in
actorsAddr) {
if
(key.includes(
"EditorCube"
)){
var
actor_addr = actorsAddr[key];
var
RootComponent = actor_addr.add(0x130).readPointer();
var
Location = RootComponent.add(0x11C);
console.log(
"[+]Actor: "
, key);
dumpVector(Location);
}
}
}
Part 1 :
[+]Actor: EditorCube8
[+] 坐标: (843.998779296875, -1169.60498046875, 295.23797607421875)
[+]Actor: EditorCube9
[+] 坐标: (1464.425537109375, -657.312744140625, 245.24537658691406)
[+]Actor: EditorCube10
[+] 坐标: (1464.425537109375, -46.785343170166016, 245.24537658691406)
[+]Actor: EditorCube11
[+] 坐标: (860.821533203125, -46.78565216064453, 245.23817443847656)
[+]Actor: EditorCube12
[+] 坐标: (1307.8231201171875, 714.8047485351563, 245.24351501464844)
[+]Actor: EditorCube13
[+] 坐标: (1310.8233642578125, 874.9715576171875, 245.2435302734375)
[+]Actor: EditorCube14
[+] 坐标: (1310.8233642578125, 790.3173828125, 395.24346923828125)
[+]Actor: EditorCube15
[+] 坐标: (-896.7808227539063, 828.98193359375, 245.21722412109375)
[+]Actor: EditorCube16
[+] 坐标: (-1034.357666015625, 746.9696655273437, 245.21559143066406)
[+]Actor: EditorCube17
[+] 坐标: (-961.6449584960938, 790.31689453125, 395.21636962890625)
[+]Actor: EditorCube18
[+] 坐标: (-1439.8385009765625, -811.4144897460937, 245.21078491210937)
[+]Actor: EditorCube19
[+] 坐标: (-1439.401123046875, -811.4213256835937, 395.2096862792969)
[+]Actor: EditorCube20
[+] 坐标: (-1309.3416748046875, -373.11163330078125, 295.2123107910156)
[+]Actor: EditorCube21
[+] 坐标: (-1123.3922119140625, 153.2134246826172, 245.2145233154297)
Part 2:
[+]Actor: EditorCube8
[+] 坐标: (859.3866577148438, -1172.3924560546875, 295.2377624511719)
[+]Actor: EditorCube9
[+] 坐标: (1464.425537109375, -657.312744140625, 245.24537658691406)
[+]Actor: EditorCube10
[+] 坐标: (1464.425537109375, -46.785343170166016, 245.24537658691406)
[+]Actor: EditorCube11
[+] 坐标: (860.821533203125, -46.78565216064453, 245.23817443847656)
[+]Actor: EditorCube12
[+] 坐标: (1307.8231201171875, 714.8047485351563, 245.24351501464844)
[+]Actor: EditorCube13
[+] 坐标: (1310.8233642578125, 874.9715576171875, 245.2435302734375)
[+]Actor: EditorCube14
[+] 坐标: (1310.8233642578125, 790.3173828125, 395.24346923828125)
[+]Actor: EditorCube15
[+] 坐标: (-896.7808227539063, 828.98193359375, 245.21722412109375)
[+]Actor: EditorCube16
[+] 坐标: (-1034.357666015625, 746.9696655273437, 245.21559143066406)
[+]Actor: EditorCube17
[+] 坐标: (-961.6449584960938, 790.31689453125, 395.21636962890625)
[+]Actor: EditorCube18
[+] 坐标: (-1439.8385009765625, -811.4144897460937, 245.21078491210937)
[+]Actor: EditorCube19
[+] 坐标: (-1439.401123046875, -811.4213256835937, 395.2096862792969)
[+]Actor: EditorCube20
[+] 坐标: (-1309.3416748046875, -373.11163330078125, 295.2123107910156)
[+]Actor: EditorCube21
[+] 坐标: (-1123.3922119140625, 153.2134246826172, 245.2145233154297)
Part 1 :
[+]Actor: EditorCube8
[+] 坐标: (843.998779296875, -1169.60498046875, 295.23797607421875)
[+]Actor: EditorCube9
[+] 坐标: (1464.425537109375, -657.312744140625, 245.24537658691406)
[+]Actor: EditorCube10
[+] 坐标: (1464.425537109375, -46.785343170166016, 245.24537658691406)
[+]Actor: EditorCube11
[+] 坐标: (860.821533203125, -46.78565216064453, 245.23817443847656)
[+]Actor: EditorCube12
[+] 坐标: (1307.8231201171875, 714.8047485351563, 245.24351501464844)
[+]Actor: EditorCube13
[+] 坐标: (1310.8233642578125, 874.9715576171875, 245.2435302734375)
[+]Actor: EditorCube14
[+] 坐标: (1310.8233642578125, 790.3173828125, 395.24346923828125)
[+]Actor: EditorCube15
[+] 坐标: (-896.7808227539063, 828.98193359375, 245.21722412109375)
[+]Actor: EditorCube16
[+] 坐标: (-1034.357666015625, 746.9696655273437, 245.21559143066406)
[+]Actor: EditorCube17
[+] 坐标: (-961.6449584960938, 790.31689453125, 395.21636962890625)
[+]Actor: EditorCube18
[+] 坐标: (-1439.8385009765625, -811.4144897460937, 245.21078491210937)
[+]Actor: EditorCube19
[+] 坐标: (-1439.401123046875, -811.4213256835937, 395.2096862792969)
[+]Actor: EditorCube20
[+] 坐标: (-1309.3416748046875, -373.11163330078125, 295.2123107910156)
[+]Actor: EditorCube21
[+] 坐标: (-1123.3922119140625, 153.2134246826172, 245.2145233154297)
Part 2:
[+]Actor: EditorCube8
[+] 坐标: (859.3866577148438, -1172.3924560546875, 295.2377624511719)
[+]Actor: EditorCube9
[+] 坐标: (1464.425537109375, -657.312744140625, 245.24537658691406)
[+]Actor: EditorCube10
[+] 坐标: (1464.425537109375, -46.785343170166016, 245.24537658691406)
[+]Actor: EditorCube11
[+] 坐标: (860.821533203125, -46.78565216064453, 245.23817443847656)
[+]Actor: EditorCube12
[+] 坐标: (1307.8231201171875, 714.8047485351563, 245.24351501464844)
[+]Actor: EditorCube13
[+] 坐标: (1310.8233642578125, 874.9715576171875, 245.2435302734375)
[+]Actor: EditorCube14
[+] 坐标: (1310.8233642578125, 790.3173828125, 395.24346923828125)
[+]Actor: EditorCube15
[+] 坐标: (-896.7808227539063, 828.98193359375, 245.21722412109375)
[+]Actor: EditorCube16
[+] 坐标: (-1034.357666015625, 746.9696655273437, 245.21559143066406)
[+]Actor: EditorCube17
[+] 坐标: (-961.6449584960938, 790.31689453125, 395.21636962890625)
[+]Actor: EditorCube18
[+] 坐标: (-1439.8385009765625, -811.4144897460937, 245.21078491210937)
[+]Actor: EditorCube19
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2025-4-17 12:42
被Je2em1ah编辑
,原因:
上传的附件: