2026 腾讯游戏安全 PC 初赛 WP

驱动加载

使用 EfiGuard 进行 PG 和 DSE 修复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 挂载 EFI 到 M:,注意在 explorer 中是看不到的
mountvol M: /S
M:
mkdir \EFI\EfiGuard

copy C:\EfiGuard\EFI\Boot\Loader.efi M:\EFI\EfiGuard\
copy C:\EfiGuard\EFI\Boot\EfiGuardDxe.efi M:\EFI\EfiGuard\

// 创建启动项,生成一个 GUID,下面要用
bcdedit /copy {bootmgr} /d "EfiGuard"
bcdedit /set {上面的GUID} path \EFI\EfiGuard\Loader.efi
bcdedit /set {fwbootmgr} displayorder {上面的GUID} /addfirst

// 取消第一启动,恢复
bcdedit /set {fwbootmgr} displayorder {bootmgr} /addfirst

绿字进系统后关闭 DSE 就可以加载驱动了

1
2
3
4
5
EfiDSEFix.exe -d

Disabling DSE...
CI!g_CiOptions at 0xFFFFF800648391B0.
Successfully disabled DSE. Original g_CiOptions value: 0xE

启动驱动后运行程序

image.png

exe 分析

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
int __fastcall main_0(int argc, const char **argv, const char **envp)
{
__int64 v4; // rcx
int inputPos; // ebx
__int64 wirtePos; // rsi
unsigned int inputChar; // edi
__int64 code; // rdx
char _AL; // al
__int64 _RCX; // rcx
char theSend; // al
int v13; // r8d
int status; // eax
DWORD LastError; // edx
char *seq; // rdx
_DWORD moveBufOut[2]; // [rsp+20h] [rbp-188h] BYREF
ioctl_8001200c_out map; // [rsp+28h] [rbp-180h] BYREF
_OWORD buf[4]; // [rsp+40h] [rbp-168h] BYREF
int (__stdcall *send)(SOCKET, const char *, int, int); // [rsp+80h] [rbp-128h] BYREF
_BYTE Buf[176]; // [rsp+E0h] [rbp-C8h] BYREF

InitTips();
printf("[*] Connecting to Shadow gate driver...\n");
hDevice = (HANDLE)OpenDriver();
if ( hDevice == (HANDLE)-1LL )
{
printf("[!] Cannot continue without driver.\n");
printf("[*] Press any key to exit...\n");
getch();
return 1;
}
printf("[+] Gate module online.\n\n");
sub_140001670();
memset(&map, 0, sizeof(map));
if ( IO_8001200C(v4, &map) )
printf("[*] Maze grid: %ux%u, Entry=(%u,%u), Exit=(%u,%u)\n", map.x, map.y, map.ex, map.ey, map.exX, map.exY);
PrintOperatorTips();
memset(&send, 0, 0x100u);
inputPos = 0;
wirtePos = 0;
while ( 2 )
{
printf("[op #%d] > ", inputPos);
inputChar = getch();
printf("%c\n", inputChar);
inputChar -= 0x1B;
_AL = inputChar;
_RCX = (unsigned __int16)Buf;
LOBYTE(_RCX) = (unsigned __int8)Buf + 11;
switch ( inputChar )
{
case '\0':
case '6': // Q
case 'V': // q
goto Exit;
case '$': // ?Hh
case '-':
case 'M':
PrintOperatorTips();
continue;
case '&': // AJaj left
case '/':
case 'F':
case 'O':
LOBYTE(code) = 0x30;
theSend = 'L';
goto Send;
case ')': // DLdl right
case '1':
case 'I':
case 'Q':
LOBYTE(code) = 0x40;
__asm { rcl al, cl; Rotate Through Carry Left }
theSend = 'R';
goto Send;
case '.':
case '<':
case 'N':
case '\\':
LOBYTE(code) = 0x10;
theSend = 'U';
goto Send;
case '0':
case '8':
case 'P':
case 'X':
LOBYTE(code) = 0x20;
theSend = 'D';
Send:
if ( inputPos < 255 )
{
*((_BYTE *)Buf + wirtePos + 80) = theSend;
++inputPos;
++wirtePos;
if ( (unsigned int)inputPos >= 0x100 )
_report_securityfailure_w(_RCX);
*((_BYTE *)Buf + wirtePos + 80) = 0;
}
v13 = dword_140005668;
memset(buf, 0, sizeof(buf));
moveBufOut[0] = 0;
++dword_140005668;
status = IO_80012004(hDevice, code, v13, buf, moveBufOut);
if ( status != 1 )
{
if ( status == -1 )
{
LastError = GetLastError();
printf("[ERROR] DeviceIoControl failed: %lu\n", LastError);
}
continue;
}
printf(L"\n");
printf("=============================================\n");
printf(" ACCESS GRANTED - Credential extracted!\n");
printf("=============================================\n");
if ( moveBufOut[0] )
{
printf("[CREDENTIAL] %s\n", (const char *)buf);
}
else
{
printf("[*] You reached the exit, but the credential could not be decrypted.\n");
printf("[*] Hint: Only the shortest path unlocks the credential.\n");
}
printf("[*] Your input (%d ops sent): %s\n", inputPos, (const char *)&send);
printf("[*] Note: blocked ops are included above; the driver only counts successful ones.\n");
printf(L"\n");
memset(buf, 0, sizeof(buf));
printf("[*] Press any key to exit...\n");
getch();
Exit:
Close3Func(_RCX);
if ( hDevice != (HANDLE)-1LL )
CloseHandle(hDevice);
printf("[*] Mission aborted. ACE HQ standing by.\n");
return 0;
case '7':
case 'W':
IO_80012008_Reset();
inputPos = 0;
dword_140005668 = 0;
LOBYTE(send) = 0;
wirtePos = 0;
printf("[*] Reset to entry point.\n");
continue;
case '9':
case 'Y':
printf("[*] Operations sent: %d\n", inputPos);
seq = (char *)&send;
if ( inputPos <= 0 )
seq = "(none)";
printf("[*] Sequence: %s\n", seq);
continue;
default:
printf("[?] Unknown command. 'h' for help.\n");
continue;
}
}
}

重点在驱动交互,其余均为负责输入的逻辑

IO_8001200C

初始化

1
2
3
4
5
6
7
BOOL __fastcall IO_8001200C(__int64 a1, void *lpOutBuffer)
{
DWORD BytesReturned; // [rsp+40h] [rbp-18h] BYREF

BytesReturned = 0;
return DeviceIoControl(hDevice, 0x8001200C, 0, 0, lpOutBuffer, 0x18u, &BytesReturned, 0);
}
1
2
3
4
5
6
7
8
9
struct __unaligned ioctl_8001200c_out
{
DWORD x;
DWORD y;
DWORD ex;
DWORD ey;
DWORD exX;
DWORD exY;
};

IO_80012004

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
48
49
50
51
52
53
54
55
__int64 __fastcall IO_80012004(void *hDevice, __int64 _RDX, int a3, _BYTE *buf, DWORD *out)
{
__int64 v5; // rdi
char v6; // al
unsigned int rest; // esi
int lpInBuffer__1; // eax
DWORD Size; // ecx
__int64 Size_1; // rdi
MoveOutBuf IOOutBuf; // [rsp+20h] [rbp-C8h] BYREF
DWORD lpBytesReturned; // [rsp+B0h] [rbp-38h] BYREF
ioctl_80012004_in lpInBuffer_; // [rsp+B8h] [rbp-30h] BYREF
__int64 v17; // [rsp+D0h] [rbp-18h]

v17 = v5;
v6 = _RDX ^ 0xFA;
rest = 0;
memset(&IOOutBuf, 0, sizeof(IOOutBuf));
LOBYTE(_RDX) = (unsigned __int8)(_RDX ^ 0x40) >> 5;
lpInBuffer__1 = (unsigned __int8)(_RDX | (8 * v6));
__asm { rcr rdx, 8Bh; Rotate Through Carry Right }
*(_QWORD *)&lpInBuffer_.code = (unsigned __int8)lpInBuffer__1;
lpBytesReturned = 0;
lpInBuffer_.check = a3 ^ lpInBuffer__1 ^ 0xDEAD1337;
if ( DeviceIoControl(hDevice, 0x80012004, &lpInBuffer_, 0xCu, &IOOutBuf, 0x84u, &lpBytesReturned, 0) )
{
if ( *(_DWORD *)IOOutBuf.msg == 'WIN!' )
{
rest = 1;
if ( buf )
{
if ( out )
{
Size = IOOutBuf.size;
*out = IOOutBuf.size;
if ( Size - 1 > 0x3E )
{
*buf = 0;
}
else
{
Size_1 = Size;
memcpy(buf, IOOutBuf.p2, Size);
buf[Size_1] = 0;
}
}
}
}
}
else
{
rest = -1;
}
memset(&IOOutBuf, 0, sizeof(IOOutBuf));
return rest;
}

负责移动,发送给驱动的数据结构见下

1
2
3
4
5
6
struct ioctl_80012004_in
{
DWORD code;
DWORD pos;
DWORD check;
};
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
from struct import pack

def calcMoveCode(_RDX):
'''
计算键盘映射到驱动键盘映射
0x10 up
0x20 down
0x30 left
0x40 right
:param _RDX:
:return:
'''
v6 = (_RDX ^ 0xFA) & 0xff
_RDX = ((_RDX ^ 0x40) >> 5) & 0xff
res = (_RDX | (8 * v6)) & 0xff
print(hex(res))
return res

def IO_MOVE_GenBuf(code, index):
code = calcMoveCode(code)
check = (code ^ index ^ 0xDEAD1337) & 0xffffffff
print(hex(check))

b = pack('<III', code, index, check)
print(f'{b.hex()}')

IO_MOVE_GenBuf(0x30, 0)
IO_MOVE_GenBuf(0x20, 1)
IO_MOVE_GenBuf(0x10, 2)
IO_MOVE_GenBuf(0x40, 3)

IO_80012008_Reset

重置迷宫

1
2
3
4
5
6
7
BOOL IO_80012008_Reset()
{
DWORD BytesReturned; // [rsp+40h] [rbp-18h] BYREF

BytesReturned = 0;
return DeviceIoControl(hDevice, 0x80012008, 0, 0, 0, 0, &BytesReturned, 0);
}

sys 分析

有一些难以分析的 call,nop 掉看整体流程

image.png

载入点

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
__int64 __fastcall sub_140003208(struct _DRIVER_OBJECT *DriverObject)
{
void *Pool2; // rax
NTSTATUS Pool2_1; // edi
struct _UNICODE_STRING DestinationString; // [rsp+40h] [rbp-18h] BYREF

Pool2 = (void *)ExAllocatePool2(64, 472, 0x657A614D);
MemoryPool = Pool2;
if ( !Pool2 )
return 0xC000009ALL;
Pool2_1 = (int)Pool2; // 混淆
if ( (int)Pool2 < 0
|| (KeInitializeSpinLock(&SpinLock),
KeInitializeSpinLock(&SpinLock_),
((void (*)(void))loc_140001E60)(),
DestinationString = 0,
RtlInitUnicodeString(&DestinationString, L"\\Device\\ShadowGate"),
Pool2_1 = IoCreateDevice(DriverObject, 0, &DestinationString, 0x22u, 0x100u, 0, &DeviceObject),
Pool2_1 < 0) )
{
_mm_lfence();
ExFreePoolWithTag(MemoryPool, 0x657A614Du);
LABEL_5:
MemoryPool = 0;
return (unsigned int)Pool2_1;
}
RtlInitUnicodeString(&SymbolicLinkName, L"\\??\\ShadowGate");
Pool2_1 = IoCreateSymbolicLink(&SymbolicLinkName, &DestinationString);
if ( Pool2_1 < 0 )
{
_mm_lfence();
IoDeleteDevice(DeviceObject);
ExFreePoolWithTag(MemoryPool, 0x657A614Du);
DeviceObject = 0;
goto LABEL_5;
}
DriverObject->DriverUnload = (PDRIVER_UNLOAD)sub_140001840;
DriverObject->MajorFunction[0] = (PDRIVER_DISPATCH)sub_1400014B0;
DriverObject->MajorFunction[2] = (PDRIVER_DISPATCH)sub_140001410;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = (PDRIVER_DISPATCH)j_IRP_Control_Func;
DeviceObject->Flags |= 4u;
DeviceObject->Flags &= ~0x80u;
return 0;
}

Contorl 里面也有两处难以分析 call,nop 掉看整体逻辑

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
__int64 __fastcall IRP_Control_Func(__int64 a1, IRP *IRP, __int64 a3, char a4)
{
__int16 v4; // r11
__int16 v5; // r13
__int16 v6; // di
struct _IO_STACK_LOCATION *CurrentStackLocation; // rax
unsigned int Status_1; // ebp
ULONG_PTR info; // r8
ULONG IoControlCode; // edx
unsigned int inputLen; // edi
unsigned int outLen; // eax
buf_u *SystemBuffer; // r14
unsigned int Status; // edx
KIRQL NewIrql_1; // al
KSPIN_LOCK *SpinLock; // rcx
KIRQL NewIrql; // al
struct_MemoryPool *P; // rcx
unsigned int _____1; // eax
unsigned int ____; // esi
KIRQL NewIrql_4; // al
struct_MemoryPool *MemoryPool; // rdi
KIRQL NewIrql_2; // bl
HANDLE CurrentThreadId; // rax
KSPIN_LOCK *p_kspin_lock1C0; // rcx
KSPIN_LOCK *p_kspin_lock1C0_1; // rcx
KIRQL NewIrql_5; // al
char _DI_1; // cl
KIRQL NewIrql_3; // si
__int64 n255; // rdi
KSPIN_LOCK *p_kspin_lock1C0_2; // rcx
DWORD v37; // [rsp+0h] [rbp-158h] BYREF
__int64 v38; // [rsp+8h] [rbp-150h]
__m128 buf[16]; // [rsp+20h] [rbp-138h] BYREF

v6 = __ROR2__(v5, 1);
LOBYTE(v6) = a4 - v6;
CurrentStackLocation = IRP->Tail.Overlay.CurrentStackLocation;
_DI = v6 - v4;
__asm { rcr di, 0AFh }
Status_1 = 0;
info = 0;
IoControlCode = CurrentStackLocation->Parameters.DeviceIoControl.IoControlCode;
inputLen = CurrentStackLocation->Parameters.DeviceIoControl.InputBufferLength;
outLen = CurrentStackLocation->Parameters.DeviceIoControl.OutputBufferLength;
SystemBuffer = (buf_u *)IRP->AssociatedIrp.SystemBuffer;
if ( ::MemoryPool )
{
switch ( IoControlCode )
{
case 0x80012004:
if ( SystemBuffer && inputLen >= 0xC && outLen >= 0x84 )// move?
{
v38 = *(_QWORD *)&SystemBuffer->_12004.code;
if ( SystemBuffer->_12004.check == ((unsigned __int8)v38 ^ HIDWORD(v38) ^ 0xDEAD1337) )
{
NewIrql = KeAcquireSpinLockRaiseToDpc(&::MemoryPool->kspin_lock1C0);
P = ::MemoryPool;
++::MemoryPool->dwordBC;
KeReleaseSpinLock(&P->kspin_lock1C0, NewIrql);
____ = _____1; // 可能混淆
customMemset(SystemBuffer, 0, 0x84u);
NewIrql_4 = KeAcquireSpinLockRaiseToDpc(&::MemoryPool->kspin_lock1C0);
MemoryPool = ::MemoryPool;
NewIrql_2 = NewIrql_4;
CurrentThreadId = PsGetCurrentThreadId();
p_kspin_lock1C0 = &::MemoryPool->kspin_lock1C0;
*(_QWORD *)MemoryPool[1].gap8 = CurrentThreadId;
KeReleaseSpinLock(p_kspin_lock1C0, NewIrql_2);
((void (__fastcall *)(struct_MemoryPool *, _QWORD))loc_14040305A)(::MemoryPool, ____);
if ( ____ == 2 )
{
p_kspin_lock1C0_1 = &::MemoryPool->kspin_lock1C0;
SystemBuffer[2]._1200c.enY = 'WIN!';
NewIrql_5 = KeAcquireSpinLockRaiseToDpc(p_kspin_lock1C0_1);
_DI = _DI_1;
__asm { rcl dil, 0BEh }
NewIrql_3 = NewIrql_5;
LOBYTE(_R8D) = _DI;
__asm { rcr r8d, 22h }
n255 = *(unsigned int *)&::MemoryPool->gap8[172];
if ( (unsigned int)n255 > 0xFF )
n255 = 255;
sub_140003600(buf, (__m128 *)::MemoryPool->buf, (unsigned int)n255);
_mm_lfence();
p_kspin_lock1C0_2 = &::MemoryPool->kspin_lock1C0;
buf[0].m128_i8[n255] = 0;
KeReleaseSpinLock(p_kspin_lock1C0_2, NewIrql_3);
v37 = 0;
sub_1403F7C6D(buf, (unsigned int)n255, (__int64)&SystemBuffer[2]._12004 + 16, (__int64)&v37);
SystemBuffer[5]._1200c.enX = v37;
memset(buf, 0, sizeof(buf));
}
sub_1400026B4(SystemBuffer);
}
else
{
customMemset(SystemBuffer, 0, 0x84u);
sub_140002038(SystemBuffer);
}
info = 132;
goto LABEL_23;
}
break;
case 0x80012008:
sub_140001A7C((__int64)::MemoryPool); // reset
sub_14031A53E();
info = 0;
goto LABEL_23;
case 0x8001200C:
if ( SystemBuffer && outLen >= 0x18 ) // init
{
NewIrql_1 = KeAcquireSpinLockRaiseToDpc(&::MemoryPool->kspin_lock1C0);
SystemBuffer->_1200c.enY = 0;
SystemBuffer->_1200c.x = 13;
*(_QWORD *)&SystemBuffer->_1200c.y = 13;
SpinLock = &::MemoryPool->kspin_lock1C0;
SystemBuffer->_1200c.exitX = 12;
SystemBuffer->_1200c.exitY = 12;
KeReleaseSpinLock(SpinLock, NewIrql_1);
info = 0x18;
goto LABEL_23;
}
break;
default:
Status_1 = 0xC0000010;
LABEL_23:
Status = Status_1; // 可能混淆
return IO_CompleteReq(IRP, Status, info);
}
Status_1 = 0xC0000023;
goto LABEL_23;
}
Status = 0xC00000A3;
// 可能混淆
return IO_CompleteReq(IRP, Status, info);
}
/* Orphan comments:
可能混淆
*/

