首页
社区
课程
招聘
[原创]腾讯PC客户端复赛题解
2021-4-12 23:49 8447

[原创]腾讯PC客户端复赛题解

2021-4-12 23:49
8447

flag:


1.

 

首先是解密文件名,然后打开文件,获取文件大小,申请同样大小的内存,然后将目标文件读到内存中。
2.
IDA动态追踪了一下,发现打开的标志是 OPEN_EXISTING,但是并没有提供 hack.dat 这个文件。

 

于是我创建了个hack.data 这个文件,内容随机写了一串字符串"1111111111111111111"。

3.
读取文件之后,发现调用了一个算法函数,然后计算了另一个函数的大小,然后将算法函数申请的内存的值,赋给ProcName,发现这个ProcName的大小正好是"ShooterClient.exe"的长度。

所以推测为:前面的一系列操作,目的是解密出游戏的进程名,加密字符串的目的是规避字符串检测。

 

然后,后面的进程快照的遍历,证实了这个推测。

1
2
注意:Eprocess +0x2d8 ImageFileName    : [15] UChar
进程快照遍历的只是进程名的前15个字节。

4.

 

然后进程快照遍历,通过字符串对比,找到游戏进程的PID。
为了方便,直接通过x64dbg,在OpenProcess调用前,修改PID参数为游戏进程。

5.

 

然后是 GetProcAddress了五次,

 

获取了LdrLoadDLL (LoadLibrary),RtlInitAnsiString,RtlFreeUnicodeString,LdrGetProcedureAddress(GetProcAddress),NtAllocateVirtual,RtlAnsiStringtoUnicodeString。

 

先是猜测注入,然后发现是修复DLL的导入表。


5.
最后是,在游戏进程申请一块内存,然后写入ShellCode,首先写入了一个DLL,然后写入了一个函数(ShellCode),最后写入一个缓冲区。
最后是创建一个远程线程,执行函数。

6.

 

效果就是右键自瞄,但是没有判断障碍物。

7.

 

继续x64dbg追踪,先是从内存中dump下来要注入的dll文件。

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
#include<windows.h>
#include<stdio.h>
 
int main()
{
    HANDLE a=CreateFileA("C:\\Users\\TalShang\\Desktop\\PC客户端题目\\复赛\\dump dll\\Project1\\Project1\\dump.dll",
        FILE_ALL_ACCESS,TRUE,NULL, OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,0);
 
    DWORD pid = 0x4028;
    HANDLE handle=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);
    /*DWORD b=GetLastError();
    printf("Error:%x",b);*/
    DWORD64 addr = 0x000001B7F00C9080;
    char* mem=(char*)malloc(0xFA00);
    SIZE_T readSize=0;
    ReadProcessMemory(handle,(LPVOID)addr,mem, 0xFA00,&readSize);
 
    WriteFile(a, mem, 0xFA00,NULL,NULL);
 
 
    system("pause");
    CloseHandle(a);
    CloseHandle(handle);
    return 0;
}


8.
在游戏进程内追踪Shellcode。

 

发现先是调用NtVirtualAllocate 申请内存后,把注入的DLL贴过来。

 

然后是通过LdrLoadDLL和LdrGetProcudre 来修复DLL的导入表

 



user32.dll

 

9.最后是调用DllEntry。

10.分析DLL的功能实现

 

这里发现输出flag:开启自瞄成功

 

 

这里发现是三角函数自瞄。

11.

 

自己写自瞄。

 

1.首先写个注入

 

因为提示不用在意注入方式和读写方式,所以采用简单的远程线程注入,不使用外挂程序提供的内存注入。

 

用dump出来的dll测试注入,注入成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<windows.h>
#include<stdio.h>
 
int main()
{
    DWORD pid = 21492;
    HANDLE handle=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);
    if (!handle) return 0;
    PVOID mem=VirtualAllocEx(handle, NULL, 0x100, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    char* Dllpath = (char*)"C:\\Users\\TalShang\\Desktop\\PC客户端题目\\复赛\\dump dll\\Project1\\Project1\\dump.dll";
    SIZE_T retSize = 0;
    BOOLEAN isflag=WriteProcessMemory(handle, mem, Dllpath,strlen(Dllpath)+1,&retSize);
    if (!isflag) return 0;
    LPTHREAD_START_ROUTINE loadDll=(LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("kernel32.dll"),"LoadLibraryA");
    HANDLE thread = CreateRemoteThread(handle,NULL,NULL,loadDll,mem,NULL,NULL);
    system("pause");
    CloseHandle(thread);
    CloseHandle(handle);
    return 0;
}

2.

 

写个外部自瞄的DLL,自瞄热键同样设置为鼠标右键。

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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
//此处我省略了部分内容,封装的读写没有贴出来。
 
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include<iostream>
typedef struct _MATRIX {
    union {
        struct {
            float        _11, _12, _13, _14;
            float        _21, _22, _23, _24;
            float        _31, _32, _33, _34;
            float        _41, _42, _43, _44;
 
        };
        float m[4][4];
    };
} Matrix;
 
typedef struct _VECTOR3 {
    float x;
    float y;
    float z;
} Vector3;
 
typedef struct _VECTOR2 {
    float x;
    float y;
} Vector2;
 
 
typedef struct TAimBot
{
    bool bLook;
    ULONG64 dwObj;
    ULONG64 dwObjMesh;
    Vector3 Position;
    FLOAT Value1;
    FLOAT Value2;
}AimBot;
 
 
/*计算2d距离*/
float Get2dDistance(float x1, float y1, float x2, float y2)
{
    float dDistance;
    x1 = x1 - x2;
    y1 = y1 - y2;
    dDistance = sqrt(x1 * x1 + y1 * y1);
    return dDistance;
}
 
 
/*计算3d距离*/
float Get3dDistance(Vector3 MyPos, Vector3 TargerPos, int Divide)
{
    float fDistance;
    Vector3 vec;
    vec.x = TargerPos.x - MyPos.x;
    vec.y = TargerPos.y - MyPos.y;
    vec.z = TargerPos.z - MyPos.z;
    fDistance = sqrt(pow(vec.x, 2) + pow(vec.y, 2) + pow(vec.z, 2));
    return fDistance / Divide;
}
 
/*读坐标*/
Vector3 ReadVector3(ULONG64 addr, DWORD Type)
{
    return ReadMem<Vector3>(addr + Type);
}
 
/*3d->2d*/
DWORD g_dwWidth,g_dwHeight;
bool WorldToScreen_2D(Matrix viewWorld, Vector3 TargerPos, Vector2* result)
{
    float fViewW = viewWorld._14 * TargerPos.x + viewWorld._24 * TargerPos.y + viewWorld._34 * TargerPos.z + viewWorld._44;
    if (fViewW < 0.01f) { return false; }
    fViewW = 1 / fViewW;
    float fBoxX = g_dwWidth/2 + (viewWorld._11 * TargerPos.x + viewWorld._21 * TargerPos.y + viewWorld._31 * TargerPos.z + viewWorld._41) * fViewW * g_dwWidth/2;
    float fBoxY = g_dwHeight/2 - (viewWorld._12 * TargerPos.x + viewWorld._22 * TargerPos.y + viewWorld._32 * TargerPos.z + viewWorld._42) * fViewW * g_dwHeight/2;
    result->x = fBoxX;
    result->y = fBoxY;
    return true;
}
 
 
/*读矩阵*/
void GetMatrix(ULONG64 Addr, Matrix* ViewWorld)
{
    *ViewWorld = ReadMem<Matrix>(ReadMem<ULONG64>(ReadMem<ULONG64>(Addr) + 0x20) + 0x270);
}
 
int toMove(int a)
{
    a = fabs(a);
    if (a > 200)
        return 50;
    else if (a > 100)
        return 25;
    else if (a > 80)
        return 25;
    else if (a > 60)
        return 20;
    else if (a > 45)
        return 15;
    else if (a > 20)
        return 5;
    else if (a > 9)
        return 3;
    else if (a > 3)
        return 1;
    return 0;
}
 
void myMouseMove(int x, int y, float AimSpeed)
{
    int fTargetX = 0, fTargetY = 0;
    fTargetX = x - g_dwWidth / 2;
    fTargetY = y - g_dwHeight / 2;
 
    if (fTargetX > 0)
        fTargetX = toMove(fTargetX);
    else
        fTargetX = -toMove(fTargetX);
 
 
    if (fTargetY > 0)
        fTargetY = toMove(fTargetY);
    else
        fTargetY = -toMove(fTargetY);
 
    mouse_event(1, fTargetX / AimSpeed, fTargetY / AimSpeed, 0, 0);
}
 
std::string GetObjectClassName(ULONG64 Gname, DWORD ID)
{
    ULONG64 ulGname, ulTempAddr, ulTempAddr2;
    DWORD Page, Order;
    static char strObjectType[64] = { "\0" };
    if (ID > 0 && ID < 2000000)
    {
        ulGname=ReadMem<ULONG64>(Gname);
        Page = ID / 16384;
        Order = ID % 16384;
        ulTempAddr=ReadMem<ULONG64>((ULONG64)(ulGname + Page * 8));
        if (ulTempAddr > 0)
        {
            ulTempAddr2=ReadMem<ULONG64>(ulTempAddr + Order * 8);
            if (ulTempAddr2 > 0)
            {
                ReadVirtual((LPVOID)(ulTempAddr2 + 12), strObjectType, 64 * sizeof(char));
            }
        }
    }
    return strObjectType;
}
 
DWORD WINAPI start(DWORD Param)
{
    //AllocConsole();
    //SetConsoleCtrlHandler(NULL, true);
    //freopen( "CONOUT$", "w", stdout);
    Matrix ViewWorld{};
    AimBot aimBot;
    Vector3 position;
    Vector2 result{};
    while (true)
    {
 
       ULONG64  ulBase = (ULONG64)GetModuleHandle(NULL);
       ULONG64 ulUworld = ReadMem<ULONG64>(ulBase+0x2F71060);
       ULONG64 ulGname= ulBase + 0x2E6E0C0;
       ULONG64 ulMatrix = ulBase+0x2BF1100;
       ULONG64 ulUlevel = ReadMem<ULONG64>(ulUworld + 0x30);
       ULONG64 ulActor = ReadMem<ULONG64>(ulUlevel + 0x98);
       DWORD dwCount = ReadMem<DWORD>(ulUlevel + 0xA0);
       GetMatrix(ulMatrix, &ViewWorld);
       g_dwWidth = ReadMem<DWORD>(ulBase +0x2BF3240);
       g_dwHeight = ReadMem<DWORD>(ulBase + 0x2BF3244);  // +4
       //printf("%d %d %d %f\n", dwCount, g_dwWidth, g_dwHeight, ViewWorld._43);
       for (int i = 0; i < dwCount; i++)
       {
           ULONG64 ulObject = ReadMem<ULONG64>(ulActor + i * 8);
           if (ulObject < 0)
               continue;
           DWORD id = ReadMem<DWORD>(ulObject+0x18);
           std::string strType = GetObjectClassName(ulGname, id);
           if (strType.find("BotPawn_C") == strType.npos)
               continue;
           position = ReadVector3(ReadMem<ULONG64>(ulObject + 0x158), 0x1A0);
           if (WorldToScreen_2D(ViewWorld, position, &result))
           {
               if (!aimBot.bLook)
               {
                   aimBot.Value1 = Get2dDistance(g_dwWidth / 2, g_dwHeight / 2, result.x, result.y);
                   if (aimBot.Value1 <= 200.f)
                   {
                       if (aimBot.Value2 == 0)
                       {
                           aimBot.Value2 = aimBot.Value1;
                           aimBot.dwObj = ulObject;
                       }
                       else if (aimBot.Value1 < aimBot.Value2)
                       {
                           aimBot.Value2 = aimBot.Value1;
                           aimBot.dwObj = ulObject;
                       }
                   }
               }
           }
       }
        if (GetAsyncKeyState(2) != 0)
        {
            aimBot.bLook = true;
            position = ReadVector3(ReadMem<ULONG64>(aimBot.dwObj + 0x158), 0x1A0);
            if (WorldToScreen_2D(ViewWorld, position, &result))
            {
                myMouseMove(result.x, result.y,5); //参数3是自瞄速度,如果自瞄乱晃就调高,这个是根据游戏灵敏度修正的,灵敏度高则需要调高。
            }
        }
        else
        {
            aimBot.bLook = false;
            aimBot.dwObj = 0;
            aimBot.dwObjMesh = 0;
            aimBot.Value1 = 0;
            aimBot.Value2 = 0;
        }
    }
}
 
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        DisableThreadLibraryCalls(hModule);
        CreateThread(NULL,NULL,(LPTHREAD_START_ROUTINE) start,NULL,NULL,NULL);
        break;
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

(2).跑完了整个流程,然后对加密细节进行分析。
1.先回到我dump dll的地方。
发现在VirtualProtect前,调用了一次算法函数,注意这里传入的大小为64000(0xFA00),是我们dump出来的DLL的大小。

2.进入算法函数。
发现有两个分支,因为整个过程调用了两次算法函数,一次传入hack.dat的内容,一次传入一个dll。分别对应了这两个分支。

1
2
3
4
5
注意:在x64上,使用了SSE2指令集,xmm系列寄存器除了用于浮点操作时,还经常用于初始化大数组。
_mm_loadu_si128:加载128位值
mm_subs_epu8(a,b):r=a-b
_mm_xor_si128(a,b):r=a^b
注意:IDA7.5版本,可以在F5界面右键选择Disable SSE,查看内联汇编xmm。

3.退出算法函数,往上追。
发现了一个坑,刚开始看到sleep(5000),我一直以为是延时注入,并没有想到这里是printf(),所以在这里栽了个坑。
因为这里printf了一个字符串,猜测前面有字符串比较或者解密,继续往上追。

4.往printf上追
发现了一个字符串的初始化和解密。在解密完这个字符串后,然后和hack.data的内容进行一个字节一个字节的对比,相同则输出flag。

5.现在整个解密流程已经明显了,先是读入hack.dat的内容,然后调用算法函数走第二个分支解密,然后初始化和解密一个字符串,如果hack.dat的解密内容和字符串一致,则输出flag。

 

6.还原hack.dat

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
#include<windows.h>
#include<stdio.h>
 
