{
	"id": "a4379a64-20b2-4e83-b365-64ff51044d7e",
	"created_at": "2026-04-06T15:53:13.894633Z",
	"updated_at": "2026-04-10T03:22:09.531564Z",
	"deleted_at": null,
	"sha1_hash": "1d62314c2ca52f8b3a516a51fc786308c6bfc987",
	"title": "ELF shared library injection forensics",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 196390,
	"plain_text": "ELF shared library injection forensics\r\nBy backtrace\r\nPublished: 2016-04-22 · Archived: 2026-04-06 15:17:27 UTC\r\nAt Backtrace we built and are continually building security and forensics features into our product that rely on\r\nunderstanding the structural nuances of ELF binary internals, and process memory infection techniques. This\r\narticle outlines some of the core concepts that are being applied in our technology today.\r\nFor well over a decade attackers have been installing memory resident backdoors, rootkits, and parasites of\r\nvarious kinds into userland processes. The goal is to inject executable code into an existing process, to alter its\r\nfunctionality, while remaining stealth and keeping the attackers activity surreptitious. The most commonly used\r\napproach for inserting executable code into an existing process is a technique that has been termed shared library\r\ninjection. This approach is flexible for a variety of reasons, and it can be very difficult for forensics investigators\r\nto detect whether the shared library is legitimate or not. The focus of this blog post is going to be centered around\r\nhow to identify legitimate shared library objects vs. suspicious and potentially malicious ones. Gaining this\r\nknowledge is essential to move forward in identifying other related attacks such as PLT/GOT infections, which\r\nallow an attacker to hijack shared library functions and alter the flow of execution for their own gain.\r\nThe following is a great talk by Georg Wicherski, speaking at Syscan 2013 about a real world example of an\r\nAdvanced Nation State Actor who maintained stealth residence on a large European IT companies network, by\r\nusing ELF shared library injection to modify the functionality behind the Apache web-server process, creating a\r\nstealth and nearly undetectable backdoor into the Linux servers on the network.\r\nBuilding the process image\r\nLinux and other modern UNIX flavor operating systems use the ELF binary format as the basis for building a\r\nprocess image, which generally consists of an executable file and shared libraries who have had their segments\r\nmapped into the address space. The internals to how this is accomplished vary by degree between operating\r\nsystems, but typically the kernel is responsible for loading the programs individual segments, of which there is\r\ntypically a segment for code and a segment for data. These are marked by program headers that are of a type\r\ncalled PT_LOAD . The kernel is also responsible for loading the program interpreter, also known as the dynamic\r\nlinker (i.e. /lib/x86_64/ld-linux.so ), which is marked by a single program header of type PT_INTERP . Using\r\nthe readelf utility we can view the program headers of a simple dynamic-linked executable to demonstrate this.\r\n$ readelf -l host\r\nElf file type is EXEC (Executable file)\r\nEntry point 0x400440\r\nThere are 9 program headers, starting at offset 64\r\nProgram Headers:\r\nType Offset VirtAddr PhysAddr\r\nhttps://web.archive.org/web/20210205211142/https://backtrace.io/blog/backtrace/elf-shared-library-injection-forensics/\r\nPage 1 of 12\n\nFileSiz MemSiz Flags Align\r\nPHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040\r\n0x00000000000001f8 0x00000000000001f8 R E 8\r\nINTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238\r\n0x000000000000001c 0x000000000000001c R 1\r\n[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]\r\nLOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000\r\n0x0000000000000764 0x0000000000000764 R E 200000\r\nLOAD 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10\r\n0x0000000000000230 0x0000000000000238 RW 200000\r\nDYNAMIC 0x0000000000000e28 0x0000000000600e28 0x0000000000600e28\r\n0x00000000000001d0 0x00000000000001d0 RW 8\r\nThe above output is slightly truncated to show only the most relevant parts of the executable. The INTERP\r\nprogram header displays the path to the dynamic linker, and the two LOAD program headers mark the text and\r\ndata segment. Also notice the DYNAMIC segment, which contains data stored as an array of structs. Note that the\r\ndynamic segment exists within the data segment range, so it gets loaded into memory along with the second\r\nLOAD segment.\r\nThe dynamic segment is an array of ElfN_Dyn structs\r\ntypedef struct {\r\nElf64_Sxword d_tag;\r\nunion {\r\nElf64_Xword d_val;\r\nElf64_Addr d_ptr;\r\n} d_un;\r\n} Elf64_Dyn;\r\nextern Elf64_Dyn _DYNAMIC[];\r\nThe dynamic segment is parsed by the dynamic linker at runtime, as it contains all of the necessary information\r\nfor the dynamic linker to begin finding shared library dependencies, and linking them into the process using a\r\ncomplex formula of relocation based runtime patching. The dynamic linker is itself a shared library object. It is\r\nresponsible for hot patching a process’s memory so that shared executable code executes within the current\r\nmemory layout. These patches are applied with the help of ELF relocation records, which are stored in ELF\r\nsections of type SHT_REL , and SHT_RELA . More details on the internals of relocations can be found in the ELF\r\nspecs, and also described with some easier to understand examples in the book Learning Linux binary analysis.\r\nFor the purpose of this article we are most interested in the way that the dynamic linker determines which shared\r\nlibrary objects should be linked into a process image. The ElfN_Dyn struct has a member called d_tag which\r\ntells the dynamic linker which type of data is stored in the struct. Since there is an array of them each one may\r\ncontain values relating to different things. See man elf(5) for a complete list of possible d_tag definitions. The\r\ndynamic linker looks for dynamic entries that hold a d_tag value of type DT_NEEDED to determine which shared\r\nobjects are to be linked into the address space.\r\nhttps://web.archive.org/web/20210205211142/https://backtrace.io/blog/backtrace/elf-shared-library-injection-forensics/\r\nPage 2 of 12\n\nThe dynamic segment viewed with readelf\r\n$ readelf -d host Dynamic section at offset 0xe28 contains 24 entries:\r\nTag Type Name/Value\r\n0x0000000000000001 (NEEDED) Shared library: [libc.so.6]\r\n0x000000000000000c (INIT) 0x4003e0\r\n0x000000000000000d (FINI) 0x4005f4\r\n0x0000000000000019 (INIT_ARRAY) 0x600e10\r\n0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)\r\n0x000000000000001a (FINI_ARRAY) 0x600e18\r\n0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)\r\n0x000000006ffffef5 (GNU_HASH) 0x400298\r\n0x0000000000000005 (STRTAB) 0x400318\r\n0x0000000000000006 (SYMTAB) 0x4002b8\r\n0x000000000000000a (STRSZ) 61 (bytes)\r\n0x000000000000000b (SYMENT) 24 (bytes)\r\n0x0000000000000015 (DEBUG) 0x0\r\n0x0000000000000003 (PLTGOT) 0x601000\r\n0x0000000000000002 (PLTRELSZ) 72 (bytes)\r\n0x0000000000000014 (PLTREL) RELA\r\n0x0000000000000017 (JMPREL) 0x400398\r\n0x0000000000000007 (RELA) 0x400380\r\n0x0000000000000008 (RELASZ) 24 (bytes)\r\n0x0000000000000009 (RELAENT) 24 (bytes)\r\n0x000000006ffffffe (VERNEED) 0x400360\r\n0x000000006fffffff (VERNEEDNUM) 1\r\n0x000000006ffffff0 (VERSYM) 0x400356\r\n0x0000000000000000 (NULL) 0x0\r\nNearly all of the dynamic segment entries shown in the above output are relevant and necessary for the dynamic\r\nlinker to accomplish what it needs on one level or another, and are explained in more depth in the ELF(5) manual\r\npages. In particular we are interested in the NEEDED entries, because these allow us to see which shared libraries\r\nare needed by the dynamic linker, legitimately or not. This leads us to the first infection point that can be used by\r\nattackers, that we call a DT_NEEDED infection. This technique was originally published in the Cerberus ELF\r\ninterface phrack article, by Mayhem. This is a direct binary modification technique that requires the attacker to\r\nmodify the executable program on-disk, therefore being less stealth since this type of blatant modification can be\r\npicked up by a simple IDS program such as tripwire. Otherwise it can often be detected by the trained eye;\r\ntypically an attacker overwrites the DT_DEBUG entry with an extra DT_NEEDED entry which points to a shared\r\nlibrary path of the attackers choosing. This is easily spotted because the DT_NEEDED entries are expected to be\r\ncontiguous. Now examine the following readelf output to see how this stands out like a sore thumb.\r\nUsing readelf to see how firefox has been infected\r\nhttps://web.archive.org/web/20210205211142/https://backtrace.io/blog/backtrace/elf-shared-library-injection-forensics/\r\nPage 3 of 12\n\nDynamic section at offset 0x1da98 contains 33 entries:\r\nTag Type Name/Value\r\n0x0000000000000001 (NEEDED) Shared library: [libpthread.so.0]\r\n0x0000000000000001 (NEEDED) Shared library: [libdl.so.2]\r\n0x0000000000000001 (NEEDED) Shared library: [libstdc++.so.6]\r\n0x0000000000000001 (NEEDED) Shared library: [libm.so.6]\r\n0x0000000000000001 (NEEDED) Shared library: [libgcc_s.so.1]\r\n0x0000000000000001 (NEEDED) Shared library: [libc.so.6]\r\n0x0000000000000001 (NEEDED) Shared library: [ld-linux-x86-64.so.2]\r\n0x000000000000000c (INIT) 0x41e0\r\n0x000000000000000d (FINI) 0x169f0\r\n0x0000000000000019 (INIT_ARRAY) 0x21d8c0\r\n0x000000000000001b (INIT_ARRAYSZ) 24 (bytes)\r\n0x000000000000001a (FINI_ARRAY) 0x21d8d8\r\n0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)\r\n0x000000006ffffef5 (GNU_HASH) 0x2d0\r\n0x0000000000000005 (STRTAB) 0x1cd0\r\n0x0000000000000006 (SYMTAB) 0x788\r\n0x000000000000000a (STRSZ) 5575 (bytes)\r\n0x000000000000000b (SYMENT) 24 (bytes)\r\n0x0000000000000015 (NEEDED) Shared library: [sneaky_rabbit.so.1]\r\n0x0000000000000003 (PLTGOT) 0x21dce8\r\n0x0000000000000002 (PLTRELSZ) 2016 (bytes)\r\n0x0000000000000014 (PLTREL) RELA\r\n0x0000000000000017 (JMPREL) 0x3a00\r\n0x0000000000000007 (RELA) 0x35b0\r\n0x0000000000000008 (RELASZ) 1104 (bytes)\r\n0x0000000000000009 (RELAENT) 24 (bytes)\r\n0x0000000000000018 (BIND_NOW)\r\n0x000000006ffffffb (FLAGS_1) Flags: NOW\r\n0x000000006ffffffe (VERNEED) 0x3460\r\n0x000000006fffffff (VERNEEDNUM) 7\r\n0x000000006ffffff0 (VERSYM) 0x3298\r\n0x000000006ffffff9 (RELACOUNT) 36\r\n0x0000000000000000 (NULL) 0x0\r\nEven if the injected shared library wasn’t named so silly, like sneaky_rabbit.so.1 , it would still be very\r\napparent. It is not as easy for an attacker to create a new entry in the dynamic segment as it is to overwrite an\r\nexisting one. Therefore overwriting DT_DEBUG is perfect since it is only used for debugging purposes. The reason\r\nit is difficult for an attacker to add a new item to the dynamic segment is because the .got.plt section directly\r\nfollows it which cannot be shifted forward without also adjusting every single .rela.plt entry, who’s\r\nr_offset members reference the locations within the .got.plt section. We cannot rule out possible infection\r\nmethods, but it is highly unlikely that attackers will reliably insert a phony DT_NEEDED entry that is contiguous\r\nhttps://web.archive.org/web/20210205211142/https://backtrace.io/blog/backtrace/elf-shared-library-injection-forensics/\r\nPage 4 of 12\n\nwith the ones generated by the linker. In short, it is safe to say that we have established a generally reliable way to\r\ndetect bogus DT_NEEDED entries by checking to see if one has been inserted over the DT_DEBUG entry.\r\nLegitimate shared library linking\r\nIn order to detect suspicious shared objects in a process image we must first understand what a legitimate shared\r\nlibrary object looks like. The dynamic linker loads shared libraries in three primary ways that can be considered as\r\nlegitimate:\r\nValid DT_NEEDED entries\r\nAssuming the DT_NEEDED entry is valid, which we can ascertain with some degree of confidence using the\r\nknowledge just described: If the DT_NEEDED entries are contiguous, then they are most likely legitimate.\r\nMost attackers are interested in avoiding modification of the binary file on disk, but we would be remiss to not\r\nmention the DT_NEEDED infection as it is necessary to validate that the entries we are viewing are legitimate\r\nbefore using them as a baseline for detecting other infections that rely on in-memory shared library injection\r\ntechniques. Also sometimes called Reflective DLL injection\r\ntechniques.\r\nIt is important to note that the DT_NEEDED entries must be transitively examined. For every DT_NEEDED entry\r\nthere is a shared object file with with its own DT_NEEDED entries, so there is a dependency tree that begins with\r\nthe parent object, and that must be followed through each child object. Duplicates will come up, especially with\r\nlibc.so since so many different shared libraries rely on it, but the duplicates are simply ignored.\r\nExamples of dependency resolution\r\n$ readelf -d /bin/ls | grep NEEDED\r\n0x0000000000000001 (NEEDED) Shared library: [libselinux.so.1]\r\n0x0000000000000001 (NEEDED) Shared library: [libacl.so.1]\r\n0x0000000000000001 (NEEDED) Shared library: [libc.so.6]\r\n$ readelf -d /lib/x86_64-linux-gnu/libselinux.so.1 | grep NEEDED\r\n0x0000000000000001 (NEEDED) Shared library: [libpcre.so.3]\r\n0x0000000000000001 (NEEDED) Shared library: [libdl.so.2]\r\n0x0000000000000001 (NEEDED) Shared library: [libc.so.6]\r\n0x0000000000000001 (NEEDED) Shared library: [ld-linux-x86-64.so.2]\r\n$ readelf -d /lib/x86_64-linux-gnu/libacl.so.1 | grep NEEDED\r\n0x0000000000000001 (NEEDED) Shared library: [libattr.so.1]\r\n0x0000000000000001 (NEEDED) Shared library: [libc.so.6]\r\n$ readelf -d /lib/x86_64-linux-gnu/libc.so.6 | grep NEEDED\r\n0x0000000000000001 (NEEDED) Shared library: [ld-linux-x86-64.so.2]\r\nWith the readelf command we can demonstrate how the resolution is actually accomplished, but to resolve all\r\nof them from the command line it really makes sense just to use the /usr/bin/ldd command that is available in\r\nLinux and FreeBSD. This will show them all without duplicates. Keep in mind too that linux-vdso.so.1 doesn’t\r\nhttps://web.archive.org/web/20210205211142/https://backtrace.io/blog/backtrace/elf-shared-library-injection-forensics/\r\nPage 5 of 12\n\noriginate from a DT_NEEDED entry, it is mapped into glibc linked processes, by the Linux kernel, and it does\r\nnot exist at all in FreeBSD.\r\n$ /usr/bin/ldd /bin/ls\r\nlinux-vdso.so.1 =\u003e (0x00007fff7f1e9000)\r\nlibselinux.so.1 =\u003e /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007f699ec1b000)\r\nlibacl.so.1 =\u003e /lib/x86_64-linux-gnu/libacl.so.1 (0x00007f699ea12000)\r\nlibc.so.6 =\u003e /lib/x86_64-linux-gnu/libc.so.6 (0x00007f699e647000)\r\nlibpcre.so.3 =\u003e /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007f699e3da000)\r\nlibdl.so.2 =\u003e /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f699e1d6000)\r\n/lib64/ld-linux-x86-64.so.2 (0x000055a1ccba0000)\r\nlibattr.so.1 =\u003e /lib/x86_64-linux-gnu/libattr.so.1 (0x00007f699dfd0000)\r\nlibpthread.so.0 =\u003e /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f699ddb2000)\r\nLibraries that are preloaded with LD_PRELOAD\r\nAlthough some userland rootkits rely on the LD_PRELOAD environment variable for linking the shared library code\r\ninto the process image, it is a legitimate way to link a shared object and without some analysis being performed on\r\na shared object that was preloaded, we will assume that the library is legitimate. There are some fairly surefire\r\nways to quickly identify if a preloaded shared library is malicious, especially if it has symbol names that override\r\ncommon libc.so functions, such as read , write , open , socket , etc. This indicates that the shared library\r\nis trying to override or replace existing shared library functionality and is almost a sure sign of some type of code\r\npatching, but is beyond the scope of this article. Keep in mind that most serious attackers are not going to use\r\nLD_PRELOAD because it requires them restarting whatever process they want to infect, in order to get the dynamic\r\nlinker to preload their shared object. Most attackers want to surreptitiously infect an existing process image\r\nwithout having to create a new process.\r\nLibraries that are loaded with dlopen()\r\nvoid * dlopen(const char *filename, int flag)\r\nIn Linux the dlopen function is apart of the libdl shared library, and it is considered the legitimate way to\r\nrequest the dynamic linker to dynamically load a shared library object at any given point in runtime. By\r\nexamining a programs symbol table we can identify whether or not it is using the dlopen function and use some\r\nstatic analysis to see what string value is being passed to the function. This has to be done at runtime since not all\r\nstrings will be stored in the .rodata and .data sections on-disk. It is possible that they were pulled from some\r\nexternal source and then stored in a stack buffer, a heap buffer, or even in the .bss section which marks the\r\nuninitialized gives global data found at the tail end of the data segment.\r\nIn FreeBSD the dlopen function exists only in ld-elf.so , but has a fake symbol in libc.so . The purpose (or\r\nside-effect) of this, is to waste an attackers time, who now believe that their shellcode should be invoking a\r\nfunction in libc.so that doesn’t even exist. The fake dlopen symbol looks legitimate, and the executables\r\nhave a dlopen@plt entry that presumably transfers control to the dlopen in libc.so , but in actuality control\r\nis being transferred to the dlopen function that exists in the dynamic linker. I just recently acquired this\r\nhttps://web.archive.org/web/20210205211142/https://backtrace.io/blog/backtrace/elf-shared-library-injection-forensics/\r\nPage 6 of 12\n\nknowledge from a colleague, and verified it by using GDB to inspect a program that calls dlopen . Upon\r\ninspection I verified that the global offset table entry for dlopen contained a value that points into the ld-elf.so address range, and not into the libc.so address range. A quick look at the disassembly also reveals that\r\ndlopen in libc.so does nothing more than pass an argument with the current time zone to __rtld_error ,\r\nresulting in failure for any attackers who are attempting to trigger it.\r\nFake dlopen in FreeBSD’s libc.so\r\n0000000000123540 \u003cdlopen\u003e:\r\n123540: 55 push %rbp\r\n123541: 48 89 e5 mov %rsp,%rbp\r\n123544: 48 8d 3d d5 c5 25 00 lea 0x25c5d5(%rip),%rdi # 37fb20 \u003ctzname+0x10\u003e\r\n12354b: 31 c0 xor %eax,%eax\r\n12354d: e8 42 44 f1 ff callq 37994 \u003c_rtld_error@plt\u003e\r\n123552: 31 c0 xor %eax,%eax\r\n123554: 5d pop %rbp\r\n/proc/pid/maps and the final analysis\r\nTo find each shared library object within a process image, we may use the more naive approach of analyzing the\r\nfirst 64 sizeof Elf64_Ehdr bytes of file backed memory mappings. Lets not be short sighted though and only\r\nanalyze file backed mappings when it is totally conceivable for an attacker to store the injected shared object into\r\nanonymous memory mappings, such as those created with the mmap flag MAP_ANONYMOUS . We should only\r\nobserve the memory maps that have executable permissions, since the initial ELF file header exists in the text\r\nregion. If the mapping is found to be an executable ELF object of type ET_DYN and it’s within an anonymous\r\nmemory mapping, it should be immediately flagged as suspicious. On other hand, if it is a file mapping, then we\r\nmust put it through heuristics to determine if it was legitimately linked using one of the three primary linking\r\nconcepts described above.\r\nCheck if it has a corresponding legitimate DT_NEEDED entry through transitive dependency resolution\r\nCheck if its path was set with the LD_PRELOAD environment variable, or if it is a dependency of a shared\r\nobject that was set by LD_PRELOAD\r\nCheck if its path was passed in a call to dlopen , or if it is a dependency of any shared objects that were\r\nlinked by dlopen\r\nIf none of the above heuristics returned true, then the shared object should be flagged as suspicious. This leads us\r\nto the eventual question of “What other ways can a shared library be linked into a process?”\r\nShared library injection techniques\r\nAlthough it is enough to know only the legitimate ways in which a shared library is linked in order to detect\r\nsuspicious ones, it can give a forensics investigator some advantage in atleast knowing the other ways in which a\r\nshared library can be linked into an already existing process image by an attacker. This way, if the investigator is\r\nable to do some reverse engineering work, they will know exactly what to look for.\r\nhttps://web.archive.org/web/20210205211142/https://backtrace.io/blog/backtrace/elf-shared-library-injection-forensics/\r\nPage 7 of 12\n\nopen/mmap based shellcode\r\nAttackers can inject shellcode into a process using the ptrace system call. The shellcode uses the open and\r\nmmap system calls to map the shared library into the process address space. This may be mapped in either as a\r\nfile backed mapping (which is more obvious since it shows up in /proc/pid/maps ), or it may store the code into\r\nan anonymous memory mapping. At this point the attacker must use some relatively complex code that is capable\r\nof using ptrace to apply all of the necessary relocations to the shared library just as the dynamic linker would to\r\nmake it suitable for execution within the given address space.\r\nVDSO manipulation\r\nAs discussed in the 2009 paper titled Modern day ELF Runtime infection via GOT poisoning, the attacker may\r\nnot be allowed to inject shellcode into the address space directly with the PTRACE_POKETEXT request due to PaX\r\nrestrictions which prevent even the ptrace system call from writing to read-only regions, due to the\r\naccess_process_vm kernel function bailing out when mprotect restrictions are enabled. After discussions with a\r\ncolleague and some experimentation, it quickly became evident that an attacker could hijack control of the code\r\nstubs mapped in from the linux-vdso.so object, allowing for glibc syscall hijacking by resetting the %rax\r\nregister with the desired syscall number, and by setting the other registers with the needed arguments. Using some\r\nptrace trickery, the user can set the instruction pointer to this VDSO code using the modified registers to\r\nredirect execution to the desired system calls, such as open and mmap . Once the shared library has been mapped\r\ninto the process address space, and any relocations have been applied, the user can reset the instruction pointer\r\nback to the start of the VDSO stub with the original register values set and execute the syscall that was intended\r\nbefore control was hijacked.\r\n__libc_dlopen_mode shellcode\r\nThis tends to be the most commonly used method for attacker driven shared library loading. The\r\n__libc_dlopen_mode function exists in the GNU C library, which is linked into virtually every process on a\r\ngiven Linux system. This means that an attacker can inject shellcode into a process that invokes\r\n__libc_dlopen_mode which in-turn will map the requested shared library into the process, and handle applying\r\nall of the relocations, thus removing the need for the attacker to understand all of the ELF relocation internals.\r\nThis technique is a solid and stable way to inject shared libraries into a process, although it is not the most stealth\r\nsince the shared library is mapped into the process as a file-backed mapping instead of an anonymous mapping.\r\nNonetheless it seems to be the most popular technique (Not that any of this knowledge is particularly well known\r\nor popular).\r\nFreeBSD dlopen shellcode\r\nIn the FreeBSD operating system the dlopen function only exists in the dynamic linker, as mentioned\r\npreviously. An attacker can simply use a command such as readelf -s /libexec/ld-elf.so | grep dlopen to\r\nacquire the offset and then calculate the final address at runtime, before injecting shellcode that invokes dlopen\r\ndirectly.\r\nLegitimate dlopen calls vs. illegal (attacker) dlopen calls\r\nhttps://web.archive.org/web/20210205211142/https://backtrace.io/blog/backtrace/elf-shared-library-injection-forensics/\r\nPage 8 of 12\n\nThe way to distinguish between an illegal dlopen call, and a legitimate dlopen call stands the same for both\r\nLinux and FreeBSD. A legitimate dlopen call will always be a call instruction into the PLT (procedure\r\nlinkage table). This can easily be observed by disassemblers which track the ELF sections, and thus will see any\r\ncalls that refer to the .plt section.\r\nExample of legitimate dlopen cal\r\n0000000000400678 \u003cmain\u003e:\r\n400678: 55 push %rbp\r\n400679: 48 89 e5 mov %rsp,%rbp\r\n40067c: 48 83 ec 10 sub $0x10,%rsp\r\n400680: be 00 00 00 00 mov $0x0,%esi\r\n400685: bf 34 07 40 00 mov $0x400734,%edi\r\n40068a: e8 b1 fe ff ff callq \u003cdlopen@plt\u003e\r\n40068f: 48 89 45 f8 mov %rax,-0x8(%rbp) Typically an attacker will be using ptrace to inject code somewhere\r\ninto the process address space that temporarily overwrites some existing code in the text segment, and any calls to\r\nthe dlopen function will typically be directly to the code for the dlopen function and not into the PLT entry\r\naddress, of which we can easily establish the address range using the readelf tool, and verify.\r\n$ readelf -S test | grep \".plt\" -A1 | egrep -v 'got|rela|WA|AI'\r\n[12] .plt PROGBITS 0000000000400500 00000500\r\n0000000000000050 0000000000000010 AX 0 0 16\r\nLooking at a legitimate dlopen@plt call does bring to mind the possibility of an attacker modifying the line of\r\ncode right before the legitimate call to dlopen@plt , to set the %rdi register to point to a string containing a\r\nmalicious shared library path. In Linux/FreeBSD this method would be somewhat rare because it would require\r\nthat the process being infected already has a legitimate use of dlopen with a corresponding dlopen@plt entry.\r\nIt is certaintly difficult to isolate every single edge case of possible infection vectors, but its good to be aware of\r\nwhat’s possible, even if unlikely.\r\nExample of suspicious dlopen call\r\nAssuming that the address of __libc_dlopen_mode in libc.so is 0x7f3e444a39f0 , then the shellcode will use\r\nsome type of branch instruction to transfer execution to that location. Typically the branch instruction used will\r\nnot be an immediate call instruction. It is a common practice for shellcode of this nature to use an indirect call or\r\nindirect jump because the instruction that moves the target address into a register can have the source operand\r\nbytes be all zeroes until the target address is learned, at which time the zeroes will be replaced with the target\r\naddress. This allows a shellcode template to exist before the address is known. The shellcode will have to specify\r\na string path name for the shared library it is loading and will likely use a call; pop trick to get the address of\r\nthe string, which is another dead give away since the code generated by the compiler would be using either a hard\r\ncoded address or IP-relative addressing to reference a string.\r\nShellcode before its patched with target address\r\nhttps://web.archive.org/web/20210205211142/https://backtrace.io/blog/backtrace/elf-shared-library-injection-forensics/\r\nPage 9 of 12\n\njmp B\r\nA:\r\npop %rdi ; pop the address of string into first argument\r\nmov $0x80000000, %rsi ; RTLD_DLOPEN flag\r\nmovabs $0x000000000000, %rcx ; will contain the address of __libc_dlopen_mode\r\ncall *%rcx\r\nB:\r\ncall A ; will push the address of string onto the stack\r\n.string \"evil_lib.so\"\r\nThe main thing to be aware of is that a suspicious call to dlopen will be one that doesn’t go through the PLT.\r\nAn example of backtrace detecting an injected shared object\r\nSaruman PIE injection\r\nThe Saruman tool is a prototype which allows the user to inject a PIE executable into an existing process address\r\nspace using thread injection. That is to say that a user can inject an entire dynamic linked PIE executable into an\r\nexisting process, and it will run concurrently alongside the existing process from a spawned thread. This is an anti-forensics technique that would typically be difficult to detect. Using the techniques that have been discussed\r\nthroughout this blog post we are able to quickly identify the injected executable object.\r\nNOTE: Remember that PIE executables are ET_DYN (dynamic objects) just like shared libraries, so the\r\ntechniques discussed will also find injected PIE executables since they are essentially the same thing as\r\nshared object files.*\r\nIn our example we will inject a remote backdoor called ./backdoor into an existing process called ./host\r\nwhich simply printf's a string \"I am a host\" in a loop. We will then demonstrate after injection that the\r\nremote backdoor is available for accepting login, and commands from the attacker.\r\nTerminal 1 (Start the host)\r\n$ ./host\r\nI am a host\r\nI am a host\r\nI am a host\r\nI am a host\r\nI am a host\r\nTerminal 2 (Inject our PIE executable backdoor)\r\n# ./saruman `pidof host` ./backdoor\r\n[+] Thread injection succeeded, tid: 3425\r\nhttps://web.archive.org/web/20210205211142/https://backtrace.io/blog/backtrace/elf-shared-library-injection-forensics/\r\nPage 10 of 12\n\n[+] Saruman successfully injected program: ./backdoor\r\n[+] PT_DETACHED -\u003e 3419\r\nTerminal 3 (Observe backdoor)\r\nNotice that there are two pid’s for ./host, one for the original program and now another for the injected thread that\r\nis the backdoor program.\r\n$ ps auxw | grep './host'\r\nelfmast+ 3419 100 0.0 6504 1332 pts/2 R+ 12:35 2:22 ./host\r\nelfmast+ 3425 0.0 0.0 6504 1332 pts/2 S+ 12:35 0:00 ./host\r\nTelnet to port 31337 to connect to the backdoor program that is running stealth within the apparent ./host\r\naddress space.\r\n$ telnet localhost 31337\r\nTrying 127.0.0.1...\r\nConnected to localhost.\r\nEscape character is '^]'.\r\nPassword: password Welcome to the Backdoor server! Type ‘HELP’ for a list of commands\r\ncommand:~#\r\nUsing Backtrace technology to detect the infected process\r\n$ ptrace --module=security:enable,true `pidof host`\r\n/home/elfmaster/host.34444.1460662985.btt\r\n$ hydra host.3444.1460662985.btt\r\nhttps://web.archive.org/web/20210205211142/https://backtrace.io/blog/backtrace/elf-shared-library-injection-forensics/\r\nPage 11 of 12\n\nNotice in the bottom pane where it says “Unknown shared object”. It has identified the parasite code within the\r\nprocess. As a side note, you may see in the second pane, that the syscall accept is present since we had just\r\nlogged into the backdoor with telnet.\r\nSummary\r\nIn this blog post we covered the details of shared library injection. Stay tuned for future posts describing some of\r\nthe other exploitation and malware techniques used in UNIX-flavor operating systems and how Backtrace detects\r\nthem.\r\nEdits\r\n04/27/2016 – Updated the article to reflect the information given to me by Shawn Webb, that the dlopen\r\nfunction in libc.so is a fake, which aims to foil attackers who are trying to use it for shared library\r\ninjection.\r\n04/27/2016 – Added credit and a link to the classic 2003 phrack paper titled “Cerberus ELF interface” by\r\nMayhem. This article covers some of the most innovative methods for ELF infection that have been\r\nexplored to date.\r\nSource: https://web.archive.org/web/20210205211142/https://backtrace.io/blog/backtrace/elf-shared-library-injection-forensics/\r\nhttps://web.archive.org/web/20210205211142/https://backtrace.io/blog/backtrace/elf-shared-library-injection-forensics/\r\nPage 12 of 12",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"references": [
		"https://web.archive.org/web/20210205211142/https://backtrace.io/blog/backtrace/elf-shared-library-injection-forensics/"
	],
	"report_names": [
		"elf-shared-library-injection-forensics"
	],
	"threat_actors": [],
	"ts_created_at": 1775490793,
	"ts_updated_at": 1775791329,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/1d62314c2ca52f8b3a516a51fc786308c6bfc987.pdf",
		"text": "https://archive.orkl.eu/1d62314c2ca52f8b3a516a51fc786308c6bfc987.txt",
		"img": "https://archive.orkl.eu/1d62314c2ca52f8b3a516a51fc786308c6bfc987.jpg"
	}
}