VEH全称(Vectored Exception Handling),中文名向量异常处理
try_except是SEH,中文名结构化异常处理
不用管这么多,就是异常处理
VEH的优先级比SEH高,简单来说就是程序执行的时候报错了如果设置了异常处理那就会走到里面去
这个免杀的思路就是创建一个线程在shell Sleep的时候将Beacon的内存改为不可执行,在shell醒来的时候去执行这块内存就会报错,因为设置了VEH就会走到VEH里面,VEH将这块内存的属性加上可执行,然后shell在继续去执行就不会报错了,执行完之后shell又去Sleep了,线程继续修改内存属性为不可执行,循环往复
这样在shell Sleep的时候Beacon是不可执行的,有些杀软可能就不会去扫描这块内存了,达到了规避的效果
1 | PVOID AddVectoredExceptionHandler( |
添加VEH,简单说就是指定报错后处理的函数
First 这个参数好像是确定调用优先级的,这次用不到
Handler 异常处理的函数指针,这个函数也是有指定结构的
1 | LONG WINAPI VectoredExceptionHandler(PEXCEPTION_POINTERS Exception) |
PEXCEPTION_POINTERS是指向EXCEPTION_POINTERS结构的指针
EXCEPTION_POINTERS里面东西挺多的,报错的时候寄存器的值啊错误码啊什么的,用到什么再写什么吧
1 | HANDLE CreateEventA( |
创建或打开事件对象
lpEventAttributes 指向SECURITY_ATTRIBUTES结构的指针。如果此参数为NULL,则句柄不能被子进程继承。
bManualReset 如果该参数为TRUE则创建的是一个手动重置的事件对象,需要用ResetEvent设置为信号状态,如果该参数为FALSE则会创建一个自动重置的事件对象,在单个线程被释放后自动将事件状态重置为非信号状态
bInitialState 如果此参数为TRUE则表示该事件对象的信号状态,FALSE则为非信号状态
lpName 该事件对象的名字,为NULL则代表该事件对象没有名字
1 | BOOL SetEvent( |
将事件对象设置为信号状态
hEvent 事件对象的句柄
1 | BOOL ResetEvent( |
将事件对象设置为非信号状态
hEvent 事件对象的句柄
这里提到了信号状态和非信号状态,我的理解就是像开关一样,因为在前面说过需要创建一个线程让shell在Sleep的时候将Beacon的内存改为不可执行
这个线程需要while循环一直改,可是又不能一直改,正在执行的时候要是改了内存属性那程序就崩溃了
稍微来看一点代码,完整的代码会在后面说
1 | VOID WINAPI NewSleep(DWORD dwMilliseconds) |
这个方法需要Hook Sleep函数,这是新写的Sleep函数,在Hook后会跳到这里来执行
注意这里已经被Hook了,所以新Sleep函数需要先UnHook,然后再执行正常的Sleep函数
最后再把SleepHook起来
最后将事件对象设置为信号状态
来看一下设置属性的函数
1 | DWORD WINAPI SetProtect(LPVOID lpPatameter) |
WaitForSingleObject要等到hEvent为信号状态了然后再去改属性
修改完成后ResetEvent将属性设置为非信号状态
下一次循环的时候就卡在WaitForSingleObject这里了,需要Sleep函数结束将信号将事件对象改为信号状态了再修改属性,这样就不会一直在循环了
首先需要Hook VirtuaAlloc和Sleep函数
Hook VirtualAlloc是因为需要知道起始的内存位置和内存大小是多少,这样后面修改内存属性的时候不会找不到
1 | VOID HookVirtualAlloc() |
关于Hook可以看InlineHook那篇文章,因为这里是32位的所以只需要修改五个字节
E9是jmp的硬编码,后面四字节是跳转到新的函数指针
这里就不多讲关于Hook的知识了
调用了三次复制
这样调用这个函数的时候就会跳到新的函数的位置
VirtualProtect就是修改dll那块属性加上可写,不然覆盖不了
UnHookVirtualAlloc就是把原先的字节覆盖回来就好了
Hook Sleep同理在后面的全部代码里面贴出来
1 | VOID WINAPI NewSleep(DWORD dwMilliseconds) |
UnHook然后正常执行Sleep最后在Hook起来把事件对象设置为信号状态
1 | LPVOID WINAPI NewVirtualAlloc(LPVOID lpAddress, |
相同的也是UnHook然后执行正常的VirtualAlloc将内存的地址和大小保存到一个全局变量,最后再Hook起来,返回申请到的内存地址
1 | BOOL is_Exception(DWORD Except_EIP) |
如果内存不可执行再执行的话就会报出0xc0000005错误码,可以通过Exception->ExceptionRecord->ExceptionCode得到错误码来判断是否是因为无可执行属性报的错
is_Exception是判断报错的位置是否在申请的内存中,在的话就把这块内存加上可执行的属性
1 | HINTERNET Session = InternetOpenA("aa", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0); |
这里首先会加载一个0x400000的shellcode然后会加载一个0x3d000的Beacon,后面操作都是会Beacon里面执行
在NewVirtualAlloc打印一下就可以看到了,打印用了之后会报毒所以就删了,估计是被当成特征码了
1 |
|
64位除了指针大小和Hook方式不太一样别的基本相同
1 | mov rax, address |
64位不能直接jmp所以不需要偏移
代码放github上吧,贴这里就有点不清楚了
https://github.com/Macchiatosheep/VEH_SC
这咋说呢,最近windows defender好强,这方法也只能勉强上线,有时候还会被杀,一有点敏感动作就直接被干掉,卡巴也是可以上线不过有动作就被干掉
然后国内的杀软根本不来整内存,感觉国内的杀软和国外的就不是一个档次的,拿这种免杀国内的又有点…
反正就是不上不下的,国内基本都能过,国外一个都过不了的感觉
差不多就是这种状态我现在
然后这也有点小瑕疵,因为有别的动作的时候又会开内存,导致原先Beacon的内存位置被覆盖掉然后is_Exception会出现问题
感觉CS的真的也差不多了,除非二开不然光用代码已经很难过动态了
学这些的话如果以后真的要写C2的话帮助应该很大