#include <assert.h>
#include <stdbool.h>
#include <elf.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <inttypes.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
#include <sys/mman.h>
#include <stdarg.h>
#include <sys/auxv.h>
#include "intercept.h"
#include "intercept_log.h"
#include "intercept_util.h"
#include "libsyscall_intercept_hook_point.h"
#include "disasm_wrapper.h"
#include "magic_syscalls.h"
int (*intercept_hook_point)(long syscall_number,
long arg0, long arg1,
long arg2, long arg3,
long arg4, long arg5,
long *result)
__attribute__((visibility("default")));
void (*intercept_hook_point_clone_child)(void)
__attribute__((visibility("default")));
void (*intercept_hook_point_clone_parent)(long)
__attribute__((visibility("default")));
bool debug_dumps_on;
void
debug_dump(const char *fmt, ...)
{
int len;
va_list ap;
if (!debug_dumps_on)
return;
va_start(ap, fmt);
len = vsnprintf(NULL, 0, fmt, ap);
va_end(ap);
if (len <= 0)
return;
char buf[len + 1];
va_start(ap, fmt);
len = vsprintf(buf, fmt, ap);
va_end(ap);
syscall_no_intercept(SYS_write, 2, buf, len);
}
static void log_header(void);
void __attribute__((noreturn)) xlongjmp(long rip, long rsp, long rax);
struct context {
struct patch_desc *patch_desc;
long rip;
long r15;
long r14;
long r13;
long r12;
long r10;
long r9;
long r8;
long rsp;
long rbp;
long rdi;
long rsi;
long rbx;
long rdx;
long rax;
char padd[0x200 - 0x168];
long SIMD[16][8];
};
struct wrapper_ret {
long rax;
long rdx;
};
static bool patch_all_objs;
static struct intercept_desc *objs;
static unsigned objs_count;
static bool libc_found;
static void *vdso_addr;
static struct intercept_desc *
allocate_next_obj_desc(void)
{
if (objs_count == 0)
objs = xmmap_anon(sizeof(objs[0]));
else
objs = xmremap(objs, objs_count * sizeof(objs[0]),
(objs_count + 1) * sizeof(objs[0]));
++objs_count;
return objs + objs_count - 1;
}
static const char *
get_lib_short_name(const char *name)
{
const char *slash = strrchr(name, '/');
if (slash != NULL)
name = slash + 1;
return name;
}
static bool
str_match(const char *name, size_t name_len,
const char *expected)
{
return name_len == strlen(expected) &&
strncmp(name, expected, name_len) == 0;
}
static const char *
get_name_from_proc_maps(uintptr_t addr)
{
static char paths[0x10000];
static char *next_path = paths;
const char *path = NULL;
char line[0x2000];
FILE *maps;
if ((next_path >= paths + sizeof(paths) - sizeof(line)))
return NULL;
if ((maps = fopen("/proc/self/maps", "r")) == NULL)
return NULL;
while ((fgets(line, sizeof(line), maps)) != NULL) {
unsigned char *start;
unsigned char *end;
if (sscanf(line, "%p-%p %*s %*x %*x:%*x %*u %s",
(void **)&start, (void **)&end, next_path) != 3)
continue;
if (addr < (uintptr_t)start)
break;
if ((uintptr_t)start <= addr && addr < (uintptr_t)end) {
path = next_path;
next_path += strlen(next_path) + 1;
break;
}
}
fclose(maps);
return path;
}
static uintptr_t
get_any_used_vaddr(const struct dl_phdr_info *info)
{
const Elf64_Phdr *pheaders = info->dlpi_phdr;
for (Elf64_Word i = 0; i < info->dlpi_phnum; ++i) {
if (pheaders[i].p_type == PT_LOAD && pheaders[i].p_memsz != 0)
return info->dlpi_addr + pheaders[i].p_vaddr;
}
return 0;
}
static const char *
get_object_path(const struct dl_phdr_info *info)
{
if (info->dlpi_name != NULL && info->dlpi_name[0] != '\0') {
return info->dlpi_name;
} else {
uintptr_t addr = get_any_used_vaddr(info);
if (addr == 0)
return NULL;
return get_name_from_proc_maps(addr);
}
}
static bool
is_vdso(uintptr_t addr, const char *path)
{
return addr == (uintptr_t)vdso_addr || strstr(path, "vdso") != NULL;
}
static bool
should_patch_object(uintptr_t addr, const char *path)
{
static uintptr_t self_addr;
if (self_addr == 0) {
extern unsigned char intercept_asm_wrapper_tmpl[];
Dl_info self;
if (!dladdr((void *)&intercept_asm_wrapper_tmpl, &self))
xabort("self dladdr failure");
self_addr = (uintptr_t)self.dli_fbase;
}
static const char libc[] = "libc";
static const char pthr[] = "libpthread";
static const char caps[] = "libcapstone";
if (is_vdso(addr, path)) {
debug_dump(" - skipping: is_vdso\n");
return false;
}
const char *name = get_lib_short_name(path);
size_t len = strcspn(name, "-.");
if (len == 0)
return false;
if (addr == self_addr) {
debug_dump(" - skipping: matches self\n");
return false;
}
if (str_match(name, len, caps)) {
debug_dump(" - skipping: matches capstone\n");
return false;
}
if (str_match(name, len, libc)) {
debug_dump(" - libc found\n");
libc_found = true;
return true;
}
if (patch_all_objs)
return true;
if (str_match(name, len, pthr)) {
debug_dump(" - libpthread found\n");
return true;
}
debug_dump(" - skipping, patch_all_objs == false\n");
return false;
}
static int
analyze_object(struct dl_phdr_info *info, size_t size, void *data)
{
(void) data;
(void) size;
const char *path;
debug_dump("analyze_object called on \"%s\" at 0x%016" PRIxPTR "\n",
info->dlpi_name, info->dlpi_addr);
if ((path = get_object_path(info)) == NULL)
return 0;
debug_dump("analyze %s\n", path);
if (!should_patch_object(info->dlpi_addr, path))
return 0;
struct intercept_desc *patches = allocate_next_obj_desc();
patches->base_addr = (unsigned char *)info->dlpi_addr;
patches->path = path;
find_syscalls(patches);
return 0;
}
const char *cmdline;
static unsigned char asm_wrapper_space[0x100000];
static unsigned char *next_asm_wrapper_space = asm_wrapper_space + PAGE_SIZE;
static bool
is_asm_wrapper_space_full(void)
{
return next_asm_wrapper_space + asm_wrapper_tmpl_size + 256 >
asm_wrapper_space + sizeof(asm_wrapper_space);
}
void
mprotect_asm_wrappers(void)
{
mprotect_no_intercept(
round_down_address(asm_wrapper_space + PAGE_SIZE),
sizeof(asm_wrapper_space) - PAGE_SIZE,
PROT_READ | PROT_EXEC,
"mprotect_asm_wrappers PROT_READ | PROT_EXEC");
}
static __attribute__((constructor)) void
intercept(int argc, char **argv)
{
(void) argc;
cmdline = argv[0];
if (!syscall_hook_in_process_allowed())
return;
vdso_addr = (void *)(uintptr_t)getauxval(AT_SYSINFO_EHDR);
debug_dumps_on = getenv("INTERCEPT_DEBUG_DUMP") != NULL;
patch_all_objs = (getenv("INTERCEPT_ALL_OBJS") != NULL);
intercept_setup_log(getenv("INTERCEPT_LOG"),
getenv("INTERCEPT_LOG_TRUNC"));
log_header();
init_patcher();
dl_iterate_phdr(analyze_object, NULL);
if (!libc_found)
xabort("libc not found");
for (unsigned i = 0; i < objs_count; ++i) {
if (objs[i].count > 0 && is_asm_wrapper_space_full())
xabort("not enough space in asm_wrapper_space");
allocate_trampoline_table(objs + i);
create_patch_wrappers(objs + i, &next_asm_wrapper_space);
}
mprotect_asm_wrappers();
for (unsigned i = 0; i < objs_count; ++i)
activate_patches(objs + i);
}
static void
log_header(void)
{
static const char self_decoder[] =
"tempfile=$(mktemp) ; tempfile2=$(mktemp) ; "
"grep \"^/\" $0 | cut -d \" \" -f 1,2 | "
"sed \"s/^/addr2line -p -f -e /\" > $tempfile ; "
"{ echo ; . $tempfile ; echo ; } > $tempfile2 ; "
"paste $tempfile2 $0 ; exit 0\n";
intercept_log(self_decoder, sizeof(self_decoder) - 1);
}
void
xabort_errno(int error_code, const char *msg)
{
static const char main_msg[] = " libsyscall_intercept error\n";
if (msg != NULL) {
size_t len = 0;
while (msg[len] != '\0')
++len;
syscall_no_intercept(SYS_write, 2, msg, len);
}
if (error_code != 0) {
char buf[0x10];
size_t len = 1;
char *c = buf + sizeof(buf) - 1;
do {
*c-- = (error_code % 10) + '0';
++len;
error_code /= 10;
} while (error_code != 0);
*c = ' ';
syscall_no_intercept(SYS_write, 2, c, len);
}
syscall_no_intercept(SYS_write, 2, main_msg, sizeof(main_msg) - 1);
syscall_no_intercept(SYS_exit_group, 1);
__builtin_unreachable();
}
void
xabort(const char *msg)
{
xabort_errno(0, msg);
}
void
xabort_on_syserror(long syscall_result, const char *msg)
{
if (syscall_error_code(syscall_result) != 0)
xabort_errno(syscall_error_code(syscall_result), msg);
}
static void
get_syscall_in_context(struct context *context, struct syscall_desc *sys)
{
sys->nr = (int)context->rax;
sys->args[0] = context->rdi;
sys->args[1] = context->rsi;
sys->args[2] = context->rdx;
sys->args[3] = context->r10;
sys->args[4] = context->r8;
sys->args[5] = context->r9;
}
struct wrapper_ret
intercept_routine(struct context *context)
{
long result;
int forward_to_kernel = true;
struct syscall_desc desc;
struct patch_desc *patch = context->patch_desc;
get_syscall_in_context(context, &desc);
if (handle_magic_syscalls(&desc, &result) == 0)
return (struct wrapper_ret){.rax = result, .rdx = 1 };
intercept_log_syscall(patch, &desc, UNKNOWN, 0);
if (intercept_hook_point != NULL)
forward_to_kernel = intercept_hook_point(desc.nr,
desc.args[0],
desc.args[1],
desc.args[2],
desc.args[3],
desc.args[4],
desc.args[5],
&result);
if (desc.nr == SYS_vfork || desc.nr == SYS_rt_sigreturn) {
return (struct wrapper_ret){.rax = context->rax, .rdx = 0 };
}
if (forward_to_kernel) {
if (desc.nr == SYS_clone && desc.args[1] != 0)
return (struct wrapper_ret){
.rax = context->rax, .rdx = 2 };
else
result = syscall_no_intercept(desc.nr,
desc.args[0],
desc.args[1],
desc.args[2],
desc.args[3],
desc.args[4],
desc.args[5]);
}
intercept_log_syscall(patch, &desc, KNOWN, result);
return (struct wrapper_ret){ .rax = result, .rdx = 1 };
}
struct wrapper_ret
intercept_routine_post_clone(struct context *context)
{
if (context->rax == 0) {
if (intercept_hook_point_clone_child != NULL)
intercept_hook_point_clone_child();
} else {
if (intercept_hook_point_clone_parent != NULL)
intercept_hook_point_clone_parent(context->rax);
}
return (struct wrapper_ret){.rax = context->rax, .rdx = 1 };
}