nebula - 给 arm64 linux 写的 shellcode 框架
link: GitHub - vmsplit/nebula: arm64 linux position-independent shellcode framework
最近搞了个项目叫 nebula,是给 arm64 linux 用的 position-independent shellcode 框架。
说白了就是一个自己能跑的 shellcode 模板。你把它注入到任意进程里,它自己算基址、自己找 libc、自己解析符号,然后执行你写的 payload。不需要外部 loader 帮你做重定位,不依赖固定地址。扔哪都能跑。
为什么做这个
arm64 linux 这边一直没啥好用的 shellcode 框架。x86/x64 工具链很成熟了,但 arm64 相对空白。现在 Apple Silicon、AWS Graviton、各种 arm 服务器越来越多,这块需求肯定会涨。
另外就是学习。想真正搞懂 ELF 动态链接、PIC 原理、运行时符号解析这些东西,最好的办法就是自己从零写一遍。看文档看一百遍不如自己撸一次代码。
怎么工作的
核心流程大概这样:
入口用 adr 指令算自己的运行时基地址。这是 PIC 的关键 - adr 是 pc-relative 的,不管代码被加载到哪个地址都能算出正确的基址。
然后读 /proc/self/maps 找 libc。逐行解析,提取路径末尾的文件名,用 djb2 哈希匹配。找到了就拿到 libc 基址。
接着遍历 ELF 的 dynamic section,找 DT_SYMTAB 和 DT_STRTAB。有了符号表和字符串表,就能遍历所有导出符号,逐个哈希比对。匹配上了就 base + st_value 拿到函数地址。
最后把 write、read、mmap、mprotect 这些函数指针填进 context 结构体,调用你的 payload。这时候你就有完整的 libc 访问权限了。
syscall 不走 libc wrapper,直接 inline asm。arm64 linux 的约定是 x8 放 syscall number,x0-x5 放参数,svc #0 触发。这样整个 shellcode 不依赖任何外部符号。
能干嘛用
几个场景:
红队/渗透测试。做 implant 的时候经常需要往目标进程注 shellcode。如果你的 shellcode 依赖固定地址或者需要 loader 帮你重定位,那就很蠢。nebula 让你编译完直接扔。
恶意软件分析。想理解真实 malware 怎么做运行时解析的,自己写一个是最快的学习路径。很多 APT 样本用类似的技术规避静态分析。
安全研究。研究 ELF 内部结构、动态链接机制、进程注入技术,nebula 是个很好的 playground。
学生/求职。想进红队、做恶意软件分析、或者政府安全合同这类工作,光看书不够。自己写过这种底层的东西,面试的时候能聊的深度完全不一样。
技术细节
编译用这些 flag:
-fPIE position independent -mcmodel=tiny ±1MB pc-relative addressing -fno-plt 不生成 PLT stub -fno-jump-tables 不用绝对地址跳转表
linker script 把所有 section 从地址 0 开始连续排列。__start 和 __end 两个 marker 用来算 shellcode 自己的 size。
整个产物大概 4KB。
用法
make 编译完会生成 bin/nebula. bin。这就是 raw shellcode。
用我写的 loader 可以测试:
gcc -o bin/loader test/loader.c ./bin/loader bin/nebula.bin
实际用的话在 nebula_exec() 里写你的 payload:
void nebula_exec(nebula_ctx_t *ctx) { ctx->libc. write(2, “pwned\n”, 6); }
ctx 里有所有解析好的函数指针,随便用。
代码
github. com/vmsplit/nebula
有问题直接开 issue。