-
-
腾讯游戏安全2024安卓初赛复现
-
发表于: 2024-6-2 18:09 27452
-
这个可能是属于UE4逆向首先要做的事情,网上关于这一步有很多教程,这里便不再详说。
看UE4具体版本:
版本是4.27
将UE4.so拖入IDA,IDA分析完后获取三个主要结构的偏移。
GWorld:0x0B32D8A8
GName:0x0B171CC0
GUObjectArray:0xB1B5F98
拿到dump下来的SDK后应该就可以通过它来锁定实例对一些属性进行一个修改。
不过这块我要怎么实现呢?写frida脚本吗?
是的,写frida脚本,但是这和我之前写的一些小小的勾取脚本不同,这次要锁定类的方法是根据地址。
下文的代码块中基本都是JS代码,实现一些功能的函数。
这里有一步操作是我们可以从SDK_dump文件中获取到很多函数及类的偏移。
将K2_GetActorLocation函数的偏移传入构造JavaScript函数,然后将玩家的类地址和移动到的坐标作为参数即可调用我们自定义的函数。
将获取到的坐标进行一个搜索
冻结后实现一个方向无法移动,证实该坐标的正确。
从之前的SDK_dump中获取函数的偏移
构建setActorLocation函数
这里只要调用该函数即可实现坐标的变化。
瞬移到此结束。
虽然瞬移到此结束,但是这里我要说明一下这里有关函数类封装性的分析。
这里拿SetActorLocation来做个例子。
dump下的SDK中相关的该函数地址为0x965dc3c。转到该地址后
实际参数只有三个。
这里的话,要关联一些关于SDK的知识。SDK中看到的函数地址是中转函数。
这里的三个参数数量是正确的,分别是类指针,存放参数用来解析的结构体,存放返回值的指针,它是一个中转函数。
在下文的return这里的这个函数才是实际和我们源码看到的相符的。
而我们再进入这个函数时,可以看到和源码中被return的那个函数相符的部分。
所以sub_8C3181C这里才是我们实际要调用的函数。
通过改变玩家高度大概锁定了天上的flag在高度3000左右往上。
这里就是对所有高度大于3000的对象实例尽可能调用SetVisibility函数来使得Actor可见。
第一个参数为Component型
所以传入Actor对象实例的0x130偏移为指针。
然后试图对所有对象进行调用。
这里发现问题似乎在于函数的使用,这里使用类本身的setStaticMeshActorCollisionEnabled()可以实现立方体可碰撞,但是我选用上面的更为全局的碰撞函数setActorEnableCollision()时却无法实现。这里不太清楚为什么,把section3搞完再去搜一下。
解读一下第二种碰撞函数:对actor加上0x220的偏移,定位到StaticMeshComponent* StaticMeshComponent,读取指针定位到StaticMeshComponent对象,再读取一次指针定位到StaticMeshComponent对象的虚函数表。然后加上0x660的偏移读取SetCollisionEnable虚函数地址。
碰撞了所有的三个立方体后天上又出现flag的一部分。
第三个是直接找到了SetCollisionEnabled的地址然后构建函数。
第一个无法实现,第二个和第三个本质上是一样的,可以实现功能。
这个类的方法直接猜了下和getflag这种字符串有关,直接在sdkdump中搜到了。但是正解我还需要再学习一下。
找到该函数后直接到目标地址。
dlopen是一个UE4.so中专门的函数。
调用了libplay中的get_last_flag函数。
再次进入后发现一堆异或。
这段异或尝试后发现是base64的码表。
打算通过动调理清这段加密流程。
在手机端启动idaserver。
然后以该命令以调试状态启动目标app
可以Attach到进程,但是F9之后没有任何反应,手机显示wait for debug,电脑直接跑飞。
看来动调并不太行,于是转而去读汇编。
E4C这一段下面的BR会根据一些比较来决定跳转到下面的EA8还是ED8,这个让gpt就可以分析出来。
大致分析EA8是主要加密段,进行一个异或操作,下面的ED8类似RET,进行一个结束处理。(分析不一定准确,因为不会ARM汇编,只能结合AI)
再分析下面的函数,进入后发现EEC
这个数组异或完后猜测是base64变表,那这个函数大抵就是base64了。
再往下的部分大概就是比较了。
汇编喂给GPT后分析出大致是24个字节的比较。
这里就能推断出上文其中一个进行异或的数组是密文
异或后:
这段数组应该就是作为EA8段异或操作的key数组。
根据这些写出脚本:
拿到最后一部分flag:_Anti_Cheat_Expert
flag:FLAG{8939008_Anti_Cheat_Expert}
在复习的过程中还是学了不少东西的,也多亏了有两位师傅的无私解疑和指导,写的文章还很粗糙,需要时间打磨。
最后在这里贴上frida的脚本供于参考。
以及我学习中有看的一些文章
不错文章:http://www.yxfzedu.com/article/670
不错文章:https://iosre.com/t/%E8%AE%B0%E5%BD%95%E4%B8%80%E6%AC%A1%E8%99%9A%E5%B9%BB4ue4%E6%89%8B%E6%B8%B8%E9%80%86%E5%90%91/19808
好文章:http://www.yxfzedu.com/article/10613
<meta
-
data
android:name
=
"com.epicgames.ue4.GameActivity.EngineVersion"
android:value
=
"4.xx.xx"
/
>
<meta
-
data
android:name
=
"com.epicgames.ue4.GameActivity.EngineVersion"
android:value
=
"4.xx.xx"
/
>
.
/
Ue4dumper
-
-
package com.tencent.ace.match2024
-
-
ptrdec
-
-
sdku
-
-
gname
0x0B171CC0
-
-
guobj
0x0B1B5F98
-
-
output
/
data
/
local
/
tmp
-
-
newue
+
.
/
Ue4dumper
-
-
package com.tencent.ace.match2024
-
-
ptrdec
-
-
sdku
-
-
gname
0x0B171CC0
-
-
guobj
0x0B1B5F98
-
-
output
/
data
/
local
/
tmp
-
-
newue
+
function set(moduleName){
//获取libUE4的基地址
moduleBase=Module.findBaseAddress(moduleName);
//UE4基地址加上在IDA中获取的GName偏移获取GName地址
GName=moduleBase.add(GName_Offset);
//同上获取GUObjectArray的地址
GUObjectArray=moduleBase.add(GUObjectArray_Offset);
//读取GWorld的指针
GWorld =moduleBase.add(GWorld_Offset).readPointer();
}
function setPlayerHP(hp=1000000){
//生命值属性的偏移
getPlayAddr().add(0x510).writeFloat(hp);
//写入
}
function set(moduleName){
//获取libUE4的基地址
moduleBase=Module.findBaseAddress(moduleName);
//UE4基地址加上在IDA中获取的GName偏移获取GName地址
GName=moduleBase.add(GName_Offset);
//同上获取GUObjectArray的地址
GUObjectArray=moduleBase.add(GUObjectArray_Offset);
//读取GWorld的指针
GWorld =moduleBase.add(GWorld_Offset).readPointer();
}
function setPlayerHP(hp=1000000){
//生命值属性的偏移
getPlayAddr().add(0x510).writeFloat(hp);
//写入
}
getNameId: function(obj){
console.
log
(
"obj:${obj}"
);
try
{
var nameId=ptr(obj).add(offset_UObject_FNameIndex).readU32();
//读取4字节。
//console.log("nameId:${nameId}");
return
nameId;
}
catch
(e){
console.
log
(
"error"
)
return
0;
}
}
function getFNameFromID(index) {
// FNamePool相关偏移量和步长
var FNameStride = 0x2;
// FNameEntry 的步长,每个FNameEntry占用2字节
var offset_GName_FNamePool = 0x30;
// GName 到 FNamePool 的偏移量
var offset_FNamePool_Blocks = 0x10;
// FNamePool 到 Blocks 的偏移量
// FNameEntry相关偏移量和位
var offset_FNameEntry_Info = 0;
// FNameEntry 到 Info 的偏移量
var FNameEntry_LenBit = 6;
// FNameEntry 长度位
var offset_FNameEntry_String = 0x2;
// FNameEntry 到字符串部分的偏移量
// 计算块和偏移量
var Block = index >> 16;
// 块索引
var Offset = index & 65535;
// 块内偏移量
// 获取FNamePool的起始地址
var FNamePool = GName.add(offset_GName_FNamePool);
// console.log(`FNamePool: ${FNamePool}`);
// console.log(`Block: ${Block}`);
// 获取特定块的地址
var NamePoolChunk = FNamePool.add(offset_FNamePool_Blocks + Block * 8).readPointer();
// console.log(`NamePoolChunk: ${NamePoolChunk}`);
// 计算FNameEntry的地址
var FNameEntry = NamePoolChunk.add(FNameStride * Offset);
// console.log(`FNameEntry: ${FNameEntry}`);
try
{
// 读取FNameEntry的Header
if
(offset_FNameEntry_Info !== 0) {
var FNameEntryHeader = FNameEntry.add(offset_FNameEntry_Info).readU16();
}
else
{
var FNameEntryHeader = FNameEntry.readU16();
}
}
catch
(e) {
// 捕捉读取异常并返回空字符串
// console.log(e);
return
""
;
}
// console.log(`FNameEntryHeader: ${FNameEntryHeader}`);
// 获取字符串地址
var str_addr = FNameEntry.add(offset_FNameEntry_String);
// console.log(`str_addr: ${str_addr}`);
// 计算字符串长度和宽度
var str_length = FNameEntryHeader >> FNameEntry_LenBit;
// 计算字符串长度
var wide = FNameEntryHeader & 1;
// 判断字符串是否为宽字符
// 如果是宽字符,返回 "widestr"
if
(wide)
return
"widestr"
;
// 如果字符串长度合理,读取并返回UTF-8字符串
if
(str_length > 0 && str_length < 250) {
var str = str_addr.readUtf8String(str_length);
return
str;
}
else
{
return
"None"
;
// 长度不合理,返回 "None"
}
}
getNameId: function(obj){
console.
log
(
"obj:${obj}"
);
try
{
var nameId=ptr(obj).add(offset_UObject_FNameIndex).readU32();
//读取4字节。
//console.log("nameId:${nameId}");
return
nameId;
}
catch
(e){
console.
log
(
"error"
)
return
0;
}
}
function getFNameFromID(index) {
// FNamePool相关偏移量和步长
var FNameStride = 0x2;
// FNameEntry 的步长,每个FNameEntry占用2字节
var offset_GName_FNamePool = 0x30;
// GName 到 FNamePool 的偏移量
var offset_FNamePool_Blocks = 0x10;
// FNamePool 到 Blocks 的偏移量
// FNameEntry相关偏移量和位
var offset_FNameEntry_Info = 0;
// FNameEntry 到 Info 的偏移量
var FNameEntry_LenBit = 6;
// FNameEntry 长度位
var offset_FNameEntry_String = 0x2;
// FNameEntry 到字符串部分的偏移量
// 计算块和偏移量
var Block = index >> 16;
// 块索引
var Offset = index & 65535;
// 块内偏移量
// 获取FNamePool的起始地址
var FNamePool = GName.add(offset_GName_FNamePool);
// console.log(`FNamePool: ${FNamePool}`);
// console.log(`Block: ${Block}`);
// 获取特定块的地址
var NamePoolChunk = FNamePool.add(offset_FNamePool_Blocks + Block * 8).readPointer();
// console.log(`NamePoolChunk: ${NamePoolChunk}`);
// 计算FNameEntry的地址
var FNameEntry = NamePoolChunk.add(FNameStride * Offset);
// console.log(`FNameEntry: ${FNameEntry}`);
try
{
// 读取FNameEntry的Header
if
(offset_FNameEntry_Info !== 0) {
var FNameEntryHeader = FNameEntry.add(offset_FNameEntry_Info).readU16();
}
else
{
var FNameEntryHeader = FNameEntry.readU16();
}
}
catch
(e) {
// 捕捉读取异常并返回空字符串
// console.log(e);
return
""
;
}
// console.log(`FNameEntryHeader: ${FNameEntryHeader}`);
// 获取字符串地址
var str_addr = FNameEntry.add(offset_FNameEntry_String);
// console.log(`str_addr: ${str_addr}`);
// 计算字符串长度和宽度
var str_length = FNameEntryHeader >> FNameEntry_LenBit;
// 计算字符串长度
var wide = FNameEntryHeader & 1;
// 判断字符串是否为宽字符
// 如果是宽字符,返回 "widestr"
if
(wide)
return
"widestr"
;
// 如果字符串长度合理,读取并返回UTF-8字符串
if
(str_length > 0 && str_length < 250) {
var str = str_addr.readUtf8String(str_length);
return
str;
}
else
{
return
"None"
;
// 长度不合理,返回 "None"
}
}
function getActorsAddr(){
var Level_Offset=0x30
//偏移
var Actors_Offset=0x98
var Level=GWorld.add(Level_Offset).readPointer()
//读取GWorld的level指针
var Actors=Level.add(Actors_Offset).readPointer()
//读取Actors的指针
var Actors_Num=Level.add(Actors_Offset).add(8).readU32()
//获取Actor的数量
var actorsAddr={};
//空对象,下面的实现类似字典
for
(var index=0;index<Actors_Num;index++)
{
var actor_addr=Actors.add(index*8).readPointer()
//读取当前索引处的Actor地址
var actorName=UObject.getName(actor_addr)
//通过地址获取字符串名字
actorsAddr[actorName]=actor_addr;
//以字符串名字对应地址值
//console.log(`actors[${index}]`,actorName);
}
return
actorsAddr;
}
function getActorsAddr(){
var Level_Offset=0x30
//偏移
var Actors_Offset=0x98
var Level=GWorld.add(Level_Offset).readPointer()
//读取GWorld的level指针
var Actors=Level.add(Actors_Offset).readPointer()
//读取Actors的指针
var Actors_Num=Level.add(Actors_Offset).add(8).readU32()
//获取Actor的数量
var actorsAddr={};
//空对象,下面的实现类似字典
for
(var index=0;index<Actors_Num;index++)
{
var actor_addr=Actors.add(index*8).readPointer()
//读取当前索引处的Actor地址
var actorName=UObject.getName(actor_addr)
//通过地址获取字符串名字
actorsAddr[actorName]=actor_addr;
//以字符串名字对应地址值
//console.log(`actors[${index}]`,actorName);
}
return
actorsAddr;
}
function getActorAddr(str){
//根据对象名字获取对象地址
var player_addr;
var actorsAddr=getActorsAddr();
for
(var key in actorsAddr){
if
(key==str){
//console.log(actorsAddr[key]);
player_addr=actorsAddr[key];
}
}
if
(player_addr==null)
{
console.
log
(
"null pointer!"
);
}
return
player_addr;
}
function setPlayerHP(hp=1000000){
//生命值属性的偏移
getAcotrAddr(playerName).add(0x510).writeFloat(hp);
//通过偏移定位到生命值的变量并写入值
}
function getActorAddr(str){
//根据对象名字获取对象地址
var player_addr;
var actorsAddr=getActorsAddr();
for
(var key in actorsAddr){
if
(key==str){
//console.log(actorsAddr[key]);
player_addr=actorsAddr[key];
}
}
if
(player_addr==null)
{
console.
log
(
"null pointer!"
);
}
return
player_addr;
}
function setPlayerHP(hp=1000000){
//生命值属性的偏移
getAcotrAddr(playerName).add(0x510).writeFloat(hp);
//通过偏移定位到生命值的变量并写入值
}
//main:
set(
"libUE4.so"
)
setPlayerHP();
//main:
set(
"libUE4.so"
)
setPlayerHP();
function
getActorLocation(actor_addr){
GWorld = moduleBase.add(GWorld_Offset).readPointer();
actor_addr = ptr(actor_addr)
var
buf = Memory.alloc(0x100);
var
f_addr = moduleBase.add(0x965ddf8);
//这个地址可在dump下的SDK文件里找到
// 将目标函数地址转换为JavaScript函数
var
getLocationFunc =
new
NativeFunction(f_addr,
'void'
, [
'pointer'
,
'pointer'
,
'pointer'
]);
// 调用目标函数并传递内存地址作为参数
try
{
getLocationFunc(actor_addr,buf,buf);
dumpVector(buf);
//info(ptr(actor_addr).add(0x130).readPointer().add(0x14c).readU8()&32 != 0);
}
catch
(e){
}
}
function
getActorLocation(actor_addr){
GWorld = moduleBase.add(GWorld_Offset).readPointer();
actor_addr = ptr(actor_addr)
var
buf = Memory.alloc(0x100);
var
f_addr = moduleBase.add(0x965ddf8);
//这个地址可在dump下的SDK文件里找到
// 将目标函数地址转换为JavaScript函数
var
getLocationFunc =
new
NativeFunction(f_addr,
'void'
, [
'pointer'
,
'pointer'
,
'pointer'
]);
// 调用目标函数并传递内存地址作为参数
try
{
getLocationFunc(actor_addr,buf,buf);
dumpVector(buf);
//info(ptr(actor_addr).add(0x130).readPointer().add(0x14c).readU8()&32 != 0);
}
catch
(e){
}
}
function
setActorLocation(actor_addr,x,y,z){
GWorld = moduleBase.add(GWorld_Ptr_Offset).readPointer();
actor_addr = ptr(actor_addr)
var
f_addr = moduleBase.add(0x8C3181C);
//加上偏移获取目标函数的偏移
// 将目标函数地址转换为JavaScript函数
var
setLocationFunc =
new
NativeFunction(f_addr,
'bool'
, [
'pointer'
,
'bool'
,
'pointer'
,
'bool'
,
'float'
,
'float'
,
'float'
]);
// 调用目标函数并传递内存地址作为参数
setLocationFunc(actor_addr,0,ptr(0),0,x,y,z);
//dumpVector(buf);
}
function
setActorLocation(actor_addr,x,y,z){
GWorld = moduleBase.add(GWorld_Ptr_Offset).readPointer();
actor_addr = ptr(actor_addr)
var
f_addr = moduleBase.add(0x8C3181C);
//加上偏移获取目标函数的偏移
// 将目标函数地址转换为JavaScript函数
var
setLocationFunc =
new
NativeFunction(f_addr,
'bool'
, [
'pointer'
,
'bool'
,
'pointer'
,
'bool'
,
'float'
,
'float'
,
'float'
]);
// 调用目标函数并传递内存地址作为参数
setLocationFunc(actor_addr,0,ptr(0),0,x,y,z);
//dumpVector(buf);
}
/
/
显示
/
/
void SetVisibility(
bool
bNewVisibility,
bool
bPropagateToChildren);
function SetVisibility(Component,bNewVisibility,bPropagateToChildren)
{
var pSetVisibility
=
moduleBase.add(
0x8E619BC
);
var callSetVisibility
=
new NativeFunction(pSetVisibility,
"void"
,[
'pointer'
,
'int'
,
'int'
]);
callSetVisibility(ptr(Component).add(
0x130
).readPointer(),bNewVisibility,bPropagateToChildren);
}
/
/
显示
/
/
void SetVisibility(
bool
bNewVisibility,
bool
bPropagateToChildren);
function SetVisibility(Component,bNewVisibility,bPropagateToChildren)
{
var pSetVisibility
=
moduleBase.add(
0x8E619BC
);
var callSetVisibility
=
new NativeFunction(pSetVisibility,
"void"
,[
'pointer'
,
'int'
,
'int'
]);
callSetVisibility(ptr(Component).add(
0x130
).readPointer(),bNewVisibility,bPropagateToChildren);
}
/
/
碰撞
function setActorEnableCollision(actor_addr,bNewActorEnableCollision
=
1
){
var f_addr
=
moduleBase.add(
0x8C21320
);
let CallFunc
=
new NativeFunction(f_addr,
'void'
, [
'pointer'
,
'char'
]);
CallFunc(ptr(actor_addr),bNewActorEnableCollision);
}
/
/
碰撞
function setActorEnableCollision(actor_addr,bNewActorEnableCollision
=
1
){
var f_addr
=
moduleBase.add(
0x8C21320
);
let CallFunc
=
new NativeFunction(f_addr,
'void'
, [
'pointer'
,
'char'
]);
CallFunc(ptr(actor_addr),bNewActorEnableCollision);
}
function setStaticMeshActorCollisionEnabled(actor_addr,NewType
=
3
){
actor_addr
=
ptr(actor_addr)
var f_addr
=
actor_addr.add(
0x220
).readPointer().readPointer().add(
0x660
).readPointer();
var getActorCollisionEnabled
=
new NativeFunction(f_addr,
'char'
, [
'pointer'
,
'char'
]);
let ret
=
getActorCollisionEnabled(actor_addr.add(
0x220
).readPointer(),NewType);
info(ret);
}
function setStaticMeshActorCollisionEnabled(actor_addr,NewType
=
3
){
actor_addr
=
ptr(actor_addr)
var f_addr
=
actor_addr.add(
0x220
).readPointer().readPointer().add(
0x660
).readPointer();
var getActorCollisionEnabled
=
new NativeFunction(f_addr,
'char'
, [
'pointer'
,
'char'
]);
let ret
=
getActorCollisionEnabled(actor_addr.add(
0x220
).readPointer(),NewType);
info(ret);
}
function SetCollisionEnable(actor_addr,dr
=
3
) {
var f_addr
=
moduleBase.add(
0x933b300
);
let CallFunc
=
new NativeFunction(f_addr,
'void'
,[
'pointer'
,
'int'
]);
CallFunc(ptr(actor_addr).add(
0x130
).readPointer(),dr);
}
function SetCollisionEnable(actor_addr,dr
=
3
) {
var f_addr
=
moduleBase.add(
0x933b300
);
let CallFunc
=
new NativeFunction(f_addr,
'void'
,[
'pointer'
,
'int'
]);
CallFunc(ptr(actor_addr).add(
0x130
).readPointer(),dr);
}
adb shell am start
-
D
-
n com.tencent.ace.match2024
/
com.epicgames.ue4.SplashActivity
adb shell am start
-
D
-
n com.tencent.ace.match2024
/
com.epicgames.ue4.SplashActivity
#include<stdio.h>
int
main()
{
char encode[]
=
{
0x55
,
0x4d
,
0x60
,
0x74
,
0x38
,
0x49
,
0x64
,
0x50
,
0x2c
,
0x7b
,
0x4f
,
0x03
,
0x68
,
0x36
,
0x1f
,
0x9f
,
0x8e
,
0x8a
,
0
,
0
};
char key[]
=
{
0x0A
,
0x0C
,
0x0E
,
0x00
,
0x51
,
0x16
,
0x27
,
0x38
,
0x49
,
0x1A
,
0x3B
,
0x5C
,
0x2D
,
0x4E
,
0x6F
,
0xFA
,
0xFC
,
0xFE
};
for
(
int
i
=
0
; i <
18
; i
+
+
)
{
encode[i] ^
=
key[i ];
printf(
"%c"
, encode[i]);
}
}
#include<stdio.h>
int
main()
{
char encode[]
=
{
0x55
,
0x4d
,
0x60
,
0x74
,
0x38
,
0x49
,
0x64
,
0x50
,
0x2c
,
0x7b
,
0x4f
,
0x03
,
0x68
,
0x36
,
0x1f
,
0x9f
,
0x8e
,
0x8a
,
0
,
0
};
char key[]
=
{
0x0A
,
0x0C
,
0x0E
,
0x00
,
0x51
,
0x16
,
0x27
,
0x38
,
0x49
,
0x1A
,
0x3B
,
0x5C
,
0x2D
,
0x4E
,
0x6F
,
0xFA
,
0xFC
,
0xFE
};
for
(
int
i
=
0
; i <
18
; i
+
+
)
{
encode[i] ^
=
key[i ];
printf(
"%c"
, encode[i]);
}
}
var GWorld_Offset
=
0x0B32D8A8
var GName_Offset
=
0x0B171CC0
var GUObjectArray_Offset
=
0xB1B5F98
var playerName
=
"FirstPersonCharacter_C"
var moduleBase
var GWorld
var GName
var GUObjectArray
/
/
Class: UObject
/
/
对象的内部索引,用于唯一标识对象。
var offset_UObject_InternalIndex
=
0xC
;
/
/
指向描述对象类的 UClass 对象
var offset_UObject_ClassPrivate
=
0x10
;
/
/
对象名称在 FName 表中的索引
var offset_UObject_FNameIndex
=
0x18
;
/
/
指向包含该对象的外部对象,表示层次关系。
var offset_UObject_OuterPrivate
=
0x20
;
var UObject
=
{
getClass: function(obj){
var classPrivate
=
ptr(obj).add(offset_UObject_ClassPrivate).readPointer();
/
/
读取指针
/
/
console.log(`classPrivate: ${classPrivate}`);
return
classPrivate;
},
getNameId: function(obj){
/
/
console.log(`obj:${obj}`);
try
{
var nameId
=
ptr(obj).add(offset_UObject_FNameIndex).readU32();
/
/
读取
4
字节。
/
/
console.log(`nameId:${nameId}`);
return
nameId;
}catch(e){
console.log(
"error"
)
return
0
;
}
},
getName: function(obj) {
if
(this.isValid(obj)){
return
getFNameFromID(this.getNameId(obj));
}
else
{
return
"None"
;
}
},
getClassName: function(obj) {
if
(this.isValid(obj)) {
var classPrivate
=
this.getClass(obj);
return
this.getName(classPrivate);
}
else
{
return
"None"
;
}
},
isValid: function(obj) {
var isValid
=
(ptr(obj) >
0
&& this.getNameId(obj) >
0
&& this.getClass(obj) >
0
);
/
/
console.log(`isValid: ${isValid}`);
return
isValid;
}
}
function getFNameFromID(index) {
/
/
FNamePool相关偏移量和步长
var FNameStride
=
0x2
;
/
/
FNameEntry 的步长,每个FNameEntry占用
2
字节
var offset_GName_FNamePool
=
0x30
;
/
/
GName 到 FNamePool 的偏移量
var offset_FNamePool_Blocks
=
0x10
;
/
/
FNamePool 到 Blocks 的偏移量
/
/
FNameEntry相关偏移量和位
var offset_FNameEntry_Info
=
0
;
/
/
FNameEntry 到 Info 的偏移量
var FNameEntry_LenBit
=
6
;
/
/
FNameEntry 长度位
var offset_FNameEntry_String
=
0x2
;
/
/
FNameEntry 到字符串部分的偏移量
/
/
计算块和偏移量
var Block
=
index >>
16
;
/
/
块索引
var Offset
=
index &
65535
;
/
/
块内偏移量
/
/
获取FNamePool的起始地址
var FNamePool
=
GName.add(offset_GName_FNamePool);
/
/
console.log(`FNamePool: ${FNamePool}`);
/
/
console.log(`Block: ${Block}`);
/
/
获取特定块的地址
var NamePoolChunk
=
FNamePool.add(offset_FNamePool_Blocks
+
Block
*
8
).readPointer();
/
/
console.log(`NamePoolChunk: ${NamePoolChunk}`);
/
/
计算FNameEntry的地址
var FNameEntry
=
NamePoolChunk.add(FNameStride
*
Offset);
/
/
console.log(`FNameEntry: ${FNameEntry}`);
try
{
/
/
读取FNameEntry的Header
if
(offset_FNameEntry_Info !
=
=
0
) {
var FNameEntryHeader
=
FNameEntry.add(offset_FNameEntry_Info).readU16();
}
else
{
var FNameEntryHeader
=
FNameEntry.readU16();
}
} catch(e) {
/
/
捕捉读取异常并返回空字符串
/
/
console.log(e);
return
"";
}
/
/
console.log(`FNameEntryHeader: ${FNameEntryHeader}`);
/
/
获取字符串地址
var str_addr
=
FNameEntry.add(offset_FNameEntry_String);
/
/
console.log(`str_addr: ${str_addr}`);
/
/
计算字符串长度和宽度
var str_length
=
FNameEntryHeader >> FNameEntry_LenBit;
/
/
计算字符串长度
var wide
=
FNameEntryHeader &
1
;
/
/
判断字符串是否为宽字符
/
/
如果是宽字符,返回
"widestr"
if
(wide)
return
"widestr"
;
/
/
如果字符串长度合理,读取并返回UTF
-
8
字符串
if
(str_length >
0
&& str_length <
250
) {
var
str
=
str_addr.readUtf8String(str_length);
return
str
;
}
else
{
return
"None"
;
/
/
长度不合理,返回
"None"
}
}
/
/
获取对象实例
function getActorAddr(
str
){
var player_addr;
var actorsAddr
=
getActorsAddr();
for
(var key
in
actorsAddr){
if
(key
=
=
str
){
console.log(actorsAddr[key]);
player_addr
=
actorsAddr[key];
}
}
if
(player_addr
=
=
null)
{
console.log(
"null pointer!"
);
}
return
player_addr;
}
function getActorsAddr(){
var Level_Offset
=
0x30
/
/
偏移
var Actors_Offset
=
0x98
var Level
=
GWorld.add(Level_Offset).readPointer()
/
/
读取GWorld的level指针
var Actors
=
Level.add(Actors_Offset).readPointer()
/
/
读取Actors的指针
var Actors_Num
=
Level.add(Actors_Offset).add(
8
).readU32()
/
/
获取Actor的数量
var actorsAddr
=
{};
/
/
空对象,下面的实现类似字典
for
(var index
=
0
;index<Actors_Num;index
+
+
)
{
var actor_addr
=
Actors.add(index
*
8
).readPointer()
/
/
读取当前索引处的Actor地址
var actorName
=
UObject.getName(actor_addr)
/
/
通过地址获取字符串名字
actorsAddr[actorName]
=
actor_addr;
/
/
以字符串名字对应地址值
/
/
console.log(`actors[${index}]`,actorName);
}
return
actorsAddr;
}
function
set
(moduleName){
/
/
获取libUE4的基地址
moduleBase
=
Module.findBaseAddress(moduleName);
/
/
UE4基地址加上在IDA中获取的GName偏移获取GName地址
GName
=
moduleBase.add(GName_Offset);
/
/
同上获取GUObjectArray的地址
GUObjectArray
=
moduleBase.add(GUObjectArray_Offset);
/
/
读取GWorld的指针
GWorld
=
moduleBase.add(GWorld_Offset).readPointer();
}
function setPlayerHP(hp
=
1000000
){
/
/
生命值属性的偏移
getActorAddr(playerName).add(
0x510
).writeFloat(hp);
/
/
写入
}
/
/
瞬移
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
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){
/
/
dumpAddr(
'firstPersion_RootComponent'
,firstPersion_RootComponent_ptr,
0x152
)
/
/
从地址空间中读取三个浮点数
const values
=
Memory.readByteArray(addr,
3
*
4
);
/
/
3
个
float
共占
12
个字节
/
/
解析浮点数并初始化 Vector 对象
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 getActorLocation(actor_addr){
GWorld
=
moduleBase.add(GWorld_Offset).readPointer();
actor_addr
=
ptr(actor_addr)
var buf
=
Memory.alloc(
0x100
);
var f_addr
=
moduleBase.add(
0x965ddf8
);
/
/
将目标函数地址转换为JavaScript函数
var getLocationFunc
=
new NativeFunction(f_addr,
'void'
, [
'pointer'
,
'pointer'
,
'pointer'
]);
/
/
调用目标函数并传递内存地址作为参数
try
{
getLocationFunc(actor_addr,buf,buf);
dumpVector(buf);
return
buf;
/
/
info(ptr(actor_addr).add(
0x130
).readPointer().add(
0x14c
).readU8()&
32
!
=
0
);
}
catch (e){
}
}
/
/
965dc3c
function setActorLocation(actor_addr,x,y,z){
GWorld
=
moduleBase.add(GWorld_Offset ).readPointer();
actor_addr
=
ptr(actor_addr)
var f_addr
=
moduleBase.add(
0x8C3181C
);
/
/
加上偏移获取目标函数的偏移
/
/
将目标函数地址转换为JavaScript函数
var setLocationFunc
=
new NativeFunction(f_addr,
'bool'
, [
'pointer'
,
'bool'
,
'pointer'
,
'bool'
,
'float'
,
'float'
,
'float'
]);
/
/
调用目标函数并传递内存地址作为参数
setLocationFunc(actor_addr,
0
,ptr(
0
),
0
,x,y,z);
/
/
dumpVector(buf);
}
/
/
/
/
显示
/
/
void SetVisibility(
bool
bNewVisibility,
bool
bPropagateToChildren);
function SetVisibility(Component,bNewVisibility,bPropagateToChildren)
{
var pSetVisibility
=
moduleBase.add(
0x8E619BC
);
var callSetVisibility
=
new NativeFunction(pSetVisibility,
"void"
,[
'pointer'
,
'int'
,
'int'
]);
callSetVisibility(ptr(Component).add(
0x130
).readPointer(),bNewVisibility,bPropagateToChildren);
}
/
/
/
/
碰撞函数
function setActorEnableCollision(actor_addr,bNewActorEnableCollision
=
1
){
var f_addr
=
moduleBase.add(
0x8C21320
);
let CallFunc
=
new NativeFunction(f_addr,
'void'
, [
'pointer'
,
'char'
]);
CallFunc(ptr(actor_addr),bNewActorEnableCollision);
}
function setStaticMeshActorCollisionEnabled(actor_addr,NewType
=
3
){
actor_addr
=
ptr(actor_addr)
var f_addr
=
actor_addr.add(
0x220
).readPointer().readPointer().add(
0x660
).readPointer();
/
/
这段似乎进行了一个读取虚函数表的操作
var getActorCollisionEnabled
=
new NativeFunction(f_addr,
'char'
, [
'pointer'
,
'char'
]);
let ret
=
getActorCollisionEnabled(actor_addr.add(
0x220
).readPointer(),NewType);
console.log(
"未知函数:"
,ret);
}
function SetCollisionEnable(actor_addr,dr
=
3
) {
var f_addr
=
moduleBase.add(
0x933b300
);
let CallFunc
=
new NativeFunction(f_addr,
'void'
,[
'pointer'
,
'int'
]);
CallFunc(ptr(actor_addr).add(
0x130
).readPointer(),dr);
}
/
/
获取虚表指针
async function GetActorVrtualAdress() {
try
{
moduleBase
=
Module.findBaseAddress(
"libUE4.so"
);
console.log(
"[+] moduleBase:"
, moduleBase);
console.log(
"[+] GWorld_Offset:"
, GWorld_Offset);
GWorld
=
moduleBase.add(GWorld_Offset).readPointer();
console.log(
"[+] GWorld:"
, GWorld);
var Level_Offset
=
0x30
;
var Actors_Offset
=
0x98
;
var Level
=
GWorld.add(Level_Offset).readPointer();
console.log(
"[+] Level Address:"
, Level);
var Actors
=
Level.add(Actors_Offset).readPointer();
console.log(
"[+] Actors Address:"
, Actors);
var Actors_Num1
=
Level.add(Actors_Offset).add(
8
).readU32();
console.log(
"[+] ActorNum:"
, ptr(Actors_Num1));
/
/
使用循环遍历每个 Actor
for
(var index
=
0
; index <Actors_Num1; index
+
+
) {
try
{ console.log(
"[+] Number:"
, index);
var actor_addr
=
Actors.add(index
*
8
).readPointer();
if
(actor_addr.isNull()) {
console.error(`Error: Actor address at index ${index}
is
null`);
continue
;
}
var ActorClass
=
UObject.getClassName(actor_addr);
var vtable_addr
=
actor_addr.readPointer();
console.log(`[
+
] Actor Class: ${ActorClass}`);
console.log(`[
+
] Actor Address: ${actor_addr}`);
console.log(`[
+
] Virtual Address:
0x
${(vtable_addr.sub(moduleBase)).toString(
16
)}`);
}catch(h){
console.error(
"Error occurred:1"
, h);
}
}
} catch (e) {
console.error(
"Error occurred:2"
, e);
}
}
(async ()
=
> {
await GetActorVrtualAdress();
})();
/
/
主要实现作弊的hanshu
function dumpActorInstances(){
GWorld
=
moduleBase.add(GWorld_Offset).readPointer();
/
/
找到GWorld这个类并储存它的地址作为指针
var Level_Offset
=
0x30
var Actors_Offset
=
0x98
var Level
=
GWorld.add(Level_Offset).readPointer()
/
/
关卡的指针
var Actors
=
Level.add(Actors_Offset).readPointer()
/
/
Actor的指针
var Actors_Num
=
Level.add(Actors_Offset).add(
8
).readU32()
/
/
Actors的数量
var actorsInstances
=
{};
for
(var index
=
0
; index < Actors_Num; index
+
+
){
var actor_addr
=
Actors.add(index
*
8
).readPointer();
var ActorName
=
UObject.getName(actor_addr)
actorsInstances[index]
=
ActorName;
/
/
console.log(`actors[${index}]:${actor_addr}`,actorName);
if
(ActorName.includes(
"Cube"
))
{
/
/
设置为可碰撞
try
{
console.log(
'[+] ActorName:'
, ActorName);
/
/
setStaticMeshActorCollisionEnabled(actor_addr,
1
);
setStaticMeshActorCollisionEnabled(actor_addr,
3
);
/
/
SetCollisionEnable(actor_addr,
3
);
}
catch(gg){}
/
/
getActorLocation(actor_addr);
}
var ccc
=
getActorLocation(actor_addr)
const values
=
Memory.readByteArray(ccc,
3
*
4
);
var z
=
new Float32Array(values,
8
,
1
)[
0
];
/
/
高度,Section1
if
(z>
3000
)
/
/
高度大于
3000
则执行
{
try
{
/
/
setActorHidden(actor_addr)
/
/
setActorCollisionEnabled(actor_addr,
1
)
SetVisibility(actor_addr,
1
,
0
);
}
catch(e){}
}
}
}
set
(
"libUE4.so"
)
setPlayerHP();
/
/
GetActorVrtualAdress();
var b
=
getActorAddr(playerName);
/
/
getActorLocation(b);
/
/
getActorLocation(b);
dumpActorInstances();
/
/
(
-
1569.0198974609375
,
-
415.2847595214844
,
268.3680725097656
)
/
/
setActorLocation(b,
-
1500
,
-
400
,
3000
);
/
/
下面这一句代码是指定要Hook的so文件名和要Hook的函数名,函数名就是上面IDA导出表中显示的那个函数
var GWorld_Offset
=
0x0B32D8A8
var GName_Offset
=
0x0B171CC0
var GUObjectArray_Offset
=
0xB1B5F98
var playerName
=
"FirstPersonCharacter_C"
var moduleBase
var GWorld
var GName
var GUObjectArray
/
/
Class: UObject
/
/
对象的内部索引,用于唯一标识对象。
var offset_UObject_InternalIndex
=
0xC
;
/
/
指向描述对象类的 UClass 对象
var offset_UObject_ClassPrivate
=
0x10
;
/
/
对象名称在 FName 表中的索引
var offset_UObject_FNameIndex
=
0x18
;
/
/
指向包含该对象的外部对象,表示层次关系。
var offset_UObject_OuterPrivate
=
0x20
;
var UObject
=
{
getClass: function(obj){
var classPrivate
=
ptr(obj).add(offset_UObject_ClassPrivate).readPointer();
/
/
读取指针
/
/
console.log(`classPrivate: ${classPrivate}`);
return
classPrivate;
},
getNameId: function(obj){
/
/
console.log(`obj:${obj}`);
try
{
var nameId
=
ptr(obj).add(offset_UObject_FNameIndex).readU32();
/
/
读取
4
字节。
/
/
console.log(`nameId:${nameId}`);
return
nameId;
}catch(e){
console.log(
"error"
)
return
0
;
}
},
getName: function(obj) {
if
(this.isValid(obj)){
return
getFNameFromID(this.getNameId(obj));
}
else
{
return
"None"
;
}
},
getClassName: function(obj) {
if
(this.isValid(obj)) {
var classPrivate
=
this.getClass(obj);
return
this.getName(classPrivate);
}
else
{
return
"None"
;
}
},
isValid: function(obj) {
var isValid
=
(ptr(obj) >
0
&& this.getNameId(obj) >
0
&& this.getClass(obj) >
0
);
/
/
console.log(`isValid: ${isValid}`);
return
isValid;
}
}
function getFNameFromID(index) {
/
/
FNamePool相关偏移量和步长
var FNameStride
=
0x2
;
/
/
FNameEntry 的步长,每个FNameEntry占用
2
字节
var offset_GName_FNamePool
=
0x30
;
/
/
GName 到 FNamePool 的偏移量
var offset_FNamePool_Blocks
=
0x10
;
/
/
FNamePool 到 Blocks 的偏移量
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
赞赏
- [原创]zygisk改机模块demo 2662
- [原创]ByteCTF逆向WP 6291
- [原创]春秋杯Re2024WP 7001
- 腾讯游戏安全2024安卓初赛复现 27453