首页
论坛
课程
招聘
[原创]2022腾讯游戏安全竞赛PC赛道初赛writeup
2022-4-20 02:23 5044

[原创]2022腾讯游戏安全竞赛PC赛道初赛writeup

2022-4-20 02:23
5044

补丁:

1
2
3
4
5
6
7
8
9
10
11
02 00 00 00 00 00 00 00 03 00 00 00 02 00 00 00 01 00 00 00 00 00 00 00 02 00 00 00 03 00 00 00 01 00 00 00
=>
02 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00
 
03 00 00 00 E8 03 00 00
=>
03 00 00 00 00 00 00 00
 
03 00 00 00 F4 01 00 00
=>
03 00 00 00 00 00 00 00

分析过程:
IDA静态分析,互斥体防多开,窗口创建流程,D3D设备初始化,窗口消息循环处初始化shellcode并调用DX绘制流程
运行后一段时间程序停止绘制,在绘制流程中把GetTickCount相关时间检测的流程打补丁,不过多赘述
根据文档说明需要将一面黄色的棋子绘制出来,首先猜测可能跟顶点的绘制模式有关,ida动态调试加载符号信息,将DX相关的虚函数进行标识

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
__int64 __usercall sub_7FF62C2E5040@<rax>(...)
{
/*省略一些代码*/
  *(_OWORD *)(a1 - 72) = a6;
  *(_OWORD *)(a1 - 88) = a7;
  *(_OWORD *)(a1 - 104) = a8;
  v20 = a5;
  *(_OWORD *)(a1 - 120) = a9;
  v21 = a4;
  v22 = a2;
  v23 = a3;
  v24 = *(__int64 **)&a16;
  a12 = 1;
  v25 = a15 + (a4 ^ (a3 + a2)) % 256 - a5 % 256;
  (*(void (__fastcall **)(_QWORD, signed int *, char *))(**(_QWORD **)&a16 + 0x2F8i64))(*(_QWORD *)&a16, &a12, &v45);// RSGetViewports
  v26 = (v21 ^ v22 * v23) % 256 - (v20 >> 8) % 256;
  v27 = (float)(v47 - (float)(2 * (v26 + v22) - 1)) / v47;
  v28 = (float)(v47 - (float)(2 * v22 + 99)) / v47;
  v29 = (float)((float)(2 * (v26 + v23) - 1) - v46) / v46;
  v30 = (float)((float)(2 * v23 + 99) - v46) / v46;
  (*(void (__fastcall **)(__int64 *, _QWORD, _QWORD, signed __int64, _DWORD, float **))(*v24
                                                                                      + 0x70))(// Map
    v24,
    *(_QWORD *)&a17,
    0i64,
    4i64,
    0,
    &v44);
/*....*/
  (*(void (__fastcall **)(__int64 *, _QWORD, _QWORD))(*v24 + 0x78))(v24, *(_QWORD *)&a17, 0i64);// UnMap
  v42 = *v24;
  a14 = 28;
  a13 = 0;
  (*(void (__fastcall **)(__int64 *, _QWORD, signed __int64, int *, signed int *, int *))(v42
                                                                                        + 0x90))(// IASetVertexBuffers
    v24,
    0i64,
    1i64,
    &a17,
    &a14,
    &a13);
  (*(void (__fastcall **)(__int64 *, signed __int64))(*v24 + 0xC0))(v24, 5i64);// IASetPrimitiveTopology
  (*(void (__fastcall **)(__int64 *, __int64))(*v24 + 0x88))(v24, a18);// IASetInputLayout
  (*(void (__fastcall **)(__int64 *, __int64, _QWORD, _QWORD))(*v24 + 0x58))(v24, a19, 0i64, 0i64);// SetShader
  (*(void (__fastcall **)(__int64 *, __int64, _QWORD, _QWORD))(*v24 + 0x48))(v24, a20, 0i64, 0i64);// SetShader
  (*(void (__fastcall **)(__int64 *, _QWORD, _QWORD, _QWORD))(*v24 + 0xB8))(v24, 0i64, 0i64, 0i64);// SetShader
  return (*(__int64 (__fastcall **)(__int64 *, signed __int64))(*v24 + 0x68))(v24, 4i64);// Draw
}

