本质菜逼,虽然大体思路是对的,但是对花指令还是有畏惧心理

客户端分析

逆向Ring3的客户端发现

image-20260410120738775

使用sc或者kdmmapper加载驱动

image-20260410121010006

在管理员模式打开ShadowGateApp.exe

image-20260410121101119

在如下内容中得知描述迷宫的结构体

image-20260507142224454

struct maze
{
vec2 size;
vec2 start;
vec2 end;
};

image-20260507143356750

现在反过来看花指令部分。根据提示,迷宫是13x13,没有回显,一共有5种方式可以暴露操作的结果,前五步按顺序使用这5中漏洞。

先创建两个事件

HANDLE CreateEvent()
{
HANDLE result; // rax

g_hOkEvent = CreateEventW(0, 1, 0, L"Global\\MazeMoveOK");
result = CreateEventW(0, 1, 0, L"Global\\MazeMoveWall");
g_hWallEvent = result;
return result;
}

然后弄了两个信号量不知道要干嘛

image-20260507143851522

驱动分析

尝试使用sxe在加载时断点逐步调试拿到解码好的汇编,但是显示没有加载,尝试使用自己的驱动加载器加载

#include <ntifs.h>
#include <ntimage.h>

typedef NTSTATUS(*DriverEntry_t)(
PDRIVER_OBJECT DriverObject,
PUNICODE_STRING RegistryPath
);

typedef struct _SYSTEM_MODULE_ENTRY {
PVOID Reserved[2];
PVOID ImageBase;
ULONG ImageSize;
ULONG Flags;
USHORT Index;
USHORT Unknown;
USHORT LoadCount;
USHORT ModuleNameOffset;
CHAR FullPathName[256];
} SYSTEM_MODULE_ENTRY, * PSYSTEM_MODULE_ENTRY;

typedef struct _SYSTEM_MODULE_INFORMATION {
ULONG NumberOfModules;
SYSTEM_MODULE_ENTRY Modules[1];
} SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;

// ZwQuerySystemInformation 原型
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation = 0,
SystemModuleInformation = 11
} SYSTEM_INFORMATION_CLASS;

NTSYSAPI
NTSTATUS
NTAPI
ZwQuerySystemInformation(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);

//typedef struct _SYSTEM_MODULE_ENTRY {
// PVOID Reserved[2];
// PVOID ImageBase;
// ULONG ImageSize;
// ULONG Flags;
// USHORT Index;
// USHORT Unknown;
// USHORT LoadCount;
// USHORT ModuleNameOffset;
// CHAR FullPathName[256];
//} SYSTEM_MODULE_ENTRY, * PSYSTEM_MODULE_ENTRY;

//typedef struct _SYSTEM_MODULE_INFORMATION {
// ULONG NumberOfModules;
// SYSTEM_MODULE_ENTRY Modules[1];
//} SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;

PVOID GetKernelBase(PCSTR ModuleName)
{
NTSTATUS status;
ULONG size = 0;
PVOID buffer = NULL;
PVOID base = NULL;

status = ZwQuerySystemInformation(
SystemModuleInformation,
NULL,
0,
&size
);

if (status != STATUS_INFO_LENGTH_MISMATCH)
return NULL;

buffer = ExAllocatePool2(
POOL_FLAG_NON_PAGED,
size,
'bKmM'
);

if (!buffer)
return NULL;

status = ZwQuerySystemInformation(
SystemModuleInformation,
buffer,
size,
&size
);

if (!NT_SUCCESS(status))
{
ExFreePool(buffer);
return NULL;
}

PSYSTEM_MODULE_INFORMATION modules =
(PSYSTEM_MODULE_INFORMATION)buffer;

for (ULONG i = 0; i < modules->NumberOfModules; i++)
{
PSYSTEM_MODULE_ENTRY mod = &modules->Modules[i];

PCSTR fullPath = mod->FullPathName;
PCSTR fileName = fullPath + mod->ModuleNameOffset;

if (_stricmp(fileName, ModuleName) == 0)
{
base = mod->ImageBase;
break;
}
}

ExFreePool(buffer);
return base;
}

PVOID GetKernelModuleBaseByName(PCSTR moduleName)
{
NTSTATUS status;
ULONG size = 0;
PVOID buffer = NULL;
PVOID base = NULL;

// 先获取长度
status = ZwQuerySystemInformation(SystemModuleInformation, NULL, 0, &size);
if (status != STATUS_INFO_LENGTH_MISMATCH)
return NULL;

// 分配非分页池
buffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, size, 'tag1');
if (!buffer)
return NULL;

status = ZwQuerySystemInformation(SystemModuleInformation, buffer, size, &size);
if (!NT_SUCCESS(status)) {
ExFreePool(buffer);
return NULL;
}

PSYSTEM_MODULE_INFORMATION sysModules = (PSYSTEM_MODULE_INFORMATION)buffer;
for (ULONG i = 0; i < sysModules->NumberOfModules; i++)
{
PSYSTEM_MODULE_ENTRY entry = &sysModules->Modules[i];
PCSTR name = entry->FullPathName + entry->ModuleNameOffset; // 获取文件名
if (_stricmp(name, moduleName) == 0)
{
base = entry->ImageBase;
break;
}
}

ExFreePool(buffer);
return base;
}

PVOID GetKernelExport(PVOID moduleBase, PCSTR functionName)
{
if (!moduleBase || !functionName)
return NULL;

PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)moduleBase;
if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE)
return NULL;

PIMAGE_NT_HEADERS64 ntHeaders = (PIMAGE_NT_HEADERS64)((PUCHAR)moduleBase + dosHeader->e_lfanew);
if (ntHeaders->Signature != IMAGE_NT_SIGNATURE)
return NULL;

IMAGE_DATA_DIRECTORY exportDirData = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
if (!exportDirData.Size)
return NULL;

PIMAGE_EXPORT_DIRECTORY exportDir = (PIMAGE_EXPORT_DIRECTORY)((PUCHAR)moduleBase + exportDirData.VirtualAddress);

ULONG* nameRvas = (ULONG*)((PUCHAR)moduleBase + exportDir->AddressOfNames);
USHORT* ordinals = (USHORT*)((PUCHAR)moduleBase + exportDir->AddressOfNameOrdinals);
ULONG* functions = (ULONG*)((PUCHAR)moduleBase + exportDir->AddressOfFunctions);

for (ULONG i = 0; i < exportDir->NumberOfNames; i++)
{
PCSTR name = (PCSTR)((PUCHAR)moduleBase + nameRvas[i]);
if (_stricmp(name, functionName) == 0)
{
USHORT ordinal = ordinals[i];
ULONG funcRva = functions[ordinal];
return (PVOID)((PUCHAR)moduleBase + funcRva);
}
}

return NULL; // 未找到
}



BOOLEAN FixRelocation(PVOID newBase, PIMAGE_NT_HEADERS64 nt)
{
ULONGLONG delta = (ULONGLONG)newBase - nt->OptionalHeader.ImageBase;
if (delta == 0) return TRUE;

IMAGE_DATA_DIRECTORY relocDir =
nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];

if (!relocDir.Size) return TRUE;

PIMAGE_BASE_RELOCATION reloc =
(PIMAGE_BASE_RELOCATION)((PUCHAR)newBase + relocDir.VirtualAddress);

PUCHAR end = (PUCHAR)reloc + relocDir.Size;

while ((PUCHAR)reloc < end && reloc->SizeOfBlock)
{
if (reloc->SizeOfBlock < sizeof(IMAGE_BASE_RELOCATION))
break;

UINT32 count =
(reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(USHORT);

PUSHORT list =
(PUSHORT)((PUCHAR)reloc + sizeof(IMAGE_BASE_RELOCATION));

for (UINT32 i = 0; i < count; i++)
{
USHORT type = list[i] >> 12;
USHORT offset = list[i] & 0xFFF;

if (type == IMAGE_REL_BASED_ABSOLUTE)
continue;

if (type == IMAGE_REL_BASED_DIR64)
{
PULONGLONG addr =
(PULONGLONG)((PUCHAR)newBase + reloc->VirtualAddress + offset);

*addr += delta;
}
}

reloc = (PIMAGE_BASE_RELOCATION)((PUCHAR)reloc + reloc->SizeOfBlock);
}

return TRUE;
}

BOOLEAN FixImportTable(PVOID imageBase, PIMAGE_NT_HEADERS64 nt)
{
IMAGE_DATA_DIRECTORY importDir =
nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];

if (!importDir.Size)
return TRUE;

PIMAGE_IMPORT_DESCRIPTOR import =
(PIMAGE_IMPORT_DESCRIPTOR)((PUCHAR)imageBase + importDir.VirtualAddress);
size_t i = 0;
while (i < 2)
{
++i;

PCSTR moduleName = (PCSTR)((PUCHAR)imageBase + import->Name);

PVOID moduleBase = GetKernelBase(moduleName);
//PVOID moduleBase = GetKernelModuleBaseByName(moduleName);

if (!moduleBase)
return FALSE;

PIMAGE_THUNK_DATA64 thunk =
(PIMAGE_THUNK_DATA64)((PUCHAR)imageBase + import->FirstThunk);

PIMAGE_THUNK_DATA64 origThunk =
(PIMAGE_THUNK_DATA64)((PUCHAR)imageBase + import->OriginalFirstThunk);

//PIMAGE_THUNK_DATA64 origThunk;

if (import->OriginalFirstThunk)
{
origThunk =
(PIMAGE_THUNK_DATA64)((PUCHAR)imageBase + import->OriginalFirstThunk);
}
else
{
origThunk =
(PIMAGE_THUNK_DATA64)((PUCHAR)imageBase + import->FirstThunk);
}

while (origThunk->u1.AddressOfData)
{
PIMAGE_IMPORT_BY_NAME importName =
(PIMAGE_IMPORT_BY_NAME)((PUCHAR)imageBase + origThunk->u1.AddressOfData);

PVOID fn = GetKernelExport(moduleBase, importName->Name);

if (!fn)
return FALSE;

thunk->u1.Function = (ULONGLONG)fn;

origThunk++;
thunk++;
}
import++;
}
return TRUE;
}


NTSTATUS ExecuteMappedDriver(PDRIVER_OBJECT DriverObject, PVOID imageBase)
{
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)imageBase;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((PUCHAR)imageBase + dos->e_lfanew);

ULONG entryRVA = nt->OptionalHeader.AddressOfEntryPoint;

DriverEntry_t entry = (DriverEntry_t)((PUCHAR)imageBase + 0x3208);

UNICODE_STRING fakeReg;
RtlInitUnicodeString(&fakeReg, L"\\Registry\\Machine\\System\\Fake");

return entry(DriverObject, &fakeReg); // DriverObject = NULL
}

NTSTATUS MapImage(PDRIVER_OBJECT DriverObject, PVOID buffer)
{
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)buffer;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((PUCHAR)buffer + dos->e_lfanew);

SIZE_T imageSize = nt->OptionalHeader.SizeOfImage;

PVOID imageBase = ExAllocatePool(NonPagedPool, imageSize);
if (!imageBase)
return STATUS_INSUFFICIENT_RESOURCES;

RtlZeroMemory(imageBase, imageSize);

// 拷贝 headers
RtlCopyMemory(imageBase, buffer, nt->OptionalHeader.SizeOfHeaders);

// 拷贝 section
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(nt);
for (int i = 0; i < nt->FileHeader.NumberOfSections; i++)
{
PVOID dest = (PUCHAR)imageBase + section[i].VirtualAddress;
PVOID src = (PUCHAR)buffer + section[i].PointerToRawData;

RtlCopyMemory(dest, src, section[i].SizeOfRawData);
}
// - IAT 导入解析
FixImportTable(imageBase, nt);

// - 重定位修复
FixRelocation(imageBase, nt);

return ExecuteMappedDriver(DriverObject, imageBase);
}

NTSTATUS ReadFileToBuffer(
PCWSTR filePath,
PVOID* outBuffer,
PULONG outSize
)
{
NTSTATUS status;
HANDLE fileHandle;
OBJECT_ATTRIBUTES objAttr;
IO_STATUS_BLOCK ioStatus;
UNICODE_STRING uFilePath;

*outBuffer = NULL;
*outSize = 0;

RtlInitUnicodeString(&uFilePath, filePath);

InitializeObjectAttributes(
&objAttr,
&uFilePath,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
NULL
);

// 打开文件
status = ZwCreateFile(
&fileHandle,
GENERIC_READ,
&objAttr,
&ioStatus,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_OPEN,
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0
);

if (!NT_SUCCESS(status)) {
DbgPrint("ZwCreateFile failed: 0x%X\n", status);
return status;
}

// 获取文件大小
FILE_STANDARD_INFORMATION fileInfo;
status = ZwQueryInformationFile(
fileHandle,
&ioStatus,
&fileInfo,
sizeof(fileInfo),
FileStandardInformation
);

if (!NT_SUCCESS(status)) {
ZwClose(fileHandle);
return status;
}

ULONG fileSize = (ULONG)fileInfo.EndOfFile.QuadPart;

// 分配内存
PVOID buffer = ExAllocatePool(NonPagedPool, fileSize);
if (!buffer) {
ZwClose(fileHandle);
return STATUS_INSUFFICIENT_RESOURCES;
}

// 读取文件
status = ZwReadFile(
fileHandle,
NULL,
NULL,
NULL,
&ioStatus,
buffer,
fileSize,
NULL,
NULL
);

if (!NT_SUCCESS(status)) {
ExFreePool(buffer);
ZwClose(fileHandle);
return status;
}

ZwClose(fileHandle);

*outBuffer = buffer;
*outSize = fileSize;

DbgPrint("Read file success, size: %lu\n", fileSize);

return STATUS_SUCCESS;
}

VOID
DriverUnload(PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
DbgPrint("Driver Stopping -> %wZ\n", &DriverObject->DriverName);
}

NTSTATUS
DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
DbgPrint("Driver Running -> %wZ\n", &DriverObject->DriverName);
DriverObject->DriverUnload = DriverUnload;
PVOID buffer;
ULONG size;

NTSTATUS status = ReadFileToBuffer(
L"\\??\\C:\\Users\\360safe\\Desktop\\G\\ShadowGateSys.sys",
&buffer,
&size
);

if (!NT_SUCCESS(status)) {
DbgPrint("Can't read file\n");
return status;
}

MapImage(DriverObject, buffer);
status = STATUS_SUCCESS;
return status;
}

直接跳到主要功能执行,然后再花指令运行过后导致判断结果改变,

image-20260410132844375

image-20260410132917132

运行到下面代码,在IDA中patch如下

image-20260410133015837

过后

image-20260410133212133

DevControlHandle

image-20260410134445998

接受3个ioctl

  • 0x80012004: 暂时未知
  • 0x80012008: 重置迷宫。
  • 0x8001200C: 对exe的分析得知是返回迷宫信息。

查找g_Pool的所有引用逆向得到

struct maybe
{
PKSPIN_LOCK spinlock0;
PKSPIN_LOCK spinlock1;
PKSPIN_LOCK spinlock2;
PKSPIN_LOCK spinlock3;
char unknown1[140];
vec2 coord;
DWORD *countB;
char unknown3[254];
PKSPIN_LOCK queryMazeSpinLock;
HANDLE currentPid;
HANDLE currentTid;
};

0x80012004再判断

sub_140002161:大量花指令,但是传递了迷宫结构体和一个checksum

[2]中使用的方法是在exe上的DeviceControl下断点判断MasterIrp的值

image-20260507153528833

//小端序列转换后
MasterIrp = 00000052
MasterIrp+1 = 00000000
MasterIrp+2 = DEAD1365

>>> x=0x52
>>> y=0
>>> z=0xDEAD1365
>>> x^y^0xDEAD1337 == z
True
方向 按键 编码后的 x
UP W/I 0x52
DOWN S/K 0xD3
LEFT A/J 0x53
RIGHT D/L 0xD0

迷宫的值

这个点我分析的时候太执着于花指令了,居然还么看过在内存中的值

image-20260507154321311

根据13x13的大小拿到迷宫

dump = b""
with open("./mazedump.bin", "rb") as f:
dump = f.read(13*13)
print(dump)
for i in range(13):
for j in range(13):
if (dump[i*13+j] == 0x00):
print("#", end="")
else:
print(".", end="")
print()

迷宫

#######.#####
......#...#.#
#####.#####.#
#...#.......#
#.#########.#
#.#.#.....#.#
#.#.#.###.#.#
#.#...#.#...#
#.#####.#.###
#.......#.#..
###.###.#.#.#
..#.#.#.#.#.#
#####.#.#####

随便写个脚本就可以求最短路径[2]

from collections import deque

maze = [
[0,0,0,0,0,0,0,1,0,0,0,0,0],
[1,1,1,1,1,1,0,1,1,1,0,1,0],
[0,0,0,0,0,1,0,0,0,0,0,1,0],
[0,1,1,1,0,1,1,1,1,1,1,1,0],
[0,1,0,0,0,0,0,0,0,0,0,1,0],
[0,1,0,1,0,1,1,1,1,1,0,1,0],
[0,1,0,1,0,1,0,0,0,1,0,1,0],
[0,1,0,1,1,1,0,1,0,1,1,1,0],
[0,1,0,0,0,0,0,1,0,1,0,0,0],
[0,1,1,1,1,1,1,1,0,1,0,1,1],
[0,0,0,1,0,0,0,1,0,1,0,1,0],
[1,1,0,1,0,1,0,1,0,1,0,1,0],
[0,0,0,0,0,1,0,1,0,0,0,0,0],
]

dx = [0, 0, -1, 1] # UP, DOWN, LEFT, RIGHT
dy = [-1, 1, 0, 0]

q = deque([((0,0), [])])
visited = {(0,0)}
while q:
(cx,cy), path = q.popleft()
if (cx,cy) == (12,12):
print("Path:", "".join("WSAD"[d] for d in path))
break
for d in range(4):
nx, ny = cx+dx[d], cy+dy[d]
if 0<=nx<13 and 0<=ny<13 and (nx,ny) not in visited and maze[ny][nx]==0:
visited.add((nx,ny))
q.append(((nx,ny), path+[d]))
#Path: DDDDDDSSDDDDWWDDSSSSSSSSAASSSSDD

image-20260507155253516

image-20260507155422339

得到flag

flag{SHAD0WNT_HYPERVMX}

信息泄露

事件对象

这个在比赛的时候就分析出来了的。Global\\MazeMoveOKGlobal\\MazeMoveWall

在exe中

image-20260507155751027

在驱动中,这样就可以和exe进行通讯了

image-20260507155825223

使用如下代码可以检测

ResetEvent(hEvtOK);
ResetEvent(hEvtWall);
// ... 发送移动 IOCTL ...
if (WaitForSingleObject(hEvtOK, 100) == WAIT_OBJECT_0) // 移动成功
if (WaitForSingleObject(hEvtWall, 100) == WAIT_OBJECT_0) // 撞墙

信号量

就是sub_14021B91F那堆乱糟糟的代码,不过可以尝试动态调试一下

image-20260507160328946

image-20260507160341767

第一次创建了Globa1\{A7F3B2C1-9E4D-4C8A-B5D6-1F2E3A4B5C6D}

image-20260507160440861

第二次创建了Global\{B8E2C3D0-0F5A-5D9B-C6E7-2A3F4B5C6D7E}

image-20260507160550533

sys驱动中是通过ObReferenceObjectByName找到的,

image-20260507161155869

跨进程 TEB 通信

这两个都是结合sys的导入表看到的

image-20260507161700419

LastError泄露

所以说还是要对关键结构体熟悉

image-20260507162507236

句柄通讯

查找PsGetThreadTEB的引用

image-20260507162333388

qword_140005090ZwSetInformationObject。探测时把 HANDLE 放到 TEB+0x1748 中,观察是否被设置 HANDLE_FLAG_PROTECT_FROM_CLOSE

从Ring3获得flag

具体代码可见2

参考

[1] https://mp.weixin.qq.com/s/qHuLHvkBoYhOF2rkNRciaw

[2] https://www.52pojie.cn/thread-2102723-1-1.html