Make a Shared Library Do Something Different
LD_PRELOAD
is an environment variable
that allows to load a shared library before any other libraries when a
program starts. This gives us the ability to override standard functions
with custom implementations without modifiying the original program.
LD_DEBUG
is an environment varibale
used to trace and debug the dynamic linker during program startup.
LD_PRELOAD="lib_01.so" program
LD_PRELOAD="lib_01.so:lib_02.so" program
LD_DEBUG=all LD_PRELOAD="lib_01.so:lib_02.so" program
man ld.so
With LD_PRELOAD, we can monitor standard library function calls by add tracing or simulate rare errors - like making a socket call to return EAGAIN.
In this section, we will override the malloc function. We print a message each time malloc is called.
#include <stdlib.h>
int main(int argc, char** argv)
{
char *p = malloc(8);
(p);
free
return 0;
}
$ gcc -o main main.c
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
static void* (*real_malloc)(size_t) = NULL;
void* malloc(size_t size)
{
if(real_malloc == NULL)
{
= dlsym(RTLD_NEXT, "malloc");
real_malloc
if (NULL == real_malloc)
{
(stderr, "dlsym failed: %s\n", dlerror());
fprintf}
}
(stderr, "malloc(%ld) = ", size);
fprintfvoid *p = real_malloc(size);
(stderr, "%p\n", p);
fprintfreturn p;
}
$ gcc -o mymalloc.so -fPIC -shared mymalloc.c -ldl
$ ./main
$ LD_PRELOAD=./mymalloc.so ./main
malloc(8) = 0x593c4106e2a0
$ LD_DEBUG=all LD_PRELOAD=./mymalloc.so ./main
file=./mymalloc.so [0]; needed by ./main [0]
file=./mymalloc.so [0]; generating link map
dynamic: 0x00007272bc74cdf8 base: 0x00007272bc749000 size: 0x0000000000004030
entry: 0x00007272bc749000 phdr: 0x00007272bc749040 phnum: 11
file=libc.so.6 [0]; needed by ./main [0]
file=libc.so.6 [0]; generating link map
dynamic: 0x00007272bc602940 base: 0x00007272bc400000 size: 0x0000000000211d90
entry: 0x00007272bc42a390 phdr: 0x00007272bc400040 phnum: 14
relocation processing: /lib/x86_64-linux-gnu/libc.so.6
symbol=malloc; lookup in file=./main [0]
symbol=malloc; lookup in file=./mymalloc.so [0]
binding file /lib/x86_64-linux-gnu/libc.so.6 [0] to ./mymalloc.so [0]: normal symbol `malloc' [GLIBC_2.2.5]
relocation processing: ./mymalloc.so (lazy)
symbol=malloc; lookup in file=./main [0]
symbol=malloc; lookup in file=./mymalloc.so [0]
binding file ./main [0] to ./mymalloc.so [0]: normal symbol `malloc' [GLIBC_2.2.5]
calling init: /lib/x86_64-linux-gnu/libc.so.6
calling init: ./mymalloc.so
initialize program: ./main
transferring control: ./main
malloc(8) = 0x60827a8082a0
In this section we will override the sendmsg function to simulate socket error. We get the error code from environment variable.
import socket
= 12345
PORT = 1024
BUFFER_SIZE
= socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock "0.0.0.0", PORT))
sock.bind((
print(f"UDP server listening on port {PORT}...")
while True:
= sock.recvfrom(BUFFER_SIZE)
data, addr print(f"Received {len(data)} bytes from {addr}: {data.decode()}")
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
int main()
{
int sockfd;
struct sockaddr_in dest_addr;
const char *msg = "Hello via sendmsg";
= socket(AF_INET, SOCK_DGRAM, 0);
sockfd if (sockfd < 0)
{
("socket");
perrorreturn 1;
}
(&dest_addr, 0, sizeof(dest_addr));
memset.sin_family = AF_INET;
dest_addr.sin_port = htons(12345);
dest_addr(AF_INET, "127.0.0.1", &dest_addr.sin_addr);
inet_pton
struct iovec iov;
.iov_base = (void *)msg;
iov.iov_len = strlen(msg);
iov
struct msghdr msgh;
(&msgh, 0, sizeof(msgh));
memset.msg_name = &dest_addr;
msgh.msg_namelen = sizeof(dest_addr);
msgh.msg_iov = &iov;
msgh.msg_iovlen = 1;
msgh
ssize_t sent = sendmsg(sockfd, &msgh, 0);
if (sent < 0)
{
(stderr, "Send failed, errno=%d: %s\n", errno, strerror(errno));
fprintf(sockfd);
closereturn 1;
}
("Sent %ld bytes\n", sent);
printf(sockfd);
closereturn 0;
}
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <dlfcn.h>
#include <errno.h>
#include <stdlib.h>
static ssize_t (*real_sendmsg)(int sockfd, const struct msghdr *msg, int flags) = NULL;
static int get_errno_from_env(void)
{
const char *val = getenv("MY_ERRNO");
return val ? atoi(val) : 0;
}
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags)
{
if (!real_sendmsg)
{
= dlsym(RTLD_NEXT, "sendmsg");
real_sendmsg if (!real_sendmsg)
{
(stderr, "Error in dlsym(sendmsg): %s\n", dlerror());
fprintf= EIO;
errno return -1;
}
}
int val = get_errno_from_env();
if (val != 0)
{
(stderr, "sendmsg: MY_ERRNO=%d\n", val);
fprintf= val;
errno return -1;
}
return real_sendmsg(sockfd, msg, flags);
}
$ gcc -o client client.c
$ gcc -shared -fPIC -o mysendmsg.so mysendmsg.c -ldl
$ python3 server.py
$ ./client
Sent 17 bytes
$ MY_ERRNO=103 LD_PRELOAD=./mysendmsg.so ./client
sendmsg: MY_ERRNO=103
Send failed, errno=103: Software caused connection abort