剖析Windows Defender驱动程序:WdFilter(Part 2) - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

剖析Windows Defender驱动程序:WdFilter(Part 2)

xiaohui 系统安全 2020-05-18 09:01:24
24817438
收藏

导语:上一篇文章,我们着重介绍了初始化的过程,本文,我们会接着介绍回调中用到的主要函数。

上一篇文章,我们着重介绍了初始化的过程,本文,我们会接着介绍回调中用到的主要函数。

MpHandleProcessNotification

void __fastcall MpHandleProcessNotification(
  _In_  PEPROCESS       Process, 
  _In_  HANDLE          ParentId, 
  _In_  HANDLE          ProcessId, 
  _In_  BOOLEAN         Create, 
  _In_  BOOLEAN         IsTransacted, 
  _In_  PUNICODE_STRING ImageFileName, 
  _In_  PUNICODE_STRING CommandLine, 
  _Out_ PBYTE           AccessDenied
);

该函数有两个非常清晰的代码路径,它们由Create标志定义。在创建流程的情况下,过滤器中的第一步(可能也是最重要的步骤之一)是创建ProcessContext结构,这是在MpCreateProcessContext内部完成的。

NTSTATUS __fastcall MpCreateProcessContext(
  _In_  HANDLE          ProcessId, 
  _In_  LONGLONG        CreationTime, 
  _In_  PUNICODE_STRING FileNameAndCmdLine[2], // This is probably a struct with two UNICODE_STRING
  _Out_ PProcessCtx     *ProcessCtx
)

此函数主要从Lookaside MpProcessTable-> ProcessCtxLookaside分配内存以容纳一个Process Context,大小为0xC0-Tag MPpX,分配内存后,它将开始填充Process Context结构的成员,此结构如下所示:

typedef struct _ProcessCtx
{
  SHORT Magic;        // Set to 0xDA0F
  SHORT StructSize;   // Sizeof 0xC0
  LIST_ENTRY ProcessCtxList ;
  HANDLE ProcessId;
  QWORD CreationTime;
  PUNICODE_STRING ProcessCmdLine;
  INT RefCount;
  DWORD ProcessFlags;
  DWORD ProcessRules;
  QWORD SthWithCodeInjection;   // Requires further investigation 
  QWORD SthWithCodeInjection1;  // Both fields used in MpAllowCodeInjection
  PMP_DOC_RULE pDocRule;
  BOOLEAN (__fastcall *pCsrssPreScanHook)(PFLT_CALLBACK_DATA, FltStreamCtx *);
  INT field_60;
  INT NotificationsSent;
  INT field_68;
  INT field_6C;
  PVOID Wow64CpuImageBase;
  INT ProcessSubsystemInformation;
  PUNICODE_STRING ImageFileName;
  INT64 InfoSetFromUserSpace;   // This requires further investigation too
  INT64 InfoSetFromUserSpace1;  // This data is filled in the function
  INT64 InfoSetFromUserSpace2;  // MpSetProcessInfoByContext which uses
  INT64 InfoSetFromUserSpace3;  // data that comes from MsMpEng to populate
  INT64 InfoSetFromUserSpace4;  // this fields
  INT64 InfoSetFromUserSpace5;
  _PS_PROTECTION ProcessProtection;
  INT StreamHandleCtxCount;
} ProcessCtx, *PProcessCtx;

一旦检索或创建了流程上下文(从现在开始即为ProcessCtx),该函数将继续查看是否应将文档规则附加到此流程。这是在MpSetProcessDocOpenRule内部完成的,涉及两个结构。一个保存所有文档规则的列表,一个保存每个规则的列表。

typedef struct _MP_DOC_OPEN_RULES
{
  SHORT Magic;        // Set to 0xDA14
  SHORT StructSize;   // Sizeof 0x100 
  SINGLE_LIST_ENTRY *__shifted(MP_DOC_RULE,8) DocObjectsList;
  ERESOURCE DocRulesResource;
  struct _PAGED_LOOKASIDE_LIST DocObjectsLookasideList;
} MP_DOC_OPEN_RULES, *PMP_DOC_OPEN_RULES;

typedef struct _MP_DOC_RULE
{
  SHORT Magic;        // Set to 0xDA15
  SHORT StructSize;   // Sizeof 0x228
  INT RefCount;
  SINGLE_LIST_ENTRY SingleListEntryDocRules;
  WCHAR DocProcessName[261];
  PCWSTR RuleExtension;
} MP_DOC_RULE, *PMP_DOC_RULE;

该代码基本上会迭代比较ImageFileName和DocProcessName的单个列表条目,如果有任何规则匹配,则MP_DOC_RULE结构的指针将保存在ProcessCtx-> pDocRule中。

下一步是检查是否已创建上下文的进程是csrss.exe – MpSetProcessPreScanHook,如果存在,则指向CsrssPreScanHook的指针将保存在ProcessCtx.pCsrssPreScanHook中,并设置标志MpData-> pCsrssHookData-> HookSetFlag,仅对csrss.exe的ProcessCtx执行此操作。

12.png

通知流程创建之前的最后一步是检查流程是否与某些异常匹配,并相应地设置ProcessCtx.ProcessFlags。为此,需要执行以下三个函数:

1. MpSetProcessExempt;

2. MpSetProcessHardening;

3. MpSetProcessHardeningExclusion。

第一个将遍历以下结构的单个列表条目,其中有很多结构。

// Sizeof 0x20
typedef struct _MP_PROCESS_EXCLUDED
{
  SINGLE_LIST_ENTRY ExcludedProcessList;
  UNICODE_STRING ProcessPath;
  BYTE NoBackslashFlag;
  BYTE WildcardPathFlag;
} MP_PROCESS_EXCLUDED, *PMP_PROCESS_EXCLUDED;

并且它将检查ImageFileName的FinalComponent是否为前缀或等于列表中的任何前缀,如果匹配,则将通过将OR设置为0x1来设置ProcessFlags。驱动程序具有根据从用户空间收到的消息将进程/路径添加到MP_PROCESS_EXCLUDED列表的能力。

14.png

此检查中有一种特殊情况,当进程为MsMpEng时在这种情况下,ProcessFlags将使用0x9进行匹配。

第二次检查将首先检查FinalComponent是否与mpcmdrun.exe或msmpeng.exe匹配,如果它确实使用先前创建的MpServiceSID,它将检查进程的访问令牌是否与该SID相匹配。如果这些进程名都不匹配,则它将检查nissrv.exe和NriServiceSID。如果成功匹配了其中任何一种情况,那么ProcessFlags将被0x10赋值。

如果我们运行的是MpFilter而不是WdFilter,则还有另一种可能的情况,在这种情况下,进程名称将再次与msseces.exe进行比较,如果与进程名称匹配,则将与0x80进行比较。

如果需要,将首先创建最后一个检查,以列出强化的排除进程的列表条目。此列表条目的值在WdFilter中以硬编码形式保留名称,该标志指示其适用于哪个系统,最后指示将应用于ProcessFlags的掩码值。

15.png

用这些值填充下面的结构:

// Sizeof 0x20
typedef struct _MP_PROCESS_HARDENING_EXCLUDED
{
  LIST_ENTRY ProcessExcludedList;
  PUNICODE_STRING ProcessPath;
  INT ProcessHardeningExcludedMask;
} MP_PROCESS_HARDENING_EXCLUDED, *PMP_PROCESS_HARDENING_EXCLUDED;

一旦结构被填充,检查过程就非常标准了,代码将比较名称,如果名称匹配,则将ProcessHardeningExcludedFlag应用于ProcessCtx.ProcessFlags。在下图中,我们可以看到我系统的MP_PROCESS_HARDENING_EXCLUDED中的进程列表。

17.png

// Sizeof 0x78
typedef struct _MP_PROCESS_EXCLUSION
{
  ERESOURCE ProcessExclusionResource;
  MP_PROCESS_EXCLUDED *ProcessExclusionList;
  MP_PROCESS_HARDENING_EXCLUDED *ProcessHardenedExclusionList;
} MP_PROCESS_EXCLUSION, *PMP_PROCESS_EXCLUSION;

完成所有这些操作后,“默认” ProcessCtx已准备就绪,现在是时候通知回调\Callback\WdProcessNotificationCallback。 Argument1将包含以下结构:

typdef struct _MP_PROCESS_CB_NOTIFY
{
  HANDLE ProcessId;
  HANDLE ParentId;
  PUNICODE_STRING ImageFileName;
  INT OperationType;  // ProcessCreation = 1; ProcessTermination = 2; SetProcessInfo = 3
  BYTE ProcessFlags;
} MP_PROCESS_CB_NOTIFY, *PMP_PROCESS_CB_NOTIFY;

通知回调之后,我们只需要最后一步即可完成ProcessNotification回调,此步骤是向用户空间进程发送一条消息,侦听端口ProtectionPortServerCookie。

在进入创建和发送消息的功能之前,我将快速解释一下未设置标志Create(表示创建过程正在退出)的情况。在这种情况下,ProcessCtx将由进程ID获得,并使用该ProcessCtx将填充结构MP_PROCESS_CB_NOTIFY并通知回调。此后,将调用MpSendProcessMessage来创建和发送消息。

最后一个细节是对MpCopyCacheProcessTerminate的调用,该调用将在MP_COPY_CACHE_ENTRY数组上进行迭代:

typedef struct _MP_COPY_CACHE_ENTRY
{
  DWORD Flags;
  HANDLE ProcessId;
  HANDLE ThreadId;
  UNICODE_STRING FileName;
  QWORD FileSize;
  QWORD TimeStamp;
  INT64 qword38;
} MP_COPY_CACHE_ENTRY, *PMP_COPY_CACHE_ENTRY;

MpSendProcessMessage

NTSTATUS __fastcall MpSendProcessMessage(
  _In_  BYTE                CreateFlag,
  _In_  PEPROCESS           Process, 
  _In_  HANDLE              ProcessId, 
  _In_  BOOLEAN             IsTransacted, 
  _In_  HANDLE              ParentId, 
  _In_  PAuxPidCreationTime ParentPidAndCreationTime, 
  _In_  PUNICODE_STRING     ImageFileName, 
  _In_  PProcessCtx         ProcessCtx, 
  _In_  PUNICODE_STRING     CommandLine, 
  _Out_ PBYTE               AccessDenied
)

在这个函数中,两个消息都将使用异步结构创建,但是如果参数CreateFlag是0x1,那么消息将同步发送(FltSendMessage),如果是0x0,它将被加入队列,工作线程将处理它。

在该调用之后,我们将得到一个需要用特定数据填充的缓冲区。同样,该缓冲区将8个字节移入名为AsyncMessageData的结构中。结构看起来如下所示:

typedef struct _AsyncMessageData
{
  INT Magic;
  INT Size;
  INT64 NotificationNumber;
  DWORD SizeOfData;
  INT RefCount;
  INT TypeOfOperation;
  union {
    // This are the ones I have for now
    ImageLoadAndProcessNotifyMessage ImageLoadAndProcessNotify;
    TrustedOrUntrustedProcessMessage TrustedProcess;
    ThreadNotifyMessage ThreadNotify;
    CheckJournalMessage CheckJournal;
  };
} AsyncMessageData, *PAsyncMessageData;

正如我们所看到的,这个结构包含一个union,在这个union中,每种不同消息类型的特定数据都将启动。在这种情况下,我们将重点关注与ProcessNotify有关的数据,这个结构看起来像这样:

typedef struct _ImageLoadAndProcessNotifyMessage
{
  AuxPidCreationTime ParentProcess;   // ZwOpenProcess -> PsGetProcessCreateTimeQuadPart
  AuxPidCreationTime CurrentProcess;  // ZwOpenProcess -> PsGetProcessCreateTimeQuadPart
  BYTE CreateFlag;
  BYTE ProcessFlags;
  BYTE UnkGap[10];  // Weird alignment :S 
  DWORD FileNameLength;
  DWORD OffsetToImageFileName;
  DWORD SessionId;
  DWORD CommandLineLenght;
  DWORD OffsetToCommandLine;
  DWORD TokenElevationType;
  DWORD TokenElevation;
  DWORD TokenIntegrityLevel;
  DWORD Unk;
  AuxPidCreationTime CreatorProcess;  // Parameter -> ParentPidAndCreationTime
} ImageLoadAndProcessNotifyMessage, *PImageLoadAndProcessNotifyMessage;

