Create a gist now

Instantly share code, notes, and snippets.

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <dlfcn.h>
#include <errno.h>
#include <sys/mman.h>
#include <unistd.h>
#if 0
#define HOOK_DBG(fmt, args...) fprintf(stderr, fmt, ##args)
#define HOOK_DBG(fmt, args...) do {} while (0)
#ifndef __x86_64__
#error "Hmm, this is implemented only for __x86_64__"
#define JMPQ_ARG_MASK ((1ULL << 32ULL) - 1ULL)
static size_t* findFunctionGotEntry(void *plt) {
size_t got_offset = 0;
size_t *got_entry = 0;
HOOK_DBG("DLS %llx\n", plt);
got_offset = *((size_t*)(2 + ((char*)plt)));
got_offset &= JMPQ_ARG_MASK;
got_entry = (size_t*)(((char*)plt) + got_offset + JMPQ_INSN_LEN);
HOOK_DBG("GOT %s %llx\n", got_entry);
return got_entry;
static int setProtection(void *ptr, bool writable) {
size_t PAGE_SIZE = sysconf(_SC_PAGESIZE);
size_t PAGE_MASK = PAGE_SIZE - 1;
void *page_ptr = (void*)(((size_t)ptr) & ~PAGE_MASK);
if (mprotect(page_ptr, PAGE_SIZE,
PROT_READ | (writable ? PROT_WRITE : 0)) < 0)
HOOK_DBG("failed to mprotect %p, error %s\n", ptr, strerror(errno));
return -1;
return 0;
/* This function accepts the PLT pointer
* the reason is that assignment from the function name
* like "void* = (void*)read" will always return the PLT address
* while dlsym may return the already resolved function address
static int swapFunctionsImpl(void *f1_plt, void *f2_plt, const char *f1, const char *f2) {
int ret = -1;
void *f1_real = 0;
void *f2_real = 0;
size_t* f1_got = 0;
size_t* f2_got = 0;
/* This calls the resolver and returns the function pointer
* if we look at the GOT before calling the function or doing a dlsym(RTLD_NEXT),
* it will contain the address of the PLT + JMPQ_INSN_LEN to force a jump
* to the resolver
f1_real = dlsym(RTLD_NEXT, f1);
if (!f1_real) {
HOOK_DBG("failed to resolve %s, error %s\n", f1, strerror(errno));
goto fail_dlsym;
f2_real = dlsym(RTLD_NEXT, f2);
if (!f2_real) {
HOOK_DBG("failed to resolve %s, error %s\n", f2, strerror(errno));
goto fail_dlsym;
f1_got = findFunctionGotEntry(f1_plt);
if (!f1_got) {
goto fail_dlsym;
f2_got = findFunctionGotEntry(f2_plt);
if (!f2_got) {
goto fail_dlsym;
if (setProtection(f1_got, true)) {
goto fail_prot_rw;
if (setProtection(f2_got, true)) {
goto fail_prot_rw;
f1_got, f2_got, f1_real, f2_real);
*f1_got = ((size_t)f2_real);
*f2_got = ((size_t)f1_real);
//unfortunately, we cannot get the protection flags to restore them later
//and PLT interferes with __do_global_dtors_aux() which causes a segfault
//if we mprotect the memory as read-only. Maybe we should remove write
//access here but explicitely restore it for libc functions
//setProtection(f1_got, false);
//setProtection(f2_got, false);
return ret;
#define swapFunctions(a, b) swapFunctionsImpl((void*)a, (void*)b, #a, #b)
int main() {
swapFunctions(read, write);
char *msg = "foo\n";
read(1, msg, 4);
swapFunctions(read, write);
return 0;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment