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的运算公式
把黄色小球坐标减去 Rotation转成的单位向量乘以100 即可实现枪口对准黄色小球
实现代码
效果:
UMyBlueprintFunctionCheckPass.start1
和 UMyBlueprintFunctionCheckPass.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)
struct
UMyBlueprintFunctionCheckPass :
public
UBlueprintFunctionLibrary
{
void
start2();
void
start1();
void
CheckPassWordInc();
};
struct
UMyBlueprintFunctionCheckPass :
public
UBlueprintFunctionLibrary
{
void
start2();
void
start1();
void
CheckPassWordInc();
};
var
target19f4304 = baseAddress.add(0x19f4304);
Interceptor.attach(target19f4304, {
onEnter:
function
(args) {
console.log(
"Called CheckPassWordInc"
);
console.log(
"R0 : "
+ args[0].toString(16));
},
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));
},
onLeave:
function
(retval) {
console.log(
"CheckPassWordInc returns"
);
retval.replace(1);
}
});
.text:
046D1862
4F
F4
40
72
MOV.W R2,
.text:
046D1866
07
2B
CMP
R3,
.text:
046D1868
08
BF IT EQ
.text:
046D186A
4F
F4
35
72
MOVEQ.W R2,
.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,
.text:
046D1866
07
2B
CMP
R3,
.text:
046D1868
08
BF IT EQ
.text:
046D186A
4F
F4
35
72
MOVEQ.W R2,
.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'
,
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'
,
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_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_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,
27);
v240 = ((
int
(__fastcall *)(
int
*, unsigned
int
,
int
,
int
))(*(_DWORD *)(v98 + v14 + 4) + v10))(
v253,
insn,
22,
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;
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;
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编辑
,原因:
上传的附件: