2020腾讯游戏安全技术初赛ring0题wp

题目链接在https://gslab.qq.com/html/competition/2020/race-before.htm

本文相关文件在:
https://pan.baidu.com/s/14_bPIwp6CNq_NQm4rq57Fw
1.分析过程:
由于加了vmp的壳。通过ida查看导入表,还是有一些导入表的:
Address Ordinal Name Library
000000014023E000 MmGetSystemRoutineAddress ntoskrnl
000000014023E010 ExAllocatePool ntoskrnl
000000014023E018 NtQuerySystemInformation ntoskrnl
000000014023E020 ExFreePoolWithTag ntoskrnl
000000014023E028 IoAllocateMdl ntoskrnl
000000014023E030 MmProbeAndLockPages ntoskrnl
000000014023E038 MmMapLockedPagesSpecifyCache ntoskrnl
000000014023E040 MmUnlockPages ntoskrnl
000000014023E048 IoFreeMdl ntoskrnl
000000014023E050 KeQueryActiveProcessors ntoskrnl
000000014023E058 KeSetSystemAffinityThread ntoskrnl
000000014023E060 KeRevertToUserAffinityThread ntoskrnl
000000014023E068 DbgPrint ntoskrnl
000000014023E078 KeQueryPerformanceCounter HAL
对MmGetSystemRoutineAddress下断,发现该驱动会调用这个函数获取KdDisableDebugger,并调用禁止内核调试。后来才发现仅仅将该函数ret是不够的(无法在windbg ctrl+break),还好之前研究过tp的反双机调试,直接把之前写好的往上套过了反双机调试。断下后通过“.writemem 路径 startaddr size“命令dump该驱动。此时已经是脱壳后的sys,可以看到一些没有被vm的代码来静态分析。
Dump后的字符串信息:

image.png

一些重要函数:
image.png

根据编写驱动的习惯,一般再DriverEntry里面设置驱动对象的分发函数。所以上图代码至少是DriverEntry的一部分代码。观察函数sub_FFFFF80277E06A00:
image.png

证明了之前猜测使用KdDisableDebugger函数来禁止内核调试。再看sub_FFFFF80277E06B60:
image.png

里面是一长串注册表地址:
\REGISTERY\MACHINE\SOFTWARE\AppDataLow\Tencent\{61B942F7-A946-4585-B624-B2C0228FFEBC}和一个”key“
通过动态调试分析,该函数返回后执行下面代码:
image.png

该函数如果返回0则加载驱动失败,否则成功。
仔细动态调试该内部,首先通过ZwOpenKey看上述地址是否存在,存在则调用ZwQueryValueKey得到buf长度( cmp dword ptr [rsp+188h+var_158+4], 0C0000023h
0C0000023h==STATUS_BUFFER_TOO_SMALL。)。调用ExAllocatePoolWithQuotaTag动态申请内存:

.vmp2:FFFFF80277E0712E mov eax, dword ptr [rsp+188h+NumberOfBytes]
.vmp2:FFFFF80277E07132 mov r8d, 'rega' ; Tag
.vmp2:FFFFF80277E07138 mov edx, eax ; NumberOfBytes
.vmp2:FFFFF80277E0713A mov ecx, 1 ; PoolType
.vmp2:FFFFF80277E0713F call ExAllocatePoolWithQuotaTag

再次ZwQueryValueKey调用获取数据
将得到的数据和‘\x01’比较,如果相同则返回1.最后调用ExFreePoolWithTag释放内存。因此在注册表编辑如下:


image.png

分析IRP_MJ_DEVICE_CONTROL例程


image.png

IRP_MJ_CLOSE和IRP_MJ_CREATE很简单没做什么,就不分析了。
这3个分发函数都不能实现输出Hello Word。于是通过ida的交叉引用功能对


image.png

进行引用,直接在hello处引用失败,原来是前面有个\r\n。于是找到
image.png

(里面的函数名被修改成实际的api,这是通过动态调试发现的。)
一直回溯发现到


image.png

对该函数进行动态调试发现其实该驱动创建一个系统线程,该线程的函数就是上面的引用helloworld的地方。
继续回溯发现
image.png

驱动创建了一个事件,而且该事件初始被reset了,结合上述分析。线程函数其实就是在wait这个事件。只要通过打开这个事件并对其进行set就能触发输出Hello World!
image.png

触发该事件的驱动代码:
#include <ntddk.h>
#define EventName L"\\BaseNamedObjects\\tp2020"
VOID UnLoadDriver(PDRIVER_OBJECT pDriverObject)
{
    PDEVICE_OBJECT pDevObj;
    UNICODE_STRING sysLinkName;
    KdPrint(("[MyProtect_Unload] ==>\n"));


    KdPrint(("delete device! unload!\n"));
}
NTSTATUS DriverDefaultDisPatch(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
    PIO_STACK_LOCATION pIrpStack;
    NTSTATUS status = STATUS_SUCCESS;

    pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
    pIrp->IoStatus.Information = 0;
    pIrp->IoStatus.Status = status;

    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING pPath)
{

    NTSTATUS status = STATUS_SUCCESS;
    UNICODE_STRING devName;
    UNICODE_STRING linkName;
    PDEVICE_OBJECT pDevObj;
    UNICODE_STRING ustrEventName;
    UNICODE_STRING ustrEventName_2;
    ULONG i;

    DriverObject->DriverUnload = UnLoadDriver;
    for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
    {
        DriverObject->MajorFunction[i] = DriverDefaultDisPatch;
    }

    //DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverControlIo;
    UNICODE_STRING u_e_name;
    RtlInitUnicodeString(&u_e_name, EventName);
    HANDLE hEvent=0;
    PRKEVENT pEvent= IoCreateNotificationEvent(&u_e_name, &hEvent);
    if (pEvent)
    {
        KeSetEvent(pEvent, 0, 0);
    }
else
        DbgPrint("IoCreateNotificationEvent error");
    return status;
}

推荐阅读更多精彩内容