Skip to main content
Angrop provides powerful methods to write data to memory and modify memory contents using arithmetic and logical operations. These operations are essential for setting up data structures, strings, and shellcode.

Writing to Memory: write_to_mem()

The write_to_mem() method writes arbitrary data to a specified memory address.

Method Signature

rop.write_to_mem(addr, data, preserve_regs=None, fill_byte=b"\xff")
Parameters:
  • addr: Target memory address (integer or RopValue)
  • data: Bytes to write (must be a bytes object)
  • preserve_regs: Set of register names that should not be modified
  • fill_byte: Single byte used to pad data if needed (default: b"\xff")
Returns: A RopChain that writes the data to memory

Basic Examples

1

Write a string to memory

import angr
import angrop

proj = angr.Project("/bin/bash")
rop = proj.analyses.ROP()
rop.find_gadgets()

# Write "/bin/sh" to address 0x61b100
chain = rop.write_to_mem(0x61b100, b"/bin/sh\x00")
chain.pp()
2

Write binary data

# Write shellcode to memory
shellcode = b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff"
chain = rop.write_to_mem(0x61b000, shellcode)
3

Write with register preservation

# Write data without modifying rax
chain = rop.write_to_mem(0x61b100, b"data", preserve_regs={'rax'})
Angrop automatically handles writing data in chunks based on available gadgets. It will use the most efficient write size (8, 4, 2, or 1 byte) depending on what gadgets are available.

Real-World Example: Building an Execve Chain

From angrop’s Python API documentation:
import angr
import angrop

proj = angr.Project("/bin/bash")
rop = proj.analyses.ROP()
rop.find_gadgets()

# Write "/bin/sh" to memory
chain = rop.write_to_mem(0x61b100, b"/bin/sh\x00")

# Call execve with the string address
chain += rop.func_call("execve", [0x61b100, 0, 0])

# Or use the convenience method
chain = rop.execve()  # Automatically writes "/bin/sh" and calls execve

Writing Files and Paths

import os
import angr
import angrop

proj = angr.Project("./binary")
rop = proj.analyses.ROP()
rop.find_gadgets()

# Write a file path
chain = rop.write_to_mem(0x61b100, b"/home/ctf/flag\x00")

# Open the file
chain += rop.func_call("open", [0x61b100, os.O_RDONLY])

# Read into buffer
chain += rop.func_call("read", [3, 0x61b200, 0x100])

# Write to stdout
chain += rop.func_call("write", [1, 0x61b200, 0x100])

Memory Addition: mem_add()

Add a value to data stored at a memory location.

Method Signature

rop.mem_add(addr, value, size=None)
Parameters:
  • addr: Memory address to modify
  • value: Value to add (integer or RopValue)
  • size: Number of bytes to operate on (1, 2, 4, or 8). Default is architecture word size.
Returns: A RopChain that performs the addition

Example

import angr
import angrop

proj = angr.Project("/bin/bash")
rop = proj.analyses.ROP()
rop.find_gadgets()

# Add 0x1000 to the value at address 0x804f124
chain = rop.mem_add(0x804f124, 0x1000)

# Add with specific size (4 bytes)
chain = rop.mem_add(0x804f124, 0x1000, size=4)
The memory operation methods require gadgets that can perform memory writes with arithmetic operations, such as add dword ptr [rax], ebx; ret.

Memory XOR: mem_xor()

XOR data at a memory location with a value.

Method Signature

rop.mem_xor(addr, value, size=None)
Parameters:
  • addr: Memory address to modify
  • value: Value to XOR with
  • size: Number of bytes (1, 2, 4, or 8)

Example

# XOR the value at 0x804f124 with 0x41414141
chain = rop.mem_xor(0x804f124, 0x41414141)

# Useful for encoding/decoding data in memory
encoded_value = 0x12345678
key = 0xdeadbeef
chain = rop.write_to_mem(0x61b100, encoded_value.to_bytes(4, 'little'))
chain += rop.mem_xor(0x61b100, key, size=4)
# Memory now contains: 0x12345678 ^ 0xdeadbeef

Memory OR: mem_or()

Perform bitwise OR on data at a memory location.

Method Signature

rop.mem_or(addr, value, size=None)
Parameters:
  • addr: Memory address to modify
  • value: Value to OR with
  • size: Number of bytes (1, 2, 4, or 8)

Example

# Set specific bits at a memory location
chain = rop.mem_or(0x804f124, 0x000000ff, size=4)

# Useful for setting flags or enabling bits
chain = rop.write_to_mem(0x61b100, b"\x00\x00\x00\x00")
chain += rop.mem_or(0x61b100, 0x80000000, size=4)  # Set MSB

Memory AND: mem_and()

Perform bitwise AND on data at a memory location.

Method Signature

rop.mem_and(addr, value, size=None)
Parameters:
  • addr: Memory address to modify
  • value: Value to AND with
  • size: Number of bytes (1, 2, 4, or 8)

Example

# Clear specific bits at a memory location
chain = rop.mem_and(0x804f124, 0xffffff00, size=4)

# Useful for masking or disabling bits
chain = rop.write_to_mem(0x61b100, b"\xff\xff\xff\xff")
chain += rop.mem_and(0x61b100, 0x0000ffff, size=4)  # Keep only lower 16 bits

Complete Memory Operations Example

From angrop’s Python API documentation:
import angr
import angrop

proj = angr.Project("/bin/bash")
rop = proj.analyses.ROP()
rop.find_gadgets()

# Write initial value
chain = rop.write_to_mem(0x804f124, b"\x00\x00\x00\x00")

# Add to memory
chain += rop.mem_add(0x804f124, 0x41414141)

# XOR to encode
chain += rop.mem_xor(0x804f124, 0x12345678)

# OR to set flags
chain += rop.mem_or(0x804f124, 0x80000000)

# AND to mask
chain += rop.mem_and(0x804f124, 0xffffffff)

Writing with Badbytes

When badbytes are configured, angrop uses sophisticated techniques to avoid them:
import angr
import angrop

proj = angr.Project("/bin/bash")
rop = proj.analyses.ROP()

# Configure badbytes (null bytes and newlines)
rop.set_badbytes([0x00, 0x0a])
rop.find_gadgets()

# Write data containing badbytes
data = b"\x00\x0a\x0d\xff"  # Contains 0x00 and 0x0a
chain = rop.write_to_mem(0x61b100, data)

# Angrop automatically:
# 1. Writes safe initial values
# 2. Uses mem_xor/mem_add/mem_or/mem_and to construct the target bytes

How Badbyte Avoidance Works

From the source code (mem_writer.py:606-630), angrop uses multiple strategies:
1

Try chunk transforms

Find operations (XOR, OR, AND, ADD) that can transform safe bytes into target bytes:
# Example: To write 0x0a (badbyte)
# Write 0x0b (safe), then subtract 1
# Or write 0x00 (if safe), then OR with 0x0a via intermediate
2

Try per-byte operations

Handle each byte individually using different operations:
# Byte 0: XOR
# Byte 1: ADD
# Byte 2: OR
# etc.
3

Use register arithmetic

Construct values in registers using arithmetic, then write to memory.
The fill_byte parameter should never be a badbyte. Angrop will raise an error if you try to use a badbyte as fill_byte.

Memory Write Gadget Requirements

For memory operations to work, angrop needs specific types of gadgets:

For write_to_mem():

  • Gadgets like: mov [rax], rbx; ret
  • Controllable address register (rax)
  • Controllable data register (rbx)
  • Address and data must be independent

For mem_add(), mem_xor(), etc.:

  • Gadgets like: add [rax], ebx; ret
  • xor [rdi], rsi; ret
  • or [rcx], rdx; ret
  • and [r8], r9; ret
If you get “Fail to write data to memory” or “Fail to perform _mem_change” errors, it means angrop couldn’t find suitable gadgets. Try:
  1. Using fast_mode=False when initializing ROP
  2. Checking if the binary has the necessary gadgets
  3. Using alternative approaches (e.g., setting up registers and using library functions)

Advanced Memory Techniques

Building Data Structures

import struct
import angr
import angrop

proj = angr.Project("/bin/bash")
rop = proj.analyses.ROP()
rop.find_gadgets()

# Build a simple structure in memory
struct_addr = 0x61b100

# Write struct fields
chain = rop.write_to_mem(struct_addr + 0, struct.pack('<Q', 0x1234))
chain += rop.write_to_mem(struct_addr + 8, struct.pack('<Q', 0x5678))
chain += rop.write_to_mem(struct_addr + 16, b"name\x00")

Writing Pointer Arrays

# Build argv array for execve
argv_addr = 0x61b200
arg0_addr = 0x61b100  # Points to "/bin/sh"

# Write the string
chain = rop.write_to_mem(arg0_addr, b"/bin/sh\x00")

# Write pointer array
chain += rop.write_to_mem(argv_addr, struct.pack('<Q', arg0_addr))
chain += rop.write_to_mem(argv_addr + 8, struct.pack('<Q', 0))  # NULL terminator

# Call execve
chain += rop.func_call("execve", [arg0_addr, argv_addr, 0])

Modifying Existing Data

# Increment a counter in memory
counter_addr = 0x804f000
chain = rop.mem_add(counter_addr, 1, size=4)

# Toggle a flag
flag_addr = 0x804f004
chain += rop.mem_xor(flag_addr, 1, size=1)

# Enable specific bits in a bitmask
mask_addr = 0x804f008
chain += rop.mem_or(mask_addr, 0b10101010, size=1)

# Clear specific bits
chain += rop.mem_and(mask_addr, 0b11110000, size=1)

Size Specification

All memory operations support explicit size specification:
# 1 byte (8-bit)
chain = rop.mem_add(addr, 0xff, size=1)

# 2 bytes (16-bit)
chain = rop.mem_xor(addr, 0xffff, size=2)

# 4 bytes (32-bit)
chain = rop.mem_or(addr, 0xffffffff, size=4)

# 8 bytes (64-bit)
chain = rop.mem_and(addr, 0xffffffffffffffff, size=8)
If you don’t specify size, angrop uses the architecture’s default word size (4 bytes for 32-bit, 8 bytes for 64-bit).

Error Handling

from angrop.errors import RopException

try:
    # This might fail if address contains badbytes
    chain = rop.write_to_mem(0x0a0a0a0a, b"data")
except RopException as e:
    print(f"Write failed: {e}")
    # Use an alternative address
    chain = rop.write_to_mem(0x61b100, b"data")

Best Practices

  1. Choose safe addresses: Use addresses that don’t contain badbytes
  2. Minimize writes: Write larger chunks when possible instead of byte-by-byte
  3. Use fill_byte wisely: Choose a fill byte that’s not a badbyte
  4. Verify operations: Use chain.pp() to inspect generated chains
  5. Consider data alignment: Some gadgets may require aligned addresses

Performance Considerations

  • Writing large data may generate long chains
  • Memory operations with badbytes are slower due to additional transformations
  • Using size parameter efficiently can reduce chain length
  • Preservation of registers adds constraints and may increase chain complexity

Next Steps

  • Function Calls - Use memory writes to set up function arguments
  • Syscalls - Combine memory writes with syscalls for exploits
  • Badbytes - Learn more about handling restricted bytes

Build docs developers (and LLMs) love