{
	"id": "f37505bd-1f66-4361-a38e-0cbc32c47725",
	"created_at": "2026-04-06T00:20:12.726114Z",
	"updated_at": "2026-04-10T13:12:51.793888Z",
	"deleted_at": null,
	"sha1_hash": "459c2d691add882a515ee00cf9df9e7552361b2f",
	"title": "Shared Library Injection on Android 8.0",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 176791,
	"plain_text": "Shared Library Injection on Android 8.0\r\nBy Alexandr Fadeev\r\nPublished: 2018-08-25 · Archived: 2026-04-05 19:50:04 UTC\r\nFull solution: https://github.com/fadeevab/TinyInjector\r\nOne of the ways to carry out the shared library injection is to use ptrace system call (syscall). One process (a\r\ntracer) attaches to a tracee and calls dlopen inside tracee's virtual memory space.\r\nSuperuser privileges (root) are required to attach to any process in the system and to overcome other security\r\ncontrols. It is possible to use ptrace without the root, but both processes must have the same user ID. In the\r\ncurrent shared library injection it is assumed that SELinux is enforced as well as dm-verity and a few other\r\nsecurity controls.\r\nEnvironment State\r\nroot privileges 🔓\r\nSELinux 🔒\r\ndm-verity 🔒\r\nASLR 🔒\r\nhttps://fadeevab.com/shared-library-injection-on-android-8/\r\nPage 1 of 8\n\nEnvironment State\r\nlinker namespace 🔒\r\nSELinux - LSM module aimed to provide Mandatory Access Control (MAC).\r\ndm-verity - Linux kernel module to protect read-only partitions (/system, /vendor).\r\nASLR - address space layout randomization.\r\nLinker namespace - restrictions applied to dynamic linking.\r\nWhen a tracer attaches to a target process (tracee), it's possible to read/write data from/to the process' memory,\r\nread/write registers, to call mmap , mprotect , to manipulate with memory, to call any system API.\r\nThe ptrace syscall is not the only way to trap into a remote process, but the most obvious and powerful one.\r\nI wish to express my thanks to the author shunix for the series of articles about Shared Library Injection in\r\nAndroid.\r\n1. Shared Library Injection in Android (ASLR bypass is there as well).\r\n2. Bypass SELinux on Android.\r\n3. ARM and Thumb Instruction Set.\r\nI use his TinyInjector as a basis, and I extend one to the Android 8.0 realms, particularly to the ARM64\r\narchitecture (original version supports 32-bit ARM), SELinux, and the linker namespace.\r\nMore About Ptrace\r\nThe help topic of \"SELinux Policy Manager\" has a pretty nice overview of ptrace (lockdown_ptrace.txt):\r\nMost people do not realize that any program they run can examine the memory of any other process run\r\nby them. Meaning the computer game you are running on your desktop can watch everything going on\r\nin Firefox or a programs ... that attempts to hide passwords.\r\nptrace allows developers and administrators to debug how a process is running using tools like strace,\r\nptrace and gdb. You can even use gdb (GNU Debugger) to manipulate another process running\r\nmemory and environment.\r\nThe problem is this is allowed by default.\r\n... It would be more secure if we could disable it by default.\r\nNote: Disabling ptrace can break some bug trappers that attempt to collect crash data.\r\nIf you ever thought about any system security hardening, you would have to think about disabling ptrace (together\r\nwith the root user). However, you cannot disable them altogether, the problem lays in CIA principle\r\n(Confidentiality, Integrity, Availability): improving the integrity/confidentiality usually leads to degradation of\r\navailability. Disabling the ptrace makes the system to be almost undebuggable and unmaintainable.\r\nPreconditions\r\nIn order to attach to any process via ptrace system call, it is necessary:\r\nhttps://fadeevab.com/shared-library-injection-on-android-8/\r\nPage 2 of 8\n\n1. to have root privileges\r\n2. to be in a proper SELinux context allowed to use ptrace\r\nRoot is needed to:\r\n1. attach via ptrace to any process\r\n2. list all processes\r\n3. read /proc/\u003cpid\u003e/maps\r\n4. change SELinux labels\r\nIf you don't have superuser privileges, the injector (tracer) needs to have a tracee's UID to be able to attach to a\r\ntracee process.\r\nIf /proc filesystem is mounted with hidepid option, then an ordinary non-root process is not even able to see\r\nother processes in the system.\r\nhttps://fadeevab.com/shared-library-injection-on-android-8/\r\nPage 3 of 8\n\n$ grep proc /proc/mounts\r\nproc /proc proc rw,relatime,gid=3009,hidepid=2 0 0\r\n$ ps\r\nPID USER COMMAND\r\n17677 u0_a155/data/data/bash/bash -i\r\n21955 u0_a155ps\r\nDisabling SELinux: setenforce 0\r\nI haven't had to disable SELinux on my Android 8.0 device. Only root. I tested TinyInjector, launching one from\r\nthe /data partition, and it works just fine in Android 8.0 having only root:\r\n# On the device under root\r\n/data/local/tmp/injector com.android.example.app /data/local/tmp/libinject.so\r\nBut, on your device, you might be not able to attach the injector to another process with SELinux enforced. The\r\ncurrent version of TinyInjector contains the code to disable SELinux through selinuxfs. The trick to disable\r\nSELinux can be shortly described as the following shell script:\r\n# get actual selinuxfs directory here, e.g. /sys/fs/selinux\r\ngrep selinuxfs /proc/mount\r\n# disable SELinux; under the root\r\necho 0 \u003e /sys/fs/selinux/enforce\r\nDisabling SELinux: playing with labels\r\nI find the way above to be pretty rough to disable SELinux. Depending on a vendor of your device the selinuxfs\r\nmay be not even mounted (/sys/fs/selinux may not be present in such case). Disabling SELinux may be not\r\nallowed on the device. In such case there are a few other ways to deal with SELinux to allow ptrace:\r\n1. Search for a context which is allowed to use ptrace,\r\n2. either set a desired label to injector using semanager or chcon tool,\r\nchcon -v u:object_r:apk_data_file:s0 injector\r\n3. or place injector near the tracee and trap into the context using restorecon ,\r\n4. or find any other way to trap into desired SELinux context, e.g. through any standard Android behavior\r\nwhich leads to obtaining the desired context or the tracee's user ID.\r\nOvercome dm-verity\r\nhttps://fadeevab.com/shared-library-injection-on-android-8/\r\nPage 4 of 8\n\ndm-verity protects read-only partitions such as /system and /vendor. Pushing the binaries into /data partition\r\nallows to evade altering the protected partitions.\r\nadb push injector /data/local/tmp/\r\nadb push libinject.so /data/local/tmp/\r\nadb shell /data/local/tmp/injector com.android.example.app /data/local/tmp/libinject.so\r\nHow Injection Is Performed\r\n1. An injector attaches to a target process via ptrace .\r\n2. The target process is being suspended.\r\n3. The injector gets a content of registers saving the running context of the target.\r\n4. The injector finds symbols mmap , dlopen and dlsym in the virtual memory of the target process.\r\n5. mmap is used to allocate memory for the string parameters and for the stack.\r\n6. dlopen loads a shared library.\r\n7. dlsym retrieves some function from the newly loaded shared library.\r\n8. The function obtained via dlsym could be called.\r\n9. The injector restores registers of the target process restoring a running context.\r\n10. The target process continues as usually while the new shared library is injected and some custom function\r\nis called from that library.\r\nPorting TinyInjector to AARCH64 (ARM64)\r\nptrace() ARM64 Implementation\r\nInitially, TinyInjector supports ARM32 only. One of the differences lays around the low-level implementation of\r\nthe ptrace() system call.\r\nptrace() prototype is fairly generalized, but functionality is strongly tied to hardware: ptrace allows to manipulate\r\nregisters, and concrete list of registers depends on assembler, and assembler comes from architecture of CPU.\r\n\"Back-end\" of ptrace is provided by Linux kernel implementation.\r\n#include \u003csys/ptrace.h\u003e\r\nlong ptrace(enum __ptrace_request request, pid_t pid,\r\n void *addr, void *data);\r\nFor some reasons ARM64 ptrace usage pretty differs from ARM32.\r\n#if defined(__aarch64__)\r\n #define pt_regs user_pt_regs\r\n#endif\r\nstatic void PtraceGetRegs(pid_t pid, struct pt_regs *regs) {\r\nhttps://fadeevab.com/shared-library-injection-on-android-8/\r\nPage 5 of 8\n\n#if defined(__aarch64__)\r\n struct {\r\n void* ufb;\r\n size_t len;\r\n } regsvec = { regs, sizeof(struct pt_regs) };\r\n ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, \u0026regsvec);\r\n #else\r\n ptrace(PTRACE_GETREGS, pid, NULL, regs);\r\n #endif\r\n}\r\nARM32 (AARCH32) ARM64 (AARCH64)\r\nPTRACE_GETREGS PTRACE_GETREGSET\r\npid pid\r\nNULL NT_PRSTATUS\r\nstruct pt_regs * { struct user_pt_regs *ubf, size_t len }\r\n(Obviously, additional control is provided for the 4th parameter void *data passing the structure struct\r\npt_regs together with length instead of the raw pointer).\r\nAnother inconvenience has come from the struct pt_regs definition: the similar struct user_pt_regs is\r\nintroduced instead, and the register shortcuts are absent there. I added some macro to make code a bit more\r\nportable (I haven't found a better way):\r\n#if defined(__aarch64__)\r\n #define pt_regs user_pt_regs\r\n #define uregs regs\r\n #define ARM_r0 regs[0]\r\n #define ARM_lr regs[30]\r\n #define ARM_sp sp\r\n #define ARM_pc pc\r\n #define ARM_cpsr pstate\r\n#endif\r\nX0 is a 64-bit analogue of 32-bit R0.\r\nX30 is a return address.\r\nPSTATE is a processor state register.\r\nARM64/ARM32 ABI Differences\r\nABI formula:\r\nABI = PCS + ELF + DWARF + ...\r\nhttps://fadeevab.com/shared-library-injection-on-android-8/\r\nPage 6 of 8\n\nIn our case, PCS rules contained differences.\r\nPCS stands for a Procedure Call Standard, and it is about CPU rules of how to make procedure calls: how to pass\r\narguments, how to return the value, what registers are expected to get modified during a call, how the stack should\r\nbe used, and so on.\r\nIn the ARM32 PCS the first 4 parameters are passed to a procedure call through R0-R3 registers, and the rest of\r\nthe arguments are passed through the stack.\r\nfor(int i = 0; i \u003c argc \u0026\u0026 i \u003c 4; ++i) {\r\n regs.uregs[i] = args[i];\r\n}\r\nIn the ARM64 PCS the first 8 parameters are passed through X0-X7 registers to a procedure call. Taking mmap\r\nas the syscall which receives the largest number of arguments (6 arguments) among the syscalls in the\r\nTinyInjector, all arguments will be passed via the registers. (In the loop below I pass only 6 arguments instead of 8\r\nmistakenly, but it's enough to make mmap work).\r\n#if defined(__aarch64__)\r\n #define uregs regs\r\n#endif\r\nfor(int i = 0; i \u003c argc \u0026\u0026 i \u003c 6; ++i) {\r\n regs.uregs[i] = args[i];\r\n}\r\nThe Linker Namespace Bypass\r\nAs I mentioned in my review of the Android linker namespace, the caller of dlopen can be forged.\r\nBy pointing the X30 register (LR) to a code section of any shared library which namespace has wide privileges,\r\nwe make the dynamic linker of target process to apply desired namespace rules to dlopen call.\r\nCurrently, I would like to load shared library from /data/local/tmp (/sdcard can be acceptable option as well).\r\ndlopen of the most of the Android applications is limited about to load shared libraries from such untrusted\r\nplace. But the linker namespace of /system/lib/libRS.so allows to load shared libraries from any place of /data.\r\nSteps:\r\n1. Load the start of the libRS.so's code segment in the target process.\r\n#if defined(__aarch64__)\r\n#define VNDK_LIB_PATH \"/system/lib64/libRS.so\"\r\n#else\r\n#define VNDK_LIB_PATH \"/system/lib/libRS.so\"\r\nhttps://fadeevab.com/shared-library-injection-on-android-8/\r\nPage 7 of 8\n\n#endif\r\nlong vndk_return_addr = GetModuleBaseAddr(pid, VNDK_LIB_PATH);\r\n2. Point the return address to libRS.so:\r\nlong ret = CallRemoteFunctionFromNamespace(pid, function_addr, vndk_return_addr, params, 2);\r\nInside the function:\r\nregs.ARM_lr = return_addr;\r\n3. Perform the remote call:\r\nPtraceSetRegs(pid, \u0026regs);\r\nWhen dlopen call finishes, I watch the same effect as a pointing LR to NULL: get the control back after the\r\nfault and restoring the running context by setting stored registers.\r\nSELinux Label for Injection Library\r\nThe final step is to overcome SELinux that denies to mmap a shared library from /data. Use the same trick with a\r\nlabel as before:\r\nchcon -v u:object_r:apk_data_file:s0 /data/local/tmp/libinject.so\r\nSource: https://fadeevab.com/shared-library-injection-on-android-8/\r\nhttps://fadeevab.com/shared-library-injection-on-android-8/\r\nPage 8 of 8",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://fadeevab.com/shared-library-injection-on-android-8/"
	],
	"report_names": [
		"shared-library-injection-on-android-8"
	],
	"threat_actors": [],
	"ts_created_at": 1775434812,
	"ts_updated_at": 1775826771,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/459c2d691add882a515ee00cf9df9e7552361b2f.pdf",
		"text": "https://archive.orkl.eu/459c2d691add882a515ee00cf9df9e7552361b2f.txt",
		"img": "https://archive.orkl.eu/459c2d691add882a515ee00cf9df9e7552361b2f.jpg"
	}
}