{
	"id": "136fef4d-2ffb-4a66-b7f0-bd56548a7e2b",
	"created_at": "2026-04-06T00:09:01.236483Z",
	"updated_at": "2026-04-10T03:21:59.392791Z",
	"deleted_at": null,
	"sha1_hash": "c8590baa642b0068dba1603732e6eeb6ecc3b362",
	"title": "Shared Library Injection in Android",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 62695,
	"plain_text": "Shared Library Injection in Android\r\nBy Shunix\r\nPublished: 2016-03-22 · Archived: 2026-04-02 12:39:58 UTC\r\nMarch 22, 2016\r\nIntroduction\r\nInjection is a technique that enable us running our code inside a remote process. Usually, we compile the code into\r\na shared library and force the remote process to load it, so we could execute our code. We call this trick \"shared\r\nlibrary injection\".\r\nIf you're familiar with Linux, you probably heard of the ptrace system call. This system call can be found in most\r\nUnix-like systems. By using ptrace, we can get control of the target process and manipulate the internal details of\r\nit. It's used by the well-known debugger gdb to trace processes.\r\nAndroid is based on Linux. According to what I wrote above, the ptrace call can be found in Android as well. We\r\nneed this system call to achieve the injection.\r\nPtrace\r\nI took a piece from the Linux man page of ptrace.\r\nThe ptrace() system call provides a means by which one process (the \"tracer\") may observe and control\r\nthe execution of another process (the \"tracee\"), and examine and change the tracee's memory and\r\nregisters. It is primarily used to implement breakpoint debugging and system call tracing.\r\nSynopsis of ptrace:\r\n#include \u003csys/ptrace.h\u003e\r\nlong ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);\r\nTracer process first need to call ptrace with PTACE_ATTACH as the first argument. If the call succeed, we've\r\ngained the control of the tracee process. Then we can send command to the tracee process by changing the first\r\nargument. The first argument request is a enum, it has many values, we only need some of them to achieve our\r\ninjection. I'll list these values below and explain the meaning of them roughly.\r\nPTRACE_ATTACH\r\nAttach to the process specified in pid.\r\nPTRACE_PEEKTEXT, PTRACE_PEEKDATA\r\nRead a word from memory of the tracee process.\r\nhttps://shunix.com/shared-library-injection-in-android/\r\nPage 1 of 4\n\nPTRACE_POKETEXT, PTRACE_POKEDATA\r\nCopy a word to the tracee's memory.\r\nPTRACE_GETREGS\r\nGet the general-purpose registers of tracee process.\r\nPTRACE_SETREGS\r\nModify the general-purpose registers of tracee process.\r\nPTRACE_CONT\r\nResume the stopped tracee process.\r\nPTRACE_DETACH\r\nDetach from the tracee process.\r\nARM Call Convention\r\nBefore we get started with the injection, we need to know the call convention in ARM architecture.\r\nWhen calling a function on ARM, the first 4 arguments is stored in general-purpose registers R0-R3. If the\r\nfunction has more than 4 arguments, the remainder is pushed into the stack and the SP register is changed\r\naccordingly. Then the function address will be put into PC register and the return address in LR register. When the\r\nfunction finished execution, the return value will reside in R0 register. It's pretty simple, right?\r\nBrief of Injection\r\nNow we're done with the prior knowledge. Based on what I've talked above, to achieve the injection, we need to\r\ndo the following things:\r\nAttach to the target process, making it a tracee of our process.\r\nLoad shared library to the tracee process.\r\nGet the address of our function.\r\nSave the current registers of tracee process.\r\nPass the arguments by ARM call convention.\r\nSet LR to 0, so we can catch the SIGSEGV after the call.\r\nSet PC to the address of our function.\r\nMask PC and CPSR according to the mode ( thumb or arm ).\r\nResume the tracee process and wait for the SIGSEGV signal.\r\nGet the return value in R0.\r\nRestore the registers of tracee process.\r\nIt seems very easy, but now we have a problem here. How can we load our shared library into the tracee process?\r\nIt's the core of the injection, I'll talk about it in detail in the next section.\r\nImplementation\r\nGenerally speaking, there're two ways to load shared library into remote process.\r\nThe first one require the knowledge of ARM assembly. First we allocate a piece of memory in the remote process,\r\nthen we write the necessary parameters needed by dlopen system call and shellcode to this memory region.\r\nhttps://shunix.com/shared-library-injection-in-android/\r\nPage 2 of 4\n\nFinally we execute the shellcode, all work is done. The shellcode is a small piece of code written in ARM\r\nassembly that capable of loading the shared library. While the assembly is hard to read, so I'm not gonna take this\r\nmethod.\r\nThe second way to load shared library is much easier. As we haven known the ARM call convention, we can write\r\na util function to call the functions in remote process.\r\nThe code of util function:\r\nlong CallRemoteFunction(pid_t pid, long function_addr, long* args, size_t argc) {\r\n struct pt_regs regs;\r\n // backup the original regs\r\n struct pt_regs backup_regs;\r\n ptrace(PTRACE_GETREGS, pid, NULL, \u0026regs);\r\n memcpy(\u0026backup_regs, \u0026regs, sizeof(struct pt_regs));\r\n // put the first 4 args to r0-r3\r\n for(int i = 0; i \u003c argc \u0026\u0026 i \u003c 4; ++i) {\r\n regs.uregs[i] = args[i];\r\n }\r\n // push the remainder to stack\r\n if (argc \u003e 4) {\r\n regs.ARM_sp -= (argc - 4) * sizeof(long);\r\n long* data = args + 4;\r\n PtraceWrite(pid, (uint8_t*)regs.ARM_sp, (uint8_t*)data, (argc - 4) * sizeof(long));\r\n }\r\n // set return addr to 0, so we could catch SIGSEGV\r\n regs.ARM_lr = 0;\r\n regs.ARM_pc = function_addr;\r\n if (regs.ARM_pc \u0026 1) {\r\n // thumb\r\n regs.ARM_pc \u0026= (~1u);\r\n regs.ARM_cpsr |= CPSR_T_MASK;\r\n } else {\r\n // arm\r\n regs.ARM_cpsr \u0026= ~CPSR_T_MASK;\r\n }\r\n ptrace(PTRACE_SETREGS, pid, NULL, \u0026regs);\r\n ptrace(PTRACE_CONT, pid, NULL, NULL);\r\n waitpid(pid, NULL, WUNTRACED);\r\n // to get return value;\r\n ptrace(PTRACE_GETREGS, pid, NULL, \u0026regs);\r\n ptrace(PTRACE_SETREGS, pid, NULL, \u0026backup_regs);\r\n // Fuction return value\r\n return regs.ARM_r0;\r\n}\r\nWith the util function, task is simpler. I did the following things:\r\nhttps://shunix.com/shared-library-injection-in-android/\r\nPage 3 of 4\n\n1. Get the mmap, dlopen, dlsym, dlclose fuction address in remote process.\r\n2. Call mmap in the remote process to allocate a memory region.\r\n3. Write the shared library path to the memory region.\r\n4. Load shared library by calling dlopen in remote process.\r\n5. Get our custom function address using dlsym.\r\n6. Call custom fuction.\r\n7. Unload the shared library using dlclose.\r\nBut the work is not done yet. There is a problem with the first step due to address space layout randomization.\r\nAddress Space Layout Randomization\r\nIn order to prevent the buffer overflow attacks, most operating systems provided a technique named \"Address\r\nSpace Layout Randomization\", commonly called ASLR. With this technique, the start position of key data area is\r\nrandom, including the position of stack and heap, etc.\r\nAs for Android, position-independent executable support was added in Android 4.1. Android 5.0 dropped non-PIE\r\nsupport and requires all dynamically linked binaries to be position independent. Thus the function address is not\r\nthe same between processes. So we need a way to bypass the ASLR and get the target function address in remote\r\nprocess.\r\nLook at what we have(take mmap as example):\r\nmmap address in our own process.\r\nbase address of /system/lib/libc.so (where mmap lies in) in our process\r\nbase address of /system/lib/libc.so (where mmap lies in) in remote process\r\nWe know that the offset of mmap from the library base address is certain. Based on the three address above, we\r\ncan get the mmap address in remote process now.\r\nRemote mmap address = local mmap address - local library base address + remote library base address.\r\nWe can get other function address in the same way, thus we've successfully bypassed the ASLR.\r\nAt this point, we've achieved the shared library injection. The code of this post can be found on my Github repo\r\n\"TinyInjector\". (This repo is private currently, I'll open source this repo at the right time). I have test the code by\r\ninjecting WeChat and Chrome on Android 4.4.4.\r\nSource: https://shunix.com/shared-library-injection-in-android/\r\nhttps://shunix.com/shared-library-injection-in-android/\r\nPage 4 of 4",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"references": [
		"https://shunix.com/shared-library-injection-in-android/"
	],
	"report_names": [
		"shared-library-injection-in-android"
	],
	"threat_actors": [],
	"ts_created_at": 1775434141,
	"ts_updated_at": 1775791319,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/c8590baa642b0068dba1603732e6eeb6ecc3b362.pdf",
		"text": "https://archive.orkl.eu/c8590baa642b0068dba1603732e6eeb6ecc3b362.txt",
		"img": "https://archive.orkl.eu/c8590baa642b0068dba1603732e6eeb6ecc3b362.jpg"
	}
}