发送消息后,在使用FltSendMessage的情况下,该函数将继续检查调用状态并相应地填充MpData的某些字段:

· FltSendMessageCount

· FltSendMessageError

· FltSendMessageStatusTimeout

如果一切顺利,代码将检查ReplyBuffer(第一个字节应为0x5D,第二个字应为0x60,即回复消息的大小)。此回复缓冲区可以包含的内容包括是否允许创建进程(字节0x48)。

最后,完成之前的最后一步是设置流程信息(主要使用从ReplyBuffer接收的信息),然后,它将测试ProcessFlags & 0x20 || ProcessFlags & 0x18,将进程添加到“受信任”或“不受信任”列表中,分别在MpSetTrustedProcess或MpSetUntrustedProcess内部完成。

MpPowerStatusCallback

在结束之前还有最后一件事,我之前说过,我将稍微介绍一下在初始化期间注册的power-setting回调例程。

NTSTATUS MpPowerStatusCallback(
  LPCGUID SettingGuid, 
  PVOID Value, 
  ULONG ValueLength, 
  PVOID Context
  )
{
  if (Value && Value == 4 && IsEqualGUID(SettingGuid, GUID_LOW_POWER_EPOCH)) {
    if ( *(ULONG *) Value ) {
      if ( *(ULONG *) Value == 1 ) {
        MpData->LowPowerEpochOn = 1;
        MpData->MachineUptime = 0;
      }
    } else {
      MpData->MachineUptime = *(ULONG64 *) 0xFFFFF78000000014;
    }
  }
  return STATUS_SUCCESS;
}

附注

这个小的windbg脚本使我们可以打印系统中所有ProcessCtx所需的任何数据,我们只需要WdFilter的符号并根据需要调整命令!list。

r @$t0 = poi(poi(WdFilter!MpProcessTable)+180); // Pointer to MpProcessTable->ProcessCtxArray
.for (r $t1 = 0; @$t1 != 0x80; r $t1 = @$t1+1)  // Array size 0x80
{  
  r @$t2 = @$t0+10*@$t1;                        // Move pointer to next LIST_ENTRY
  .if ( @$t2 == poi(@$t2) ) {                   // Check if our pointer value is the same as Blink
    .continue                                   
  } 
  .else {                                       // We walk the LIST_ENTRY and print whatever
                                                // member we want from ProcessCtx in this case
                                                // ProcessCtx.ProcessId and ProcessCtx.ProcessCmdLine 
   !list -t nt!_LIST_ENTRY.Flink -x "dd @$extret+10 L1; dS /c100 poi(@$extret+20)" -a "L1" poi(@$t2) 
  } 
}

如果运行上述脚本,你应该会看到类似以下内容的信息:

27.png

到此为止,我们已经了解了WdFilter是如何初始化的,以及它如何在整个过程创建回调中处理过程创建。另外,我们还了解了ProcessCtx结构,该结构将在整个驱动程序中使用,以跟踪系统上运行的不同进程。接下里,我们将重点了解以下内容:

1. 映像加载回调;

2. 线程创建回调;

3. 发送同步/异步通知。

不过请注意:我将在本文中解释的回调主要依赖于ProcessCtx.ProcessRules,尽管我尝试使用不同类型的进程(甚至是恶意软件),但我仍无法确定每种规则对应的进程类型(也许与Windows Defender配置有关)。

出于演示目的,我已强制代码遵循不同的路径。

MpCreateThreadNotifyRoutineEx-MpCreateThreadNotifyRoutine

我们将看到的前两个回调是MpCreateThreadNotifyRoutine和MpCreateThreadNotifyRoutineEx,它们在创建新线程或删除线程时都会收到通知。有两种不同的回调,因为第一个使用PsSetCreateThreadNotifyRoutine注册,而第二个使用PsSetCreateThreadNotifyRoutineEx注册,此函数从Windows 10开始可用,并且指向它的指针保存在MpData中,当然如果第二个回调的指针为NULL将不会被注册。

如PsSetCreateThreadNotifyRoutineEx文档的备注部分所述,这两个函数的不同之处在于执行回调的上下文引用了MS文档:“使用PsSetCreateThreadNotifyRoutine,回调在创建者线程上执行。使用PsSetCreateThreadNotifyRoutineEx,可以在新创建的线程上执行回调。”

MpCreateThreadNotifyRoutine

回调的代码差异可能超出你的预期,因此我们将对两者进行研究。从MpCreateThreadNotifyRoutine开始,请记住,此回调在创建者线程的上下文中执行,该回调将检查以下三件事来执行:

1. Create参数设置为TRUE;

2. ProcessId与0x4(系统)不同;

3. Curren线程不是一个系统线程!PsIsSystemThread。

如果满足这三个条件,则代码将继续设置一个标志,该标志指示当前进程是否与参数ProcessId中的进程相同。

一个进程可能正在另一个进程中创建线程,并且由于此回调在创建者线程的上下文中执行,因此当前进程将是创建者,而参数ProcessId将是线程将要执行的那个。

如果它们相同,则将根据规则NotifyNewThreadSameProcess(0x10000000)测试当前进程ProcessCtx.ProcessRules,并将相应地设置一个标志。如果当前进程不相同,则将根据规则NotifyNewThreadDifferentProcess(0x400000)测试ProcessRules并相应地设置其他标志。如果未设置这些标志,则回调将返回。下面的伪代码将显示这种行为,以防我的解释不够清楚。

BOOLEAN SameProcess = 1;
BOOLEAN NotifyNewThreadSameProcFlag = 0;
BOOLEAN NotifyNewThreadDiffProcFlag = 0;

if ( Create && ProcessId != 4 && !PsIsSystemThread(KeGetCurrentThread()) ) {

    SameProcess = ProcessId == PsGetCurrentProcessId();
    // Retrieve the ProcessCtx by the ProcessId
    MpGetProcessContextById(PsGetCurrentProcessId(), &CurrentProcessCtx);

    if ( SameProcess && CurrentProcessCtx->ProcessRules & NotifyNewThreadSameProcess ) 
        NotifyNewThreadSameProcFlag = 1;
    if ( !SameProcess && CurrentProcessCtx->ProcessRules & NotifyNewThreadDifferentProcess )
        NotifyNewThreadDiffProcFlag = 1;

    if ( !NotifyNewThreadSameProcFlag && !NotifyNewThreadDiffProcFlag )
        goto Cleanup;
}

如果设置了其中一个标志,则代码将继续获取我称为AuxPidCreationTime的结构,我们在第1部分中看到了,但其余部分包含de PID和进程的CreationTime,在这两个进程都具有此结构之后(即使是相同的过程也获得两次),代码将继续调用MpGetPriorityInfo,此函数将主要调用FltRetrieveIoPriorityInfo来获取当前线程的IO_PRIORITY_INFO并使用此数据填充我创建的MP_IO_PRIORITY结构:

typedef struct _MP_IO_PRIORITY
{
    IO_PRIORITY_HINT IoPriority
    ULONG ThreadPriority  
    ULONG PagePriority    
} MP_IO_PRIORITY, *PMP_IO_PRIORITY;

根据设置的标志,不同的消息将被发送到MsMpEng。对于NotifyNewThreadDifferentProcess,将以OperationType等于NewThreadDifferentProcess(0x3)的方式调用MpSendSyncMonitorNotification,并且数据将是执行线程的进程的AuxPidCreationTime。

3.png

如果线程是在同一个进程中创建的,那么在调用MpSendSyncMonitorNotification之前,将初始化要发送的数据,MpCreatePsThreadSyncMonitorData函数负责这一工作。这个函数将基本填充以下结构:

typedef struct _ThreadNotifySyncMessage
{
  AuxTidCreationTime CreatedThread;
  AuxTidCreationTime CurrentThread;
  AuxPidCreationTime Process;
  INT64 Unk;
  PVOID ThreadStartAddress;
} ThreadNotifySyncMessage, *PThreadNotifySyncMessage;

要获取ThreadStartAddress的值,它将打开以获取线程的句柄(PsLookupThreadByThreadId),然后使用这个句柄调用类ThreadQuerySetWin32StartAddress的ZwQueryInformationThread。一旦ThreadNotifySyncMessage被填充,函数MpSendSyncMonitorNotification将以这种结构被调用,因为Data和OperationType等于NewThreadSameProcess(0x6)。

5.png

最后,如果设置了NotifyNewThreadDifferentProcess,则回调将执行最后一步。此步骤将包括发送带有以下数据的异步通知:

typedef struct _ThreadNotifyMessage
{
  AuxPidCreationTime CurrentProcess;
  INT CurrentThreadId;
  AuxPidCreationTime CreatedThreadProcess;
  AuxTidCreationTime CreatedThread;
  WCHAR *ImageFileName;
} ThreadNotifyMessage, *PThreadNotifyMessage;

对于ImageFileName而言,此字段将从ProcessCtx中被检索到,在本例中,ProcessCtx对应于线程创建者进程中的一个字段,它可能与线程将要运行的那个字段不同。

7.png

MpCreateThreadNotifyRoutineEx

该例程比上一个例程简单得多,在本例中,该函数在新线程上执行,这基本上意味着当前进程将始终与参数ProcessId指示的进程匹配。首先,为了实际发送通知,必须满足以下条件:

1. MpProcessTable-> CreateThreadNotifyLock设置为不同于0的值(我知道,lock不是此字段的最佳名称,为零时被锁定);

2. 将参数设置为TRUE;

3. 除PsInitialSystemProcess以外的当前进程;

4. 在ProcessCtx.ProcessFlags中设置的ThreadNotifyRoutineExSet(0x400)标志;

5. 在ProcessCtx.ProcessRules中设置的规则NotifyProcessCmdLine(0x20000000)。

以下伪代码对此进行了更好的解释:

if ( _InterlockedCompareExchange(&MpProcessTable->CreateThreadNotifyLock, 0, 0) 
    && IoGetCurrentProcess() != PsInitialSystemProcess && Create ) {

    // Retrieve the ProcessCtx using the Process Object, in the end it will use
    // the CreationTime (PsGetProcessCreateTimeQuadPart) and the ProcessId (PsGetProcessId)   
    MpGetProcessContextByObject(IoGetCurrentProcess(), &ProcessCtx)

    // Same as ((ProcessCtx->ProcessFlags >> 10) & 1  && (ProcessCtx->ProcessRules >> 0x1D) & 1)
    if ((ProcessCtx->ProcessFlags & ThreadNotifyRoutineExSet) 
        && (ProcessCtx->ProcessRules & NotifyProcessCmdLine)) {
      .....
    }
}

这里需要说明一下,如果MP_DATA中指向PsSetCreateThreadNotifyRoutineEx的指针不为NULL,则在每个ProcessCtx中设置ThreadNotifyRoutineExSet标志:

9.png

在规则NotifyProcessCmdLine的情况下,它是在设置过程信息时来自MsMpEng的。同样,我没有设法通过任何过程触发此规则,所以我真的不知道该规则适用于哪种过程,我对此表示歉意。因此,在流程创建的最后,如果设置了此规则,则MpProcessTable-> CreateThreadNotifyLock值将增加:

10.png

返回到实际函数,如果满足所有条件,那么首先要递减CreateThreadNotifyLock并从ProcessCtx中删除ThreadNotifyRoutineExSet,一旦完成,将获得流程对象的句柄(ObOpenObjectByPointer,ObjectType为PsProcessType)此句柄将用于检索MpGetProcessCommandLineByHandle内的Process CommandLine,此函数几乎使用ZwQueryInformationProcess,并将ProcessInformationClass设置为ProcessCommandLineInformation。该命令行将与ProcessCtx-> ProcessCmdLine内部的命令行进行比较,以防万一它们不匹配,则该函数将获得MP_IO_PRIORITY,AuxPidCreationTime,并且它将调用MpSendSyncMonitorNotification并将这两个命令行作为数据。

11.png

如上图所示,如果有人修改了来自PEB的命令行,这个回调将通知MsMpEng被篡改的命令行。不过前提是,设置了ProcessCtx的规则和标志。

本文,我们介绍了回调中用到的主要函数。下一篇文章,我们会接着介绍回调的具体过程和方法。

本文翻译自:https://n4r1b.netlify.com/posts/2020/01/dissecting-the-windows-defender-driver-wdfilter-part-1/ https://n4r1b.netlify.com/posts/2020/02/dissecting-the-windows-defender-driver-wdfilter-part-2/如若转载,请注明原文地址:
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2024 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务