Created
November 7, 2025 05:53
-
-
Save w568w/9a705c9545335129960a047216bf06ce to your computer and use it in GitHub Desktop.
Modify the command line of a running process shown in `top`, `ps`, etc.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import os | |
| import sys | |
| import ctypes | |
| from typing import Tuple, List | |
| def get_argv_address(pid: int) -> Tuple[int, int, bytes]: | |
| """ | |
| Get the memory address of the argv area of a given process. | |
| Args: | |
| pid (int): Target process ID. | |
| Returns: | |
| Tuple[int, int, bytes]: A tuple containing: | |
| - start_addr (int): Starting address of argv in memory | |
| - end_addr (int): Ending address of argv in memory | |
| - argv_bytes (bytes): Raw argv bytes read from /proc/[pid]/cmdline | |
| """ | |
| # 1. Read current cmdline — the raw argv contents | |
| cmdline_path = f"/proc/{pid}/cmdline" | |
| with open(cmdline_path, 'rb') as f: | |
| cmdline_bytes = f.read() | |
| # Parse arguments from cmdline (null-separated) | |
| args = cmdline_bytes.rstrip(b'\x00').split(b'\x00') | |
| print(f"Current arguments: {[arg.decode() for arg in args]}") | |
| # 2. Locate the stack region from /proc/[pid]/maps | |
| maps_path = f"/proc/{pid}/maps" | |
| stack_start: int | None = None | |
| stack_end: int | None = None | |
| with open(maps_path, 'r') as f: | |
| for line in f: | |
| if '[stack]' in line: | |
| addr_range = line.split()[0] | |
| start, end = addr_range.split('-') | |
| stack_start = int(start, 16) | |
| stack_end = int(end, 16) | |
| break | |
| if not stack_start: | |
| raise RuntimeError("Stack region not found in memory map.") | |
| print(f"Stack region: 0x{stack_start:x} - 0x{stack_end:x}") | |
| # 3. Search for argv bytes in the stack memory range | |
| mem_path = f"/proc/{pid}/mem" | |
| chunk_size = 4096 | |
| with open(mem_path, 'rb') as mem: | |
| addr = stack_start | |
| while addr < stack_end: | |
| try: | |
| mem.seek(addr) | |
| chunk = mem.read(chunk_size) | |
| offset = chunk.find(cmdline_bytes) | |
| if offset != -1: | |
| found_addr = addr + offset | |
| print(f"Found argv address: 0x{found_addr:x}") | |
| return found_addr, found_addr + len(cmdline_bytes), cmdline_bytes | |
| addr += chunk_size | |
| except OSError: | |
| # Skip unreadable pages | |
| addr += chunk_size | |
| continue | |
| raise RuntimeError("Unable to locate argv bytes in process memory.") | |
| def modify_argv(pid: int, argv_addr: int, new_args: List[str], original_length: int) -> None: | |
| """ | |
| Modify the argv of a running process. | |
| Args: | |
| pid (int): Process ID. | |
| argv_addr (int): Starting memory address of argv. | |
| new_args (List[str]): List of new argument strings. | |
| original_length (int): Length (in bytes) of the original argv data. | |
| """ | |
| # Construct new cmdline data | |
| new_cmdline = b'\x00'.join(arg.encode('utf-8') for arg in new_args) + b'\x00' | |
| print(f"\nModifying argv to: {new_args}") | |
| print(f"New data length: {len(new_cmdline)} bytes") | |
| # Pad with null bytes if the new data is shorter | |
| if len(new_cmdline) < original_length: | |
| padding = original_length - len(new_cmdline) | |
| new_cmdline += b'\x00' * padding | |
| print(f"Padded with {padding} null bytes") | |
| PTRACE_ATTACH = 16 | |
| PTRACE_DETACH = 17 | |
| libc = ctypes.CDLL("libc.so.6") | |
| # Attach to the process (requires root privileges) | |
| result = libc.ptrace(PTRACE_ATTACH, pid, 0, 0) | |
| if result == 0: | |
| os.waitpid(pid, 0) | |
| print("Attached to target process.") | |
| try: | |
| # Write new argv data into process memory | |
| mem_path = f"/proc/{pid}/mem" | |
| with open(mem_path, 'r+b') as mem: | |
| mem.seek(argv_addr) | |
| mem.write(new_cmdline) | |
| print("✓ argv successfully modified.") | |
| finally: | |
| # Detach to resume normal execution | |
| libc.ptrace(PTRACE_DETACH, pid, 0, 0) | |
| print("Detached from process.") | |
| def main() -> None: | |
| if len(sys.argv) < 2: | |
| print("Usage:") | |
| print(f" View argv: {sys.argv[0]} <pid>") | |
| print(f" Modify argv: {sys.argv[0]} <pid> <new_arg0> <new_arg1> ...") | |
| sys.exit(1) | |
| pid = int(sys.argv[1]) | |
| try: | |
| # Locate argv in process memory | |
| addr, end_addr, old_data = get_argv_address(pid) | |
| print(f"argv memory range: 0x{addr:x} - 0x{end_addr:x} ({end_addr - addr} bytes)") | |
| # If additional args provided, perform modification | |
| if len(sys.argv) > 2: | |
| new_args = sys.argv[2:] | |
| new_len = sum(len(arg.encode()) for arg in new_args) + len(new_args) | |
| old_len = end_addr - addr | |
| if new_len > old_len: | |
| print(f"\nWarning: New argv ({new_len} bytes) exceeds original size ({old_len} bytes)") | |
| print("This may overwrite adjacent data! Aborting.") | |
| return | |
| modify_argv(pid, addr, new_args, old_len) | |
| # Verify modification | |
| print("\nVerifying modification...") | |
| new_addr, new_end, new_data = get_argv_address(pid) | |
| except PermissionError: | |
| print("Error: Root privileges are required.") | |
| sys.exit(1) | |
| except Exception as e: | |
| print(f"Error: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| sys.exit(1) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment