no good equivalent for arm64 linux. nebula provides a minimal pic shellcode template that resolves symbols at runtime. self-contained, injectable anywhere (pretty much).
_start--> compute runtime base viaadr x20, __start--> callnebula_entry
nebula_entry--> parse/proc/self/maps--> find libc base --> walkDT_SYMTAB/DT_STRTAB--> resolvewrite,mmap, ... --> populatectx->libc.*--> exec payload
modules
parse /proc/self/maps, extract name from path, djb2 hash. if hash matches and base contains valid elf64 header, return base.
symbols
locate PT_DYNAMIC, extract DT_SYMTAB/DT_STRTAB/DT_STRSZ. iterate symbol table, hash each name, compare. match --> return base + st_value.
inline asm. arm64 linux convention: x8 = nr, x0-x5 = args, svc #0
static inline i64 sys_write(i32 fd, const void *buf, size_t n)
{
register i64 x0 asm("x0") = fd;
register i64 x1 asm("x1") = (i64) buf;
register i64 x2 asm("x2") = n;
register i64 x8 asm("x8") = 64;
asm volatile("svc #0" : "+r"(x0) : "r"(x1),"r"(x2),"r"(x8) : "memory");
return x0;
}| flag | purpose |
|---|---|
-fPIE |
position independent |
-mcmodel=tiny |
±1MB pc-relative |
-fno-plt |
no PLT stubs |
-fno-jump-tables |
no absolute jumps |
linker script starts at 0, contiguous sects. __start/__end markers for size calc.
nebula_ctx_t
{
self { base, size, crc }
proc { pid, comm }
libc { base, write, read, mmap, mprotect, munmap, exit }
ready
}| path | desc |
|---|---|
src/entry.S |
entry stub |
src/nebula.c |
core runtime |
include/*. h |
types, elf, syscalls, hash |
test/loader.c |
simple ass loader |
make
# if you wanna use my loader
gcc -o bin/loader test/loader.c -Wall
./bin/loader bin/nebula.binpls impl nebula_exec():
void nebula_exec(nebula_ctx_t *ctx)
{
ctx->libc.write(2, "hi\n", 3);
}- Cracked5pider/Stardust
- arm64.syscall.sh
- my own code for other previous projects
