首页
论坛
课程
招聘
[原创]辣鸡版2022腾讯游戏安全竞赛PC赛道决赛writeup
2022-5-1 00:30 8291

[原创]辣鸡版2022腾讯游戏安全竞赛PC赛道决赛writeup

2022-5-1 00:30
8291

目录

辣鸡版2022腾讯游戏安全竞赛PC赛道决赛writeup

三环以及通信逻辑分析

  • 打开三环的exe,发现是一个MFC的程序。可以看到打开之后创建了一个线程

    image-20220424203349918

  • 线程中调用了 NtQuerySystemInformation ,并将 D3DCompile 作为了参数中的一员,初步分析是用于通信的代码。接下来到驱动中找对应的通信函数。

    image-20220424203435242

  • sub_140001438 中,可以看到调用了 ZwSetSystemInformation ,且providersinature和三环传入的参数相同。可以判定这里是在注册通信。 sub_1400013E0 是通信的处理函数。image-20220424203720943

  • 跟进去可以发现 sub_1400014A0 是真正的通信处理函数。至此可以判断三环并没有进行什么操作,只是与零环进行了通信,主要逻辑都在零环实现。

sub_1400014A0函数分析

  • 这个函数中,首先获取了 dwm.exeeprocesspid ,然后进行了获取模块地址和获取函数地址的初始化操作。

    image-20220424204133182

  • 接下来根据系统版本的不同,用不同的方法寻找特征码并定位到需要hook的函数

    image-20220424204245579

  • 接下来申请了两块内存,然后将驱动中保存的 shellcode 进行相应的解密并填充到申请的内存地址中去

    image-20220424204418098

  • 之后进行了shellcode中一些参数的填充

    image-20220424204503593

  • 在完成参数的填充后,使用mdl+锁页写内存方式,对dwm.exe中dxgi.dll的相关内容进行修改,达到hook的目的。

    image-20220424204616926

  • 5秒之后重新使用同样的方式,将hook恢复。因此图像只能被绘制5秒。

    image-20220424204734161

  • 再过1.5秒对shellcode的内存进行释放操作

    image-20220424210613062

  • 为了方便之后shellcode的定位,这里将恢复hook和释放内存的代码进行patch,使得shellcode和hook不会被清除。分别将+18cb和+19ab处的 jz 改为 jmp

    image-20220424210347920

    image-20220424210525796

  • 使用CE在 dxgi.dll 中对相应特征码进行查找,可以找到在我本机(21h2)中,hook的位置为 dxgi.dll+6EFC0 。hook后该地址中的内容如下

    image-20220424205114913

对shellcode的分析和flag的绘制

  • 由于该驱动向dwm.exe中注入了shellcode,因此在shellcode被解密之后使用windbg将shellcode dump下来进行分析。shellcode中的内容如下。

    image-20220424205606996

    可以看到和初赛的题目类似,也是有三个函数,点进去看也是使用opcode的虚拟机代码。但是发现sub_0并没有发生任何改变。因此这里直接套用我初赛时的代码对其进行分析。分析发现与初赛不同,本次shellcode的绘制并不是先画11个格子的flag再画31个格子的ACE,而是不按顺序地画。因此需要将初赛时的hookhandler函数稍加修改。每次发现坐标在 -1~1 之外的格子时进行旗帜的绘制。修改后的代码如下

    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
    EXTERN_C VOID HookHandler(PGuestContext context)
    {
        static bool flag = false; if (!flag) { flag = true; A("inline hook成功!"); }
     
        float  xgap = 0.064935, ygap = 0.118343; // 同一格子内部距离
        float xbet = 0.012987, ybet = 0.023669; // 两个相邻格子之间的距离
     
        float trans = getScreenZoom() / 1.25;
        xgap *= trans, ygap *= trans, xbet *= trans, ybet *= trans; // 进行缩放
     
        float origin_a1_x = -0.9305;
        float origin_a1_y = 0.906692;
    #define eps 1e-2
        if (fabs(getScreenZoom() - 1.00) < eps) origin_a1_y = 0.906692; // 缩放100%
        else if (fabs(getScreenZoom() - 1.25) < eps) origin_a1_y = 0.882840; // 缩放125%
        else if (fabs(getScreenZoom() - 1.50) < eps) origin_a1_y = 0.858773; // 缩放150%
        else if (fabs(getScreenZoom() - 1.75) < eps) origin_a1_y = 0.834448; // 缩放175%
     
        if (fabs(getScreenZoom() - 1.00) < eps) origin_a1_x = -0.9472; // 缩放100%
        else if (fabs(getScreenZoom() - 1.25) < eps) origin_a1_x = -0.9305// 缩放125%
        else if (fabs(getScreenZoom() - 1.50) < eps) origin_a1_x = -0.9236// 缩放150%
        else if (fabs(getScreenZoom() - 1.75) < eps) origin_a1_x = -0.9139// 缩放175%
     
        PInfo info = (PInfo)(context->mRcx);
     
        static int ct = 0;
        ct++;
     
        static int wrong_ct = 0;
        bool wrong_flag = false;
     
        if (info->a1.x < -1.0 || info->a1.x > 1.0 ||
            info->a2.x < -1.0 || info->a2.x > 1.0 ||
            info->a3.x < -1.0 || info->a3.x > 1.0 ||
            info->a4.x < -1.0 || info->a4.x > 1.0 ||
     
            info->a1.y < -1.0 || info->a1.y > 1.0 ||
            info->a2.y < -1.0 || info->a2.y > 1.0 ||
            info->a3.y < -1.0 || info->a3.y > 1.0 ||
            info->a4.y < -1.0 || info->a4.y > 1.0
            )
        {
            wrong_flag = true;
            wrong_ct++;
        }
     
        if (wrong_flag)
        {
            if (wrong_ct <= 6)
            {
                float a1x = origin_a1_x;
                float a1y = origin_a1_y - (wrong_ct - 1) * (ygap + ybet);
                info->a1.x = a1x, info->a1.y = a1y;
                info->a2.x = a1x + xgap, info->a2.y = a1y;
                info->a3.x = a1x, info->a3.y = a1y - ygap;
                info->a4.x = a1x + xgap, info->a4.y = a1y - ygap;
            }
            else if (wrong_ct <= 9)
            {
                float a1x = origin_a1_x + (wrong_ct - 6) * (xgap + xbet);
                float a1y = origin_a1_y - (4 - 1) * (ygap + ybet);
                info->a1.x = a1x, info->a1.y = a1y;
                info->a2.x = a1x + xgap, info->a2.y = a1y;
                info->a3.x = a1x, info->a3.y = a1y - ygap;
                info->a4.x = a1x + xgap, info->a4.y = a1y - ygap;
            }
            else if (wrong_ct == 10)
            {
                float a1x = origin_a1_x + (7 - 6) * (xgap + xbet);
                float a1y = origin_a1_y - (2 - 1) * (ygap + ybet);
                info->a1.x = a1x, info->a1.y = a1y;
                info->a2.x = a1x + xgap, info->a2.y = a1y;
                info->a3.x = a1x, info->a3.y = a1y - ygap;
                info->a4.x = a1x + xgap, info->a4.y = a1y - ygap;
            }
            else if (wrong_ct == 11)
            {
                float a1x = origin_a1_x + (8 - 6) * (xgap + xbet);
                float a1y = origin_a1_y - (3 - 1) * (ygap + ybet);
                info->a1.x = a1x, info->a1.y = a1y;
                info->a2.x = a1x + xgap, info->a2.y = a1y;
                info->a3.x = a1x, info->a3.y = a1y - ygap;
                info->a4.x = a1x + xgap, info->a4.y = a1y - ygap;
                wrong_ct = 0;
            }
        }
     
        if (ct == 42) ct = 0;
    }
  • 为了让钩子正确挂到shellcode+30e的位置,需要进行的流程如下

    1. dwm.exe 中注入dll,获取注入之后的dll的关键数据的地址。
    2. 将关键数据的地址写入驱动并编译,然后使用驱动对已经加载的2022GameSafeRace驱动进行对应地址的hook。(这里可以使用零环三环通信来达到目的,但因为时间赶因此直接采用了将信息硬编码到驱动中的方法)。
    3. 对2022GameSafeRace驱动中申请shellcode的代码进行hook,在申请内存时获取shellcode的地址
    4. 对2022GameSafeRace驱动中解密shellcode的代码进行hook,在shellcode解密完成时对shellcode+30e的位置挂上inlinehook跳转到第一步注入的dll中的hook处理函数中。
  • 以下为关键部分的代码。分别在 2022GameSafeRace+16572022GameSafeRace+1791 处挂上inlinehook。hookhandler函数中进行的操作已经在上面给出描述,这里不贴具体代码。

    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
    NTSTATUS solve()
    {
        NTSTATUS status = STATUS_SUCCESS;
        ULONG64 moduleBase = 0;
        SIZE_T size = QuerySysModule("2022GameSafeRace.sys", &moduleBase);
     
        if (!moduleBase)
        {
            KdPrintEx((77, 0, "获取模块失败!\r\n"));
            return STATUS_UNSUCCESSFUL;
        }
     
        //0000000140001657 4C 8D 4D D7  lea     r9, [rbp-29h]   ; RegionSize
        PUCHAR t = moduleBase + 0x1657;
     
        if ((t[0] != 0x4c || t[1] != 0x8d) && (t[0] != 0xff || t[1] != 0x25))
        {
            KdPrintEx((77, 0, "hook点查找失败!\r\n"));
            return STATUS_UNSUCCESSFUL;
        }
     
        g_166a_origin = moduleBase + 0x166A;
     
        char bufcode[] = {
                        0xFF, 0x25, 0x00, 0x00, 0x00, 0x00,
                        0x00, 0x00,
                        0x00, 0x00,
                        0x00, 0x00,
                        0x00, 0x00,
        };
     
        *(PULONG64)&bufcode[6] = (ULONG64)AsmKernelHookHandler;
     
        ULONG t_cr0 = wpoff();
        RtlMoveMemory(t, bufcode, sizeof(bufcode));
        KdPrintEx((77, 0, "patch完成\r\n"));
        KdPrintEx((77, 0, "g_origin = %llx\r\n", g_166a_origin));
        wpon(t_cr0);
        KdPrintEx((77, 0, "166a patch完成\r\n"));
     
        // 1791处下hook
        t = moduleBase + 0x1791;
        g_179F_origin = moduleBase + 0x179f;
        *(PULONG64)&bufcode[6] = (ULONG64)AsmHandler1791;
        t_cr0 = wpoff();
        RtlMoveMemory(t, bufcode, sizeof(bufcode));
        wpon(t_cr0);
        KdPrintEx((77, 0, "1791 patch完成\r\n"));
    }
  • 在注入dll并按步骤加载驱动后,可以看到flag被正确绘制了出来。

