一、写在前面
1、 BeaconEye从发布到现在已经有半年多时间,本文仅是对现有的绕过以及检测方法的改进。
2、特别感谢cheery师傅的指导。
3、Beacon扫描代码地址: https://github.com/j0urney1/BeaconEye_C
4、绕过内存扫描代码地址: https://github.com/j0urney1/TitanLdr_for_memory
二、目前的绕过方法
目前已知绕过BeaconEye的方法如下
1、修改beacon.dll中memset的值。
2、shellcode运行完之后将PEB中NumberOfHeaps的值修改为0。
三、修改NumberOfHeaps的值
1、先查看BeaconEye代码,扫描进程时通过读取进程PEB中NumberOfHeaps的值来确定堆个数。
如果在加载完shellcode之后将进程NumberOfHeaps修改成0,就可以绕过BeaconEye的扫描,这种方法的检测也很简单,即使将NumberOfHeaps改成了0,ProcessHeaps的值任然不会变,且是以null结束,如下图,所以在扫描内存的时候直接改为死循环即可。
四、修改beacon.dll
1、beaconconfig数据类型如下,每个成员以一个指针大小对齐。
1typedef struct _data
2{
3 USHORT dataType;
4 union
5 {
6 USHORT shortdata;//1
7 DWORD dworddata;//2
8 PVOID pvoiddata;//3
9 }datastruct;
10}data, * pdata;
11typedef struct _config
12{
13 PVOID unuse[2];
14 data data[ANYSIZE_ARRAY];
15}config,*Pconfig;
2、修改了memset设置内存的值之后,BeaconConfig的内存如下。
这种内存似乎不能用rule规则来扫描,因为不可预测memset之后内存的值,但是可以通过比对内存来进行扫描,因为memset之后,没有修改过的内存的值是一样的,可能不是很好描述,具体代码如下。
64位进程扫描
1BOOL CheckX64(HANDLE hProcess,ULONGLONG heapAddress, DWORD dwHezpSize,PWSTR ProcessName,DWORD PID)
2{
3 BOOL re = FALSE;
4 PBYTE buffer = NULL;
5 BYTE temp[0x10];
6 DWORD i = 0, offset;
7 ULONGLONG CheckValue = 0;
8
9 if (dwHezpSize >= 0x810)
10 {
11 if (buffer = LocalAlloc(LPTR, dwHezpSize))
12 {
13 if (NT_SUCCESS(NtWow64ReadVirtualMemory64(hProcess, heapAddress, buffer, dwHezpSize, NULL)))
14 {
15 for (i = 0x10; i < dwHezpSize - 106; i++)
16 {
17 memset(temp, buffer[i], sizeof(temp));
18 CheckValue = *(PULONGLONG)temp & ~0xFFFF;
19 if (RtlEqualMemory(buffer + i, temp, 0x10))
20 {
21 offset = i + 0x10;
22 if (*(USHORT*)(buffer + offset) == 0x1 && ((*(PULONGLONG)(buffer + offset) & ~0xFFFF) == CheckValue))
23 {
24 offset += 8;
25 if (*(USHORT*)(buffer + offset) == 0x00 || *(USHORT*)(buffer + offset) == 0x01 || *(USHORT*)(buffer + offset) == 0x02 || *(USHORT*)(buffer + offset) == 0x04 || *(USHORT*)(buffer + offset) == 0x08 || *(USHORT*)(buffer + offset) == 0x10)
26 {
27 if ((*(PULONGLONG)(buffer + offset) & ~0xFFFF) == CheckValue)
28 {
29 offset += 8;
30 if (*(USHORT*)(buffer + offset) == 0x1 && ((*(PULONGLONG)(buffer + offset) & ~0xFFFF) == CheckValue))
31 {
32 offset += 10;
33 if ((*(PULONGLONG)(buffer + offset) & 0xFFFFFFFFFFFF) == (*(PULONGLONG)temp & 0xFFFFFFFFFFFF))
34 {
35 offset += 6;
36 if (*(USHORT*)(buffer + offset) == 0x2 && ((*(PULONGLONG)(buffer + offset) & ~0xFFFF) == CheckValue))
37 {
38 offset += 12;
39 if (*(PDWORD)(buffer + offset) == *(PDWORD)temp)
40 {
41 offset += 4;
42 if (*(USHORT*)(buffer + offset) == 0x2 && ((*(PULONGLONG)(buffer + offset) & ~0xFFFF) == CheckValue))
43 {
44 offset += 12;
45 if (*(PDWORD)(buffer + offset) == *(PDWORD)temp)
46 {
47 offset += 4;
48 if (*(USHORT*)(buffer + offset) == 0x1 && ((*(PULONGLONG)(buffer + offset) & ~0xFFFF) == CheckValue))
49 {
50 offset += 10;
51 if (RtlEqualMemory(buffer + offset, temp, 0x10))
52 {
53 wprintf(L"Process: %ws Pid: %d Arch x64ntFind Data at %I64Xn", ProcessName, PID, heapAddress + i);
54 DisplayX64(hProcess, heapAddress + i);
55 re = TRUE;
56 break;
57 }
58 }
59 }
60 }
61 }
62 }
63 }
64 }
65 }
66 }
67 }
68 }
69 }
70 }
71 LocalFree(buffer);
72 }
73 }
74 return re;
75
76}
77
32位进程扫描
1BOOL CheckX86(HANDLE hProcess, PVOID heapAddress, DWORD dwHezpSize, PWSTR ProcessName, DWORD PID)
2{
3 BOOL re = FALSE;
4 PBYTE buffer = NULL;
5 BYTE temp[0x8];
6 DWORD i = 0, offset, CheckValue = 0;
7
8 if (dwHezpSize >= 0x408)
9 {
10 if (buffer = LocalAlloc(LPTR, dwHezpSize))
11 {
12 NtReadVirtualMemory(hProcess, heapAddress, buffer, dwHezpSize, NULL);
13 for (i = 8; i < dwHezpSize - 54; i++)
14 {
15 memset(temp, buffer[i], sizeof(temp));
16 CheckValue = *(PDWORD)temp & ~0xFFFF;
17 if (RtlEqualMemory(buffer + i, temp, 8))
18 {
19 offset = i + 8;
20 if ((*(USHORT*)(buffer + offset) == 0x1) && ((*(PDWORD)(buffer + offset) & ~0xFFFF) == CheckValue))
21 {
22 offset += 4;
23 if (*(USHORT*)(buffer + offset) == 0x00 || *(USHORT*)(buffer + offset) == 0x01 || *(USHORT*)(buffer + offset) == 0x02 || *(USHORT*)(buffer + offset) == 0x04 || *(USHORT*)(buffer + offset) == 0x08 || *(USHORT*)(buffer + offset) == 0x10)
24 {
25 offset += 2;
26 if (*(USHORT*)(buffer+offset) == *(USHORT*)temp)
27 {
28 offset += 2;
29 if ((*(USHORT*)(buffer + offset) == 0x1) && ((*(PDWORD)(buffer + offset) & ~0xFFFF) == CheckValue))
30 {
31 offset += 6;
32 if (*(USHORT*)(buffer + offset) == *(USHORT*)temp)
33 {
34 offset += 2;
35 if (*(USHORT*)(buffer + offset) == 0x2 && ((*(PDWORD)(buffer + offset) & ~0xFFFF) == CheckValue))
36 {
37 offset += 8;
38 if (*(USHORT*)(buffer + offset) == 0x2 && ((*(PDWORD)(buffer + offset) & ~0xFFFF) == CheckValue))
39 {
40 offset += 8;
41 if (*(USHORT*)(buffer + offset) == 0x1 && ((*(PDWORD)(buffer + offset) & ~0xFFFF) == CheckValue))
42 {
43 offset += 6;
44 if (RtlEqualMemory(buffer + offset, temp, 8))
45 {
46 wprintf(L"Process: %ws Pid: %d Arch x86ntFind Data at %pn", ProcessName, PID, (PBYTE)heapAddress + i);
47 DisplayX86(hProcess, (PBYTE)heapAddress + i);
48 re = TRUE;
49 break;
50 }
51 }
52 }
53 }
54 }
55 }
56 }
57 }
58 }
59 }
60 }
61 LocalFree(buffer);
62 }
63 }
64 return re;
65}
66
具体结果如下
五、绕过
1、修改后的代码应该是能无视修改NumberOfHeaps和beacon.dll的绕过方法。
2、在我的认知里目前能绕过的就是hook掉Sleep,在进入休眠加密BeaconConfig,这种方法对于自己写加载器似乎很好实现,但是对于注入到其他进程似乎不太好实现。
3、CS在4.4版本推出了自定义反射dll加载功能,该功能可以让用户自己实现反射dll的加载过程。具体步骤如下
11、添加异常处理,当sleep结束后触发,解密映射到内存的dll,以及beaconconfig。
22、找到反射dll映射到内存的地址,以及beaconconfig的地址,每次进入Sleep之后加密beaconconfig和dll在内存中的映射。
33、这样加密之后不仅能绕过BeaconEye的内存扫描,也可以绕过卡巴斯基的内存扫描
六、参考
1、https://www.anquanke.com/post/id/253039
2、https://www.cobaltstrike.com/blog/cobalt-strike-4-4-the-one-with-the-reconnect-button/
3、https://xz.aliyun.com/t/9399
4、https://github.com/SecIdiot/TitanLdr
原文始发于微信公众号(我真不是红队啊):再探BeaconEye