腾讯游戏安全2024决赛-PC-WriteUp
感谢宝子们对我的帮助!
特别感谢!存宝!翔哥!呜呜呜呜!
WP是简单的版本,因为做题的时候是远程好盆友的电脑,自己没有windows环境,所以代码和截图说明就没有那么多了,就简单版本!希望大家不要喷我!我就是记录一下!存个档!

题目解读后,可以分为两个块,一个是写注册机,一个是分析内核shellcode的位置和干了什么
分析USER和KEY
在进行这个题目的时候,发现双机调试不能使用,分析的ark工具也不能使用,基本上这种方式不太行了,但是仍然比不了自己手速!哈哈哈哈!在没有蓝屏的时候用ark dump了一份load.dump.bin,哈哈哈哈
因为题目要求将card.txt放置到C盘根目录:这个时候发现一个现象,如果我不放到根目录下,或者如果我card.txt里面的值被我随便修改的情况下可以进行双机调试不会被检测,这个时候自己有了一点想法,(但是还是先做这个问为主),因为这个时候可以进行双机调试所以自己用双机调试做的USER和KEY分析的东西
load.dump.bin中,可以看到路径的名称为:\\??\\C:\\card.txt,但是回溯不了很烦

还根据ZwCreateFile入手
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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的名字

一般来说如果读取文件来说,用的是ZwCreateFile,进行栈回溯一直跟一直跟,因为在vm中一定要回到text段,这个时候我认为的方式是:参数写入后,进行call
lea reg,xxxxxx call vm
但是感觉栈回溯太多了不太行,然后想了一下看了一下这个地址的address模块在这个loader.sys中,计算一下offset = 0x675e20,然后交叉引用回去即可

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

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

key的算法位置:

这两个位置我是动态调试出来的,大概算法的表示为-进行分割后,user计算后的值转化为10进制,就是key的值
注册机:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #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;} |

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

只需要把jne -> nop即可,因为我们不需要对al进行处理了
mov byte [rsp+0x20 {var_b8}], 0x1
mov al, byte [rsp+0x20 {var_b8}] {0x1}
分析内核内存空间中的shellcode
目前是前三问都OK了,这个时候会出现双机调试的问题,根本调试不了,因为在没有双机调试的环境下进行分析的时候发现不会蓝屏,但是当驱动正常加载后,进行打开ark这种工具,几秒后还是会蓝屏
想了一下还是在内核当中起了线程(因为第四问也说了:在Load驱动运行后找到内核内存空间中的shellcode),因为一定是线程在做的检测的事情导致的蓝屏,尝试断PsCreateSystemThread没有发现什么重要的信息,但是一定是有线程跑不然为什么会蓝屏?
所以注册进程回调看有什么异常的,打印ProcessId和ThreadId
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 | #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

然后直接dump,看一下里面做了什么
发现这里就是一个push mov ret返回真正的地址位置,这两个都是这种形式

现在可以确定线程的入口是伪造的,当线程启动后会还原入口,所以这就是为什么看的线程的这些都没有啥问题的原因,这个时候猜测的这两个线程肯定是有一个做了检测的工作,另一个做了真正的事情,不然没必要创建两条
所以直接修改入口为ret,这个时候可以发现可以双机调试了,也可以打开ark工具,其中的另一个很奇怪的shellcode如下

这里做了很多xmm的操作,大概看了一下这个xmm操作都是混淆,分析的时候直接跳过了这些分析的
现在可以确定的是百分百分确定了线程会被创建,一定会用到PsCreateSystemThread,但是bp不到,直接去看一下函数的实现

直接下断PsCreateSystemThreadEx

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

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

插入APC:使用KeInsertQueueApc函数来插入APC到目标线程的APC队列中,直接断KeInsertQueueApc
查看一下KPAC结构

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

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

第二次APC的位置

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

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

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

解密出的函数列表:
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 | nt!KeWaitForSingleObjectnt!KeDelayExecutionThreadnt!KeQueryTimeIncrementnt!KeInitializeApcnt!KeInsertQueueApcnt!KeBugCheckExnt!ExAllocatePoolWithTagnt!ExFreePoolnt!RtlInitUnicodeStringnt!RtlUnicodeStringToAnsiStringnt!RtlAnsiStringToUnicodeStringnt!RtlFreeAnsiStringnt!RtlCharToIntegernt!HalPutDmaAdapternt!ZwQueryInformationFilent!ZwCreateFilent!ZwReadFilent!ZwQuerySystemInformationnt!ZwClosent!PsGetThreadIdnt!PsLookupThreadByThreadIdnt!PsCreateSystemThreadnt!PsTerminateSystemThreadnt!PsGetProcessPebnt!PsGetProcessWow64Processnt!PsGetProcessExitStatusnt!PsGetCurrentThreadIdnt!PsLookupProcessByProcessIdnt!PsGetProcessImageFileNament!MmCopyVirtualMemorynt!MmCopyMemorynt!MmGetPhysicalAddressnt!MmMapIoSpacent!MmUnmapIoSpacent!MmIsAddressValidnt!PsGetCurrentProcessnt!SeLocateProcessImageNament!DbgPrintnt!DbgPrintExnt!memsetnt!memcpynt!memcpynt!memcmpnt!strlennt!sqrtfnt!vsnprintfnt!vsnwprintf |
这样就比较好看了,直接hook所有的函数,发现一直在循环的遍历进程,感觉有点问题存在,直接过去分析看看做了什么,如果是一直循环遍历进程,自己猜测肯定是在准备比对什么东西,但是没有比对到,直接去分析vm中的东西

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

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

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

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