对屏幕内容进行截图

方法1

  • 使用github上的一个开源项目https://github.com/lainswork/dwm-screen-shot ,可以截图。

  • 截图的效果如下

    image-20220424213719309

方法2

  • 为了给屏幕上的内容进行截图,想到可以对directX中处理swapchain的函数进行hook。直接从swapchain中取出渲染的图像。这样就能绕过通过将图像绘制在不同dc上而达到的反截图效果。一些游戏辅助的绘制代码也是使用了这个思路,对dwm.exe中swapchain相关的一些函数进行挂钩,然后直接在这里面进行绘制,这样就能绕过一般的截图方法。

  • 具体代码删掉了,找别人要的一份绘制的代码,闭源的,不好往出发(,简单来说就是找了一个hook点,可以拿到swapchain。

  • 这里用了DirectXTK库里面的 SaveWICTextureToFile 来将图像保存到硬盘上,代码如下。

    1
    2
    3
    4
    5
    6
    7
    void screenshot()
    {
        ID3D11Resource* res;
        m_pSwapChain->GetBuffer(0, __uuidof(res), reinterpret_cast<void**>(&res));
        DirectX::SaveWICTextureToFile(g_pD3D11Context, res,
            GUID_ContainerFormatJpeg, L"E:\\SCREENSHOT.JPG");
    }
  • 最后将得到的dll注入到dwm.exe中,可以成功得到截图。

    image-20220501001028272


[2022夏季班]《安卓高级研修班(网课)》月薪三万班招生中~

收藏
点赞2
打赏
分享
最新回复 (3)
雪    币: 3577
活跃值: 活跃值 (4151)
能力值: ( LV10,RANK:177 )
在线值:
发帖
回帖
粉丝
YenKoc 活跃值 2 2022-5-1 15:06
2
0
钟师傅tql
雪    币: 1496
活跃值: 活跃值 (2425)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
smallzhong_ 活跃值 2022-5-1 18:00
3
0
YenKoc [em_13]钟师傅tql
YenKoc师傅才真的强
雪    币: 1264
活跃值: 活跃值 (505)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
shun其自然 活跃值 2022-5-4 01:35
4
0
牛逼牛逼,赤裸裸教过检测!
游客
登录 | 注册 方可回帖
返回