char buffer[] = {
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,0x00,0x00,0x00 };
int main()
{
    //0x1A0:370E371F350C2019131B011A23233D5C
    //0x190:E9ACEAC0D6DBE7F4E2D5EAEBF9F5FAF6
    //0x180:C1E9E4A4F1FCF5E8FFE1F1EBD5EC2C3D
    //0x170+4:C1F2DAF8
    //0x170:EC97CCED
    //0x168+6:1h
    //0x168+4:92h
    //0x168:0ECC7F2E9h
 
    for (int i = 0; i<sizeof(buffer); i++)
    {
        buffer[i] += 0x13;
        buffer[i] ^= 0x3f;
    }
 
    HANDLE a = CreateFileA("C:\\Users\\TalShang\\Desktop\\PC客户端题目\\复赛\\hack.dat",
        FILE_ALL_ACCESS, TRUE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
    WriteFile(a, buffer, sizeof(buffer), NULL, NULL);
    return 0;
}

7.细节说明:
buffer的内容是我直接从动态调试的IDA里拷贝出来的。

注意:还原的hack.dat的内容应该以0x2C结尾,(0x2C解密后就是0),不用0结尾,对比一直失败,算是一个小坑吧。

8.总结一下,逆向很吃经验,但是也不能完全依靠经验,上文中,笔者就是完全依靠经验,忽略了printf函数,栽了个大跟头。所以大家在逆向小程序的时候,最好是把所有分支走一遍。


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

最后于 2021-4-26 19:04 被0x太上编辑 ,原因:
上传的附件:
收藏
点赞5
打赏
分享
最新回复 (13)
雪    币: 62
活跃值: (652)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
dz默契 2021-4-12 23:56
2
0
牛逼牛逼,支持太上大牛
雪    币: 530
活跃值: (1136)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
青丝梦 2021-4-13 00:00
3
0
来了 来了
雪    币: 0
活跃值: (258)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Mr.zhu 2021-4-13 00:13
4
0
太上老哥牛逼!
雪    币: 5076
活跃值: (4379)
能力值: ( LV5,RANK:65 )
在线值:
发帖
回帖
粉丝
gamehack 2021-4-13 19:33
5
0
66666 
雪    币: 74
活跃值: (7195)
能力值: ( LV9,RANK:335 )
在线值:
发帖
回帖
粉丝
PlaneJun 6 2021-4-13 22:13
6
0
太上大哥牛逼!
雪    币: 200
活跃值: (30)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
月生ahh 2021-4-13 23:24
7
0
牛逼!
雪    币: 2932
活跃值: (1759)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
列明 2021-4-14 00:38
8
0
看不懂,像是看天書一樣,你的一系列步驟都是一步接着一步的,很順暢,可是我看的時候根本不知道下一步該做什麽,為什麽下一步就到了那裏了。怎麽回事呢?
雪    币: 29412
活跃值: (18675)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
kanxue 8 2021-4-22 16:55
9
0
目标程序能上传论坛一份?
雪    币: 392
活跃值: (3999)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
0x太上 2021-4-25 11:19
10
0
kanxue 目标程序能上传论坛一份?
已传 新人第一次发帖忘了附件了
雪    币: 5851
活跃值: (4422)
能力值: ( LV10,RANK:160 )
在线值:
发帖
回帖
粉丝
淡然他徒弟 1 2021-4-25 13:21
11
0
WP写错了

flag不是“自瞄开启成功”

然后进程名称是解密出来的没错 但是解密出来的数据不仅仅作为进程名使用。

还有后面跟hack.exe解密出来的flag进行对比 然后printf

然后如何生成正确的hack.dat也没写...

总结:不建议新手朋友作为正解WP参考 思路可以参考~
希望作者可以重新做一遍~ 更改一下自己的答案 不要误导了新手朋友~
雪    币: 392
活跃值: (3999)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
0x太上 2021-4-25 22:07
12
0
淡然他徒弟 WP写错了 flag不是“自瞄开启成功” 然后进程名称是解密出来的没错 但是解密出来的数据不仅仅作为进程名使用。 还有后面跟hack.exe解密出来的flag进行对比 然后print ...
flag占2分,是有两个flag,没注意printf了一句话是我的失误,图上的flag只能占一分,在这里申明一下,整体的思路和流程是没有任何误导性质的,只是漏解,没有误解,新手朋友们可以自己仔细看一下解密函数,上文我提到了是我的"推测",不知道大家注意到这个词没有.......
雪    币: 5851
活跃值: (4422)
能力值: ( LV10,RANK:160 )
在线值:
发帖
回帖
粉丝
淡然他徒弟 1 2021-4-26 02:44
13
0
0x太上 flag占2分,是有两个flag,没注意printf了一句话是我的失误,图上的flag只能占一分,在这里申明一下,整体的思路和流程是没有任何误导性质的,只是漏解,没有误解,新手朋友们可以自己仔细看一下 ...

不知道有两个flag从何得出呢?(通过两分判断他就是有两个flag?如果你坚持认为有两个flag 可以帮你求证下出题人)

 整个hack.exe唯一有flag关键字的 就只有printf那里 

包括hack.dat的生成 解密函数的还原 都没见你在wp中提到

整个题目有flag关键字的地方 就是正确生成hack.dat后

由hack.exe自己打印的flag


我认为误导是因为 新手朋友来做这题 如果看到了你的wp (因为加优加精了) 会误以为这就是正解wp

按你的流程做完 并不能得到符合题意的flag 符合题意的hack.dat

所以我得出了 这个WP仅仅只能作为参考 并且 可能会误导新手 这个结论


并且贴主你在知道漏解的情况下 我觉得本着对看雪论坛的负责 对其他会看到这个帖子的人负责 


你不应该重新修改下自己的帖子吗?


我并无恶意 只是站在新手朋友跟论坛的角度出发 

最后于 2021-4-26 02:49 被淡然他徒弟编辑 ,原因:
雪    币: 392
活跃值: (3999)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
0x太上 2021-4-26 19:07
14
0
淡然他徒弟 0x太上 flag占2分,是有两个flag,没注意printf了一句话是我的失误,图上的flag只能占一分,在这里申明一下,整体的思路和流程是没有 ...
新人第一次在看雪发帖,不太懂看雪的规则,已经纠正了内容,还望多多海涵。如果您能帮我求证下出题人,看下我的得分,我觉得这是很开心的一件事。
游客
登录 | 注册 方可回帖
返回