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

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

2024-4-22 08:59
12054

图片描述

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

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

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

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

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

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

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

这种混淆的特征是有一个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出内存

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

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

patch 二进制脚本

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

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

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

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

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

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

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

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

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

绿球Actor:

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

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

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

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

实现世界坐标转屏幕坐标

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

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

思路: 修改 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 即可实现枪口对准黄色小球

实现代码

效果:

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

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

没时间继续分析了 (

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)
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)
// 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
 
};
// 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
 
};
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);
        }
    });
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);
        }
    });
.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
.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
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.");
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.");
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))
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))
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.")
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.")
v240 = ((int (__fastcall *)(int *, unsigned int, int, int))(*(_DWORD *)(v98 + v14 + 4) + v10))(
                 v253,
                 insn,
                 22,                            // getbit
                 27);
v240 = ((int (__fastcall *)(int *, unsigned int, int, int))(*(_DWORD *)(v98 + v14 + 4) + v10))(
                 v253,
                 insn,
                 22,                            // getbit
                 27);
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) {
 
    }
});
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) {
 
    }
});
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);
    }
}
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)
    {

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2024-4-22 11:48 被cslime编辑 ,原因:
上传的附件:
收藏
免费 7
支持
分享
最新回复 (2)
雪    币: 231
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
初赛的解题报告能发一下吗,想学习下
2024-4-22 11:24
0
雪    币: 3573
活跃值: (31026)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2024-4-22 13:54
1
游客
登录 | 注册 方可回帖
返回
//