熬了两夜,用血换了个第四(存哥yyds
![result](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/result.png)
初赛
题目说明:
- 下载ctf1.exe (md5:63b2b6e27bc8e95cf81e5be8e5081265)。
- 运行ctf1.exe,依据程序提示,找到flag。
评分标准:满分10分
- flag(5分),找到flag并正确提交。
- 文档(4分),详细描述解题过程,如涉及编写程序,必须提供源代码。
- 时间(1分),正确提交flag的顺序。前10名计1分,后每10名-0.1分。
打开程序,提示右上角,尝试拖动发现会卡住,尝试修改窗体大小发现还是不行。目测应该是对鼠标消息做了判断。OD加载程序,找到窗体的消息处理函数在 0x41B010
![初赛_1](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%88%9D%E8%B5%9B_1.png)
根据消息跟了跟发现在这里判断了坐标
![初赛_2](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%88%9D%E8%B5%9B_2.png)
![初赛_3](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%88%9D%E8%B5%9B_3.png)
5条ja跳转patch掉 运行发现可以看到内容了
![初赛_4](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%88%9D%E8%B5%9B_4.png)
好家伙,果然和2018年的决赛不一样。2018年就是一个flag的图片了,这还改了视角?
数据段上看到大量大量的float变量,交叉跟了下,找到了渲染的位置
![初赛_5](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%88%9D%E8%B5%9B_5.png)
Box中存着所有的坐标点,do-while循环取坐标点渲染,直接把box dump下来,处理一下,用matplotlib 画三维
![初赛_6](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%88%9D%E8%B5%9B_6.png)
调了下视角转二维
![初赛_7](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%88%9D%E8%B5%9B_7.png)
Flag:dogod
附exp
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
|
# ax = plt.axes(projection='3d')
ax = plt.axes()
plt.figure(figsize=(100000,100000))
xline = []
yline = []
zline = []
with open('./location.txt') as f:
xline = f.readline().split()
yline = f.readline().split()
zline = f.readline().split()
xline = np.array(xline).astype(float)
yline = np.array(yline).astype(float)
# zline = np.array(zline).astype(float)*10000
zline = np.zeros(1020)*10
# print(len(xline))
# print(len(yline))
# print(len(zline))
# print(xline)
# print(yline)
# print(zline)
# ax.scatter3D(xline, yline, zline, 'gray')
ax.scatter(xline,yline)
|
决赛
题目说明:
- shootgame是一个游戏,hack.exe是游戏shootergame的一个外挂程序。
- 运行shootgame游戏,运行hack.exe,成功执行外挂功能并分析外挂实现过程。
- 实现一个与hack.exe的功能相同的,但是游戏逻辑原理不同的外挂程序。(游戏逻辑原理不同:指外挂程序对读写游戏数据结构或代码的攻击内容不同,并不是读写内存方式、注入内存方式、外挂核心核心代码载体差异的不同)
评分标准:满分10分
- flag(2分) 成功执行hack功能,给出外挂执行成功的flag。
- 代码(3分) 与hack.exe外挂功能相同,但实现原理与hack.exe不同的程序源码,仅需要提供可编译的工程完整代码,不需要提供已经编译出来的bin文件。
- 文档(4分) 详细描述解题过程,如涉及编写程序,必须提供源代码。
- 时间(1分) 正确提交flag、代码、文档的顺序,第1名计1分,后每1名-0.05分。
很明显,今年是写挂了。本fw通了两个宵只能很勉强的做出几个简单的功能。羡慕带哥直接dump SDK两小时生成带几十个功能的盖。(师傅们tql
hack.exe
加载程序进IDA,main函数开头解密字符串,试图打开hack.dat文件,因为dat文件不存在,所以hack.exe退出
![决赛_hack.exe_1](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_hack.exe_1.png)
我随便创了一个文件然后往里面写了一些东西,然后hack.exe就可以往下运行读取其中的内容然后进decode解密,decode函数里面是SSE优化后的解密算法,其中有两个分支,若长度大于0x40会走SSE优化的算法,若小于则走下面的分支,但本质的解密算法是一样的。具体算法后面给出分析
![决赛_hack.exe_2](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_hack.exe_2.png)
这一部分是计算shellcode的长度,shellcode会用于dll的加载
![决赛_hack.exe_3](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_hack.exe_3.png)
再下面v9就是decode函数对hack.dat解密出来的内容,通过取值取出ProcName,结合上下文可以知道这个ProcName就是游戏的进程名,在下面的 CreateToolhelp32Snapshot + Process32First 进程枚举中搜索游戏进程
再往下走下面两个循环分别是解密出flag和check flag
![决赛_hack.exe_4](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_hack.exe_4.png)
如果check成功则解密出 flag:%s 字符串并调用printf函数输出flag
![决赛_hack.exe_5](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_hack.exe_5.png)
下面会解密出一个dll并申请空间用于后续的注入
![决赛_hack.exe_6](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_hack.exe_6.png)
起动调程序,通过setIP手动的让程序运行decode函数解密DLL,然后通过idc脚本对其dump确定是一个dll文件,附dump.idc
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
|
#include <idc.idc>
#define PT_LOAD 1
#define PT_DYNAMIC 2
static main(void)
{
auto ImageBase,StartImg,EndImg;
auto i,dumpfile;
StartImg=0x19B11F51440;
EndImg=0x19B11F51440 + 0xFA00;
if(dumpfile = fopen("D:\\DumpFile","wb"))
{
dump(dumpfile,StartImg,EndImg);
fclose(dumpfile);
}
}
static dump(dumpfile,startimg,endimg)
{
auto i;
auto size;
size = endimg-startimg;
for ( i=0; i < size; i=i+1 )
{
fputc(Byte(startimg+i),dumpfile);
}
}
|
![决赛_hack.exe_7](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_hack.exe_7.png)
利用注入器将DLL注入到游戏中,弹出提示,测试发现带有右键自瞄的外挂功能
![决赛_hack.exe_8](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_hack.exe_8.png)
回过头来继续看hack.exe。后面一大段都是解密出各种函数(这里就不一一截图了)
![决赛_hack.exe_9](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_hack.exe_9.png)
最后将dll,shellcode写入到游戏进程空间并起远线程调用shellcode加载dll
![决赛_hack.exe_10](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_hack.exe_10.png)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
内容分析完了,用x64dbg对decode函数动态调试,获得解密逻辑,通过flag反推出正确hack.dat文件 附exp.py
string = "2RSRhrofoWtLeLrJCSlTireznrtx.oeLxuehyyAwbpCOZq0tsS7MZyVdOUoE8\x00\x00\x00"
# code = [
# 0x32,0x52,0x53,0x52,0x68,0x72,0x6F,0x66,0x6F,0x57,0x74,0x4C,0x65,0x4C,0x72,0x4A
# ,0x43 ,0x53 ,0x6C ,0x54 ,0x69 ,0x72 ,0x65 ,0x7A ,0x6E ,0x72 ,0x74 ,0x78 ,0x2E ,0x6F ,0x65 ,0x4C
# ,0x78 ,0x75 ,0x65 ,0x68 ,0x79 ,0x79 ,0x41 ,0x77 ,0x62 ,0x70 ,0x43 ,0x4F ,0x5A ,0x71 ,0x30 ,0x74
# ,0x73 ,0x53 ,0x37 ,0x4D ,0x5A ,0x79 ,0x56 ,0x64 ,0x4F ,0x55 ,0x6F ,0x45 ,0x38 ,0x0, 0x0, 0x0]
code = [ord(x) for x in string]
i = 0;
j = 0;
k = 0;
for i in range(4):
for j in range(16):
code[k] += 0x13;
code[k] = code[k] & 0xFF
code[k] ^= 0x3F;
k += 1
for i in range(len(code)):
print(hex(code[i]), end=" ")
with open("hack.dat", 'wb') as fp:
fp.write(bytes(code))
|
可以看到外挂成功启动并输出flag
![决赛_hack.exe_11](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_hack.exe_11.png)
Flag: 2RSRhrofoWtLeLrJCSlTireznrtx.oeLxuehyyAwbpCOZq0tsS7MZyVdOUoE8
DumpFile.dll
直接看这个dll发现很乱,来回看了一下发现是封装了一个hook引擎进去。外挂注入后可以实现右键自瞄的功能,同时只有敌人离自己在一定范围内才会触发自瞄。因为是按键自瞄,直接想到GetAsyncKeyState函数。IDA看dll的导入表果然看到交叉引用,只有sub_180005050一个函数调用过该函数,确定此处为作弊功能
![决赛_DumpFile.dll_1](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_DumpFile.dll_1.png)
发现浮点数写操作,0x398很像是一个偏移,附加上去跟了一下
![决赛_DumpFile.dll_2](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_DumpFile.dll_2.png)
动调跟了发现这两个值分别是角色的上下摇摆角和左右偏移角。向上回溯寻找v16的来源定位到这个地方
![决赛_DumpFile.dll_3](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_DumpFile.dll_3.png)
获得计算公式,发现此处的基址是出于作弊模块空间的,回到初始化函数找到原基址,输入CE
![决赛_DumpFile.dll_4](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_DumpFile.dll_4.png)
![决赛_DumpFile.dll_5](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_DumpFile.dll_5.png)
以此为入手点,从头看这个作弊函数。这个大循环就是自瞄了
![决赛_DumpFile.dll_6](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_DumpFile.dll_6.png)
GetAimTarget函数会尝试匹配符合条件的自瞄目标,匹配成功则返回目标对象,否则返回-1,其也是通过枚举角色结构列表来实现的
![决赛_DumpFile.dll_7](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_DumpFile.dll_7.png)
然后会获取每个角色的名字进行匹配
![决赛_DumpFile.dll_8](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_DumpFile.dll_8.png)
![决赛_DumpFile.dll_9](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_DumpFile.dll_9.png)
会在CampName函数中对名字进行字符串匹配
![决赛_DumpFile.dll_10](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_DumpFile.dll_10.png)
下面是距离计算逻辑
![决赛_DumpFile.dll_11](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_DumpFile.dll_11.png)
![决赛_DumpFile.dll_12](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_DumpFile.dll_12.png)
通过屏幕中心点与敌人的屏幕坐标 距离 来匹配距离准星 最近的敌人,若能匹配到,敌人对象就会通过v3返回。至此GetAimTarget函数分析完毕。
![决赛_DumpFile.dll_13](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_DumpFile.dll_13.png)
通过GetAimTarget取到的敌人对象,则通过内存对其取坐标,易分析得0x164,0x168,0x16C分别为对象的X,Y,Z坐标
最后会对坐标做角度变换和归一化处理,最终计算得到上下摇摆角和左右偏移角填入游戏数据
![决赛_DumpFile.dll_14](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_DumpFile.dll_14.png)
至此外挂功能函数分析完毕
外挂攻击的数据(实现自瞄的方式)
[[[[[ShooterClient.exe + 2F71060 + 160] + 38] + 0] + 30] + 398] // 上下摇摆角
[[[[[ShooterClient.exe + 2F71060 + 160] + 38] + 0] + 30] + 39C] // 左右偏移角
外挂实现
写了两个外挂,编译环境均为 visual studio 2019 x64 release
Cheat1
纯外部跨进程的通过修改镜头角度的实现右键自瞄
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <cstring>
#include "Offset.hpp"
#include "Myd3d.hpp"
#include "GameControler.hpp"
// #include "GameControlerByDriver.hpp"
using namespace std;
CGameControler GameControler("ShooterClient.exe");
D3DXVECTOR3 local_pos, target_pos;
string GetName(DWORD64 Entity)
{
DWORD64 Index = GameControler.read<DWORD>((Entity + 0x18));
DWORD64 Base = GameControler.read<DWORD64>((DWORD64)GameControler.GetGameBase() + offset_Name);
DWORD64 tmp1 = GameControler.read<DWORD64>(Base + 8 * ((Index) / 0x4000));
DWORD64 tmp2 = GameControler.read<DWORD64>(tmp1 + 8 * ((Index) % 0x4000));
return GameControler.ReadString((LPCVOID)(tmp2 + 0xC), 64);
}
int Cheat()
{
DWORD64 Uworld = GameControler.read<DWORD64>((DWORD64)GameControler.GetGameBase() + offsets_UWorld);
// printf("Uworld:%llx\n", Uworld);
DWORD64 ULevel = GameControler.read<DWORD64>(DWORD64(Uworld + offsets_Ulevel));
DWORD64 AActor = GameControler.read<DWORD64>(DWORD64(ULevel + offsets_Actor));
// printf("AActor:%p\n", AActor);
DWORD num = GameControler.read<DWORD>(DWORD64(ULevel + offsets_Actor) + 8);
DWORD64 GameInstance = GameControler.read<DWORD64>(DWORD64(Uworld + offsets_GameInstance));
DWORD64 LocalPlayer = GameControler.read<DWORD64>( GameControler.read<DWORD64>(DWORD64(GameInstance + offsets_LocalPlayer)) );
DWORD64 PlayerController = GameControler.read<DWORD64>(DWORD64(LocalPlayer + offsets_PlayerController));
DWORD64 LocalPawn = GameControler.read<DWORD64>(DWORD64(PlayerController + offsets_LocalPawn));
// printf("LocalPawn:%p\n", LocalPawn);
// printf("My HP:%f\n\n", GameControler.read<float>((DWORD64)(LocalPawn + offsets_Health)));
DWORD64 playobject = GameControler.read<DWORD64>(DWORD64(LocalPawn + offsets_PlayObject));
float MyX = GameControler.read<float>(DWORD64(playobject + offsets_TargetX));
float MyY = GameControler.read<float>(DWORD64(playobject + offsets_TargetY));
float MyZ = GameControler.read<float>(DWORD64(playobject + offsets_TargetZ));
// printf("[*] Local : X: %f \t Y : %f \t Z : %f\n", MyX, MyY, MyZ);
local_pos = GameControler.read<D3DXVECTOR3>(playobject + offsets_TargetX);
DWORD64* EntityList = (DWORD64*)malloc((num + 1) * sizeof(DWORD64*));
GameControler.Read((LPCVOID)AActor, EntityList, num * sizeof(DWORD64*));
for (int i = 0; i < num; ++i) {
DWORD64 Entity = EntityList[i];
if (Entity == LocalPawn) continue;
if (!Entity) continue;
float HP = GameControler.read<float>((DWORD64)(Entity + offsets_Health));
if (HP <= 0.f) continue;
string sName = GetName(Entity);
if (strcmp(sName.c_str(), "BotPawn_C")) continue;
DWORD64 playobject = GameControler.read<DWORD64>(DWORD64(Entity + offsets_PlayObject));
float targetX = GameControler.read<float>(DWORD64(playobject + offsets_TargetX));
float targetY = GameControler.read<float>(DWORD64(playobject + offsets_TargetY));
float targetZ = GameControler.read<float>(DWORD64(playobject + offsets_TargetZ));
// printf("[*] target[%d] : %s : X: %f \t Y : %f \t Z : %f\n", i, sName.c_str(), targetX, targetY, targetZ);
target_pos = GameControler.read<D3DXVECTOR3>(playobject + offsets_TargetX);
}
free(EntityList);
// printf("\n");
if (GetAsyncKeyState(2) != 0) {
D3DXVECTOR3 angle = { 0, 0, 0 };
float diff_x = target_pos.x - local_pos.x; // X差
float diff_y = target_pos.y - local_pos.y; // Y差
angle.x = atan2f(target_pos.z - local_pos.z, sqrtf((float)(diff_x * diff_x) + (float)(diff_y * diff_y)) ) * 57.295784;
angle.y = (float)((float)(atan2f(diff_y, diff_x) * 360.0) * 0.25) / 1.5707963;
if (angle.x < 0.0)
angle.x = angle.x + 360.0;
if (angle.y < 0.0)
angle.y = angle.y + 360.0;
GameControler.write<D3DXVECTOR3>(PlayerController + offsets_Pitch, angle);
// printf("Write X : %f \t Y : %f\n", angle.x, angle.y);
}
}
int main() {
printf("Strat Cheating......\n");
while (1) {
Cheat();
// Sleep(500);
}
}
|
Cheat2
思路:因为要实现自瞄,而镜头又不知道还有什么别的攻击方式,因此换了一个思路,把子弹出发的坐标改到敌人的坐标上,这样可以开枪直接就可以打死敌人 并且可以无视任何建筑物。我从子弹数量入手,找到游戏开枪的地方,开枪的地方一定会有一个子弹开始坐标,我们先用CE搜索子弹数量,并对其下访问断点
![决赛_外挂实现_1](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_%E5%A4%96%E6%8C%82%E5%AE%9E%E7%8E%B0_1.png)
定位到了子弹数量减少处。因为要实现修改子弹发射点到敌人坐标处,我们需要找到子弹发射的地方,因此这里向上回溯
回溯了一层发现大量的call,我们在头部下断,粗粗看了一下这些call,发现在子弹减少call的上一个call里调用了rand函数。
![决赛_外挂实现_2](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_%E5%A4%96%E6%8C%82%E5%AE%9E%E7%8E%B0_2.png)
因为弹道是具有随机性的, rand函数引起了我的注意,然后想起第一天在百度上搜到的关于这款游戏的开发文档的源码
![决赛_外挂实现_3](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_%E5%A4%96%E6%8C%82%E5%AE%9E%E7%8E%B0_3.png)
上下看了一下发现其他部分也很像,认为这个call就是武器开火函数
![决赛_外挂实现_4](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_%E5%A4%96%E6%8C%82%E5%AE%9E%E7%8E%B0_4.png)
![决赛_外挂实现_5](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_%E5%A4%96%E6%8C%82%E5%AE%9E%E7%8E%B0_5.png)
从call的头部下断 然后在游戏中开枪 再往下可以看到调用了函数取了一个坐标,我不知道这个坐标具体是个什么坐标 但是肯定是跟弹道有关的坐标
![决赛_外挂实现_6](/images/CTF/%E8%85%BE%E8%AE%AF%E6%B8%B8%E6%88%8F%E5%AE%89%E5%85%A8_2021/%E5%86%B3%E8%B5%9B_%E5%A4%96%E6%8C%82%E5%AE%9E%E7%8E%B0_6.png)
我通过在此处下断点,面对墙壁开枪,并修改了这些值,发现墙壁上没有出现弹孔,认为这些值就是控制子弹的起始坐标,而下面这个call就是碰撞call。如果能修改这个参数为敌人的坐标,应该就可以实现子弹全图自瞄。 我采用了外部注入shellcode hook此处,跳转到写入的自瞄shellcode上,完成了参数的修改。经测试,确实可以达到开枪后子弹能直接打中敌人并且无视建筑物的效果。 同时,在我添加bot后发现bot开枪也是经过的这个函数 所以我们hook这个函数 不仅可以让bot的子弹打不中我们 我们还可以打死这些bot (同时实现了无敌跟子弹穿墙追踪的效果)
下面贴一下shellcode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// AimBotShell
push rcx | 保护寄存器 压栈
push rdx | 保护寄存器 压栈
mov rcx,6666666666666666 | target_pos_addr
mov rdx,qword ptr ds:[rcx] | 读取xy 两个8字节 所以直接用rdx
mov qword ptr ss:[rsp+70],rdx | 更改0x60这个位置 因为前面保护寄存器压栈了0x10字节,所以这里是0x70
mov edx,dword ptr ds:[rcx+8] | 读取z坐标 4字节 所以是edx
mov dword ptr ss:[rsp+78],edx |
pop rdx | 恢复寄存器
pop rcx | 恢复寄存器 先进后出
mulss xmm8,xmm0 | 执行被破坏的原代码
mulss xmm7,xmm0 |
addss xmm8,dword ptr ss:[rsp+60] |
push 66666666 |跳回原处
mov dword ptr ss:[rsp+4],6666 |
ret |
|
1
2
3
4
|
// JmpShell 为了不影响寄存器 我们用栈做跳板 这样还能支持跨4gb跳转
push 66666666h
mov dword ptr [rsp+4], 6666h
retn
|
待外挂启动后,会先将shellcode注入游戏空间,并跨进程的不断枚举游戏对象列表,发现存活的敌人便将其坐标写入自瞄坐标内存上,shellcode会在每次开枪的时候将自瞄坐标内存上的值替换到碰撞call的参数上,以此就可以实现站着不动开枪杀死全图敌人且无敌的效果。
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
// Cheat2.cpp
include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <cstring>
#include "Offset.hpp"
#include "Myd3d.hpp"
#include "GameControler.hpp"
// #include "GameControlerByDriver.hpp"
using namespace std;
CGameControler GameControler("ShooterClient.exe");
D3DXVECTOR3 target_pos;
DWORD64 target_pos_addr, payload_addr;
BYTE AimBotShell[] = "\x51\x52\x48\xB9\x66\x66\x66\x66\x66\x66\x66\x66\x48\x8B\x11\x48\x89\x54\x24\x70\x8B\x51\x08\x89\x54\x24\x78\x5A\x59\xF3\x44\x0F\x59\xC0\xF3\x0F\x59\xF8\xF3\x44\x0F\x58\x44\x24\x60\x68\x66\x66\x66\x66\xC7\x44\x24\x04\x66\x66\x00\x00\xC3";
/* AimBotShell
push rcx | 保护寄存器 压栈
push rdx | 保护寄存器 压栈
mov rcx, 6666666666666666 | target_pos_addr
mov rdx, qword ptr ds : [rcx] | 读取xy 两个8字节 所以直接用rdx
mov qword ptr ss : [rsp + 70] , rdx | 更改0x60这个位置 因为前面保护寄存器压栈了0x10字节,所以这里是0x70
mov edx, dword ptr ds : [rcx + 8] | 读取z坐标 4字节 所以是edx
mov dword ptr ss : [rsp + 78] , edx |
pop rdx | 恢复寄存器
pop rcx | 恢复寄存器 先进后出
mulss xmm8, xmm0 | 执行被破坏的原代码
mulss xmm7, xmm0 |
addss xmm8, dword ptr ss : [rsp + 60] |
push 66666666 | 跳回原处
mov dword ptr ss : [rsp + 4] , 6666 |
ret |
*/
BYTE JmpShell[] = "\x68\x66\x66\x66\x66\xC7\x44\x24\x04\x66\x66\x00\x00\xC3";
/* JmpShell
push 66666666h
mov dword ptr [rsp+4], 6666h
retn
*/
string GetName(DWORD64 Entity)
{
DWORD64 Index = GameControler.read<DWORD>((Entity + 0x18));
DWORD64 Base = GameControler.read<DWORD64>((DWORD64)GameControler.GetGameBase() + offset_Name);
DWORD64 tmp1 = GameControler.read<DWORD64>(Base + 8 * ((Index) / 0x4000));
DWORD64 tmp2 = GameControler.read<DWORD64>(tmp1 + 8 * ((Index) % 0x4000));
return GameControler.ReadString((LPCVOID)(tmp2 + 0xC), 64);
}
int Cheat()
{
DWORD64 Uworld = GameControler.read<DWORD64>((DWORD64)GameControler.GetGameBase() + offsets_UWorld);
// printf("Uworld:%llx\n", Uworld);
DWORD64 ULevel = GameControler.read<DWORD64>(DWORD64(Uworld + offsets_Ulevel));
DWORD64 AActor = GameControler.read<DWORD64>(DWORD64(ULevel + offsets_Actor));
DWORD num = GameControler.read<DWORD>(DWORD64(ULevel + offsets_Actor) + 8); // 取对象数量
DWORD64 GameInstance = GameControler.read<DWORD64>(DWORD64(Uworld + offsets_GameInstance));
DWORD64 LocalPlayer = GameControler.read<DWORD64>( GameControler.read<DWORD64>(DWORD64(GameInstance + offsets_LocalPlayer)) );
DWORD64 PlayerController = GameControler.read<DWORD64>(DWORD64(LocalPlayer + offsets_PlayerController));
DWORD64 LocalPawn = GameControler.read<DWORD64>(DWORD64(PlayerController + offsets_LocalPawn));
// printf("My HP:%f\n\n", GameControler.read<float>((DWORD64)(LocalPawn + offsets_Health)));
DWORD64* EntityList = (DWORD64*)malloc((num + 1) * sizeof(DWORD64*));
GameControler.Read((LPCVOID)AActor, EntityList, num * sizeof(DWORD64*));
for (int i = 0; i < num; ++i) {
DWORD64 Entity = EntityList[i];
if (Entity == LocalPawn) continue;
if (!Entity) continue;
float HP = GameControler.read<float>((DWORD64)(Entity + offsets_Health));
if (HP <= 0.f) continue;
string sName = GetName(Entity);
if (strcmp(sName.c_str(), "BotPawn_C")) continue;
DWORD64 playobject = GameControler.read<DWORD64>(DWORD64(Entity + offsets_PlayObject));
float targetX = GameControler.read<float>(DWORD64(playobject + offsets_TargetX));
float targetY = GameControler.read<float>(DWORD64(playobject + offsets_TargetY));
float targetZ = GameControler.read<float>(DWORD64(playobject + offsets_TargetZ));
// printf("[*] target[%d] : %s : X: %f \t Y : %f \t Z : %f\n", i, sName.c_str(), targetX, targetY, targetZ);
target_pos = GameControler.read<D3DXVECTOR3>(playobject + offsets_TargetX);
GameControler.Write((LPVOID)target_pos_addr, target_pos, 12); // 写入敌人坐标至自瞄坐标
}
free(EntityList);
}
int main() {
printf("Strat Cheating......\n");
target_pos_addr = (DWORD64)VirtualAllocEx(GameControler.GetHandle(), NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (target_pos_addr == NULL) {
printf("VirtualAllocEx target_pos_addr Error! Error Code:%d\n", GetLastError());
exit(0);
}
payload_addr = (DWORD64)VirtualAllocEx(GameControler.GetHandle(), NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (payload_addr == NULL) {
printf("VirtualAllocEx payload_addr Error! Error Code:%d\n", GetLastError());
exit(0);
}
DWORD64 HookAddress = (DWORD64)GameControler.GetGameBase() + 0x51C48C;
DWORD64 ReturnAddress = (DWORD64)GameControler.GetGameBase() + 0x51C49C;
*(DWORD64*)(AimBotShell + 0x4) = target_pos_addr;
*(DWORD*)(AimBotShell + 0x2E) = *(DWORD32*)(&ReturnAddress);
*(DWORD*)(AimBotShell + 0x36) = *(DWORD32*)((DWORD64)(&ReturnAddress) + 4);
*(DWORD*)(JmpShell + 0x1) = *(DWORD32*)(&payload_addr);
*(DWORD*)(JmpShell + 0x9) = *(DWORD32*)((DWORD64)(&payload_addr) + 4);
GameControler.Write((LPVOID)payload_addr, AimBotShell, sizeof(AimBotShell) - 1);
DWORD OldProtect{ 0 };
BOOL nStatus = TRUE;
nStatus = VirtualProtectEx(GameControler.GetHandle(), (LPVOID)HookAddress, 0x1000, PAGE_EXECUTE_READWRITE, &OldProtect);
GameControler.Write((LPVOID)HookAddress, JmpShell, sizeof(JmpShell) - 1);
nStatus = VirtualProtectEx(GameControler.GetHandle(), (LPVOID)HookAddress, 0x1000, OldProtect, &OldProtect);
printf("target_pos_addr:%llx \t payload_addr:%llx\n", target_pos_addr, payload_addr);
if (nStatus == TRUE)
printf("Cheat Init Success!\n");
else {
printf("Cheat Init Fail!\t Error Code:%d\n", GetLastError());
exit(-1);
}
while (1) {
Cheat();
// Sleep(500);
}
}
|
至此,外挂实现完毕