CODE 0x8001200C

传递迷宫大小,位置信息

CODE 0x80012008

重置

CODE 0x80012004

移动,主要逻辑

移动延迟

KeDelayExecutionThread 对每步移动都进行一点延迟,所以算法爆破特别慢,需要 patch

image.png

VA 0x1400026BD,RVA 0x26BD 全 nop (5字节)

1
eq ShadowGateSys+0x26bd c483489090909090

五个泄漏点

sys data 段起始有一个花指令魔数,通过该魔数可以定位四个泄漏点

image.png

1. Event 泄漏

exe 0x140001340 创建 Global Event

image.png

sys 0x1400022B0 中 event 泄漏:n2==0/2 置 MazeMoveOK,否则置 MazeMoveWall

image.png

2. 信号量 泄漏

exe 0x14021B91F 创建信号量,异或 0x4B 解密字符串

image.png

image.png

得到

1
2
Global\{A7F3B2C1-9E4D-4C8A-B5D6-1F2E3A4B5C6D}
Global\{B8E2C3D0-0F5A-5D9B-C6E7-2A3F4B5C6D7E}

sys 0x140319A37 中 semaphore 泄漏,_EDX ==0/2 释放 {A7F3B2C1-9E4D-4C8A-B5D6-1F2E3A4B5C6D} ,否则释放 {B8E2C3D0-0F5A-5D9B-C6E7-2A3F4B5C6D7E}

image.png

3. TEB.LastError 泄漏

sys 0x140316ADF 中修改 exe LastErrorValue ,n2 为 0 时设置 0xC0DE0001, 2 时设置 0xC0DE0002, 其他时设置 0xC0DE0000

image.png

4. TEB + 0x1748 泄漏

exe 0x14021BC88 在释放时有部分提示

image.png

sys 0x14031857E 中,使用 PsGetThreadTeb 获取 TEB 后使用 ZwSetInfomationObjectteb + 0x1748 处进行设置

在 icall 分发处能够看到实际调用的 call 为 PsGetThreadTebZwSetInfomationObject

image.png

两个 icall 都清理为 call rax 不然反编译有问题

image.png

n2 = 0/2,设置 1,否则设置 0

image.png

5. 内存属性 泄漏

_guard_dispatch_icall_nop 下断点,当输入断在 rax=ZwProtectVirtualMemory 时就是触发第五个漏洞

调用参数为

rdx = ShadowGateApp.exe.data

rcx = ShadowGateApp.exe.data 页长度

.data 页的属性修改为 RWERW

1
2
3
4
3: kd> dq rdx L1
ffffab06`6e6a75a0 00007ff6`0eb55000
3: kd> dq r8 L1
ffffab06`6e6a75a8 00000000`00001000

image.png

找了下没找到,看堆栈这个是真的在 vm 里面了,该泄漏通过观察页得到,猜测 sys 会修改名为 .data 的页

image.png

image.png

根据上面规则猜测 n2 = 0/2 时,页属性变成 PAGE_EXECUTE_READWRITE 其他时,保持 PAGE_READWRITE

查询迷宫 & 探索迷宫

方法一:r3 下通过泄漏探索迷宫

伪代码,完整见附件

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
Init():
打开驱动设备
创建 2 个事件对象
创建 2 个命名信号量
创建 1 个 probe handle
找到当前 exe 的 .data 首页
把 probe handle 写入 TEB+0x1748
发送 QUERY,得到迷宫大小、起点、终点

EncodeMove(dir):
将 U/D/L/R 编码成驱动需要的 dir 值

DoMove(dir):
packet.dir = EncodeMove(dir)
packet.counter = global_counter
packet.check = dir ^ counter ^ 0xDEAD1337
global_counter++
发送 IOCTL_MOVE
返回 reply

PrepareDetectors():
ResetEvent(ok)
ResetEvent(wall)
DrainSemaphore(A)
DrainSemaphore(B)
SetLastError(哨兵值)
将 .data 首页改回 RW
将 probe handle flags 改回基线
重新确保 TEB+0x1748 = probe handle

ReadVerdict(reply):
同时读取:
Event 状态
Semaphore 状态
LastError
HandleFlags
.data 页保护
if Event 有效:
返回 open / wall / win
else if Semaphore 有效:
返回 open / wall / win
else if LastError 有效:
返回 open / wall / win
else if HandleFlags 相对基线发生变化:
返回 open / wall / win
else:
根据 .data 页保护返回 open / wall / win

ReplayPath(path):
RESET 驱动
for step in path:
PrepareDetectors()
verdict = ReadVerdict(DoMove(step))
若 replay 过程中出现 wall,则说明路径失效,直接报错

ProbeOneStep(path_to_cur, dir):
ReplayPath(path_to_cur)
PrepareDetectors()
reply = DoMove(dir)
return ReadVerdict(reply)

DFS(x, y):
标记 (x, y) 已访问
for dir in [R, D, L, U]:
nx, ny = 邻格
若越界或该格已判定,continue

verdict = ProbeOneStep(path[x][y], dir)

if verdict == wall:
maze[ny][nx] = '#'
else:
maze[ny][nx] = '.'
path[nx][ny] = path[x][y] + dir
if 未访问:
DFS(nx, ny)

BuildShortestPath():
对已恢复出的 '.' 地图做 BFS
从起点求到终点的最短路径

得到以下迷宫图

1
2
3
4
5
6
7
8
9
10
11
12
13
.......#.....
######.###.#.
.....#.....#.
.###.#######.
.#.........#.
.#.#.#####.#.
.#.#.#...#.#.
.#.###.#.###.
.#.....#.#...
.#######.#.##
...#...#.#.#.
##.#.#.#.#.#.
.....#.#.....

方法二:可以通过 sys 直接读取分配内存读取到迷宫

image.png

image.png

最短路径求解

bfs

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
from __future__ import annotations

from collections import deque
import re
from pathlib import Path

INPUT_PATH = Path("Input.txt")
OPEN_TILES = {".", "*"}

def parse_point(text: str) -> tuple[int, int] | None:
match = re.search(r"\((\d+),\s*(\d+)\)", text)
if not match:
return None
return int(match.group(1)), int(match.group(2))

def load_maze(path: Path) -> tuple[list[str], tuple[int, int], tuple[int, int]]:
lines = [line.strip() for line in path.read_text(encoding="utf-8").splitlines() if line.strip()]
start: tuple[int, int] | None = None
goal: tuple[int, int] | None = None
maze: list[str] = []

for line in lines:
if line.startswith("start="):
left, _, right = line.partition("goal=")
start = parse_point(left)
goal = parse_point(right)
continue
if set(line) <= OPEN_TILES | {"#"}:
maze.append(line)

if not maze:
raise ValueError("Input.txt 中没有找到迷宫数据")

width = len(maze[0])
if any(len(row) != width for row in maze):
raise ValueError("迷宫每一行长度必须一致")

if start is None:
start = (0, 0)
if goal is None:
goal = (width - 1, len(maze) - 1)

return maze, start, goal

def is_open(maze: list[str], x: int, y: int) -> bool:
return maze[y][x] in OPEN_TILES

def solve(maze: list[str], start: tuple[int, int], goal: tuple[int, int]) -> str:
width = len(maze[0])
height = len(maze)
sx, sy = start
gx, gy = goal

if not (0 <= sx < width and 0 <= sy < height and is_open(maze, sx, sy)):
raise ValueError("起点不可达或越界")
if not (0 <= gx < width and 0 <= gy < height and is_open(maze, gx, gy)):
raise ValueError("终点不可达或越界")

moves = [
(1, 0, "R"),
(0, 1, "D"),
(-1, 0, "L"),
(0, -1, "U"),
]

queue = deque([start])
prev: dict[tuple[int, int], tuple[tuple[int, int], str] | None] = {start: None}

while queue:
x, y = queue.popleft()
if (x, y) == goal:
break

for dx, dy, step in moves:
nx = x + dx
ny = y + dy
if not (0 <= nx < width and 0 <= ny < height):
continue
if not is_open(maze, nx, ny):
continue
if (nx, ny) in prev:
continue
prev[(nx, ny)] = ((x, y), step)
queue.append((nx, ny))

if goal not in prev:
raise ValueError("找不到从起点到终点的路径")

path: list[str] = []
cur = goal
while cur != start:
parent, step = prev[cur] # type: ignore[misc]
path.append(step)
cur = parent
path.reverse()
return "".join(path)

def main() -> None:
maze, start, goal = load_maze(INPUT_PATH)
path = solve(maze, start, goal)
print(f"start={start} goal={goal}")
print(f"length={len(path)}")
print(path)

if __name__ == "__main__":
main()

Flag

flag{SHAD0WNT_HYPERVMX}

1
2
3
4
5
6
7
8
9
=============================================
ACCESS GRANTED - Credential extracted!
=============================================
[CREDENTIAL] flag{SHAD0WNT_HYPERVMX}
[*] Your input (32 ops sent): RRRRRRDDRRRRUURRDDDDDDDDLLDDDDRR
[*] Note: blocked ops are included above; the driver only counts successful ones.

[*] Press any key to exit...
[*] Mission aborted. ACE HQ standing by.
作者:YuHuanTin
链接:https://yuhuantin.icu/2026/04/30/2026-腾讯游戏安全-PC-初赛-WP/
来源:YuHuanTin's Blog
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 许可协议。著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。