上个帖子 对整个赛题进行了大概的分析 接下来就是如何实现个跟自瞄不同的功能了 那怎么样才能够更暴力 那当然是 当当当~ 子弹追踪 接下来就是讲解如何找子弹追踪 并且 怎么实现 子弹穿墙追踪。
使用工具:IDA Pro 7.2
、CheatEngine 7.2
、x64dbg
、PYArk
2.开枪会跟什么有关系呢->那肯定是子弹数量啦 我们先把Bot的数量设置为0 防止老被Bot打死 然后我们CE搜索出子弹数量的地址 右键查找什么改写了此地址。
子弹数量一般都存储在武器指针下面 rbx应该就是武器指针哦
3.看见一个地址 我们在这F5下个断返回到上一层 武器开火的函数就能在附近找到哦 (为什么武器开火的函数就在附近呢 因为开火->子弹减少)一般正常流程都是这样~ 那我们先把子弹减少这个call nop看看
我们可以看见 墙上依然会有弹孔(假设开火函数在子弹数量减少的这个call里面的话 那么nop掉之后 相当于 子弹不会发射 那墙上也就不会有弹孔)而且我们已知rcx是武器指针 那我们看看附近有没有用到了武器指针的函数
而且通常都会是虚表函数 为什么呢 因为游戏引擎基本都是基于对象去管理事件的
例如武器.开火()这样子
我们nop这个函数试试
发现弹孔消失了 只剩下枪口的特效了 那我们就要在这个call内 认真分析了 先把这个虚表函数恢复了 然后在这里下断
4.武器子弹弹道函数的分析
我们可以看到函数内有大量的浮点操作 此时我们慢慢单步 观察这个函数内 每一个函数的参数 返回值 看看有没有取出来什么坐标之类的 因为这个虚表函数已经被确认为开火函数 并且这个虚表函数除了this之外是没有其他参数的 所以子弹出发的坐标一定是在这个函数里面 通过其他函数取出来的 所以我们要格外认真的去查看这些函数的返回值 参数(因为可能是通过参数返回的坐标)。
没单步一会就看到这个函数返回了一个坐标(存储在rax 或参数[rsp+0x60]里)此时我们先运行起来 然后我们对着墙开枪 中断以后把返回的坐标 清0 看看效果
发现弹孔消失了 所以这个坐标一定跟弹道有关 但是 并不能确认这个坐标就是子弹出发的坐标 所以我们手写一个shellcode来试试
5.选取一个合适的位置来hook 首先我们要保证不破坏原来的上下文(就是寄存器保护好) 并且不破坏原来的执行代码
所以 当当当~ 我使用了 这种支持跨4gb的跳转 并且不会影响寄存器的跳转
我们对着天开枪 这个Bot就死了
这样就实现了穿墙加子弹追踪的效果。
然后在后续中 发现多个Bot存在时 Bot会出现打不中我的情况 在这个函数下断分析 发现机器人开火的时候也会走这里 那我们这时候可以改改我们的shellcode
判断下武器指针是否属于我们自己 不是我们自己的话 就不修改 是我们自己的话就给Bot坐标 这样就不会影响Bot的开枪了。(懒得写了)
FLAG毫无技术难度 重新去看的时候 没多久就搞定了..
重新回来看FLAG 发现了这里 不清楚是个啥 于是x64dbg 设置RIP
可以看见解密出来以后的字符串 FileName = "flag:%s\n\r"
进到140001010可以看见明显的特征(分析多了 这里其实可以看出来 这是一个printf函数)
所以在v23为假的情况下会执行这个流程打印v9 v9是从hack.dat解密出来的
往上重新分析下v23是如何被更改的
回到加密函数 第一个参数是要被解密的BufferPtr 第二个是长度
并且经过这个解密函数后解密前后的Buffer长度是一样的
并且在大于等于0x40的时候会走上面这个分支 小于0x40会走下面这个分支
我们已经知道了FLAG的长度 为0x3E
所以我们只需要看下面这个分支就好了 (实际上 这两个分支的算法都是一样的
只不过是被编译器用SSE加速优化了)
发的两篇帖子 都是大晚上随手写的 写的不好的地方 欢迎指正啦~
而且这两篇帖子在写的时候 都是从一个不太了解UE4引擎的普通的参赛选手的角度出发去写的(为了让没有做过游戏逆向的朋友们也能看懂 并且如何得出做题的思路~~)
所以我觉得这次的赛题无论懂UE4引擎的数据结构好 不懂UE4引擎的数据结构好
其实只要思路正确都是可以快速做出答案的哦~ 并且还可以剩余很多时间去精进自己的WriteUp(意思就是基于做完题目的前提下 疯狂内卷 写个无限血量 无限子弹 分析下hack.dll的hook点 绘制的实现 自己写个透视 漏脚打脚 漏头打头 之类的提高分数)
当然如果是懂UE4引擎的数据结构 或者 有相关FPS外挂经验的选手可能在做题的过程中 更容易猜出hack.dll的意图 从而更好的去解题 其实题目还有很多有意思的地方 例如hack.dll的实现 或者是比子弹穿墙更加变态的实现(模拟弹道)
UE4 SDK的生成 等等等 涉及的面太多太多 我没有在帖子中一一讲解 因为真的讲不完
相关代码已经贴出来了 如果有讲的不好的地方 或者 不懂的地方 欢迎跟帖~
赛题链接https://gslab.qq.com/html/competition/2021/race-pre.htm
push xxxx
mov [rsp
+
4
],xxxx
ret
push xxxx
mov [rsp
+
4
],xxxx
ret
/
/
dllmain.cpp : 定义 DLL 应用程序的入口点。
void DllEntry();
char
*
GetName(uintptr_t
Object
);
BOOL
APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
AllocConsole();
freopen(
"CON"
,
"w"
, stdout);
CreateThread(
0
,
0
, (LPTHREAD_START_ROUTINE)DllEntry,
0
,
0
,
0
);
break
;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break
;
}
return
TRUE;
}
uintptr_t GameBase{
0
};
uintptr_t BotPosPtr{
0
};
const char BotName[
10
]
=
"BotPawn_C"
;
struct Array
{
uintptr_t
*
ArrayEntry;
int
Count;
};
struct Vec3
{
float
x;
float
y;
float
z;
};
Vec3
*
GetPos(uintptr_t
Object
);
void Hook();
void DllEntry()
{
GameBase
=
reinterpret_cast<uintptr_t>(GetModuleHandleW(L
"ShooterClient.exe"
));
Hook();
while
(true)
{
auto UOjbectArrayPtr
=
*
(uintptr_t
*
)(GameBase
+
0x2F71060
);
if
(!UOjbectArrayPtr)
continue
;
UOjbectArrayPtr
=
*
(uintptr_t
*
)(UOjbectArrayPtr
+
0x30
);
if
(!UOjbectArrayPtr)
continue
;
auto UOjbectArray
=
*
(Array
*
)(UOjbectArrayPtr
+
0x98
);
/
/
自己定义一个结构
for
(
int
index
=
0
; index < UOjbectArray.Count; index
+
+
)
{
auto
Object
=
UOjbectArray.ArrayEntry[index];
if
(
Object
)
{
auto NamePtr
=
GetName(
Object
);
if
(NamePtr)
{
if
(!strcmp(NamePtr,
"BotPawn_C"
))
{
auto pPos
=
GetPos(
Object
);
memcpy((void
*
)BotPosPtr, pPos, sizeof(Vec3));
}
/
/
printf(
"Ptr:%llx Name:%s\n"
,
Object
, NamePtr);
}
}
}
}
}
void Hook()
{
uint8_t BulletShellCode[]
=
"\x81\xC1\x6B\x63\x19\x36\x8B\xC1\x25\xFF\xFF\x7F\x00\x0D\x00\x00\x80\x3F\x89\x85\x00\x01\x00\x00\x50\x51\x48\xB8\x66\x66\x66\x66\x66\x66\x36\x12\x48\x8B\x08\x48\x89\x4C\x24\x70\x8B\x48\x08\x89\x4C\x24\x78\x59\x58\x68\x78\x56\x34\x12\xC7\x44\x24\x04\x34\x12\x00\x00\xC3"
;
uint8_t JmpShellCode[]
=
"\x68\x78\x56\x34\x12\xC7\x44\x24\x04\x34\x12\x00\x00\xC3"
;
BotPosPtr
=
(uintptr_t)malloc(sizeof(Vec3));
auto HookMemory
=
(uintptr_t)VirtualAlloc(
0
,
0x1000
,
0x1000
, PAGE_EXECUTE_READWRITE);
if
(BotPosPtr && HookMemory)
{
auto HookAddress
=
GameBase
+
0x51C162
;
auto ReturnAddress
=
GameBase
+
0x51C17A
;
*
(uintptr_t
*
)(BulletShellCode
+
0x1C
)
=
BotPosPtr;
*
(uint32_t
*
)(BulletShellCode
+
0x36
)
=
*
(uint32_t
*
)(&ReturnAddress);
*
(uint32_t
*
)(BulletShellCode
+
0x3E
)
=
*
(uint32_t
*
)((uint64_t)(&ReturnAddress)
+
4
);
memcpy((void
*
)HookMemory, BulletShellCode, sizeof(BulletShellCode)
-
1
);
*
(uint32_t
*
)(JmpShellCode
+
0x1
)
=
*
(uint32_t
*
)(&HookMemory);
*
(uint32_t
*
)(JmpShellCode
+
0x9
)
=
*
(uint32_t
*
)((uint64_t)(&HookMemory)
+
4
);
DWORD old{
0
};
VirtualProtect((void
*
)HookAddress,
0x100
,
0x40
, &old);
memcpy((void
*
)HookAddress, JmpShellCode, sizeof(JmpShellCode)
-
1
);
VirtualProtect((void
*
)HookAddress,
0x100
, old, &old);
}
printf(
"HookMemory:%llx BotPosPtr:%llx\n"
, HookMemory, BotPosPtr);
}
char
*
GetName(uintptr_t
Object
)
{
if
(IsBadReadPtr((void
*
)
Object
,
8
))
{
return
0
;
}
auto NameIndex
=
*
(
int
*
)(
Object
+
0x18
);
if
(!NameIndex)
return
NULL;
auto NameBase
=
*
(uintptr_t
*
)(GameBase
+
0x2E6E0C0
);
if
(!NameBase)
return
NULL;
auto NameIndexPtr
=
*
(uintptr_t
*
)(NameBase
+
8
*
(static_cast<uint64_t>(NameIndex)
/
0x4000
));
if
(!NameIndexPtr)
return
NULL;
NameIndexPtr
=
*
(uintptr_t
*
)(NameIndexPtr
+
8
*
(static_cast<uint64_t>(NameIndex)
%
0x4000
));
if
(!NameIndexPtr)
return
NULL;
return
(char
*
)(NameIndexPtr
+
0xC
);
/
/
v4
=
(
*
(
*
(qword_1800091A0
+
8i64
*
(
*
v3
/
0x4000
))
+
8i64
*
(
*
v3
%
0x4000
))
+
0x10i64
); 这里dump出来的dll最后是
0x10
哦
/
/
这里
0xC
为什么跟 dump出来的 那个
0x10
不一样呢 因为
0x10
取出来的名字是不完整的 不知道为什么出题人要这样写
}
Vec3
*
GetPos(uintptr_t
Object
)
{
if
(IsBadReadPtr((void
*
)
Object
,
8
))
{
return
0
;
}
auto PosPtr
=
*
(uintptr_t
*
)(
Object
+
0x158
);
return
(Vec3
*
)(PosPtr
+
0x164
);
}
/
/
dllmain.cpp : 定义 DLL 应用程序的入口点。
void DllEntry();
char
*
GetName(uintptr_t
Object
);
BOOL
APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
AllocConsole();
freopen(
"CON"
,
"w"
, stdout);
CreateThread(
0
,
0
, (LPTHREAD_START_ROUTINE)DllEntry,
0
,
0
,
0
);
break
;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break
;
}
return
TRUE;
}
uintptr_t GameBase{
0
};
uintptr_t BotPosPtr{
0
};
const char BotName[
10
]
=
"BotPawn_C"
;
struct Array
{
uintptr_t
*
ArrayEntry;
int
Count;
};
struct Vec3
{
float
x;
float
y;
float
z;
};
Vec3
*
GetPos(uintptr_t
Object
);
void Hook();
void DllEntry()
{
GameBase
=
reinterpret_cast<uintptr_t>(GetModuleHandleW(L
"ShooterClient.exe"
));
Hook();
while
(true)
{
auto UOjbectArrayPtr
=
*
(uintptr_t
*
)(GameBase
+
0x2F71060
);
if
(!UOjbectArrayPtr)
continue
;
UOjbectArrayPtr
=
*
(uintptr_t
*
)(UOjbectArrayPtr
+
0x30
);
if
(!UOjbectArrayPtr)
continue
;
auto UOjbectArray
=
*
(Array
*
)(UOjbectArrayPtr
+
0x98
);
/
/
自己定义一个结构
for
(
int
index
=
0
; index < UOjbectArray.Count; index
+
+
)
{
auto
Object
=
UOjbectArray.ArrayEntry[index];
if
(
Object
)
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2021-4-23 10:22
被淡然他徒弟编辑
,原因: