Skip to content

Instantly share code, notes, and snippets.

@andyrudoff
Last active May 14, 2022 03:02
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andyrudoff/aa37894bad22baf7ccaa to your computer and use it in GitHub Desktop.
Save andyrudoff/aa37894bad22baf7ccaa to your computer and use it in GitHub Desktop.
interpose on libc syscalls by code patching
*.o
tester
elmo.so
This is a quickie example of how to interpose on libc syscalls.
Example is x86_64 only.
To run: make test
/*
* elmo.c -- (e)xecution (l)ocation (mo)difier
*
* Force load this using LD_PRELOAD.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <err.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/reg.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <sys/uio.h>
/*
* elmo_write -- this gets control when libc's write() is called
*
* If you print something from this routine, you'll get infinite recursion.
*/
static ssize_t
elmo_write(int fd, const void *buf, size_t count)
{
return syscall(SYS_write, fd, buf, count);
}
/*
* elmo_read -- this gets control when libc's read() is called
*/
static ssize_t
elmo_read(int fd, void *buf, size_t count)
{
ssize_t retval;
fprintf(stderr, "elmo_read(%d, %p, %zu)", fd, buf, count);
retval = syscall(SYS_read, fd, buf, count);
fprintf(stderr, " = %zu\n", retval);
return retval;
}
/*
* patch -- hot patch the code at "from" to jump to "to"
*
* This is x86_64 specific.
*
* The code at "from" is overwritten to contain a jump instruction to
* the new routine at "to". The patched routine is destroyed -- you never
* jump back to it. Instead, the new routine is expected to use syscall(2)
* to perform the function of the old routine as necessary.
*
* The mprotect() call could be optimized not to change protections more
* than once on the same page.
*/
static void
patch(void *from, void *to)
{
unsigned char *p = (unsigned char *)from;
unsigned long long pgbegin = (unsigned long long)from & PAGE_MASK;
unsigned long long pgend = ((unsigned long long)from + 12) & PAGE_MASK;
/* allow writes to the normally read-only code pages */
if (mprotect((void *) pgbegin, pgend - pgbegin + PAGE_SIZE,
PROT_READ|PROT_WRITE|PROT_EXEC) < 0)
err(1, "elmo mprotect");
p[0] = 0x49; /* movabs */
p[1] = 0xbb; /* %r11 */
memcpy(&p[2], &to, 8); /* 64-bit abs value */
p[10] = 0x41; /* jmp */
p[11] = 0xff; /* jmp */
p[12] = 0xe3; /* *%r11 */
}
/*
* elmo_constructor -- constructor for elmo library
*
* Called automatically by the run-time loader.
*/
__attribute__((constructor))
static void
elmo_constructor(void)
{
patch((void *) write, (void *) elmo_write);
patch((void *) read, (void *) elmo_read);
}
#
# Makefile for elmo
#
CFLAGS = -std=gnu99 -Wall -Werror -fPIC
all: elmo.so tester
tester: tester.o
$(CC) -o tester tester.o
elmo.so: elmo.o
$(CC) -Wl,-z,relro -shared -Wl,-soname,elmo.so -o elmo.so elmo.o
test: all
LD_PRELOAD=elmo.so LD_LIBRARY_PATH=. ./tester
clobber: clean
rm -f elmo.so tester
clean:
rm -f *.o core a.out
/*
* trivial program for testing
*/
#include <stdio.h>
#include <stdlib.h>
int
main(int argc, char *argv[])
{
printf("hello from main.\n");
FILE *fp = fopen("/etc/passwd", "r");
char buf[8192];
fgets(buf, 8192, fp);
printf("read line from /etc/passwd: %s", buf);
fclose(fp);
exit(0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment