【反EDR 】概要

news/2025/1/19 9:54:59/文章来源:https://www.cnblogs.com/o-O-oO/p/18673079

一、什么是 EDR

EDR 是“端点检测和响应”的缩写。它是部署在每台机器上的代理,用于观察操作系统生成的事件以识别攻击。如果检测到某些东西,它将生成警报并将其发送到 SIEM 或 SOAR,由人工分析师进行查看。“响应”是指在识别威胁后执行的操作,例如隔离主机,这不是本文的一部分。EPP 是端点保护平台,它将尝试中断攻击,而不仅仅是检测攻击。

MDE (Microsoft Defender for Endpoint) 的 UI:

我们可以看到 EDR 检测到了某些东西,并试图向分析师提供有关该事件的更多信息:涉及的进程、它们的参数和哈希值、子进程等。分析师最终必须决定这是误报还是主动攻击。但一般来说,红队希望避免引起任何警报,并试图保持低调。

EDR 尝试在痛苦金字塔的更高层实施检测,主要是在TTP:工具、技术、程序。

二、理想化的 EDR

了解和理解哪怕只是一个 EDR 都很困难,而了解和理解所有的 EDR 则是不可能的。这里写的 EDR 是理想 EDR 的抽象版本。与其说是今天正在做的事情,不如说是理论上使用可用的 Windows 传感器/遥测基础设施可以实现什么。最接近的灵感是 Windows Defender for Endpoint (MDE),我用它来进行测试。

我不会教你如何绕过特定的 EDR,而是教你如何从概念上思考攻击面,以实施你自己的技术。EDR 的实际内部工作原理大多未知(Elastic 除外),并且被视为黑盒。虽然我们大多知道 EDR 会收到什么样的信息,但不太清楚这些信息在内部是如何被使用和关联的。

作为一名黑客,我们对系统的输入和输出很感兴趣。本文应该对输入进行概述。

三、Shellcode 加载器

加载程序将加载 shellcode。shellcode 通常是我们的信标,例如 CobaltStrike、Sliver 或 Metasploit。

加载程序包含加密的 shellcode,将其加载到内存中并执行。

目标是使该过程不会被 EDR 检测到初始访问 (IA)。

Shellcode 加载器示例

在执行shellcode时,通常的步骤如下:

  • 分配具有读写权限的内存区域
    
  • 将 shellcode复制到该区域(也对其进行解密)
    
  • 将内存区域的权限更改为读取-执行
    
  • 执行shellcode
    

在 C 语言中看起来像这样,但在大多数语言中都类似:

char *shellcode = "\xAA\xBB...";
char *dest = VirtualAlloc(NULL, 0x1234, 0x3000, p_RW);
memcpy(dest, shellcode, 0x1234)
VirtualProtect(dest, 0x1234, p_RX, &result)
(*(void(*)())(dest))(); // jump to dest: execute shellcode

这个简单的配方有很多变体,其中一些专注于远程进程的 shellcode 注入。其工作原理相同,在OpenProcess()目标进程上使用,并将其用作hProcess函数调用(如VirtualAlloc(hProcess, ...)和WriteProcessMemory(hProcess, ...))的参数。EDR 会更严格地审查跨进程访问hProcess。

另一个典型的做法是通过创建新线程来调用 shellcode。无论是CreateThread()在您自己的地址空间中,还是CreateRemoteThread()用于进程注入或模块踩踏。

复制本身,这里由用户空间函数执行memcpy(),也可以用或其他函数完成RtlCopyMemory()。

四、EDR 检测

检测加载器主要有三种技术:

文件扫描:文件签名(“yara”)扫描

内存扫描:签名(“yara”)扫描进程内存

遥测/行为:进程执行的操作(主要通过操作系统)

例如,Windows Defender Antivirus 可实现 AV 扫描,而 Windows Defender for Endpoint MDE 是一种 EDR,它严重依赖遥测来执行行为分析。如果它觉得有必要,它也会扫描进程的内存。

我把这称为“Bubbles of Bane”:

C2 框架生成的大多数 .exe 文件植入程序都经过签名,因此没有用处。因此,第一步是混淆代码,这很难。
有关示例,请参阅利用 Cobalt Strike 配置文件的强大功能来逃避 EDR 。

或者也可以使用加载器,它将植入物作为有效载荷携带并在执行时加载它。这种技术通常使用 C2 生成的 shellcode(或者,可以使用 C2 生成的 DLL 输出或 EXE。可以将其转换为 Shellcode 或 DLL,例如使用 Donut)。
使用加载器的优点是可以加密有效载荷,因此唯一需要从 AV 文件签名扫描中混淆的是实际的加载器本身。

公共加载器通常迟早都会被签名。但它们很容易用 Windows 理解的基本上所有语言编写(C、.net C#、vba、vbs、powershell、jscript……)。简单的自写加载器出奇地有效,正如本文将展示的那样。

除了扫描文件之外,EDR 还可以扫描进程的内存。这样可以击败加载程序,因为有效载荷代码必须在内存中解密才能执行。为了避免在内存中检测到,进程需要在休眠时加密其内存区域。这样,当 EDR 扫描进程时,内存中就不应该有任何可疑内容。内存扫描是一项性能密集型操作,只有当 EDR 认为值得时才会执行。这是基于收集的遥测数据(或定期“按需”执行,例如每天一次)。

典型的内存扫描器是 pe-sieve 和 moneta

大多数检测用例都依赖于遥测:Windows 中的重要函数调用会生成事件,这些事件由 EDR 进行处理、关联和分析。例如更改内存区域的权限、创建进程和线程、复制内存等。

例如,如果我们使用加载程序绕过 AV,并简单地为我们的 shellcode 分配一个内存区域,我们就不会为 EDR 生成太多遥测数据。但有效载荷将被内存扫描仪检测到。如果我们引入内存加密来绕过内存扫描仪,那么我们会生成更多的遥测数据,进而可以用来检测内存加密。

带有艾克记忆加密的Bubbles of Bane:

五、AV 签名扫描

当文件被写入磁盘时,AV 会对其进行扫描。AV 有一个包含已知恶意软件签名的数据库(如 yara 规则)。
文件写入事件由操作系统生成,并通过 AMSI 或内核微过滤器传递给 AV。

签名扫描基于文件的静态内容。将解析 PE 标头并扫描 PE 部分的内容。它发生在 EXE 执行之前。
一旦检测到,将在执行前删除该文件。

签名看起来类似于 yara 规则

// https://github.com/Yara-Rules/rules/blob/master/malware/APT_APT17.yar (shortened)
rule APT17_Sample_FXSST_DLL
{meta:...strings:$x1 = "Microsoft? Windows? Operating System" fullword wide$x2 = "fxsst.dll" fullword ascii$y1 = "DllRegisterServer" fullword ascii$y2 = ".cSV" fullword ascii$s1 = "VirtualProtect"$s2 = "Sleep"$s3 = "GetModuleFileName"condition:uint16(0) == 0x5a4d and filesize < 800KB and ( 1 of ($x*) or all of ($y*) ) and all of ($s*)
}

一个通用的解决方案是代码混淆,本文不会介绍它。它通常不能可靠地应用于编译后的代码,但需要纳入编译过程。这意味着每个工具都需要自己实现它。

它将解决我们所有的问题:磁盘或内存中没有签名,不需要加载它,因此没有遥测。

https://retooling.io/blog/an-unexpected-journey-into-microsoft-defenders-signature-world https://avred.r00ted.ch

六、AV 仿真

AV 组件还将执行目标二进制的模拟。

模拟意味着 AV 将自行读取和解释 .text 部分中的 ASM 指令。它不会在本机执行这些指令,它不是虚拟化执行,也不是 qemu/bochs 完整模拟。它是一种 CPU 模拟,包括常见的 Windows 系统调用和子系统。

伪代码如下:

asm_bytes = [0xB8, 0x04, 0x00, 0x00, 0x00, # mov eax, 40xBB, 0x06, 0x00, 0x00, 0x00, # mov ebx, 60x01, 0xD8                      # add eax, ebx]asm_instructions = disassembler.disasm(asm_bytes);# asm_instructions = [# { name = "mov", src = "4", dst="eax" }# { name = "mov", src = "6", dst="ebx" }# { name = "add", src = "ebx", dst="eax" }# ]for instruction in asm_instructions:if instruction.name == "add":register[instruction.dst] += register[instruction.src]if instruction.name == "mov":...

AV 仿真为 X86 汇编创建了自己的“解释器”,并重新实现了部分 Windows 操作系统系统调用,以及虚拟文件系统(FileOpen())、虚拟注册表RegOpen()、虚假进程等。该ntdll.dll函数 GetUserNameA()可以实现为始终返回“JohnDoe”。

RedTeamer 的示例经验:

  • 编写加载器

  • 插入 Metasploit shellcode

  • 文件被放到磁盘上时被检测到

然后:

  • 编写第二个加载器

  • 使用强 AES 加密 metasploit shellcode

  • 当放到磁盘上时仍能检测到

AV 模拟器将执行/模拟加载程序。一段时间后,执行停止,并在内存中发现 Metasploit shellcode 未加密。然后 AV 将在内存中检测它的签名。

检测模拟器的可能性是无限的。但一般来说,模拟器不会永远运行,而是受到以下限制:

参考:

#Windows Offender:对 Windows Defender 的防病毒模拟器进行逆向工程 (视频)https://i.blackhat.com/us-18/Thu-August-9/us-18-Bulazel-Windows-Offender-Reverse-Engineering-Windows-Defenders-Antivirus-Emulator.pdf

接收事件

EDR 通过操作系统接收进程正在执行的事件:

接收数据的渠道主要有两个:

1、用户模式(挂钩 API)

2、内核回调(ETW、ETW-TI、内核模式驱动程序)

当添加/删除/更改某些内容时,这些传感器将创建有关系统正在发生的事情的事件,例如:

  • 文件

  • 注册表项

  • 进程、线程

  • 内存区域

EDR 将包含与恶意行为事件相匹配的规则。
规则可以是:

精确/脆弱:能很好地检测某一特定事物(低假阳性FP),容易绕过

稳健性:更通用的检测,更难绕过,FP 更高,更多异常

请注意,EDR 本身无法看到进程内部的数据修改。换句话说,调用函数RtlCopyMemory()的进程ntdll.dll可能会生成遥测数据,因为ntdll.dll可以将其挂钩。在 for 循环中对字节复制执行相同操作不会产生任何遥测数据。

遥测数据可从挂钩ntdll.dll和内核获取。用户模式挂钩可以轻松删除,但这会生成遥测数据。内核空间事件更可靠,无法删除。

请注意,Windows 的主要执行单元是线程,而不是进程。但为了简单起见,我将主要使用进程。

该图形有点过于简单,可以通过更多传感器进行扩展,这些传感器是 EDR 的输入:

因此,EDR 输入为:

  • 用户模式钩子/AMSI

  • 内核回调

  • ETW

  • ETW-TI

我将对每一个问题分别进行讨论。

用户模式钩子

虽然 Linux 的官方内核接口是系统调用,但对于 Windows 来说,它的是ntdll.dll。这称为本机 API (NtAPI)。ntdll.dll将为我们调用正确的系统调用。Windows 应用程序接口 (WinAPI),其他 DLL 类似kernel32.dll,都在末尾使用或调用 NtAPI ( ntdll.dll)。请注意,系统调用号可能会因 Windows 版本而异,因此对它们进行硬编码并不可靠。

示例 NtAPI 函数ntdll.dll,使用 ASM 指令执行系统调用syscall:

SysNtCreateFile procmov r10, rcxmov eax, 55hsyscallretSysNtCreateFile endp

典型的 WinAPI 调用,带有一个hook:

用户空间钩子只是ntdll.dll导出函数中的补丁,它在函数执行之前调用另一个 DLL。Windows 提供了直接钩子函数的功能。

Original Function On-Disk: EDR Hooked Function In-Memory:---------------------- -----------------------mov r10, rcx mov r10, rcx
>mov eax, 50h jmp 0x7ffaeadea621test byte ptr [0x7FFE0h], 1          test byte ptr [0x7FFE0h], 1jne 0x17e76540ea5 jne 0x17e76540ea5syscall syscallret                                     ret

常见挂钩ntdll.dll函数的示例:

EDR 接收函数调用名称及其参数作为遥测数据。

这是通过使用内核回调(PsSetCreateProcessNotifyRoutine)在早期阶段每当有新进程创建时得到通知,然后将 DLL 注入到该进程中(如amsi.dll),通过使用异步过程调用(kKAPC 注入)修补原始ntdll.dll函数以绕道而行来实现的amsi.dll。

ntdll.dll修补后,每个函数调用都会被拦截amsi.dll。

使用 KAPC 进行 EDR 函数挂钩将创建一个执行挂钩的 APC。“Early Bird APC 注入”技术使用相同的 APC 机制,因此可以在执行 KAPC 挂钩之前运行。

可以使用以下方法绕过用户模式钩子:

  • 直接系统调用(避免调用ntdll.dll)

  • 间接系统调用(调用ntdll.dll函数,但在钩子之后)

  • 修补/恢复ntdll.dll(彻底移除挂钩)

用户模式钩子很容易被绕过,因为它们完全位于“我们自己的”内存空间中,我们可以自由地对其进行操作。但恢复ntdll.dll自身会产生遥测数据,这就是为什么要使用直接系统调用来实现这一点的原因。

EDR 不应仅依赖用户模式钩子,而应仅将其用于辅助遥测。但它们提供的信息比内核回调更多。内核回调仅“看到” syscall/ntdll.dll 函数,而不是最初启动的原始函数。这很有用,因为它可以生成更通用的检测,而无需挂钩所有奇怪和不寻常的 DLL 函数。但它可能会产生更多的误报,因为仅使用系统调用来识别“非恶意”行为更加困难。

❗例如, 和CreateFileA()都会CreateFileW()在 最后调用。OpenFile()CreateFileTransacted()NtCreateFile()

请注意,调用堆栈可以显示链中最初调用了哪个函数。用户模式钩子越来越少使用,而且并非所有 EDR 都使用用户模式钩子( 来源):

内核遥测

Windows 操作系统以通知回调例程的形式提供有关进程的信息。尤其是有关进程、线程和映像创建的信息。它由内核本身生成,无法像使用用户模式挂钩(没有内核权限)那样抑制它们。

这些回调是在相关进程和线程的上下文中启动的。因此,事件包含有关原始进程的信息。

内核模式检测有多种不同的来源:

  • ETW(Windows 事件跟踪基础结构)

  • ETW-TI(线程智能)

  • 内核回调(PsSetCreateProcessNotifyRoutine 等)

  • NDIS / Minifilter 驱动程序(用于文件系统)

内核回调包括:

  • PsSetCreateProcessNotifyRoutine:进程创建、终止

  • PsSetCreateThreadNotifyRoutine:线程创建、删除

  • PsSetLoadImageNotifyRoutine:Windows 图像加载器

  • ObRegisterCallbacks:对象管理器回调,如 NtOpenProcess、NtOpenThread、NtOpenFile 等

参考:

https://blog.whiteflag.io/blog/from-windows-drivers-to-a-almost-complete-working-edr/

一个示例事件是PS_CREATE_NOTIFY回调,它向 EDR 提供不同的信息:

Sysmon 可以从内核捕获该事件,并会产生 以下内容:

Process Create:
RuleName: -
UtcTime: 2024-04-28 22:08:22.025ProcessGuid: {a23eae89-bd56-5903-0000-0010e9d95e00}
ProcessId: 6228
Image: C:\Windows\System32\wbem\WmiPrvSE.exe
FileVersion: 10.0.22621.1 (WinBuild.160101.0800)
Description: WMI Provider Host
Product: Microsoft® Windows® Operating System
Company: Microsoft Corporation
OriginalFileName: Wmiprvse.exe
CommandLine: C:\Windows\system32\wbem\wmiprvse.exe -secured -Embedding
CurrentDirectory: C:\Windows\system32\User: NT AUTHORITY\NETWORK SERVICE
LogonGuid: {a23eae89-b357-5903-0000-002005eb0700}
LogonId: 0x7EB05
TerminalSessionId: 1
IntegrityLevel: System
Hashes: SHA1=91180ED89976D16353404AC982A422A707F2AE37,MD5=7528CCABACCD5C1748E63E192097472A,SHA256=196CABED59111B6C4BBF78C84A56846D96CBBC4F06935A4FD4E6432EF0AE4083,IMPHASH=144C0DFA3875D7237B37631C52D608CBParentProcessGuid: {a23eae89-bd28-5903-0000-00102f345d00}
ParentProcessId: 580
ParentImage: C:\Windows\System32\svchost.exe
ParentCommandLine: C:\Windows\system32\svchost.exe -k DcomLaunch -p
ParentUser: NT AUTHORITY\SYSTEM

请注意,只有字段ImageFilename、CommandLine、ParentProcessId 直接转换为内核事件的Image、CommandLine、ParentProcessId。但大多数其他信息都是由 Sysmon 额外收集的。这些附加信息是通过查询内核(例如通过GetProcessInformation在 上发出 )收集的ProcessId。或者以其他方式收集,例如解析进程的 PEB。并非所有提供的信息都同样值得信赖。

使用 SilkETW 记录的ETWImageLoad事件Microsoft-Windows-kernel-Process:

{ProviderGuid: "22fb2cd6-0e7b-422b-a0c7-2fad1fd0e716",ProviderName: "Microsoft-Windows-kernel-Process",EventName: "ImageLoad",ThreadID: 9584,ProcessID: 7536,ProcessName: "notepad",YaraMatch: [],Opcode: 0,OpcodeName: "Info",TimeStamp: "2024-07-08T19:06:10.8845667+01:00",PointerSize: 8,EventDataLength: 142,XmlEventData: {ProviderName: "Microsoft-Windows-kernel-Process",FormattedMessage: "Process 7’536 had an image loaded with name \Device\HarddiskVolume2\Windows\System32\notepad.exe. ",EventName: "ImageLoad"ProcessID: "7’536",PID: "7536",TID: "9584",PName: "",DefaultBase: "0x7ff631650000",ImageName: "\Device\HarddiskVolume2\Windows\System32\notepad.exe",ImageBase: "0x7ff631650000",ImageCheckSum: "265’248",ImageSize: "0x38000",MSec: "9705.0646",TimeDateStamp: "1’643’917’504",}
}

内存区域

启动 .exe 时,PE .exe 文件中的各个部分会被完全以块的形式复制到内存中。

.text包含汇编代码,而.data和类似的包含程序的数据。

可以使用或类似方法创建新的内存区域 VirtualAlloc()。

来自 PE 映像的内存区域称为备份区域。它们是值得信赖的,因为它们是 AV 在磁盘上扫描的 PE 文件的 1:1 副本。
内存区域由磁盘上的文件“备份”。它也可以称为 IMAGE 区域。

如果进程通过分配来分配额外的内存,则该内存为“无备份”。也称为用户内存或私有内存。没有文件后端,因此它是“无备份的”。

一般认为,内存区域具有以下属性:

  • USER/PRIVATE/Unbacked:不良、潜在恶意的 shellcode

  • 图像/背景:很好,非常值得信赖

这主要是因为漏洞利用或进程注入的 shellcode 通常位于 PRIVATE 内存中。此外,线程应该从备份区域启动。PRIVATE RWX 内存更加可疑。

这里有一些 IMG 类型(IMAGE,backed)的可信内存区域:

这里有一些类型为 PRV (PRIVATE,无备份) 的不可信内存区域:内存区域损坏

内存页的一个属性是写入时复制 (COW)。内存扫描器能够检查内存页是否被写入,这对于只读 .text 部分和其他部分来说并不常见,因为这些部分应该在进程之间共享。Moneta 通过PSAPI_WORKING_SET_EX_BLOCKfromPSAPI_WORKING_SET_EX_INFORMATION结构使用它。首选仅数据攻击,例如针对 AMSI 补丁或 ETW 补丁。

参考:

https://www.trustedsec.com/blog/windows-processes-nefarious-anomalies-and-you-memory-regionshttps://www.arashparsa.com/bypassing-pesieve-and-moneta-the-easiest-way-i-could-find/https://www.outflank.nl/blog/2023/10/05/solving-the-unhooking-problem/https://www.ired.team/offective-security/code-injection-process-injection/ntcreatesection-+-ntmapviewofsection-code-injection

内存扫描

内存签名扫描将检测内存中的恶意代码,无论是在 .text 还是数据部分(堆栈、堆、.data 等)。

它基本上与 AV 签名扫描相同;针对已知的恶意签名,对内存内容进行 grep 或 yara 检测。

内存扫描对性能要求很高。它不是持续进行的,而是依赖于触发器。

查询进程信息

EDR 在收到事件后,还将尝试丰富它:

  • 进程信息(如可执行文件名称和命令行参数)

  • 内存扫描(可能)

  • 处理图像文件扫描(很少)

EDR 不仅会接收事件,还会主动向操作系统查询更多信息。例如,在接收PS_CREATE_NOTIFY事件时,EDR 将获取有关创建事件的进程的更多信息,例如通过使用GetProcessInformation()或OpenProcess(),访问 PEB、参数或内存区域。或者访问ImageFileName并扫描原始 EXE 映像文件。

请注意,即使经过 SYSTEM 或 PPL,EDR 也是一个正常进程,并且拥有自己的专用内核驱动程序。凭借其 SYSTEM 权限,它可以收集有关几乎所有其他进程的信息。

以下是处理程序函数的一个例子PsSetCreateProcessNotifyRoutine:

void CreateProcessNotifyRoutine(HANDLE ppid, HANDLE pid, BOOLEAN create) {if (create) {PEPROCESS process = NULL;PUNICODE_STRING processName = NULL;// Retrieve the process name from the EPROCESS structurePsLookupProcessByProcessId(pid, &process);SeLocateProcessImageName(process, &processName);DbgPrint("MyDumbEDR: %d (%wZ) launched", pid, processName);}
}

处理函数仅接收pid进程的。为了显示图像名称,必须调用一些函数来访问 PEB 或 EPROCESS 结构。

数据存放在PEB(Process Environment Block,进程环境块GS:[0x60])中。它处于用户模式,可以自由操作。

  • 图像基地址

  • 已加载的 DLL

  • 工艺参数:

 图片名称参数环境变量工作目录

EPROCESS 是一个内核数据结构,不能直接操作(有时是间接操作):

  • 进程创建和退出时间

  • 进程 ID

  • 父进程 ID

  • PEB 地址

  • 图像文件名

类似于PEB中的流程参数图像名称也可以在 SectionObject 中使用

流程信息数据结构

PEB

typedef struct _PEB {BYTE Reserved1[2];BYTE BeingDebugged;BYTE Reserved2[1];PVOID Reserved3[2];PPEB_LDR_DATA Ldr;PRTL_USER_PROCESS_PARAMETERS ProcessParameters;PVOID Reserved4[3];PVOID AtlThunkSListPtr;PVOID Reserved5;ULONG Reserved6;PVOID Reserved7;ULONG Reserved8;ULONG AtlThunkSListPtr32;PVOID Reserved9[45];BYTE Reserved10[96];PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;BYTE Reserved11[128];PVOID Reserved12[1];ULONG SessionId;
} PEB, *PPEB;

ProcessParameters

typedef struct _RTL_USER_PROCESS_PARAMETERS {BYTE Reserved1[16];PVOID Reserved2[10];UNICODE_STRING ImagePathName;UNICODE_STRING CommandLine;
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;

调用堆栈分析

当进程调用某个 Windows 函数时,可以找出导致此次调用的父函数。这称为调用堆栈。

EDR 可以选择检查启动函数或 API 调用的进程,并分析调用堆栈中是否存在可疑情况:

使用该技术可以检测多种攻击和绕过。但它对性能要求较高。

调用堆栈的起源应该来自备用内存中的内存区域,经过支持 DLL(例如user32.dll),然后 ntdll.dll,最后syscall执行实际指令。

Elastic 具有调用堆栈分析规则来识别:

  • 直接系统调用

  • 基于回调的逃避攻击

  • 模块踩踏

  • 从未支持的区域加载库

  • 从不受支持的区域创建的进程

如果调用来自不受支持的区域,则它很可能来自 shellcode。

调用堆栈分析通常不适用于所有 API 函数。Elastic 提到以下内容:

  • VirtualAlloc、VirtualProtect

  • MapViewOfFile、MapViewOfFile2

  • VirtualAllocEx,VirtualProtectEx

  • 队列用户APC

  • 设置线程上下文

  • 写入进程内存、读取进程内存

参考:

https://www.elastic.co/security-labs/upping-the-ante-detecting-in-memory-threats-with-kernel-call-stackshttps://www.elastic.co/security-labs/doubling-down-etw-callstacks

线程状态分析

线程可能因各种原因而处于休眠状态。通过调查状态以及线程如何由于其调用堆栈而进入休眠状态,我们发现了休眠信标或内存加密的指标。

清理(欺骗)调用堆栈NtDelayExecution():

如果正在使用内存加密,则通常通过调用以下任一方法使线程进入睡眠状态:

  • Kernelbase.dll!SleepEx

  • ntdll.dll!NtDelayExecution

对这些睡眠函数的调用有疑点:

  • 调用堆栈中的虚拟内存调用

  • 来源位于非支持内存区域

参考:

https://www.mdsec.co.uk/2022/07/part-1-how-i-met-your-beacon-overview/

性能影响

EDR 的性能至关重要。如果开发人员的机器在安装 10,000 个 NPM 包时速度很慢,人们就会转向 Apple,因为 Apple 的保护措施较少,而 Microsoft 不能允许这种情况发生。这是一个严重的问题,因此 Microsoft 引入了异步Dev Drive扫描。

如果检测可以直接应用于罕见事件(比如打开 lsass.exe 的进程句柄),则性能最不密集的操作。内存扫描可能涉及迭代或 yara 扫描兆字节的 .text 部分,这非常昂贵。扫描文件是最昂贵的,即使使用 SSD 也是如此。

大多数检测都介于两者之间:一个或多个事件包含可疑信息,从而导致更多关联。然后这些事件可能会启动内存扫描。

什么可能触发内存扫描?

VirtualAlloc()并且WriteProcessMemory()通常被称为函数。CreateRemoteThread()不仅不经常被调用,而且它还是潜在恶意行为的更明显指标。

EDR 攻击

EDR 接收来自大量传感器的事件,这些传感器的可信度各不相同。此外,所需的许多信息在事件本身中不可用,而必须在内核(KPROCESS、EPROCESS)或进程内存空间本身(例如包括命令行参数、父进程 ID 的 PEB)中或通过内核访问。

许多攻击都依赖于TOCTOU漏洞的事实:检查时间、使用时间。

命令行欺骗

EDR 可以检查新生成的进程是否存在潜在的恶意命令行参数。
【例如】使用mimikatz时:mimikatz.exe "privilege::debug" "lsadump::sam"。即使我们重命名mimikatz.exe,参数privilege::debug也是一个非常明确的指标,误报率很低。

但在 Windows 中,命令行参数是可以伪造的。进程的命令行参数存储在相应进程的 PEB 中。此外,当我们创建新进程时,进程创建函数还将包含初始参数(要启动的 exe 的参数)。

因此我们基本上有两个地方可以放置命令行参数:

  • 在子进程的 PEB 中

  • 在子创建函数中:CreateProcessW(..., "command line args", ...)

在 PEB 中:

typedef struct _PEB {...PRTL_USER_PROCESS_PARAMETERS ProcessParameters;...
}typedef struct _RTL_USER_PROCESS_PARAMETERS {...UNICODE_STRING ImagePathName;UNICODE_STRING CommandLine;
} *PRTL_USER_PROCESS_PARAMETERS;

由于 PEB 可通过其流程进行修改,因此其中的数据不可信。

EDR 向现有进程查询其命令行,并且通常盲目信任它:

但可以验证一下,当父进程调用CreateProcess()创建子进程时:

EDR 可以比较命令行CreateProcess()和结果子进程的 PEB,如果它们不匹配则发出警报。

拦截函数调用参数实际上 CreateProcessW(..., "command line args", ...) 也没有多大帮助,因为我们可以使用虚假参数创建处于暂停状态的进程,然后远程用正确的参数覆盖它们,然后恢复该进程。

父级:使用虚假参数创建新的暂停进程

EDR:接收带有虚假参数的事件

父级:使用真实参数覆盖子级的 PEB

父进程:继续(启动)子进程(使用实参数)

子进程:再次用假参数覆盖其 PEB

EDR:查询进程获取虚假参数

如果 EDR 将来认为子进程是恶意的,它将向分析师提供信息,包括从 PEB 中获取的进程的命令行参数。因此,子进程需要再次覆盖 PEB,作为“清理”。

因此,进程的命令行参数是相当不可信的。

PPID 欺骗

在 Windows 中,与 Linux 不同,父进程和子进程之间没有依赖关系,因为没有fork()。子进程从父进程获取某些属性,包括父进程的 PID。它也将存储在进程的 EPROCESS 结构中。

CreateProcessW()可以指示该函数在STARTUPINFOEX结构中提供其自身的属性,包括子进程的父进程。因此,在创建时,我们可以为子进程指定错误的父进程 PID。

CreateProcessW()界面:

BOOL CreateProcessW([in, optional] LPCWSTR lpApplicationName,[in, out, optional] LPWSTR lpCommandLine,[in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes,[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,[in] BOOL                  bInheritHandles,[in] DWORD dwCreationFlags,[in, optional] LPVOID lpEnvironment,[in, optional] LPCWSTR lpCurrentDirectory,[in] LPSTARTUPINFOW lpStartupInfo, // PPID spoofing here[out] LPPROCESS_INFORMATION lpProcessInformation
);

实际的 PPID 欺骗只是设置属性 struct STARTUPINFOEX 并将其作为 lpStartupInfo 参数:

{STARTUPINFOEXA si;HANDLE fakeParent = OpenProcess(.., <pid of fake parent process>);..UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &fakeParent, ..);CreateProcessA(NULL, (LPSTR)"notepad", .., EXTENDED_STARTUPINFO_PRESENT, .., &si.StartupInfo, ..);
}

在哪里:

typedef struct _STARTUPINFOEXA {STARTUPINFOA StartupInfo;LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList; // attributes, one is the ppid
} STARTUPINFOEXA, *LPSTARTUPINFOEXA;

它将存储在 EPROCESS 内核结构中:

typedef struct _EPROCESS
{KPROCESS Pcb;...HANDLE InheritedFromUniqueProcessId; // PPID...
}

EDR 可以使用以下命令检索 NtQueryInformationProcess():

__kernel_entry NTSTATUS NtQueryInformationProcess([in] HANDLE ProcessHandle,[in] PROCESSINFOCLASS ProcessInformationClass,[out] PVOID ProcessInformation, // PROCESS_BASIC_INFORMATION[in] ULONG ProcessInformationLength,[out, optional] PULONG ReturnLength
);typedef struct _PROCESS_BASIC_INFORMATION {NTSTATUS ExitStatus;PPEB PebBaseAddress;ULONG_PTR AffinityMask;KPRIORITY BasePriority;ULONG_PTR UniqueProcessId;ULONG_PTR InheritedFromUniqueProcessId; // PID
} PROCESS_BASIC_INFORMATION;

可以检测到 PPID 欺骗,因为在创建进程时,会向 EDR 传递有关新进程的事件。此事件通常位于原始进程的上下文中,或者在其中引用该进程。然后,EDR 可以将结构的内容 STARTUPINFOEX与事件来自的进程进行比较(例如,只需比较两者的 PID)。在这里,EDR 看到CreateProcess()PPID=y 的调用(2),以及发起此调用的进程的有效 PID(1),其 PID=x。

因此,EDR 具有:

父级:PID

父进程:PPID 在其发出的CreateProcess()发往子进程的调用中

子代:其 PPID

并比较它们,尤其是 1) 和 2)。或者后面的 1/2 和 3。对于收到的事件,原始 PID 来自哪里并不总是完全清楚的(例如 ETW)。

请注意,它InheritedFromUniqueProcessId存储在 EPROCESS 中,但仍然不可信任,因为它可以从用户空间进行设置。

ETW 补丁

ETW 补丁将覆盖EtwEventWrite(),ntdll.dll因此该进程将不再自行发出任何 ETW 事件。这主要适用于 Powershell 和 .NET 相关事件。
它通常涉及:

  • VirtualProtect .文本:RX -> RW

  • 覆盖内存(用 替换函数体return 0)

  • VirtualProtect .文本:RW -> RX

更改权限ntdll.dll进行修改可能会产生比修补 ETW 避免的更多的遥测数据。其内存权限需要从 RX 更改为 RW,然后再改回 RX。
请注意,这只会影响修补进程生成的事件。ETW 无法全局停用。
ETW 事件主要用于托管进程(DotNet、C#)和 Powershell。ETW 以前被 Sysmon 大量使用,因此 ETW-patch 是针对 Sysmon 的。
参考:

https://jsecurity101.medium.com/understanding-etw-patching-9f5af87f9d7bhttps://jsecurity101.medium.com/refining-detection-new-perspectives-on-etw-patching-telemetry-e6c94e55a9ad

AMSI-AV 修补

AMSI 将扫描在受支持的 Windows 解释器(如 Powershell、MS Office VBA 运行时或 .NET)中执行的脚本。换句话说,应用程序本身要求操作系统通过 AMSI 对其打算执行的某个文件或缓冲区执行 AV 扫描。
要禁用 AMSI 运行时代码扫描,例如修补amsi.dll!AmsiOpenSession以删除遥测。替代方案是AmsiScanString() / AmsiScanBuffer()。
该过程与ETW-patch相同:使代码部分可写,破坏功能,再次恢复原始权限。

禁用 AMSI-AV 功能通常由加载程序在执行签名良好的恶意托管代码或 Powershell 脚本之前完成。加载程序正在接受扫描,但运行时加载的 .NET/Powershell 不会被扫描。

这对于在 powershell 中加载签名的恶意 powershell 脚本非常有用,否则该脚本将被 AMSI 接口扫描。一个著名的生成混淆 AMSI-AV 补丁的网站是https://amsi.fail。

AMSI-hooks 修补

ntll.dllAMSI-hook 修补(或 AMSi 修补)只是删除调用 的EDR补丁amsi.dll。它与 ETW 补丁或 AMSI-AV 补丁基本相同,因为它只是ntdll.dll 再次修改。它可以生成额外的遥测数据,例如从磁盘加载干净版本的 时ntll.dll。

参考:

https://github.com/ZeroMemoryEx/Amsi-Killerhttps://github.com/Vixx/AMSI-BYPASS

AMSI 绕过

AMSI 绕过既可以指绕过上述的 AMSI-AV 接口,也可以指调用 OS 内核函数而不调用ntdll.dll其中的钩子。

这可以通过使用直接系统调用来完成:如果您知道正确的系统调用号,则可以直接调用它,而无需涉及ntdll.dll。

ntdll.dll或者对于间接系统调用:在钩子调用之后重新使用部分函数。

在这两种情况下,AMSI-hook 都会被绕过,并且 EDR 将不会获得任何遥测数据。

如果这是带有 hooked 的正常函数调用图ntdll.dll:

这里与:

直接系统调用:自己执行系统调用(使用正确的系统调用号)

间接系统调用:重用 hooked 的部分ntdll.dll,调用系统调用但不调用钩子

ntdll.dll或者完全用磁盘中未挂钩的版本替换,就像在 RefleXXion 中一样。

参考:

https://eversinc33.com/posts/avoiding-direct-syscalls.htmlhttps://www.outflank.nl/blog/2019/06/19/red-team-tropics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/https://passthehashbrowns.github.io/hiding-your-syscallshttps://github.com/JustasMasiulis/inline_syscallhttps://github.com/jthuraisamy/SysWhispers2https://github.com/klezVirus/SysWhispers3https://alice.climent-pommeret.red/posts/direct-syscalls-hells-halos-syswhispers2

图像欺骗

与欺骗参数类似,攻击者可能还想“欺骗”exe:启动 EDR 记录的非恶意 exe(如 notepad.exe),然后用恶意 exe(如 mimikatz)替换进程内容。这会试图欺骗 EDR,让其认为已启动非恶意程序。这可以绕过简单的 EDR。

.exe 文件被称为进程的映像。

工艺镂空:

还有一些其他的技术:

  • Process Hollowing:使用以下代码覆盖已暂停进程的进程内存:WriteProcessMemory()

  • 进程复制:使用事务性 NTFS (TxF) 覆盖文件,启动进程,然后回滚事务,以恢复原始文件

  • Process Herpaderping:将恶意代码写入 exe,创建进程,在扫描之前快速用非恶意内容替换恶意内容

  • 进程重影:创建空文件,将其半删除,写入恶意数据,然后从中创建进程

内存扫描将使用签名(如 AV)扫描进程的内存。因此,即使注入了真正的进程,仍可以识别出像 CobaltStrike 这样的恶意代码。

或者通过将进程内存内容与 exe 文件内容进行比较。原始 exe 名称存储在 PEB(peb.ProcessParameters.ImagePathName)或内核的 EPROCESS 结构(eprocess.ImageFilename[15],eprocess.SeAuditProcessCreationInfo.ImageFileName)中。将内存内容与文件内容进行比较会耗费大量性能。

或者,EDR 可以收集识别操作的遥测数据。或者使用直接系统调用之类的支持技术,例如使用调用堆栈分析。

挖空参考:

https://www.ired.team/offective-security/code-injection-process-injection/process-hollowing-and-pe-image-relocationshttps://github.com/m0n0ph1/Process-Hollowinghttps://www.darkrelay.com/post/demystifying-hollow-process-injection

模块踩踏(Module Stomping)

这与图像欺骗类似,但使用 DLL。

模块踩踏将 shellcode 写入远程进程中未使用的 DLL 的 .text 部分,并从那里开始创建新的线程。

与图像欺骗相同,可以通过以下方式检测:

  • 内存签名扫描

  • .text 部分的内存/文件比较

  • 踩踏遥测

  • 识别支持技术,例如使用遥测的直接/间接系统调用

参考:

https://www.blackhillsinfosec.com/dll-jmping/https://blog.f-secure.com/hiding-malicious-code-with-module-stomping/https://blog.f-secure.com/hiding-malicious-code-with-module-stomping-part-2/https://trustedsec.com/blog/loading-dlls-reflectionshttps://williamknowles.io/living-dangerously-with-module-stomping-leveraging-code-coverage-analysis-for-injecting-into-legitimately-loaded-dlls/https://notes.dobin.ch/#root/PBXfEsTWGbEg/yFUsQJlBd3r0/iMYKnoX7AZ7w/W5TwpJ5or9DW-dRWk

内存加密

可以在休眠前加密所有可疑区域,并在进程恢复时再次解密。这并非易事,需要非常小心、奇怪的 Windows 功能以及有效负载(例如信标本身)的支持。它可以创建大量遥测数据,但其中大部分都无法被 EDR 很好地捕获。

信标通常Sleep()会持续一段时间。如果它使用内存加密,则在此期间执行的任何扫描都只会看到加密内存。

调用堆栈欺骗

调用堆栈基本上是一个函数调用层次结构:一个函数列表,每个函数都由其前面的函数调用。当进程调用系统调用(或挂钩ntdll.dll函数)时,EDR 可以检索并分析此列表。

当使用直接系统调用、间接系统调用或其他诡计时,调用堆栈默认看起来“错误”,这可以通过 EDR 识别。

调用堆栈欺骗可确保调用堆栈再次看起来真实。它是一种支持技术:例如,可以使用调用堆栈检测 AMSI 绕过,因此我们需要改进 AMSI 绕过,使调用堆栈看起来更自然。

实际的调用堆栈欺骗通常不会生成遥测数据,并且可以相当安全地实现。但通过重新使用现有的调用堆栈欺骗实现,可以通过签名扫描(无论是在磁盘上还是在内存中)来识别它。

可疑的调用堆栈NtDelayExecution():

清理(欺骗)调用堆栈NtDelayExecution():

反检测依赖于伪造调用堆栈、复制干净的调用堆栈或隐藏恶意调用堆栈。存在许多检查调用堆栈完整性的技术,通常是通过与其他信息关联。例如,线程起始地址应该来自合理的位置。

在普通线程中,用户模式起始地址通常是线程堆栈中的第三个函数调用 - 位于 ntdll!RtlUserThreadStart 和 kernel32!BaseThreadInitThunk 之后。因此,当线程被劫持时,调用堆栈中这一点将显而易见。对于“早起的” APC 注入,调用堆栈的基础将是 ntdll!LdrInitializeThunk、ntdll!NtTestAlert、ntdll!KiUserApcDispatcher,然后是注入的代码。

参考:

https://sabotagesec.com/gotta-catch-em-all-catching-your-favorite-c2-in-memory-using-stack-thread-telemetry/https://trustedsec.com/blog/windows-processes-nefarious-anomalies-and-you-threadshttps://www.mdsec.co.uk/2022/07/part-1-how-i-met-your-beacon-overview/https://gist.github.com/jaredcatkinson/23905d34537ce4b5b1818c3e6405c1d2https://whiteknightlabs.com/2024/04/30/sleeping-safely-in-thread-pools/https://oldboy21.github.io/posts/2024/06/sleaping-issues-swappala-and-Reflective-dll-friends-forever/https://oldboy21.github.io/posts/2024/05/swappala-why-change-when-you-can-hide/https://kyleavery.com/posts/avoiding-memory-scanners/

远程进程

攻击者可以选择干扰自己的进程,还是干扰系统中的另一个进程。此处描述的 Windows 函数大多也可用于另一个进程,只需OpenProcess()先使用即可。

这主要用于进程注入。迁移到另一个进程(如teams.exe)非常有用。它的C2可以隐藏在应用程序的正常通信中,它是JavaScript,因此有很多RW->RX分配。

EDR 会更严格地审查与远程进程的交互,因此留在自己的进程中更安全。对于迁移,请使用 DLL 侧载或其他不依赖OpenProcess() 某些东西的技术。

其中包括:

  • VirtualAllocEx() / VirtualFreeEx()

  • 读取进程内存() / 写入进程内存()

  • 创建远程线程()

  • 查询信息处理()/Nt查询信息处理()

暂停进程

一种非常常见的方法是创建一个带有参数的暂停进程CREATE_SUSPEND,然后对其进行处理,然后让其执行/恢复。

CreateProcessA("C:\\Windows\\System32\\calc.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
...
ResumeThread(pi.hProcess);

许多技术都依赖于此功能。目前使用暂停进程似乎不会对 EDR 造成太大影响,但这可能会在未来改变它。

例如,我们可以创建一个处于挂起状态的新进程,并排队 APC 来执行我们的 shellcode,这可能使其对 EDR 不可见(因为它可能在 KAPC 注入之前执行)。

结尾

EDR 智慧

  • 使用威胁检查或 avred 来识别你的资料中哪些部分被 AV 识别,然后对其进行修补

  • 内存扫描需要耗费大量的性能,通常需要触发才能执行

  • 用户模式 AMSI 越来越不重要,因此 AMSI-hooks 修补也很重要

编写加载器时的错误

  • 使用函数调用复制内存

  • 投入超过最低限度的努力来处理熵

  • 投入超过最低限度的努力来处理加密

  • 生成过多遥测数据

  • 线程未在备用内存中启动

  • 再次标记 RX 页面 RW

  • 调用栈不干净

建议的加载器

建议的装载机布局:

  • EXE 文件:所有代码都应包含在 .text 部分 (IMAGE) 中

  • 执行护栏:仅允许其在预期目标上执行(反中间盒)

  • 反模拟:阻止 AV 模拟我们的二进制文件(内存使用情况、CPU 周期数、时间欺骗……)

  • EDR 风水:通过使用非恶意数据和免费数据进行大量 Alloc/Copy/VirtualProtect 循环来调节 EDR

  • 有效载荷:加密(如何加密无所谓)

  • Alloc/Decode/Virtualprotect/Exec:尽可能正常(避免在此处使用 DLL 函数)。避免 RWX。

  • 有效载荷执行:尽可能正常(跳转到有效载荷,避免创建新线程)

原创 Ots安全

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/871613.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

USB接口颜色都代表什么含义

手机充电器人人都有!充电器线颜色都不同!你知道不同颜色的USB接口的各个颜色都代表什么含义吗?大部分人都是不知道的,这篇文章让您 一目了然!建议收藏备用!以备不时之需!

Windows资源管理器Icon图标注入

免责声明 本文发布的工具和脚本,仅用作测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。简介 使用图标将 DLL 注入资源管理器的非正统和隐蔽方式 IconJector 这是一个Windows资源管理器DLL注入技术,使用Windows上的更改图…

ElasticSearch Query DSL(查询领域特定语言)

目录常用 DSL 关键字查询上下文相关度评分:_score源数据:_source数据源过滤器query 和 filter 上下文相关性评分 (relevance scores)query 的上下文filter 的上下文关于 query 和 filter 上下文的例子全文查询 (full text query)intervals 查询请求示例intervals的顶级参数ma…

ESP32 学习笔记(九)舵机实验

概念 舵机是一种位置(角度)伺服的驱动器,适用于那些需要角度不断变化并可以保持的控制系统。舵机只是一种通俗的叫法,其本质是一个伺服电机。 舵机有很多规格,但所有的舵机都有外接三根线,分别用棕、红、橙三种颜色进行区分,由于舵机品牌不同,颜色也会有所差异,棕色为…

(原创)[开源][.Net Standard 2.0] SimpleMMF (进程间通信框架)更新 v1.1,极低CPU占用

一、前言 在上一篇 (原创)[.Net] 进程间通信框架(基于共享内存)——SimpleMMF 中,发布了v1.0版,最大的问题是:CPU占用较高,至少40-50%。 这既与我的开发水平有关,也与SimpleMMF诞生环境有关,这个主要是用在数字孪生各软件之间同步数据,而部署软件的工作站性能都强悍…

Elasticsearch 笔记

目录ES 相关概念概述核心概念1)索引 index2)类型 type3) 字段 Filed4)映射 mapping5)文档 document6)集群 cluster7)节点 node8)分片和复制 shards & replicasDocker 中安装 ElasticSearch下载 ElasticSearch 和 Kibana配置启动 ElasticSearch单节点多节点启动开启 …

VMware Avi Load Balancer 31.1.1 发布 - 多云负载均衡平台

VMware Avi Load Balancer 31.1.1 发布 - 多云负载均衡平台VMware Avi Load Balancer 31.1.1 发布 - 多云负载均衡平台 应用交付:多云负载均衡、Web 应用防火墙和容器 Ingress 服务 请访问原文链接:https://sysin.org/blog/vmware-avi-load-balancer-31/ 查看最新版。原创作品…

Angular 中依赖注入问题造成 Observable 订阅不更新

这是园子博客后台从 angular 15 升级到 angular 19 后遇到的一个问题。博客后台「随笔 」的侧边栏会显示随笔的分类列表 ,通过这个列表的上下文菜单可以修改分类名称,升级后测试时发现一个问题,修改分类名称后分类列表没有随之更新这是园子博客后台从 angular 15 升级到 ang…

极紫外光刻掩模上三维图案的严格模拟(下)

1D线掩模:全3D计算域 首先,使用包含吸收体结构和多层反射镜的3D计算域重新审视EUV线掩模。图5显示了对几何体进行离散化的网格(使用网格生成器JCMgeo自动生成)。对于三维设置,网格由棱柱形元素组成(而不是二维设置中的三角形元素)。使用不同的空间网格对相同的物理设置进…

极紫外光刻掩模上三维图案的严格模拟(上)

对具有二维周期性吸收体图案的极紫外光刻掩模的光散射进行了模拟。在一项详细的收敛研究中,表明在相对较大的3D计算域以及存在侧壁角度和拐角圆角的情况下,可以获得准确的结果。 材料和参数设置 所研究的结构由多层反射镜上的吸收器堆叠组成(共120层)。图1显示了几何形状的…

如何在M芯片的Mac上爽玩原神

如何在M芯片的Mac上爽玩原神 【热点速递】苹果震撼发布全新M4 Mac mini,国补福利下惊喜价如何在M芯片的Mac上爽玩原神【热点速递】苹果震撼发布全新M4 Mac mini,国补福利下惊喜价仅约3500元!这不仅是一次办公体验的全新升级,更是对高效能与性价比完美融合的一次致敬。想象一…

macOS安装软件过程中常见几种报错的解决办法

macOS安装软件过程中常见几种报错的解决办法 对于刚使用 macOS 或者在更新系统后尝试运行应用对于刚使用 macOS 或者在更新系统后尝试运行应用时遇到问题的用户,可能会看到以下几种错误提示:xxx已损坏,无法打开,你应该将它移到废纸篓打不开 xxx,因为它来自身份不明的开发者…