首页
社区
课程
招聘
[原创] 腾讯游戏安全技术竞赛2024决赛 安卓方向解题报告
2024-4-22 08:59 3856

[原创] 腾讯游戏安全技术竞赛2024决赛 安卓方向解题报告

2024-4-22 08:59
3856

赛题信息

图片描述

决赛解题情况

3种方式实现透视,2种方式实现自瞄,绕过反frida检测,密码校验算法的混淆大部分被还原

Section0

frida-server-arm没法注入,我采用的方案是修改重打包apk在启动时机加载frida-gadget来使用frida

这个输入框和登入按钮是UE4中的组件,需要先DumpSDK再进行逆向
把初赛写的sdkdumper稍作修改的同时观测游戏内存,得到以下UE4关键偏移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
GName 4DC61A8
GObject 4E533AC
ChunkSize 10000
GWorld 4F5C0D0
 
classobj
+0x40 super
+0x6C funcs
+0x44 fields
 
funcobj
+0x2C nextfunc
+0x44 param
+0xA4 funcaddr
 
fieldsobj
+0x10 next (ptr)
+0x14 name (int)
+0x24 size (int)
+0x34 offset (int)
+0x50 typeclass (ptr)

根据以上信息Dump出SDK。通过观测 Class和Actors 发现可疑对象 UMyBlueprintFunctionCheckPass的函数CheckPassWordInc是校验输入的password用的,其他两个是反调试

1
2
3
4
5
6
7
8
9
// Size: 0x0 (Inherited: 0x0)
struct UMyBlueprintFunctionCheckPass : public UBlueprintFunctionLibrary
{
 
    void start2();// Function /Script/acegamefs.MyBlueprintFunctionCheckPass.start2 rva 0x19f6354
    void start1();// Function /Script/acegamefs.MyBlueprintFunctionCheckPass.start1 rva 0x19f637c
    void CheckPassWordInc();// Function /Script/acegamefs.MyBlueprintFunctionCheckPass.CheckPassWordInc rva 0x19f63a4
 
};

先给出跳过登入验证的脚本,方便Section1的进行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var target19f4304 = baseAddress.add(0x19f4304);
    Interceptor.attach(target19f4304, {
        onEnter: function(args) {
            console.log("Called CheckPassWordInc");
            console.log("R0 : " + args[0].toString(16));
             
            // while(1);
     
        },
        onLeave: function(retval) {
            console.log("CheckPassWordInc returns");
            retval.replace(1);
        }
    });

跟进发现函数 sub_223c524(r5_1, r0_6); 是CheckPassword算法实现的函数 第一个参数是password字符串,第二个参数是返回数据,用的Thumb2指令集

去混淆思路

跟进函数发现有分支混淆,都是类似于这种的条件分支混淆

1
2
3
4
5
6
7
.text:046D1862 4F F4 40 72                   MOV.W           R2, #0x300
.text:046D1866 07 2B                         CMP             R3, #7
.text:046D1868 08 BF                         IT EQ
.text:046D186A 4F F4 35 72                   MOVEQ.W         R2, #0x2D4
.text:046D186E 8A 58                         LDR             R2, [R1,R2]
.text:046D1870 72 44                         ADD             R2, LR
.text:046D1872 97 46                         MOV             PC, R2

这种混淆的特征是有一个IT指令作为起始, 后面必定跟着一个 MOV PC, XX 来跳转, 但是有很多不确定的常数参与进运算, 不能做静态去混淆

我的思路是用Unicorn模拟执行CheckPassword函数, 跟踪 IT CC 类型的指令的执行并保存快照, 跟踪到执行MOV PC, XX 之后拿到PC寄存器的指针记录一个分支的目的地址, 之后还原快照并根据指令的条件修改CPSR使得分支指令执行结果与之前的相反, 再走到 MOV PC, XX 之后拿到PC寄存器的指针记录第二个分支的目的地址

由于有些分支是基于上一条分支被执行的前提下才会可能被执行到, 我的处理思路是用BFS走完未执行的分支(如果运行出错则放弃继续执行该分支), 尽量可能多的覆盖所有分支

最后打印出分支对应的跳转地址(满足条件和未满足条件的地址), 用Capstone根据分支的条件Keystone编译出IDA容易识别的条件跳转代码替换原有代码

去混淆实现

先Dump出执行环境(寄存器,堆栈,模块)
用这个frida脚本在执行函数的时候陷入死循环并打印出寄存器,并干掉反调试函数,随后用 CheatEngine Dump出内存

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
var libUE4Base = Module.findBaseAddress('libUE4.so');
console.log('libUE4.so base address: ' + libUE4Base.toString(16));
 
var libcBase = Module.findBaseAddress('libc.so');
console.log('libc.so base address: ' + libcBase.toString(16));
 
var targetAddress2 = libUE4Base.add(0x223c534);
Interceptor.replace(targetAddress2, new NativeCallback(function () {
        console.log("start1 has been called");
        return 0;
}, 'int', []));
 
var targetAddress3 = libUE4Base.add(0x223c544);
Interceptor.replace(targetAddress3, new NativeCallback(function () {
        console.log("start2 has been called");
        return 1;
}, 'int', []));
 
const moduleName = "libUE4.so";
 
var offsets = [
0x223c524
]
 
offsets.forEach(function(offset) {
    var funcAddress = Module.findBaseAddress(moduleName).add(offset);
    console.log("Hooking address at: " + funcAddress);
     
    Interceptor.attach(funcAddress, {
        onEnter: function(args) {
            console.log(`offset 0x${offset.toString(16)} exec`);
            console.log("Registers: " + JSON.stringify(this.context));
            while(1);
        },
        onLeave: function(retval) {
             
        },
    });
});
 
console.log("Script loaded.");

模拟脚本: (由于太长,放在了解题报告文件夹的"模拟执行脚本"目录下)

Keystone重新编译分支指令脚本:

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
from capstone import *
from capstone.arm import *
from keystone import *
import json
 
patches = []
 
with open("B4846000-B97F44A4.CEM","rb") as fp:
    code_data = fp.read()
 
with open("patchloc.txt","r") as fp:
    for line in fp.read().splitlines():
        EA = int(line[line.find('EA = 0x')+len('EA = 0x'):line.find('EA = 0x')+len('EA = 0x')+7],16)
        B1 = int(line[line.find('B1(True) = 0x')+len('B1(True) = 0x'):line.find('B1(True) = 0x')+len('B1(True) = 0x')+7],16)
        B2 = int(line[line.find('B2(False) = 0x')+len('B2(False) = 0x'):line.find('B2(False) = 0x')+len('B2(False) = 0x')+7],16)
        patches.append((EA,B1,B2))
 
cc_map = {
    ARM_CC_EQ: 'eq',  # 相等
    ARM_CC_NE: 'ne',  # 不相等
    ARM_CC_HS: 'hs',  # 无符号高或相同 (又名cs)
    ARM_CC_LO: 'lo',  # 无符号低
    ARM_CC_MI: 'mi',  # 负
    ARM_CC_PL: 'pl',  # 正或零
    ARM_CC_VS: 'vs',  # 溢出
    ARM_CC_VC: 'vc',  # 未溢出
    ARM_CC_HI: 'hi',  # 无符号高
    ARM_CC_LS: 'ls',  # 无符号低或相同
    ARM_CC_GE: 'ge',  # 有符号大于或等于
    ARM_CC_LT: 'lt',  # 有符号小于
    ARM_CC_GT: 'gt',  # 有符号高于
    ARM_CC_LE: 'le',  # 有符号低于或等于
    ARM_CC_AL: 'al',  # 总是
}
 
cs = Cs(CS_ARCH_ARM, CS_MODE_THUMB)
cs.detail = True
ks = Ks(KS_ARCH_ARM, KS_MODE_THUMB)
 
patchbytes = []
 
for p in patches:
    EA = p[0]
    B1 = p[1]
    B2 = p[2]
    insn = next(cs.disasm(code_data[EA:EA+10],EA))
    CC = cc_map[insn.cc]
    CODE = f"b{CC} #{hex(B1)}"
 
    INSN1_DATA, INST1_SIZE = ks.asm(CODE,EA)
    NEXT_EA = EA + len(INSN1_DATA)
 
    CODE = f"b #{hex(B2)}"
    INSN2_DATA, INST2_SIZE = ks.asm(CODE,NEXT_EA)
    patchbytes.append((EA,INSN1_DATA))
    patchbytes.append((NEXT_EA,INSN2_DATA))
 
print(json.dumps(patchbytes))

patch 二进制脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import mmap
import os
 
# ELF文件路径
elf_file_path = "libUE4_patched.so"
 
# 给定地址和要写入的字节值
patches = [[74252284, [17, 240, 171, 172]],......]
 