IASetPrimitiveTopology 的参数依据msdn尝试修改一下,然而蓝色的方块有变化,黄色方块依旧没有画出来,感觉走远了。
回到shellcode的初始化过程中

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
void __fastcall sub_7FF62C2E1090(__int64 a1)
{
/*...*/
  v1 = a1;
  v2 = *(_QWORD *)(a1 + 0x28);
  if ( v2 )
  {
    v3 = *(_QWORD *)(v1 + 0x38);
    v9 = xmmword_7FF62C2E3480;
    (*(void (__fastcall **)(__int64, __int64, __int128 *))(*(_QWORD *)v2 + 0x190i64))(v2, v3, &v9);// ClearRenderTargetView
    if ( !byte_7FF62C2E8314 )
    {
      _mm_storeu_si128((__m128i *)ProcName, _mm_load_si128((const __m128i *)&xmmword_7FF62C2E3490));
      Dst = 0i64;
      v8 = 11257i64;
      strcpy(ModuleName, "ntdll.dll");
      v12 = 1835355500;
      v13 = 7959151;
      v4 = GetModuleHandleA(ModuleName);
      v5 = GetProcAddress(v4, ProcName);
      if ( v5 )
        ((void (__fastcall *)(signed __int64, void **, _QWORD, __int64 *, signed int, signed int))v5)(
          -1i64,
          &Dst,
          0i64,
          &v8,
          4096,
          64);
      v6 = (char *)Dst;
      qword_7FF62C2E8308 = (__int64)Dst;
      if ( Dst )
      {
        memcpy(Dst, sub_7FF62C2E5040, 0x1301ui64);// 填充代码
      }
      else
      {
        *errno() = 22;
        invalid_parameter_noinfo();
      }
      *(_DWORD *)v6 = 0x53C48B48;
      *((_DWORD *)v6 + 1) = 0x41575655;
      *((_WORD *)v6 + 4) = 0x4156;
      if ( v6 == (char *)0xFFFFFFFFFFFFECFFi64 )
      {
        *errno() = 22;
        invalid_parameter_noinfo();
      }
      else
      {
        memcpy(v6 + 0x1301, &unk_7FF62C2E6350, 0x18F0ui64);// 填充数据
      }
      *(_QWORD *)(v6 + 0x2BF1) = D3DCompile;
      qword_7FF62C2E8318 = (__int64 (__fastcall *)(_QWORD))(v6 + 0x650);//***************
      *((_DWORD *)v6 + 411) = 9601;
      *(_DWORD *)(v6 + 1097) = 3764;
      *((_DWORD *)v6 + 384) = 4865;
      byte_7FF62C2E8314 = 1;
      dword_7FF62C2E8310 = GetTickCount();
    }
    if ( qword_7FF62C2E8318 )
    {
      qword_7FF62C2E8318(*(_QWORD *)(v1 + 0x30));//调用shellcode
      GetTickCount();
    }
    (*(void (__fastcall **)(_QWORD, _QWORD, _QWORD))(**(_QWORD **)(v1 + 0x30) + 64i64))(
      *(_QWORD *)(v1 + 0x30),
      0i64,
      0i64);                                    // dxgi!Present
  }
}

shellcode调用的地方首先是对一些绘制的buffer进行初始化,然后调用一个解析器,最后对buffer进行回收,buffer初始化和回收的代码就不贴了,直接贴解析器部分的代码

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
__int64 __usercall sub_7FF62C2E5460@<rax>(int a1@<r8d>, signed int a2@<r9d>, __int128 *a3@<xmm6>, __int128 *a4@<xmm7>, __int128 *a5@<xmm8>, __int128 *a6@<xmm9>, __int64 a7, signed int a8, signed int a9)
{
  __int64 v9; // rsi
  signed int v10; // er14
  int v11; // er15
  __int64 result; // rax
  __int64 v13; // rcx
  int v14; // edx
  int v15; // er9
  int v16; // eax
  BYTE *v17; // [rsp+50h] [rbp-30h]
  __int64 v18; // [rsp+58h] [rbp-28h]
  __int128 v19; // [rsp+60h] [rbp-20h]
  __int64 v20; // [rsp+70h] [rbp-10h]
  int v21; // [rsp+78h] [rbp-8h]
  int v22; // [rsp+7Ch] [rbp-4h]
 
  v17 = &ContextRecord.FltSave.Reserved4[48];
  v9 = 0i64;
  v10 = a2;
  v11 = a1;
  v18 = 0i64;
  _mm_storeu_si128((__m128i *)&v19, (__m128i)0i64);
  v20 = 0i64;
  v21 = 50;
  v22 = 50;
  while ( 2 )
  {
    result = (__int64)v17;
    switch ( *(_DWORD *)&v17[4 * v9] )
    {
      case 0:
        result = HIDWORD(v18);
        LODWORD(v18) = HIDWORD(v18) + v18;
        goto LABEL_10;
      case 1:
        result = HIDWORD(v18);
        LODWORD(v18) = v18 - HIDWORD(v18);
        goto LABEL_10;
      case 2:
        v13 = *(signed int *)&v17[4 * v9 + 4];
        v9 += 2i64;
        result = *((unsigned int *)&v18 + v13);
        *((_DWORD *)&v18 + *(signed int *)&v17[4 * v9]) = result;
        goto LABEL_10;
      case 3:
        v14 = *(_DWORD *)&v17[4 * v9 + 4];
        v9 += 2i64;
        result = (__int64)v17;
        *((_DWORD *)&v18 + *(signed int *)&v17[4 * v9]) = v14;
        goto LABEL_10;
      case 4:
        ++v9;
        v15 = v18;
        v16 = v18 * (HIDWORD(v18) + 1);
        LODWORD(v18) = *(_DWORD *)&v17[4 * v9] ^ 0x414345;
        result = (unsigned int)((signed int)(v18 ^ (HIDWORD(v18) + v15)) % 256
                              + (((signed int)(v18 ^ v15 * HIDWORD(v18)) % 256
                                + ((signed int)(v18 ^ (HIDWORD(v18) + v16)) % 256 << 8)) << 8));
        HIDWORD(v18) = result;
        goto LABEL_10;
      case 5:
        result = sub_7FF62C2E5040(
                   a7,
                   SHIDWORD(v19),
                   SDWORD2(v19),
                   v20,
                   SHIDWORD(v20),
                   a3,
                   a4,
                   a5,
                   a6,
                   -256,
                   v11,
                   v10,
                   a7,
                   a8,
                   a9,
                   (int)v17,
                   v18,
                   v19,
                   *((__int64 *)&v19 + 1),
                   v20);
        goto LABEL_10;
      case 6:
        result = sub_7FF62C2E5040(
                   a7,
                   SHIDWORD(v19),
                   SDWORD2(v19),
                   v20,
                   SHIDWORD(v20),
                   a3,
                   a4,
                   a5,
                   a6,
                   -13771801,
                   v11,
                   v10,
                   a7,
                   a8,
                   a9,
                   (int)v17,
                   v18,
                   v19,
                   *((__int64 *)&v19 + 1),
                   v20);
        goto LABEL_10;
      case 7:
        return result;
      default:
LABEL_10:
        if ( (unsigned __int64)++v9 < 0x58F )
          continue;
        return result;
    }
  }
}

7种命令
case 0是加法,case 1 是减法,case 2是存储器到存储器的赋值,case 3是立即数到存储器的赋值,case 4是某种加密算法,经过一些简单补丁可以验证case 5是画黄色方块 case 6画蓝色方块,case 7是程序退出

 

简单的写一个py脚本

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
import idc
data = 0x7FF62C2E6350
i = 0
cmd = 0
stack = [0 for i in range(10)]
i = 0
while(i <= 1596):
    cmd = idc.Dword(data+(i*4))
    p1 = idc.Dword(data+(i+1)*4)
    p2 = idc.Dword(data+(i+2)*4)
    #print("ip",i,hex(data+i*4),cmd)
    i=i+1
    if cmd == 0:
        stack[0] = stack[0] + stack[1]
        print("stack[0]=%d"%(stack[0]), "add")
        continue
    if cmd == 1:
        stack[0] = stack[0] - stack[1]
        print("stack[0]=%d"%(stack[0]), "sub")
        continue
    if cmd == 2:
        i=i+2
        stack[p2] = stack[p1]
        print("stack[%d]=%d"%(p2,stack[p1]), cmd, i)
        continue
    if cmd == 3:
        i=i+2
        stack[p2] = p1
        print("stack[%d]=%d"%(p2,p1), cmd)
        continue
    if cmd == 4:
        i=i+1
        v15 = stack[0]
        v16 = stack[0] * (stack[1] + 1)
        stack[0] = p1 ^ 0x414345
        stack[1] = (stack[0] ^(stack[1] + v15)) % 256
        + (((stack[0] ^(stack[1] * v15)) % 256
        + ((stack[0] ^(stack[1] + v16)) % 256) << 8) << 8)
        print("crypt stack[0]=%d"%stack[0])
        print("crypt stack[1]=%d"%stack[1])
        continue
    if cmd == 5 or cmd == 6:
        print(stack[:8])
        print(cmd,"draw")
        continue
    if cmd == 7:
        break
    print("ip",i,"error")
    break

case 4好像写的不太对,先凑合着用
然后经过修修改改一段时间发现画case 6的跟画case 5的不太一样啊,明明都是画方块,不太对劲
画case 6即蓝色方块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
('stack[0]=900', 2L, 1563)
('stack[1]=420', 3L)
('stack[0]=1320', 'add')
('stack[4]=1320', 2L, 1570)
('stack[0]=0', 2L, 1573)
('stack[1]=240', 3L)
('stack[0]=240', 'add')
('stack[5]=240', 2L, 1580)
('stack[0]=1320', 2L, 1583)
('stack[1]=240', 2L, 1586)
crypt stack[0]=14120879
crypt stack[1]=183
('stack[6]=14120879', 2L, 1591)
('stack[7]=183', 2L, 1594)
[14120879L, 183L, 0, 12856715L, 1320L, 240L, 14120879L, 183L]
(6L, 'draw')

画case 5即黄色方块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
('stack[0]=0', 2L, 3)
('stack[4]=0', 2L, 6)
('stack[0]=0', 2L, 9)
('stack[1]=1000', 3L)
('stack[0]=-1000', 'sub')
('stack[4]=-1000', 2L, 16)
('stack[0]=0', 2L, 19)
('stack[5]=0', 2L, 22)
('stack[0]=-1000', 2L, 25)
('stack[1]=0', 2L, 28)
crypt stack[0]=1248208
crypt stack[1]=200
('stack[3]=1248208', 2L, 33)
('stack[0]=200', 2L, 36)
('stack[1]=1248208', 2L, 39)
('stack[6]=200', 2L, 42)
('stack[7]=1248208', 2L, 45)
[200L, 1248208L, 0, 1248208L, -1000L, 0, 200L, 1248208L]
(5L, 'draw')

蓝色方块绘制流程中crypt之后只有两条语句
黄色方块绘制流程中crypt之后还有额外的三条语句
所以这个ce搜一下批量打一下补丁

1
2
3
02 00 00 00 00 00 00 00 03 00 00 00 02 00 00 00 01 00 00 00 00 00 00 00 02 00 00 00 03 00 00 00 01 00 00 00
=>
02 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00

(当然全部改成8也可以,switch case default分支会自动到下一个指令)
然后找了个蓝色方块修改一下立即数赋值的语句,感受一下坐标系,发现黄色方块中的立即数赋值不太合理,手动修改一两个发现黄色方块回来了,于是乎把赋值为500和1000修改一下

1
2
3
4
5
6
7
03 00 00 00 E8 03 00 00
=>
03 00 00 00 00 00 00 00
 
03 00 00 00 F4 01 00 00
=>
03 00 00 00 00 00 00 00

【看雪培训】《Adroid高级研修班》2022年夏季班招生中!

收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回