Skip to content

Cheat sheet — Disassembly Basics

Companion to Module 07 — Disassembly Basics · CC BY 4.0 — print it, pin it, share it.

Last reviewed: 2026-07

Only handle live malware in an isolated, disposable analysis VM. Disassembly is static — the sample never runs — but the bytes still live on disk.

objdump — flat disassembly for the overview

objdump -d sample                      # disassemble executable sections (Intel or AT&T)
objdump -d -M intel sample             # force Intel syntax (mov dst, src) — easier to read
objdump -d -j .text sample             # just the .text section
objdump -t sample                      # symbol table — find function names
objdump -s -j .rodata sample           # raw hex dump of a section (the encrypted blob lives here)

radare2 — analyse and read a function

r2 sample                              # open (add -w to write/patch, -d to debug)
r2 -q -c 'aaa; afl' sample             # -q: batch, run commands, quit
aaa                 # analyse all — build functions, xrefs, strings
afl                 # list all discovered functions (find sym.decode, main)
s sym.decode        # seek to a function by name
pdf                 # print disassembly of the current function
pdf @ sym.decode    # print disassembly of a named function directly
iz                  # strings in the data sections
izz                 # strings in the whole binary
axt @ sym.decode    # cross-references TO this function (who calls the decoder)
V                   # visual mode;  VV = graph view of the current function
# One-liner: disassemble the decode routine
r2 -q -c 'aaa; pdf @ sym.decode' sample

The XOR-decode loop — the pattern that matters most

loop:
    movzx  eax, byte [rbx + rcx]   ; load buf[i]  (rcx = index i)
    xor    al, dl                  ; XOR with key byte (dl often holds the key)
    mov    byte [rbx + rcx], al    ; store decoded byte
    inc    rcx                     ; i++
    cmp    rcx, rsi                ; compare i with length
    jne    loop                    ; not done → repeat
  • Shape to spot: load byte → xor → store → inc index → cmp with length → jne back. Recognise it without knowing in advance what it does; then read out the key and the length, and reverse it.

x86-64 reading kit

Registers   rax rbx rcx rdx rsi rdi rsp rbp  r8–r15
Arg order   rdi rsi rdx rcx r8 r9   (System V AMD64 ABI, first six args)
Prologue    push rbp / mov rbp, rsp        Epilogue  pop rbp / ret
Locals      [rbp - N]                       Loop      cmp + jne/jl + index inc
Families    mov lea push pop | add sub imul xor | cmp test | je jne jg jl | call ret

Recover the string, then prove it

# read key + blob out of the disassembly, reverse in Python:
python3 -c 'b=bytes.fromhex("..."); print(bytes(x^0x5a for x in b))'
rule xor_decode_stub {
    strings:
        $stub = { 0F B6 04 0B 30 D0 88 04 0B 48 FF C1 }   // the decode-loop bytes
    condition:
        $stub                                              // prove on sample, quiet on benign
}

Gotchas worth remembering

  • radare2 keeps you on the raw assembly on purpose — no decompiler abstraction at this stage. Reading instructions yourself is what separates understanding from guessing; Ghidra/Binary Ninja come on top of this skill, not instead of it.
  • Watch the XOR key width (byte vs. word) and the loop bound — those are exactly the load-bearing details a decompiler's pseudocode (and an AI summary) quietly gets wrong, and they change the recovered string.
  • aaa before pdf — radare2 shows almost nothing useful until it has analysed the binary and named functions.
  • AT&T vs. Intel syntax flips operand order (mov src, dst vs mov dst, src); pick one (-M intel) and stay consistent or you'll misread every mov.
  • The AI explanation of a function is a hypothesis; trace the key and bounds by hand before you trust the recovered string as an IOC.

Comments

Sign in with GitHub to comment. Choose the type: Feedback (errors or suggestions on this page) · Hints (help for fellow learners — no spoilers) · General (anything else).