with open(elf_file_path, "r+b") as f: 
    mm = mmap.mmap(f.fileno(), 0)
    for address, bytes_to_patch in patches:
        for i, byte in enumerate(bytes_to_patch):
            mm[offset + i] = byte
    mm.flush()
 
    mm.close()
print("Patching complete.")

IDA打开patch后的文件,看到代码大部分逻辑被还原,有些分支不是这种特征,手动patch一下就行

虚拟机逆向

libUE4.so+46CFFAC 是进入虚拟机的函数,去混淆后逻辑能被IDA正常识别
函数调用混淆都能通过unicorn跟踪发现真实地址
通过unicorn跟踪发现 0x46d27bd 函数是获取指令比特位返回记作getbit ,类似这样的函数调用就是 getbit

1
2
3
4
5
v240 = ((int (__fastcall *)(int *, unsigned int, int, int))(*(_DWORD *)(v98 + v14 + 4) + v10))(
                 v253,
                 insn,
                 22,                            // getbit
                 27);

IDA F5出来的伪代码放在 虚拟机伪代码.cpp 文件,基本逻辑已经清晰了

没时间继续逆向虚拟机了... 看Section1

Section1

绘制实现

交叉引用 ConnectingMessage 字符串找到 GameViewportClient.DrawTransition 通过 Hook DrawTransition 函数,调用参数里传来的UCanvas对象的 K2_DrawLine` 函数来实现绘制

写一个frida脚本来加载绘制模块和 Hook DrawTransition
并在hook回调中调用绘制模块的函数

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
var dlopen = new NativeFunction(Module.findExportByName(null, "dlopen"), 'pointer', ['pointer', 'int']);
 
var path = Memory.allocUtf8String("/data/app/~~VyMRNKmFZvxhmAMGn3isIw==/com.tencent.ace.gamematch2024final-Yo7yrhen-gaBFjDK0HUabw==/lib/arm/libRenderModule.so");
var mode = 0x1; // RTLD_LAZY
 
var handle = dlopen(path, mode);
if (handle.isNull()) {
    console.log("Failed to load library");
} else {
    console.log("Library loaded successfully");
}
 
var libUE4Base = Module.findBaseAddress('libUE4.so');
console.log('libUE4.so base address: ' + libUE4Base.toString(16));
 
var libcBase = Module.findBaseAddress('libc.so');
console.log('libc.so base address: ' + libcBase.toString(16));
 
var baseAddress = Module.findBaseAddress('libUE4.so');
var DrawTras = baseAddress.add(0x36f787c);
 
var RenderHookStub = Module.findExportByName("libRenderModule.so", "RenderHookStub")
var pGDrawBlock = Module.findExportByName("libRenderModule.so", "pGDrawBlock")
 
console.log("RenderHookStub : " + RenderHookStub.toString(16));
 
Interceptor.attach(DrawTras, {
    onEnter: function(args) {
        var renderHookStub = new NativeFunction(RenderHookStub, 'void', ['int', 'int']);
        renderHookStub(args[0].toInt32(), args[1].toInt32());
    },
    onLeave: function(retval) {
 
    }
});

绘制模块根据外挂程序远程传来的绘制信息来绘制直线

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
struct DrawInfoBlock {
    LineInfo Buffer[1000];
    int Count;
};
 
DrawInfoBlock gDrawBlock;
extern "C" DrawInfoBlock* pGDrawBlock = &gDrawBlock;
extern "C" void RenderHookStub(void* thiz,void* Canvas) {
    Log("RenderHookStub has been called.\n");
    if(gUE4Base == 0)
    {
        gUE4Base = process_get_simplelibaddr(getpid(),"libUE4.so");
    }
    if(gUE4Base == 0)
    {
        return;
    }
    auto drawLine = (fnDrawLine)(gUE4Base + 0x3bdc3d8);
    Log("drawLine = %X\n",drawLine);
    drawLine(Canvas,0,0,100,100,2,255,255,255,255);
    for(int i=0;i<gDrawBlock.Count;i++)
    {
        auto &e = gDrawBlock.Buffer[i];
        drawLine(Canvas,e.StartX,e.StartY,e.EndX,e.EndY,2,e.R,e.G,e.B,e.A);
    }
}

第一种透视实现方法

通过遍历GObject找到 DebugCanvasObject 对象 ,用 CheatEngine 观测内存发现矩阵偏移为 DebugCanvasObject + 0x210

观察游戏内Actor发现
黄球Actor:

1
2
3
4
5
会动的黄球
[15379] <bbd8e200> <b5deac40> Sphere4_Blueprint_C /Game/FirstPersonBP/Maps/FirstPersonExampleMap->FirstPersonExampleMap->PersistentLevel->YellowBall
 
不会动的黄球
AStaticMeshActor

绿球Actor:

1
[15048] <d1519180> <eafc1200> AStaticMeshActor /Game/FirstPersonBP/Maps/FirstPersonExampleMap->FirstPersonExampleMap->PersistentLevel->GreenBall

通过 AStaticMeshActor->StaticMeshComponent->RelativeLocation 拿到小球坐标
通过小球Actor的类名来判断小球颜色
矩阵乘法实现世界坐标转屏幕坐标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static std::optional<vec3_t> W2S(const vec3_t &world)
{
    auto &viewProjection = martix;
    auto tempVec = world;
    auto& inOutPosition = tempVec;
    float res[4] = {
        viewProjection[0][0] * inOutPosition.x + viewProjection[1][0] * inOutPosition.y + viewProjection[2][0] * inOutPosition.z + viewProjection[3][0],
        viewProjection[0][1] * inOutPosition.x + viewProjection[1][1] * inOutPosition.y + viewProjection[2][1] * inOutPosition.z + viewProjection[3][1],
        viewProjection[0][2] * inOutPosition.x + viewProjection[1][2] * inOutPosition.y + viewProjection[2][2] * inOutPosition.z + viewProjection[3][2],
        viewProjection[0][3] * inOutPosition.x + viewProjection[1][3] * inOutPosition.y + viewProjection[2][3] * inOutPosition.z + viewProjection[3][3],
    };
    auto r = res[3];
    if (r > 0) {
        auto rhw = 1.0f / r;
        float inOutPosition[3];
        inOutPosition[0] = (((res[0] * rhw) / 2.0f) + 0.5f) * 1920;
        inOutPosition[1] = (0.5f - ((res[1] * rhw) / 2.0f)) * 1080;
        inOutPosition[2] = r;
        return vec3_t(inOutPosition[0],inOutPosition[1],inOutPosition[2]);
    }
    return {};
}

第二种透视实现方法

通过 CameraRotation,CameraLocation,CameraFOV也可以实现世界坐标转屏幕坐标

GObject 搜索名为 /Engine/Transient->GameEngine->LocalPlayer 的对象拿到LocalPlayer (ULocalPlayer) 的地址

通过 ULocalPlayer->PlayerController->PlayerCameraManager->CameraCachePrivate 拿到 CameraRotation,CameraLocation,CameraFOV

实现世界坐标转屏幕坐标

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
Matrix4x4 toMatrix(Vector3 Rotation, Vector3 origin)
{
    float Pitch = (Rotation.x * float(PI) / 180.f);
    float Yaw = (Rotation.y * float(PI) / 180.f);
    float Roll = (Rotation.z * float(PI) / 180.f);
    float SP = sinf(Pitch);
    float CP = cosf(Pitch);
    float SY = sinf(Yaw);
    float CY = cosf(Yaw);
    float SR = sinf(Roll);
    float CR = cosf(Roll);
     
    Matrix4x4 Matrix;
    Matrix.m[0][0] = CP * CY;
    Matrix.m[0][1] = CP * SY;
    Matrix.m[0][2] = SP;
    Matrix.m[0][3] = 0.f;
    Matrix.m[1][0] = SR * SP * CY - CR * SY;
    Matrix.m[1][1] = SR * SP * SY + CR * CY;
    Matrix.m[1][2] = -SR * CP;
    Matrix.m[1][3] = 0.f;
    Matrix.m[2][0] = -(CR * SP * CY + SR * SY);
    Matrix.m[2][1] = CY * SR - CR * SP * SY;
    Matrix.m[2][2] = CR * CP;
    Matrix.m[2][3] = 0.f;
    Matrix.m[3][0] = origin.x;
    Matrix.m[3][1] = origin.y;
    Matrix.m[3][2] = origin.z;
    Matrix.m[3][3] = 1.f;
    return Matrix;
}
 
 
static std::optional<vec3_t> W2S(const vec3_t &world)
{
    vec3_t world_loc = world;
    auto matrix = toMatrix(LocalCameraRotation,vec3_t());
    const auto axisx = Vector3(matrix.m[0][0], matrix.m[0][1], matrix.m[0][2]);
    const auto axisy = Vector3(matrix.m[1][0], matrix.m[1][1], matrix.m[1][2]);
    const auto axisz = Vector3(matrix.m[2][0], matrix.m[2][1], matrix.m[2][2]);
    const auto delta = world_loc - LocalCameraPosition;
    auto transformed = Vector3(delta.Dot(axisy), delta.Dot(axisz), delta.Dot(axisx));
     
    if (transformed.z < 0.001f) transformed.z = 0.001f;
 
    auto fov_angle = LocalCameraFOV;
    const float center = 1540/2;
    const float centery = 720/2;
    return Vector3(center + transformed.x * (center / (tanf(fov_angle * PI / 360))) / transformed.z, centery - transformed.y * (center / (tanf(fov_angle * PI / 360))) / transformed.z, 0.f);
}

第三种透视实现方法

思路: 调用函数 PlayerController.ProjectWorldLocationToScreen 来实现世界坐标转屏幕坐标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static std::optional<vec3_t> W2S(const vec3_t &world)
{
    if(!LocalPlayerController)
    {
        return {};
    }
    using FnProjectWorldLocationToScreen =
        bool (*)(void* thiz,float worldX,float worldY,float worldZ, float *screen,int _bo);
 
    auto ProjectWorldLocationToScreen = (FnProjectWorldLocationToScreen)(gUE4Base + 0x39B8820);
    float out[3] = {0.f};if(ProjectWorldLocationToScreen((void*)LocalPlayerController,world.x,world.y,world.z,out,1))
    {
        return vec3_t(out[0],out[1],out[2]);
    }
    return {};
}

第一种自瞄实现方法

通过修改 APlayerCcontroller->ControlRotation 实现自瞄
三角函数计算出角度,写进去就能了

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
vec3_t calc_viewoffset(vec3_t target) {
    float x = target.x - LocalCameraPosition.x;
    float y = target.y - LocalCameraPosition.y;
    float z = target.z - LocalCameraPosition.z;
 
    float anglex, angley;
    anglex = 0;
    if (x > 0 && y == 0) anglex = 0;//第一象限
    if (x > 0 && y > 0) anglex = abs(atanf(y / x)) / PI * 180.f;
    if (x == 0 && y > 0) anglex = 90.f;//第二象限
    if (x < 0 && y > 0)anglex = 90.f + abs(atanf(x / y)) / PI * 180.f;
    if (x < 0 && y == 0)anglex = 180.f;//第三象限
    if (x < 0 && y < 0)anglex = 180.f + abs(atanf(y / x)) / PI * 180.f;
    if (x == 0 && y < 0)anglex = 270.f;//第四象限
    if (x > 0 && y < 0)anglex = 270.f + abs(atanf(x / y)) / PI * 180.f;
 
    if(anglex > 180.f)
    {
        anglex = (360.f - anglex) * -1;
    }
 
    angley = atanf(z / sqrt(x * x + y * y)) / PI * 180.f;
    if(angley >= 90.f)
    {
        angley = (180.f - angley) * -1;
    }
    return { angley,anglex,0 };
}
 
void aimto(vec3_t target)
{
    auto offset = calc_viewoffset(target);
    memory_event(offset);
}

第二种自瞄实现方法

思路: 修改 FirstPersonCharacter_C.GunOffset

CE尝试修改FirstPersonCharacter_C.GunOffset,发现子弹位置变动

对FirstPersonCharacter_C.GunOffset 下访问断点,观察返回地址发现游戏引擎调用 GreaterGreater_VectorRotator 函数

猜测开枪函数计算子弹坐标是由 GunOffset 绕着自身的Rotation旋转加上自身坐标

用R表示用摄像机Rotation转换出的旋转矩阵
用g表示GunOffset成员
用p表示摄像机坐标
用d表示子弹坐标

构建出一个等式,当 d = 黄色小球坐标时, 已知 R,p,d
解方程得到g的运算公式

Pasted image 20240419172253

把黄色小球坐标减去 Rotation转成的单位向量乘以100 即可实现枪口对准黄色小球

实现代码

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
75
76
77
Matrix4x4 toMatrix(Vector3 Rotation, Vector3 origin)
{
 
    float Pitch = (Rotation.x * float(PI) / 180.f);
    float Yaw = (Rotation.y * float(PI) / 180.f);
    float Roll = (Rotation.z * float(PI) / 180.f);
    float SP = sinf(Pitch);
    float CP = cosf(Pitch);
    float SY = sinf(Yaw);
    float CY = cosf(Yaw);
    float SR = sinf(Roll);
    float CR = cosf(Roll);
 
    Matrix4x4 Matrix;
 
    Matrix.m[0][0] = CP * CY;
    Matrix.m[0][1] = CP * SY;
    Matrix.m[0][2] = SP;
    Matrix.m[0][3] = 0.f;
    Matrix.m[1][0] = SR * SP * CY - CR * SY;
    Matrix.m[1][1] = SR * SP * SY + CR * CY;
    Matrix.m[1][2] = -SR * CP;
    Matrix.m[1][3] = 0.f;
    Matrix.m[2][0] = -(CR * SP * CY + SR * SY);
    Matrix.m[2][1] = CY * SR - CR * SP * SY;
    Matrix.m[2][2] = CR * CP;
    Matrix.m[2][3] = 0.f;
    Matrix.m[3][0] = origin.x;
    Matrix.m[3][1] = origin.y;
    Matrix.m[3][2] = origin.z;
    Matrix.m[3][3] = 1.f;
    return Matrix;
}
 
Vector3 toDirection(Vector3 Rotation)
{
 
    float Pitch = (Rotation.x * float(PI) / 180.f);
    float Yaw = (Rotation.y * float(PI) / 180.f);
    float Roll = (Rotation.z * float(PI) / 180.f);
    float SP = sinf(Pitch);
    float CP = cosf(Pitch);
    float SY = sinf(Yaw);
    float CY = cosf(Yaw);
    float SR = sinf(Roll);
    float CR = cosf(Roll);
    return vec3_t(CP*CY,CP*SY,SP);
 
}
 
vec3_t UnrotateVector(vec3_t Vector)
{
 
    auto matrix = toMatrix(LocalCameraRotation,vec3_t(0,0,0)).inverse();
 
    float res[4] = {
 
        matrix.m[0][0] * Vector.x + matrix.m[1][0] * Vector.y + matrix.m[2][0] * Vector.z + matrix.m[3][0],
 
        matrix.m[0][1] * Vector.x + matrix.m[1][1] * Vector.y + matrix.m[2][1] * Vector.z + matrix.m[3][1],
 
        matrix.m[0][2] * Vector.x + matrix.m[1][2] * Vector.y + matrix.m[2][2] * Vector.z + matrix.m[3][2],
 
        matrix.m[0][3] * Vector.x + matrix.m[1][3] * Vector.y + matrix.m[2][3] * Vector.z + matrix.m[3][3],
 
    };
 
    return vec3_t(res[0],res[1],res[2]);
 
}
 
auto off =  toDirection(LocalCameraRotation) * 100.f;
auto spawnPosition = ball.Position;
spawnPosition = spawnPosition - off;
auto delta = spawnPosition - LocalCameraPosition;
auto rotvec = UnrotateVector(delta);
wr<vec3_t>(pPerson+0x3f4,rotvec);

效果:

基础保护

UMyBlueprintFunctionCheckPass.start1UMyBlueprintFunctionCheckPass.start2 是检测调试和注入模块的函数,用frida patch掉即可绕过

1
2
3
4
5
6
7
8
9
10
11
12
var baseAddress = Module.findBaseAddress('libUE4.so');
var targetAddress2 = baseAddress.add(0x19f4610);
Interceptor.replace(targetAddress2, new NativeCallback(function () {
        console.log("start1 has been called");
        return 0;
}, 'int', []));
 
var targetAddress3 = baseAddress.add(0x223c544);
Interceptor.replace(targetAddress3, new NativeCallback(function () {
        console.log("start2 has been called");
        return 1;
}, 'int', []));

frida hook libc.so.fopen 可以看到游戏检测 /proc/self/maps

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Interceptor.attach(Module.findExportByName("libc.so", "fopen"), {
     
    onEnter: function (args) {
        var pathname = Memory.readUtf8String(args[0]);
        if(pathname == "/proc/self/maps") {
            console.log("[*] Call stack:");
            this.backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE)
                .join("\n");
            console.log(this.backtrace);
        }
    },
    onLeave: function (retval) {
    }
});

没时间继续分析了 (


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2024-4-22 11:48 被cslime编辑 ,原因:
上传的附件:
收藏
点赞6
打赏
分享
最新回复 (2)
雪    币: 223
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wx_青竹 2024-4-22 11:24
2
0
初赛的解题报告能发一下吗,想学习下
雪    币: 19589
活跃值: (29262)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2024-4-22 13:54
3
1
感谢分享
游客
登录 | 注册 方可回帖
返回