-
-
[原创]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 |