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#