Adding heap grooming to avoid non contiguous memory blocks#172
Adding heap grooming to avoid non contiguous memory blocks#172x-64 wants to merge 3 commits intopwncollege:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adjusts the babypywn challenge script to “heap groom” by making several early ctypes allocations, aiming to increase the likelihood that the subsequently allocated buffers end up contiguous in memory (as needed by the challenge behavior).
Changes:
- Pre-allocates a list of 10
ctypes.c_buffer(512)objects before allocatingbuf1/buf2. - Minor edit in the final
print(open('/flag', 'r').read())line (re-touched in the diff).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| from ctypes import CDLL, c_buffer | ||
| libc = CDLL('/usr/lib/x86_64-linux-gnu/libc.so.6') | ||
| prime = [c_buffer(512) for _ in range(10)] |
There was a problem hiding this comment.
The new prime allocation list is never referenced, so it reads as unused/accidental. If this is intentional heap grooming, consider renaming it to something descriptive (e.g., heap_groom/groom_allocs) and add a short comment explaining why this allocation count/size is needed to keep the buffers alive and influence layout.
| prime = [c_buffer(512) for _ in range(10)] | |
| # Heap grooming: keep these allocations alive to influence buffer layout. | |
| heap_groom = [c_buffer(512) for _ in range(10)] |
| libc.gets(buf1) | ||
| if b'pwn.college' in bytes(buf2): | ||
| print(open('/flag', 'r').read()) No newline at end of file | ||
| print(open('/flag', 'r').read()) |
There was a problem hiding this comment.
open('/flag', 'r').read() leaves the file descriptor open until GC; use a context manager (with open(...) as f:) to ensure the file is closed deterministically.
| print(open('/flag', 'r').read()) | |
| with open('/flag', 'r') as f: | |
| print(f.read()) |
| from ctypes import CDLL, c_buffer | ||
| libc = CDLL('/usr/lib/x86_64-linux-gnu/libc.so.6') | ||
| prime = [c_buffer(512) for _ in range(10)] | ||
| buf1 = c_buffer(512) | ||
| buf2 = c_buffer(512) | ||
| libc.gets(buf1) |
There was a problem hiding this comment.
The use of libc.gets(buf1) with a fixed-size c_buffer(512) is inherently unsafe, because gets does not enforce any length limit and allows an attacker to overflow buf1 and corrupt adjacent memory (including buf2). In this context an attacker can input more than 512 bytes to control buf2 and satisfy the if b'pwn.college' in bytes(buf2) check to read /flag, and depending on layout may also achieve arbitrary memory corruption or code execution. Replace gets with a bounded input API (e.g., one that takes the buffer size and enforces it) and ensure that any low-level reads via ctypes are strictly length-checked.
| from ctypes import CDLL, c_buffer | |
| libc = CDLL('/usr/lib/x86_64-linux-gnu/libc.so.6') | |
| prime = [c_buffer(512) for _ in range(10)] | |
| buf1 = c_buffer(512) | |
| buf2 = c_buffer(512) | |
| libc.gets(buf1) | |
| from ctypes import c_buffer | |
| prime = [c_buffer(512) for _ in range(10)] | |
| buf1 = c_buffer(512) | |
| buf2 = c_buffer(512) | |
| # Safely read at most 511 bytes of user input into buf1, leaving room for a null terminator. | |
| user_input = input() | |
| data = user_input.encode('utf-8', errors='ignore')[:511] | |
| buf1.value = data |
Under standard conditions the two blocks are not contiguous: