腾讯游戏安全2024决赛-PC-WriteUp

image-20240420144943673

题目解读后,可以分为两个块,一个是写注册机,一个是分析内核shellcode的位置和干了什么

分析USER和KEY

在进行这个题目的时候,发现双机调试不能使用,分析的ark工具也不能使用,基本上这种方式不太行了,但是仍然比不了自己手速!哈哈哈哈!在没有蓝屏的时候用ark dump了一份load.dump.bin,哈哈哈哈

因为题目要求将card.txt放置到C盘根目录:这个时候发现一个现象,如果我不放到根目录下,或者如果我card.txt里面的值被我随便修改的情况下可以进行双机调试不会被检测,这个时候自己有了一点想法,(但是还是先做这个问为主),因为这个时候可以进行双机调试所以自己用双机调试做的USERKEY分析的东西

load.dump.bin中,可以看到路径的名称为:\\??\\C:\\card.txt,但是回溯不了很烦

image-20240420150237918

还根据ZwCreateFile入手

NTSYSAPI NTSTATUS ZwCreateFile(
  [out]          PHANDLE            FileHandle,
  [in]           ACCESS_MASK        DesiredAccess,
  [in]           POBJECT_ATTRIBUTES ObjectAttributes,
  [out]          PIO_STATUS_BLOCK   IoStatusBlock,
  [in, optional] PLARGE_INTEGER     AllocationSize,
  [in]           ULONG              FileAttributes,
  [in]           ULONG              ShareAccess,
  [in]           ULONG              CreateDisposition,
  [in]           ULONG              CreateOptions,
  [in, optional] PVOID              EaBuffer,
  [in]           ULONG              EaLength
);

直接看一下ObjectAttributes里面的ObjectName的名字

image-20240420152156047

一般来说如果读取文件来说,用的是ZwCreateFile,进行栈回溯一直跟一直跟,因为在vm中一定要回到text段,这个时候我认为的方式是:参数写入后,进行call

lea reg,xxxxxx
call vm

但是感觉栈回溯太多了不太行,然后想了一下看了一下这个地址的address模块在这个loader.sys中,计算一下offset = 0x675e20,然后交叉引用回去即可

image-20240420151126308

这个的路径是xor 0xace做到的,我这边直接解密,也可以看到code References的地址

image-20240420161653829

直接windbg分析后,这个位置不难,很好的可以分析user算法的位置:

image-20240420162925132

key的算法位置:

image-20240420163148409

这两个位置我是动态调试出来的,大概算法的表示为-进行分割后,user计算后的值转化为10进制,就是key的值

注册机:

#include <iostream>
using namespace std;

uint64_t getkey(string user)
{
    uint32_t encuser = 0;
    for(int i : user)
    {
        encuser = (i + encuser)*0x1003f;
    }
    return encuser;
}

int main()
{
    string user;
    cout << "input your user: " << endl;
    cin >> user;
    cout << "your key : " << getkey(user) << endl;
    return 0;
}

image-20240420164840893

第一问:administrator-4007951923

第二问:如上

第三问:编写一个exp,在exp程序运行后,对于任意的用户名-key,Loader.sys均能正确启动

image-20240420165332668

只需要把jne -> nop即可,因为我们不需要对al进行处理了

mov     byte [rsp+0x20 {var_b8}], 0x1
mov     al, byte [rsp+0x20 {var_b8}]  {0x1}

分析内核内存空间中的shellcode

目前是前三问都OK了,这个时候会出现双机调试的问题,根本调试不了,因为在没有双机调试的环境下进行分析的时候发现不会蓝屏,但是当驱动正常加载后,进行打开ark这种工具,几秒后还是会蓝屏

想了一下还是在内核当中起了线程(因为第四问也说了:在Load驱动运行后找到内核内存空间中的shellcode),因为一定是线程在做的检测的事情导致的蓝屏,尝试断PsCreateSystemThread没有发现什么重要的信息,但是一定是有线程跑不然为什么会蓝屏?

所以注册进程回调看有什么异常的,打印ProcessIdThreadId

#include <ntddk.h>

VOID ThreadNotifyRoutine(
    HANDLE ProcessId,
    HANDLE ThreadId,
    BOOLEAN Create
)
{
    // 打印线程创建或退出的信息
    if (Create) {
        DbgPrint("Created: Process ID=%p, Thread ID=%p\n", ProcessId, ThreadId);
    } else {
        DbgPrint("Exited: Process ID=%p, Thread ID=%p\n", ProcessId, ThreadId);
    }
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    NTSTATUS status;

    // 注册线程创建/退出通知回调
    status = PsSetCreateThreadNotifyRoutine(ThreadNotifyRoutine);
    if (!NT_SUCCESS(status)) {
        DbgPrint("Failed to register thread notify routine: %08x\n", status);
        return status;
    }

    DbgPrint("Successfully registered thread notify routine.\n");
    DriverObject->DriverUnload = DriverUnload;
    return STATUS_SUCCESS;
}

void DriverUnload(PDRIVER_OBJECT DriverObject)
{
    // 注销回调
    PsRemoveCreateThreadNotifyRoutine(ThreadNotifyRoutine);
    DbgPrint("Thread notify routine unregistered.\n");
}

主要观察processid = 4的,因为初赛也是这样,所以主要观察点在这个位置,发现确实创建了两条很可疑的线程,但是自己在观察这些线程地址的时候,确实没有很奇怪的地址存在,那么很有可能,在线程地址那里做了个跳板,之后进行了恢复所以看到的所有的线程都是很正常的地址

直接使用PsLookupThreadByThreadId通过threadId,找到startAddress

image-20240420172331527

然后直接dump,看一下里面做了什么

发现这里就是一个push mov ret返回真正的地址位置,这两个都是这种形式

image-20240420172706303

现在可以确定线程的入口是伪造的,当线程启动后会还原入口,所以这就是为什么看的线程的这些都没有啥问题的原因,这个时候猜测的这两个线程肯定是有一个做了检测的工作,另一个做了真正的事情,不然没必要创建两条

所以直接修改入口为ret,这个时候可以发现可以双机调试了,也可以打开ark工具,其中的另一个很奇怪的shellcode如下

image-20240420213702822

这里做了很多xmm的操作,大概看了一下这个xmm操作都是混淆,分析的时候直接跳过了这些分析的

现在可以确定的是百分百分确定了线程会被创建,一定会用到PsCreateSystemThread,但是bp不到,直接去看一下函数的实现

image-20240420215036575

直接下断PsCreateSystemThreadEx

image-20240420215519991

这里可以获取函数的入口点

image-20240420215828748

但是按照道理来说如果直接ret的话,不应该会做shellcode的事情,但是现象是:断下后,瞬间入口还原了,并且蓝屏了,说明有个地方启动了,这里就猜测是插了APC

image-20240420220005391

插入APC:使用KeInsertQueueApc函数来插入APC到目标线程的APC队列中,直接断KeInsertQueueApc

查看一下KPAC结构

image-20240420220442148

可以查看到APC的内容在loader.sys

image-20240420221636273

可以看到这里进行了cmp ,直接修改了je -> jmp 就不会蓝屏了

image-20240420221313269

第二次APC的位置

image-20240420221712471

第二次的时候发现都是xmm的操作,很难去分析,所以没有管第二次的问题,但是由于题目上说:分析shellcode反复在读取哪个内存地址,可以确定这里一定是一个while 1一直在循环操作,为了降低CPU的利用率,猜测会用延时函数,最有可能的就是KeDelayExecutionThread

image-20240420222115464

可以看到找到了这个shellcode的位置,可以看到是根据rax来的,往上跟一下

image-20240420222409846

可以看到表的解密方式是xor acacacacacacac

image-20240420232951847

解密出的函数列表:

nt!KeWaitForSingleObject
nt!KeDelayExecutionThread
nt!KeQueryTimeIncrement
nt!KeInitializeApc
nt!KeInsertQueueApc
nt!KeBugCheckEx
nt!ExAllocatePoolWithTag
nt!ExFreePool
nt!RtlInitUnicodeString
nt!RtlUnicodeStringToAnsiString
nt!RtlAnsiStringToUnicodeString
nt!RtlFreeAnsiString
nt!RtlCharToInteger
nt!HalPutDmaAdapter
nt!ZwQueryInformationFile
nt!ZwCreateFile
nt!ZwReadFile
nt!ZwQuerySystemInformation
nt!ZwClose
nt!PsGetThreadId
nt!PsLookupThreadByThreadId
nt!PsCreateSystemThread
nt!PsTerminateSystemThread
nt!PsGetProcessPeb
nt!PsGetProcessWow64Process
nt!PsGetProcessExitStatus
nt!PsGetCurrentThreadId
nt!PsLookupProcessByProcessId
nt!PsGetProcessImageFileName
nt!MmCopyVirtualMemory
nt!MmCopyMemory
nt!MmGetPhysicalAddress
nt!MmMapIoSpace
nt!MmUnmapIoSpace
nt!MmIsAddressValid
nt!PsGetCurrentProcess
nt!SeLocateProcessImageName
nt!DbgPrint
nt!DbgPrintEx
nt!memset
nt!memcpy
nt!memcpy
nt!memcmp
nt!strlen
nt!sqrtf
nt!vsnprintf
nt!vsnwprintf

这样就比较好看了,直接hook所有的函数,发现一直在循环的遍历进程,感觉有点问题存在,直接过去分析看看做了什么,如果是一直循环遍历进程,自己猜测肯定是在准备比对什么东西,但是没有比对到,直接去分析vm中的东西

image-20240421002456708

下读断点,断下后,进行分析,发现时逐字节比对,比对是不是GameSec.exe

image-20240421002755756

这个GameSec.exe需要自己创建这个进程,然后继续分析,直接写了一个helloworld改个名字丢进去看看做啥了,这个时候不难想到是在读R3进程的东西

这个时候可以看到了MmCopyVirtualMemoryPsGetProcessPeb在一直做事情

直接跟PsGetProcessPeb回溯

image-20240421004847500

直接对rsp+40h下访问断点

image-20240421005101685

所以第四问:PsGetProcessPeb + 0xACE

第五问:编写一个search程序,在Load驱动运行后找到内核内存空间中的shellcode,输出shellcode范围内的任意地址 (NMI即可)