Last active
May 19, 2021 18:21
-
-
Save jakobrs/cc311aaceffec348dfea6b69386659ee to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Compile with: gcc ./dlopen_from.c -masm=intel -shared -O3 -o libdlopen_from.so -ldl | |
#define _GNU_SOURCE | |
#include <sys/types.h> | |
#include <sys/mman.h> | |
#include <sys/socket.h> | |
#include <signal.h> | |
#include <unistd.h> | |
#include <link.h> | |
#include <string.h> | |
#include <stdio.h> | |
#include <stdint.h> | |
#include <dlfcn.h> | |
static void *alt_object; | |
static struct link_map *alt_linkmap; | |
static void *(*alt_dlopen_from_child)(int socket, const char *file, int mode, void *address); | |
static int client_socket; | |
static void *dlopen_from_parent(int socket, int mode); | |
void *dlopen_from_child(int socket, const char *file, int mode, void *address); | |
void *dlopen_from(const char *file, int mode, void *address); | |
static void *dlopen_from_post(); | |
// Implementations | |
#define getaora() ((void **)(__builtin_frame_address(0) + 8)) | |
static void *dlopen_from_parent(int socket, int mode) { | |
int l_length; | |
if (read(socket, &l_length, sizeof(int)) == 0) return NULL; | |
char l_name[l_length]; | |
if (read(socket, l_name, l_length * sizeof(char)) == 0) return NULL; | |
close(socket); | |
return dlopen(l_name, mode); | |
} | |
__attribute__((noinline)) | |
void *dlopen_from_child(int socket, const char *file, int mode, void *address) { | |
client_socket = socket; | |
size_t address_offset = (size_t)address % getpagesize(); | |
if (mmap(address - address_offset, | |
address_offset + 0xd, | |
PROT_READ | PROT_WRITE | PROT_EXEC, | |
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, | |
0, 0) == MAP_FAILED) { | |
perror("mmap() failed"); | |
raise(SIGKILL); | |
} | |
// I couldn't find a way to save `rax` from being overwritten in | |
// dlopen_from_post below, so as a workaround instead of running this: | |
// mov rdi, &dlopen_from_post | |
// call rdi | |
// I run this: | |
// push rax | |
// mov rdi, &dlopen_from_post | |
// jmp rdi | |
// So that rax is stored where the return address should be. | |
*((uint8_t *)(address + 0x0)) = 0x50; // push rax | |
*((uint16_t *)(address + 0x1)) = 0xbf48; // mov rdi, ... | |
*((void **)(address + 0x3)) = &dlopen_from_post; | |
*((uint16_t *)(address + 0xb)) = 0xe7ff; // jmp rdi | |
*getaora() = address; | |
return dlopen(file, mode); | |
} | |
// Like dlopen, but with an extra parameter specifying the "real" caller. | |
void *dlopen_from(const char *file, int mode, void *address) { | |
int switch_to_alt = 1; | |
Dl_info info; | |
if (alt_object == NULL) { | |
if (dladdr(&dlopen_from, &info) == 0) { | |
printf("dladdr() failed\n"); | |
return NULL; | |
} | |
alt_object = dlmopen(LM_ID_NEWLM, info.dli_fname, RTLD_NOW); | |
if (alt_object == NULL) { | |
printf("Failed to dlmopen() libdlopen_from.so into new namespace\n"); | |
return NULL; | |
} | |
alt_dlopen_from_child = dlsym(alt_object, "dlopen_from_child"); | |
dladdr1(alt_dlopen_from_child, &info, (void **)&alt_linkmap, RTLD_DL_LINKMAP); | |
if (alt_linkmap == NULL) { | |
printf("Failed to get link_map structure for alt_object\n"); | |
return NULL; | |
} | |
} | |
void *address_aligned = address - ((size_t)address % getpagesize()); | |
struct link_map *address_linkmap; | |
dladdr1(address_aligned, &info, (void **)&address_linkmap, RTLD_DL_LINKMAP); | |
struct link_map *current_linkmap = alt_linkmap; | |
while (current_linkmap != NULL) { | |
if (current_linkmap == address_linkmap) { | |
switch_to_alt = 0; | |
} | |
current_linkmap = current_linkmap->l_next; | |
} | |
int sv[2]; | |
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) != 0) { | |
perror("Error creating socket pair"); | |
return NULL; | |
} | |
int fork_result = fork(); | |
if (fork_result == -1) { | |
printf("fork() failed"); | |
return NULL; | |
} else if (fork_result > 0) { | |
close(sv[1]); | |
return dlopen_from_parent(sv[0], mode); | |
} else { | |
close(sv[0]); | |
if (switch_to_alt) { | |
return alt_dlopen_from_child(sv[1], file, mode, address); | |
} else { | |
return dlopen_from_child(sv[1], file, mode, address); | |
} | |
} | |
} | |
static void *dlopen_from_post() { | |
void *rax = __builtin_extract_return_addr(__builtin_return_address(0)); | |
if (rax == NULL) raise(SIGKILL); | |
struct link_map *a; | |
if (dlinfo(rax, RTLD_DI_LINKMAP, &a)) { | |
perror("dlinfo failed"); | |
raise(SIGKILL); | |
} | |
if (a == NULL) { | |
perror("dlinfo returned NULL"); | |
raise(SIGKILL); | |
} | |
const char *l_name = a->l_name; | |
int l_length = strlen(l_name) + 1; | |
if (write(client_socket, &l_length, sizeof(int)) == -1) { | |
perror("write()ing l_length failed"); | |
raise(SIGKILL); | |
} else if (write(client_socket, l_name, l_length * sizeof(char)) == -1) { | |
perror("write()ing l_name failed"); | |
raise(SIGKILL); | |
} | |
close(client_socket); | |
dlclose(rax); | |
// We can't even return anyway | |
raise(SIGKILL); | |
__builtin_unreachable(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment