Here is a working, very Linux-specific example.
First, you need an example program that uses memcpy(). Here is example.c I used for testing.
Code:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
int arg;
char *preload;
preload = getenv("LD_PRELOAD");
if (preload)
printf("LD_PRELOAD is set to \"%s\".\n", preload);
else
printf("LD_PRELOAD is unset.\n");
printf("The address of memcpy() is %p.\n", memcpy);
fflush(stdout);
for (arg = 1; arg < argc; arg++) {
char *buffer;
size_t length;
length = strlen(argv[arg]);
buffer = malloc(length + 1);
if (!buffer) {
fprintf(stderr, "Not enough memory.\n");
return 1;
}
memcpy(buffer, argv[arg], length);
buffer[length] = '\0';
printf("Copied \"%s\".\n", buffer);
fflush(stdout);
free(buffer);
}
return 0;
}
Next, you need the libmemcpy.c which provides an interposed memcpy():
Code:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <sys/types.h>
#include <errno.h>
extern char **environ;
extern ssize_t write(int, const void *, size_t);
static void init(void) __attribute__((constructor));
/* Backup memcpy() called prior to init().
*/
static void *backup_memcpy(void *dest, const void *src, size_t n)
{
const unsigned char *s = (const unsigned char *)src;
const unsigned char *const z = n + (const unsigned char *)src;
unsigned char *d = (unsigned char *)dest;
while (s < z)
*(d++) = *(s++);
return dest;
}
/* Actual memcpy() to call.
*/
static void *(*actual_memcpy)(void *, const void *, size_t) = backup_memcpy;
/* Library initialization function.
*/
static void init(void)
{
void *actual;
size_t n, i;
/* Obtain the pointer to the memcpy() we interposed. */
actual = dlsym(RTLD_NEXT, "memcpy");
/* Replace actual_memcpy, but only if actual
* is neither NULL nor backup_memcpy. */
if (actual && actual != backup_memcpy)
*(void **)&actual_memcpy = actual;
/* Remove LD_PRELOAD from environment.
* This has no functional effects, only hides
* the library from the application.
*/
n = 0;
while (environ[n])
n++;
i = 0;
while (i < n)
if (environ[i][0] == 'L' &&
environ[i][1] == 'D' &&
environ[i][2] == '_' &&
environ[i][3] == 'P' &&
environ[i][4] == 'R' &&
environ[i][5] == 'E' &&
environ[i][6] == 'L' &&
environ[i][7] == 'O' &&
environ[i][8] == 'A' &&
environ[i][9] == 'D' &&
environ[i][10] == '=') {
environ[i] = environ[--n];
environ[n] = (char *)0;
} else
i++;
/* Done. */
}
/* Interposed version of memcpy().
* For illustration, it attempts to write a . to standard error
* at every invocation.
*/
void *memcpy(void *dest, const void *src, size_t n)
{
int saved_errno;
/* Try writing a . to standard error, keeping errno intact. */
saved_errno = errno;
write(2, ".", 1);
errno = saved_errno;
/* Call the actual memcpy() function. */
return actual_memcpy(dest, src, n);
}
The above version removes the LD_PRELOAD environment variable (for testing whether there is a slowdown). It should have no effect, so you can freely omit the removal from the init() function if you wish.
For illustration, the interposed memcpy() above will (try to) write a . to standard error when called.
Finally, you need the libpreload.c which checks the executable file name, and chooses whether or not to interpose the current program with libmemcpy or not:
Code:
#include <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>
#include <errno.h>
/* init() will be run prior to application main().
*/
static void init(int, char *[], char *[]) __attribute__((constructor));
/* This function will return the path to the current executable.
*/
static char *executable_path(void)
{
size_t size = 4096;
char *buffer;
ssize_t n;
while (1) {
/* Allocate a new buffer. */
buffer = malloc(size);
if (!buffer) {
errno = ENOMEM;
return NULL;
}
n = readlink("/proc/self/exe", buffer, size);
/* Error? */
if (n == (ssize_t)-1) {
const int saved_errno = errno;
free(buffer);
errno = saved_errno;
return NULL;
}
/* Buffer was long enough? */
if (n < size) {
buffer[n] = '\0';
return buffer;
}
/* Free buffer, and retry with a bigger one. */
free(buffer);
size += 4096;
}
}
static void init(int argc, char *argv[], char *environ[])
{
int saved_errno;
char *executable;
int interpose = 1;
size_t n, i;
/* Save errno. It probably does not matter,
* but it is a good idea to be careful. */
saved_errno = errno;
/* First, remove LD_PRELOAD from environment.
* This has no functional effects, only hides
* the library from the application.
*/
n = 0;
while (environ[n])
n++;
i = 0;
while (i < n)
if (environ[i][0] == 'L' &&
environ[i][1] == 'D' &&
environ[i][2] == '_' &&
environ[i][3] == 'P' &&
environ[i][4] == 'R' &&
environ[i][5] == 'E' &&
environ[i][6] == 'L' &&
environ[i][7] == 'O' &&
environ[i][8] == 'A' &&
environ[i][9] == 'D' &&
environ[i][10] == '=') {
environ[i] = environ[--n];
environ[n] = NULL;
} else
i++;
/* Find out the path to the current executable. */
executable = executable_path();
if (executable) {
char *name = executable;
/* Set name to the last component. */
{
char *p = executable;
while (*p)
if (*(p++) == '/')
name = p;
}
/* If the name is "repeat", then clear the interpose flag. */
if (name[0] == 'r' &&
name[1] == 'e' &&
name[2] == 'p' &&
name[3] == 'e' &&
name[4] == 'a' &&
name[5] == 't' &&
name[6] == '\0')
interpose = 0;
free(executable);
}
/* Should we interpose for this executable? */
if (interpose) {
/* Add the preload to the environment.
* We removed it earlier, so this should be safe. */
environ[n] = "LD_PRELOAD=./libmemcpy.so.1";
environ[n+1] = NULL;
}
/* It is too late to dlopen() the library,
* but we can still re-execute the same binary,
* with the same arguments, just with a different
* environment. */
execve("/proc/self/exe", argv, environ);
/* Whoops, execution failed.
* Hide the LD_PRELOAD, but let the program proceed. */
if (interpose)
environ[n] = NULL;
/* Restore errno. */
errno = saved_errno;
}
In another thread I suggested using dlopen() to interpose the symbols to josymadamana. Unfortunately, that does not work, because the symbol resolution has already occurred when the init/constructor is called.
However, the solution is quite simple: re-execute the same binary, with the same command-line arguments, but with the environment (specifically, LD_PRELOAD) adjusted per the executable name.
The above libpreload.c always re-executes the binary, either with LD_PRELOAD=./libmemcpy.so.1 or if the binary file name is repeat, without LD_PRELOAD in the environment.
To compile the three files, I used
Code:
gcc -W -Wall -O3 example.c -o example
gcc -W -Wall -O3 example.c -o repeat
gcc -fPIC libmemcpy.c -ldl -shared -Wl,-soname,libmemcpy.so.1 -o libmemcpy.so.1
gcc -fPIC libpreload.c -ldl -shared -Wl,-soname,libpreload.so -o libpreload.so
Here are the outputs of test runs:
Code:
./repeat foobar
LD_PRELOAD is unset.
The address of memcpy() is 0x4006d0.
Copied "foobar".
./example foobar
LD_PRELOAD is unset.
The address of memcpy() is 0x4006d0.
Copied "foobar".
LD_PRELOAD=./libpreload.so ./repeat foobar
LD_PRELOAD is unset.
The address of memcpy() is 0x4006d0.
Copied "foobar".
LD_PRELOAD=./libpreload.so ./example foobar
LD_PRELOAD is unset.
The address of memcpy() is 0x4006d0.
.Copied "foobar".
Notice the . on the last line? That was output by the interposed memcpy() in libmemcpy.c.